Sei sulla pagina 1di 1106

O F F I C I A L

M I C R O S O F T

L E A R N I N G

P R O D U C T

10550A

Programming in Visual Basic with Microsoft Visual Studio 2010

ii

Programming in Visual Basic with Microsoft Visual Studio 2010

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. The names of manufacturers, products, or URLs are provided for informational purposes only and Microsoft makes no representations and warranties, either expressed, implied, or statutory, regarding these manufacturers or the use of the products with any Microsoft technologies. The inclusion of a manufacturer or product does not imply endorsement of Microsoft of the manufacturer or product. Links may be provided to third party sites. Such sites are not under the control of Microsoft and Microsoft is not responsible for the contents of any linked site or any link contained in a linked site, or any changes or updates to such sites. Microsoft is not responsible for webcasting or any other form of transmission received from any linked site. Microsoft is providing these links to you only as a convenience, and the inclusion of any link does not imply endorsement of Microsoft of the site or the products contained therein. 2011 Microsoft Corporation. All rights reserved. Microsoft, and Windows are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries. All other trademarks are property of their respective owners.

Product Number: 10550A Part Number: X17-53002 Released: 04/2011

Programming in Visual Basic with Microsoft Visual Studio 2010

iii

iv

Programming in Visual Basic with Microsoft Visual Studio 2010

Programming in Visual Basic with Microsoft Visual Studio 2010

vi

Programming in Visual Basic with Microsoft Visual Studio 2010

Programming in Visual Basic with Microsoft Visual Studio 2010

vii

viii

Programming in Visual Basic with Microsoft Visual Studio 2010

Programming in Visual Basic with Microsoft Visual Studio 2010

vii

Acknowledgements
Microsoft Learning would like to acknowledge and thank the following for their contribution towards developing this title. Their effort at various stages in the development has ensured that you have a good classroom experience.

Carsten ThomsenContent Developer


Carsten Thomsen is an independent consultant who has worked since 1990 as a software developer, analyst, architect, and author. He holds a number of Microsoft certifications, including MCTS and MCPD, in various tools and applications, including the Microsoft .NET Framework, Microsoft Visual Basic, Microsoft Visual C#, and Microsoft SharePoint Server. He works with architecture, research, analysis, development, and troubleshooting, and spends countless hours with the Windows operating system and many other Microsoft server products, including Hyper-V and SQL Server. Carsten also works with agile development, and is a certified ScrumMaster.

Rob WindsorTechnical Reviewer


Rob Windsor is a developer, trainer, writer, and Senior Consultant with ObjectSharp Consultinga Microsoft Gold Partner based in Toronto, Canada. He has over fifteen years of experience in developing rich-client and web applications with Delphi, VB, C#, and VB.NET. Rob is a member of the INETA Speakers Bureau and is a regular speaker at conferences, code camps, and user groups across North America and Europe. He regularly contributes articles and videos to MSDN, TechNet, and the Pluralsight On-Demand library and is the co-author of Professional Visual Basic 2010 and .NET for Wrox. Rob is President of the Toronto Visual Basic User Group and has been recognized as a Microsoft Most Valuable Professional for his involvement in the developer community.

viii

Programming in Visual Basic with Microsoft Visual Studio 2010

Contents
Module 1: Introducing Visual Basic and the .NET Framework
Lesson 1: Introduction to the .NET Framework 4 Lesson 2: Creating Projects Within Visual Studio 2010 Lesson 3: Writing a Visual Basic Application Lesson 4: Building a Graphical Application Lesson 5: Documenting an Application Lesson 6: Debugging Applications by Using Visual Studio 2010 Lab: Introducing Visual Basic and the .NET Framework 1-3 1-12 1-24 1-33 1-46 1-52 1-62

Module 2: Using Visual Basic Programming Constructs


Lesson 1: Declaring Variables and Assigning Values Lesson 2: Using Expressions and Operators Lesson 3: Creating and Using Arrays Lesson 4: Using Decision Statements Lesson 5: Using Iteration Statements Lab: Using Visual Basic Programming Constructs 2-3 2-18 2-28 2-37 2-47 2-59

Module 3: Declaring and Calling Methods


Lesson 1: Defining and Invoking Methods Lesson 2: Specifying Optional Parameters and Output Parameters Lab: Declaring and Calling Methods 3-3 3-23 3-29

Module 4: Handling Exceptions


Lesson 1: Handling Exceptions Lesson 2: Raising Exceptions Lab: Handling Exceptions 4-3 4-15 4-23

Module 5: Reading and Writing Files


Lesson 1: Accessing the File System Lesson 2: Reading and Writing Files by Using Streams Lab: Reading and Writing Files 5-3 5-23 5-38

Module 6: Creating New Types


Lesson 1: Creating and Using Enumerations Lesson 2: Creating and Using Classes Lesson 3: Creating and Using Structures Lesson 4: Comparing References to Values Lab: Creating New Types 6-3 6-11 6-28 6-34 6-44

Programming in Visual Basic with Microsoft Visual Studio 2010

ix

Module 7: Encapsulating Data and Methods


Lesson 1: Controlling Visibility of Type Members Lesson 2: Sharing Methods and Data Lab: Encapsulating Data and Methods 7-3 7-12 7-23

Module 8: Inheriting from Classes and Implementing Interfaces


Lesson 1: Using Inheritance to Define New Reference Types Lesson 2: Defining and Implementing Interfaces Lesson 3: Defining Abstract Classes Lab: Inheriting from Classes and Implementing Interfaces 8-3 8-19 8-29 8-37

Module 9: Managing the Lifetime of Objects and Controlling Resources


Lesson 1: Introduction to Garbage Collection Lesson 2: Managing Resources Lab: Managing the Lifetime of Objects and Controlling Resources 9-3 9-16 9-28

Module 10: Encapsulating Data and Defining Overloaded Operators


Lesson 1: Creating and Using Properties Lab A: Creating and Using Properties Lesson 2: Creating and Using Indexers Lab B: Creating and Using Indexers Lesson 3: Overloading Operators Lab C: Overloading Operators 10-3 10-19 10-28 10-35 10-43 10-57

Module 11: Decoupling Methods and Handling Events


Lesson 1: Declaring and Using Delegates Lesson 2: Using Lambda Expressions Lesson 3: Handling Events Lab: Decoupling Methods and Handling Events 11-3 11-9 11-16 11-28

Module 12: Using Collections and Building Generic Types


Lesson 1: Using Collections Lab A: Using Collections Lesson 2: Creating and Using Generic Types Lesson 3: Defining Generic Interfaces and Understanding Variance Lesson 4: Using Generic Methods and Delegates Lab B: Building Generic Types 12-3 12-16 12-22 12-33 12-37 12-47

Module 13: Building and Enumerating Custom Collection Classes


Lesson 1: Implementing a Custom Collection Class Lesson 2: Adding an Enumerator to a Custom Collection Class Lab: Building and Enumerating Custom Collection Classes 13-3 13-16 13-27

Programming in Visual Basic with Microsoft Visual Studio 2010

Module 14: Using LINQ to Query Data


Lesson 1: Using the LINQ Extension Methods and Query Operators Lesson 2: Building Dynamic LINQ Queries and Expressions Lab: Using LINQ to Query Data 14-3 14-24 14-37

Module 15: Integrating Visual Basic Code with Dynamic Languages and COM Components
Lesson 1: Integrating Visual Basic Code with Ruby and Python Lesson 2: Accessing COM Components from Visual Basic Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components 15-3 15-14 15-24

Lab Answer Keys

About This Course

xi

About This Course


This section provides you with a brief description of the course, audience, suggested prerequisites, and course objectives.

Course Description
This course teaches you Visual Basic language syntax, program structure, and implementation by using Microsoft Visual Studio 2010 and the Microsoft .NET Framework 4. This course provides a solid foundation in Visual Basic to the level necessary to enable students to attend other courses in the Technical Specialist tracks.

Audience
This course is intended for experienced developers who already have programming experience in Visual Basic, C, C++, C#, or Java, and understand the concepts of Object Oriented Programming. These developers will be likely to develop enterprise business solutions. These professional developers will be attending the course so that they can quickly ramp up on Visual Basic Programming in the .NET Framework. The course focuses on Visual Basic program structure, language syntax, and implementation details with the .NET Framework 4.0. This course also focuses on new enhancement in the Visual Basic 2010 language using Visual Studio 2010.

Student Prerequisites
This course requires that you meet the following prerequisites: This course is targeted at developers who already have Visual Basic knowledge. This course is not for new developers; at least 12 months experience working with an Object Oriented language is expected. Creating classes Inheritance and abstraction Polymorphism Interfaces Delegates Events Exceptions

Experience with the Microsoft .NET Framework Knowledge of the Visual Studio integrated development environment (IDE).

Course Objectives
After completing this course, students will be able to: Describe the purpose of the .NET Framework, and explain how to use Microsoft Visual Basic and Visual Studio 2010 to build .NET Framework applications. Describe the syntax of basic Visual Basic programming constructs. Describe how to create and call methods. Describe how to catch, handle, and throw exceptions.

xii

About This Course

Describe how to perform basic file I/O operations in a Visual Basic application. Describe how to create and use new types (enumerations, classes, and structures), and explain the differences between reference types and value types. Describe how to control the visibility and lifetime of members in a type. Describe how to use inheritance to create new reference types. Describe how to manage the lifetime of objects and control the use of resources. Describe how to create properties and indexers to encapsulate data, and explain how to define operators for this data. Describe how to decouple an operation from the method that implements it, and explain how to use these decoupled operations to handle asynchronous events. Describe the purpose of collections, and explain how to use generics to implement type-safe collection classes, structures, interfaces, and methods. Describe how to implement custom collection classes that support enumeration. Describe how to query in-memory data by using Language-Integrated Query (LINQ) queries. Describe how to integrate code written by using a dynamic language such as Ruby and Python, or technologies such as Component Object Model (COM), into a Visual Basic application.

Course Outline
This section provides an outline of the course: Module 1, "Introducing Visual Basic and the .NET Framework," provides an overview of the .NET Framework and shows how you can start to build your own .NET Framework applications by using Visual Basic and Visual Studio 2010. Module 2, "Using Visual Basic Programming Constructs," provides an introduction to Visual Basic programming language syntax and introduces many of the basic Visual Basic language data types and programming constructs. Module 3, "Declaring and Calling Methods," introduces the concept of methods and describes how, in object-oriented languages such as Visual Basic, a method is a unit of code that is designed to perform a discrete piece of work. This module shows you how to declare and call methods by using Visual Basic. Module 4, "Handling Exceptions," introduces the importance of exception handling and explains why applications should be designed with exception handling in mind. This module explains how you can implement effective exception handling in your applications and describes how to use exceptions in your methods to indicate an error condition to the code that calls your methods. Module 5, "Reading and Writing Files," explains how the ability to access and manipulate files on the file system is a common requirement for many applications. This module shows you how to read and write to files by using the classes in the .NET Framework. It also describes the different approaches that you can take and explains how to read and write different formats of data. Module 6, "Creating New Types," explains how to build your own types that model items in the real world and describes how to implement the business logic for these items that your applications require. This module explains the differences between reference types and value types. Module 7, "Encapsulating Data and Methods," describes how to use the access modifiers that Visual Basic provides to enable you to implement encapsulation. This module also introduces the Shared access

About This Course

xiii

modifier, which enables you to define members that can be shared over multiple instances of the same type. Module 8, "Inheriting from Classes and Implementing Interfaces," explains that inheritance is a key concept in an object-oriented language and describes how you can use inheritance, interfaces, and abstract classes to develop object hierarchies. This module also explains how you can use these object hierarchies to help reduce bugs by defining clear contracts for the functionality that a class should expose and providing default implementations where you can sensibly abstract code into a base type. Module 9, "Managing the Lifetime of Objects and Controlling Resources," introduces the concept of resource management and discusses its importance. This module explains how the .NET Framework simplifies resource management by automatically reclaiming the resources for a managed object when an application no longer references it. This module also explains that the garbage collector does not control unmanaged resources and describes the steps that you can take to dispose of such resources. Module 10, "Encapsulating Data and Defining Overloaded Operators," introduces properties and indexers. These are elements of Visual Basic that enable you to encapsulate data and expose data appropriately and efficiently. This module also describes how to implement operators for your types by using overloading. Module 11, "Decoupling Methods and Handling Events," explains how to decouple an operation from the method that implements it and describes how to use anonymous methods to implement decoupled operations. This module also explains how to use events to inform consuming applications of a change or notable occurrence in a type. Module 12, "Using Collections and Building Generic Types," introduces the concept of collection classes and explains that you can use them with greater flexibility than a simple array. This module also introduces generics and explains how to use generic classes to maintain type integrity and avoid the issues that are associated with a lack of type safety. Module 13, "Building and Enumerating Custom Collection Classes," explains how to use the collection classes that the .NET Framework base class library includes. This module also describes how to build custom collection classes. Module 14, "Using LINQ to Query Data," explains how you can use LINQ to abstract the mechanism that an application uses to query data from the application code. This module describes built-in Visual Basic LINQ extension methods and LINQ query operators. This module also describes how to build LINQ queries dynamically by using expression trees. Module 15, "Integrating Visual Basic Code with Dynamic Languages and COM Components," explains how the .NET Framework 4 enables you to invoke code and components that were written by using other languages from your Visual Basic code. It describes how the dynamic language runtime (DLR) enables you to reuse code built by using a wide range of scripting languages, such as Ruby and Python. This module also describes how to invoke COM components from a Visual Basic application.

xiv

About This Course

Course Materials
The following materials are included with your kit: Course Handbook A succinct classroom learning guide that provides all the critical technical information in a crisp, tightly-focused format, which is just right for an effective in-class learning experience. Lessons: Guide you through the learning objectives and provide the key points that are critical to the success of the in-class learning experience. Labs: Provide a real-world, hands-on platform for you to apply the knowledge and skills learned in the module. Module Reviews and Takeaways: Provide improved on-the-job reference material to boost knowledge and skills retention. Lab Answer Keys: Provide step-by-step lab solution guidance at your finger tips when its needed.

Course Companion Content on the http://www.microsoft.com/learning/companionmoc/ Site: Searchable, easy-to-navigate digital content with integrated premium on-line resources designed to supplement the Course Handbook. Modules: Include companion content, such as questions and answers, detailed demo steps and additional reading links, for each lesson. Additionally, they include Lab Review questions and answers and Module Reviews and Takeaways sections, which contain the review questions and answers, best practices, common issues and troubleshooting tips with answers, and real-world issues and scenarios with answers. Resources: Include well-categorized additional resources that give you immediate access to the most up-to-date premium content on TechNet, MSDN, Microsoft Press

Student Course files on the http://www.microsoft.com/learning/companionmoc/ Site: Includes the Allfiles.exe, a self-extracting executable file that contains all the files required for the labs and demonstrations. Course evaluation At the end of the course, you will have the opportunity to complete an online evaluation to provide feedback on the course, training facility, and instructor. To provide additional comments or feedback on the course, send e-mail to support@mscourseware.com. To inquire about the Microsoft Certification Program, send e-mail to mcphelp@microsoft.com.

About This Course

xv

Virtual Machine Environment


This section provides the information for setting up the classroom environment to support the business scenario of the course.

Virtual Machine Configuration


In this course, you will use Windows Server 2008 with Hyper-V to perform the labs. The following table shows the role of each virtual machine used in this course. Virtual machine 10550A-GEN-DEV Role Windows Server 2008 R2 Client

Software Configuration
The following software is installed on each VM: Visual Studio 2010 Ultimate Edition IronRuby IronPython SandCastle HTML Help Workshop The 2010 Microsoft Office system

Course Files
There are files associated with the labs in this course. The lab files are located in the folder, D:\Labfiles\ on the student computers.

Classroom Setup
Each classroom computer will have the same virtual machine configured in the same way.

Course Hardware Level


To ensure a satisfactory student experience, Microsoft Learning requires a minimum equipment configuration for trainer and student computers in all Microsoft Certified Partner for Learning Solutions (CPLS) classrooms in which Official Microsoft Learning Product courseware are taught. This course requires that you have a computer that meets or exceeds hardware level 6, which prescribes the following: Intel Virtualization Technology (Intel VT) or AMD Virtualization (AMD-V) processor Dual 120-GB hard disks, 7,200 RM SATA or better (configured as a stripe array) 4 gigabytes (GB) of RAM, expandable to 8 GB or higher DVD drive Network adapter Super VGA (SVGA) 17-inch monitor Microsoft Mouse or compatible pointing device

xvi

About This Course

Sound card with amplified speakers

In addition, the instructor computer must be connected to a projection display device that supports SVGA 1024 768 pixels, 16-bit colors.

Introducing Visual Basic and the .NET Framework

1-1

Module 1
Introducing Visual Basic and the .NET Framework
Contents:
Lesson 1: Introduction to the .NET Framework 4 Lesson 2: Creating Projects Within Visual Studio 2010 Lesson 3: Writing a Visual Basic Application Lesson 4: Building a Graphical Application Lesson 5: Documenting an Application Lesson 6: Debugging Applications by Using Visual Studio 2010 Lab: Introducing Visual Basic and the .NET Framework 1-3 1-12 1-24 1-33 1-46 1-52 1-62

1-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

Microsoft Visual Studio 2010 and the.NET Framework 4 provide a comprehensive development platform that enables you to build, debug, deploy, and manage applications. This module describes the purpose of the .NET Framework 4 and how you can build applications by using Visual Studio 2010.

Module Objectives:
After completing this module, you will be able to: Explain the purpose of the .NET Framework 4. Create Visual Basic projects by using Visual Studio 2010. Explain the structure of a Visual Basic application. Use the Windows Presentation Foundation (WPF) Application template to build a simple graphical application. Use XML comments to document an application. Use the debugger to step through a program.

Introducing Visual Basic and the .NET Framework

1-3

Lesson 1

Introduction to the .NET Framework 4

This lesson introduces the .NET Framework 4 and describes the key concepts of .NET and .NET tools that help simplify development.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of the .NET Framework 4. Describe the role of Visual Basic for writing the code for .NET Framework 4 applications. Describe the purpose of an assembly. Explain how the common language runtime (CLR) compiles and runs assemblies. Describe the tools that the .NET Framework 4 provides.

1-4

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is the .NET Framework 4?

The .NET Framework 4 provides a comprehensive development platform that offers a fast and efficient way to build applications and services. Using Visual Studio 2010 and the .NET Framework 4, developers can create a wide range of solutions that operate across a range of computing devices. The .NET Framework 4 provides three principal elements: the CLR, the .NET Framework class library, and a collection of development frameworks.

Common Language Runtime


The .NET Framework 4 provides an environment called the CLR. The CLR manages code execution and simplifies the development process by providing a robust and a secure execution environment that provides common services, such as memory management, transactions, interprocess communications, and multithreading.

The .NET Framework Class Library


The .NET Framework 4 provides a library of reusable classes that developers can use to build applications. The classes provide common functionality and constructs that simplifies application development and removes the need for constant logic reinvention. For example, the System.IO.File class contains functionality that enables developers to manipulate files on the Windows file system. In addition to using the classes in the .NET Framework class library, you can extend these classes by creating your own libraries of classes.

Development Frameworks
The .NET Framework 4 provides several development frameworks that you can use to build common types of applications. These frameworks provide the necessary components and infrastructure to get you started. The development frameworks include: ASP.NET. Enables you to build server-side web applications. WPF. Enables you to build rich client applications.

Introducing Visual Basic and the .NET Framework

1-5

Windows Communication Foundation (WCF). Enables you to build secure and reliable service-oriented applications. Windows Workflow Foundation (WF). Enables you to build workflow solutions to fulfill the complex business requirements of modern organizations.

Question: What is the purpose of the .NET Framework 4 and the three main components it provides?

Additional Reading
For more information about the .NET Framework, see the Microsoft .NET page at http://go.microsoft.com/fwlink/?LinkId=192876

1-6

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is Visual Basic?

Visual Basic is the language of choice for many developers. Visual Basic uses an English-language like syntax, similar Pascal and has several extensions and features that are designed for operation with the .NET Framework. Because of English languagelike syntax, many new developers find Visual Basic easy to learn Visual Basic 2010 is an evolution of the Visual Basic language and including classic Visual Basic. Today, Visual Basic is engineered for Rapid Application Development (RAD), building type-safe and object-oriented applications. CLR runs executable code that is generated by using a compiler, such as the Visual Basic compiler. You can build applications for.NET Framework by using any language with a compiler that can generate executable code in a format that the CLR recognizes. Visual Studio 2010 provides compilers for C++, Visual C#, F#, and Visual Basic. Compilers for other languages from third-party vendors, including IronRuby and IronPython, are available for download from CodePlex. Visual Studio supports Visual Basic with a full-featured code editor, compiler, project templates, designers, code wizards, and other tools. It also provides a powerful and easy-to-use debugger. Visual Basic is also available from Microsoft as Visual Basic Express Edition, which provides a subset of the features that are provided with Visual Studio. Question: Which programming languages have you used?

Additional Reading
For more information about Visual Basic 2010, see the Visual Basic page at http://go.microsoft.com/fwlink/?LinkID=210573&clcid=0x409
For more information about the new features of Visual Basic2010, see the What's New in Visual Basic 2010 page at http://go.microsoft.com/fwlink/?LinkID=210575&clcid=0x409

Introducing Visual Basic and the .NET Framework

1-7

What Is an Assembly?

When you compile a Visual Basic application by using Visual Studio 2010, the compiler generates an executable file that the CLR can run. This file is called an assembly. An assembly contains code in an intermediate format called Microsoft intermediate language (MSIL). All compilers for the .NET Framework generate code in this format, regardless of the programming language that was used to write an application. This enables the CLR to run code in the same way, irrespective of the language that the developer used. Assemblies are the building blocks of .NET Framework applications; they form the fundamental unit of deployment, version control, reuse, and security. An assembly is a collection of types and resources that work together and form a logical unit of functionality. An assembly provides the CLR with the information that it needs to be aware of type implementations. An assembly can be of two types: an executable program or a library that contains MSIL code that other programs can reuse. Using a library, developers can modularize the development of their applications into logical components. When you distribute assemblies to customers as part of an application, you need to ensure that the assembly contains versioning information, and that the assembly is signed. Versioning the assembly is important because ultimately, any applications that you build will have multiple releases. Versioning information can help you identify which versions the customers already have and enable you to perform the necessary steps to upgrade the application. Similarly, versioning information can also help in documenting and fixing bugs. Signing your assemblies is equally important because it ensures that your assembly cannot easily be modified or replaced by an alternative implementation from a malicious source, and because it gives the assembly a strong name.

1-8

Programming in Visual Basic with Microsoft Visual Studio 2010

Information such as the assembly version and security identity is stored as metadata in an assembly manifest. The manifest also contains metadata that describes the scope of the assembly and any references to classes and resources. The manifest is typically stored in a portable executable (PE) file.

Assembly Versioning
Assembly version information is stored in the assembly manifest and is used with the assembly name and culture to derive the assemblys identity. An assembly version number consists of the following: Major version number Minor version number Build number Revision number

Assembly Signing
Assembly signing is an important step that developers should include in their build process because it provides the following benefits: It protects assemblies from modification. It enables you to include the signed assembly in the Global Assembly Cache (GAC), so you can share the assembly with multiple applications. It guarantees that the name of the assembly is unique. It provides the sign tool with the .NET Framework, so you can use the assembly-signing functionality in Visual Studio 2010.

Question: Why would you choose to distribute an assembly rather than distribute the source code?

Additional Reading
For more information about the purpose and features of assemblies, see the Assemblies in the Common Language Runtime page at http://go.microsoft.com/fwlink/?LinkId=192879
For more information about assembly versioning, see the Assembly Versioning page at http://go.microsoft.com/fwlink/?LinkId=192880
For more information about assembly signing, see the SignTool.exe (Sign Tool) page at http://go.microsoft.com/fwlink/?LinkId=192881

Introducing Visual Basic and the .NET Framework

1-9

How the CLR Loads, Compiles, and Runs Assemblies

Assemblies contain the MSIL code, which is not executable. When you run a .NET Framework application, the CLR loads the MSIL code from an assembly and converts it into the machine code that the computer requires. The CLR is a fundamental component of the .NET Framework. It handles code execution and provides useful services for application development. The CLR contains several components that perform the following tasks when you run a .NET Framework application: 1. 2. The Class Loader locates and loads all assemblies that the application requires. The assemblies will already be compiled into MSIL. The MSIL-to-native compiler verifies the MSIL code and then compiles all assemblies into machine code ready for execution.

Note: The CLR performs the verification step because it is possible to write your own MSIL code. If you use a Visual Basic compiler, the MSIL code will be valid, but the CLR cannot make any assumptions. 3. 4. The Code Manager loads the executable assembly and runs the startup object. The Garbage Collector provides automatic lifetime memory management of all objects that your application creates. The Garbage Collector disposes the objects that your application is no longer using. The Exception Manager provides structured exception handling for .NET applications, which is integrated with Windows-structured exception handling.

5.

Question: What steps does the CLR perform when you run your application?

1-10

Programming in Visual Basic with Microsoft Visual Studio 2010

What Tools Does the .NET Framework Provide?

The .NET Framework provides several tools to help simplify the development of .NET applications. The following table describes some of the key tools. Tool Certificate Creation Tool (Makecert.exe) Global Assembly Cache Tool (Gacutil.exe) Native Image Generator (Ngen.exe) Description Enables users to create x.509 certificates for use in their development environment. Typically, you can use these certificates to sign your assemblies and define Secure Sockets Layer (SSL) connections. Enables users to manipulate the assemblies in the GAC. This can include installing and uninstalling assemblies in the GAC so that multiple applications can access them. Enables users to improve the performance of .NET applications. The Native Image Generator improves performance by precompiling assemblies into images that contain processor-specific machine code. The CLR can then run the precompiled images instead of using just-intime (JIT) compilation. Alternatively, if you use JIT compilation, your code is compiled just before it is executed. Enables users to manipulate assemblies such as determining whether an assembly is managed or disassembling an assembly to view the compiled MSIL code. Enables users to sign assemblies with strong names. It includes commands to create a new key pair, extract a public key from a key pair, and verify assemblies.

MSIL Disassembler (Ildasm.exe) Strong Name Tool (Sn.exe)

Question: You have created two applications that both use an assembly named Contoso.ReportGenerator.dll. Both applications will run on the same machine. What is the best approach to share the Contoso.ReportGenerator.dll assembly and which tool should you use?

Introducing Visual Basic and the .NET Framework

1-11

Additional Reading
For more information about the tools that the .NET Framework provides, see the .NET Framework Tools page at http://go.microsoft.com/fwlink/?LinkId=192882

1-12

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 2

Creating Projects Within Visual Studio 2010

This lesson introduces you to Visual Studio 2010 and describes how Visual Studio 2010can help simplify the development of .NET applications by using predefined application templates and features of the integrated development environment (IDE).

Lesson Objectives:
After completing this lesson, you will be able to: Describe the features of Visual Studio 2010 that aid in programming productivity. Describe the various project types that Visual Studio 2010 supports. Describe the primary files that are found in most Visual Studio solutions. Explain how to create a console application by using the Console Application template in Visual Studio 2010. Use Visual Studio to compile and run an application.

Introducing Visual Basic and the .NET Framework

1-13

Key Features of Visual Studio 2010

Visual Studio 2010 presents a single development environment that enables you to rapidly design, implement, build, test, and deploy various types of applications and components by using a range of programming languages. Some of the key features of Visual Studio 2010 are: Intuitive integrated development environment. The Visual Studio 2010 IDE provides all of the features and tools that are necessary to design, implement, build, test, and deploy applications and components. Rapid application development. Visual Studio 2010 provides design views for graphical components that enable you to build complex user interfaces easily. Alternatively, you can use the Code Editor views, which provide more control. Visual Studio 2010 also provides wizards that help speed up the development of particular components. Server and data access. Visual Studio 2010 provides Server Explorer, which enables you to log on to servers and explore their databases and system services. It provides a familiar way to create, access, and modify databases that your application uses. Debugging features. Visual Studio 2010 provides a debugger, which enables you to step through local or remote code, pause at breakpoints, and follow execution paths. Error handling. Visual Studio 2010 provides the Error List window, which displays any errors, warnings, or messages that are produced as you edit and build your code. Help and documentation. Visual Studio 2010 also provides help and guidance through Microsoft IntelliSense, code snippets, and the integrated help system, which contains documentation and samples.

1-14

Programming in Visual Basic with Microsoft Visual Studio 2010

Question: What are the main reasons for choosing Visual Studio 2010 over a text editor such as Notepad++?

Introducing Visual Basic and the .NET Framework

1-15

Templates in Visual Studio 2010

Visual Studio 2010 supports the development of different types of applications such as Windows-based client applications, web-based applications, services, and libraries. To help you get started, Visual Studio 2010 provides several application templates that provide a structure for the different types of applications. These templates: Provide starter code that you can build on to create a functioning application. Include supporting components and controls that are relevant to the project type. Configure the Visual Studio 2010 IDE to the type of application that you are developing. Add references to any initial assemblies that this type of application usually requires.

Types of Templates
The following table describes some of the common application templates that you can use when you develop .NET Framework applications by using Visual Studio 2010. Template Console Application Description Provides environment settings, tools, project references, and starter code to develop an application that runs in a command-line interface. This type of application is considered lightweight compared to the Windows Forms application template because there is no graphical user interface. Provides environment settings, tools, project references, and starter code to build a rich graphical Windows application. A WPF application enables you to create the next generation of Windows applications, with much more control over user interface design, including direct editing of the markup such as when creating HTML pages. The markup is XML-based and aptly named XAML. Provides environment settings, tools, and starter code to build a .dll

WPF Application

Class Library

1-16

Programming in Visual Basic with Microsoft Visual Studio 2010

Template

Description assembly. You can use this type of file to store functionality that you might want to invoke from many other applications.

Windows Forms Application ASP.NET Web Application ASP.NET MVC Application

Provides environment settings, tools, project references, and starter code to build a graphical Windows Forms application. Provides environment settings, tools, project references, and starter code to create a server-side, compiled ASP.NET web application. Provides environment settings, tools, project references, and starter code to create a Model-View-Controller (MVC) web application. An ASP.NET MVC Web application differs from the standard ASP.NET Web application in that the application architecture helps you separate the presentation layer, business logic layer, and data access layer. Provides environment settings, tools, project references, and starter code to build a rich, graphical web application. Provides environment settings, tools, project references, and starter code to build Service Orientated Architecture (SOA) services.

Silverlight Application WCF Service Application

Question: What project templates would you use for each of the following? A client application that will run on a Windows-based computer. A library of functionality that you want to use in other applications. A website that you will host on an Internet Information Services (IIS) web server.

Introducing Visual Basic and the .NET Framework

1-17

Structure of Visual Studio Projects and Solutions

Visual Studio 2010 uses solutions and projects as conceptual containers to organize your source files during development. This simplifies the build and deployment process for your .NET Framework applications.

Visual Studio Projects


A project is used to organize source files, references, and project-level configuration settings that make up a single .NET Framework application or library. When you create a project in Visual Studio, the project is automatically organized into a solution. The following table describes some of the common file types that you will find in a Visual Studio project. File .vb Description Code files that can belong to a single project solution. This type of file can represent any of the following: Modules Windows Forms and Page code-behind files Class files Project files that can belong to multiple project solutions. The .vbproj file also stores settings for the project, such as the output path for the build output and the target platform. Files that represent ASP.NET web pages. An ASP.NET file can contain your Visual Basic code, or you can use an accompanying .aspx.vb file to store your code in addition to the page markup. Configuration files are XML-based files that you can use to store applicationlevel settings such as database connection strings, which you can then modify without recompiling your application.

.vbproj

.aspx

.config

1-18

Programming in Visual Basic with Microsoft Visual Studio 2010

File .xaml

Description XAML files are used in WPF and Microsoft Silverlight applications to define user interface elements.

Visual Studio Solutions


A single Visual Studio solution is a container for one or more projects. By default, when you create a new project, Visual Studio automatically creates a solution for the project. You can add additional projects to a solution. This is useful if, for example, you are building a library assembly and an application that tests the library. You can build and compile both projects as part of the same solution rather than having to run multiple instances of Visual Studio. A solution can also contain project-independent items that any of the projects in the solution can use. For example, an ASP.NET solution can contain a single cascading style sheet (.css) file that applies a standard look and feel to any of the included ASP.NET projects. Categorizing multiple projects into a single Visual Studio solution provides the following advantages: It enables you to work on multiple projects within a single Visual Studio 2010 session. It enables you to apply configuration settings globally to multiple projects. It enables you to deploy multiple projects within a single solution.

The following table describes the solution definition files. File .sln Description A Visual Studio 2010 solution file that provides a single point of access to multiple projects, project items, and solution items. The .sln file is a standard text file, but it is not a good practice to change it outside Visual Studio 2010. A solution user options file that stores any settings that you have changed to customize the Visual Studio 2010 IDE. This includes the startup project and debugging settings, but the file can be deleted with no harm to the content of the project(s) in the solution.

.suo

Question: What role does the .sln file play in Visual Studio solutions?

Introducing Visual Basic and the .NET Framework

1-19

Creating a .NET Framework Application

The application templates that Visual Studio 2010 provides enable you to start creating an application with minimal effort. You can then add your code and customize the project to meet your own requirements. The following steps describe how to create a console application.

Create a new console project by using the Console Application template in Visual Studio 2010
1. 2. 3. Open Visual Studio 2010. On the File menu, click New Project. In the New Project dialog box, specify the following settings for the project, and then click OK. a. b. c. d. In the Installed Templates list, under Visual Basic, click Windows. In the center pane, click Console Application. In the Name box, specify a name for the project. In the Location box, specify the path where you want to save the project.

Programmer Productivity Features


Visual Studio 2010 provides a host of features that can help you to write code. When writing code, developers need to recall information about many program elements. Instead of manually looking up information by searching help files or other source code, the IntelliSense feature in Visual Studio provides the information that developers need directly from the editor. IntelliSense provides the following features: Quick Info. This option displays the complete declaration for any identifier in your code. Position the mouse pointer over an identifier to display Quick Info for that identifier, which appears in a yellow pop-up window.

1-20

Programming in Visual Basic with Microsoft Visual Studio 2010

Complete Word. This option types the rest of a variable, command, or function name after you have entered enough characters to disambiguate the term. Type the first few letters of the name and then press Alt+Right Arrow or Ctrl+Spacebar to complete the word.

Often, when you are building a .NET Framework application, you will need to repeat common constructs in your code. Examples might be a loop or code to handle exceptions. Code snippets are designed to ease the burden of having to implement such common code by providing boilerplate code templates that can be readily inserted into your code and amended to suit your needs. You can access these code snippets by using the Code Snippet Picker. You can manage code snippets by using the Code Snippet Manager dialog box on the Tools menu. The Code Snippet Manager enables you to add new code snippets by specifying new folders that the Code Snippet Picker will look in for code snippets; by importing code snippets; or by searching for code snippets online. The Code Snippets Manager is also useful for discovering the shortcut key sequence that is associated with a code snippet. Finally, Visual Studio 2010 provides a host of other features on the shortcut menu that appears when you right-click a code statement. These include Create Unit Tests, Insert Snippet, Go To Definition, Find All References, and Outline. These features will be covered in more detail in the later modules. Question: What is the purpose of code snippets?

Introducing Visual Basic and the .NET Framework

1-21

Building and Running a .NET Framework Application

Visual Studio provides an integrated environment that enables you to quickly compile and run your applications. You can also build and run an application from the command line if you do not have Visual Studio available. The following steps describe how to build and run an application.

Build and run an application in Visual Studio 2010


The following steps assume that you have created a new console application. 1. 2. In Visual Studio 2010, on the Build menu, click Build Solution. On the Debug menu, click Start Debugging.

Build an application from the command line


The following steps assume that you have created a new console application named MyProject, which is saved in the C:\Users\Student\Documents \Visual Studio 2010\MyProject\ folder. 1. Click Start, point to All Programs, click Microsoft Visual Studio 2010, click Visual Studio Tools, and then click Visual Studio Command Prompt(2010). 2. In the Visual Studio Command Prompt window, type the text in the following code example, and then press Enter.
vbc.exe /t:exe /out:"C:\Users\Student\Documents\Visual Studio 2010\MyProject\myApplication.exe" "C:\Users\Student\Documents\Visual Studio 2010\MyProject\*.vb"

1-22

Programming in Visual Basic with Microsoft Visual Studio 2010

3.

Right-click the Start menu, click Open Windows Explorer, and then move to C:\Users\Student\Documents\Visual Studio 2010\MyProject\. The MyProject folder should now contain the myApplication.exe executable assembly, which you can run.

Question: Describe two ways to build and run a .NET Framework application.

Introducing Visual Basic and the .NET Framework

1-23

Demonstration: Disassembling a .NET Framework Assembly

Demonstration Steps
1. 2. 3. 4. Run MyFirstApplication.exe in the D:\Demofiles\Mod1\Demo1 folder, and examine the applications output. Close MyFirstApplication.exe. Run ildasm.exe in the C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin folder. Using ildasm, open the MyFirstApplication.exe in the D:\Demofiles\Mod1\Demo1 folder, and then inspect the contents of the MyFirstApplication assembly. Examine the following items: 5. The public key token and the version number in the assembly Manifest. The constructor and Main procedure in the MyFirstApplication.Program node.

Close ildasm.exe.

Question: When developing a .NET Framework application, how would you find ildasm useful?

1-24

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 3

Writing a Visual Basic Application

This lesson describes the structure of a simple Visual Basic application and how a Visual Basic application contains one or more classes. This lesson describes how to reference functionality that is defined in classes in other assembles and libraries and how you can use the Console class in the .NET Framework class library to perform simple input and output operations. Finally, this lesson explains how and why you should add comments to your applications.

Lesson Objectives:
After completing this lesson, you will be able to: Describe how Visual Basic uses namespaces and classes. Describe the structure of an application. Perform input and output operations by using methods that the Console class provides. Apply best practices in commenting a Visual Basic application.

Introducing Visual Basic and the .NET Framework

1-25

What Are Modules, Classes, and Namespaces?

Visual Basic is an object-oriented language that uses classes and namespaces to modularize .NET Framework applications into logical components. In addition to classes, but unlike other object-oriented languages, Visual Basic supports working with modules. A module is similar to a shared class, in that a module has exactly one instance and does not need to be created before you can use it. You add a module to a project and then you add your variables and methods to the module. By default, these are then globally available within your project. A class is essentially a blueprint that defines the characteristics of an entity and includes properties that define the types of data that the object can contain and methods that describe the behavior of the object. A namespace represents a logical collection of classes. Classes are stored in assemblies, and a namespace is simply a device to disambiguate classes that might have the same name in different assemblies. For example, the System.IO namespace includes the following classes that enable you to manipulate the Windows file system. However, you could create classes with the same name under your own namespace: File FileInfo Directory DirectoryInfo Path

To use a class that is defined in the .NET Framework, perform the following tasks: 1. 2. Add a reference to the assembly that contains the compiled code for the class. Bring the namespace that contains the class into scope.

If you are writing a .NET Framework application to write text to a new file on the file system, you can bring the System.IO namespace into scope and then use the WriteAllText method of the File class.

1-26

Programming in Visual Basic with Microsoft Visual Studio 2010

To bring a namespace into scope in a Visual Basic application, you can use the Imports statement. The following code example shows how to bring the System, System.IO, and System.Collections namespaces into scope.
Imports System Imports System.IO Imports System.Collections

The Imports statement is simply a convenience, and you can manage without it. For example, you can use System.Console rather than Console. Question: In your console application, you want to use the Console class, which is part of the System namespace. How do you bring the System namespace into scope?

Introducing Visual Basic and the .NET Framework

1-27

Structure of a Console Application

When you create a new console application by using the Console Application template, Visual Studio 2010 performs the following tasks: It creates a new .vbproj file to represent the console project and structure all of the default components in a console project. It adds references to the assemblies in the .NET Framework class library that console applications most commonly require. This set of assemblies includes the System assembly. It creates the Module1.vb file with a Main procedure, which provides an entry point into or a startup point for the console application.

The Module1.vb file that Visual Studio 2010 creates resembles the following code example.
Module Module1 Sub Main() End Sub End Module

The following table describes the code items in the Module1.vb file. Code item
Module Module1 ... End Module Sub Main() ... End Sub

Description Defines a new public or global module named Module1.

Defines a new Main procedure of type Sub, which does not have a return type.

1-28

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is the Main Procedure?


Every Visual Basic application must have a Main procedure. This procedure provides the CLR with a starting point for the application. When you run a Visual Basic application, the Main procedure is the first method that the CLR executes. When you develop your .NET Framework applications, it is good practice to keep the Main procedure lightweight and let it serve as just a starting point, not a container for most of the logic in your application. The Main procedure has the following significant characteristics: It is Public. This means that it is visible to other classes outside Module1. It is globally shared, so it can and must be called without creating an instance of the Module1. It is a Sub method, so it is a method that does not return data. Note: If you create a Main method in a class, instead of the Main procedure in a module, you must make it shared by applying the Shared keyword to the method declaration. Question: In your console application, you have a procedure named Main. What is its purpose?

Additional Reading
For more information the Main procedure, see http://go.microsoft.com/fwlink/?LinkID=210577&clcid=0x409

Introducing Visual Basic and the .NET Framework

1-29

Performing Input and Output by Using a Console Application

The System namespace provides the Console class, which contains several methods that enable you to add basic console I/O functionality to an application, such as accepting input and displaying data. The following table describes some of the key methods that the Console class provides. Method Clear Description Clears the console window and console buffer of any data. The following code example provides an example of this.
Console.Clear() ' clears the console display

Read

Reads the next character from the console window. The following code example provides an example of this. Dim nextCharacter As Integer = Console.Read()

ReadKey

Reads the next character or key press from the console window. The following code example provides an example of this. Dim key As ConsoleKeyInfo = Console.ReadKey()

ReadLine

Reads the next line of characters from the console window. The following code example provides an example of this. Dim line As String = Console.ReadLine()

Write

Writes the text to the console window. The following code example provides an example of this.

1-30

Programming in Visual Basic with Microsoft Visual Studio 2010

Method

Description Console.Write("Hello there!")

WriteLine

Writes the text followed by a line break to the console window. The following code example provides an example of this. Console.WriteLine("Hello there!")

Question: Which two methods would you use to do the following: Display the message "Please press any key" on a new line. Capture the key that the user pressed.

Additional Reading
For more information about the Console class, see the Console Class page at http://go.microsoft.com/fwlink/?LinkId=192883

Introducing Visual Basic and the .NET Framework

1-31

Techniques for Commenting Visual Basic Applications

It is good programming practice to begin all procedures with a brief comment that describes the functional characteristics of the procedure. This is for your own benefit and the benefit of anyone else who examines the code. Comment blocks can be added by typing in three apostrophes above a procedure or method stub, after which, Visual Basic adds the following code block.
''' ''' ''' ''' Sub <summary> </summary> <remarks></remarks> Main()

End Sub

If the procedure or method declaration contains parameters or a return type, they will be added to the comment block. In Visual Basic, general comments begin with a single apostrophe ('). Comments can follow a statement on the same line or occupy an entire line. Both are illustrated in the following code example.
' This is a comment on a separate line. Dim message As String = "Hello there!" ' This is an inline comment.

The Comment and Uncomment Toolbar Buttons


You can add or remove comment symbols for a block of code by selecting the lines of code and choosing the Comment or Uncomment buttons on the Text Editor toolbar.

Commenting Guidelines
As your code becomes more complex, use comments to make your code more readable and easier to maintain. You should use comments to explain the purpose of a section of code in natural language, especially when the purpose might not be obvious or clear.

1-32

Programming in Visual Basic with Microsoft Visual Studio 2010

The following list provides some guidelines regarding when you should comment your code: Begin procedures with a comment block. This block should include information such as the purpose of the procedure, the value returned, the arguments, and so on. In longer procedures, use comments to break up units of work within the procedure. When you declare variables, use a comment to indicate how the variable will be used. When you write a decision structure, use a comment to indicate how the decision is made and what it implies.

Question: Why is it important for you to comment your code?

Introducing Visual Basic and the .NET Framework

1-33

Lesson 4

Building a Graphical Application

This lesson introduces you to applications that have a graphical user interface and provides the example of a WPF application. This lesson also explains what WPF is, how WPF applications are structured, and how you can create your own WPF applications by using Visual Studio 2010.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of WPF. Describe the structure of a WPF application. Describe the controls that WPF provides and how to set control properties. Describe the concept of events and how WPF controls use events. Explain how to build a simple WPF application by using Visual Studio 2010.

1-34

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is Windows Presentation Foundation?

Windows Presentation Foundation (WPF) is the unified graphical subsystem for Windows that provides the foundation for building applications and high-fidelity experiences. It unifies how Windows creates, displays, and manipulates documents, media, and user interfaces. This enables you to create visually stunning user experiences.

Features of WPF
The main features of WPF are: Extensive support for client application development. Developers can create eye-catching, highly functional applications. WPF includes several text-rendering features, such as OpenType and TrueType. Ease of user interface design. WPF provides a set of built-in controls. It uses the concept that there is a logical separation of a control from its appearance, which is generally considered to be a good architectural principle. Use of Extensible Application Markup Language (XAML). XAML enables developers to use an XMLbased model to manipulate the object model declaratively. XAML is faster and easier to implement than procedural code. XAML is used to define the user interface in a WPF application. Support for interoperability with older applications. Developers can use WPF inside existing Win32 code or existing Win32 code inside WPF. Question: What are some of the advantages of using WPF instead of Windows Forms?

Additional Reading
For more information about what WPF is, visit http://go.microsoft.com/fwlink/?LinkId=192884.

Introducing Visual Basic and the .NET Framework

1-35

Structure of a WPF Application

When you create a new WPF application by using the WPF Application template, Visual Studio 2010 performs the following tasks: It creates a new .vbproj file to represent the WPF project and structure all of the default components in a WPF project. It adds references to the necessary assemblies, which include the PresentationCore, PresentationFramework, System, System.Core, and System.Xaml assemblies. It creates the Application.xaml markup file and an Application.xaml.vb code-behind file, which you can use to define application-level resources and functionality. It creates the Main Window.xaml markup file and the Main Window.xaml.vb code-behind file, which you use as a starting point to building your first WPF window.

The default markup that is generated in the Main Window.xaml markup file is shown in the following code example.
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="110" Width="190"> <Grid> ... </Grid> </Window>

This markup defines a simple window with a default title, width, and height. You can change these properties by editing the XAML code or by using the Properties window in Visual Studio. You can also change these properties dynamically by using code when the application runs. The Grid control governs the layout of controls that you add to the window. If you want to use an alternative layout, you can replace the markup for the Grid control with a different layout control.

1-36

Programming in Visual Basic with Microsoft Visual Studio 2010

The default markup that is generated in the Application.xaml markup file is shown in the following code example.
<Application x:Class="Application" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> </Application.Resources> </Application>

Note that the Application element contains a StartupUri attribute that points to the window you want to open when the application runs. Both Application.xaml and MainWindow.xaml markup files use XAML to represent resources and user interface elements. XAML is a markup language for declarative application programming. Using the XAML markup at design time enables you to separate the user interface design from the application logic, which is stored in code-behind files. XAML directly represents the instantiation of managed objects. Question: Can you think of any other markup languages that behave like XAML?

Introducing Visual Basic and the .NET Framework

1-37

WPF Control Library

WPF includes a rich library of controls that you can use to build your WPF applications. The controls that are included in the library are common user interface components that you would typically find in every Windows-based application, such as the button and the text box. You can also define your own custom controls.

Common WPF Controls


The following table describes some of the commonly used controls in the WPF control library. It also provides a simple XAML example for each, showing the common properties that you can set at design time. Control Button Description The Button control represents a typical clickable button that you would find in most Windows applications. The Canvas control represents a layout panel that enables you to position child controls absolutely. The ComboBox control represents a drop-down list that a user can scroll through and select from. XAML example
<Button Name="MyButton" BorderBrush="Black" BorderThickness="1" Click="MyButton_Click" ClickMode="Press"> Click Me </Button> <Canvas Background="Black" Height="200" Width="200"> <!-- Child controls --> </Canvas> <ComboBox Name="MyComboBox"> <ComboBoxItem> Item a </ComboBoxItem> <ComboBoxItem> Item b </ComboBoxItem>

Canvas

ComboBox

1-38

Programming in Visual Basic with Microsoft Visual Studio 2010

Control

Description

XAML example
</ComboBox>

Grid

The Grid control represents a flexible table that can contain multiple columns and rows. You typically use the Grid control to position child controls.

<Grid ShowGridLines="True" Width="200" Height="200"> <Grid.ColumnDefinitions> <ColumnDefinition /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition /> </Grid.RowDefinitions> <!-- Child controls --> </Grid> <Label Name="MyLabel"> Hello </Label>

Label

The Label control represents a read-only text block that you could use to display some static text. The StackPanel control enables you to stack child controls horizontally or vertically.

StackPanel

<StackPanel Name="MyStackPanel" Orientation="Vertical"> <Label>Item 1</Label> <Label>Item 2</Label> <Label>Item 3</Label> </StackPanel> <TextBox Name="MyTextBox"> </TextBox>

TextBox

The TextBox control represents an editable field that you can use to display and capture text.

Note that you can also define controls dynamically by using Visual Basic in your code-behind file.

WPF Control Properties


Each control in WPF has an associated set of properties that you can use to define the appearance and behavior of a control. For example, most controls have a Height property and a Width property that specify the dimensions of the control, and a Margin property that indicates where the control should appear relative to the layout control it is contained within. You can set control properties: In the XAML window declaratively by editing the XAML directly. In the Properties window. This approach modifies the XAML definition of a control on your behalf. At runtime by using the Visual Basic code. This approach does not change the XAML definition of any controls.

Question: You are building a simple form to capture user credentials and enable users to log on. Which controls could you use to build this form?

Additional Reading

Introducing Visual Basic and the .NET Framework

1-39

For more information about the controls in the WPF control library, see the Control Library page at http://go.microsoft.com/fwlink/?LinkId=192886

1-40

Programming in Visual Basic with Microsoft Visual Studio 2010

WPF Events

When you create a WPF, ASP.NET, or Windows Forms application in Visual Studio 2010, you create an event-driven application. Event-driven applications execute code in response to an event. Each form and control that you create exposes a predefined set of events. When one of these events occurs, and there is code in the associated event handler, that code is invoked.

Handling Events
You can specify the events that a control responds to at design time by editing the XAML definition of a control (you specify the event and the name of an event-handling method to run when the event occurs). Alternatively, you can use the Events tab in the Properties window (this technique modifies the XAML definition of a control automatically). You must provide the methods that handle the events by using code in the code-behind file. The following code examples show the XAML markup for a Button control with a Click event handler, and the Visual Basic code that defines the event handler. When the user clicks the button, the MyButton_Click method is called. The parameters to the MyButton_Click method are defined by WPF, and they are populated with information about the button and the event at runtime.
[XAML control declaration] <Button Name="MyButton" Click="MyButton_Click">Click Me</Button> [Visual Basic event handler] Private Sub MyButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) ' Code to do something goes here. End Sub

The following code examples show how you can define a closing event handler for a Window control.
[XAML control declaration]

Introducing Visual Basic and the .NET Framework

1-41

<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="110" Width="190" Closing="Window_Closing"> </Window> [Visual Basic event handler] Private Sub Window_Closing(ByVal sender As System.Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing End Sub

Because you can use the Handles statement in Visual Basic, you do not have to add the Closing event to the Windows XAML as shown. However, it makes it easier to read and edit the XAML. Question: When you develop your WPF applications, how do you specify events for controls?

Additional Reading
For more information about how WPF handles events, see the Events (WPF) page at http://go.microsoft.com/fwlink/?LinkID=210583&clcid=0x409

1-42

Programming in Visual Basic with Microsoft Visual Studio 2010

Building a Simple WPF Application

You can create a WPF application in Visual Studio 2010 by using the WPF Application template.

Create a new WPF application


1. 2. 3. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010. In Visual Studio 2010, on the File menu, click New Project. In the New Project dialog box, perform the following, and then click OK. In the center pane, click WPF Application. In the Name box, type a name for your WPF application. In the Location box, type a path where you would like to save your project.

Add controls to the WPF application


1. 2. 3. On the View menu, click Toolbox. In the Toolbox window, double-click the control that you want to add to your application. You can then use the Design window or the XAML window to customize the control.

Set control properties


1. 2. In the Design window, click the control that you want to customize. You can then set the properties as follows: Switch to the XAML window, and then edit the XAML directly. Switch to the Properties window, and then set the predefined properties.

Note: You can also set properties in Visual Basic by using the Code Editor window.

Introducing Visual Basic and the .NET Framework

1-43

Add event handlers to controls


1. 2. In the Design window, click the control that you want to add an event handler to. In the Properties window, on the Events tab, double-click the event that you want to add, for example, a Click event handler for a button.

Add code to the WPF application


1. 2. In the Solution Explorer window, right-click the XAML file that you want to add code to, and then click View Code. You can then use the Code Editor window to define the logic behind your controls.

1-44

Programming in Visual Basic with Microsoft Visual Studio 2010

Demonstration: Building a Simple WPF Application

Demonstration Steps
1. 2. Open Microsoft Visual Studio 2010. In Visual Studio 2010, create a new project with the following characteristics: 3. 4. 5. Type: WPF Application Name: MyFirstWpfApp Location: D:\Demofiles\Mod1\Demo2\Starter

Use the Toolbox to add a button control to the application. Examine the XAML mark-up generated by Visual Studio 2010. Use the Properties window to set the following properties for the button control: Name: ClickMeButton Font Size: 20 Height: 50 Width: 150

6.

Use the XAML window to perform the following: In the Button element, set the Content attribute to Click Me. In the Window element, set the Height attribute to 150. In the Window element, set the Width attribute to 190.

7. 8.

Use the Events tab in the Properties window to generate a Click event handler for the button control. Open the MainWindow.xaml.vb file, and in the ClickMeButton_Click method, add the following code.

Introducing Visual Basic and the .NET Framework

1-45

...

Private Sub ClickMeButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles ClickMeButton.Click MessageBox.Show("You clicked me!!") End Sub ...

9.

Build and run the application.

Question: When you are developing a WPF application in Visual Studio 2010, what are the two main ways in which you can set properties for WPF controls?

1-46

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 5

Documenting an Application

This lesson introduces XML comments and explains how you can use them when you are developing your .NET applications. This lesson also shows how to build a formatted help file by using the Sandcastle tool.

Lesson Objectives:
After completing this lesson, you will be able to: Describe what XML comments are and how you can use them in .NET applications. Describe some of the commonly used XML comment tags. Explain how to generate an XML documentation file and how to use Sandcastle to generate a formatted help file by using this XML documentation file.

Introducing Visual Basic and the .NET Framework

1-47

What Are XML Comments?

In Visual Studio 2010, you can add comments to your source code that will be processed to an XML file. This file can then be the input to a process that creates Help documentation for the classes in your code. You can also use an XML file to support IntelliSense on your component. Inline comments are part of the Visual Basic standard, whereas XML comments are a Microsoft extension and are used by third-party tools such as Sandcastle Help File Builder.

XML Documentation Comments


Documentation comments in Visual Basic begin with three apostrophes (''') followed by an XML documentation tag. In the following code example, the Module1module contains <summary> and <remarks> documentation tags.
''' <summary> ''' Prints a greeting on the screen ''' </summary> ''' <remarks></remarks> Module Module1 ''' <summary> ''' We use console-based I/O. ''' </summary> ''' <remarks></remarks> Sub Main() Console.WriteLine("Hello World") End Sub End Module

Question: Why should you use XML comments rather than standard comments?

1-48

Programming in Visual Basic with Microsoft Visual Studio 2010

Additional Reading
For more information about XML comments, see the Documenting Your Code with XML (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210585&clcid=0x409

Introducing Visual Basic and the .NET Framework

1-49

Common XML Comment Tags

There are several suggested XML tags that you can use. You can also create your own custom tags. The following table shows some XML tags and their uses. Tag <summary> </summary> <remarks> </remarks> <example> </example> <code>...</code> <returns> </returns> Purpose Provides a brief description. Use the <remarks> tag for a longer description. Provides a detailed description. This tag can contain nested paragraphs, lists, and other types of tags. Provides an example of how a method, property, or other library member should be used. It often involves the use of a nested <code> tag. Indicates that the enclosed text is application code. Documents the return value and type of a method.

Question: Which tag should you use to provide a detailed description of a method?

Additional Reading
For more information about XML comment tags, see the Recommended XML Tags for Documentation Comments (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210586&clcid=0x409

1-50

Programming in Visual Basic with Microsoft Visual Studio 2010

Generating Documentation from XML Comments

You can compile the XML tags and documentation into an XML file by selecting the XML documentation file check box on the Compile page of the Project Designer for a project or by using the /doc commandline switch when you build an application that has embedded XML comments. If there are no errors, you can view the XML file that is generated by using an application such as Windows Internet Explorer, and you can generate a help file by using a tool such as Sandcastle. Note: Sandcastle is not provided as part of Visual Studio, but it is available separately from the CodePlex website.

Generate an XML file by using Visual Studio 2010


1. 2. In Solution Explorer, right-click a project, and then click Properties. In the Properties window, on the Compile tab, select the XML documentation file check box.

Generate an XML file by using vbc.exe


1. 2. 3. Click Start, point to All Programs, click Microsoft Visual Studio 2010, click Visual Studio Tools, and then click Visual Studio Command Prompt (2010). Open the ConsoleApplication solution from the D:\Demofiles\Mod1\Samplecode folder. In the Visual Studio Command Prompt (2010) window, type the command in the following code example.
vbc.exe /t:exe /doc:"D:\Demofiles\Mod1\Samplecode\ConsoleApplication\bin\Debug\ConsoleApplication.xml " /out:"D:\Demofiles\Mod1\Samplecode\ConsoleApplication\bin\Debug\ConsoleApplication.exe " "D:\Demofiles\Mod1\Samplecode\ConsoleApplication\*.vb"

Note: The /doc switch instructs the compiler to generate an XML file that contains the XML comments.

Introducing Visual Basic and the .NET Framework

1-51

The XML that the compiler generates should resemble the following code example.
<?xml version="1.0"?> <doc> <assembly> <name>ConsoleApplication</name> </assembly> <members> ... <member name="M:ConsoleApplication.Module1.Main"> <summary>We use console-based I/O.</summary> <remarks></remarks> </member> <member name="T:ConsoleApplication.Module1"> <summary>Prints a greeting on the screen</summary> <remarks></remarks> </member> </members> </doc>

Generate a .chm file by using Sandcastle Help File Builder


Now that you have an XML file that contains the comments that were extracted from your project, you can create a .chm file by using a tool such as Sandcastle Help File Builder. 1. 2. 3. Click Start, point to All Programs, click Sandcastle Help File Builder, and then click Sandcastle Help File Builder GUI. In Sandcastle Help File Builder, on the File menu, click New Project. In the Save New Help Project As dialog box, perform the following, and then click Save: a. b. 4. 5. 6. Browse to the path where you want to save the project. Specify a name for the Sandcastle project.

In the Project Explorer window, right-click Documentation Sources, and then click Add Documentation Source. In the Select the documentation source(s) dialog box, browse to the XML file folder, and then click Open. On the Documentation menu, click Build Project.

Wait for the project to build successfully. This will take a minute. Question: Which switch do you need to provide to get vbc.exe to produce XML output?

Additional Reading
For more information about Sandcastle Help File Builder, see the Sandcastle Help File Builder page at http://go.microsoft.com/fwlink/?LinkID=210587&clcid=0x409

1-52

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 6

Debugging Applications by Using Visual Studio 2010

In this lesson, you will learn how to use Visual Studio 2010 to help you debug your applications. You will learn how to use the Debug toolbar, breakpoints, and debug windows to examine your application and step through application code at run time.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the functions that Visual Studio 2010 provides to aid in debugging. Explain how to set, disable, enable, and remove breakpoints. Explain how to step into, step over, and step out of code. Describe how to use the debug windows to examine information about an application.

Introducing Visual Basic and the .NET Framework

1-53

Debugging in Visual Studio 2010

Debugging is an essential part of application development. You may notice errors as you write code, but some errorsespecially logic errorsmay only occur in specific circumstances that you do not test for. Users may report these errors to you, and you will have to correct them. Visual Studio 2010 provides several tools to help you debug code. You might use these while you develop code, during a test phase, or after the application has been released. You can use the tools in the same way regardless of the circumstances. You can run an application with or without debugging enabled. When debugging is enabled, your application is said to be in the Debug mode. To access numerous debug functions, including the ability to step through code line by line, you can use the controls on the Debug menu, the controls on the Debug toolbar, and keyboard shortcuts.

Debug Controls
The following table lists the main debug controls on the Debug menu and the Debug toolbar, and the corresponding keyboard shortcuts. Toolbar Menu option button Start Debugging Start Debugging/C ontinue Keyboard shortcut F5 Description This button is available when your application is not running and when you are in break mode. It will start your application in the Debug mode or resume the application if you are in the break mode. This button causes application processing to pause and break mode to be entered. The button is available when an application is running.

Break All

Break all

Ctrl+Break

1-54

Programming in Visual Basic with Microsoft Visual Studio 2010

Toolbar Menu option button Stop Debugging Step Into Step Over Step Out Windows Stop Debugging Step into Step over Step out

Keyboard shortcut Ctrl+Alt+Break F8 Shift+F8 Ctrl+Shift+F8 Various

Description This button stops debugging. It is available when an application is running or in the break mode. This button is used for stepping through code. See the next topic in this lesson. This button is used for stepping through code. See the next topic in this lesson. This button is used for stepping through code. See the next topic in this lesson. This button enables access to various debug windows, each of which has its own shortcut key.

Question: What are some of the debug functions that Visual Studio 2010 provides?

Introducing Visual Basic and the .NET Framework

1-55

Using Breakpoints

When you run an application in the Debug mode, you can pause execution and enter the break mode. In the break mode, no further execution takes place until you restart the application or step through the code line by line. You can also view and change variable values, execute additional code or evaluate expressions, and more. When you are in the break mode, the current line of code is indicated by a yellow arrow on the gray bar to the left of the code and by a yellow background for the next statement due to be executed. The Break All debug function enables you to enter break mode. However, this function does not give you much control over exactly where code execution pauses. Breakpoints enable you to choose exactly where code execution will pause. If you place a breakpoint on a line of code, the application will enter the break mode as soon as that line of code is reached before it executes that line of code.

Set a Breakpoint
1. 2. Locate the line of code where you want to set a breakpoint. Add a breakpoint by using one of the following steps: a. b. c. d. Click the gray bar to the left of the line of code. Position the cursor on the line of code, and then press F9. Position the cursor on the line of code, and then, on the Debug menu, click Toggle Breakpoint. Right-click the line of code, point to Breakpoint, and then click Insert Breakpoint.

The breakpoint is indicated by a solid red circle on the gray bar to the left of the code and by a red background for the line of code that contains the breakpoint.

1-56

Programming in Visual Basic with Microsoft Visual Studio 2010

Disable or Enable a Breakpoint


1. 2. Locate a line of code that has an enabled or disabled breakpoint. Disable or enable the breakpoint by using one of the following steps: a. b. c. Right-click the solid red circle on the gray bar to the left of the line of code, and then click Disable Breakpoint or Enable Breakpoint. Right-click the line of code that contains the breakpoint, point to Breakpoint, and then click Disable Breakpoint or Enable Breakpoint. If the breakpoint is disabled, click the solid red circle to the left of the code to enable it.

Disabled breakpoints are indicated by a red circle outline on the gray bar to the left of the code and a red outline around the code that contains the breakpoint.

Remove a Breakpoint
1. 2. Locate a line of code that has a breakpoint. Remove the breakpoint by using one of the following steps: a. b. c. d. e. If the breakpoint is enabled, click the solid red circle in the code to the left of the code to remove it. Position the cursor on the line of code, and then press F9. Position the cursor on the line of code, and then, on the Debug menu, click Toggle Breakpoint. Right-click the line of code, point to Breakpoint, and then click Delete Breakpoint. Right-click the solid red circle on the gray bar to the left of the line of code, and then click Delete Breakpoint.

Question: How would you use the debug functions in Visual Studio 2010 to debug your application and pause on a specific line of code?

Introducing Visual Basic and the .NET Framework

1-57

Stepping Through Code

You can step through code one statement at a time to see exactly how processing proceeds through your application. This is an extremely useful debugging technique because it enables you to test the logic that your application uses. Between statement executions, you can view and edit variable values. Each time your code reaches a branching statement such as a conditional statement, you can verify that the correct code executes and modify the code if it does not. The various tools that you use to step through code enable you to step through code in exactly the way you want to. You can, for example, step through each line in each method that is executed, or you can ignore the statements inside a method that you know is working correctly. You can also skip over code completely, which prevents some statements from execution.

Step Into, Step Over, and Step Out


There are three debug functions that are essential for stepping through code. These are as follows: Step into. This function executes the statement at the current execution position. If the statement is a method call, the current execution position will move to the code inside the method. After you have stepped into a method, you can continue executing statements inside the method one line at a time. This also applies to properties. In addition, you can use the Step into function to start an application in the Debug mode. If you do this, the application will enter break mode as soon as it starts. Step over. As with Step into, the Step over function executes the statement at the current execution position. However, this function does not step into code inside a method or property. Instead, the code inside the method or property is executed, and the executing position moves to the statement after the method call or property access. The exception to this is where the code for the method or property contains a breakpoint. If this is the case, execution will continue up to the breakpoint. Step out. The Step out function enables you to execute the remaining code in a method, property access, or loop. Execution will continue to the statement that called the method or accessed the property, or to the statement following the loop code. Execution will pause at this point.

1-58

Programming in Visual Basic with Microsoft Visual Studio 2010

Skipping Code
In the break mode, the next statement to be executed is indicated by a yellow arrow on the gray bar to the left of the code and a yellow background for the statement. You can override this and set a different statement as the next one to execute. To do this, right-click the statement that you want to be executed next, and then click Set next statement. The arrow and yellow background will move to the statement that you have chosen. If you use this technique, you should be aware that you will change the way in which your application works. If you skip important code, such as variable assignments or critical method calls, you risk introducing errors that would not otherwise occur. You should skip statements with caution.

Continuing and Restarting


After you finish stepping through your code, you can return to the Debug mode by using the start/continue functions. Then, execution will continue until you enter break mode again, either with the Break all button or if the code encounters a breakpoint. If you want to terminate the application and then run it again in the Debug mode, you can use the Restart function. This is useful if you want to test the code that executes when an application first runs or any code that is only executed once when an application is used. Question: Why would you use the Step into and Step over debug functions?

Introducing Visual Basic and the .NET Framework

1-59

Using Debug Windows

Visual Studio 2010 includes several windows that you can use to help debug your applications. These windows are available at runtime; mostly in the break mode. The following table describes some of the commonly used debug windows in Visual Studio 2010. Window QuickWatch Description This is a modal window that enables you to evaluate variables and expressions. Type variable names or expressions in Expression, and then click Reevaluate to view the value and type of the variable or the result of the expression. Click Close to close the QuickWatch window. This window enables you to view and edit local (in-scope) variables. You can expand variables, view members, and edit the contents of some variables in the Value column. This window enables you to evaluate expressions, execute statements, and print variable values. You can use this window to issue Visual Studio 2010 commands such as Debug.Print? to print the value of a variable or expression. In this window, you can view error and information messages. One of the main uses of this window is to view traces from your applications by using the System.Diagnostics.Debug.WriteLine method. This window enables you to examine and edit the contents of the memory that an application uses. This is an advanced function and can cause your application to behave unpredictably if you do not use this window carefully. This window enables you to view the stack of method calls that are used to reach the current code location. The current position is shown at the top of the window, and the series of calls that the application has processed to reach this location is shown below. This window enables you to view information about the modules (assemblies and executable files) that an application uses. Each module is listed along with its location,

Locals Immediate

Output

Memory

Call Stack

Modules

1-60

Programming in Visual Basic with Microsoft Visual Studio 2010

Window

Description version, and other information.

Processes Threads

In this window, you can view information about the processes that the debugger is attached to. In this window, you can examine and control threads in an application.

Question: Why would you use the Locals and Immediate windows when developing your application?

Introducing Visual Basic and the .NET Framework

1-61

Walkthrough: Lab Solution

In this demonstration, the instructor will walk you through some of the applications you will build as part of the lab exercises throughout the 15 modules. You can open the Lab solutions on the virtual machine and view the steps demonstrated. Open the solution code for a particular module by accessing the solution file (.sln) from the D:\Labfiles\Labxx\Exy\Solution folders, where xx is the number of the module, and y is the number of the exercise.

1-62

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab: Introducing Visual Basic and the .NET Framework

Objectives:
After completing this lab, you will be able to: Create, build, and run a simple console application by using Visual Studio 2010 and Visual Basic 2010. Create, build, and run a basic WPF application by using Visual Studio 2010. Use the Visual Studio 2010 debugger to set breakpoints, step through code, and examine the values of variables. Generate documentation for an application.

Introduction
In this lab, you will create simple console and WPF solutions to get started with using Visual Studio 2010 and Visual Basic. You will also configure projects, use code-editing features, and create comments. You will become familiar with the debugger interface. You will compile, run, and use the debugger to step through a program. Finally, you will generate documentation for an application.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

Note: Step-by-step instructions for completing the labs in this course are available in the lab answer keys provided. Completed, working code is available in the Solution folders under the Lab files folder for each lab exercise on the virtual machine.

Introducing Visual Basic and the .NET Framework

1-63

Lab Scenario

Fabrikam, Inc. produces a range of highly sensitive measuring devices that can repeatedly measure objects and capture data. You have been asked to write a Visual Basic application to read a small set of input data that a measuring device has generated, format this data to make it more readable, and then display formatted results. The data consists of text data that contains pairs of numbers representing x-coordinates and ycoordinates of the location of an object. Each line of text contains one set of coordinates. The following code example resembles a typical dataset.
23.8976,12.3218 25.7639,11.9463 24.8293,12.2134

You have been asked to format the data like the following code example.
x:23.8976 y:12.3218 x:25.7639 y:11.9463 x:24.8293 y:12.2134

1-64

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 1: Creating a Simple Console Application


In this exercise, you will initially create and test the application by using console I/O. You will then use I/O redirection to run the application by using data that is held in a file and verify that the results are as expected.

Scenario
You have decided to implement a console application as a prototype to read input from the keyboard and format it. After the code begins working, you will run it and redirect the input to come from a file that contains the data that you want to format. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. Create a new Console Application project. Add code to read user input and write output to the console. Modify the program to read and echo text until end-of-file is detected. Add code to format the data and display it. Test the application by using a data file.

Task 1: Create a new Console Application project


Open Microsoft Visual Studio 2010. Create a new console application project named ConsoleApplication in the D:\Labfiles\Lab01\Ex1\Starter folder.

Task 2: Add code to read user input and write output to the console
In the Main procedure in the Module1 module, add code to read a line of text from the keyboard and store it in a string variable named line. Add code to write the text back to the console by using the Console.WriteLine method. Build the application. Run the application and verify that it works as expected. You should be able to enter a line of text and see that line written to the console.

Task 3: Modify the program to read and echo text until end-of-file is detected
In the Main procedure, modify the statement and comment shown in bold in the following code example, which reads a line of text from the keyboard.
Sub Main() ' Buffer to hold each line as it's read in Dim line As String line = Console.ReadLine() ' Loop until no more input (Ctrl-Z in a console or end-of-file) While Not line Is Nothing line = Console.ReadLine() End While ' Write the results out to the console window Console.WriteLine(line) End Sub

Introducing Visual Basic and the .NET Framework

1-65

This code incorporates the statement into a While loop that repeatedly reads text from the keyboard until the Console.ReadLine method returns Nothing (this happens when the Console.ReadLine method detects the end of a file, or the user presses Ctrl+Z). Move the Console.WriteLine statement into the body of the While loop as shown in bold in the following code example. This statement echoes each line of text that the user has entered.
Sub Main() ' Buffer to hold each line as it's read in Dim line As String line = Console.ReadLine() ' Loop until no more input (Ctrl-Z in a console or end-of-file) While Not line Is Nothing ' Write the results out to the console window Console.WriteLine(line) line = Console.ReadLine() End While End Sub

Build the application. Run the application and verify that it works as expected. You should be able to repeatedly enter lines of text and see those lines echoed to the console. The application should only stop when you press Ctrl+Z.

Task 4: Add code to format the data and display it


In the body of the While loop, add the statement and comment shown in bold before the Console.WriteLine statement in the following code example.
Sub Main() ' Buffer to hold each line as it's read in Dim line As String line = Console.ReadLine() ' Loop until no more input (Ctrl-Z in a console or end-of-file) While Not line Is Nothing ' Format the data line = line.Replace(",", " y:") ' Write the results out to the console window Console.WriteLine(line) line = Console.ReadLine() End While End Sub

This code replaces each occurrence of the comma character "," in the input read from the keyboard and replaces it with the text y:. It uses the Replace method of the line string variable. The code then assigns the result back to the line variable. Add the statement shown in bold in the following code example to the code in the body of the While loop.
Sub Main() ' Buffer to hold each line as it's read in Dim line As String

1-66

Programming in Visual Basic with Microsoft Visual Studio 2010

line = Console.ReadLine() ' Loop until no more input (Ctrl-Z in a console or end-of-file) While Not line Is Nothing ' Format the data line = line.Replace(",", " y:") line = "x:" & line ' Write the results out to the console window Console.WriteLine(line) line = Console.ReadLine() End While End Sub

This code adds the prefix x: to the line variable by using the string concatenation operator &, before the Console.WriteLine statement. The code then assigns the result back to the line variable. Build the application. Run the application and verify that it works as expected. The application expects input that looks like the following code example.
23.54367,25.6789

Your code should format the output to look like the following code example.
x:23.54367 y:25.6789

Task 5: Test the application by using a data file


Add the DataFile.txt file that contains the sample data to the project. This file is located in the D:\Labfiles\Lab01\Ex1\Starter folder, and you can copy it by pointing to Add, and then click Existing Item on the context menu, which is accessible by right-clicking the project in Solution Explorer. The file should be copied to the build output folder when the project is built. You can do this by setting the Build Action property to None, and the Copy to Output property to Copy Always, for the DataFile.txt item in Solution Explorer. Rebuild the application. Open a Visual Studio Command Prompt window, and then move to the D:\Labfiles\Lab01\Ex1\Starter\ConsoleApplication\bin\Debug folder. Run the ConsoleApplication application and redirect input to come from DataFile.txt. You can do this by typing in ConsoleApplication< DataFile.txt in the Command Prompt window. Verify that the output that is generated looks like the following code example.
x:23.8976 y:12.3218 x:25.7639 y:11.9463 x:24.8293 y:12.2134

Close the Command Prompt window, and then return to Visual Studio. Modify the project properties to redirect input from the DataFile.txt file when the project is run by using Visual Studio. You can do this by typing in < DataFile.txt in the Command line arguments text box, on the Debug page, of the Project Designer. Run the application in the Debug mode from Visual Studio.

Introducing Visual Basic and the .NET Framework

1-67

The application will run, but the console window will close immediately after the output is generated. This is because Visual Studio only prompts the user to close the console window when a program is run without debugging. When a program is run in the Debug mode, Visual Studio automatically closes the console window as soon as the program finishes. Set a breakpoint on the End Sub statement that signals the end of the Main procedure. Run the application again in the Debug mode. Verify that the output that is generated is the same as the output that is generated when the program runs from the command line. The application will run, and then the program will stop when control reaches the end of the Main procedure and Visual Studio has the focus.

1-68

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 2: Creating a WPF Application


In this exercise, you will create a simple WPF application that provides similar functionality to the console application that you developed in Exercise 1. You will initially test the display formatting by providing fields that the user can type data into. When you are satisfied that the display format is correct, you will modify the application to read input from the console and modify the Debug properties of the application to redirect this input to come from the same file as before.

Scenario
You have been asked to change the application to generate the data in a more helpful manner. The application should perform the same task as the console application except that the output is displayed in a WPF window. The main tasks for this exercise are as follows: 1. 2. 3. 4. Create a new WPF Application project. Create the user interface. Add code to format the data that the user enters. Modify the application to read data from a file.

Task 1: Create a new WPF application project


Create a new project named WpfApplication in the D:\Labfiles\Lab01\Ex2 \Starter folder by using the Windows Presentation Foundation (WPF) Application project template. Name: WpfApplication Location: D:\Labfiles\Lab01\Ex2\Starter Solution name: WpfApplication Create directory for solution: Select the check box

Task 2: Create the user interface


Add TextBox, Button, and TextBlock controls to the MainWindow window. Place them anywhere in the window. Using the Properties window, set the properties of each control by using the values in the following table. Leave any other properties at their default values. Control Type TextBox Property Name Name Height HorizontalAlignment Margin VerticalAlignment Width Button Name Property Value TestTextBox 28 Left 12,12,0,0 Top 302 TestButton

Introducing Visual Basic and the .NET Framework

1-69

Control Type

Property Name Content Height HorizontalAlignment Margin VerticalAlignment Width

Property Value Format Data 23 Left 320,17,0,0 Top 80 FormattedTextTextBlock 238 Left 14,50,0,0

TextBlock

Name Height HorizontalAlignment Margin Text VerticalAlignment Width

Top 384

The MainWindow window should appear as the following image.

1-70

Programming in Visual Basic with Microsoft Visual Studio 2010

Task 3: Add code to format the data that the user enters
Create an event handler for the Click event of the button. Add code to the event-handler method that reads the contents of the TextBox control into a string variable named line, formats this string in the same way as the console application in Exercise 1, and then displays the formatted result in the TextBlock control. Notice that you can access the contents of a TextBox control and a TextBlock control by using the Text property. Your code should resemble the following code.
Private Sub TestButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles TestButton.Click ' Copy the contents of the TextBox into a string Dim line As String = TestTextBox.Text ' Format the data in the string line = line.Replace(",", " y:") line = "x:" + line ' Store the results in the TextBlock FormattedTextTextBlock.Text = line End Sub

Build the solution, and then correct any errors. Run the application and verify that it works in a similar manner to the original console application in Exercise 1. Close the MainWindow window, and then return to Visual Studio.

Task 4: Modify the application to read data from a file


Create an event handler for the Window_Loaded event. This event occurs when the window is about to be displayed, just after the application has started. You can do this by clicking the title bar of the MainWindow window in the MainWindow.xaml file, and then double-clicking the Loaded event in the list of events in the Properties window. In the event-handler method, add the code shown in bold in the following code example.
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded ' Buffer to hold a line read from the file on standard input Dim line As String line = Console.ReadLine() ' Loop until the end of the file While Not line Is Nothing ' Format the data in the buffer line = line.Replace(",", " y:") line = "x:" & line & vbNewLine ' Put the results into the TextBlock FormattedTextTextBlock.Text &= line line = Console.ReadLine() End While End Sub

This code reads text from the standard input, formats it in the same manner as Exercise 1, and then appends the results to the end of the TextBlock control. It continues to read all text from the standard input until end-of-file is detected.

Introducing Visual Basic and the .NET Framework

1-71

Notice that you can use the &= operator to append data to the Text property of a TextBlock control, and you can add the newline character, vbNewLine, between lines for formatted output to ensure that each item appears on a new line in the TextBlock control. Perform the following steps to modify the project settings to redirect standard input to come from the DataFile.txt file. A copy of this file is available in the D:\Labfiles\Lab01\Ex2\Starter folder. a. b. In Solution Explorer, right-click the WpfApplication project, point to Add, and then click Existing Item. In the Add Existing Item WpfApplication dialog box, move to the D:\Labfiles\Lab01\Ex2\Starter folder, select All Files (*.*) in the drop-down list box adjacent to the File name text box, click DataFile.txt, and then click Add. In Solution Explorer, select DataFile.txt. In the Properties window, change the Build Action property to None, and then change the Copy to Output property to Copy Always. In Solution Explorer, right-click the WpfApplication project, and then click Properties. On the Debug tab, in the Command line arguments: text box, type < DataFile.txt On the File menu, click Save All. Close the WpfApplication properties window.

c. d. e. f. g.

Build and run the application in the Debug mode. Verify that, when the application starts, it reads the data from DataFile.txt and displays in the TextBlock control the results in the following code example.
x:23.8976 y:12.3218 x:25.7639 y:11.9463 x:24.8293 y:12.2134

Close the MainWindow window, and then return to Visual Studio.

1-72

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 3: Verifying the Application


In this exercise, you will create some additional test data and use it as input to your application. You will use the Visual Studio 2010 debugger to step through your code and examine it as it runs.

Scenario
You want to verify that the code for your WPF application is operating exactly as you require. You decide to create some additional test data and use the Visual Studio 2010 debugger to step through the application. The main tasks for this exercise are as follows: 1. 2. Modify the data in the DataFile.txt file. Step through the application by using the Visual Studio 2010 debugger.

Task 1: Modify the data in the DataFile.txt file


Modify the contents of the DataFile.txt file as the following code example shows.
1.2543,0.342 32525.7639,99811.9463 24.8293,12.2135 23.8976,12.3218 25.7639,11.9463 24.8293,12.2135

Note: There must be a blank line at the end of DataFile.txt.

Task 2: Step through the application by using the Visual Studio 2010 debugger
Set a breakpoint at the start of the Window_Loaded event handler.
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

Start the application running in the Debug mode.

Note: When the application runs the Window_Loaded event handler, it reaches the breakpoint and drops into Visual Studio. The method declaration is highlighted. Step into the second statement in the Window_Loaded method that contains executable code.

Note: The While statement should be highlighted. This is because the statement that declares the line variable does not contain any executable code. Examine the value of the line variable. It should be 1.2543,0.342. This is the text from the first line of the DataFile.txt file. Step into the next statement. The cursor moves to the line in the following code example.
line = line.Replace(",", " y:")

Step into the next statement.

Introducing Visual Basic and the .NET Framework

1-73

Examine the value of the line variable. It should now be 1.2543 y:0.342. This is the result of calling the Replace method and assigning the result back to line. Step into the next statement. Examine the value of the line variable. It should now be x:1.2543 y:0.342. This is the result of prefixing the text x: to line and suffixing a newline character. The latter is displayed as a space. Step into the next statement.

Note: The cursor moves to the end of the While loop. In the Immediate window, examine the value of the Text property of the FormattedTextTextBlock control. It should contain the same text as the line variable.

Note: If the Immediate window is not visible, press Ctrl+Alt+I. Set another breakpoint at the end of the While loop. Continue running the program for the next iteration of the While loop. It should stop when it reaches the breakpoint at the end of the loop. Examine the value of the line variable. It should now be x:32525.7639 y:99811.9463. This is the data from the second line of DataFile.txt. Remove the breakpoint from the end of the While loop. Continue the programming running. The Window_Loaded method should now run to completion and display the MainWindow window. The TextBlock control should contain all of the data from DataFile.txt, formatted correctly. Close the MainWindow window, and then return to Visual Studio.

1-74

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 4: Generating Documentation for an Application


In this exercise, you will add XML comments to your application and use the Sandcastle tool to generate documentation for the application.

Scenario
You must ensure that your application is fully documented so that it can be maintained easily. You decide to add XML comments to the methods that you have added to the WPF application and generate a help file. The main tasks for this exercise are as follows: 1. 2. 3. 4. Open the starter project. Add XML comments to the application. Generate an XML comments file. Generate a .chm file.

Task 1: Open the starter project


In Visual Studio, open the WpfApplication solution located in the D:\Labfiles\Lab01\Ex4\Starter folder. This solution is a working copy of the solution from Exercise 2.

Task 2: Add XML comments to the application


Display the MainWindow.xaml.vb file. Add the XML comment in the following code example before the MainWindow class declaration. There should be no blank line between the class declaration and the last line of the XML comment.
''' ''' ''' ''' <summary> WPF application to read and format data </summary> <remarks></remarks>

Add the XML comment in the following code example before the TestButton_Click method. There should be no blank line between the method declaration and the last line of the XML comment.
''' ''' ''' ''' ''' ''' ''' ''' <summary> Read a line of data entered by the user. Format the data and display the results in the FormattedTextTextBlock control. </summary> <param name="sender"></param> <param name="e"></param> <remarks></remarks>

Add the XML comment in the following code example before the Window_Loaded method. There should be no blank line between the method declaration and the last line of the XML comment.
''' ''' ''' ''' ''' ''' ''' ''' <summary> After the Window has loaded, read data from the standard input. Format each line and display the results in the FormattedTextTextBlock control. </summary> <param name="sender"></param> <param name="e"></param> <remarks></remarks>

Introducing Visual Basic and the .NET Framework

1-75

Save MainWindow.xaml.vb.

Task 3: Generate an XML comments file


Set the project properties to generate an XML documentation file when the project is built. Build the solution, and then correct any errors. Verify that an XML comments file named WpfApplication.xml has been generated in the D:\Labfiles\Lab01\Ex4\Starter\WpfApplication\bin\Debug folder, and then examine it. Copy the WpfApplication.xml file to the D:\Labfiles\Lab01\Ex4\HelpFile folder.

Task 4: Generate a .chm file


Open a Windows Command Prompt window as Administrator. The Administrator password is Pa$$w0rd. Browse to the D:\Labfiles\Lab01\Ex4\HelpFile folder. Copy the sandcastle.config file from the C:\Program Files (x86)\Sandcastle\Presentation\vs2005\configuration folder, to the current folder by running the following command:
copy "C:\Program Files (x86) \Sandcastle\Presentation\vs2005\configuration\sandcastle.config"

Use Notepad to edit the builddoc.cmd script, and then edit the input variable to D:\Labfiles\Lab01\Ex4\Starter\WpfApplication\bin\Debug \WpfApplication.exe. Run the builddoc.cmd script. Open the test.chm file that the builddoc.cmd script generates in the D:\Labfiles\Lab01 \Ex4\HelpFile\Output folder. Browse documentation that is generated for your application, and then close test.chm. Close Visual Studio.

1-76

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Review

Review Questions
1. 2. 3. 4. Which methods did you use to capture and display information in your console application? Which event did you handle on the Format Data button in your WPF application? Which debugging functions did you use when you verified the application? How do you instruct Visual Studio 2010 to produce an XML file that contains XML comments?

Introducing Visual Basic and the .NET Framework

1-77

Module Review and Takeaways

Review Questions
1. 2. 3. 4. 5. 6. 7. What is the purpose of the .NET Framework and the role of Visual Basic? What is the purpose of Visual Studio 2010 templates? What is the purpose of Visual Studio projects and solutions? What is the purpose of a Main procedure or method? List some of the controls that WPF provides. What is the purpose of XML comments? What is the purpose of the Visual Studio 2010 debugger?

Best Practices Related to Writing a Visual Basic Application


Supplement or modify the following best practices for your own work situations: Keep the Main procedure or method small and lightweight. Declare variables by using meaningful names and avoid reference to the underlying data type such as nameString. Define controls by using meaningful names and suffix using the underlying control type such as NameLabel. Add comments to your code that describe your ideas.

Tools
Tool Gacutil.exe Use for Where to find it

Enables users to manipulate the C:\Program Files assemblies in the GAC. This can \Microsoft SDKs\Windows include installing and uninstalling

1-78

Programming in Visual Basic with Microsoft Visual Studio 2010

Tool

Use for assemblies in the GAC so that multiple applications can access them.

Where to find it \v7.0A\bin

Ildasm.exe

Enables users to manipulate C:\Program Files assemblies, such as determining \Microsoft SDKs\Windows whether an assembly is managed, \v7.0A\bin or disassembling an assembly to view the compiled MSIL code. Enables users to create x.509 certificates for use in their development environment. Typically, you can use these certificates to sign your assemblies and define SSL connections. C:\Program Files \Microsoft SDKs\Windows \v7.0A\bin

Makecert.e xe

Ngen.exe

Enables users to improve the C:\Windows\Microsoft.NET performance of .NET applications. \Framework\v4.0.30319 The Native Image Generator improves performance by precompiling assemblies into images that contain processorspecific machine code. The CLR can then run the precompiled images instead of using JIT compilation. Enables users to sign assemblies C:\Program Files with strong names. The Strong \Microsoft SDKs\Windows Name Tool includes commands \v7.0A\bin to create a new key pair, extract a public key from a key pair, and verify assemblies.

Sn.exe

Using Visual Basic Programming Constructs

2-1

Module 2
Using Visual Basic Programming Constructs
Contents:
Lesson 1: Declaring Variables and Assigning Values Lesson 2: Using Expressions and Operators Lesson 3: Creating and Using Arrays Lesson 4: Using Decision Statements Lesson 5: Using Iteration Statements Lab: Using Visual Basic Programming Constructs 2-3 2-18 2-28 2-37 2-47 2-59

2-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

To make the best use of a programming language, you need to understand the constructs that the language provides. Visual Basic is a procedural programming language that shares many features with other procedural programming languages that you may be familiar with. For example, you can declare variables, assign values to them, and make decisions based on the values of these variables. This module introduces many of the basic Visual Basic language data types and programming constructs, and describes the syntax and semantics of these constructs.

Objectives:
After completing this module, you will be able to: Explain how to declare variables and assign values. Use operators to construct expressions. Create and use arrays. Use decision statements. Use iteration statements.

Using Visual Basic Programming Constructs

2-3

Lesson 1

Declaring Variables and Assigning Values

All applications use data. This data might be supplied through a user interface, from a database, from a network service, or from some other source. To store and use data in your applications, you must familiarize yourself with how to define and use variables and data types in Visual Basic. This lesson describes how Visual Basic uses variables and the built-in data types that Visual Basic provides. This lesson also explains how to convert the data that is held in a variable from one data type to another.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of variables. Describe the purpose of data types. Explain how to declare and assign variables. Explain how variable scope determines where a variable is accessible in an application. Explain how to convert data in a variable to a different data type. Describe the best practices for using read-only variables and constants.

2-4

Programming in Visual Basic with Microsoft Visual Studio 2010

What Are Variables?

A variable represents a named location in memory for a piece of data. An application can access a piece of data by using the variable it has been assigned to. Variables store values that an application can change while it is running. You often need to store values temporarily when you perform calculations or pass data between the user, an application, and a database. For example, you might want to retrieve several values from a database, compare them, and perform different operations on them depending on the result of the comparison. A variable has the following six facets: Name. Unique identifier that refers to the variable in code Address. Memory location of the variable Data type. Type and size of data that the variable can store Value. Value at the address of the variable Scope. Defined areas of code that can access and use the variable Lifetime. Period of time that a variable is valid and available for use

Examples of Variables
You can use variables in many ways, including: As a counter for loop structures. As temporary storage for property values. As a container to store a value that was returned from a function.

Question: What is a variable and how are variables used in Microsoft .NET Framework applications?

Using Visual Basic Programming Constructs

2-5

What Are Data Types?

A variable holds data that has a specified type. When you declare a variable to store data in an application, you need to choose an appropriate data type for that data. Visual Basic is a type-safe language, which means that the compiler guarantees that values that are stored in variables are always of the appropriate type. Visual Basic allows conversions of many data types to other data types. Data loss can occur when the value of one data type is converted to a data type with less precision or smaller capacity. A run-time error occurs if such a narrowing conversion fails. By using the Option Strict compiler switch, you can enable or disable compile-time notification of these narrowing conversions. In addition, with Option Strict turned off, you can use late binding and write something such as the following.
Dim dates As Object = New List(Of DateTime) dates.Add(52) Console.WriteLine(dates.Count)

The code compiles, but it will throw an exception at runtime, because 52 cannot be converted to a DateTime value; it is not the appropriate type.

Commonly Used Data Types


The following table shows the commonly used data types in Visual Basic, and their characteristics. Type Integer Long Description Whole numbers Whole numbers (bigger range) Size (bytes) 4 8 Range 2,147,483,648 to 2,147,483,647 9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

2-6

Programming in Visual Basic with Microsoft Visual Studio 2010

Type Single

Description Floatingpoint numbers Double precision (more accurate) floatingpoint numbers Monetary values Single character Boolean Moments in time Sequence of characters

Size (bytes) 4

Range +/3.4 10^38

Double

+/1.7 10^308

Decimal Char Boolean DateTime String

16 2 1 8 2 per character

28 significant figures N/A True or false 0:00:00 on 01/01/0001 to 23:59:59 on 12/31/9999 N/A

Question: What type would you use to store a sequence of alphanumeric characters?

Additional Reading
For more information about the Option Strict compiler switch, see the Option Strict Statement page, at http://go.microsoft.com/fwlink/?LinkID=210881&clcid=0x409
For more information about late binding, see the Early and Late Binding (Visual Basic) page, at http://go.microsoft.com/fwlink/?LinkID=210882&clcid=0x409

Using Visual Basic Programming Constructs

2-7

Declaring and Assigning Variables

Before you can use a variable, you must declare it so that you can specify its name and characteristics.

Note: By default, Visual Basic forces explicit declaration of all variables in a file. However, this can be changed by using the Option Explicit compiler switch, which helps you enable or disable an explicit variable declaration.

Identifiers
The name of a variable is referred to as an identifier. Visual Basic has specific rules concerning the identifiers that you can use: An identifier can only contain letters, digits, and underscore characters. An identifier must start with a letter or an underscore. An identifier for a variable should not be one of the keywords that Visual Basic reserves for its own use. A full list of Visual Basic keywords is provided in the CD content for this topic. However, it is possible to use a reserved keyword as an identifier, if it is enclosed in square brackets[].

Note: Visual Basic is case-insensitive. If you use the name, MyData, as the identifier of a variable, this is the same as myData. You cannot declare two variables at the same time called, MyData and myData, because Visual Basic will not allow two variables with the same name. You should use meaningful names for your variables because this can make your code easier to understand. You should also adopt a naming convention and be consistent.

2-8

Programming in Visual Basic with Microsoft Visual Studio 2010

Note: Different organizations may have different naming conventions. Some common conventions are described in the CD content for this topic. If your organization does not currently follow any specific naming style, you may want to adopt these conventions.

Declaring a Variable
When you declare a variable, you reserve some storage space for that variable in memory. You must specify the type of data that it will hold. You can declare multiple variables in a single declaration by using the comma separator; all variables declared in this way have the same type. The syntax for declaring variables is shown in the following code example.
Dim variableName As DataType ' OR Dim variableName1, variableName2 As DataType

Assigning a Value to a Variable


After you declare a variable, you can assign a value to it for later use in the application by using an assignment statement. You can change the value in a variable as many times as you want during the application. The assignment operator (=) assigns a value to a variable. The syntax of a variable assignment is shown in the following code example.
variableName = value

The value on the right side of the expression is assigned to the variable on the left side of the expression. The following code example declares an integer called price and assigns the number 10 to the integer.
Dim price As Integer = 10

The following code example assigns the number 20 to an existing integer variable called price.
price = 20

You can also assign variables when you declare them. The following code example shows the syntax of a variable declaration and assignment.
Dim variableName As DataType = value

The type of the expression must match the type of the variable, otherwise your program will not compile. For example, the code in the following code example will not work because you cannot assign a string value to an integer variable.
Dim numberOfEmployees As Integer numberOfEmployees = "Hello"

Note: When you declare a variable, it contains a random value until you assign a value to it. This behavior was a rich source of bugs in C and C++ programs that created a variable and accidentally used it as a source of information before giving it a value. Visual Basic does allow you to use an unassigned variable, although a warning is generated. You can modify this

Using Visual Basic Programming Constructs

2-9

behavior to generate an error instead. You should assign a value to a variable before you use it; otherwise, your program might not work as expected.

Implicitly Typed Variables


When you declare variables, you can also omit an explicit data type such as Integer or String. When the compiler sees the omitted data type, it uses the value that is assigned to the variable to determine the type. Consequently, you must initialize a variable that is defined in this way when it is defined, as shown in the following code example.
Dim price = 20

In this example, the price variable is an implicitly typed variable. However, omitting the data type does not mean that you can later assign a value of a different type to price. The type of price is fixed, in much the same way as if you had explicitly declared it to be an integer variable. Implicitly typed variables are useful when you do not know, or it is difficult to establish explicitly, the type of an expression that you want to assign to a variable. Question: What is the syntax for declaring and assigning a variable?

Additional Reading
For more information about the keyword in Visual Basic, see the Visual Basic Keywords page at http://go.microsoft.com/fwlink/?LinkId=192890
For more information about naming conventions, see the General Naming Conventions page at http://go.microsoft.com/fwlink/?LinkId=192891
For more information about capitalization conventions, see the Capitalization Conventions page at http://go.microsoft.com/fwlink/?LinkId=192892
For more information about the Option Explicit compiler switch, see the Option Explicit Statement page, at http://go.microsoft.com/fwlink/?LinkID=210883&clcid=0x409

2-10

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is Variable Scope?

The scope of a variable determines the parts of a program that can access that variable. If you attempt to reference a variable outside its scope, the compiler will generate an error.

Levels of Scope
Variables can have one of the following levels of scope: Block Procedure Class Namespace

These levels of scope progress from the narrowest (block) to the widest (namespace). The following sections describe these different scopes.

Block Scope
A block is a set of statements that is enclosed within initiating and terminating declaration statements, such as a loop. If you declare a variable within a block, you can use it only within that block. The lifetime of the variable is still that of the entire block. The following code example shows how to declare a local variable called, area, with block-level scope.
If length > 10 Then Dim area As Integer = length * length End If

Procedure Scope
Variables that are declared within a procedure are not available outside that procedure. Only the procedure that contains the declaration can use the variable. When you declare variables in a block or

Using Visual Basic Programming Constructs

2-11

procedure, they are known as local variables. The following code example shows how to declare a local variable called, name, with procedure-level scope.
Sub ShowName() Dim name As String = "Bob" MessageBox.Show("Hello " & name) End Sub

Module or Class Scope


If you want the lifetime of a local variable to extend beyond the lifetime of the procedure, declare the variable at module or class-level scope. When you declare variables in a module, class, or structure, but not inside a procedure, they are known as module or class variables. You can assign a scope to class variables by using an access modifier. The following code example shows how to declare a local variable called, message, with class-level scope.
Private message As String Sub SetString() message = "Hello World!" End Sub Sub ShowString() MessageBox.Show(message) End Sub

Namespace Scope
When you declare variables at class level by using the Public keyword, they are available to all procedures within the namespace. The following code example shows you how to declare a variable called, message in one class that you can access in another class.
Public Class CreateMessage Public message As String = "Hello" End Class Public Class DisplayMessage Public Sub ShowMessage() Dim newMessage As New CreateMessage() MessageBox.Show(newMessage.message) End Sub End Class

Question: You are developing an application and you need to declare a variable that is accessible to two methods in the same class. What is the easiest way to achieve this?

Additional Reading
For more information about scopes, seethe Scope in Visual Basic page at http://go.microsoft.com/fwlink/?LinkID=210884&clcid=0x409

2-12

Programming in Visual Basic with Microsoft Visual Studio 2010

Converting a Value to a Different Data Type

When you are designing applications, you may need to convert data from one type to another. Conversions are necessary when a value of one type must be assigned to a variable of a different type. For example, you might need to convert the string value "99" that you have read from a text file into the integer value 99 that you can store in an integer variable. The process of converting a value of one data type to another is called conversion or casting.

Implicit and Explicit Conversions


There are two types of conversions in the .NET Framework: Implicit conversion. Automatically performed by the common language runtime (CLR) on operations that are guaranteed to succeed without losing information. A widening conversion can always be performed implicitly, whether the Option Strict statement is set to On or Off. Explicit conversion. Requires you to write code to perform a conversion that otherwise could lose information or produce an error.

An explicit conversion reduces the possibility of some bugs in your code and makes your code more efficient. Visual Basic prohibits implicit conversions that lose precision. However, be aware that some explicit conversions can yield unexpected results.

Implicit Conversions
An implicit conversion occurs when a value is converted automatically from one data type to another. The conversion does not require any special syntax in the source code. Visual Basic only allows safe implicit conversions, such as widening of integers. The following code example shows how data is converted implicitly from an integer to a Long type.
Dim a As Integer = 4 Dim b As Long b = a ' Implicit conversion of Integer to Long

Using Visual Basic Programming Constructs

2-13

This conversion always succeeds and never results in a loss of information. However, the converse conversion is not true; you cannot implicitly convert a Long value to an Integer type because this conversion risks losing information (the Long value might be outside the range that the Integer type supports). The following table shows the implicit type conversions that are supported in Visual Basic. From SByte Byte Short UShort Integer UInteger Long, ULong Single Char To Short, Integer, Long, Single, Double, and Decimal Short, UShort, Integer, UInteger, Long, ULong, Single, Double, and Decimal Integer, Long, Single, Double, and Decimal Integer, UInteger, Long, ULong, Single, Double, and Decimal Long, Single, Double, and Decimal Long, ULong, Single, Double, and Decimal Single, Double, and Decimal Double UShort, Integer, UInteger, Long, ULong, Single, Double, and Decimal

Explicit Conversions
In Visual Basic, you can use the CType function to perform explicit conversions. The CType function takes two argumentsthe object to convert, and the data type to which you want to convert. The syntax for performing an explicit conversion is shown in the following code example.
Dim variableName1 As DataType= CType(variableName2, convertDataType)

You can only perform meaningful conversions in this way, such as converting a Long to an Integer type. You cannot use a cast if the format of the data has to physically change, such as if you are converting a string to an integer. To perform these types of conversions, you can use the methods of the System.Convert class.

Using the System.Convert Class


The System.Convert class provides methods that can convert a base data type to another base data type. These methods have names such as ToDouble, ToInt32, ToString, and so on. All languages that target the CLR can use this class. You might find this class easier to use for conversions because Microsoft IntelliSense helps you locate the conversion method that you need. The following code example converts a String to an Integer type.
Dim possibleInt As String = "1234" Dim count As Integer = Convert.ToInt32(possibleInt)

In addition to the Convert.ToString method, many types implement their own ToString method. The following code example converts an Integer to a String type.
Dim number As Integer = 1234 Dim numberString As String = count.ToString()

2-14

Programming in Visual Basic with Microsoft Visual Studio 2010

Some of the built-in data types in Visual Basic provide a .TryParse method, which enables you to determine whether the conversion will succeed before you perform the conversion. The following code example shows how to convert a String to an Integer type by using the Integer.TryParse method.
Dim number As Integer = 0 Dim numberString As String = "1234" If Integer.TryParse(numberString, number) Then ' Conversion succeeded, number now equals 1234 Else ' Conversion failed, number now equals 0 End If

Using the Type Conversion Functions


The Visual Basic language contains a number of type conversion functions, specific to Visual Basic, and not the .NET Framework. The type conversion functions are compiled inline, so the conversion code is part of the code that evaluates the expression. Each of the type conversion function coerces an expression to a specific data type.

Note: In general, you should use the Visual Basic type conversion functions in preference to the .NET Framework methods, because the Visual Basic functions are designed for optimal interaction with Visual Basic code. The following table lists the different type conversion functions, and the name of each function spells the return data type. Function Name CBool(expression) CByte(expression) CChar(expression) CDate(expression) CDbl(expression) CDec(expression) CInt(expression) CLng(expression) CObj(expression) CSByte(expression) CShort(expression) CSng(expression) CStr(expression)

Using Visual Basic Programming Constructs

2-15

Function Name CUInt(expression) CULng(expression) CUShort(expression) Question: You are converting a String to an Integer type, but you are unsure whether the String will contain a valid Integer value. Which conversion approach should you use?

Additional Reading
For more information about the CType function, see the CType Function (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210885&clcid=0x409
For more information about the System.Convert class, see the Convert Class page at http://go.microsoft.com/fwlink/?LinkId=192894
For more information about the type conversion functions, see the Type Conversion Functions page at http://go.microsoft.com/fwlink/?LinkID=210894&clcid=0x409

2-16

Programming in Visual Basic with Microsoft Visual Studio 2010

Read-Only Variables and Constants

Read-only variables and constants enable you to store data just like you can with any other variables in Visual Basic. However, these variables have some subtle differences. You can use read-only variables and constants to store data that does not change. You can use read-only variables or constants for many values such as: The number of hours in a day. The speed of light. The number of degrees in a circle.

Comparing Read-Only Variables and Constants


There is a subtle difference between using a read-only variable and using a constant. When you use a constant in an application, you can only initialize the constant when it is declared. However, you can initialize a read-only variable in its declaration or in the constructor of the class that contains the readonly variable. Therefore, you can only define and initialize constants at design time. You cannot assign a different value to the constant when your application runs.

Syntax
You declare read-only variables by using the ReadOnly keyword, as the following code example shows.
ReadOnly variableName As DataType = Value

You declare constants by using the Const statement, as the following code example shows.
Const variableName As DataType = Value

Variables can only be declared by using the ReadOnly keyword at the class or modules level, whereas variables declared by using the Const statement can also be declared at the procedure or local level.

Using Visual Basic Programming Constructs

2-17

Examples
The following code example declares a constant to store the current date and time. This example uses the DateTime class and the Now property, which enables you to compute the current date and time at runtime. If you tried to use this approach with a constant, you would get a compile error.
ReadOnly currentDateTime As String = DateTime.Now.ToString

The following code example declares a PI constant to calculate the area and circumference of a circle with a radius of 5.
Const PI As Double = 3.14159 Dim radius As Double = 5 Dim area As Double= PI * radius * radius Dim circumference As Double= 2 * PI * radius

Question: What are the main differences between a constant and a read-only variable?

Additional Reading
For more information about constants, see the ReadOnly (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210886&clcid=0x409
For more information about constants, see the Const Statement (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210887&clcid=0x409

2-18

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 2

Using Expressions and Operators

The value that you assign to a variable can be a simple constant, but more frequently, it is a value that is the result of an expression that is evaluated at runtime. This lesson describes how to build an expression by using the various operators that Visual Basic provides. This lesson also describes operator precedence and how to control the order in which the elements in an expression are evaluated by using parentheses. Finally, this lesson explains the best practices for dynamically constructing string values.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of an expression. Describe the purpose of operators. Explain how to specify operator precedence. Explain the best practices for concatenating string values.

Using Visual Basic Programming Constructs

2-19

What Is an Expression?

Expressions are a central component of practically every Visual Basic application. This is because expressions are the fundamental constructs that you use to evaluate and manipulate data. Expressions are collections of operands and operators. These terms are defined as follows: Operands. Operands are values, for example, numbers and strings. They can be constant (literal) values, variables, properties, or method-call results. Operators. Operators define operations to perform on operands, for example, addition or multiplication. Operators exist for all of the basic mathematical operations in addition to some more advanced operations, such as logical comparison or the manipulation of the bits of data that constitutes a value.

All expressions are evaluated to a single value when your application runs. The type of value that an expression produces depends on the types of the operands and operators that you use. There is no limit to the length of expressions in Visual Basic applications, although in practice, you are limited by the memory of your computer and your patience when typing. However, it is usually advisable to use shorter expressions and assemble the results of expression-processing piecemeal. This makes it easier for you to see what your code is doing, in addition to making it easier to debug your code when it does not work as you expect it to.

Examples
You can combine the basic building blocks of operators and operands to make expressions as simple or as complex as you like. At the simplest end of the scale, you can use a single operand for an expression, as the following code example shows.
a

2-20

Programming in Visual Basic with Microsoft Visual Studio 2010

This may not seem very useful, but is, in fact, essential. For example, if you wanted to assign a value to a variable, you would require an expression of this type. You can build more complicated expressions by using operators, as the following code example shows.
a + 1

The + operator can operate on different data types, and the result of this expression depends on the data types of the operands. For example, if a is an integer, the result of the expression is an integer with the value 1 greater than a. If a is a Double, the result is a Double with the value 1 greater than a. The difference is subtle, but important. In the second case (a is a Double), the Visual Basic compiler has to generate code to convert the constant integer value 1 into the constant Double value 1 before the expression can be evaluated. The rule is that the type of the expression is the same as the type of the operands, although one or more of the operands might need to be converted to ensure that they are all compatible. This is important because the expression in the following code example contains two integer operands, so the result is an integer.
5 / 2

The value of the result is the integer value 2 (not 2.5). If you convert one of the operands to a Double, the Visual Basic compiler will convert the other operand to a Double, and the result will be a Double. Consequently, the expression in the following code example yields the Double value 2.5.
5.0 / 2

You can continue building up expressions with additional values and operators, as the following code example shows.
a + b - 2

This expression evaluates to the sum of variables a and b with the value 2 subtracted from the result. Some operators, such as +, can be used to evaluate expressions that have a range of types. For example, the expression in the following code example uses the + operator to concatenate two strings.
"Answer: " + c.ToString

The + operator uses an operand that is a result of a method call, ToString. This method converts the value of a variable into a string, whatever type it is. When concatenating strings, you should always use the & operator as shown in the following code example.
"Answer: " & c.ToString

The .NET Framework class library contains many additional methods that you can use to perform mathematical and string operations on data. Later in this module, you will see how you can create your own. The System.Math namespace in particular contains several useful methods that you can use in expressions, as the following code example shows.
b * System.Math.Tan(theta)

This expression evaluates to the product of the variable b and the tangent of the variable theta. Question: What is the value of the expression "99" + "1"?

Using Visual Basic Programming Constructs

2-21

What Are Operators?

Operators combine operands together into expressions. Visual Basic provides a wide range of operators that you can use to perform most fundamental mathematical and logical operations.

Operator Types
Operators fall into the following three categories: Unary. This type of operator operates on a single operand. For example, you can use the - operator as a unary operator. To do this, you place it immediately before a numeric operand, and it converts the value of the operand to its current value multiplied by 1. Binary. This type of operand operates on two values. This is the most common type of operator, for example,*, which multiplies the value of two operands. Ternary. There is only one ternary operator in Visual Basic. This is the If operator and it is used in conditional expressions.

Visual Basic Operators


The following table shows the operators that you can use in Visual Basic, grouped by type. Operator type Arithmetic Comparison String concatenation Logical operations Bitwise operations Casting Operators +, -, *, \, /, ^, Mod =, <>, <, >, <=, >=, TypeOf...Is, Is, IsNot, Like &, (+) And, Or, Xor, Not, AndAlso, OrElse And, Or, Xor, Not TryCast, DirectCast

2-22

Programming in Visual Basic with Microsoft Visual Studio 2010

Operator type Assignment Bit shift Type information Indirection and Address Conditional

Operators =, +=, -=, *=, &=, /=, \=, <<=, >>=, ^= <<, >> GetType AddressOf, Function, Sub If

Incrementing and Decrementing Variables


If you want to add 1 to a variable, you can use the + operator, as the following code example shows.
count = count + 1

If you want to subtract 1 from a variable, you can use the - operator, as the following code example shows.
count = count - 1

Using Compound Assignment Operators


If you want to add 42 to the value of a variable, you can combine the assignment operator and the addition operator. For example, the statement in the following code example adds 42 to a variable called answer. After this statement runs, the value of answer is 42 more than it was before.
answer = answer + 42

However, adding a value to a variable is so common that Visual Basic lets you perform this task in a shorthand manner by using the operator +=. To add 42 to answer, you can write the statement as shown in the following code example.
answer += 42

You can use this shortcut to combine any arithmetic operator with the assignment operator, as the following table shows. These operators are collectively known as the compound assignment operators. Replace this
variable = variable * number variable = variable / number variable = variable \ number variable = variable + number variable = variable - number

With this
variable *= number variable /= number variable \= number variable += number variable -= number

Using Visual Basic Programming Constructs

2-23

Question: Which operator would you use to calculate the remainder after dividing one integer value by another?

Additional Reading
For more information about the operators in Visual Basic, see the Operators (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210888&clcid=0x409

2-24

Programming in Visual Basic with Microsoft Visual Studio 2010

Specifying Operator Precedence

An expression can contain a complex series of operators and operands. The order in which the operators are processed and the operands are evaluated depends on the operators themselves. In many cases, there is not always a simple left-to-right flow of an expression. Each operator that you use to build an expression has an associated precedence that determines the order in which it is processed. Also, operators have a particular associativity, which determines the order in which they are processed in relation to operators with a matching precedence. To make expressions work in exactly the way you want, you can control the processing order by using parentheses.

Operator Precedence
Some operators have a higher precedence than others, which means that they are processed before other operators. For example, in the following code example, the division is performed before the addition.
a = b + 1 / 2

The following table shows the precedence of operators from highest at the top of the list to lowest at the bottom of the list. Precedence Highest Operator ^, +, - (unary) *, /, \ Mod, +, -, & <<, >> =, <>, <, <=, >, >=, Is, IsNot, Like, TypeOf...Is Not, And, AndAlso, Or, OrElse, Xor

Using Visual Basic Programming Constructs

2-25

Precedence Lowest

Operator Assignment operators

Operator Associativity
When you use operators of the same precedence, the operator associativity is used to determine the order of processing. Operators are either right-associative or left-associative. Left-associative operators are processed from left to right, for example, the / operator, as the following code example shows.
a / 5 / b

Here, a is divided by 5 and then the result of that division is divided by b. All binary operators are leftassociative, but assignment operators are right-associative, as the following code example shows.
a = b = c

Here, the value of c is assigned to b, and then the value of b is assigned to a. In practice, this rarely has an effect. Also, it is worth noting that for many operators, associativity is not always important, as the following code example shows.
a + 5 + b

In this code example, there is no difference to the result if you process the expression from left to right or right to left. However, the + operator is still defined as left-associative, which may have an effect in more advanced situations, for example, when you overload operators.

Using Parentheses
You can use parentheses to control the order of processing and change the precedence in an expression. Any part of an expression that you surround with parentheses is processed before the part of the expression that is not inside the parentheses, as the following code example shows.
a = (b + 1) / 2

Here, the (b + 1) part of the expression is processed first, and the result of that operation is divided by 2 to determine the value that is assigned to a. You can nest parentheses to further control the order of expression execution. Question: How can you control the order of processing in an expression?

2-26

Programming in Visual Basic with Microsoft Visual Studio 2010

Best Practices for Performing String Concatenation

Concatenating multiple strings in Visual Basic is simple to achieve by using the & operator. However, this is considered bad practice because strings are immutable. This means that every time you concatenate a string, you create a new string in memory and the old string is discarded. The following code example creates five string values as it runs.
Dim address As String= "23" address = address & ", Oxford Street" address = address & ", Thornbury"

An alternative approach is to use the StringBuilder class, which enables you to build a string dynamically and more efficiently. The following code example shows how to use the StringBuilder class.
Dim address As New StringBuilder() address.Append("23") address.Append(", Oxford Street") address.Append(", Thornbury") Dim concatenatedAddress As String = address.ToString()

Note: The StringBuilder class is in the System.Text namespace. Question: Why is concatenating strings considered bad practice, and how can you avoid it?

Using Visual Basic Programming Constructs

2-27

Additional Reading
For more information about the StringBuilder class, see the StringBuilder Class page at http://go.microsoft.com/fwlink/?LinkId=192898

2-28

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 3

Creating and Using Arrays

Variables hold a single value. Sometimes, you need to be able to store and process a set of values, and you often do not know in advance how big this set is going to be. For example, you may have a list of customers in a database that you want to retrieve and process. Arrays enable you to read and process a variable number of related data items. This lesson introduces arrays and explains how you can use them to store and manipulate data.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of an array. Explain how to create and initialize an array. Describe the common properties and methods that arrays expose. Explain how to access data in an array.

Using Visual Basic Programming Constructs

2-29

What Is an Array?

An array is a set of objects that are grouped together and managed as a unit. You can think of an array as a sequence of elements. All elements in an array have the same type. You can build simple arrays that have one dimension (a list), two dimensions (a table), three dimensions (a cube), and so on. Arrays have the following features: Every element in the array contains a value. Arrays are zero-indexed. The first item in an array is element 0. The length of an array is the total number of elements that it can contain. The lower bound of an array is the index of its first element. Arrays can be single-dimensional, multidimensional, or jagged. The rank of an array is the number of dimensions in the array.

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. Question: What is an array, and why would you want to use arrays in a Visual Basic application?

2-30

Programming in Visual Basic with Microsoft Visual Studio 2010

Creating and Initializing Arrays

When you declare an array, you specify the type of data that it contains and a name for the array. Declaring an array brings the array into scope, but does not actually allocate any memory for it. The CLR physically creates the array when you specify the size or redimension it by using the ReDim statement.

Single-Dimensional Arrays
To declare an array, you specify the type of elements in the array and use parentheses, (), to indicate that a variable is an array. You specify the size of the array when you declare the array, when you allocate memory for the array later by using the New keyword, or when you use the ReDim statement. The size of an array can be any integer expression. Alternatively, you can initialize an array and specify a set of values in braces, {}. In this case, the compiler uses the number of items in the set to determine the size of the array.

Note: If you do not initialize the elements in an array, the Visual Basic compiler initializes them for you automatically when you assign values by using the New keyword. The values that are used depend on the type of the elements in the array. For example, if the array contains numeric data, each element will be initialized to zero. If the array contains strings, each element will be initialized to the value Nothing. The following code example shows the syntax for declaring and initializing a single-dimensional array.
Dim arrayName1([Size]) As Type arrayName1 = New Type(Size) {element1, element2, ..., elementN} Dim arrayName2() [As Type] = {element1, element2, ..., elementN}

Using Visual Basic Programming Constructs

2-31

Multidimensional Arrays
An array can have more than one dimension. The number of dimensions corresponds to the number of indexes that are used to identify an individual element in the array. You can specify up to 32 dimensions, but you will rarely need more than three. You can declare a multidimensional array variable just as you declare a single-dimensional array, but you separate the dimensions by using commas. As with a single-dimensional array, you can also specify sets of data for each dimension and the compiler will use the number of elements in a set to size the corresponding dimension. You nest sets inside braces when you initialize a multidimensional array. The following code example shows the syntax for declaring and initializing a multidimensional array.
Dim arrayName1(Size1, Size2) As Type arrayName1 = [New Type(,)]{{element1, element2, element3}, {element4, element5, element6}} Dim arrayName2(,) [As Type] = {{element1, element2, element3}, {element4, element5, element6}}

When you add dimensions to an array, the total storage of the array increases dramatically. Therefore, you should avoid declaring an array that is larger than your requirements.

Jagged Arrays
Multidimensional arrays in Visual Basic must be rectangular; the number of elements in each dimension must be the same. However, Visual Basic also supports jagged arrays. A jagged array is simply an array of arrays, and the size of each array can vary. Jagged arrays are useful for modeling sparse data structures where you might not always want to allocate memory for every item if it is not going to be used. The following code example shows how to declare and initialize a jagged array. Note that you must specify the size of the first array, but you must not specify the size of the arrays that are contained within this array. You allocate memory to each array within a jagged array separately, by using the New keyword.
Dim jaggedArray(8)() jaggedArray(0) = New jaggedArray(1) = New jaggedArray(8) = New As Integer Integer(4) {1, 2, 3, 4, 5} ' Can specify different sizes Integer(6) {} Integer(20) {}

Implicitly Typed Arrays


Similar to implicitly typed variables where the compiler infers the type from the initializer, you can have implicitly typed arrays. When you use implicitly typed arrays, the type is inferred from the type of elements that are specified in the initializer. The elements that are specified must all be of the same type, otherwise the compiler will display an error. The following code example shows how to create an implicitly typed array.
Dim numbers() = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

The following code example shows an example that does not compile because the initializer contains multiple elements of different data types.
Dim mixed() = {1, DateTime.Now, True, False, 1.2}

Question: How do you declare a multidimensional array?

2-32

Programming in Visual Basic with Microsoft Visual Studio 2010

Additional Reading
For more information about arrays, see the Arrays in Visual Basic page at http://go.microsoft.com/fwlink/?LinkID=210889&clcid=0x409

Using Visual Basic Programming Constructs

2-33

Common Properties and Methods Exposed by Arrays

Arrays in Visual Basic are very useful for storing data and provide some useful functionality that enables you to manipulate data. All arrays in Visual Basic are actually instances of another type called, System.Array. The System.Array type provides common functionality that you can use from your own arrays. The following table describes some of the main properties and methods that arrays provide. Member BinarySearch Type Method Description Enables you to search a sorted single-dimensional array for a particular value by using a binary search algorithm
Dim numbers() As Integer = {1, 2, 3, 4, 5} Dim searchTerm As Object = 3 Dim result As Integer = Array.BinarySearch(numbers, searchTerm)

Clone

Method

Enables you to create a shallow copy of an array, which only copies the elements in the array, but does not copy objects that those elements might reference
Dim numbers() As Integer = {1, 2, 3, 4, 5} Dim numbersClone As Object = numbers.Clone()

CopyTo

Method

Enables you to copy all elements and element references in an array to a new array
Dim numbers() As Integer = {1, 2, 3, 4, 5} Dim newNumbers() As Integer ReDim newNumbers(oldNumbers.Length) oldNumbers.CopyTo(newNumbers, 0);

2-34

Programming in Visual Basic with Microsoft Visual Studio 2010

Member

Type

Description Enables you to iterate through each of the items in sequence in an array
Dim numbers() As Integer = {1, 2, 3, 4, 5} Dim results As IEnumerator = numbers.GetEnumerator() ' OR For Each number As Integer In numbers Next

GetEnumerator Method

GetLength

Method

Enables you to get the length of a specific dimension in an array


Dim numbers() As Integer = {1, 2, 3, 4, 5} Dim count As Integer= numbers.GetLength(0)

GetValue

Method

Enables you to get a value at a specific index in an array


Dim numbers() As Integer = {1, 2, 3, 4, 5} Dim number As Object = numbers.GetValue(2) ' returns the value 3

Length

Property

Enables you to get the number of items in the array


Dim numbers() As Integer = {1, 2, 3, 4, 5} Dim numberCount As Integer = numbers.Length ' Returns the value 5

Rank

Property

Enables you to get the number of dimensions in an array


Dim numbers() As Integer = {1, 2, 3, 4, 5} Dim rank As Integer= numbers.Rank ' Returns the value 1

SetValue

Method

Enables you to set a value at a specific index in an array


Dim numbers() As Integer = {1, 2, 3, 4, 5} numbers.SetValue(5000, 4) ' Changes the value 5 to 5000

Sort

Method

Enables you to sort the elements in a single-dimensional array


Dim numbers() As Integer = {5, 2, 1, 3, 4} Array.Sort(numbers) ' Sorted values: 1 2 3 4 5

Question: What members would you use to locate the last element in an array and then change that elements value?

Additional Reading
For more information about the System.Array class, see the Array Class page at http://go.microsoft.com/fwlink/?LinkId=192903

Using Visual Basic Programming Constructs

2-35

Accessing Data in an Array

You can access data in an array in several ways, such as specifying an index of a specific element, or iterating through the entire collection and returning each element in sequence.

Accessing Specific Elements


You can access specific elements by using an index that specifies the element that you want to return. Note that arrays are zero-indexed, so the first element in any dimension in an array is at index zero. The last element in a dimension is at index N-1, where N is the size of the dimension. If you attempt to access an element outside this range, the CLR throws an IndexOutOfRangeException exception. The following code example uses an index to access the element at index two. Remember that arrays use zero-based indexes, so this example returns the value 3.
Dim oldNumbers() As Integer = {1, 2, 3, 4, 5} Dim number As Integer = oldNumbers(2)

Iterating Through All Elements


You can iterate through an array by using a For loop. You can use the Length property of the array to determine when to stop the loop, as the following code example shows.

Note: The For statement is described in more detail later in this module.
Dim oldNumbers() As Integer = {1, 2, 3, 4, 5} For I As Integer = 0 To oldNumbers.Length - 1 Dim number As Integer = oldNumbers(i) Next

2-36

Programming in Visual Basic with Microsoft Visual Studio 2010

An alternative approach is to use the For Each loop (which is covered in more detail in Module 12). The For Each statement automatically retrieves all of the elements from the array in index order and assigns them to a variable that is specified in the For Each construct.
Dim oldNumbers() As Integer = {1, 2, 3, 4, 5} For Each number As Integer In oldNumbers ... Next

Question: Explain two approaches to accessing data in an array.

Using Visual Basic Programming Constructs

2-37

Lesson 4

Using Decision Statements

By default, Visual Basic executes the statements in a program in a sequential manner. However, you frequently need to specify that alternative statements should run depending on the value of an expression or a Boolean condition. To achieve this, Visual Basic provides conditional decision statements. This lesson introduces the different types of decision statements and explains how you can use them in your .NET Framework applications.

Lesson Objectives:
After completing this lesson, you will be able to: Explain how to use the If Else statement. Explain how to use the If operator. Explain how to use the Select Case statement. Describe when you should use each of the different decision constructs that are available in Visual Basic. Describe the guidelines that will help you decide when to choose a particular decision construct.

2-38

Programming in Visual Basic with Microsoft Visual Studio 2010

Using One-Way If Statements

One-way If statements are very useful when you want to execute a code statement based on a condition. The basic syntax for a one-way If statement is shown in the following code example.
If [condition] Then [code to execute]

In this code, if the expression [condition] evaluates to a Boolean true value, [code to execute] is executed. Notice that the condition must be enclosed in parentheses. You can execute more than one code statement. To do this, you delimit the code to run by using braces. This extends the syntax, as the following code example shows.
If [condition] Then [code to execute if condition is true] End If

It is standard practice to use this format even if you only execute a single line of code when [condition] is true, because it makes your code both easier to read and to extend. For example, if you want to run code when a variable, a, has a value of more than 50, you can use the code as shown in the following code example.
If a > 50 Then ' Add code to execute if a is greater than 50 here. End If

Using the Conditional Logical Operators


Visual Basic also provides two Boolean operators: the logical AndAlso operator, and the logical OrElse operator. Collectively, these are known as conditional logical operators. Their purpose is to combine two Boolean expressions or values into a single Boolean result. These binary operators are similar to the

Using Visual Basic Programming Constructs

2-39

equality and relational operators in that the value of the expressions in which they appear is either true or false. However, they differ in that the values on which they operate must be either true or false. The outcome of the AndAlso operator is True only if both of the Boolean expressions on which it operates are true. For example, the statement in the following code example assigns the value True to validPercentage only if the value of percent is greater than or equal to 0 and the value of percent is less than or equal to 100.
Dim validPercentage As Boolean If percent >= 0 AndAlso percent <= 100 Then validPercentage = True End If

Note: You can achieve the same result by assigning the value of the Boolean expression directly to the validPercentage variable, as the following code example shows.
validPercentage = percent >= 0 AndAlso percent <= 100

The outcome of the OrElse operator is true if either of the Boolean expressions on which it operates is true. You use the OrElse operator to determine whether any one of a combination of Boolean expressions is true. For example, the statement in the following code example assigns the value true to invalidPercentage if the value of percent is less than 0 or the value of percent is greater than 100.
Dim invalidPercentage As Boolean If percent< 0 OrElse percent > 100 Then invalidPercentage = True End If

Sometimes, when you evaluate an expression that uses the AndAlso and OrElse operators, it is not necessary to evaluate both operands to determine the overall result. For example, in the following code example, if the value of the age variable is greater than or equal to 20, the value of the entire expression is false, regardless of whether the value of the height variable is greater than 180.
Age < 20 AndAlso height > 180

Similarly, in the following code example, if the price variable has a value greater than or equal to 25, the value of the entire expression is true, regardless of whether the value of the weight variable is greater than 100.
Price>= 25 OrElse weight > 100

The AndAlso and OrElse operators in Visual Basic can handle these situations optimally, and in cases such as this, evaluation of the operands stops as soon as the result can be determined. What this means is that the expression height >180 in the first case and the expression weight > 100 in the second case will not be evaluated. This behavior is known as short-circuiting. Whenever you use the AndAlso or OrElse operators, you can also use the And or Or operators. The difference between the two operators relates to short-circuiting, meaning that AndAlso or OrElse operators apply a short-circuiting behavior, whereas the And or Or operators do not. Question: When must you close an If statement with a matching End If statement?

2-40

Programming in Visual Basic with Microsoft Visual Studio 2010

Using Either-Or If Statements

To provide an additional code block to run only if [condition] evaluates to False, you use the Else keyword, as the following code examples show.
If [condition] Then [code to execute if condition is true] Else [code to execute if condition is false] End If

If a > 50 Then ' Add code to execute if a is greater than 50 here. Else ' Add code to execute if a is less than or equal to 50 here. End If

Using the If Operator


As an alternative to using the If Then Else statements, in some simple cases, you can use the If ternary operator. The basic syntax to use the If operator is shown in the following code example.
Dim result As Type = If([condition], [True expression], [False expression])

In this code, if the expression [condition] evaluates to True, [True expression] is run, but if the [condition] evaluates to False, [False expression] is run. The following code example shows an example of using the If operator to check the value of a string, and then return a response.

Using Visual Basic Programming Constructs

2-41

Dim carColor As String = "green" Dim response As String = If(carColor = "red", "You have a red car", "You do not have a red car")

Question: Think of a scenario where you may want to use the If Then Else statement, and discuss.

Additional Reading
For more information about the If operator, seethe If Operator (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210889&clcid=0x409

2-42

Programming in Visual Basic with Microsoft Visual Studio 2010

Using Multiple-Outcome If Statements

You can combine several If statements to create a multiple-outcome statement. The following code example shows an example of the syntax.
If [condition] Then [code to execute if condition is True] ElseIf [condition2] [code to execute if condition is False and condition2 is True] Else [code to execute if condition and condition2 are both False] End If

It is important to note that if [condition] is True, the first block of code is run, regardless of the value of [condition2]. If this is the case, the remaining code is skipped, and [condition2] is not evaluated. This has performance consequences because it takes time for each condition to be evaluated. You can streamline your code by ensuring that the most commonly fulfilled condition or the condition that takes least processing to evaluate is tested first. The following code example shows an example of a multiple-outcome statement that uses this structure.
If a > 50 Then ' Add code ElseIf a > 10 ' Add code ' equal to Else ' Add code End If to execute if a is greater than 50 here. to execute if a is greater than 10 and less than or 50 here. to execute if a is less than or equal to 50 here.

Note that the Else statement must always come after all the ElseIf statements. Question: What is the purpose of the Else statement in an Else If construct?

Using Visual Basic Programming Constructs

2-43

Using the Select Case Statement

The Select Case statement enables you to run one of several blocks of code depending on the value of a variable or expression. These code blocks provide a very simple, easy-to-read structure, and offer an alternative approach to using If Else statements.

Select Case Statement Syntax


The basic syntax for the Select Case statement is shown in the following code example.
Select Case[expression to check] Case [test1] ... Case [test2] ... Case Else ... End Select

In a Select Case statement, you specify the expression to check in [expression to check], and supply values to compare with the variable in [testX]. Each comparison is tested in turn, so if [expression to check] equals [test1], the first code block is run, and if [expression to check] equals [test2], the second code block is run, and so on. There is no limit to the number of comparisons that you can include here, other than the memory of your computer. If no match is made, the block of code that is specified by Case Else is run. The Case Else block is optional. The type of value that [expression to check] returns must be an integer, string, or Boolean, and the values that are specified by the case statements must match this type. Each comparison ([testX]) can be a single value or multiple values separated by commas, as the following code example shows.
Select Case a Case 0

2-44

Programming in Visual Basic with Microsoft Visual Studio 2010

' Executed if a is 0. Case 1, 2, 3 ' Executed if a is 1, 2, or 3. Case Else ' Executed if a is any other value. End Select

The following code example shows an example of using a Select Case statement to check the value of a string.
Select Case carColor.ToLower Case "red" ' Red car Case "blue" ' Blue car Case Else ' Unknown car End Select

Question: With the exception of the default case, is the order of the cases in a Select Case statement important?

Using Visual Basic Programming Constructs

2-45

Guidelines for Choosing a Decision Construct

In this lesson, you have seen several structures that you can use to implement conditional statements. You should choose the structure to use based on the functionality that you want to implement. The guidelines for choosing a decision structure are as follows: Use an If structure when you have a single condition that controls the execution of a single block of code. A typical example of this is after you receive a user response to a yes/no question. Your code can use an If structure to run a block of code if the user responds with yes. Use an If Else structure when you have a single condition that controls the execution of one of two blocks of code. If you prompt a user to choose between two alternatives, this is the structure to use. Use an If ElseIf Else structure to run one of several blocks of code based on conditions that involve several variables. A good example of this is to check x-coordinates and y-coordinates for points that are in defined rectangular areas of a surface. You can use an expression for each rectangular area in each condition. You can also use this structure to check whether a single variable has a value in a certain range or ranges. Use a nested If structure to perform more complicated analysis of conditions that involve several variables. This structure gives you the greatest flexibility, but often leads to code that is difficult to read, with several levels of indentation. You can use this structure to test multiple variables and conditions to provide a multiple outcome structure. Use a Select Case statement to perform an action based on the possible values of a single variable.

2-46

Programming in Visual Basic with Microsoft Visual Studio 2010

You can use this structure, instead of a nested If structure, to make your code clearer when you are testing the value of a single variable.

Question: Which statement would you use to perform an action based on the possible values of a single variable?

Using Visual Basic Programming Constructs

2-47

Lesson 5

Using Iteration Statements

When you are writing the logic for your .NET Framework applications, it is common for you to want to repeatedly run a section of logic, either a set amount of times, or until a condition is met. To achieve this, you can use the iteration statements that Visual Basic provides. This lesson introduces the three main iteration statements that are available in Visual Basic and explains how you can use them in your applications.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the types of iteration statement that are available in Visual Basic. Explain how to use the While statement. Explain how to use the Do statement. Explain how to use the For statement. Describe the difference between the Exit and Continue statements in Visual Basic.

2-48

Programming in Visual Basic with Microsoft Visual Studio 2010

Types of Iteration Statements

There are three types of iteration statements that you can use in Visual Basic applications. Each of these statements works in a slightly different way and has a distinct purpose.

While Loops
A While loop enables you to run a block of code zero or more times. The While loops do not use a counter variable, although you can implement a counter variable by defining it outside the loop and manipulating it for each iteration. At the start of each iteration of a While loop, a Boolean condition is checked. If this condition evaluates to True, an iteration begins. If the condition evaluates to False, the loop terminates. The While loops can be very useful, if you do not know in advance whether you must perform iterative processing on a variable.

Do Loop Loops
The Do Loop loops are exactly like While loops, apart from one detail. In a Do Loop loop, the condition is evaluated at the end of the iteration, instead of at the start. This means that a Do Loop loop always runs at least once, unlike a While loop, which might not run at all. Do Loop loops are very useful when you do not know in advance how many times your code needs to run. For example, you can use a Do Loop loop to prompt a user repeatedly until the user provides valid input. Note: The Do...Loop statement can work exactly like a While...End While statement, but please see the later topics for more information.

Using Visual Basic Programming Constructs

2-49

For Loops
A For loop enables you to run code repeatedly a set number of times. To achieve this, you define a counter variable for the loop, the value of which is changed for each iteration. When the counter variable reaches the limit value that you define, the loop terminates. The code in the body of a For loop can use the value of the counter variable. This means, for example, that you can use a For loop to process each member of an array. You can also nest For loops with different counters so that you can process multidimensional arrays or examine pixels at specified coordinates. Question: Which iteration statement would you use to prompt a user for a valid response?

Additional Reading
For more information about the While...End While statement, see the While...End While Statement (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210890&clcid=0x409
For more information about the Do...Loop statement, seethe Do...Loop Statement (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210891&clcid=0x409
For more information about the For...Next statement, see the For...Next Statement (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210892&clcid=0x409

2-50

Programming in Visual Basic with Microsoft Visual Studio 2010

Using the While Loop

A While loop enables you to run a block of code zero or more times. At the beginning of each iteration, the While loop evaluates an expression. If this expression is True, the next iteration begins. If it is False, the loop terminates.

While Loop Syntax


The syntax of a While loop contains the following elements: The While keyword to define the While loop A condition that is tested at the start of each iteration A block of code to run for each iteration

The following code example shows the syntax of a While loop.


While[condition] ' Code to loop. End While

In the example, [condition] can be any expression that evaluates to a Boolean value. Each time an iteration begins, including the first time that the While loop is encountered, the expression is evaluated. If the expression is true, the iteration runs; otherwise, the loop is terminated.

Note: The condition is evaluated once for each iteration, before the iteration begins. The condition is not monitored while the iteration runs, so the last iteration is always completed before the loop terminates.

Using Visual Basic Programming Constructs

2-51

Example
The following code example shows a simple calculation that you can use to determine how many years it would take a bank balance to exceed a specified value with a specified interest rate.
Dim Dim Dim Dim balance As Double= 100 rate As Double = 2.5 targetBalance As Double= 1000 years As Integer = 0

While balance <= targetBalance balance *= (rate / 100) + 1 years += 1 End While

In this code, the condition, balance <= targetBalance, is checked before each iteration. If balance is more than targetBalance before the loop starts, no iterations will run and years will remain at its default value of 0.

Note: When you use the While loop, your code must change the Boolean condition; otherwise, your loop will iterate an infinite number of times. Question: When using the While loop, what type must the condition expression evaluate to?

2-52

Programming in Visual Basic with Microsoft Visual Studio 2010

Using the Do Loop

A Do loop enables you to run a block of code one or more times. At the end of each iteration, the Do loop evaluates a Boolean expression. If this expression is True, another iteration begins. If it is False, the loop is terminated.

Do Loop Syntax
The syntax of a Do loop contains the following elements: The Do keyword to define the Do loop A block of code to run for each iteration A condition that is tested at the end of each iteration

The following code example shows the syntax of a Do loop.


Do ' Code to loop. Loop While | Until [condition]

As with While loops, [condition] can be any expression that evaluates to a Boolean value. Each time an iteration ends, the expression is evaluated. If the expression is true, the next iteration runs; otherwise, the loop ends. Notice how you can use both While and Until as part of the Loop statement. This makes the Do Loop statement more flexible, because it can iterate while a condition is true, or until the condition evaluates to True. The following code example shows the syntax of a Do Loop loop, where the condition is specified as part of the Do statement.
Do While | Until [condition] ' Code to loop. Loop

Using Visual Basic Programming Constructs

2-53

The Do While Loop loop works much like the While loop.

Examples
A typical use of a Do Loop loop is to prompt a user for input and then continue to prompt the user if the input is invalid. The following code example illustrates this with code that requires a string that is at least five characters long.
Dim userInput As String = "" Do userInput = GetUserInput()

If userInput.Length < 5 Then ' You must enter at least 5 characters. End If Loop While userInput.Length < 5

In this code, a method called GetUserInput() obtains the user input and returns it as a string. The code for this method is not shown here.

Note: When you use the Do loop, your code must change the Boolean condition; otherwise, your loop will iterate an infinite number of times. Question: What is the minimum number of iterations that a Do Loop loop will perform?

2-54

Programming in Visual Basic with Microsoft Visual Studio 2010

Using the For Loop

A For loop enables you to run a block of code repeatedly and track the number of iterations that are performed by using a counter variable.

For Loop Syntax


The syntax of a For loop contains the following elements: 1. 2. 3. 4. The For keyword to define the For loop The loop specification, which consists of the following elements: A numeric variable to use for the counter (this can be a variable that is already defined or a variable that is defined as part of the loop specification) A starting value for the counter variable. A limit for the counter variable Instructions for how to modify the counter variable at the end of each iteration A block of code to run for each iteration

The syntax is shown in the following code example.


For [counter variable] = [starting value] To [limit] Step [counter modification] ' Code to loop. Next

The following table explains the purpose of the placeholders in this code. Placeholder [counter variable] Usage The identifier of an existing numeric variable, or a definition for a new numeric variable.

Using Visual Basic Programming Constructs

2-55

Placeholder [starting value] [limit]

Usage A number to assign to the counter variable for the first iteration. A condition to be tested at the start of each iteration. If the condition evaluates to True, the loop continues. If it evaluates to False, the loop ends.

[counter modification or An operation to perform at the end of each iteration. step]

Examples
The following code example shows a simple For loop that performs 10 iterations. The variable, i, is created and set to 0 for the first iteration, and it is incremented at the end of each iteration. When the value of i reaches 10, the loop terminates (the loop does not run when i is 10).
For i As Integer = 0 To 9 ' Code to loop, which can use i. Next

The following code example extends this code to use a step of 2 instead of 1.
For i As Integer = 0 To 9 Step 2 ' Code to loop, which can use i. Next

This code loops five times, with values of i for each iteration of 0, 2, 4, 6, and 8. In these code examples, the control variable, i, is created as part of the For construct. The scope of i is the body of the For loop. When the loop finishes, i is no longer available. If you need to examine the value of the control variable outside the loop, you can declare a variable before the loop starts, and use that, as the following code example shows.
Dim j As Integer For j = 0 To 9 ' Code to loop, which can use j. Next ' j is also available here

You can use nested For loops that each define their own counter variable. This idiom is useful if you need to process multidimensional arrays. The following code example shows how to use two nested For loops to process the characters in an array of strings in reverse order.
Dim strings() As String = {"One", "Two", "Three", "Four", "Five"} Dim result As String = "" For stringIndex As Integer= 0 to strings.Length - 1 For charIndex As Integer = _ strings(stringIndex).Length - 1 To charIndex + 1 Step -1 result += strings(stringIndex)(charIndex) Next

Next

After the variables are initialized, the outer For loop iterates with counter values of 0, 1, 2, 3, and 4 (strings.Length is 5). For each value of this counter, the corresponding string in the array is used to determine the starting value for the counter of the inner loop.

2-56

Programming in Visual Basic with Microsoft Visual Studio 2010

The inner loop iterates through the string, character by character, starting at the end of the string and working back (the charIndex control variable is set to the length of the string and decremented at the end of each iteration. The loop stops when charIndex is less than zero). The body of the inner loop retrieves the character that charIndex indexed from the string referenced by stringIndex in the array. Note that you can retrieve individual characters from a string by using array-like index access. When every character in the string has been processed, the first iteration of the outer loop finishes, and the outer loop begins its second iteration. This process continues until every character in every string is processed. The value of result when this code finishes is the string enoweerhruoevi. Question: What are the four components of a For loop?

Using Visual Basic Programming Constructs

2-57

Exit and Continue Statements

When you use the various While, Do, and For loop constructs, you can also use the Exit and Continue statements to modify the behavior of the loop.

Note: Use Exit and Continue with caution. They can lead to code that is difficult to understand and maintain.

Exit Statement
The Exit statement enables you to exit the loop entirely, and skip to the next line of code outside the loop. The Exit statement is particularly useful if you are iterating through an array looking for a record, and you want to exit the loop when you have found the record. The following code example shows how to exit a While loop if the value 5 is found in an array.
Dim oldNumbers()As Integer = {1, 2, 3, 4, 5, 6, 7, 8} Dim count As Integer = 0 While oldNumbers.Length > count If oldNumbers(count) = 5 Then Exit While End If count += 1 End While

The Exit statement produces identical behavior when used with the While, Do, and For loops.

Continue Statement
The Continue statement is similar to the Exit statement, except that instead of exiting the loop entirely, you skip the remaining code in the current iteration, test the condition, and then start the next iteration of

2-58

Programming in Visual Basic with Microsoft Visual Studio 2010

the loop. The following code example shows how to add additional logic to a For loop that will not run when the value 5 is found.
Dim oldNumbers()As Integer = {1, 2, 3, 4, 5, 6, 7, 8} For count As Integer = 0 To oldNumbers.Length - 1 If oldNumbers(count) = 5 Then Continue For End If ' Code that won't be hit when the value 5 is found Console.WriteLine(oldNumbers(count))

Next

The Continue statement produces identical behavior when it is used with the While and Do loops. The only subtle difference is that when it is used with the For loop, the remaining code in the current iteration is skipped as with the other loops, but the modifier in the For specification is incremented before the condition is tested, and the next iteration begins. Question: What is the difference between the Exit and Continue statements?

Using Visual Basic Programming Constructs

2-59

Lab: Using Visual Basic Programming Constructs

Objectives:
After completing this lab, you will be able to: Use Visual Basic data types and expressions to help implement a numeric algorithm. Use Visual Basic programming constructs to perform common programming tasks. Use arrays to store and process data.

Introduction
In this lab, you will create several applications that implement some common algorithms. This will help you to become familiar with using the Visual Basic syntax. You will also learn many of the core Visual Basic programming constructs.

Important: The purpose of these exercises, and the remaining exercises throughout this course, is not to make you familiar with mathematical algorithms or engineering processes. Rather, the aim is to enable you to take a description of a problem or algorithm and use Visual Basic to implement a solution.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

2-60

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Scenario

Fabrikam, Inc. produces a range of highly sensitive measuring devices that can repeatedly measure objects and capture data. You have been asked to implement some embedded functionality that several scientific instruments require. You will write Visual Basic applications to build and test your implementations.

Using Visual Basic Programming Constructs

2-61

Exercise 1: Calculating Square Roots with Improved Accuracy


In this exercise, you will write a program that prompts the user for a numeric value and then uses Newton's method to calculate the square root of this number. You will display the result, and compare it to the Double value that is calculated by using the Math.Sqrt method in the .NET Framework class library.

Scenario
Software is being developed to support devices that perform scientific analysis. The software requires applications to perform calculations with a high degree of accuracy. The .NET Framework uses the Double type to perform many of the calculations. The Double type has a very large range, but the accuracy is not always sufficient. The Decimal type provides a higher degree of accuracy at the cost of a smaller range and increased memory requirements. However, this accuracy is important. One scientific calculation requires the ability to calculate square roots to a high degree of accuracy. You decide to implement Newton's algorithm for estimating and successively refining square roots, but generate the result by using the Decimal type. The process that Newton used for calculating the square root of 10 is as follows: 1. Start with an initial guess: use the value that you want to find the square root of, and divide by 2. In this case, 10 / 2, has the value 5. 2. Refine the guess by dividing the original number by the previous guess, adding the value of the previous guess, and dividing the entire result by 2: calculate ((number / guess) + guess) / 2. In this example, calculate ((10 / 5 ) + 5 ) / 2 = 3.5 The answer 3.5 then becomes the next guess. 3. Perform the calculation ((number / guess) + guess) / 2 again, with the new guess In this example, calculate ((10 / 3.5) + 3.5) / 2 = 3.17857 3.17857 is then the next guess. 4. Repeat this process until the difference between subsequent guesses is less than some predetermined amount. The final guess is the square root of 10 to the accuracy that was specified by this predetermined amount.

The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. Create a new WPF application project. Create the user interface. Calculate square roots by using the Math.Sqrt method of the .NET Framework. Calculate square roots by using Newton's method. Test the application.

Task 1: Create a new WPF Application project.


Open Microsoft Visual Studio 2010. Create a new project named, SquareRoots, in the D:\Labfiles\Lab02\Ex1\Starter folder, by using the WPF Application project template.

Task 2: Create the user interface.


Add a TextBox, a Button, and two Label controls to the MainWindow window. Place them anywhere in the window.

2-62

Programming in Visual Basic with Microsoft Visual Studio 2010

Using the Properties window, set the properties of each control by using the values in the following table. Leave any other properties at their default values. Control Type TextBox Property Name Name Height HorizontalAlignment Margin Text VerticalAlignment Width Button Name Content Height HorizontalAlignment Margin VerticalAlignment Width Label Name Content Height HorizontalAlignment Margin VerticalAlignment Width Label Name Content Height HorizontalAlignment Margin VerticalAlignment Width Property Value InputTextBox 28 Left 12,12,0,0 0.00 Top 398 CalculateButton Calculate 23 Right 0,11,12,0 Top 75 FrameworkLabel 0.00 (Using .NET Framework) 28 Left 12,41,0,0 Top 479 NewtonLabel 0.00 (Using Newton) 28 Left 12,75,0,0 Top 479

Using Visual Basic Programming Constructs

2-63

The MainWindow window should look like the following image.

Task 3: Calculate square roots by using the Math.Sqrt method of the .NET Framework.
Create an event handler for the Click event of the button. In the CalculateButton_Click method, add code to read the data that the user enters in the InputTextBox control, and then convert it into a Double. Store the double value in a variable named, numberDouble. Use the TryParse method of the Double type to perform the conversion. If the text that the user enters is not valid, display a message box with the text "Please enter a double," and then execute a Return statement to exit the method.

Note: You can display a message in a message box by using the MessageBox.Show method. Check that the value that the user enters is a positive number. If it is not, display a message box with the text, "Please enter a positive number.", and then return from the method. Calculate the square root of the value in the numberDouble variable by using the Math.Sqrt method. Store the result in a double variable named, squareRoot. Format the value in the squareRoot variable by using the layout shown in the following code example, and then display it in the FrameWorkLabelLabel control.
99.999 (Using the .NET Framework)

Build and run the application to test your code. Use the test values that are shown in the following table, and then verify that the correct square roots are calculated and displayed (ignore the "Using Newton" label for the purposes of this test). Test value 25 625 Expected result 5 25

2-64

Programming in Visual Basic with Microsoft Visual Studio 2010

Test value 0.00000001 10 Fred 10 8.8 2.0 2

Expected result 0.0001 Message box appears with the message "Please enter a positive number" Message box appears with the message "Please enter a double" 3.16227766016838 2.96647939483827 1.4142135623731 1.4142135623731

Close the application and return to Visual Studio.

Task 4: Calculate square roots by using Newton's method.


In the CalculateButton_Click method, after the code that you added in the previous task, create a decimal variable named, numberDecimal. Initialize this variable with the data that the user enters in the InputTextBox control, but convert it into a Decimal this time (previously, you read it as a Double). If the text that the user enters is not valid, display a message box with the text, "Please enter a decimal.", and then execute a Return statement to exit the method.

Note: This step is necessary because the Decimal and Double types have different ranges. A number that the user enters that is a valid Double might be out of range for the Decimal type. Declare a decimal variable named, delta, and initialize it to the value of the expression Math.Pow(10, 28). This is the smallest value that the Decimal type supports, and you will use this value to determine when the answer that is generated by using Newton's method is sufficiently accurate. When the difference between two successive estimates is less than this value, you will stop.

Note: The Math.Pow method returns a double. You will need to use the Convert.ToDecimal method to convert this value to a decimal before you assign it to the delta variable. Declare another Decimal variable named, guess, and initialize it with the initial guess at the square root. This initial guess should be the result of dividing the value in numberDecimal by 2. Declare another decimal variable named, result. You will use this variable to generate values for each iteration of the algorithm, based on the value from the previous iteration. Initialize the result variable to the value for the first iteration by using the expression ((numberDecimal / guess) + guess) / 2. Add a While loop to generate further refined guesses. The body of the While loop should assign result to guess, and generate a new value for result by using the expression ((numberDecimal / guess) + guess) / 2. The While loop should terminate when the difference between result and guess is less than or equal to delta.

Note: Use the Math.Abs method to calculate the absolute value of the difference between result and guess. Using Newton's algorithm, it is possible for the difference between the two

Using Visual Basic Programming Constructs

2-65

variables to alternate between positive and negative values as it diminishes. Consequently, if you do not use the Math.Abs method, the algorithm might terminate early with an inaccurate result. When the While loop has terminated, format and display the value in the result variable in the NewtonLabel control. Format the data in a similar manner to the previous task.

Task 5: Test the application.


Build and run the application in Debug mode to test your code. Use the test values shown in the following table, and verify that the correct square roots are calculated and displayed. Compare the value in the two labels, and then verify that the square roots that are calculated by using Newton's method are more accurate than those calculated by using the Math.Sqrt method. Test value 25 625 0.00000001 10 8.8 2.0 2 .NET Framework 5 25 0.0001 3.16227766016838 2.96647939483827 1.4142135623731 1.4142135623731 Newton's algorithm 5.000000000000000000000000000 25.000000000000000000000000000 0.0001000000000000000000000000 3.1622776601683793319988935444 2.9664793948382651794845589763 1.4142135623730950488016887242 1.4142135623730950488016887242

As a final test, try the value 0.0000000000000000000000000001 (27 zeroes after the decimal point). Can you explain the result? Close the application and return to Visual Studio.

2-66

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 2: Converting Integer Numeric Data to Binary


In this exercise, you will create another application that enables the user to enter an integer value, generate a string that holds the binary representation of this value, and then display the result.

Scenario
Another device has the requirement to display Decimal numeric data in a binary format. You have been asked to develop some code that can convert a non-negative decimal integer value into a string that contains the binary representation of this value. The process for converting the decimal value 6 into its binary representation is as follows: 1. Divide the integer by 2, save the integer result, and use the remainder as the first binary digit. In this example, 6 / 2 is 3, and the remainder is 0. Save the character, "0", as the first character of the binary representation. 2. Divide the result of the previous division by 2, save the result, and use the remainder as the next binary digit. In this example, 3 / 2 is 1, and the remainder is 1. Save the character, "1", as the next character of the binary representation. 3. Repeat the process until the result of the division is zero. In this example, 1 / 2 is zero, and the remainder is 1. Save the character, "1", as the final character of the binary representation. 4. Display the characters saved in reverse order. In this example, the characters were generated in the sequence "0", "1", 1", so display them in the order of "1", "1","0". The value 110 is the binary representation of the decimal value, 6. The main tasks for this exercise are as follows: 1. 2. 3. 4. Create a new WPF Application project. Create the user interface. Add code to generate the binary representation of an integer value. Test the application.

Task 1: Create a new WPF application project.


Create a new project named, IntegerToBinary, in the D:\Labfiles\Lab02\Ex2\Starter folder, by using the WPF Application project template.

Task 2: Create the user interface.


Add a TextBox, Button, and Label control to the MainWindow window. Place them anywhere in the window. Using the Properties window, set the properties of each control by using the values in the following table. Leave any other properties at their default values. Control TextBox Property Name Height Value InputTextBox 28

Using Visual Basic Programming Constructs

2-67

Control

Property HorizontalAlignment Margin Text VerticalAlignment Width

Value Left 12,12,0,0 0 Top 120 ConvertButton Convert 23 Left 138,12,0,0 Top 75 BinaryLabel 0 28 Left 12,41,0,0 Top 120

Button

Name Content Height HorizontalAlignment Margin VerticalAlignment Width

Label

Name Content Height HorizontalAlignment Margin VerticalAlignment Width

The MainWindow window should look like the following image.

2-68

Programming in Visual Basic with Microsoft Visual Studio 2010

Task 3: Add code to generate the binary representation of an integer value.


Create an event handler for the Click event of the button. In the ConvertButton_Click method, add code to read the data that the user enters in the InputTextBox control, and then convert it into an Integer type. Store the integer value in a variable named, i. Use the TryParse method of the Integer type to perform the conversion. If the text that the user enters is not valid, display a message box with the text, "TextBox does not contain an integer," and then run a Return statement to exit the method. Check that the value that the user enters is not a negative number (the integer-to-binary conversion algorithm does not work for negative numbers). If it is negative, display a message box with the text, "Please enter a positive number or zero.", and then return from the method. Declare an integer variable named, remainder, and initialize it to zero. You will use this variable to hold the remainder after dividing i by 2 during each iteration of the algorithm. Declare a StringBuilder variable named, binary, and instantiate it. You will use this variable to construct the string of bits that represent i as a binary value. Import the System.Text namespace by using the Error Correction Options dialog box. You can do this by placing the cursor over the last character, r, in the text, StringBuilder, move the cursor over the exclamation icon, click the dropdown arrow, and then click Import 'System.Text'. Add a Do Until loop that performs the following tasks: a. b. c. Calculates the remainder after dividing i by 2, and then stores this value in the remainder variable. Divides i by 2, by using the integer division operator (\). Prefixes the value of remainder to the start of the string being constructed by the binary variable.

Terminate the Do Until loop when i is equal to zero.

Note: To prefix data into a StringBuilder object, use the Insert method of the StringBuilder class, and then insert the value of the data at position 0.

Using Visual Basic Programming Constructs

2-69

Display the value in the binary variable in the BinaryLabelLabel control.

Note: Use the ToString method to retrieve the string that the StringBuilder object constructs. Set the Content property of the Label control to display this string.

Task 4: Test the application.


Build and run the application to test your code. Use the test values shown in the following table, and verify that the binary representations are generated and displayed. Test value 0 1 1 10.5 Fred 4 999 65535 65536 1 Message box appears with the message, "Please enter a positive number or zero." Message box appears with the message, "Text Box does not contain an integer." Message box appears with the message, "Text Box does not contain an integer." 100 1111100111 1111111111111111 10000000000000000 Expected result

Close the application and return to Visual Studio.

2-70

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 3: Multiplying Matrices


In this exercise, you will create another WPF application. This WPF application will provide a user interface that enables the user to provide the data for two matrices and store this data in rectangular arrays. The application will calculate the product of these two arrays and display them.

Scenario
Some of the devices that Fabrikam, Inc. has developed perform calculations that involve sets of data that are held as matrices. You have been asked to implement code that performs matrix multiplication. You decide to test your code by building a WPF application that enables a user to specify the data for two matrices, calculate the product of these matrices, and then view the result. Multiplying matrices is an iterative process that involves calculating the sum of the products of the values in each row in one matrix with the values in each column in the other, as the following image shows.

This image shows a 34 matrix multiplying a 45 matrix. This will result in a 35 matrix.

Note: The number of columns in the first matrix must match the number of rows in the second matrix. The starter code that is provided for you in this lab ensures that this is always the case. To calculate each element xa,b in the result matrix, you must calculate the sum of the products of every value in row a in the first matrix with every value in column b in the second matrix. For example, to calculate the value placed at x3,2 in the result matrix, you calculate the sum of the products of every value in row 3 in the first matrix with every value in column 2 in the second matrix: (53)+(42)+(26)+(31) = 38 You perform this calculation for every element in the result matrix. The main tasks for this exercise are as follows: 1. 2. 3. 4. Open the MatrixMultiplication project and examine the starter code. Define the matrix arrays and populate them with the data in the Grid controls. Multiply the two input matrices and calculate the result. Display the result and test the application.

Task 1: Open the MatrixMultiplication project and examine the starter code.
Open the MatrixMultiplication project located in the D:\Labfiles\Lab02\Ex3\Starterfolder. Examine the user interface that the MainWindow window defines.

The user interface contains three Grid controls, three ComboBox controls, and a Button control. When the application runs, the first Grid control, labeled, Matrix 1, represents the first matrix, and the second Grid control, labeled, Matrix 2, represents the second matrix. The user can specify the dimensions of the matrices by using the ComboBox controls and then enter data into each cell in them. There are several rules that govern the compatibility of matrices to be multiplied together, and Matrix 2 is

Using Visual Basic Programming Constructs

2-71

automatically configured to have an appropriate number of rows based on the number of columns in Matrix 1. When the user clicks the Calculate button, Matrix 1 and Matrix 2 are multiplied together, and the result is displayed in the Grid control labeled, Result Matrix. The dimensions of the result are determined by the shapes of Matrix 1 and Matrix 2. The following image shows the completed application running. The user has multiplied a 23 matrix with a 32 matrix, and the result is a 33 matrix.

Task 2: Define the matrix arrays and populate them with the data in the Grid controls.
In Visual Studio, review the Task List. You can view the Task List from the View menu, where you point to Other Windows, and then click Task List. If the Task List displays User Tasks, in the dropdown list box, click Comments. Open the MainWindow.xaml.vb file. At the top of the MainWindow class, remove the comment TODO: Task 2 declare variables, and then add statements that declare three two-dimensional arrays named, matrix1, matrix2, and result. The type of the elements in these arrays should be Double, and the size of each dimension should be set to 0, because the arrays will be dynamically sized based on the input that the user provides. The first dimension will be set to the number of columns, and the second dimension will be set to the number of rows. In the Task List, double-click the task TODO: Task 2 Copy data from input Grids. This task is located in the CalculateButton_Click method. In the CalculateButton_Click method, remove the comment TODO: Task 2 Copy data from input Grids. Add two statements that call the getValuesFromGrid method. This method (provided in the

2-72

Programming in Visual Basic with Microsoft Visual Studio 2010

starter code) expects the name of a Grid control and the name of an array to populate with data from that Grid control. In the first statement, specify that the method should use the data in Matrix1Grid to populate matrix1. In the second statement, specify that the method should use the data from Matrix2Grid to populate matrix2. Remove the comment TODO: Task 2 Get the matrix dimensions. Declare three integer variables named, m1columns_m2rows, m1rows, and m2columns. Initialize m1columns_m2rows with the number of columns in the matrix1 array (this is also the same as the number of rows in the matrix2 array) by using the GetLength method of the first dimension of the array. Initialize m1rows with the number of rows in the matrix1 array by using the GetLength method of the second dimension of the array. Initialize m2columns with the number of columns in the matrix2 array.

Task 3: Multiply the two input matrices and calculate the result.
In the CalculateButton_Click method, delete the comment TODO: Task 3 Calculate the result. Define a For loop that iterates through all of the rows in the matrix1 array. The dimensions of an array are integers, so use an integer variable named, row, as the control variable in this For loop. Leave the body of the For loop blank; you will add code to this loop in the next step. In the body of the For loop, add a nested For loop that iterates through all the columns in the matrix2 array. Use an integer variable named, column, as the control variable in this For loop. Leave the body of this For loop blank. The contents of each cell in the result array are calculated by adding the product of each item in the row identified by the row variable in matrix1 with each item in the column identified by the column variable in matrix2. You will require another loop to perform this calculation, and a variable to store the result as this loop calculates it. Add another nested For loop after the declaration of the accumulator variable. This loop should iterate through all the columns in the current row in the matrix1 array. Use an integer variable named, cell, as the control variable in this For loop. Leave the body of this For loop blank. In the body of this For loop, multiply the value in matrix1(cell, row) with the value in matrix2(column, cell), and then add the result to accumulator. After the closing of the innermost For loop, store the value in accumulator in the result array. The value should be stored in the cell that the column and row variables have identified.

Task 4: Display the results and test the application.


In the CalculateButton_Click method, delete the comment TODO: Task 4 Display the result. The starter code contains a method named, initializeGrid, which displays the contents of an array in a Grid control in the WPF window. Add a statement that calls this method. Specify that the method should use the grid3Grid control to display the contents of the result array. Build the solution and correct any errors. Run the application. In the MainWindow window, define Matrix 1 as a 32 matrix and define Matrix 2 as a 33 matrix.

Note: The number of rows in the Matrix 2 matrix is determined by the number of columns in the Matrix 1 matrix. Specify the values for the cells in the matrices as shown in the following tables.

Using Visual Basic Programming Constructs

2-73

Matrix 1 1 3 Matrix 2 2 4 6 8 10 12 14 16 18 5 7 9 11

Click Calculate. Verify that the Result matrix displays the values in the following table. Result 32 44 50 86 68 128

Change the data in Matrix 2 as shown in the following table. Matrix 2 1 0 0 0 1 0 0 0 1

Click Calculate. Verify that the Result matrix displays the values as shown in the following table. Result 1 3 5 7 9 11

Matrix 2 is an example of an identity matrix. When you multiply a matrix by an identity matrix, the result is the same data as defined by the original matrix (it is the matrix equivalent of multiplying a value by 1 in regular arithmetic). In this case, the values in the Result matrix are the same as those in Matrix 1. Change the data in Matrix 2 again, as shown in the following table. Matrix 2 1 0 0 0 1 0 0 0 1

2-74

Programming in Visual Basic with Microsoft Visual Studio 2010

Click Calculate. Verify that the Result matrix displays the values as shown in the following table. Result 1 3 5 7 9 11

This time, the values in Result are the same as those in Matrix 1, except that the sign of each element is inverted (Matrix 2 is the matrix equivalent of 1 in regular arithmetic). Close the MainWindow window. Close Visual Studio.

Using Visual Basic Programming Constructs

2-75

Lab Review

Review Questions
1. 2. 3. Which .NET Framework class and method did you use to calculate the square root? Which .NET Framework class did you use to construct the string that represented the binary number, and what benefits does this class provide? Which loop construct did you use to iterate through all the rows in the matrix1 array, and why was it a good choice?

2-76

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Review and Takeaways

Review Questions
1. 2. 3. 4. 5. If you omit the type when declaring a variable, what does it mean? How can you control the order of processing in an expression? What is the purpose of arrays? Name an alternative approach to using the If Else statements. Which loop construct should you use to run a block of code one or more times?

Best Practices Related to Using Visual Basic Constructs


Supplement or modify the following best practices for your own work situations: When you choose a data type, ensure that you select one that is appropriate to the type of data that you are processing. For example, do not create a Double variable for processing integer data because this requires that the compiler generates additional code to convert your integer data into Double values. Instead of concatenating strings by using the & operator, use the StringBuilder class, or use the static Format method of the String class. When you access elements in an array by using the index of an element, ensure that you test to see whether the index exists. If the index does not exist, you will get an IndexOutOfRange exception. Avoid too many nested If Else and loop statements because they can complicate debugging your applications. Avoid using Exit and Continue statements in loops, unless you really need them.

Declaring and Calling Methods

3-1

Module 3
Declaring and Calling Methods
Contents:
Lesson 1: Defining and Invoking Methods Lesson 2: Specifying Optional Parameters and ByRef Parameters Lab: Declaring and Calling Methods 3-3 3-23 3-29

3-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

A key part of developing any application is dividing the solution into logical components. In objectoriented languages such as Microsoft Visual Basic, a method is a unit of code that is designed to perform a discrete piece of work. This module introduces methods and describes how to define and use them.

Objectives:
After completing this module, you will be able to: Describe how to create and invoke methods. Define and call methods that can take optional parameters and ByRef parameters.

Declaring and Calling Methods

3-3

Lesson 1

Defining and Invoking Methods

This lesson introduces methods and explains how to create and call them. This lesson also explains how to create overloaded methods and methods that can take a variable number of parameters. Finally, this lesson explains how to refactor a method from an existing code block and how to create unit tests to test a methods functionality.

Lesson Objectives:
After completing this lesson, you will be able to: Describe how to create a method that takes parameters and returns a value. Describe how to call a method and handle a return value. Describe how to create and call overloaded methods. Describe how to use parameter arrays to pass variable numbers of arguments to methods. Describe how to refactor code into a method. Describe how to create a unit test for a method.

3-4

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is a Method?

Procedures or methods implement the behavior of a type. A method contains a block of code that defines an action that a type can perform. All code belongs to a method. You cannot write a Visual Basic program that does not contain at least one method.

Note: Visual Basic uses the terms procedure and method synonymously. But, methods are procedures when located in a class or structure, and procedures are located in a module.
Note: Some Visual Studio project templates will generate the startup code or method for you. This includes the Windows Forms application and Windows Presentation Foundation (WPF) application templates. The ability to define and call methods is a fundamental component of object-oriented programming because methods enable you to encapsulate operations that protect data that is stored inside a type. Typically, any application that you develop by using the Microsoft .NET Framework and Visual Basic will have many methods, each with a specific purpose. Some methods are fundamental to the operation of an application. For example, all Visual Basic applications must have a method called Main that defines the entry point or starting point for the application. When the user runs a Visual Basic application, the common language runtime (CLR) executes the Main method for that application.
Note: You can set the startup object or method by using the Project Designer in Visual Studio. Methods can be designed for internal use by a type, and as such are hidden from other types. Other methods may be designed to enable other types to request that an object performs an action and are exposed to the outside world. Visual Basic supports two classes of methods.

Declaring and Calling Methods

3-5

Instance methods. These methods execute in the context of a specific object and can directly access data that belongs to the object. For example, the ToString method that was described in Module 2 is an instance method. You invoke instance methods by specifying the object that they belong to. Shared methods. These methods are associated with a type rather than a specific object. Examples of shared methods include those that belong to the Convert class that was described in Module 2, such as Convert.ToInt32. You invoke these methods by specifying a type rather than an object.

Note: Module 7, Encapsulating Data and Methods describes the differences between instance and shared methods in detail. Question: Why do you need to use methods when developing a .NET Framework application with Visual Basic?

3-6

Programming in Visual Basic with Microsoft Visual Studio 2010

Creating a Method

A method contains two elements, method specification and method body. The method specification defines the method name, the parameters that the method can take, the return type of the method, and the method accessibility. The combination of the name of the method and its parameter list are referred to as the method signature. Each method in a class must have a unique signature.
Note: Method accessibility will be described in more detail in Module 7, Encapsulating Data and Methods.

Naming Methods
A method name has the same syntactic restrictions as a variable name. A method name must start with a letter or an underscore, and can only contain letters, underscores, and numeric characters. This is enforced by the compiler. Remember that Visual Basic is case-insensitive, so a class cannot contain two methods that have the same name but differ only in the case of one or more letters. The following guidelines are recommended best practices when you choose the name of a method: Use verbs or verb phrases to name methods. This helps other developers understand the structure of your code. Use Pascal case. Do not start method names with an underscore or a lower case letter. Private methods can be named by using camel casing, but you need to be persistent.

Implementing a Method Body


The body of a method is a block of Visual Basic code that is implemented by using any of the available Visual Basic programming constructs. The body is enclosed by the method declaration and an End Sub or End Function statement.

Declaring and Calling Methods

3-7

Inside a method body, you can define variables. These variables only exist while the method is running. When the method finishes, they disappear.

Specifying Parameters
Parameters are local variables that are created when the method runs, and are populated with values that are specified when the method is called. All methods must have a list of parameters. You specify the parameters in parentheses following the method name. Each parameter is separated by a comma. If a method takes no parameters, you specify an empty parameter list. For each parameter, you specify the type and the name. By convention, parameters are named by using camel case. Note that the names of parameters can be exposed to applications that use your methods through IntelliSense in Visual Studio, so keep the names of parameters meaningful.

Specifying a Return Type


All Function procedures or methods must have a return type. A procedure or method that does not return a value is a Sub method. You specify the return type after the method name when you define a procedure or method. When you declare a method that returns data, you must include a Return statement in the method block. The following code example shows how to return a string from a method.
Function MyMethod() As String Return "Hello" End Function

The expression that the Return statement specifies must have the same type as the method. When the Return statement runs, this expression is evaluated and passed back to the statement that called the method. The method then finishes, so any other statements that occur after a Return statement has been executed will not run.

Method Examples
The following code example shows a method that accepts no parameters and does not return a value.
Sub ClearReport() ' Perform some processing here. End Sub

The following code example shows a method that accepts two string parameters, but does not return a value.
Sub CreateReport(ByVal reportName As String, ByVal reportDescription As String) ' Perform some processing here. End Sub

The following code example shows a method that accepts two string parameters and returns a Boolean result by using the Return statement.
Function LockReport(ByVal reportName As String, ByVal userName As String) As Boolean Dim success As Boolean = False ' Perform some processing here. Return success

3-8

Programming in Visual Basic with Microsoft Visual Studio 2010

End Function

Note: Any variables that you declare within a method block are only accessible to other statements in that method block. Question: What are the four elements in the method specification?

Additional Reading
For more information about procedures or methods, see the Procedures in Visual Basic page at http://go.microsoft.com/fwlink/?LinkID=210898&clcid=0x409.

Declaring and Calling Methods

3-9

Calling a Method

You call a method to run the code in that method. You do not need to understand how the code in a method works. You may not even have access to this code if it is in a class in an assembly for which you do not have the source, such as the .NET Framework class library. To call a method, you specify the method name and provide any arguments that correspond to the method parameters in brackets. If the method returns a value, you specify how to handle this value, typically by assigning it to a variable of the same type.

Example
The method called LockReport in the following code example locks a report for a particular user. The method returns a Boolean result to indicate the success of the operation.
Public Function LockReport(ByVal reportName As String, ByVal userName As String) As Boolean Dim success As Boolean = False ' Perform some processing here. Return success End Function

The LockReport method expects two string parameters. The first parameter represents the name of the report that you want to lock, and the second parameter represents the user who locked the report. The following code example shows how you can call this method. The return value is assigned to a Boolean variable called isReportLocked.
Dim isReportLocked As Boolean = LockReport("Medical Report", "Don Hall")

3-10

Programming in Visual Basic with Microsoft Visual Studio 2010

The arguments that are passed to a method can be any expression that evaluates to the type that is expected by the corresponding parameters; the parameters are initialized with the values of each of these expressions.

Order of Evaluation
The arguments to a method are evaluated in strict left-to-right order. This is important if evaluating an argument modifies the value of another argument. For example, the method that is defined by the following code example takes two integer parameters and adds them together to return their sum.
Function Sum(ByVal first As Integer, ByVal second As Integer) As Integer Return first + second End Function

If an application invokes this method as shown in the following code example, the value of i (1) will be used as the first argument, i will then be incremented to 2, and the value 2 + 2 will be used as the second argument. The value that is returned to result will therefore be 5.
Dim i As Integer = 1 Dim j As Integer = 2 Dim result As Integer = Sum(i + 1, i + j)

Question: How can you call the method in the following code example?
Sub DeleteReport(ByVal reportName As String)

Declaring and Calling Methods

3-11

Creating and Calling Overloaded Methods

Sometimes, it is useful to define several implementations of a method that takes a different set of parameters. Each version of the method performs the same operation; each version just happens to use different data. An example of overloaded methods in the .NET Framework is the WriteLine method of the Console class. This method has 19 different versions that enable you to display data specified as a range of types. For example, the following code example displays an integer value and a Boolean value by using two Console.WriteLine statements. Notice that the type of the parameter that is specified in each case is different.
Dim intData As Integer = 99 Dim booleanData As Boolean = True ... Console.WriteLine(intData) Console.WriteLine(booleanData)

This technique is known as overloading. You can create as many overloaded versions of a method as you need as long as the type and number of parameters are different for each version (each method signature must be unique).
Note: Only use method overloading to provide different methods that do semantically the same thing.

Defining Overloaded Methods


Overloaded methods have the same name as each other to emphasize their common intent. However, each overloaded method must have a unique signature to differentiate it from the other overloaded versions of the method in the class. The signature of a method contains its name and its parameter list; the return type is not part of the method signature. Therefore, you cannot define overloaded methods that differ only in their return type.

3-12

Programming in Visual Basic with Microsoft Visual Studio 2010

The following code example shows how to define three overloaded Deposit methods in a class called BankAccount. The first Deposit method takes a parameter that represents the amount to deposit as a fractional number. The second Deposit method takes a parameter that represents the amount to deposit as a string. The third Deposit method takes two parameters that represent the amount to deposit as dollars and cents.
Public Class BankAccount Private balance As Decimal Public Sub Deposit(ByVal amount As Decimal) balance += amount End Sub Public Sub Deposit(ByVal amount As String) balance += Decimal.Parse(amount) End Sub Public Sub Deposit(ByVal dollars As Integer, ByVal cents As Integer) balance += dollars + (cents / 100.0) End Sub End Class

When you call the Deposit method, the compiler determines which version to invoke by examining the number and types of the arguments that you specify. Question: What is meant by overloading a method?

Additional Reading
For more information about method overloading, see the Procedure Overloading (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210899&clcid=0x409.

Declaring and Calling Methods

3-13

Using Parameter Arrays

Overloading a method is a useful technique, but it might not always be an appropriate strategy. For example, overloading a method that can take a varying number of parameters may not always be feasible, especially if there is no theoretical limit to the number of parameters. For example, suppose you wanted to define a method called Add that calculated the sum of a set of integer values. You might define overloaded versions of this method as shown in the following code example.
Function Add(ByVal one As Integer, ByVal two As Integer) As Integer Return one + two End Function Function Add(ByVal one As Integer, ByVal two As Integer, ByVal three As Integer) As Integer Return one + two + three End Function Function Add(ByVal one As Integer, ByVal two As Integer, ByVal three As Integer, ByVal four As Integer) As Integer Return one + two + three + four End Function

This solution works well if you want to sum two, three, or four integers, but what if you need to sum five, six, seven, or even 100 integers? You could define 99 overloads, but how far should you go? One way around this is to pass parameters as an array to a method. In theory, there is no limit to the size of an array (in practice, the maximum size of an array is governed by the amount of memory that is available on the computer running your application). Using this approach, you could define a single version of Add that looks like the following code example.
Function Add(ByVal data() As Integer) As Integer Dim sum As Integer = 0 For i As Integer = 0 To data.Length - 1 sum += data(i) Next

3-14

Programming in Visual Basic with Microsoft Visual Studio 2010

Return sum End Function

The downside to this approach is that you would then have to manually declare and populate the array with data, and then pass the array to the method each time you call it, as the following code example shows.
Dim myData(...) As Integer myData(0) = 99 myData(1) = 2 myData(2) = 55 myData(3) = -26 ... Dim sum As Integer = myObject.Add(myData)

Using the ParamArray Keyword


The ParamArray keyword provides a useful shorthand approach to implementing this technique. When you define a method with an array parameter prefixed with the ParamArray keyword, the Visual Basic compiler can automatically generate code that creates an array from a set of arguments that is specified when the method is invoked. The following code example shows how to define a method with the ParamArray keyword, and how you can invoke this method with a variable number of arguments.
Function Add(ByVal ParamArray data() As Integer) As Integer Dim sum As Integer = 0 For i As Integer = 0 To data.Length - 1 sum += data(i) Next Return sum End Function ... Dim sum As Integer = myObject.Add(99, 2, 55, -26)

Notice that the only difference to the Add method is the use of the ParamArray keyword. When the Add method is called, the arguments are evaluated. If they have a type that matches the type of the array specified by the ParamArray keyword, they are collected together into an array and the array is passed as the argument to the Add method.
Note: If an overload exists that matches the specified type and number of parameters, it will be called in preference to the version that takes the ParamArray array. You can use a ParamArray array in combination with other parameters, but if you specify a ParamArray array, it must be the final parameter in the parameter list that the method specified. A method can take only one ParamArray array as a parameter. Question: How do you define a method that takes a parameter array?

Additional Reading
For more information about parameter arrays, see the Parameter Arrays (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210900&clcid=0x409.

Declaring and Calling Methods

3-15

Refactoring Code into a Method

When writing the code for your applications, you may often find yourself repeatedly writing the same or very similar code. When this happens, you should consider refactoring the code into a method. In this way, if the logic that is implemented by your code changes, you only need to update the code in one place, making your application much easier to maintain. If you notice code duplication, you should create a new method from an existing block of code.

Refactor Existing Code Into A Method


1. 2. 3. In Visual Studio 2010, in the Code Editor window, locate, select, and cut the code that you want to refactor into a method. Create a new method, whether Sub or Function, and paste the code into the method. Call the new method from where you moved the original code.

The following code examples show an example of a code block that is refactored into a method.

Original Code
Dim messageContents As String = "My message text here" Dim filePath As String = "C:\Users\Student\Desktop" If messageContents Is Nothing OrElse messageContents = String.Empty Then Throw New ArgumentException("Message cannot be empty") End If If filePath Is Nothing OrElse Not System.IO.File.Exists(filePath) Then Throw New ArgumentException("File path must exist") End If System.IO.File.AppendAllText(filePath, messageContents)

3-16

Programming in Visual Basic with Microsoft Visual Studio 2010

Refactored Code
Dim messageContents As String = "My message text here" Dim filePath As String = "C:\Users\Student\Desktop" LogMessage(messageContents, filePath) ... Private Sub LogMessage(ByVal messageContents As String, ByVal filePath As String) If messageContents Is Nothing OrElse messageContents = String.Empty Then Throw New ArgumentException("Message cannot be empty") End If If filePath Is Nothing OrElse Not System.IO.File.Exists(filePath) Then Throw New ArgumentException("File path must exist") End If System.IO.File.AppendAllText(filePath, messageContents) End Sub

Question: Why would you want to refactor code into a method?

Declaring and Calling Methods

3-17

Testing a Method

When building any application, verifying that the application functions as intended should be part of the development process and not overlooked. In addition, being able to repeat the same test quickly and easily after modifying code is an important software engineering principle. Unit tests in Visual Studio 2010 can help simplify the testing process and ensure that your code gets sufficient coverage so that the bug count remains low. Unit tests achieve this by enabling you to create a series of tests that can be run at any time to provide you with feedback that indicates whether your application is still functioning as expected.

Benefits of Unit Tests


Unit tests provide several benefits, which include: They provide instant feedback. They can help you document and make it easier for another developer to understand your code. They enable you to constantly run regression test passes on your code, which helps minimize the introduction of new bugs. They can help reduce the amount of effort required to repeat tests reliably.

Create A Unit Test


The following steps assume that you have a method that resembles the following code example.
Public Function Calculate(ByVal operandOne As Integer, ByVal operandTwo As Integer) As Integer Dim result As Integer = 0 ' Perform some calculation. Return result

3-18

Programming in Visual Basic with Microsoft Visual Studio 2010

End Function

1. 2.

In Visual Studio 2010, in the Code Editor window that contains your method, right-click, and then click Create Unit Tests. In the Create Unit Tests dialog box, perform the following, and then click OK: 1. 2. In the Current selection list, expand the nodes, and then select the method that you want to create a test for. In the Output project list, ensure that Create a new Visual Basic test project is selected.

Note: If your solution already contains a Unit Test project, you could select that project in the Output project list. 3. In the New Test Project dialog box, in the Enter a name for your new project box, type a name for the test project, and then click Create.

When you click Create, Visual Studio 2010 creates a new Unit Test project with the name that you specified, and then adds that project to your solution. The Unit Test project contains a class file that contains several members; the most significant of them being a skeleton test method. The following code example shows the test method that Visual Studio 2010 created to test the Calculate method.
''' <summary> ''' A test for Calculate ''' </summary> <TestMethod()> _ Public Sub CalculateTest() Dim operandOne As Integer = 0 ' TODO: Initialize to an appropriate value Dim operandTwo As Integer = 0 ' TODO: Initialize to an appropriate value Dim expected As Integer = 0 ' TODO: Initialize to an appropriate value Dim actual As Integer actual = Module1.Calculate(operandOne, operandTwo) Assert.AreEqual(expected, actual) Assert.Inconclusive("Verify the correctness of this test method.") End Sub

The CalculateTest method contains code to perform the following tasks: Initialize the class that contains the Calculate method. Initialize the two Integer parameters. Initialize an Integer parameter for the return value. Call the Calculate method passing the two Integer parameters. Determine whether the result that is returned from the Calculate method is as expected by using an Assert.AreEqual method call.

The method stub generated by Visual Studio 2010 provides an excellent starting point for you to ensure that methods function as expected.

Run A Unit Test


After you have created a Unit Test project and defined a test method, you can run the test in Visual Studio. To do this, perform the following tasks:

Declaring and Calling Methods

3-19

1.

In Visual Studio 2010, on the Test menu, point to Windows, and then click Test View. The Test View window lists all of the test methods in your test project, and provides controls that enable you to run your tests.

2.

In the Test View window, select the tests that you want to run, and the right-click, and then click Run Selection. If you want to debug your code when running the tests, click Debug Selection.

Question: Why would you want to use unit tests when developing your .NET Framework applications?

3-20

Programming in Visual Basic with Microsoft Visual Studio 2010

Demonstration: Refactoring and Testing a Method

Demonstration Steps
1. 2. 3. 4. 5. Open Microsoft Visual Studio 2010. In Visual Studio 2010, open the MethodRefactorAndTestDemo solution in the D:\Demofiles\Mod3\Demo1\Starter folder. Open the Module1.vb file in the Code Editor window, and examine the code in the Main method. Select the contents of the Main method, right-click, and then click Cut. Below the Main method, create a new Function named GenerateRandomNumbers, and paste in the copied code.
Public Function GenerateRandomNumbers() As Integer() Dim min As Integer = 48 Dim max As Integer = 50 Dim numberOfRequirednumbers As Integer = 100 Dim randomNumbers() As Integer ReDim randomNumbers(numberOfRequirednumbers) Dim numberGenerator As New Random() For count As Integer = 0 To numberOfRequirednumbers - 1 randomNumbers(count) = numberGenerator.Next(min, max) Next ' More logic that use the randomNumbers variable. Array.Sort(randomNumbers) End Function

6.

Move the local min, max, and numberOfRequirednumbers variables up to be parameters.


Public Function GenerateRandomNumbers(ByVal min As Integer, ByVal max As Integer, ByVal numberOfRequirednumbers As Integer) As Integer() Dim randomNumbers() As Integer

Declaring and Calling Methods

3-21

...

7.

Return the randomNumbers array as the last action in the GenerateRandomNumbers method.
... Return randomNumbers End Function

8.

Call the GenerateRandomNumbers method from the Main method, and save the return value in an integer array named randomNumbers.
Sub Main() Dim randomNumbers() As Integer = GenerateRandomNumbers(48, 50, 100) End Sub

9.

In the Code Editor window, right-click the GenerateRandomNumbers method, and then click Create Unit Tests.

10. In the Create Unit Tests dialog box, click Settings. 11. In the Test Generation Settings dialog box, clear the Honor InternalsVisibleTo Attribute check box, and then click OK. 12. In the Create Unit Tests dialog box, click OK. 13. In the New Test Project dialog box, click Create.

Note: If the Add InternalsVisibleTo Attribute dialog box appears, click Yes.

Note: If the You have made changes to your tests dialog box appears, click OK. 14. In the Code Editor window, in the Module1Test class, navigate to the GenerateRandomNumbersTest method. 15. Make the following changes to the GenerateRandomNumbersTest method. Set the max variable to 100. Set the numberOfRequirednumbers variable to 998. Remove the Dim expected() As Integer = Nothing ' TODO: Initialize to an appropriate value line. Replace the Assert.AreEqual(expected, actual) line with Assert.AreEqual(1000, actual.Length) Remove the Assert.Inconclusive("Verify the correctness of this test method.") line.

Your code should resemble the following code example.


Public Sub GenerateRandomNumbersTest() Dim min As Integer = 0 Dim max As Integer = 100 Dim numberOfRequirednumbers As Integer = 998 Dim actual() As Integer actual = Module1_Accessor.GenerateRandomNumbers(min, max, numberOfRequirednumbers) Assert.AreEqual(1000, actual.Length) End Sub

16. Build the solution. 17. Open the Test View window, run the GenerateRandomNumbersTest unit test, and examine the results in the Test Results window.

3-22

Programming in Visual Basic with Microsoft Visual Studio 2010

18. In the Code Editor window, navigate to the GenerateRandomNumbersTest method, and then set the numberOfRequirednumbers variable to 999. 19. Run the GenerateRandomNumbersTest unit test, and examine the results in the Test Results window. Question: Name one way in which you can view and start your unit tests.

Declaring and Calling Methods

3-23

Lesson 2

Specifying Optional Parameters and ByRef Parameters

In the previous lesson, you learned that you can define a method that takes a variable number of arguments by using a parameter array. However, sometimes, you may want to define a method that has a fixed number of parameters, but enables an application to specify arguments for only the parameters that it needs. You can achieve this functionality by defining a method that takes optional parameters. By default, any arguments that you provide when you call a method are passed by value into the parameters that the method specifies. When the method completes, the parameters are destroyed and any changes that you make to the values in these parameters are lost. Reference parameters provide a mechanism to enable you to pass data from a method back to the code that calls the method. This lesson describes how to define and use optional parameters and ByRef parameters.

Lesson Objectives:
After completing this lesson, you will be able to: Explain the purpose of optional parameters. Describe how to call a method by using named arguments. Explain the purpose of ByRef parameters.

3-24

Programming in Visual Basic with Microsoft Visual Studio 2010

What Are Optional Parameters?

By defining overloaded methods, you can implement different versions of a method that take different parameters. When you build an application that uses overloaded methods, the compiler determines which specific instances of each method it should use to satisfy each method call. However, there are other languages and technologies that developers can use for building Windowsbased applications and components that do not follow these rules. A key feature of Visual Basic and other languages that are designed for the .NET Framework is the ability to interoperate with applications and components that are written by using other technologies. One of the principal technologies that Windows uses is the Component Object Model (COM). COM does not support overloaded methods, but instead uses methods that can take optional parameters. To make it easier to incorporate COM libraries and components into a Visual Basic solution, Visual Basic also supports optional parameters. Optional parameters are also useful in other situations. They provide a compact and simple solution when it is not possible to use overloading because the types of the parameters do not vary sufficiently to enable the compiler to distinguish between implementations. For example, consider the method in the following code example.
Sub MyMethod(ByVal intData As Integer, ByVal singleData As Single, ByVal moreIntData As Integer) ... End Sub

The MyMethod method takes three parameters: two Integer parameters and a Single parameter. If you wanted to provide an implementation of MyMethod that took only two parameters, intData and singleData, you could overload the method, as the following code example shows.
Sub MyMethod(ByVal intData As Integer, ByVal singleData As Single) ... End Sub

Declaring and Calling Methods

3-25

If you write a statement that calls the MyMethod method, you can provide either two or three parameters of the appropriate types, and the compiler uses the type information to determine which overload to call, as the following code example shows.
Dim arg1 As Integer = 99 Dim arg2 As Single = 100.0 Dim arg3 As Integer = 101 ' Call overload with three parameters MyMethod(arg1, arg2, arg3) ' Call overload with two parameters MyMethod(arg1, arg2)

However, suppose you want to implement two further versions of MyMethod that take only the first parameter and the third parameter. You might try to implement these overloads as shown in the following code example.
Sub MyMethod(ByVal intData As Integer) ... End Sub Sub MyMethod(ByVal moreIntData As Integer) ... End Sub

However, these two overloads have the same signature, so the code will fail to compile and instead generate the error 'Public Sub MyMethod(intData As Integer)' has multiple definitions with identical signatures.. Using optional parameters can help solve this problem.

Defining Optional Parameters


Optional parameters enable you to define a method and provide default values for the parameters in the parameter list. You indicate a default value prefixing the parameter with the Optional keyword and by assigning a default value. The following code example shows how to define a method with an optional parameter. All other parameters are mandatory.
Sub MyMethod(ByVal intData As Integer, ByVal singleData As Single, Optional ByVal moreIntData As Integer = 99) ... End Sub

When using optional parameters, you must specify all mandatory parameters before any optional parameters. The following code example causes a compiler error.
Sub MyMethod(ByVal intData As Integer, Optional ByVal singleData As Single = 101.1, ByVal moreIntData As Integer) ... End Sub

Calling a Method with Optional Parameters


You can call a method that takes optional parameters in the same way that you call any other method; you specify the method name and provide any necessary arguments. The difference with methods that take optional parameters is that you can omit the corresponding arguments, and the method will use the

3-26

Programming in Visual Basic with Microsoft Visual Studio 2010

default value when the method runs. In the following code example, the first call to the MyMethod method provides values for all three parameters. The second call specifies only two arguments, and these values are applied to the first and second parameters. The moreIntData parameter receives the default value of 99 when the method runs, as the following code example shows.
' Arguments provided for all three parameters MyMethod(10, 123.45, 99) ' Arguments provided for 1st two parameters only MyMethod(100, 54.321)

Question: When defining a method with optional parameters, in what order must you specify the parameters?

Additional Reading
For more information about optional parameters, see the Optional Arguments (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210901&clcid=0x409.

Declaring and Calling Methods

3-27

Calling a Method by Using Named Arguments

Traditionally, when calling a method, the order and position of arguments in the method call should correspond to the order of parameters in the method signature. If the arguments were misaligned and the types mismatched, you would get a compile error. In Visual Basic, you can specify parameters by name and supply arguments in a sequence that differs from that defined by the order of the parameters in the method signature. To use the named arguments feature, you must supply the parameter name and corresponding value separated by a colon. The following code example shows the syntax.
' Method declaration. Sub MyMethod(ByVal first As Integer, ByVal second As Double, ByVal third As String) End Sub ... ' Method call using named arguments. MyMethod(third:="Hello", first:=1234, second:=12.12)

When using named arguments in conjunction with optional parameters, you can easily omit parameters. These parameters will receive their default value. However, if you omit any mandatory parameters, your code will not compile. You can mix positional and named arguments. However, you must specify all positional arguments before any named arguments. Question: What is the syntax for using named parameters in method calls?

3-28

Programming in Visual Basic with Microsoft Visual Studio 2010

What Are ByRef Parameters?

A method can specify a return type and use a Return statement to pass a value back to code that calls it. ByRef parameters enable you to return additional data from a method. When the method completes, the value of the ByRef parameter is assigned to a variable that is specified as the corresponding argument in the method call. To define a ByRef parameter, you prefix the parameter in the method signature with the ByRef keyword. The following code example shows the syntax.
Sub MyMethod(ByVal first As Integer, ByVal second As Double, ByRef data As Integer) ... data = 99 End Sub

A method can have as many ByRef parameters as required. To use a ByRef parameter, you must provide a variable for the corresponding argument when you call the method. Otherwise, the value assigned is discarded when the method call returns.

Additional Reading
For more information about ByRef parameters, see the Passing Arguments by Value and by Reference (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210903&clcid=0x409.

Declaring and Calling Methods

3-29

Lab: Declaring and Calling Methods

Objectives
After completing this lab, you will be able to: Create and call methods. Define overloaded methods. Define methods that take ByRef parameters. Define methods that take optional parameters and call them by using named arguments.

Introduction
In this lab, you will create methods to calculate the greatest common divisor (GCD) of a pair of positive integers. You will create an overloaded version of one of these methods that can take up to five-integer parameters. You will modify the methods to take a ByRef parameter that returns the time taken to perform the calculations. Finally, you will use a method that uses optional parameters to display the relative performance of the methods by displaying a simple graph.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

3-30

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Scenario

Fabrikam, Inc. produces a range of highly sensitive measuring devices that can repeatedly measure objects and capture data. Some of the calculations that various scientific instruments perform depend on statistical information that is generated by using prime numbers. One of your colleagues has implemented a method for generating prime numbers, but the method does not have sufficient performance to meet the requirements of the devices that it will be used with. The software analysts have examined the code and have determined that it can be improved by using a faster algorithm for calculating the GCDs. You have been asked to implement a test application that can calculate the GCD of a set of numbers by using different wellknown algorithms and compare their relative performance.

Declaring and Calling Methods

3-31

Exercise 1: Calculating the Greatest Common Divisor of Two Integers by Using Euclids Algorithm
In this exercise, you will write a method that implements Euclid's algorithm for calculating the GCD of two integers passed in as parameters. You will test this method by using a Windows Presentation Foundation (WPF) application that prompts the user for the parameter values and displays the result. You will also generate a unit test project to enable you to automate testing this method.

Scenario
Some of the data that is collected by devices built by Fabrikam, Inc. must be encrypted for security purposes. Encryption algorithms often make use of prime numbers. A part of the algorithm that generates prime numbers needs to calculate the GCD of two numbers. The GCD of two numbers is the largest number that can exactly divide into the two numbers. For example, the GCD of 15 and 12 is 3. Three is the largest whole number that divides exactly into 15 and 12. The process for finding the GCD of 2806 and 345 by using Euclid's algorithm is as follows. 1. Keep taking 345 away from 2806 until less than 345 is left and store the remainder. In this case, 2806 = (8 345) + 46, so the remainder is 46. 2. Keep taking the remainder (46) away from 345 until less than 46 is left and store the remainder. 345 = (7 46) + 23, so the remainder is 23. 3. Keep taking 23 away from 46 until less than 23 is left and store the remainder. 46 = (2 23) + 0 4. The remainder is 0, so the GCD of 2806 and 345 was the value of the previously stored remainder, which was 23 in this case.

The main tasks for this exercise are as follows: 1. 2. 3. 4. Open the starter project. Implement Euclids algorithm. Test the FindGCDEuclid method. Create a unit test for the FindGCDEuclid method.

Task 1: Open the starter project.


Open Visual Studio 2010. Import the code snippets from the D:\Labfiles\Lab03\Snippets folder. You can import a code snippet from the Tools menu, where you click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language drop-down, click Visual Basic, and then click Add. Browse to the D:\Labfiles\Lab03\Snippets folder, click Select Folder, and then click OK. Open the Euclid solution in the D:\Labfiles\Lab03\Ex1\Starter folder.

Task 2: Implement Euclids algorithm.


Review the Task List. Use the Task List window to navigate to the TODO: Exercise 1, Task 2 task. This task is located in the GCDAlgorithms.vb file.

3-32

Programming in Visual Basic with Microsoft Visual Studio 2010

In the GCDAlgorithms class, remove the TODO: Exercise 1, Task 2 comment and declare a Public Shared method named FindGCDEuclid. The method should accept two integer parameters named a and b and return an integer value.

In the FindGCDEuclid method, add code that calculates and returns the GCD of the values specified by the parameters a and b by using Euclid's algorithm. Euclids algorithm works as follows: If a is zero, the GCD of a and b is b. Otherwise, repeatedly subtract b from a (when a is greater than b) or subtract a from b (when b is greater than a) until b is zero. The GCD of the two original parameters is the new value in a.

Task 3: Test the FindGCDEuclid method.


Use the Task List window to navigate to the TODO: Exercise 1, Task 3 task. This task is located in the MainWindow.xaml.vb file. This is the code-behind file for a WPF window that you will use to test the FindGCDEuclid method and display the results. Remove the TODO: Exercise 1, Task 3 comment, add code to call the staticFindGCDEuclid method of the GCDAlgorithms class, and display the results in the ResultEuclidLabel control. In the method call, use the firstNumber and secondNumber variables as arguments (these variables contain values that the user enters in the WPF window). Finally, the result should be formatted as the following example.
Euclid: result

Hint: Set the Content property of a label control to display data in a label. Use the String.Format method to create a formatted string. Build the solution and correct any errors. Run the GreatestCommonDivisor application. In the GreatestCommonDivisor application, in the MainWindow window, in the first text box, type 2806 In the second text box, type 345 and then click Find GCD (2 Integers). The result of 23 should be displayed, as the following screen shot shows.

Declaring and Calling Methods

3-33

Use the window to calculate the GCD for the values that are specified in the following table, and verify that the results that are displayed match those in the table. First number 0 0 25 25 26 27 Second number 0 10 10 100 100 100 Close the GreatestCommonDivisor application. Result 0 10 5 25 2 1

Task 4: Create a unit test for the FindGCDEuclid method.


Open the GCDAlgorithms.vb file. In the GCDAlgorithms class, create a unit test for the FindGCDEuclid method. Create a new Test Project named GCD Test Project to hold the unit test. In the GCD Test Project project, in the GCDAlgorithmsTest.vb file, locate the FindGCDEuclidTest method. In the FindGCDEuclidTest method, set the a variable to 2806, set the b variable to 345, set the expected variable to 23, and then remove the Assert.Inconclusive method call.

3-34

Programming in Visual Basic with Microsoft Visual Studio 2010

Open the Test View window and refresh the display if the unit test is not listed.

Note: If the You have made changes to your tests dialog box appears, click OK. Run the FindGCDEuclidTest test and verify that the test ran successfully.

Declaring and Calling Methods

3-35

Exercise 2: Calculating the GCD of Three, Four, or Five Integers


In this exercise, you will create overloaded versions of this method that can take three, four, or five integer parameters and calculate the GCD of all of these parameters.

Scenario
Some of the encryption algorithms used by devices that Fabrikam, Inc. builds require calculating the GCD of sets of numbers, not just pairs. You have been asked to provide implementations of the Euclid algorithm that can calculate the GCD of three, four, or five integers. The process for finding the GCD of three numbers x, y, and z is straightforward: 1. 2. Calculate the GCD of x and y by using the algorithm for two numbers and store the result in a variable r. Calculate the GCD of r and z. The result is the GCD of x, y, and z.

You can apply the same technique to calculate the GCD of four or five integers: GCD(w, x, y, z) = GCD(w, GCD(x, y, z)) GCD(v, w, x, y, z) = GCD(v, GCD(w, x, y, z))

The main tasks for this exercise are as follows: 1. 2. 3. 4. Open the starter project. Add overloaded methods to the GCDAlgorithms class. Test the overloaded methods. Create unit tests for the overloaded methods.

Task 1: Open the starter project.


Open the Euclid solution in the D:\Labfiles\Lab03\Ex2\Starter folder. This solution contains a revised copy of the code from Exercise 1.

Note: If the You have made changes to your tests dialog box appears, click OK.

Task 2: Add overloaded methods to the GCDAlgorithms class.


In Visual Studio, review the Task List. Use the Task List window to navigate to the TODO: Exercise 2, Task 2 task. In the GCDAlgorithms class, remove the TODO: Exercise 2, Task 2 comment, and then declare an overloaded version of the FindGCDEuclid method. The method should accept three integer parameters named a, b, and c and return an integer value. In the new method, add code that uses the original FindGCDEuclid method to find the GCD for the parameters a and b. Store the result in a new variable named d. Add a second call to the original FindGCDEuclid method to find the GCD for variable d and parameter c. Store the result in a new variable named e.

3-36

Programming in Visual Basic with Microsoft Visual Studio 2010

Your code should resemble the following code.


... Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer Dim d As Integer = FindGCDEuclid(a, b) Dim e As Integer = FindGCDEuclid(d, c) End Function ...

Add code to return the parameter e from the FindGCDEuclid method. Declare another overloaded version of the FindGCDEuclid method. The method should accept four integer parameters named a, b, c, and d and return an integer value. Use the other FindGCDEuclid method overloads to find the GCD of these parameters and return the result. Declare another overloaded version of the FindGCDEuclid method. The method should accept five integer parameters named a, b, c, d, and e, and return an integer value. Use the other FindGCDEuclid method overloads to find the GCD of these parameters and return the result.

Task 3: Test the overloaded methods.


Use the Task List window to navigate to the TODO: Exercise 2, Task 3 task. This task is located in the code for the WPF window that you can use to test your code. Remove the TODO: Exercise 2, Task 3 comment, locate the ElseIf sender Is FindGCD3Button Then block and modify the statement that sets the Content property of the ResultEuclidLabel control to "N/A" as follows. Call the FindGCDEuclid overload that accepts three parameters and pass the variables firstNumber, secondNumber, and thirdNumber as arguments. Display the results in the ResultEuclidLabel label control. The result should be formatted as the following code example shows.

Euclid: result

Locate the ElseIf sender Is FindGCD3Button Then block, the ElseIf sender Is FindGCD4Button Then block, and the ElseIf sender Is FindGCD5Button Then block, and modify the statements that set the Content property of the ResultEuclidLabel control to "N/A". Call the appropriate FindGCDEuclid overload by using the firstNumber, secondNumber, thirdNumber, fourthNumber, and fifthNumber variables as arguments. Display the results in the ResultEuclidLabel label control. Build the solution and correct any errors. Run the GreatestCommonDivisor application. In the GreatestCommonDivisor application, in the MainWindow window, type the values 7396,1978,1204,430, and 258 and then click Find GCD (5 Integers). Verify that the result 86 is displayed.

Use the window to calculate the GCD for the values that are specified in the following table and verify that the results that are displayed match those in the table. First Number 2806 0 Second Number 345 0 Third Number 0 0 Fourth Number 0 0 Fifth Number 0 0

Result 23 0

Declaring and Calling Methods

3-37

First Number 0 12 13 14 15 16 0

Second Number 0 24 24 24 24 24 24

Third Number 0 36 36 36 36 36 36

Fourth Number 0 48 48 48 48 48 48

Fifth Number 1 60 60 60 60 60 60

Result 1 12 1 2 3 4 12

Close the GreatestCommonDivisor application.

Task 4: Create unit tests for the overloaded methods.


In Visual Studio, review the Task List. Use the Task List window to navigate to the TODO: Exercise 2, Task 4 task. Remove the TODO: Exercise 2, Task 4 comment and add a test method named FindGCDEuclidTest1. In the FindGCDEuclidTest1 method, declare four variables named a, b, c, and expected and assign them values 7396, 1978, 1204, and 86, respectively. Declare a variable named actual, and assign it the result of a call to the FindGCDEuclid method call. Use the variables a, b, and c as arguments. Call the shared AreEqual method of the Assert class, and pass the expected and actual variables as arguments. Repeat steps 46 to create two more test methods to test the other FindGCDEuclid method overloads. Create test methods named FindGCDEuclidTest2 and FindGCDEuclidTest3. Use the values 7396, 1978, 1204, and 430 for the FindGCDEuclidTest2 method, and the values 7396, 1978, 1204, 430, and 258 for the FindGCDEuclidTest3 method. The result should be 86 in both cases. Open the Test View window and refresh the display if the unit test is not listed. Run the FindGCDEuclidTest, FindGCDEuclidTest1, FindGCDEuclidTest2, and FindGCDEuclidTest3 tests and verify that the tests ran successfully.

3-38

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 3: Comparing the Efficiency of Two Algorithms


In this exercise, you will write another method that implements Stein's algorithm for calculating the GCD of two integer parameters. The method will take a ByRef parameter that contains the time taken to perform the calculation. You will also modify the method that implements Euclid's algorithm for calculating the GCD of two parameters to take a ByRef parameter, also containing the time taken to perform the calculation. You will then modify the WPF application to test the relative performance of the methods and display the times taken.

Scenario
Stein's algorithm is an alternative algorithm for finding the GCD of two numbers. You have been told that it is more efficient than Euclid's algorithm. A colleague has previously implemented Stein's algorithm, but you decide to test this hypothesis by comparing the time taken to calculate the GCD of pairs of numbers with that taken by using Euclid's algorithm. The following steps describe the process of calculating the GCD of two numbers, u and v, by following Stein's algorithm: 1. gcd(0, v) = v because everything divides by zero, and v is the largest number that divides v. Similarly, gcd(u, 0) = u. gcd(0, 0) is not typically defined, but it is convenient to set gcd(0, 0) = 0. 2. 3. If u and v are both even, gcd(u, v) = 2gcd(u/2, v/2) because 2 is a common divisor. If u is even and v is odd, gcd(u, v) = gcd(u/2, v) because 2 is not a common divisor. Similarly, if u is odd and v is even, gcd(u, v) = gcd(u, v/2). 4. If u and v are both odd, and u v, gcd(u, v) = gcd((uv)/2, v). If both are odd and u<v, gcd(u, v) = gcd((vu)/2, u). These are combinations of one step of the simple Euclidean algorithm, which uses subtraction at each step, and an application of step 4 above. The division by 2 results in an integer because the difference of two odd numbers is even. 5. Repeat steps 35 until u = v, or (one more step) until u = 0. In either case, the result is 2kv, where k is the number of common factors of 2 found in step 2. The main tasks for this exercise are as follows: 1. 2. 3. 4. Open the starter project. Implement Stein's algorithm. Test the FindGCDStein method. Add code to test the performance of the algorithms.

Task 1: Open the starter project.


Open the Stein solution in the D:\Labfiles\Lab03\Ex3\Starter folder. This solution contains a revised copy of the code from Exercise 2.

Note: If the You have made changes to your tests dialog box appears, click OK.

Declaring and Calling Methods

3-39

Task 2: Implement Steins algorithm.


Open the GCDAlgorithms.vb file. At the end of the GCDAlgorithms class, remove the TODO: comment and declare a shared Public method named FindGCDStein. The method should accept two integer parameters named u and v and return an integer value. In the FindGCDStein method, add the code in the following code example, which calculates and returns the GCD of the values that are specified by the parameters u and v by using Stein's algorithm. You can either type this code manually, or use the Mod03Stein code snippet. To use the Mod03Stein snippet, add a blank line, type Mod03Stein, and then press TAB.

Note: For the purposes of this exercise, it is not necessary for you to understand this code. However, if you have time, you may like to compare this method with the algorithm that is described in the exercise scenario. Note that this code uses the left-shift (<<) and right-shift (>>) operators to perform fast multiplication and division by 2. If you left-shift an integer value by one place, the result is the same as multiplying the integer value by 2. Similarly, if you right-shift an integer value by one place, the result is the same as dividing the integer value by 2. In addition, the Or operator performs a bitwise OR operation between two integer values. Consequently, if either u or v is zero, the expression u And v is a fast way of returning the value of whichever variable is nonzero, or zero if both are zero. Similarly, the And operator performs a bitwise AND operation, so the expression u And 1 is a fast way to determine whether the value of u is odd or even.
Public Shared Function FindGCDStein(ByVal u As Integer, ByVal v As Integer) As Integer Dim k As Integer = 0 ' Step 1. ' gcd(0, v) = v, because everything divides zero, ' and v is the largest number that divides v. ' Similarly, gcd(u, 0) = u. gcd(0, 0) is not typically ' defined, but it is convenient to set gcd(0, 0) = 0. If u = 0 OrElse v = 0 Then Return u Or v End If ' Step 2. ' If u and v are both even, then gcd(u, v) = 2gcd(u/2, v/2), ' because 2 is a common divisor. Do While ((u Or v) And 1) = 0 u >>= 1 v >>= 1 k += 1 Loop ' Step 3. ' If u is even and v is odd, then gcd(u, v) = gcd(u/2, v), ' because 2 is not a common divisor. ' Similarly, if u is odd and v is even, ' then gcd(u, v) = gcd(u, v/2). While (u And 1) = 0 u >>= 1 End While ' Step 4. ' If u and v are both odd, and u v, ' then gcd(u, v) = gcd((u v)/2, v).

3-40

Programming in Visual Basic with Microsoft Visual Studio 2010

' If both are odd and u < v, then gcd(u, v) = gcd((v u)/2, u). ' These are combinations of one step of the simple ' Euclidean algorithm, ' which uses subtraction at each step, and an application ' of step 3 above. ' The division by 2 results in an integer because the ' difference of two odd numbers is even. Do While (v And 1) = 0 ' Loop x v >>= 1 End While ' Now u and v are both odd, so diff(u, v) is even. ' Let u = min(u, v), v = diff(u, v)/2. If (u < v) Then v -= u Else Dim diff As Integer = u - v u = v v = diff End If v >>= 1 ' Step 5. ' Repeat steps 34 until u = v, or (one more step) ' until u = 0. ' In either case, the result is (2^k) * v, where k is ' the number of common factors of 2 found in step 2. Loop While v <> 0 u <<= k Return u End Loop End Function

Task 3: Test the FindGCDStein method.


Open the MainWindow.xaml.vb file. In the MainWindow class, in the FindGCDButton_Click method, locate the TODO: Exercise 3, Task 2 comment. Remove this comment and replace the statement that sets the Content property of the ResultSteinLabel control with code that calls the FindGCDStein method by using the variables firstNumber and secondNumber as arguments. Display the results in the ResultSteinLabel control. The result should be formatted as the following code example shows.
Stein: result

Build the solution and correct any errors. Run the GreatestCommonDivisor application. In the GreatestCommonDivisor application, in the MainWindow window, in the first two boxes, type the values 298467352 and 569484 and then click Find GCD (2 Integers). Verify that the value 4 is displayed in both labels.

Close the GreatestCommonDivisor application. Open the GCDAlgorithmsTest.vb file. At the end of the GCDAlgorithmsTest class, locate the TODO: Exercise 3, Task 2 comment, remove the comment, and then add a test method named FindGCDSteinTest.

Declaring and Calling Methods

3-41

In the FindGCDSteinTest method, declare three variables named u, v, and expected and assign them values 298467352, 569484, and 4, respectively. Declare a variable named actual and assign it the result of a call to the FindGCDStein method call. Use the variables u and v as arguments. Call the shared AreEqual method of the Assert class, and pass the expected and actual variables as arguments. Open the Test View window and refresh the display if the unit test is not listed. Run the FindGCDSteinTest test and verify that the test ran successfully.

Task 4: Add code to test the performance of the algorithms.


Open the GCDAlgorithms.vb file. In the GCDAlgorithms class, locate the FindGCDEuclid method that accepts two parameters and modify the method signature to take a reference parameter named time of type Long. At the start of the FindGCDEuclid method, add code to initialize the time parameter to zero, create a new Stopwatch object named sw and start the stop watch. The Stopwatch class is useful for timing code. The Start method starts an internal timer running. You can subsequently use the Stop method to halt the timer, and establish how long the interval was between starting and stopping the timer by querying the ElapsedMilliseconds or ElapsedTicks properties. At the end of the FindGCDEuclid method, before the Return statement, add code to stop the Stopwatch object, and set the time parameter to the number of elapsed ticks of the Stopwatch object. Comment out the other FindGCDEuclid method overloads. Modify the FindGCDStein method to include the time reference parameter and add code to record the time each method takes to run. Note that the FindGCDStein method contains two Return statements, and you should record the time before each one. Open the MainWindow.xaml.vb file. In the FindGCDButton_Click method, modify each of the calls to the FindGCDEuclid method and the FindGCDStein method to use the updated method signatures, as follows. For calling the Euclid algorithm, create a variable named timeEuclid of type Long. For calling the Stein algorithm, create a variable named timeStein of type Long. Format the results displayed in the labels as the following code example shows.

[Euclid] Euclid: result, Time (ticks): result [Stein] Stein: result, Time (ticks): result

Comment out the code that calls the overloaded versions of the FindGCDEuclid method. Open the GCDAlgorithmsTest.vb file: Modify the FindGCDEuclidTest and FindGCDSteinTest methods to use the new method signatures. Comment out the methods FindGCDEuclidTest1, FindGCDEuclidTest2, and FindGCDEuclidTest3. Build the solution and correct any errors. Run the GreatestCommonDivisor application.

3-42

Programming in Visual Basic with Microsoft Visual Studio 2010

In the GreatestCommonDivisor application, in the MainWindow window, in the first two boxes, type the values 298467352 and 569484 and then click Find GCD (2 Integers). The result of 4 should be displayed. The time reported for Euclid's algorithm should be at least three times more than that for Stein's algorithm.

Note: The bigger the difference between the two values, the more efficient Stein's algorithm becomes compared with Euclid's. If you have time, try experimenting with different values. Close the GreatestCommonDivisor application. Open the Test View window and refresh the display if the unit test is not listed. Run the FindGCDEuclidTest and FindGCDSteinTest methods and verify that the tests ran successfully.

Declaring and Calling Methods

3-43

Exercise 4: Displaying Results Graphically


In this exercise, you will add a method to the application that displays the results graphically by using a bar graph. The parameters to the method are the two times taken, the orientation of the graph, and the colors to use to display the bars. The graph orientation and color parameters will be optional parameters. The default values will generate a vertical bar graph with a red bar for the first value and a blue bar for the second.

Scenario
You want to display the results of the timing comparisons graphically by using a simple, customizable bar graph. The main tasks for this exercise are as follows: 1. 2. 3. 4. Open the starter project. Display the algorithm timings graphically. Modify the DrawGraph method. Modify the code that calls the DrawGraph method.

Task 1: Open the starter project.


Open the Charting solution in the D:\Labfiles\Lab03\Ex4\Starter folder. This solution contains a revised copy of the code from Exercise 3.

Task 2: Display the algorithm timings graphically.


Open the MainWindow.xaml.vb file. In the FindGCDButton_Click method, locate the Call DrawGraph comment and add a call to the DrawGraph method by using the timeEuclid and timeStein variables as parameters. Build the solution and correct any errors. Run the GreatestCommonDivisor application. In the GreatestCommonDivisor application, in the MainWindow window, in the first two boxes, type the values 298467352 and 569484 and then click Find GCD (2 Integers). The result of 4 should be displayed. The time reported for both algorithms should be represented by a simple bar graph in the window. Close the GreatestCommonDivisor application.

Task 3: Modify the DrawGraph method.


In the MainWindow class, locate the DrawGraph method and add the following three optional parameters. A parameter named orientation of type Orientation with a default value of Orientation.Horizontal. A parameter named colorEuclid of type String with a default value of Red. A parameter named colorStein of type String with a default value of Blue.

In the DrawGraph method, locate the Use optional orientation parameter comment and remove the existing declaration of the orient variable. Locate the Use optional color parameters comment, and modify the assignment of the bEuclid and bStein variables to use the optional parameters in the method signature. To do this, you will need to

3-44

Programming in Visual Basic with Microsoft Visual Studio 2010

use the BrushConverter class and the ConvertFromString instance method as shown in the following code example.
... Private Sub DrawGraph(ByVal euclidTime As Long, ByVal steinTime As Long, Optional ByVal orient As Orientation = Orientation.Horizontal, Optional ByVal colorEuclid As String = "Red", Optional ByVal colorStein As String = "Blue") ... Dim bc = New BrushConverter() Dim bEuclid As Brush = CType(bc.ConvertFromString(colorEuclid), Brush) Dim bStein As Brush = CType(bc.ConvertFromString(colorStein), Brush) ... End Sub ...

Build the solution and correct any errors. Run the GreatestCommonDivisor application. In the GreatestCommonDivisor application, in the MainWindow window, in the first two boxes, type the values 298467352 and 569484, and then click Find GCD (2 Integers). The graph should be displayed as before, except the DrawGraph method call is now using the default parameter values, and the graph is displayed as a pair of red and blue vertical bars. Close the GreatestCommonDivisor application.

Task 4: Modify the code that calls the DrawGraph method.


In the FindGCDButton_Click method, locate the Modify the call to Drawgraph to use the optional parameters comment, and modify the DrawGraph method call to use the orient, colorEuclid, and colorStein optional parameters as follows. orientset to the selected value of the ChartOrientationListBox control. colorEuclidset to the selected item of the EuclidColorListBox control. colorSteinset to the selected item of the SteinColorListBox control.

These list boxes are already included in the user interface; they appear in the lower part of the window. The user can select the values in these list boxes to change the appearance of the graph that is displayed. Build the solution and correct any errors. Run the GreatestCommonDivisor application. In the GreatestCommonDivisor application, in the MainWindow window, in the first two boxes, type the values 298467352 and 569484. In the Euclid list, select Green, in the Stein list, select Black, in the Orientation list, select Horizontal, and then click Find GCD (2 Integers). The graph should be displayed with the specified colors and direction. Close the GreatestCommonDivisor application.

Declaring and Calling Methods

3-45

Exercise 5: Solving Simultaneous Equations (optional)


In this exercise, you will write a method that solves simultaneous linear equations with four variables (w, x, y, and z). You will use a WPF application to obtain input from the user (the coefficients of w, x, y, and z and the result for four equations) to simulate the data captured by a device, and call the method. The method will use Gaussian Elimination (a well-known algorithm for solving simultaneous linear equations) to generate solutions for w, x, y, and z, which will be returned as an array. The WPF application will then display these values.

Scenario
A key requirement of one of the engineering applications produced by Fabrikam, Inc. is the ability to solve simultaneous linear equations based on some of the data captured by various measuring devices. Suppose you need to find the values of x, y, and z given the equations in the following code example.
2x + y z = 8 -3x y + 2z = -11 -2x + y + 2z = -3 (equation E1) (equation E2) (equation E3)

The method to solve these equations, known as Gaussian Elimination, proceeds as follows: 1. Eliminate x from equations E2 and E3: To eliminate x from E2, calculate (32)E1 + E2

The coefficient of x in E2 is (3 2) times that of the coefficient of x in E1, so multiplying E1 by (3 2) and adding E2 removes x from E2. To remove x from E3, calculate E1 + E3

The coefficient of x in E3 is 1 times that of the coefficient of x in E1, so adding E1 to E3 removes x from E3. The result is shown in the following code example.
2x + y z = 8 (1/2)y + (1/2)z = 1 2y + z = 5 (E1) (E2) (E3)

2.

Next, eliminate y from E3: To eliminate y from E3, calculate 4 E2 + E3

The coefficient of y in E3 is four times that of the coefficient of y in E2, so multiplying E2 by 4 and adding E3 removes y from E3. The result is shown in the following code example.
2x + y z = 8 (1/2)y + (1/2)z = 1 -z = 1 (E1) (E2) (E3)

The equations are now in triangular formthree unknowns in the first equation, two in the second equation, and one in the third equation. 3. 4. Solve E3 and calculate the value for z, as the following code example shows.
z = -1 (E3)

Substitute the value of z into E2 to calculate the value of y, as the following code example shows.
(1/2)y 1/2 = 1 => (1/2)y = 3/2 => y = 3 (E2)

3-46

Programming in Visual Basic with Microsoft Visual Studio 2010

5.

Substitute the values of z and y into E1 to calculate the value of x, as the following code example shows.
2x + 3 + 1 = 8 2x = 4 x = 2 => => (E1)

This process is known as back substitution. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. Open the starter project. Create methods to copy arrays. Convert the equations to triangular form. Perform back substitution. Test the solution.

Task 1: Open the starter project.


Open the SimultaneousEquations solution in the D:\Labfiles\Lab03\Ex5 \Starter folder. Open the MainWindow.xaml file. This is a different application from the one that the previous exercises have used. It is a WPF application that enables a user to enter the coefficients for four simultaneous equations that contain four variables (w, x, y, and z), and then uses Gaussian Elimination to find a solution for these equations. The results are displayed in the lower part of the screen.

Task 2: Create methods to copy arrays.


Open the Gauss.vb file. This file contains a class named Gauss that provides a method named SolveGaussian. This method takes two arrays as parameters. A two-dimensional array of Double values containing the coefficients for the variables w, x, y, and z specified by the user for each equation. An array of Double values containing the result of each equation specified by the user (the value to the right of the equal sign).

The method returns an array of Double values that will be populated with the values of w, x, y, and z that provide the solutions to these equations. You will implement the body of this method in this exercise. In the Gauss class, locate the TODO: Exercise 5, Task 2 comment. Remove this comment and declare a shared Private method named DeepCopy1D. The method should accept and return a Double array. The SolveGaussian method will make a copy of the arrays passed in as parameters to avoid changing the original data that the user provided. In the DeepCopy1D method, add code to create a deep copy of the one-dimensional array that was passed into the method. Your code should perform the following tasks. Create and initialize an array with the same number of columns as the array that was passed in. Copy the values in the array that was passed as a parameter into the new array.

Declaring and Calling Methods

3-47

Return the new array.

In the Gauss class, declare another shared Private method named DeepCopy2D. The method should accept and return a two-dimensional Double array. In the DeepCopy2D method, add code to create a deep copy of the two-dimensional array that was passed into the method. Your code should do the following. Create and initialize an array with the same number of columns and rows as the array that was passed in. Copy the values in the array that was passed in as the parameter into the new array. Return the new array.

Task 3: Convert the equations to triangular form.


In the SolveGaussian method, use the DeepCopy1D and DeepCopy2D methods to create deep copies of the rhs and coefficients arrays. Locate the Convert the equation to triangular form comment, and add code to convert the equations represented by the copies of the coefficients and rhs arrays into triangular form.

Note: The Gauss class defines a constant integer named numberOfEquations that specifies the number of coefficients that the application can resolve. Your code should resemble the following code.
... ' TODO: Exercise 5, Task 3 ' Convert the equations to triangular form Dim x, sum As Double For k As Integer = 0 to numberOfEquations Try For i As Integer = k + 1 To numberOfEquations -1 x = a(i, k) / a(k, k) For j As Integer = k + 1 To numberOfEquations -1 a(i, j) = a(i, j) a(k, j) * x Next b(i) = b(i) b(k) * x Next Catch e As DivideByZeroException Console.WriteLine(e.Message) End Try

Next ...

Task 4: Perform back substitution.


In the Gauss class, in the SolveGaussian method, locate the Perform the back substitution and return the result comment, and then add code to perform back substitution. To do this, you will need to work back from the equation with one unknown and substituting the values calculated at each stage to solve the remaining equations. Your code should resemble the following code.
... ' TODO: Exercise 5, Task 4

3-48

Programming in Visual Basic with Microsoft Visual Studio 2010

' Perform the back substitution and return the result b(numberOfEquations 1) = b(numberOfEquations 1) / a(numberOfEquations - 1, numberOfEquations 1) For i As Integer = numberOfEquations 2 To 0 Step -1 sum = b(i) For j As Integer = i + 1 To numberOfEquations -1 sum = sum a(i, j) * b(j) Next Next b(i) = sum / a(i, i)

Return b ...

Task 5: Test the solution.


Open the MainWindow.xaml.vb file. In the MainWindow class, locate the TODO: Exercise 5, Step 5 comment, and add code to call the SolveGaussion method. Use the coefficients and rhs variables as parameters and set the answers array to the result. Run the GaussianElimination application. In the MainWindow window, enter the following equations, and then click Solve. x 1 -1 1 -1 y -1 2 -2 2 z 1 1 0 -2 Result 8 -11 -3 -5

w 2 -3 -2 3

Verify that the following results are displayed: w=4 x = 17 y = 11 z=6 Experiment with other equations. Note that not all systems of equations have a solution. How does your code handle this situation? Close the MainWindow window. Close Visual Studio.

Declaring and Calling Methods

3-49

Lab Review

Review Questions
1. 2. 3. When using ByRef parameters in a method, what happens when you pass a specific value and not a variable when calling the method? When adding optional parameters to an existing method signature, why will your code run successfully without making changes to any of the existing method calls? When creating a unit test method in a Visual Studio test project, what attribute must you decorate your test method with?

3-50

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Review and Takeaways

Review Questions
1. 2. 3. 4. Which type of procedure or method does not return any data? What term is given to the process of defining multiple methods with the same name, but different parameter lists? What is the difference between an optional parameter and a named argument? What is the purpose of ByRef parameters?

Best Practices Related to Using Methods


Supplement or modify the following best practices for your work situations: Keep methods as small and lightweight functional units. If methods start to become large, consider refactoring code into smaller logical methods. Create unit tests for all Public methods. You can assume that any Private methods that you create will be tested when the Public methods are called. Use ByRef parameters only when it is absolutely necessary. If you find yourself using ByRef parameters too often, reconsider the purpose of the method.

Handling Exceptions

4-1

Module 4
Handling Exceptions
Contents:
Lesson 1: Handling Exceptions Lesson 2: Raising Exceptions Lab: Handling Exceptions 4-3 4-15 4-23

4-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

In the previous modules, you have been introduced to some important concepts that will enable you to develop Microsoft .NET Framework applications. Until this point, if your application caused an exception, it would crash in an ungainly manner. This approach to handling exceptions is clearly not acceptable in a professional application. Exception handling is an important concept and your applications should be designed with exception handling in mind. This module explains how you can implement effective exception handling in your applications, and how you can use exceptions in your methods to elegantly indicate an error condition to the code that calls your methods.

Objectives:
After completing this module, you will be able to: Describe how to catch and handle exceptions. Describe how to create and raise exceptions.

Handling Exceptions

4-3

Lesson 1

Handling Exceptions

Applications may function as expected during development, with limited use and controlled input. However, when an application is deployed to its live environment and subject to constraints such as dynamically changing data at greater volumes, errors are likely to emerge. To manage the user experience and ensure that your application remains usable when exceptions occur, you need to handle these exceptions. This lesson introduces concepts such as the Try/Catch/Finally block, which will enable you to implement structured exception handling (SEH) in your applications.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of exceptions. Describe how to use a Try/Catch block. Describe how to use some of the properties that the Exception class exposes. Explain how to use a Finally block.

4-4

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is an Exception?

Many things can go wrong as an application runs, not just because of faults in the logic, but because applications typically depend on many variables outside the scope of the application, such as the existence of files on the file system and connections to databases. During the design of your application, you must consider how to ensure that your application can recover gracefully when such problems arise. It is common practice to check the return values from methods to ensure that they have executed correctly. However, there are issues with this approach: Not all methods return a value. You need to know why the method call has failed, not just that it has failed. This approach does not cover how to handle unexpected errors such as running out of memory.

Many older systems used the concept of a global error object. When a piece of code caused an error, it would set the data in this object to indicate the cause of the error and then return to the caller. It was the responsibility of the calling code to examine the error object and determine how to handle it. Needless to say, this approach was not robust because it was too easy for a programmer to forget to handle errors.

How Exceptions Propagate


In general, exceptions in the .NET Framework prove to be more useful than global error objects. An exception is an indication of an error or exceptional condition. A method can throw an exception when it detects that something unexpected has happened. For example, a method tries to open a file, but the file does not exist. When a method throws an exception, the calling code must be prepared to detect and handle this exception. If the calling code does not detect the exception, it is aborted and the exception is automatically propagated to the code that invoked the calling code. This process continues until a section of code takes responsibility for handling the exception. Execution continues in this section of code after the exception-handling logic has completed.

Handling Exceptions

4-5

As an example, suppose the A method calls the B method. As part of its processing, the B method calls the C method. While it is running, the C method throws an exception. This exception may cause the C method to abort, and the exception is passed back to the B method. If the B method is not prepared to handle the exception, it also aborts and the same exception is passed back to the A method. If the A method handles the exception, execution continues in the A method after the exception-handling logic. If the A method is not prepared to handle the exception, the exception will be propagated back to the method that called the A method. If this is the Main or other startup method, and Main is also not prepared to handle the exception, the application reports the unhandled exception to the user and then terminates. A method can catch and handle its own exceptions to provide a degree of robustness that the calling code may not even be aware of. For example, a method that updates a database may catch an exception that occurs if the connection to the database fails. It may try connecting again, possibly with an alternative set of credentials. This process can be hidden from the code that called the method.

The Exception Type


In the previous scenario, the A method is not aware that the B method called the C method. Consequently, when the B method aborted because it could not handle the exception, it was the B method that caused the exception as far as the A method was concerned. When an exception occurs, it is therefore useful to include information about the original cause so that the method that handles the exception can take the appropriate corrective action. In the .NET Framework, exceptions are based on the Exception class, which contains information about the exception. When a method throws an exception, it creates an Exception object and can populate it with information about the cause of the error. This object is passed to the code that handles the exception, which can use it to determine the best way to handle the exception. Question: Discuss your experiences of applications that have crashed, with other students and the instructor.

4-6

Programming in Visual Basic with Microsoft Visual Studio 2010

Using a Try/Catch Block

The Try/Catch block is the key programming construct for SEH. You wrap code that may fail and cause an exception in a Try block, and add one or more Catch blocks to handle any exceptions that may occur.

Try/Catch Block Syntax


The syntax for using a Try/Catch block is shown in the following code example.
Try ' Try block. Catch[catch specification 1] ' Catch block 1. Catch[catch specification n] ' Catch block n. End Try

The statements that are enclosed in the Try block can be any Visual Basic statements, and can invoke methods in other objects. If any of these statements cause an exception to be thrown, execution passes to the appropriate Catch block.

Note: When the code in the Catch block has completed, execution will continue at the first statement after the Try/Catch block. The Catch specification for each block determines what exceptions will be caught and what variable is used to store the exception, if any. You can specify Catch blocks for different types of exceptions. The .NET Framework defines many different exception types for many of the common exceptions that can occur. For example, some methods in the System.IO namespace that handle file I/O throw the FileNotFoundException exception if an application attempts to access a nonexistent file. In addition, the common language runtime (CLR) itself throws a DivideByZeroException exception if you attempt to perform numeric division by zero.

Handling Exceptions

4-7

When an exception occurs, you do not have to include a Catch block for every type of exception, and exceptions that are not matched will be propagated as described earlier. The most general form of the Catch block is one that has no catch specification, so it catches any type of exception. This is illustrated in the following code example.
Try ' Try block. Catch ' Catch block. End Try

In this code example, any exception that is thrown in the Try block will transfer control to the Catch block. However, you will not be able to determine the cause of the exception. To access this information, you must provide a variable to use in the Catch specification, as the following code example shows.
Try ' Try block. Catch ex As Exception ' Catch block, can access exception in ex. End Try

The exception information that is generated by the code that threw the exception is passed in this variable. Note that this code will also catch any type of exception. You frequently use the Exception type to catch all exceptions that have not been otherwise handled. In the following code example, if the code in the Try block causes a DivideByZeroException exception, the code in the corresponding Catch block runs. If any other type of exception occurs, the code in the Catch block for the Exception type runs.
Try ' Try block. Catch ex As DivideByZeroException ' Catch block, can access DivideByZeroException exception in ex. Catch ex As Exception ' Catch block, can access exception in ex. End Try

Sequencing Catch Blocks


You must put your Catch blocks in the correct order. When an exception is thrown, the CLR attempts to match the exception against each Catch block in turn. You must put more specific Catch blocks before less specific Catch blocks, otherwise your code will not compile.

Note: Exception types can implement a hierarchy of exceptions. For example, the .NET Framework provides an exception type called ArithmeticException, which you can use to indicate an error when evaluating an arithmetic expression. The DivideByZeroException type is a specific classification of ArithmeticException (the .NET Framework also defines two other types of ArithmeticException called OverflowException and NotFiniteNumberException). If you catch the DivideByZeroException exception, only that exception type is caught. However, if you catch the ArithmeticException exception, this catch block will trap DivideByZeroException, OverflowException, and NotFiniteNumberException. Therefore, if you have multiple catch blocks, you must ensure that you place the blocks for more specific exceptions before those for less specific ones. The Exception type is the least specific of all, and should be the final Catch block if you use it.

4-8

Programming in Visual Basic with Microsoft Visual Studio 2010

Nesting Try/Catch Blocks


A Try/Catch block is a programming construct like any other statement in Visual Basic. You can nest Try/Catch blocks, so a Try block can contain a Try/Catch block, as the following code example shows.
Try ' Outer Try block. ... Try ' Nested Try block Catch ex As FileNotFoundException ' Catch block for nested Try block End Try ... ' Outer Try block continued Catch ex As DivideByZeroException ' Catch block, can access DivideByZeroException exception in ex. Catch ex As Exception ' Catch block, can access exception in ex. End Try

If a FileNotFoundException exception occurs in the nested Try block, the nested Catch block runs. Execution continues in the nested Try block, at the first statement after the nested Catch block. If any other type of exception occurs in the nested Try block, the exception is propagated to the outer Try block, where it is caught by the Catch block for the Exception type. Execution then continues at the first statement after the outer Try/Catch block. Nesting provides a convenient mechanism for handling and recovering certain types of exception locally within a method.

Try/Catch Example
The following code example shows an example of a Try/Catch block used for file access.
Dim reader As StreamReader = Nothing Try Dim fileName As String = GetFileName() reader = New StreamReader(fileName) Dim savedData As String = reader.ReadToEnd() Catch ioex As IOException ' Handle the IO exception. Catch ex As Exception ' Handle all other types of exceptions. End Try

In this code example, the Try block contains code that attempts to read data from a file. If an exception of type IOException is thrown, the Catch block is executed. If any other type of exception is thrown, the generic Catch block is executed. Question: How would you use the Try/Catch block to catch all exceptions regardless of type, and then run some generic additional logic? Additional Reading For more information about Try/Catch blocks, see the Try...Catch...Finally Statement (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210905&clcid=0x409

Handling Exceptions

4-9

Using Exception Properties

All exception classes provide the same basic information that is common to all exceptions, but they may also provide additional information that is specific to the type of the exception. The properties that are common to all exceptions are shown in the following table. Property Message Source StackTrace TargetSite InnerException Description This property is the most commonly used and contains a string that describes the error that has occurred. This property contains a string that indicates the object or application that caused the error. This property is a string that contains the call stack at the point where the exception was thrown. This property is a string that contains the name of the method that generated the exception. This property is a member of type Exception that can be used to contain an additional exception. You can use this property to drill down into the cause of a problem in some circumstances. This property is often used in the Catch block of nested SEH code to take an exception that has been thrown and wrap it in a new exception that is then thrown and caught by code further up the call stack. This string property can be used to store a link to additional information on the error that occurred. This property is an object that you can use to store additional information about an error.

HelpLink Data

4-10

Programming in Visual Basic with Microsoft Visual Studio 2010

The following code example shows how to display the message that is provided when a DivideByZeroException exception occurs.
Try ' Try block. Catch ex As DivideByZeroException Console.WriteLine(ex.Message) End Try

Question: You have a Catch block that contains some logic to write details of any exceptions to a log file. The Catch block will catch all types of exceptions. What members of the exception class would you use to get a description and the source of the error? Additional Reading For more information about the members in the System.Exception class, see the Exception Members page at http://go.microsoft.com/fwlink/?LinkId=192912

Handling Exceptions

4-11

Using a Finally Block

Some methods may contain critical code that must always be run, even if an unhandled exception occurs. For example, a method may need to ensure that it closes a file that it was writing to, or releases some other resources before it terminates. A Finally block enables you to handle this situation. You specify a Finally block after any Catch handlers in a Try/Catch block. The Finally block specifies the code that must be run when the block finishes, irrespective of whether any exceptions, handled or unhandled, have occurred. (If an exception is caught and handled, the exception handler in the Catch block will run first, before the Finally block.) You can also add a Finally block to code that has no Catch blocks. In this case, all exceptions are unhandled, but the Finally block will always run.

Finally Block Syntax


The syntax for using a Finally block is shown in the following code example.
Try ' Try block. Catch ([catch specification 1]) ' Catch block 1. Catch ([catch specification n]) ' Catch block n. Finally ' Finally block. End Try

Flow of Control for Try/Catch/Finally


When you use Finally blocks, the flow of control is more complicated than in Try/Catch blocks. The flow of control is as follows: 1. The Try block runs.

4-12

Programming in Visual Basic with Microsoft Visual Studio 2010

2.

If an exception is thrown: If there is a matching Catch block for the exception: i. ii. The Catch block that matches the exception is executed. The Finally block executes.

If there is a matching Catch block for the exception, and this Catch block itself causes an exception: i. ii. The Catch block that matches the original exception is executed. The Finally block executes.

iii. The exception caused by the Catch handler is propagated to any enclosing Try/Catch block, or to the calling method if there is no enclosing Try/Catch block. If there is no matching Catch block for the exception: i. ii. The Finally block executes. The exception is propagated to any enclosing Try/Catch block, or to the calling method if there is no enclosing Try/Catch block. 3. If no exception is thrown, the Finally block executes.

The important thing here is that the Finally block is always executed. This enables the code in a Finally block to tidy up after an exception before any other code deals with the exception.

Try/Catch/Finally Example
The following code example shows how to implement a Try/Catch/Finally block.
Try OpenFile("MyFile") ' Open a file WriteToFile(...) ' Write some data to the file Catch ex As IOException Console.WriteLine(ex.Message) Finally CloseFile("MyFile") ' Close the file End Try

The code in the Try block calls methods that open a file and write some data to that file. If an IOException exception occurs, the Catch block displays the details of the exception. The Finally block calls the CloseFile method to close the file. This code will always run and the file will always be closed, no matter what exceptions occur. Question: Describe the difference between a Catch block and a Finally block.

Handling Exceptions

4-13

Demonstration: Raising Exceptions in Visual Studio

Demonstration Steps
1. 2. 3. 4. Open Microsoft Visual Studio 2010. In Visual Studio 2010, open the FabrikamUserManagement solution in the D:\Demofiles\Mod4\Demo1\Starter folder. Open the Module1.vb code file. In the Code Editor window, examine the following code in the Main method: 5. 6. The Main method contains a call to the Users.GetUserById method, which returns a user object for the provided user ID. If you specify a user ID that does not exist, the method returns Nothing. When the method returns, the application displays the userName field returned. The method call is in a Try/Catch block. The Catch block contains code to display details of any exceptions to the Command Prompt window.

Run the application with debugging. Switch to the Command Prompt window, and examine the Object reference not set to an instance of an object exception message. The application generated this message because a user could not be found with the ID of 5, so the GetUserById method returned Nothing. Subsequently, any code that tried to use that user object would generate a null reference exception. Because the code is enclosed in a Try/Catch block, the exception was caught and error logic was executed.

7. 8.

Stop debugging. On the Debug menu, click Exceptions.

4-14

Programming in Visual Basic with Microsoft Visual Studio 2010

9.

In the Exceptions dialog box, in the Break when an exception is list, expand Common Language Runtime Exceptions, and then expand System.

10. In the Break when an exception is list, under System, locate the System.NullReferenceException row. 11. For the System.NullReferenceException row, clear the User-unhandled check box, and then select the Thrown check box. 12. In the Exceptions dialog box, click OK. 13. Run the application with debugging. Now when the application tries to use the user object and generates a null reference exception, Visual Studio stops the application and notifies you. Question: How can you guarantee that Visual Studio will always notify you if an exception occurs, instead of automatically propagating the exception to a Catch block?

Handling Exceptions

4-15

Lesson 2

Raising Exceptions

Using a Try/Catch block enables an application to catch and handle exceptions. These exceptions may be thrown by the CLR if an application attempts to perform an illegal operation, such as attempting to divide by zero, or accessing a file for which the user running the application does not have permission. However, an application may also detect its own fault conditions, such as an invalid combination of arguments passed as parameters into a method. In this case, it is useful for the application to throw an exception that indicates the reason for the fault. This lesson explains the key concepts that enable you to create and raise exceptions.

Lesson Objectives:
After completing this lesson, you will be able to: Describe how to create a new exception object by using some of the predefined exception types that are provided with the .NET Framework. Explain how to throw an exception by using the Throw keyword. Describe some of the best practices for raising and handling exceptions.

4-16

Programming in Visual Basic with Microsoft Visual Studio 2010

Creating an Exception Object

You can add your code to your own methods that detect fault conditions, and then throw a corresponding exception to indicate the nature of the fault to the caller. The calling code can catch and handle the exception, as described in the previous lesson. The .NET Framework provides a wide range of built-in exception types, which all inherit from the Exception class. (You will learn more about inheritance in Visual Basic in Module 8, Inheriting from Classes and Implementing Interfaces.) Each exception type is intended to indicate a specific classification of exception. For example, the FileNotFoundException exception type indicates that an attempt was made to open a file that does not exist, and DivideByZeroException is used to indicate an attempt to divide by zero in a mathematical expression. There is nothing to stop you from throwing any type of exception in a method, but it is considered good practice to throw an exception of a type that is appropriate to the fault condition that is detected. The following table lists some of the more commonly used exception types. Exception type ArgumentException Description You can throw this exception if the caller specifies an argument to a method that does not conform to the requirements of the method. You can use this exception type to indicate generalized errors with arguments, or you can use the ArgumentOutOfRangeException and ArgumentNullException types to indicate more specific errors (for example, if you pass the value 100 to a method, and the method expects a value between 1 and 99, or if you pass the value Nothing as an argument). You can throw this exception if the caller specified an argument that contains data that does not have the required format. For example, if the caller passes a string argument that does not contain information in the format that the method expects, the method should throw a FormatException exception. You can throw this exception to indicate that you have not yet

FormatException

NotImplementedException

Handling Exceptions

4-17

Exception type

Description implemented the code in a method. This exception is primarily useful while you are developing code when you have defined the method, but have not written the code for the body of the method.

NotSupportedException

You can throw this exception if a caller attempts to perform an unsupported operation by using your method, such as specifying arguments that indicate that the caller wants to write to a read-only file.

FileNotFoundException You can throw these exceptions in methods that attempt to open files DirectoryNotFoundException on behalf of a caller, if the name of the file that is indicated by arguments that the caller specifies references a file that does not exist, DriveNotFoundException or the file is in a folder or drive that does not exist.

Note: You can also create your own custom exception types by inheriting from the System.Exception class. Inheritance in Visual Basic is described in Module 8, Inheriting from Classes and Implementing Interfaces.

Syntax for Creating an Exception Object


You use the New keyword to create an exception object. Specify the type of the exception, and provide information that indicates the cause of the exception. You typically provide this information as a string that contains an error message, although you can also include another exception object if your exception was the result of another exception. The text of the error message is made available to the Catch block that handles the exception in the Message property of the exception. If you include another exception object in your exception, the details are available to the Catch block that handles the exception in the InnerException property. The following code example shows two examples of how to create a FormatException object.
' Example 1 ' Create a FormatException containing an error message. Dim ex As New FormatException("Argument has the wrong format") ... ' Example 2 Try ... ' Statements that might cause an exception if data ... ' is in the wrong format Catch e As Exception ' Create a FormatException containing an error message ' and a reference to the original exception. Dim ex As New FormatException("Argument has the wrong format", e) ... End Try

Different exception classes can provide constructors that take additional parameters. The following code example shows the ArgumentOutOfRangeException exception. This exception type has a constructor that can take two string parameters. The first parameter is the name of a parameter that is out of range, and the second parameter is the text of the error message.
Dim argEx As New ArgumentOutOfRangeException("param1", "Parameter param1 too large.")

4-18

Programming in Visual Basic with Microsoft Visual Studio 2010

Question: You are in the process of adding several new methods to your application. So far you have added the method signatures. What else should you do to indicate that the method is not complete and functional?

Handling Exceptions

4-19

Throwing an Exception

After you have created an exception object, you can throw it to indicate that an exception has occurred. When you throw an exception, execution of the current block of code terminates, and the CLR passes control to the first available exception handler that catches the exception, as described in Lesson 1 of this module.

Note: Throwing an exception is an expensive operation in terms of CPU cycles, so you should use it with care.

Syntax for Throwing an Exception


To throw an exception, you use the throw keyword and specify the exception object to throw. The following code example shows the syntax.
Throw [exception object]

For example, you create and throw a FormatException exception as shown in the following code example.
Dim ex As New FormatException("Argument has the wrong format") Throw ex

Rethrowing an Exception
A common strategy is for a method or block of code to catch any exceptions and attempt to handle them. If the Catch block for an exception cannot resolve the error, it can rethrow the exception and propagate it to the caller. To do this, specify the Throw keyword, as the following code example shows.
Try ... ' Statements that might cause an exception

4-20

Programming in Visual Basic with Microsoft Visual Studio 2010

Catch e As Exception ' Attempt to handle the exception ... ' If this catch handler cannot resolve the exception, ' throw it to the calling code Throw End Try

Question: Where does execution continue after you perform a Throw statement?

Handling Exceptions

4-21

Best Practices for Handling and Raising Exceptions

The constructs for implementing exception handling in your applications are straightforward to use, but as with any programming constructs, it is important to follow a good design. The following list explains some of the best practices for handling exceptions: Throw an exception that is appropriate to the error condition that is detected. The logic in your application should not rely on Try and Catch blocks to function under nonexceptional conditions. You should design your methods so that, under normal circumstances, they will not throw exceptions; only catch and throw exceptions for conditions that are outside the expected logical flow of an application. When you define multiple Catch blocks, order them from the most specific to the least specific. If you catch the Exception type, it must be the final handler in a set of Catch blocks. Catch and log detailed exception messages for diagnostic purposes, and then display user-friendly messages to the user. Remember that any text that is displayed to the user should be localizable, and the text should be retrieved from resource files. The following code example shows how you can write a message to the Windows event log.
Imports System.Diagnostics ... ' The event source name. Dim source As String = "My Visual Basic application" ' The event log to write to. Dim log As String = "Application" ' The message you want to write. Dim message As String = "An error with code ex1032 has occurred..." ' Check to see if the event source exists, and if not, create it. If Not EventLog.SourceExists(source) Then

4-22

Programming in Visual Basic with Microsoft Visual Studio 2010

EventLog.CreateEventSource(source, log) End If ' Write the message to the event log. EventLog.WriteEntry(source, message, EventLogEntryType.Error)

Do not display detailed exception messages to the user because a malicious user could use detailed information to cause your application to malfunction, or even gain access to protected information. A common mistake that is made in data access layers is to provide detailed error information resulting from an incorrect database query. This can enable a malicious user to understand the underlying logic in your application and use this knowledge to attack your system. Effective exception handling should enable your application to recover from exceptions, and enable the user to continue using your application. In the event of an exception, the user should not lose data, and your application should not crash.

Question: In your application, you have a method that returns a user object. When you have the user object, you are going to use it as a parameter in another method call. There is a possibility that some of the data in the user object is incorrectly formatted, and if you try to use this data, it would cause an exception. What would you do in this situation?

Handling Exceptions

4-23

Lab: Handling Exceptions

Objectives
After completing this lab, you will be able to: Add code to make a method fail-safe. Add code to a method to detect a condition and throw an exception if that condition is met.

Introduction
In this lab, you will catch and handle the possible exceptions that can occur in a method. You will also use the Finally construct to implement code that runs even if an exception occurs. You will also add code that throws an exception if an error condition is detected in a method.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

4-24

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Scenario

Fabrikam, Inc. produces a range of highly sensitive measuring devices that can repeatedly measure objects and capture data. Exception handling and resource management are a critical part of all of the applications that Fabrikam, Inc. develops. Failure to handle exceptions correctly in software that drives a large piece of machinery could result in life-threatening situations. Even in smaller, less critical scientific devices, an unhandled exception could result in lost data and the need to repeat experiments.

Handling Exceptions

4-25

Exercise 1: Making a Method Fail-Safe


In this exercise, you will add fail-safe functionality to an application to ensure that it continues to function even if one or more exceptions occur. The code itself is located in a Windows Presentation Foundation (WPF) application that acts as a test harness.

Scenario
Fabrikam, Inc. provides intelligent switching devices that can monitor the environment for a critical condition (such as the temperature exceeding a specified value), and trigger a shutdown operation. These switching devices are used in applications in the energy industry to initiate the shutdown of nuclear reactors. The correct operation of these devices is essential. Fabrikam, Inc. is developing a new model of switching device, and requires you to write part of the software that controls its operation. You have been provided with the code that performs the shutdown operation. This code contains a number of steps, and they must all be run. If any step fails, the code must report the failure, but continue with the next step. The main tasks for this exercise are as follows: 1. 2. 3. 4. Open the Failsafe solution and run the application. Examine the Switch class. Handle the exceptions that the Switch class throws. Test the application.

Task 1: Open the Failsafe solution and run the application.


Open Microsoft Visual Studio 2010: Open the Failsafe solution in the D:\Labfiles\Lab04\Ex1\Starter folder. Set the SwitchTestHarness project as the startup project. Run the Failsafe project and repeatedly click Shutdown until an exception occurs.

Note: The Switch class is designed to randomly throw an exception, so you may not encounter an exception the first time that you click the button. Repeatedly click the Shutdown button until an exception occurs.

Task 2: Examine the Switch class.


If it is not already open, open the Switch.vb file in Visual Studio. Examine the Switch class.l

Note that the class contains several methods, each of which is capable of throwing at least one exception, dependent on the outcome of a random number generation. Toward the end of the file, note the definitions of each of the custom exceptions that the Switch class can throw. These are very basic exception classes that simply encapsulate an error message.

Task 3: Handle the exceptions that the Switch class throws.


The SwitchTestHarness project contains a reference to the SwitchDevice class and invokes each method in the Switch class to simulate polling multiple sensors and diagnostic devices. Currently, the project contains no exception handling, so when an exception occurs, the application will fail. You must add exception-handling code to the SwitchTestHarness project, to protect the application from exceptions that the Switch class throws.

4-26

Programming in Visual Basic with Microsoft Visual Studio 2010

Open the MainWindow.xaml.vb file in Visual Studio. In the MainWindow class, locate the ShutDownButton_Click method. This method runs when the user clicks Shutdown. Remove the comment, TODO:- Add exception handling, and then locate the Step 1 - disconnect from the Power Generator and Step 2 - Verify the status of the Primary Coolant System comments. Enclose the code between these comments in a Try/Catch block that catches the SwitchDevices.PowerGeneratorCommsException exception. This is the exception that the DisconnectPowerGenerator method can throw. In the Catch block, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 1:" and then the contents of the Message property of the exception. The Message property contains the error message that the Switch object specified when it threw the exception.

Hint: To append a line of text to a TextBlock control, use the &= operator on the Text property of the control. Enclose the code between the Step 2 - Verify the status of the Primary Coolant System and Step 3 - Verify the status of the Backup Coolant System comments in a Try/Catch block, which catches the SwitchDevices.CoolantPressureReadException and SwitchDevices.CoolantTemperatureReadException exceptions. In each exception handler, following the same pattern as step 3, print a message on a new line in the textBlock1 control (note that this is step 2, not step 1 of the shutdown process). Enclose the code between the Step 3 - Verify the status of the Backup Coolant System and Step 4 - Record the core temperature prior to shutting down the reactor comments in a Try/Catch block, which catches the SwitchDevices.CoolantPressureReadException and SwitchDevices.CoolantTemperatureReadException exceptions. In each exception handler, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 3:" and then the contents of the Message property of the exception. Enclose the code between the Step 4 - Record the core temperature prior to shutting down the reactor and Step 5 - Insert the control rods into the reactor comments in a Try/Catch block, which catches the SwitchDevices.CoreTemperatureReadException exception. In the exception handler, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 4:" and then the contents of the Message property of the exception. Enclose the code between the Step 5 - Insert the control rods into the reactor and Step 6 Record the core temperature after shutting down the reactor comments in a Try/Catch block, which catches the SwitchDevices.RodClusterReleaseException exception. In the exception handler, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 5:" and then the contents of the Message property of the exception. Enclose the code between the Step 6 - Record the core temperature after shutting down the reactor and Step 7 - Record the core radiation levels after shutting down the reactor comments in a Try/Catch block, which catches the SwitchDevices.CoreTemperatureReadException exception. In the exception handler, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 6:" and then the contents of the Message property of the exception. Enclose the code between the Step 7 - Record the core radiation levels after shutting down the reactor and Step 8 - Broadcast "Shutdown Complete" message comments in a Try/Catch block, which catches the SwitchDevices.CoreRadiationLevelReadException exception. In the exception handler, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 7:" and then the contents of the Message property of the exception.

Handling Exceptions

4-27

Enclose the two statements after Step 8 - Broadcast "Shutdown Complete" message comments in a Try/Catch block, which catches the SwitchDevices.SignallingException exception. In each exception handler, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 8:" and then the contents of the Message property of the exception. Build the solution and correct any errors.

Task 4: Test the application.


Run the application, and then click the Shutdown button. Examine the messages displayed in the MainWindow window, and verify that exceptions are now caught and reported.

Note: The Switch class randomly generates exceptions as before, so you may not see any exception messages the first time you click the button. Repeat the process of clicking the button and examining the output until you see exception messages appear.

4-28

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 2: Detecting an Exceptional Condition


In this exercise, you will modify a method so that it throws an ArgumentException exception if it is invoked with arguments that contain erroneous or invalid data.

Scenario
One of the engineering devices that Fabrikam, Inc. produces performs several calculations that involve matrices. These matrices represent the coordinates of sets of points within the bounds of a multidimensional mesh. The device itself collects the data for these points and constructs the matrices. Then, it uses a Visual Basic method to multiply them together to generate a new set of data points. Under normal operations, none of the data items in any of the matrices should be negative. However, sometimes the data that the device captures contains an errorif the device detects a value that is out of range, it generates the value 1 for a data point. Unfortunately, the code that multiplies matrices together fails to detect this condition, and calculates a result that is erroneous. You have been provided with a copy of this code as a method that is embedded in a WPF application. The main tasks for this exercise are as follows: 1. 2. 3. 4. Open the MatrixMultiplication solution. Add code to throw exceptions in the MatrixMultiply method. Handle the exceptions that the MatrixMultiply method throws. Implement test cases and test the application.

Task 1: Open the MatrixMultiplication solution.


In Visual Studio, open the MatrixMultiplication solution in the D:\Labfiles\Lab04\Ex2\Starter folder. Open the Matrix.vb file, and then locate the MatrixMultiply method. Examine the MatrixMultiply method.

The MatrixMultiply method performs the arithmetic to multiply together the two matrices passed as parameters and return the result. Currently, the method accepts matrices of any size, and performs no validation of data in the matrices before calculating the results. You will add checks to ensure that the two matrices are compatible (the number of columns in the first matrix is equal to the number of rows in the second matrix), and that no value in either matrix is a negative number. If the matrices are not compatible, or either of them contains a negative value, the method must throw an exception.

Task 2: Add code to throw exceptions in the MatrixMultiply method.


In the MatrixMultiply method, locate and remove the comment, TODO: Evaluate input matrices for compatibility. Below the comment block, add code to perform the following actions. Compare the number of columns in matrix1 to the number of rows in matrix2. Throw an ArgumentException exception if the values are not equal. The exception message should specify that the number of columns and rows should match.

Hint: You can obtain the number of columns in a matrix by examining the length of the first dimension. You can obtain the number of rows in a matrix by examining the length of the second dimension.

Handling Exceptions

4-29

Locate and remove the comment, TODO: Evaluate matrix data points for invalid data. At this point, the method iterates through the data points in each matrix, multiplying the value in each cell in matrix1 against the value in the corresponding cell in matrix2. Add code below the comment block to perform the following actions. Check that the value in the current column and row of matrix1 is greater than zero. The cell and row variables contain the column and row that you should examine. Throw an ArgumentException exception if the value is not greater than zero. The exception should contain the message, "Matrix1 contains an invalid entry in cell(x, y)." where x and y are the column and row values of the cell.

Hint: Use the String.Format method to construct the exception message. Add another block of code to check that the value in the current column and row of matrix2 is greater than zero. If it is not, throw an ArgumentException exception with the message, "Matrix2 contains an invalid entry in cell(x, y).". The column and cell variables contain the column and row that you should examine.

Task 3: Handle the exceptions that the MatrixMultiply method throws.


Open the MainWindow Windows Presentation Foundation (WPF) window in the Design View window and examine the window. This window provides the user interface that enables the user to enter the data for the two matrices to be multiplied. The user clicks the Calculate button to calculate and display the result. Open the code file for the MainWindow WPF window. In the MainWindow class, locate the ButtonCalculate_Click method. This method runs when the user clicks the Calculate button. In the ButtonCalculate_Click method, locate the line of code that invokes the Matrix.MatrixMultiply method, and enclose this line of code in a Try/Catch block that catches an ArgumentException exception named, ex. In the Catch block, add a statement that displays a message box that contains the contents of the Message property of the exception object.

Hint: You can use the MessageBox.Show method to display a message box. Specify the message to display as a string passed in as a parameter to this method. Build the solution and correct any errors. Start the application without debugging. In the MainWindow window, in the first drop-down list box, select Matrix 1: 2 Columns, in the second drop-down list box, select Matrix 1: 2 Rows, and then in the third drop-down list box, select Matrix 2: 2 Columns.

This creates a pair of 2 2 matrices initialized with zeroes. Enter some non-negative values in the cells in both matrices, and then click Calculate.

Verify that the result is calculated and displayed, and that no exceptions occur. Enter one or more negative values in the cells in either matrix, and then click Calculate again.

4-30

Programming in Visual Basic with Microsoft Visual Studio 2010

Verify that the appropriate exception message is displayed, and that it identifies the matrix and cell that is in error. Close the MainWindow window and return to Visual Studio.

The application throws and catches exceptions, so you need to test that the application functions as expected. Although you can test for negative data points by using the application interface, the user interface does not let you create arrays of different dimensions. Therefore, you have been provided with unit test cases that will invoke the MatrixMultiply method with data that will cause exceptions. These tests have already been created; you will just run them to verify that your code works as expected.

Task 4: Implement test cases and test the application.


In the Matrix Unit Test Project, open the MatrixTest class, and then examine the MatrixMultiplyTest1 method.

The MatrixMultiplyTest1 method creates four matrices: matrix1, matrix2, expected, and actual. The matrix1 and matrix2 matrices are the input matrices that are passed to the MatrixMultiply method during the test. The expected matrix contains the expected result of the matrix multiplication, and the actual matrix stores the result of the MatrixMultiply method call. The method invokes the MatrixMultiply method before using a series of Assert statements to verify that the expected and actual matrices are identical. This test method is complete and requires no further work. Examine the MatrixMultiplyTest2 method.

This method creates two compatible matrices, but matrix2 contains a negative value. This should cause the MatrixMultiply method to throw an exception. The MatrixMultiplyTest2 method is prefixed with the ExpectedException attribute, indicating that the test method expects to cause an ArgumentException exception. If the test does not cause this exception, it will fail. Examine the MatrixMultiplyTest3 method.

This method creates two incompatible matrices and passes them to the MatrixMultiply method, which should throw an ArgumentException exception as a result. Again, the method is prefixed with the ExpectedException attribute, indicating that the test will fail if this exception is not thrown. Run all tests in the solution, and verify that all tests run correctly. Close Visual Studio.

Handling Exceptions

4-31

Lab Review

Review Questions
1. 2. What construct did you use to make the method calls fail-safe? What attribute did you need to decorate the test method so that it expected an exception?

4-32

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Review and Takeaways

Review Questions
1. In your application, you have a method call that depends on many variables that are out of the control of your application. It is very likely that this method call will throw an exception. You have implemented a centralized exception-handling system so that all exceptions are caught and handled in a single place. When you make the method call, if an exception is thrown, you just want to ensure that you manage and close any resources. Which construct would you use? In your application, you have defined several custom exception classes. You have several Catch blocks that catch this type of exception. In your Catch blocks, you want to wrap this type of exception in a more generic exception type. What constructor parameter can you set to ensure that the more specific exception is included in the chain? What should you do with detailed exception messages?

2.

3.

Best Practices Related to Implementing Exception Handling


Supplement or modify the following best practices for your own work situations: Always design your applications with errors in mind. Users will always find ways to break your application. Design your exception handling such that all exceptions are handled in a centralized location. Do not design your application to rely on exceptions to function normally. Do not display detailed exception messages to the user because a malicious user could use detailed technical information to make your application malfunction.

Reading and Writing Files

5-1

Module 5
Reading and Writing Files
Contents:
Lesson 1: Accessing the File System Lesson 2: Reading and Writing Files by Using Streams Lab: Reading and Writing Files 5-3 5-23 5-38

5-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

The ability to access and manipulate the files on the file system is a common requirement for many applications. This module shows how to read and write to files by using the classes in the Microsoft .NET Framework. This module also describes the different approaches that you can take, and how to read and write different formats of data.

Objectives:
After completing this module, you will be able to: Describe how to access the file system by using the classes that the .NET Framework provides. Describe how to read and write files by using streams. Describe how to use the My namespace for reading and writing files.

Reading and Writing Files

5-3

Lesson 1

Accessing the File System

This lesson introduces several classes that provide functionality that an application can use to interact with files and directories, and with the My namespace.

Lesson Objectives:
After completing this lesson, you will be able to: Describe how to control files by using the File and FileInfo classes. Describe how to read from and write to a file by using the File class. Describe how to read from and write to a file by using the My namespace. Describe how to manipulate directories by using the Directory and DirectoryInfo classes. Describe how to specify file paths by using the Path class. Describe how to use the common file system dialog boxes.

5-4

Programming in Visual Basic with Microsoft Visual Studio 2010

Manipulating Files

A common requirement for many applications is the ability to work with files that are stored on the file system. This can involve creating a new file, copying or deleting a file, or moving a file from one directory to another. To help simplify these interactions, the .NET Framework provides several classes in the System.IO namespace. These include the File and FileInfo classes.

The File Class


The File class is a utility class that wraps various file-related functions. These functions are exposed through static methods. The following table describes some of the key methods that the File class provides and shows some code examples. Method AppendAllText Description Enables you to open an existing file, append text to that file, and then close the file, all in a single operation. Enables you to copy an existing file to a new location. Code example
Dim filePath As String = "..." Dim fileContents As String = "..." File.AppendAllText(filePath, fileContents)

Copy

Dim sourceFile As String = "..." Dim destFile As String = "..." Dim overwrite As Boolean = False File.Copy(sourceFile, destFile, overwrite) Dim filePath As String = "..." Dim bufferSize As Integer = 128 Dim file As FileStream = File.Create(filePath, bufferSize,

Create

Enables you to create a new file on the Windows file system. The Create method returns a

Reading and Writing Files

5-5

Method

Description FileStream object that enables you to interact with the file by using the streaming model. Streams are covered in the next lesson.

Code example
FileOptions.None)

Delete

Enables you to delete a file from the Windows file system. Enables you to determine whether a file exists. Enables you to get the creation time of a file.

Dim filePath As String = "..." File.Delete(filePath) Dim filePath As String = "..." Dim exists As Boolean = File.Exists(filePath) Dim filePath As String = "..." Dim time As DateTime = File.GetCreationTime(filePath) Dim filePath As String = "..." Dim time As DateTime = File.GetLastAccessTime(filePath) Dim sourceFile As String ="..." Dim destFile As String = "..." File.Move(sourceFile, destFile)

Exists

GetCreationTime

GetLastAccessTime

Enables you to get the last access time of a file. Enables you to move a file to a new location. You can also use this method to rename a file. Enables you to read all the text from a file into a string variable. Enables you to set the creation time of a file.

Move

ReadAllText

Dim filePath As String = "..." Dim fileContents As String = File.ReadAllText(filePath) Dim filePath As String = "..." File.SetCreationTime(filePath, DateTime.Now) Dim filePath As String = "..." File.SetLastAccessTime(filePath, DateTime.Now) Dim filePath As String = "..." Dim fileContents As String ="..." File.WriteAllText(filePath, fileContents)

SetCreationTime

SetLastAccessTime

Enables you to set the last access time of a file. Enables you to create a new file, write text to that file, and then close the file, all in a single operation.

WriteAllText

The FileInfo Class


The FileInfo class provides several properties and instance methods that enable you to create, copy, and move files, and process the contents of files.

5-6

Programming in Visual Basic with Microsoft Visual Studio 2010

When you create an instance of the FileInfo class, you specify the path to a file on the file system. The following code example shows how to create a new FileInfo object for controlling the myFile.txt file in the C:\Temp folder.
Dim filePath As String = "C:\Temp\myFile.txt" Dim file As New FileInfo(filePath)

You can then use the FileInfo object as a wrapper for the file, which exposes various data and functions through properties and methods. You can also use the FileInfo class to create new files. The following table describes some of the key properties and methods and provides some code examples. Member CreationTime(property) Description Enables you to get or set the creation time for a particular file. Code example
Dim filePath As String = "..." Dim file As New FileInfo(filePath) file.CreationTime = DateTime.Now ... Dim time As DateTime = file.CreationTime

CopyTo(method)

Enables you to copy the file to a new location on the file system. Enables you to delete a file.

Dim filePath As String = "..." Dim file As New FileInfo(filePath) Dim destPath As String = "..." file.CopyTo(destPath) Dim filePath As String = "..." Dim file As New FileInfo(filePath) file.Delete() Dim filePath As String = "..." Dim file As New FileInfo(filePath) Dim dirPath As String = file.DirectoryName Dim filePath As String = "..." Dim file As New FileInfo(filePath) Dim exists As Boolean = file.Exists Dim filePath As String = "..." Dim file As New FileInfo(filePath) Dim ext As String = file.Extension Dim filePath As String = "..." Dim file As New FileInfo(filePath) Dim length As Long = file.Length Dim filePath As String = "..." Dim file As New FileInfo(filePath) Dim name As String = file.Name Dim filePath As String = "..." Dim file As New FileInfo(filePath) Dim stream As FileStream =

Delete(method)

DirectoryName(property Enables you to get the ) directory path to the file. Exists(property) Enables you to determine whether the file exists. Enables you to get the extension of the file.

Extension(property)

Length(property)

Enables you to get the length of the file in bytes. Enables you to get the name of the file.

Name(property)

Open(method)

Enables you to open a file on the Windows file system. The Open

Reading and Writing Files

5-7

Member

Description method returns a FileStream object that enables you to interact with the file by using the streaming model. Streams are covered in the next lesson.

Code example
file.Open(FileMode.OpenOrCreate)

Question: In your application, you use files as a temporary storage mechanism while the application is running. When the application stops running, you want to make sure that the file exists, and then delete the file. What is the easiest way to achieve this?

Additional Reading
For more information about the File class, see the File Class page at http://go.microsoft.com/fwlink/?LinkId=192915

For more information about the FileInfo class, see the FileInfo Class page at http://go.microsoft.com/fwlink/?LinkId=192916

5-8

Programming in Visual Basic with Microsoft Visual Studio 2010

Reading from and Writing to Files

The File and FileInfo classes provide several methods that you can use to read from and write to a file. The File class contains static methods that you can use to perform atomic operations for direct reading from and writing to files. These methods are atomic because they wrap several underlying functions into a single method call. For example, the AppendAllLines method wraps operations to acquire the file handle, open a stream to the file, write data to the file, and then release the file handle. The FileInfo class contains instance methods, which when reading from and writing to files, rely on the FileStream and StreamReader classes. The use of streams is covered in Lesson 2: Reading and Writing Files by Using Streams. This topic focuses on the static methods provided by the File class that do not use streams, but provide single atomic operations.

Reading from Files


When you use the File class to read data from a file, there are many alternative methods that you can use, each offering different functionality. The following list describes some of these methods: The ReadAllBytes method enables you to read the contents of a file as binary data, and store the data in a byte array. The following code example shows how to read the contents of the myFile.txt file into a byte array called, data.
Dim filePath As String = "myFile.txt" Dim data() As Byte = File.ReadAllBytes(filePath)

The ReadAllLines method enables you to read a text file from start to finish, line by line, and store each line in a string array. The following code example shows how to read the contents of the myFile.txt file and store each line in the string array called, lines.
Dim filePath As String = "myFile.txt" Dim lines() As String = File.ReadAllLines(filePath)

Reading and Writing Files

5-9

The ReadAllText method enables you to read a file from start to finish, and store the data from the file in a string variable. The following code example shows how to read the contents of the myFile.txt file and the data in a string called, data.
Dim filePath As String = "myFile.txt" Dim data As String = File.ReadAllText(filePath)

Writing to Files
When you write data to a file by using the File class, several options are available, depending on the type of data that you want to write. With each option, you can either append the data to an existing file, or create a new file and then perform the write operation. The following list describes some of these methods: The AppendAllLines method enables you to write the contents of a string array to a text file. If the path that you specify does not exist, the operation will create a new file. The following code example shows how to write the contents of a string array called, fileLines, to the myFile.txt file.
Dim filePath As String = "myFile.txt" Dim fileLines()As String = {"Line 1", "Line 2", "Line 3"} File.AppendAllLines(filePath, fileLines)

The AppendAllText method enables you to write the contents of a string variable to a text file. Similar to the AppendAllLines method, if the file does not exist, the operation will create the file, and then perform the write operation. The following code example shows how to write the contents of a string variable called, fileContents, to the myFile.txt file.
Dim filePath As String = "myFile.txt" Dim fileContents As String = "I am writing this text to a file called myFile.txt" File.AppendAllText(filePath, fileContents)

The WriteAllBytes method enables you to write the contents of a byte array to a binary file. If the file already exists, this operation will overwrite the file. The following code example shows how to write the contents of a byte array called, fileBytes, to a new file called, myFile.txt.
Dim filePath As String = "myFile.txt" Dim fileBytes() As Byte = {12, 134, 12, 8, 32} File.WriteAllBytes(filePath, fileBytes)

The WriteAllLines method behaves in a similar way to the AppendAllLines method in that you can write the contents of a string array to a text file. The main difference is that, if the file exists, the file will be overwritten. If the file does not exist, a new file will be created. The following code example shows how to write the contents of a string array called, fileLines, to a new file called, myFile.txt.
Dim filePath As String = "myFile.txt" Dim fileLines() As String = {"Line 1", "Line 2", "Line 3"} File.WriteAllLines(filePath, fileLines)

The WriteAllText method behaves in a similar way to the AppendAllText method in that you can write the contents of a string variable to a text file. The main difference is that if the file exists, the file will be overwritten. If the file does not exist, a new file will be created. The following code example shows how to write the contents of a string variable called, fileContents, to a new file called, myFile.txt.
Dim filePath As String = "myFile.txt"

5-10

Programming in Visual Basic with Microsoft Visual Studio 2010

Dim fileContents As String = "I am writing this text to a file called myFile.txt" File.WriteAllText(filePath, fileContents)

Question: In your application, you have just added some logic to handle exceptions. You now want to extend this logic further to store details of these exceptions to a log file on the file system so that you can diagnose any problems. You will be writing a string variable and you should want to never overwrite any existing log records in a file. Which method would you use?

Reading and Writing Files

5-11

Manipulating Directories

Files are stored in directories and folders. The .NET Framework provides a pair of classes that are similar to the File and FileInfo classes that enable you to query and manage directories. Whether you want to create a new directory, delete an existing directory, or enumerate the contents of a directory, you can achieve this by using the Directory and DirectoryInfo classes in the System.IO namespace.

The Directory Class


Similar to the File class, the Directory class is a utility class that provides various operations that enable you to manage folders and directories. The Directory class exposes its functionality through static methods. The following table describes some of the methods and provides some code examples. Method Description Code example
Dim dirPath As String ="C:\NewFolder\SubFolder" Directory.CreateDirectory(dirPath)

CreateDirectory Enables you to create all the directories that are specified in the path that dont already exist. DeleteDirectory Enables you to delete one or more directories from the file system.

Dim dirPath As String = "C:\Users\Student\MyDirectory" Dim deleteSubFolders As Boolean = True Directory.Delete(dirPath, deleteSubFolders)

GetDirectories

Enables you to get all the subdirectories in the specified path. Enables you to get all the files in the specified

Dim dirPath As String = "..." Dim dirs.() As String = Directory.GetDirectories(dirPath) Dim dirPath As String = "..."

GetFiles

5-12

Programming in Visual Basic with Microsoft Visual Studio 2010

Method

Description path.

Code example
Dim files() As String = Directory.GetFiles(dirPath) Dim dirPath As String = "..." Dim dirExists As Boolean = Directory.Exists(dirPath) Dim sourcePath As String = "..." Dim destPath As String = "..." Directory.Move(sourcePath, destPath)

Exists

Enables you to determine whether a directory exists at the specified path. Enables you to move a directory. You cannot use the Move method to move directories to different drives.

Move

The DirectoryInfo Class


The DirectoryInfo class provides several properties and instance methods that enable you to work with directories. Similar to the FileInfo class, when you create an instance of the DirectoryInfo class, you typically specify the path to a directory on the file system. The following code example shows how to create an instance of the DirectoryInfo class.
Dim dirPath As String ="C:\Users\Student\Music\" Dim dir As New DirectoryInfo(dirPath)

You can then use the DirectoryInfo object as a wrapper for the directory that exposes various data and functions through properties and methods. You can also use the DirectoryInfo class to create a new directory. For example, the following code example shows how you could determine whether the directory exists, and if it does not exist, how you could create the directory.
Dim dirPath As String ="C:\Users\Student\Music\" Dim dir As New DirectoryInfo(dirPath) If Not dir.Exists Then dir.Create() End If

The following table describes some of the key properties and methods and provides some code examples. Member Create(method) Description Enables you to create the directories in the path specified. If the directory already exists, it is ignored. Enables you to delete several directories. If the directory cannot be found, a DirectoryNotFoundEx ception exception is thrown. Code example
Dim dirPath As String = "..." Dim dir As New DirectoryInfo(dirPath) dir.Create()

Delete(method)

Dim dirPath As String = "..." Dim dir As New DirectoryInfo(dirPath) dir.Delete()

Reading and Writing Files

5-13

Member Exists(property)

Description Enables you to determine whether a directory exists at the specified path.

Code example
Dim dirPath As String = "..." Dim dir As New DirectoryInfo(dirPath) Dim exists As Boolean = dir.Exists Dim dirPath As String = "..." Dim dir As New DirectoryInfo(dirPath) Dim fullName As String = dir.FullName Dim dirPath As String = "..." Dim dir As New DirectoryInfo(dirPath) Dim dirs.() As DirectoryInfo = dir.GetDirectories()

FullName(prope Enables you to get the rty) full path of the directory.

GetDirectories( method)

Enables you to get all of the subdirectories in the specified path. This method returns a DirectoryInfo array, which enables you to use each of the DirectoryInfo members on all subdirectories. Enables you to get all of the files in the specified path. This method returns a FileInfo array, which enables you to use each of the FileInfo members on all the files in the directory. Enables you to move a directory. You cannot use the MoveTo method to move directories to different drives.

GetFiles(metho d)

Dim dirPath As String = "..." Dim dir As New DirectoryInfo(dirPath) Dim files() As FileInfo = dir.GetFiles()

MoveTo(metho d)

Dim dirPath As String = "..." Dim dir As New DirectoryInfo(dirPath) Dim destPath As String = "..." dir.MoveTo(destPath)

Name(property) Enables you to get the name of the directory.

Dim dirPath As String = "..." Dim dir As New DirectoryInfo(dirPath) Dim dirName As String = dir.Name Dim dirPath As String ="C:\Users\Student\Music\" Dim dir As New DirectoryInfo(dirPath) Dim parentDir() As FileInfo = dir.Parent

Parent(property Enables you to get the ) parent directory.

Enumerating Directory Contents


The following code example shows how you can enumerate a directory and display details of all subdirectories and the files that they contain.
Dim dirPath As String ="C:\Users\Student\Documents" ' Get all sub directories in the Documents directory. Dim subDirs() As String = Directory.GetDirectories(dirPath)

5-14

Programming in Visual Basic with Microsoft Visual Studio 2010

For Each dir As String In subDirs ' Display the directory name. Console.WriteLine("{0} contains the following files:", dir) ' Get all the files in each directory. Dim files() As String = Directory.GetFiles(dir) For Each file As String In files ' Display the file name. Console.WriteLine(file) Next

Next

Question: What class would you use to retrieve an instance of a directory in the file system, which you can then interact with?

Additional Reading
For more information about the Directory class, see the Directory Class page at http://go.microsoft.com/fwlink/?LinkId=192917

For more information about the DirectoryInfo class, see the DirectoryInfo Class page at http://go.microsoft.com/fwlink/?LinkId=192918

Reading and Writing Files

5-15

Manipulating Paths

Files are held in folders. All files and folders have a name. The combination of the name of a file and the folder where it is located constitute the path to that file. Different file systems can have different conventions and rules for what constitutes a legal file and path name. The Path class provides methods that you can use to parse and construct legal file and folder names for a specified file system.

The Path Class


The Path class exposes its functionality through various static methods. The following table describes some of the methods and provides some code examples. Method Description Code example
Dim path As String = "C:\Temp\SubFolder\MyFile.txt" Dim dirs As String = Path.GetDirectoryName(path) Dim path As String = "C:\Temp\SubFolder\MyFile.txt" Dim ext As String = Path.GetExtension(path) Dim path As String = "C:\Temp\SubFolder\MyFile.txt" Dim fileName As String = Path.GetFileName(path)

GetDirectoryN Enables you to get all ame the directories in the path. GetExtension Enables you to get the extension of the specified file. Enables you to get the file name, including the extension, from the specified path. Enables you to get the file name without the extension from the specified path.

GetFileName

GetFileName WithoutExten sion

Dim path As String = "C:\Temp\SubFolder\MyFile.txt" Dim fileName As String = Path.GetFileNameWithoutExtension(path)

5-16

Programming in Visual Basic with Microsoft Visual Studio 2010

Method

Description

Code example
Dim fileName As String = Path.GetRandomFileName()

GetRandomFil Enables you to eName generate a random folder or file name. GetTempFileN Enables you to create ame a new temp file in your local Windows temp folder. This method then returns the absolute path to that file. GetTempPath Enables you to get the path to the local Windows temp folder.

Dim tempFilePath As String = Path.GetTempFileName()

Dim tempPath As String = Path.GetTempPath()

Question: You are creating a filter that enables users to browse files by extension. To start with, you need to get the extensions of each file and then run some logic depending on the result. You also want to display the file name, including the extension, in a list. Which methods would you use to query the files?

Additional Reading
For more information about the Path class, see the Path Class page at http://go.microsoft.com/fwlink/?LinkId=192919

Reading and Writing Files

5-17

Using the My Namespace

Visual Basic provides many features for rapid application development, and one of these features, the My namespace, provides easy access to information and some default object instances. The information and the default object instances are related to the application in which you use the My namespace, and the application runtime environment. This means that certain information and objects are available in one type of application, such as a console application, whereas other information is available in a Web application. The information is organized so that is discoverable by using IntelliSense in the Code Editor.

Three of the top level members of the My namespace are exposed as objects; My.Application, My.Computer, and My.User. Each of these objects works similarly to a namespace or a class with shared members, exposing related information.

5-18

Programming in Visual Basic with Microsoft Visual Studio 2010

Note: The information and objects exposed by the My namespace are available through other classes in the .NET Framework Class Library; the My namespace simply serves as a different packaging or grouping of often used functionality for Visual Basic developers. The My.Application object exposes properties, methods, and events that are related to the current application. The My.User object exposes information about the current user.

The My.Computer Object


The My.Computer object exposes information for manipulating computer components, including audio, time, keyboard, and file system. This includes the following properties. Property Name Audio Clipboard Clock FileSystem Info Keyboard Mouse Name Ports Registry Screen Description Returns an object that provides properties for methods for playing sounds. Returns an object that provides methods for manipulating the Clipboard. Returns an object that provides properties for accessing the current local time and Universal Coordinated Time from the system clock. Returns an object that provides properties and methods for working with drives, files, and directories. Returns an object that provides properties for getting information about the computer, including memory and loaded assemblies. Returns an object that provides properties for accessing the current state of the keyboard. This includes a method to send keystrokes to the active window. Returns an object that provides properties for getting information about the installed mouse. Returns the name of the computer. Returns an object that provides a property and methods for interacting with the network. Returns an object that provides properties and methods for manipulating the registry. Returns the Screen object that represents the primary display screen of the computer.

The My.Computer.FileSystem property, or rather the returned object is available in most types of applications, and it contains a number of properties and methods, as shown here as code example.
My.Computer.FileSystem.CopyFile("sourceFile", "destFile") ' Similar to File.Copy(sourceFile, destFile, overwrite) My.Computer.FileSystem.DeleteFile("file") ' Similar to File.Delete(file) My.Computer.FileSystem.FileExists("file") ' Similar to File.Exists(file) My.Computer.FileSystem.MoveFile("sourceFile", "destFile") ' Similar to File.Move(sourceFile, destFile) My.Computer.FileSystem.CreateDirectory("dirPath") ' Similar to Directory.CreateDirectory(dirPath)

Reading and Writing Files

5-19

My.Computer.FileSystem.DeleteDirectory("dirPath", FileIO.DeleteDirectoryOption.DeleteAllContents) ' Similar to Directory.Delete(dirPath, True)

Question: What is the full name of the object in the My namespace that can be used for manipulating the file system?

Additional Reading
For more information about the My namespace, see the Development with My (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=210906&clcid=0x409

For more information about the My.Application object, see the ApplicationBase Class page at http://go.microsoft.com/fwlink/?LinkID=210907&clcid=0x409

For more information about the My.Computer object, see the Computer Class page at http://go.microsoft.com/fwlink/?LinkID=210908&clcid=0x409

For more information about the My.User object, see the User Class page at http://go.microsoft.com/fwlink/?LinkID=210909&clcid=0x409

5-20

Programming in Visual Basic with Microsoft Visual Studio 2010

Using the Common File System Dialog Boxes

When you are building an application with a graphical user interface, it is unreasonable to expect users to type long, unwieldy path and filenames. Users expect the ability to browse to files and directories through dialog boxes. Creating a dialog box, such as an open or save file dialog box found in any Microsoft application, would take a considerable amount of development and test effort. Fortunately, the .NET Framework provides the OpenFileDialog and SaveFileDialog classes in the Microsoft.Win32 namespace.

Note: You can also find the OpenFileDialog and SaveFileDialog classes in the System.Windows.Forms namespace. Before the introduction of Windows Presentation Foundation (WPF), Windows Forms used to be the primary technology for implementing Windows-based client applications in the .NET Framework, hence the inclusion in the System.Windows.Forms namespace. Both the OpenFileDialog and SaveFileDialog classes provide the functionality to enable the user to browse for a file or specify a file name and create any folders that are required. The functionality is made accessible through various properties and methods, which you can use to customize the functionality of the dialog boxes to your requirements. However, note that neither dialog box actually open or save the specified file; all they do is construct a path and file name that your application can use to open or save the file. The following table describes some of the key properties that are common to both the OpenFileDialog and SaveFileDialog classes.

Reading and Writing Files

5-21

Property CheckFileExists FileName Filter InitialDirectory Title

Description Enables you to instruct the dialog box to display a warning if the user specifies a file that does not exist. Enables you to get or set the path to the file that is selected in the dialog box. Enables you to restrict the type of files that the user can select from the dialog box. Enables you to get or set the default directory that is displayed when the dialog box is first shown. Enables you to specify a title for the dialog box.

Using the OpenFileDialog and SaveFileDialog Classes


You can use the OpenFileDialog and SaveFileDialog classes in the same way that you would use any other .NET Framework class. The first step is to create an instance of the class, as the following code example shows.
Dim openDlg As New OpenFileDialog() ... Dim saveDlg As New SaveFileDialog()

After you have created an instance of either dialog class, you can use their properties to customize their behavior. Most properties that are exposed through both classes are the same, but there are some exceptions, such as the Multiselect property in the OpenFileDialog class, and the OverwritePrompt property in the SaveFileDialog class, as the following code example shows.
... openDlg.Title = "Browse for a file to open" openDlg.Multiselect = False openDlg.InitialDirectory = "C:\Users\Student\Documents" openDlg.Filter = "Word (*.doc) |*.doc;" ... saveDlg.Title = "Browse for a save location" saveDlg.DefaultExt = "doc" saveDlg.AddExtension = True saveDlg.InitialDirectory = "C:\Users\Student\Documents" saveDlg.OverwritePrompt = True

For the dialog boxes to appear when your application is running, you need to call the ShowDialog method, as the following code example shows.
... openDlg.ShowDialog() ... saveDlg.ShowDialog()

Finally, to get the paths that the user selected, query the FileName property, as the following code example shows.
... Dim selectedFileName As String = openDlg.FileName ... Dim selectedFileName As String = saveDlg.FileName

5-22

Programming in Visual Basic with Microsoft Visual Studio 2010

Depending on whether the user selected a file, or just closed the dialog box, the value that is returned from the FileName property may be a valid absolute path, or an empty string. Therefore, you should perform some validation at this point before using the result. Question: You have almost completed your implementation of a text editor, and the final step is to get users to browse to a save location, and prompt them for a file name. What class would you use and how would you use it?

Reading and Writing Files

5-23

Lesson 2

Reading and Writing Files by Using Streams

Reading and writing data in single atomic operations as described in the previous lesson is acceptable with small amounts of data. However, when you are working with large data volumes, such operations are inefficient and can consume too many resources. An alternative approach is to use streams. This lesson introduces the .NET Framework streaming model, and the classes that you can use to implement streaming in your applications.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of streams. Describe how to read and write binary data. Describe how to read and write text. Describe how to read and write primitive data types.

5-24

Programming in Visual Basic with Microsoft Visual Studio 2010

What Are Streams?

When you work with data, whether the data is stored in a file on the file system or on a Web server that is accessible over an HTTPS connection, the data sometimes becomes too large to load into memory and transmit in a single atomic operation. For example, imagine trying to load a 100-gigabyte video file from the file system into memory in a single operation. Not only would the operation take a long time, it would also consume a large amount of memory. The .NET Framework enables you to use streams. A stream is a sequence of bytes that can come from a file on the file system, a network connection, or memory. Streams enable you to read from or write to a data source in small manageable data packets. Typically, streams provide the following operations: Reading chunks of data into a type, such as a byte array Writing chunks of data from a type to a stream Querying the current position in the stream and modifying a specific selection of bytes at the current position

Streaming in the .NET Framework


The .NET Framework provides several stream classes that enable you to work with a variety of data and data sources. When choosing which stream classes to use, you need to consider the following: What type of data you are reading or writing, for example, binary or alphanumeric. Where the data is stored, for example, on the local file system, in memory, or on a Web server over a network.

The .NET Framework class library provides several classes in the System.IO namespace that you can use to read and write files by using streams. At the highest level of abstraction, the Stream class defines the common functionality that all streams provide; it provides a generic view of a sequence of bytes together with the operations and properties that all streams provide. Internally, a Stream object maintains a pointer that refers to the current location in the data source. When you first construct a Stream object

Reading and Writing Files

5-25

over a data source, this pointer is positioned before the first byte. As you read and write data, the Stream class advances this pointer to the end of the data that is read or written. You cannot use the Stream class directly. Instead, you instantiate specializations of this class that are optimized to perform stream-based I/O for specific types of data source. For example, the FileStream class implements a stream that uses a disk file as the data source, and the MemoryStream class implements a stream that uses a block of memory as the data source.

Note: The remaining topics in this lesson focus on reading data from and writing data to files on the file system, so will use the FileStream class. However, these topics involve reading and writing a variety of data, so will be using classes such as BinaryReader, BinaryWriter, StreamReader, and StreamWriter. For more information about the FileStream class, see the content for this topic on the Course Companion CD. Question: What do you think are the benefits of streaming data?

Additional Reading
For more information about the FileStream class, see the FileStream Class page at http://go.microsoft.com/fwlink/?LinkId=192920

5-26

Programming in Visual Basic with Microsoft Visual Studio 2010

Reading and Writing Binary Data

A stream that is established by using a FileStream object is just a raw sequence of bytes. If a file contains structured data, you must convert the byte sequence into the appropriate types. This can be a timeconsuming, error-prone task. However, the .NET Framework class library contains other classes that you can use to read and write textual data and primitive types in a stream that you have opened by using a FileStream object. These classes include StreamReader, StreamWriter, BinaryReader, and BinaryWriter.

The BinaryReader and BinaryWriter Classes


Many applications store data in raw binary form because writing binary is fast, it takes up less space on disk, but it is not human readable. You can take advantage of using the binary format in your .NET Framework applications by using the BinaryReader and BinaryWriter classes. You construct a BinaryReader or BinaryWriter object by providing a stream that that is connected to the source of the data that you want to read or write. The following code example shows how to initialize the BinaryReader and BinaryWriter classes, passing a FileStream object.
Dim Dim ... Dim ... Dim filePath As String = "..." file As New FileStream(filePath) reader As New BinaryReader(file) writer As New BinaryWriter(file)

After you have created a BinaryReader object, you can use its members to read the binary data. The following table describes some of the key members.

Important: When you have finished using a StreamReader or StreamWriter object, you must call the Close method to flush the stream and release any resources that are associated with the

Reading and Writing Files

5-27

stream. You must also close the FileStream object that is providing the data for the StreamReader and StreamWriter objects. Member Description

BaseStream(property) Enables you to access the underlying stream that the BinaryReader object uses. Close(method) Read(method) ReadByte(method) ReadBytes(method) Enables you to close the BinaryReader object and the underlying stream. Enables you to read the number of remaining bytes in the stream from a particular index. Enables you to read the next byte from the stream, and advance the stream to the next byte. Enables you to read a specified number of bytes into a byte array.

Note: The BinaryReader class contains a further 16 methods that can read a binary stream and convert the data into the various primitive data types that are available with Visual Basic. These methods will be discussed in more detail later in this lesson. Similarly, the BinaryWriter object exposes various members to enable you to write data to an underlying stream. The following table describes some of the key members. Member Description

BaseStream(property) Enables you to access the underlying stream that the BinaryWriter object uses. Close(method) Flush(method) Seek(method) Write(method) Enables you to close the BinaryWriter object and the underlying stream. Any data in the buffer will be flushed to the underlying stream. Enables you to explicitly flush any data in the current buffer to the underlying stream. Enables you to set your position in the current stream, thus writing to a specific byte. Enables you to write your data to the stream, and advance the stream. The Write method provides several overloads that enable you to write all primitive data types to a stream.

Reading Binary Data


The following code example shows how to use the BinaryReader and FileStream classes to read a file that contains a collection of bytes. This example uses the Read method to advance through the stream of bytes in the file.
' Source file path. Dim sourceFilePath As String = "C:\Users\Student\Documents\BinaryDataFile.bin" ' Create a FileStream object so that you can interact with the file ' system. Dim sourceFile As New FileStream( sourceFilePath, ' Pass in the source file path. FileMode.Open, ' Open an existing file.

5-28

Programming in Visual Basic with Microsoft Visual Studio 2010

FileAccess.Read)' Read an existing file. ' Create a BinaryWriter object passing in the FileStream object. Dim reader As New BinaryReader(sourceFile) ' Store the current position of the stream. Dim position As Integer = 0 ' Store the length of the stream. Dim length As Integer = CType(reader.BaseStream.Length, Integer) ' Create an array to store each byte from the file. Dim dataCollection() As Byte ReDim dataCollection(length) Dim returnedByte As Integer While (returnedByte = reader.Read()) <> -1 ' Set the value at the next index. dataCollection(position) = CType(returnedByte, Byte) ' Advance our position variable. position += 1 End While ' Close the streams to release any file handles. reader.Close() sourceFile.Close()

Note: If a file read or file write operation throws an exception, you need to ensure that streams and file handles are released. You can use the Try/Finally block to ensure that resources are released. Typically, you should place the logic that performs the read or write in the Try block, and place any logic that closes streams and releases file handles in the Finally block.

Writing Binary Data


The following code example shows how to use the BinaryWriter and FileStream classes to write a collection of four byte integers to a file.
Dim destinationFilePath As String = "C:\Users\Student\Documents\BinaryDataFile.bin" ' Collection of bytes. Dim dataCollection() As Byte = {1, 4, 6, 7, 12, 33, 26, 98, 82, 101} ' Create a FileStream object so that you can interact with the file ' system. Dim destFile As New FileStream( destinationFilePath, ' Pass in the destination path. FileMode.Create, ' Always create new file. FileAccess.Write) ' Only perform writing. ' Create a BinaryWriter object passing in the FileStream object. Dim writer As New BinaryWriter(destFile) ' Write each byte to stream. For Each data As Byte In dataCollection writer.Write(data) Next ' Close both streams to flush the data to the file.

Reading and Writing Files

5-29

writer.Close() destFile.Close()

The above code produces a file with the following contents.

Question: Why is it important to close streams when you have finished using them?

5-30

Programming in Visual Basic with Microsoft Visual Studio 2010

Reading and Writing Text

In addition to storing data in raw binary form, you can also store data as plain text. The process for reading and writing textual data to a file is very similar to reading and writing binary data, except that you use the StreamReader and StreamWriter classes.

Note: The Console class that you can use for reading from and writing to the console contains a StreamReader property called, In, and a StreamWriter property called, Out. The Console.ReadLine method reads text data from the stream that the In property identifies, and the Console.WriteLine method writes text data to the stream that the Out property identifies.

The StreamReader and StreamWriter Classes


Similar to using the BinaryReader and BinaryWriter classes, when you initialize the StreamReader or StreamWriter classes, you must provide a stream object to handle the interaction with the data source, as the following code example shows.
Dim Dim ... Dim ... Dim destinationFilePath As String = "..." file As New FileStream(destinationFilePath) reader As New StreamReader(file) writer As New StreamWriter(file)

The following table describes some of the key members that the StreamReader class provides to enable you to read text from an underlying stream.

Reading and Writing Files

5-31

Member Close(method)

Description Enables you to close the StreamReader object and the underlying stream.

EndOfStream(property) Enables you to determine whether you have reached the end of the stream. Peek(method) Read(method) Enables you to get the next available character in the stream, but does not consume it. Enables you to get and consume the next available character in the stream. This method returns an Integer variable that represents the binary of the character, which you may need to explicitly convert. Enables you to read an entire block of characters from a specific index from the stream. Enables you to read an entire line of characters from the stream. Enables you to read all characters from the current position in the stream.

ReadBlock(method) ReadLine(method) ReadToEnd(method)

The following table shows some of the key members that the StreamWriter class provides to enable you to write text to a stream. Member Description

AutoFlush(property) Enables you to instruct the StreamWriter object to flush data to the underlying stream after every write call. Close(method) Flush(method) NewLine(property) Write(method) WriteLine(method) Enables you to close the StreamWriter object and the underlying stream. Enables you to explicitly flush any data in the current buffer to the underlying stream. Enables you to get or set the characters that are used for new line breaks. Enables you to write your data to the stream, and advance the stream. Enables you to write your data to the stream followed by a new line break, and then advance the stream.

Note: The Write and WriteLine methods each provide several overloads that enable you to write various types of data, other than text.

Reading Text
The following code example shows how to use the StreamReader and FileStream classes to read a text file. This example uses the Peek and Read methods to manually get each character in the file.
Dim sourceFilePath As String = "C:\Users\Student\Documents\TextDataFile.txt" ' Create a FileStream object so that you can interact with the file ' system. Dim sourceFile As New FileStream( sourceFilePath, ' Pass in the source file path. FileMode.Open, ' Open an existing file. FileAccess.Read) ' Read an existing file. Dim reader As New StreamReader(sourceFile)

5-32

Programming in Visual Basic with Microsoft Visual Studio 2010

Dim fileContents As New StringBuilder() ' Check to see if the end of the file ' has been reached. While reader.Peek() <> -1 ' Read the next character. fileContents.Append(CType(CChar(CStr(reader.Read)), Char) End While ' Store the file contents in a new string variable. Dim data As String = fileContents.ToString() ' Always close the underlying streams release any file handles. reader.Close() sourceFile.Close()

The following code example provides an alternative approach to manually retrieving each character from the stream, by using the ReadToEnd method.
Dim sourceFilePath As String = "C:\Users\Student\Documents\TextDataFile.txt" Dim data As String ' Create a FileStream object so that you can interact with the file ' system. Dim sourceFile As New FileStream( sourceFilePath, ' Pass in the source file path. FileMode.Open, ' Open an existing file. FileAccess.Read) ' Read an existing file. Dim reader As New StreamReader(sourceFile) ' Read the entire file into a single string variable. data = reader.ReadToEnd() ' Always close the underlying streams release any file handles. reader.Close() sourceFile.Close()

Writing Text
The following code example shows how to use the StreamWriter and FileStream classes to write a string to a new file on the file system.
Dim destinationFilePath As String = "C:\Users\Student\Documents\TextDataFile.txt" Dim data As String = "Hello, this will be written in plain text" ' Create a FileStream object so that you can interact with the file ' system. Dim destFile As New FileStream( destinationFilePath, ' Pass in the destination path. FileMode.Create, ' Always create new file. FileAccess.Write) ' Only perform writing. ' Create a new StreamWriter object. Dim writer As New StreamWriter(destFile) ' Write the string to the file. writer.WriteLine(data) ' Always close the underlying streams to flush the data to the file ' and release any file handles.

Reading and Writing Files

5-33

writer.Close() destFile.Close()

Question: You want to write a series of strings to a text file, and add a line break after each string. What is the easiest way to achieve this?

Additional Reading
For more information about the StreamWriter class, see the StreamWriter Class page at http://go.microsoft.com/fwlink/?LinkId=192921

For more information about the StreamReader class, see the StreamReader Class page at http://go.microsoft.com/fwlink/?LinkId=192922

5-34

Programming in Visual Basic with Microsoft Visual Studio 2010

Reading and Writing Primitive Data Types

When you use the BinaryReader and BinaryWriter classes, you are not restricted to using unstructured byte arrays. These classes also provide methods that enable you to read and write any data into any primitive data type, which includes integers, doubles, Booleans, and strings.

Note: The streaming model that the .NET Framework implements also supports streaming of nonprimitive types such as classes and structures that you define. These types must be serializable, and you use a formatter such as a BinaryFormatter object with a FileStream object to specify how to read and write the data. Serialization and formatting objects is outside the scope of this course.

Reading Primitive Data Types


The BinaryReader class enables you to read any primitive data type by using 16 specific read methods. The following table describes some of the read methods that the BinaryReader class provides. Method ReadBoolean ReadChar ReadChars Description Enables you to read a True/False value from a stream. Enables you to read a single character from a stream. Enables you to read a collection of characters from a stream. When you use this method, you must specify the number of characters that you want the method to return. Enables you to read a double value from a stream. Enables you to read an int value from a stream. Enables you to read a long value from a stream.

ReadDouble ReadInt ReadLong

Reading and Writing Files

5-35

Method ReadString

Description Enables you to read a string value from a stream.

Each of the read methods is designed to work with a specific data type. The method reads the required number of bytes for that type, and then advances the stream to the next block of bytes. The following code example shows how to read a file that contains a variety of primitive types.
' Source file path. Dim sourceFilePath As String = "C:\Users\Student\Documents\PrimitiveDataTypeFile.txt" ' Create a FileStream object so that you can interact with the file ' system. Dim sourceFile As New FileStream( sourceFilePath, ' Pass in the source file path. FileMode.Open, ' Open an existing file. FileAccess.Read)' Read an existing file. ' Create a BinaryWriter object passing in the FileStream object. Dim reader As New BinaryReader(sourceFile) Dim booleanValue As Boolean = reader.ReadBoolean() Dim byteValue As Byte = reader.ReadByte() Dim byteArrayValue() As Byte = reader.ReadBytes(4) Dim charValue As Char = reader.ReadChar() Dim charArrayValue() As Char = reader.ReadChars(4) Dim decimalValue As Decimal = reader.ReadDecimal() Dim doubleValue As Double = reader.ReadDouble() Dim singleValue As Single = reader.ReadSingle() Dim intValue As Integer = reader.ReadInt32() Dim longValue As Long = reader.ReadInt64() Dim sbyteValue As SByte = reader.ReadSByte() Dim shortValue As Short = reader.ReadInt16() Dim stringValue As String = reader.ReadString() Dim uintValue As UInteger = reader.ReadUInt32() Dim ulongValue As ULong= reader.ReadUInt64() Dim ushortValue As UShort = reader.ReadUInt16() ' Close the streams to release any file handles. reader.Close() sourceFile.Close()

Note: When you read an array, you must specify the number of items in the array that you want to read.

5-36

Programming in Visual Basic with Microsoft Visual Studio 2010

Writing Primitive Data Types


The BinaryWriter class enables you to write any primitive data type with the write method, which provides several overloads. The following code example shows how you can use the BinaryWriter class to write a variety of primitive data types to a file.
Dim destinationFilePath As String = "C:\Users\Student\Documents\PrimitiveDataTypeFile.txt" ' Create a FileStream object so that you can interact with the file ' system. Dim destFile As New FileStream( destinationFilePath, ' Pass in the destination path. FileMode.Create, ' Always create new file. FileAccess.Write) ' Only perform writing. ' Create a BinaryWriter object passing in the FileStream object. Dim writer As New BinaryWriter(destFile) Dim booleanValue As Boolean = True writer.Write(booleanValue) Dim byteValue As Byte = 1 writer.Write(byteValue) Dim byteArrayValue() As Byte = {1, 4, 6, 8} writer.Write(byteArrayValue) Dim charValue As Char = "a" writer.Write(charValue) Dim charArrayValue() As Char = {"a", "b", "c", "d"} writer.Write(charArrayValue) Dim decimalValue As Decimal = 1.00 writer.Write(decimalValue) Dim doubleValue As Double = 2.5 writer.Write(doubleValue) Dim singleValue As Single = 4.5 writer.Write(singleValue) Dim intValue As Integer = 999999999 writer.Write(intValue) Dim longValue As Long = 999999999999999999 writer.Write(longValue) Dim sbyteValue As SByte= 99 writer.Write(sbyteValue) Dim shortValue As Short = 9999 writer.Write(shortValue) Dim stringValue As String = "MyString" writer.Write(stringValue) Dim uintValue As UInteger = 999999999 writer.Write(uintValue) Dim ulongValue As ULong = 999999999999999999 writer.Write(ulongValue) Dim ushortValue As UShort = 9999

Reading and Writing Files

5-37

writer.Write(ushortValue) ' Close both streams to flush the data to the file. writer.Close() destFile.Close()

The above code example produces a file with the following content.

Question: What method would you use to read a 64-bit signed integer from a binary stream?

Additional Reading
For more information about the BinaryWriter class, see the BinaryWriter Class page at http://go.microsoft.com/fwlink/?LinkId=192923

For more information about the BinaryReader class, see the BinaryReader Class page at http://go.microsoft.com/fwlink/?LinkId=192924

5-38

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab: Reading and Writing Files

Objectives:
After completing this lab, you will be able to: Read and write data by using the File class. Read and write data by using a FileStream class.

Introduction
In this lab, you will use the File class in the System.IO namespace to read and write data to a file on the file system. You will then use a stream class to process this file.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

Reading and Writing Files

5-39

Lab Scenario

Fabrikam, Inc. produces a range of highly sensitive measuring devices that can repeatedly measure objects and capture data. Many of the robotic devices that Fabrikam, Inc. builds are controlled by using instructions that are held in a text file that is stored on the device. You have been asked to write a simple application that a user can use to open, display, and edit one of these text files (the device will not have Notepad installed). The application will run on the device, and make use of a small screen and keypad that is built into the device. The application must be easy to use, and include full exception handling.

5-40

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 1: Building a Simple File Editor


In this exercise, you will add functionality to a simple WPF application that can be used to edit text files. The WPF application expects the user to enter the name and path of a text file by using the Open File common dialog box. The application will then open this file and display its content in a text box on the WPF form. The user can edit this text, and then save the amended text back to the file. The user interface for this application has already been completed, but you will implement the logic to enable the user to specify the file to edit, and to load and save the file. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. Open the SimpleEditor project. Display a dialog box to accept a file name from the user. Implement a new class to read and write text to a file. Update the MainWindow event handlers to consume the TextFileOperations class. Implement test cases.

Task 1: Open the SimpleEditor project.


Open Microsoft Visual Studio 2010. Open the SimpleEditor solution in the D:\Labfiles\Lab05\Ex1\Starter folder.

Task 2: Display a dialog box to accept a file name from the user.
Display the MainWindow.xaml window. The MainWindow window implements a very simple text editor. The main part of the window contains a text box that a user can use to display and edit text. The Open button enables the user to open a file, and the Save button enables the user to save the changes to the text back to a file. You will add the code that implements the logic for these two buttons. Review the Task List. Locate and double-click the task, TODO: - Implement a method to get the file name.

This task is located in the MainWindow.xaml.vb class file. Delete the comment, and then define a new private method named, GetFileName, which accepts no parameters and returns a string value that holds the file name that the user specified. In the method body, declare a new string member named, fName, and then initialize it with the String.Empty value. At the top of the file, add a statement to bring the Microsoft.Win32 namespace into scope. In the GetFileName method, after the statement that declares the fName variable, add code to the method to perform the following actions. Create a new instance of the OpenFileDialog dialog box, named openFileDlg. Set the InitialDirectory property of openFileDlg to point to the D:\Labfiles\Lab05\Ex1\Starter folder. Set the value of the DefaultExt property of openFileDlg to ".txt". Set the value of the Filter property of openFileDlg to "Text Documents (.txt)|*.txt".

Add code to perform the following tasks. Call the ShowDialog method of openFileDlg, and then save the result.

Reading and Writing Files

5-41

Note: The value that ShowDialog returns is a nullable Boolean value, so save the result in a nullable Boolean variable. If the result is True, assign the value of the FileName property of openFileDlg to the fName variable.

At the end of the method, return the value in the fName variable.

Task 3: Implement a new class to read and write text to a file.


Add a new class named, TextFileOperations, to the FileEditor project. You will use this class to wrap some common file operations. This scheme enables you to change the way in which files are read from or written to without affecting the rest of the application. At the top of the class file, add a statement to bring the System.IO namespace into scope. In the TextFileOperations class, add a shared public method named, ReadTextFileContents. The method should accept a string parameter named, fileName, and return a string object. In the ReadTextFileContents method, add code to return the entire contents of the text file whose path is specified in the fileName parameter.

Hint: Use the shared ReadAllText method of the File class. Below the ReadTextFileContents method, add a public shared method named, WriteTextFileContents. The method should not return a value type, and should accept the following parameters. A string parameter named fileName. A string parameter named text.

In the WriteTextFileContents method, add code to write the text that is contained in the text parameter to the file that is specified in the fileName parameter.

Hint: Use the shared WriteAllText method of the File class. Build the solution and correct any errors.

Task 4: Update the MainWindow event handlers to consume the TextFileOperations


class
In the Task List, locate and double-click the task, TODO: - Update the OpenButton_Click method. This task is located in the OpenButton_Click method of the MainWindow class. Remove the comment, and then add code to perform the following tasks. Invoke the GetFileName method. Store the result of the method in the fileName member. If fileName is not an empty string, call the shared ReadTextFileContents method of the TextFileOperations class, and then pass fileName as the parameter. Store the result in the Text property of the editor TextBox control in the Windows Presentation Foundation (WPF) window.

5-42

Programming in Visual Basic with Microsoft Visual Studio 2010

In the Task List, locate and double-click the task, TODO: - Update the SaveButton_Click method.

This task is located in the SaveButton_Click method of the MainWindow class. In the SaveButton_Click method, remove the comment, and then add code to perform the following tasks. Check that the fileName member is not an empty string. If fileName is not an empty string, call the shared WriteTextFileContents method of the TextFileOperations class. Pass fileName and the Text property of the editor TextBox control as the parameters.

Build the solution and correct any errors. Start the application without debugging. In the MainWindow window, click Open. In the Open dialog box, browse to the D:\Labfiles\Lab05\Ex1\Starterfolder, click Commands.txt, and then click Open. In the MainWindow window, verify that the text in the following code example is displayed in the editor TextBox control.
Move x, 10 Move y, 20 If x < y Add x, y If x > y & x < 20 Sub x, y Store 30

This is the text from the Commands.txt file. Change the Store 30 line to Save 50, and then click Save. Close the MainWindow window. Using Windows Explorer, browse to the D:\Labfiles\Lab05\Ex1\Starterfolder. Open the Commands.txt file by using Notepad. In Notepad, verify that the last line of the file contains the text Save 50. Close Notepad and return to Visual Studio.

Task 5: Implement test cases.


In the Task List, locate and double-click the task, TODO: - Complete Unit Tests. This task is located in the TextFileOperationsTest class. Remove the comment. Examine the ReadTextFileContentsTest1 method, and then uncomment the commented line.

This method creates three strings. The fileName string contains the path of a prewritten file that contains specific content. The expected string contains the contents of the prewritten file, including formatting and escape characters. The actual string is initialized by calling the ReadTextFileContents method that you just implemented.

The test method then uses an Assert statement to verify that the expected and actual strings are the same.

Reading and Writing Files

5-43

Examine the WriteTextFileContentsTest1 method, and then uncomment the commented line.

This method creates two strings. The fileName string contains the path of a nonexistent file, which the method will create when run. The text string contains some text that the method will write to the file.

The method calls the WriteTextFileContents method, passing the fileName and text strings as parameters. This creates the file at the specified location, and writes to the file. The method then creates a further string, expected, by calling the File.ReadAllText method and reading the text from the written file. The method then checks that the text string and the expected string are the same, before deleting the file that was created during the test. Run all tests in the solution, and verify that all tests run correctly.

5-44

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 2: Making the Editor XML Aware


The applications that control a robotic device read the instructions from the file and then encode them as an XML document before passing them to the instruction execution module on the device. For example, imagine that a text file contains the instructions in the following code example.
Move x, 10 Move y, 20 If x < y Add x, y If x > y Sub x, y Store 30

The control applications will wrap them in a pair of XML tags, as the following code example shows.
<ControlApplication> <Instructions Code = " Move x, 10 Move y, 20 If x < y Add x, y If x > y Sub x, y Store 30" /> </ControlApplication>

However, some of the data in these instructions can contain characters such as ">" and "<" that might be misinterpreted as XML tags rather than data. In this exercise, you will modify the WPF application to look for data that contains XML tags in the text file as it is read in, and encode this data as XML escape sequences before displaying it. For example, the "<" character will be replaced with "&gt;", the ">" symbol will be replaced with "&lt;", and so on. The WPF application will use a file stream to read the data. The main tasks for this exercise are as follows: 1. 2. 3. 4. Open the starter project. Add a new method to filter XML characters to the TextFileOperations class. Update the user interface to invoke the new method. Implement test cases.

Task 1: Open the starter project.


Open the SimpleEditor solution in the D:\Labfiles\Lab05\Ex2\Starter folder. This project is a revised version of the SimpleEditor project from Exercise 1.

Task 2: Add a new method to filter XML characters to the TextFileOperations class.
Review the Task List. In the Task List, locate and double-click the TODO: - Implement a new method in the TextFileOperations class task. This task is located in the TextFileOperations class. Remove the comment, and then add a new shared public method named, ReadAndFilterTextFileContents. The method should accept a string parameter named, fileName, and return a string value. In the ReadAndFilterTextFileContents method, add the following local variables.

Reading and Writing Files

5-45

A StringBuilder object named, fileContents, initialized to a new instance of the StringBuilder class. An integer variable called, charCode.

Add a statement that instantiates a StreamReader object, named fileReader, by using the fileName parameter. Add a While statement that reads each character in the StreamReader object until the end of the file is reached.

Hint: Use the Read method of the StreamReader class to read the next character from a stream. This method returns 1 if there is no more data. In the While block, add a Select Case statement that evaluates the charCode variable. In the Select Case statement, add Case statements for each of the characters in the following table. In each statement, append the fileContents StringBuilder object with the alternative representation shown in the table. charCode 34 38 39 60 62 Standard representation " (straight quotation mark) & (ampersand) ' (apostrophe) < (less than) > (greater than) Alternative representation &quot; &amp; &apos; &lt; &gt;

Add a default Case statement that appends the actual character read from the stream to the fileContents StringBuilder object.

Note: The Read method returns the value read from the file as an integer and stores it in the charCode variable. You must cast this variable to a character before you append it to the end of the StringBuilder object. Use the ChrW Visual Basic function to return the character associated with the specified character code. At the end of the method, return the contents of the fileContents StringBuilder object as a string. Build the solution and correct any errors.

Task 3: Update the user interface to invoke the new method.


In the Task List, locate and double-click the TODO: - Update the UI to use the new method task. This task is located in the OpenButton_Click method of the MainWindow.xaml.vb class. Delete the comment, and then modify the line of code that calls the TextFileOperations.ReadTextFileContents method to call the TextFileOperations.ReadAndFilterTextFileContents method instead. Pass the fileName field as the parameter, and then save the result in the Text property of the editor TextBox control.

5-46

Programming in Visual Basic with Microsoft Visual Studio 2010

Build the solution and correct any errors. Start the application without debugging. In the MainWindow window, click Open. In the Open dialog box, browse to the D:\Labfiles\Lab05\Ex2\Starter folder, click Commands.txt, and then click Open. In the MainWindow window, verify that the text in the following code example is displayed in the editor TextBox control.
Move x, 10 Move y, 20 If x &lt; y Add x, y If x &gt; y &amp; x &lt; 20 Sub x, y Store 30

This is the text from the Commands.txt file. Notice that the <, >, and & characters have been replaced with the text &lt;,&gt;, and &amp;. Close the MainWindow window and return to Visual Studio.

Task 4: Implement test cases.


In the Task List, locate and double-click the TODO: - Complete Unit Tests task. This task is located in the TextFileOperationsTest class. Examine the ReadAndFilterTextFileContentsTest method, and then uncomment the commented line. This method creates three strings. The filename string contains the path of a prewritten file that contains specific content. The expected string contains the contents of the prewritten file, including formatting and escape characters. The actual string is initialized by calling the ReadAndFilterTextFileContents method that you just implemented. The test method then uses an Assert statement to verify that the expected and actual strings are the same. This method is complete, and requires no further work. Run all tests in the solution, and verify that all tests run correctly. Close Visual Studio.

Reading and Writing Files

5-47

Lab Review

Review Questions
1. 2. Explain the purpose of the File.Load and File.Save static methods. You have a file that contains text. You want to read the file one character at a time. Which method of the StreamReader class would you use?

5-48

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Review and Takeaways

Review Questions
1. 2. 3. When you write data to a stream, name two methods that you can use to ensure that any buffered data is written to the underlying data source. Which two classes does the .NET Framework provide that display a graphical control that enables you to capture a save file and open file path from a user? Which stream class would you use to write textual data?

Best Practices Related to Reading and Writing Data on the File System
Supplement or modify the following best practices for your own work situations: Always check to make sure that the file exists before you try to read from it or write to it. Do not assume that the contents in the file are going to be correct. Remember that files are stored on the file system, which users have access to. Users are more than capable of editing a file that they should not edit. Always parse a file to ensure that it is valid, or be prepared to catch and handle an appropriate exception. When you use streams, always ensure that you close the stream after use to ensure that you release any handles on the underlying data source. It is easy to assume that you will have permissions to write and read files anywhere in the live environment. Typically, this is not the case. Make sure that your development environment mirrors the live environment.

Creating New Types

6-1

Module 6
Creating New Types
Contents:
Lesson 1: Creating and Using Modules and Enumerations Lesson 2: Creating and Using Classes Lesson 3: Creating and Using Structures Lesson 4: Comparing References to Values Lab: Creating New Types 6-3 6-11 6-28 6-34 6-44

6-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

The Microsoft.NET Framework base class library consists of many types that you can use in your applications. However, in all applications, you must also build your own types that implement the logic for your solution. This module explains how to create your own modules and types and describes the differences between reference types and value types.

Objectives:
After completing this module, you will be able to: Describe how to create and use modules. Describe how to create and use enumerations. Describe how to create and use classes. Describe how to create and use structures. Explain the differences between reference and value types.

Creating New Types

6-3

Lesson 1

Creating and Using Modules and Enumerations

An enumeration is a set of related constant values that have a predefined order. They are very useful when you work with data that has a specific range of values. For example, if you model the days of the week, you can use the numbers 0 through 6 to indicate Sunday through Saturday. However, this strategy does not lead to readable or easily maintainable code. If your application contains the statement that is shown in the following code example, it is easy to see that the statement assigns the value 5 to variable d, but the purpose of this is not apparent.
d = 5

However, the statement in the following code example is immediately more intuitive, and it becomes obvious that d must refer to a day of the week.
d = DaysOfWeek.Friday

This lesson describes the purpose of enumerations. It also explains how to create new enumeration types and instantiate and assign existing enumeration types.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of modules. Describe the purpose of enumerations. Describe how to create new enumeration types. Describe how to initialize and assign existing enumeration types.

6-4

Programming in Visual Basic with Microsoft Visual Studio 2010

What Are Modules?

A module is a container for enumerations, procedures, variables, constants, and events. You can think of a module as a shared class, where all members are implicitly shared. This also means that you cannot create an instance of a module; there is always exactly one instance of the module available in the namespace in which it is declared. If you do not add an access modifier, the module is publicly available within the containing namespace, but it is possibly to declare using the Friend access modifier, to limit the availability to the containing assembly, and disallow access from consumers of the assembly. You add enumerations, procedures, variables, constants, and events to a module in the same way as you do it for a class, as is shown in the following topics and lessons. Members of a module by default use the public access level, with the exception of variables and constants (private access level). If a module has a more restricted access level, it takes precedence to members with a more permissive access level. Access modifiers and levels are covered in more details in a later module.

Defining a New Module


You can use Microsoft Visual Studio to add a new module to a project. Typically, you place each module in a separate source file and give the source file the same name as the class. Visual Studio 2010 generates template code in the source file for the new module.

Add a new module to a project


1. 2. In Solution Explorer, right-click the project, point to Add, and then click Module. In the Add New Item dialog box, in the Name box, enter a name for the source file that will contain the new module, and then click Add. The following code example shows a new Module definition called Module1, created in a source file named Module1.vb.

Creating New Types

6-5

Module Module1 ... End Module

6-6

Programming in Visual Basic with Microsoft Visual Studio 2010

What Are Enumerations?

An enumeration type specifies a set of related, named constants. An enumeration type is a scalar type that has a user-defined range of values. You can create an enumeration type, declare variables of that type, and assign values to those variables in much the same way that you can use the built-in scalar types of Visual Basic, such as Integer or Single. You can use an enumeration type to represent a set of values in a specific domain. Enumerations also help to make your code easier to read and maintain. The .NET Framework base class library contains various enumerations that you can use in your applications. Many of the .NET Framework classes use these enumerations as method return values and method parameters. You may have used enumerations in other languages, such as Java and C++; however, there are a few subtle differences. The main difference is that enumerations in Microsoft Visual Basic are based on the integral data types (such as Integer and Long), whereas enumerations in Java derive from objects, which means that each enumeration can contain its own modifiable fields.

Benefits
Enumerations provide all the advantages that constants provide, and the following additional benefits: Code is easier to maintain because you assign only anticipated values to your variables. Code is easier to read because you assign easily identifiable names to your values. Code is easier to type because Microsoft IntelliSense displays a list of the possible values that you can use. Code is well-formed because you can specify a set of constant values and define a type that will accept values from only that set. Question: Discuss with other students and the instructor where and how you have used enumerations before.

Creating New Types

6-7

Creating New Enum Types

You can create your own enumeration types by using the Enum keyword. You must assign a name to the enumeration and then list the values that your enumeration accepts. Enumerations are types, so you can declare enumerations in a class, module, or a namespace, but not in a method. The following code example shows the syntax to create an enumeration.
Enum Name Value1 Value2 ... End Enum

The following code example declares an enumeration for the seasons of the year.
Enum Seasons Spring Summer Fall Winter End Enum

Internally, an enumeration type associates an integer value with each element of the enumeration. By default, the numbering starts at 0 for the first element and increments in steps of 1. If you prefer, you can associate a specific integer constant (such as 1) with an enumeration literal (such as Spring), as in the following code example. In this case, the enumeration literals, Summer, Fall, and Winter automatically have the values 2, 3, and 4.
Enum Seasons Spring = 1 Summer Fall Winter

6-8

Programming in Visual Basic with Microsoft Visual Studio 2010

End Enum

The numeric value associated with each enumeration literal becomes significant if you write code that iterates through the possible values that an enumeration variable can have. You can give more than one enumeration literal the same underlying value. For example, in the United Kingdom, fall is referred to as autumn. You can cater to both cultures, as the following code example shows.
Enum Seasons Spring Summer Fall Autumn = Fall Winter End Enum

When you declare an enumeration, the enumeration literals are given values of type Integer. You can also base an enumeration on a different underlying integer type. The following code example declares that the underlying type of the Season enumeration is a Short rather than an Integer.
Enum Seasons As Short Spring Summer Fall Winter End Enum

The main reason to do this is to save memory; an Integer occupies more memory than a Short. If you do not require the entire range of values that are available to an Integer, it makes sense to use a smaller data type. You can base an enumeration on any of the eight integer types: Byte, SByte, Short, UShort, Integer, UInteger, Long, or ULong. The values of all the enumeration literals must fit inside the range of the chosen base type. Question: Does the following code example show a legal enumeration?
Enum Seasons As SByte Spring = -3 Summer Fall Winter End Enum

Additional Reading
For more information about enumerations, see the Enum Statement (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211203&clcid=0x409

Creating New Types

6-9

Initializing and Assigning Enum Variables

The way in which you define and assign a variable that is based on an enumeration type is very similar to the way in which you use other types in Visual Basic. The type of the variable is the name of the enumeration, and the values that you can assign are the literals that the enumeration defines. The following code example uses an enumeration called Days, which contains enumeration values for each day of the week.
Enum Days Monday = 1 Tuesday = 2 Wednesday = 3 Thursday = 4 Friday = 5 Saturday = 6 Sunday = 7 End Enum Sub Main() Dim myDayOff As Days = Days.Sunday End Sub

The variable myDayOff is declared by using the Days type. Notice that when you assign a value to the myDayOff variable, you explicitly specify the enumeration to which the literal value belongs (Days.Sunday in the example). When you create an instance of the Days enumeration, you can only assign it one of the literal values that the Days enumeration defines: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, or Sunday.

Using an Enum Variable


You can perform simple operations on an enumeration variable in much the same way that you can use an integer variable. The following code example uses an enumeration variable to iterate through the days of the week and display each one in turn.

6-10

Programming in Visual Basic with Microsoft Visual Studio 2010

Enum Days Monday = 1 Tuesday = 2 Wednesday = 3 Thursday = 4 Friday = 5 Saturday = 6 Sunday = 7 End Enum ... For dayOfWeek As Days = Days.Monday To Days.Sunday Console.WriteLine(dayOfWeek) Next Output is: Monday Tuesday Wednesday Thursday Friday Saturday Sunday

Notice that you can perform comparisons by using the literal values that the enumeration defines. The comparisons are performed by using the underlying integer values for each literal. Additionally, when you display the value of an enumeration variable, the value that is displayed is the corresponding literal from the enumeration type. However, if you increment an enumeration variable outside the range of integer values that the enumeration type uses, the value that is displayed is the underlying integer value instead. (This is usually the result of a programming error, but the Visual Basic compiler does not check whether the integer value that is assigned to an enumeration variable in this way is outside the range of values that are used for the literals that the enumeration defines). Question: Describe how to initialize an enumeration variable.

Creating New Types

6-11

Lesson 2

Creating and Using Classes

Visual Basic is an object-oriented programming language. All the logic for a Visual Basic application is contained in modules, classes, and structures. This lesson explains how to create your own classes and use them in your own .NET Framework applications. It also introduces concepts such as partial classes and partial methods.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of classes. Describe how to add fields and methods to a class. Describe how to define a constructor. Explain how to create an instance of a class. Describe how to access fields and methods in a class. Describe how to create and define partial classes and partial methods.

6-12

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is a Class?

When you create a Visual Basic application, you use classes that represent the principal data types in your application. The .NET Framework provides a large number of reusable utility classes, but you can also define your own classes that encapsulate data and logic that is specific to your own applications.

Class
You can think of a class as a blueprint from which you can create objects. A class defines the characteristics of an object, such as the data that the object can contain and the operations that the object can perform. The characteristics of a class are also known as members; members are covered in the next topic.

Object
An object is an instance of a class. If a class is like a blueprint, an object is an item that you create by using that blueprint. The class is the definition of an item; the object is the item itself. Note: The term instance is often used as an alternative to object. In the real world, the plans for a house are like a class, and a house that is built by following these plans is like an object. You can build many instances of houses by following the same set of plans. All the houses will have the same layout and structure (the same rooms), but they are still different houses. In objectoriented programming, you can define a House class that specifies a particular room layout and dimensions. You can then create one or more House objects by using this class. Each House object will have the room layout and dimensions that the class defines, but some other aspects of each House object may be different, such as the location of the House object or the color of the front door.

Creating New Types

6-13

Defining a New Class


You can use Microsoft Visual Studio to add a new class to a project. Typically, you place each class in a separate source file and give the source file the same name as the class. Visual Studio 2010 generates template code in the source file for the new class.

Add a new class to a project


1. 2. In Solution Explorer, right-click the project, point to Add, and then click Class. In the Add New Item dialog box, in the Name box, enter a name for the source file that will contain the new class, and then click Add. The following code example shows a new class definition called House, in a source file named House.vb.
Public Class House End Class

Question: Explain the difference between a class and an object.

6-14

Programming in Visual Basic with Microsoft Visual Studio 2010

Adding Members to Classes

You can add fields and methods to a class that define the data and behavior of that class. You can define any number of fields and methods in a class, depending on the purpose and intended functionality of the class. Note: The fields and methods that are described in this topic are instance fields and instance methods. An instance field is a per-instance piece of data, and an instance method is a per-instance operation. Two objects that are based on the same class have their own copy of the instance fields. However, you can share fields between instances by creating Shared members. The Shared keyword is described in detail in a later module.

Defining Fields
You can think of a field as a variable that is scoped to the class. All methods that are defined in the class can access the field. Like a variable, each field has a name, a data type, and an access modifier. If you do not explicitly specify an access modifier for a field, the default access level is private, which means that it can be accessed only by methods that are defined in the class. If you want to make the field available to methods that are defined in other classes, you can mark the field as public. Note: Access modifiers are described in more detail in a later module. You can place field definitions anywhere in a class. Some programmers prefer to place their field definitions near the start of the class to make the code easy to read for other programmers. When you define a field, you can also assign a default value to that field, although you can use a constructor to change the value that is assigned to a field when an object is created. Note: The next topic describes how you can use constructors to initialize class members.

Creating New Types

6-15

Defining Methods
A method is a procedure or function inside a class. You use methods to implement the behavior of a class. Each method has a name, a parameter list, a return type, and an access modifier. A method has complete and unrestricted access to all of the other members in the class. This is an important aspect of object-oriented programming; methods encapsulate operations on the fields in the class. When you refer to a field in the class, you can prefix the field with the Me keyword, as shown in bold in the following code example. This approach helps to disambiguate any references (for example, a parameter to a method can have the same name as a field in a class, although this is not recommended practice) and helps to make your code easier to maintain.
Public HasGarage As Boolean Public Sub OpenGarageDoor(ByVal doorId As Integer) If Me.HasGarage Then ' Code to run if a residence has a garage. End If End Sub

Example
The following code example shows a Residence class that is used as part of a real-estate application. The class has four fields that represent the type of residence, the number of bedrooms, whether the residence has a garage, and whether the residence has a garden. The class has methods that calculate the value of the residence for sale purposes and the cost of rebuilding the residence for insurance purposes.
Public Enum ResidenceType House Flat Bungalow Apartment End Enum Public Class Residence Public [Type] As ResidenceType Public NumberOfBedrooms As Integer Public HasGarage As Boolean Public HasGarden As Boolean Public Function CalculateSalePrice() As Integer ' Code to calculate the sale value of the residence. End Function Public Function CalculateRebuildingCost() As Integer ' Code to calculate the rebuilding costs of the residence. End Function End Class

Using the Class Designer


You can design a class manually by writing code in the Code Editor window. However, you can also use the Class Designer window to design a class and add fields and methods graphically. To use the Class Designer window, you add a class diagram to your project. To add a class diagram to your Visual Studio solution, in Solution Explorer, right-click your project, and then click View Class Diagram. The class diagram automatically includes all enumerations, classes, and structures that you have defined in your project. It also provides a toolbox that you can use to add new items to the diagram and add fields and methods to them.

6-16

Programming in Visual Basic with Microsoft Visual Studio 2010

Question: What is the difference between a field and a method?

Creating New Types

6-17

Defining Constructors and Initializing an Object

When you create an object, it is important to ensure that the object is fully initialized and that all its fields are set to meaningful values. To achieve this, define one or more constructors in the class. The common language runtime (CLR) automatically invokes a constructor when an object is created. Note: If you do not initialize a field in a class, it is assigned its default value. If the field is a numeric value, it is initialized to zero. If the field is a Boolean value, it is initialized to False. If the field is a string, it is initialized to Nothing. If the property is a class, it is also initialized to Nothing.

Defining Constructors
A constructor is a special method that the CLR invokes automatically when you create an object. The following rules and guidelines apply when you define a constructor: Constructors are always named New. Constructors must not specify a return value, but they can take parameters. You can define any number of constructors in a class, provided each constructor has a unique parameter list. A constructor that takes no parameters is known as a default constructor. Constructors are typically declared with public accessibility to enable any part of the application to create and initialize objects. If you want to limit the parts of the application that can create and initialize objects, you can define a more restrictive access level for the constructors. Constructors typically initialize some or all of the fields in the object and can perform any additional initialization tasks that the class requires.

6-18

Programming in Visual Basic with Microsoft Visual Studio 2010

Important: If you do not define any constructors for a class, the Visual Basic compiler automatically generates a default constructor (a constructor that take no parameters) for you. This constructor does nothing, but it enables you to create an instance of the class. However, if you define one or more constructors yourself, the Visual Basic compiler will not generate a default constructor.

Example
The following code example shows how to define three constructors for the Residence class. The following list describes the constructors: The first constructor takes two parameters and sets the type of residence and the number of bedrooms that the residence has. The second constructor takes three parameters and sets the type of residence, the number of bedrooms that the residence has, and whether the residence has a garage. The third constructor takes four parameters and sets the type of residence, the number of bedrooms that the residence has, whether the residence has a garage, and whether the residence has a garden. Note: Notice how the code example uses the Me keyword to distinguish between fields and parameters with the same name.
Public Enum ResidenceType House Flat Bungalow Apartment End Enum Public Class Residence Public [Type] As ResidenceType Public NumberOfBedrooms As Integer Public HasGarage As Boolean Public HasGarden As Boolean Public Sub New(ByVal resType As ResidenceType, ByVal numberOfBedrooms As Integer) Me.[Type] = resType Me.NumberOfBedrooms = numberOfBedrooms End Sub Public Sub New(ByVal resType As ResidenceType, ByVal numberOfBedrooms As Integer, ByVal hasGarage As Boolean) Me.[Type] = resType Me.NumberOfBedrooms = numberOfBedrooms Me.HasGarage = hasGarage End Sub Public Sub New(ByVal resType As ResidenceType, ByVal numberOfBedrooms As Integer, ByVal hasGarage As Boolean, ByVal hasGarden As Boolean) Me.[Type] = resType Me.NumberOfBedrooms = numberOfBedrooms Me.HasGarage = hasGarage Me.HasGarden = hasGarden End Sub

Creating New Types

6-19

... End Class

It is also possible to call one constructor from another by using the Me keyword as part of the constructor declaration. The constructor with the matching signature will be run. Using this feature, you can implement a default constructor that calls a parameterized constructor with a set of default values for each parameter, as shown in the following code example.
Public Class Residence ... Public Sub New(ByVal resType As ResidenceType, ByVal numberOfBedrooms As Integer, ByVal hasGarage As Boolean, ByVal hasGarden As Boolean) Me.[Type] = resType Me.NumberOfBedrooms = numberOfBedrooms Me.HasGarage = hasGarage Me.HasGarden = hasGarden End Sub ' Default constructor creates a 3-bedroom residence ' with a garage and a garden Public Sub New() Me.New(ResidenceType.House, 3, True, True) End Sub ... End Class

Question: What happens if you do not define a default constructor for a class?

Additional Reading
For more information about constructors, see the Using Constructors and Destructors page at http://go.microsoft.com/fwlink/?LinkID=211205&clcid=0x409

6-20

Programming in Visual Basic with Microsoft Visual Studio 2010

Creating Objects

When you declare a class variable, it is initially unassigned. To use a class variable, you must create an instance of the corresponding class and assign it to the class variable. To create an instance of a class, you use the New operator. The New operator does two things: it causes the CLR to allocate memory for your object, and it then invokes a constructor to initialize the fields in that object. The version of the constructor that runs depends on the parameters that you specify for the New operator. The following code example shows how to create and use instances of the Residence class by using the constructors that were defined in the previous topic.
' Create a Flat with 2 bedrooms. Dim myFlat As New Residence(ResidenceType.Flat, 2) ' Create a House with 3 bedrooms and a garage Dim myHouse As New Residence(ResidenceType.House, 3, True) ' Create a Bungalow with 2 bedrooms, a garage, and a garden Dim myBungalow As New Residence(ResidenceType.Bungalow, 2, True, True)

If you call New and do not specify any parameters, the default constructor runs. Remember that if you define one or more constructors for a class, the Visual Basic compiler does not create a default constructor for you automatically.

Using an Object Initializer


You instantiate an object by calling a constructor. A constructor may take parameters that specify the values to initialize the fields in the object. However, an object may have any number of fields, and it may not always be possible or feasible to provide constructors that can initialize all possible combinations of these fields. For example, suppose that the Residence class currently provides the three constructors that are shown in the following code example.
Public Enum ResidenceType

Creating New Types

6-21

House Flat Bungalow Apartment End Enum Public Class Residence Public [Type] As ResidenceType Public NumberOfBedrooms As Integer Public HasGarage As Boolean Public HasGarden As Boolean Public Sub New(ByVal resType As ResidenceType, ByVal numberOfBedrooms As Integer) Me.[Type] = resType Me.NumberOfBedrooms = numberOfBedrooms End Sub Public Sub New(ByVal resType As ResidenceType, ByVal numberOfBedrooms As Integer, ByVal hasGarage As Boolean) Me.[Type] = resType Me.NumberOfBedrooms = numberOfBedrooms Me.HasGarage = hasGarage End Sub Public Sub New(ByVal resType As ResidenceType, ByVal numberOfBedrooms As Integer, ByVal hasGarage As Boolean, ByVal hasGarden As Boolean) Me.[Type] = resType Me.NumberOfBedrooms = numberOfBedrooms Me.HasGarage = hasGarage Me.HasGarden = hasGarden End Sub ... End Class

Using these constructors, you can create a Residence object and initialize various fields, but there is one combination missing. You can only specify that the residence has a garden if you explicitly state whether the residence has a garage; there is no constructor that enables you to initialize the HasGarden field without setting the HasGarage property. You may be tempted to define an additional constructor, as shown in the following code example.
Public Class Residence ... Public HasGarden As Boolean ... Public Sub New(ByVal resType As ResidenceType, ByVal numberOfBedrooms As Integer, ByVal hasGarage As Boolean, ByVal hasGarden As Boolean) Me.[Type] = resType Me.NumberOfBedrooms = numberOfBedrooms Me.HasGarage = hasGarage Me.HasGarden = hasGarden End Sub ' Constructor to initialize the HasGarden field without setting ' HasGarage. Public Sub New(ByVal resType As ResidenceType,

6-22

Programming in Visual Basic with Microsoft Visual Studio 2010

ByVal numberOfBedrooms As Integer, ByVal hasGarden As Boolean) Me.[Type] = resType Me.NumberOfBedrooms = numberOfBedrooms Me.HasGarden = hasGarden End Sub ... End Class

The problem is that constructors follow the same overloading rules as methods, and you cannot define two or more constructors that have the same signature. In this example, the Residence class will not compile because the two constructors have the same signature. You can solve this problem by using an object initializer. An object initializer creates an object by using a constructor, and it initializes any other fields that are mentioned in the same statement. You specify the fields to initialize and the values to set them to in braces after the call to the constructor, as the following code example shows.
' Create a house with three bedrooms and a garden. Dim myHouse As New Residence(ResidenceType.House, 3) With {.HasGarden = True}

When you create an object by using an object initializer, the appropriate constructor runs first, and then the property values are assigned. The property assignment may override the initialization that the constructor performs. Question: Which operator must you use when you initialize a class to create an instance of that class?

Creating New Types

6-23

Accessing Class Members

To access a member on an instance, use the name of the instance, followed by a period, followed by the name of the member. The following rules and guidelines apply when you access a member on an instance: To access a method, use parentheses after the name of the method. In the parentheses, pass the values for any parameters that the method requires. If the method does not take any parameters, the parentheses can be omitted. To access a public field, use the field name. You can then get the value of that field or set the value of that field. The following code example performs the following tasks: Creates a Residence instance by using the constructor that specifies the residence type and the number of bedrooms. Sets the HasGarden field to True to indicate that the residence has a garden. (You could also do this by using object initialize when the object was created.) Calls the CalculateSalePrice method to determine the current market value of the residence. Calls the CalculateRebuildingCost method to determine the cost of rebuilding the residence for insurance purposes.
' Create a three-bedroom house. Dim myHouse As New Residence(ResidenceType.House, 3) ' Indicate that the residence has a garden. myHouse.HasGarden = True ' Calculate the market value.

6-24

Programming in Visual Basic with Microsoft Visual Studio 2010

Dim salePrice As Integer = myHouse.CalculateSalePrice() ' Get the rebuilding costs. Dim rebuildCost As Integer = myHouse.CalculateRebuildingCost()

Question: Highlight the syntax errors in the following code example.


Dim myCar As New Car("Ford", "Black") ' Set a field to indicate the car's transmission. myCar,isManual() = True ' Call a method to calculate the car's value. Dim value As Double = myCar,CalculateValue

Creating New Types

6-25

Using Partial Classes and Partial Methods

There may be situations where you want to split a class definition across multiple source files. For example, you may have multiple developers who want to work concurrently on a class, or you may have parts of a class that should never be modified. The .NET Framework provides the concept of a partial class for this purpose. Some classes in the .NET Framework and Visual Studio projects use the partial concept. For example, Windows Presentation Foundation (WPF) applications use partial classes to separate out the code that Visual Studio generates to initialize a window from the programmatic logic that you add to process the user input and display the results.

Defining Partial Classes


Defining a class as partial enables you to split a class over multiple files. To define a class as partial, you must use the Partial keyword, as the following code example shows.
' File1.vb Namespace HouseSystem Partial Public Class Residence '... End Class End Namespace

' File2.vb Namespace HouseSystem Partial Public Class Residence '... End Class End Namespace

The following rules and guidelines apply when you define a partial class:

6-26

Programming in Visual Basic with Microsoft Visual Studio 2010

Each part of the class must be available when your application is compiled, because the compiler compiles the class into a single entity. Each part of the class should be prefixed with the Partial keyword for readability reasons, even if only one of the class definitions requires the Partial keyword. The partial type cannot be split over multiple assemblies. Each part of the partial type must exist in the same assembly. The Partial keyword must prefix the Class keyword.

Defining Partial Methods


When you define a partial class, you can define one or more methods in that class as partial methods. A partial method specifies the method signature in one file that holds the partial class, and it optionally specifies the code that implements the method in another file that holds the partial class. If the partial method is not implemented, it is effectively removed from the class, and any statements that call that method are also ignored when the class is compiled. Partial methods are typically used by frameworks; they provide a mechanism for the classes in the framework to invoke methods when developers outside the framework implement the code for these methods. The following code examples demonstrate splitting a method declaration and implementation across a partial file. In these examples, the first code file shows the partial class that is provided as part of a framework of classes. The FrameworkClass class defines a partial method called DoWork. The FrameworkMethod method in this class calls the DoWork method. The DoWork method is implemented by another developer in a separate file. Note that if this second file does not implement the DoWork method, the call to this method in the FrameworkMethod method will be ignored by the compiler.
' Code provided by the Framework. Partial Public Class FrameworkClass Partial Private Sub DoWork(Integer data) ' The definition of the ' partial method. Public Sub FrameworkMethod() ... DoWork(99) ' Call the partial method. ... End Sub End Class

' Code provided by a developer to link into the Framework. Partial Public Class FrameworkClass Partial Private Sub DoWork(ByVal data As Integer) ' Code that implements the DoWork method. End Sub End Class

The following rules and guidelines apply when you define partial methods: All partial methods must be of type Sub and cannot return a value. All partial methods must be declared Private. Only the partial method definitions can be prefixed with the Partial keyword.

Creating New Types

6-27

Question: What happens if you define a partial method, but do not provide an implementation of this method?

Additional Reading
For more information about partial classes and methods, see the Partial (Visual Basic)or Partial Methods (Visual Basic)page at http://go.microsoft.com/fwlink/?LinkID=211208&clcid=0x409 or http://go.microsoft.com/fwlink/?LinkID=211209&clcid=0x409

6-28

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 3

Creating and Using Structures

Classes are very useful when you want to model real-world entities in an application and encapsulate their associated business logic and data. However, when you create instances of objects, you will incur an overhead, and sometimes you require a more lightweight solution. Structures have many of the characteristics of classes but without some of the overhead, although they have some limitations. This lesson describes how to define structures and explains some of the differences between classes and structures.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of structures. Describe how to add members to structures. Describe how to initialize and access members in a structure.

Creating New Types

6-29

What Are Structures?

A structure is very similar to a class in many respects, except that it has a reduced overhead because of the way in which the CLR creates and manages instances of structures (you will see more about this later in this module). However, structures also have some limitations, which will be discussed later in this course. You typically use structures to model items that contain relatively small amounts of data. You have used structures throughout the course, although you may not have been aware of this. Many of the primitive types in the Visual Basic language are just aliases for some of the structures that the .NET Framework defines, and you can use these aliases or the corresponding structures interchangeably. The following table describes some of these structures. Structure type System.Byte System.Int16 System.Int32 System.Int64 System.Single System.Double System.Decimal System.Boolean System.Char Visual Basic keyword Byte Short Integer Long Single Double Decimal Boolean Char

6-30

Programming in Visual Basic with Microsoft Visual Studio 2010

Like a class, a structure can contain fields and implement methods. For example, the System.Int32 structure defines the ToString method, which returns a string representation of the integer value that is held. This means that you can perform operations on an Integer, as the following code example shows.
Dim x As Integer = 99 Dim x As String = x.ToString()

Note that by default, you cannot use many of the common operators such as = and <> on structure types unless you provide definitions of these operators. The syntax that you use for this is described in a later module. The types that are listed in the previous table provide their own implementations of these operators. Question: Is the following code legal?
Dim x As Integer = 99 Dim y As System.Int32 = x + 1

Creating New Types

6-31

Defining and Using a Structure

The syntax that you use to declare a structure is similar to the syntax that you use to declare a class, except that you use the Structure keyword rather than the Class keyword. The syntax that you use to define members in structures is also very similar to the way in which you define members in classes. The main difference is that when you define instance fields in a structure, you cannot assign a value in the declaration. The following code example shows a structure type named Currency, which can be used to represent a monetary value.
Structure Currency Public CurrencyCode As String ' The ISO 4217 currency code Public CurrencySymbol As String ' The currency symbol ($,,...) Public FractionDigits As Integer' The number of decimal places End Structure

Using a Structure
The CLR manages structures in a different way from classes. When you declare a structure variable, the memory for that variable is allocated automatically. Consequently, you do not have to use the New operator to create an instance of a structure type; you simply declare a variable of that type. You can then assign the individual values of the fields by using the same dot notation that you use to reference members of a class. You can read the values of fields in the same way. The following code example shows how to create and use an instance of the Currency type.
Dim unitedStatesCurrency As Currency unitedStatesCurrency.CurrencyCode = "USD" unitedStatesCurrency.CurrencySymbol = "$" unitedStatesCurrency.FractionDigits = 2

Question: What keyword do you use to declare a structure?

6-32

Programming in Visual Basic with Microsoft Visual Studio 2010

Initializing a Structure

When you create an object by using a class, you use the New operator to allocate memory for the corresponding object and invoke a constructor. You do not need to use the New operator to create an instance of a structure, because the memory is allocated automatically when you declare a Structure variable. However, if you want to initialize the fields in a structure when you create the instance, you can define one or more constructors. Constructors for structures are syntactically very similar to constructors for classes, but there are some semantic differences. The biggest differences are as follows: You cannot define a default (parameterless) constructor for a Structure. This is because, unlike a class, the compiler always generates its own default constructor for a Structure, regardless of whether you define any other constructors. All constructors must explicitly initialize every field in the Structure. In addition, a constructor cannot call other methods in a Structure before all of the fields have been assigned a value.

The following code example shows the Currency Structure again, but this time, it has a constructor defined that takes two parameters that specify the currency code and symbol to use. The variable unitedKingdomCurrency is initialized by using this constructor.
Structure Currency Public CurrencyCode As String ' The ISO 4217 currency code Public CurrencySymbol As String ' The currency symbol ($,,...) Public FractionDigits As Integer' The number of decimal places Public Sub New(ByVal code As String, ByVal symbol As String) Me.CurrencyCode = code Me.CurrencySymbol = symbol Me.FractionDigits = 2 End Sub End Structure ... Dim unitedKingdomCurrency As New Currency("GBP", "")

Creating New Types

6-33

Important: If you create an instance of a structure, but do not use a constructor, the structure is considered to be uninitialized. Although you can read and write individual fields in an uninitialized structure, you cannot use it as an argument to a method or copy it to another variable until you have explicitly assigned a value to every field in that structure. The simplest way to guarantee that a structure is fully initialized is to always use a constructor. Remember that the compiler generates a default constructor for you automatically, so you do not have to write your own if you simply want a structure to be populated with default values.

6-34

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 4

Comparing References to Values

Structure types, such as user-defined structures and the primitive types that Visual Basic uses, are also called value types. When you declare a variable as a structure type, the compiler generates code that allocates a block of memory big enough to hold a corresponding value. For example, declaring an Integer variable causes the compiler to allocate 4 bytes of memory (32 bits). A statement that assigns a value (such as 42) to the Integer variable causes the data for this value to be copied into this block of memory. Class types, such as the Residence class that was defined in Lesson 1, are called reference types. The CLR handles these types differently. When you declare a Residence variable, the compiler does not generate code that allocates a block of memory big enough to hold a Residence object. All the compiler does is allot a small piece of memory that can potentially hold the address of (or a reference to) another block of memory that contains a Residence object. Finally, the compiler initializes this reference to the null value to indicate that the object has not yet been initialized. The memory for the Residence object is allocated only when you use the New keyword to call a constructor and create the object. This lesson describes the differences between reference and value types, and explains how their behavior differs when you use them as parameters in methods. This lesson also describes how to convert a value into a reference and back again by using boxing and unboxing, and how to create value types that can hold null references.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the differences between reference and value types. Describe how to pass a value type by reference into a method. Describe how to perform boxing and unboxing. Describe how to create and use a null value type.

Creating New Types

6-35

Comparing Reference Types to Value Types

The CLR divides its memory into two main areas: the stack and the heap. Most of the time, value types are created on the stack, and reference types are created on the heap. These two areas use memory in different ways. The details of how memory is managed are described in a later module. The main difference between value types and reference types is what happens when you copy them. In the following code examples, the Residence type is a class (a reference type), and the Currency type is a Structure (a value type). When you assign a reference, you simply refer to an object in memory. If you assign the same reference to two different variables, both variables refer to the same object. In the following code example, the myHouse variable refers to a new House object. The variable refToMyHouse refers to the same object.
' Create a two-bedroom House object. Dim myHouse As New Residence(ResidenceType.House, 2) Dim refToMyHouse As Residence = myHouse

If you change the data that the myHouse variable refers to, you are changing the same object that the refToMyHouse variable refers to. The following code example updates the number of bedrooms in the House object to three by using the myHouse reference. The Console.WriteLine statement that prints out the number of bedrooms displays the value 3, despite the fact that this statement uses the refToMyHouse reference, because both references refer to the same object.
myHouse.NumberOfBedrooms = 3 Console.WriteLine(refToMyHouse.NumberOfBedrooms)

In the next example, myCurrency and mySecondCurrency are both Currency variables. The Currency variable is a value type. When you assign the myCurrency variable to the mySecondCurrency variable, the CLR creates a copy of the data and assigns it to the mySecondCurrency variable. The two variables do not refer to the same data in memory, so you can change the values in the myCurrency variable and the information in the mySecondCurrency variable will not change.

6-36

Programming in Visual Basic with Microsoft Visual Studio 2010

' Create a Currency object. Dim myCurrency As New Currency("USD", "$") ' Create a second Currency object that is a copy of the first. Dim mySecondCurrency As Currency = myCurrency myCurrency.CurrencyCode = "GBP" Console.WriteLine(mySecondCurrency.CurrencyCode)' Displays "USD"

Note: Enumerations are also value types and follow the same copy behavior as structures. Question: If Residence is a class (a reference type), what message does the following code example display?
Dim myHouse As New Residence(ResidenceType.House, 2) Dim anotherHouse As New Residence(ResidenceType.House, 2) If myHouse Is anotherHouse Then Console.WriteLine("They are the same house") Else Console.WriteLine("They are different houses") End If

Creating New Types

6-37

Passing a Value Type by Reference into a Method

The fundamental difference in behavior between value and reference types has a significant impact on what happens if you pass parameters of these types into a method. For example, the following code example shows a method called UpdateCurrency. This method takes a Currency parameter (a value type) and changes the currencyCode field in this parameter.
Public Sub UpdateCurrency(ByVal currencyParam As Currency) currencyParam.CurrencyCode = "EUR" End Sub ... Dim myCurrency As New Currency(...) myCurrency.CurrencyCode = "USD" UpdateCurrency(myCurrency) Console.WriteLine(myCurrency.currencyCode)

The code creates a Currency variable called myCurrency and assigns the currencyCode field of this variable to the value "USD" before calling the UpdateCurrency method. When the method is called, the expression myCurrency is evaluated, and the value of this expression is passed as the parameter to the UpdateCurrency method. Note that this value is a copy of the data in the myCurrency variable. Consequently, the UpdateCurrency method only changes the data in this copy. When the method completes, this copy is lost. The value in the myCurrency variable is unchanged, so the Console.WriteLine statement displays the string "USD". You can contrast this to what happens in the following code example when you pass a Residence parameter into a method (the Residence parameter is a reference type).
Public Sub UpdateResidence(ByVal residenceParam As Residence) residenceParam.NumberOfBedrooms = 3 End Sub ...

6-38

Programming in Visual Basic with Microsoft Visual Studio 2010

' Create a two-bedroom house. Dim myResidence As New Residence(ResidenceType.House, 2) UpdateResidence(myResidence) Console.WriteLine(myResidence.NumberOfBedrooms)

In this case, when you call the UpdateResidence method, the expression myResidence is evaluated, and this is a reference to a Residence object. This reference is passed as the parameter to the UpdateResidence method. The parameter residenceParam and the variable myResidence both refer to the same Residence object in memory. Consequently, when the code in the UpdateResidence method modifies the numberOfBedrooms field in the parameter, it updates the same object that the myResidence variable references. When the method finishes, the Console.WriteLine statement displays the value, 3.

Using the ByRef Keyword


If you want to pass a value parameter by reference into a method, you can use the ByRef keyword. The ByRef keyword causes the method to pass a reference to data into a method, rather than passing a copy. This means any changes to that parameter in the method are made to the referenced object and will remain when the method has completed. To use the ByRef keyword, you must prefix the parameter in the method signature with the ByRef keyword. The following code example shows how to use the ByRef keyword with the Currency variable value type.
Public Sub UpdateCurrency(ByRef currencyParam AS Currency) currencyParam.CurrencyCode = "EUR" End Sub ... Dim myCurrency As New Currency(...) myCurrency.CurrencyCode = "USD" UpdateCurrency(myCurrency) Console.WriteLine(myCurrency.CurrencyCode)

This time, the UpdateCurrency method takes a reference to a Currency variable. The argument that is passed in is a reference to the myCurrency variable. In the method, the currencyParam parameter refers to the myCurrency variable (it is not a copy), and any changes made through this reference modify the data in the myCurrency variable. When the method finishes, the Console.WriteLine statement displays the value, "EUR". Question: In the following code example, what is the value of the myString variable after the ChangeInput method completes?
Module Module1 Sub Main() Dim myString As String = "Original value" ChangeInput(myString) End Sub Sub ChangeInput(ByVal input As String) input = "Changed value" End Sub End Module

Creating New Types

6-39

Additional Reading
For more information about using the ByRef keyword, see the ByRef (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211211&clcid=0x409

6-40

Programming in Visual Basic with Microsoft Visual Studio 2010

Boxing and Unboxing

Reference types refer to objects and value types hold values. The Visual Basic language defines a special type called Object that you can use to refer to any type, as the following code example shows.
Dim myHouse As New Residence(...) Dim obj As Object = myHouse

The Object type is useful if you want to define methods that can take parameters of different types, and you do not know in advance what those types are. For example, the collection classes in the .NET Framework class library enable you to build collections of objects of almost any type, and the methods that these classes define use the Object type. You will see more about the collection classes in a later module. Note: The Object type is an alias for the System.Object class. This class underpins the entire type system that the .NET Framework implements; all data types are really just specialized versions of the Object type. The purpose of the System.Object class and how it relates to other types is described in more detail in a later module. In some cases, you may need to convert a value type to a reference type, such as Object. You can achieve this quite simply, as the following code example shows.
Dim myCurrency As New Currency()' Value type. Dim o As Object = myCurrency' Box the value type into a reference.

The second statement requires a little explanation. Remember that the myCurrency variable is a value type that is created on the stack. If the reference inside the o variable referred directly to the myCurrency variable, the reference would refer to the stack. However, all references must refer to objects on the heap; creating references to items on the stack can seriously compromise the robustness of the CLR and create a potential security risk, so it is not allowed. Therefore, the CLR allocates a piece of memory from the heap,

Creating New Types

6-41

copies the value of the Currency variable myCurrency to this piece of memory, and then refers the object o to this copy. This automatic copying of an item from the stack to the heap is called boxing. Because a variable of type Object can refer to a boxed copy of a value, it is only reasonable to allow you to access that boxed value through the variable. You may expect to be able to access the boxed Currency variable value that a variable o refers to by using a simple assignment statement, as the following code example shows.
Dim anotherCurrency As Residence = o

However, if you try this syntax, you will get a compile-time error. This is because the o variable could be referencing anything and not just a Currency variable value, as the following code example shows.
Dim myHouse As New Residence(...) Dim myCurrency As Currency Dim o As Object o = myHouse' o refers to a Residence myCurrency = o ' what is stored in myCurrency?

To obtain the value of the boxed copy, you must convert it. The conversion causes the compiler to generate code that checks whether it is safe to convert the object variable into the specified type. The following code example shows how to use a cast in this scenario.
Dim myCurrency As Currency Dim o As Object ' boxing ... Dim anotherCurrency As Currency = CType(o, Currency) ' compiles okay

If the compiler-generated code that checks the type of the object successfully determines that the o variable refers to a Currency variable value, this statement extracts the value from the boxed Currency object on the heap and copies it to the anotherCurrency object, which is held on the stack (it is a value type). This process is called unboxing. However, if o does not refer to a boxed Currency object, there is a type mismatch, which causes the cast to fail, and the compiler-generated code throws an InvalidCastException exception at run time. Important: Boxing and unboxing only occur when you convert from a value type to a reference type (such as an object) and back again. If you convert from one reference type to another, no copies are made, and all that happens is that a new reference is created to the existing object on the heap. Question: Is the following code an example of boxing or unboxing?
Dim amount As Object = "1234" Dim convertedAmount As Integer = CType(amount, Integer)

6-42

Programming in Visual Basic with Microsoft Visual Studio 2010

Nullable Types

When you create a reference variable, it is initially unassigned. You cannot use a reference variable until you have assigned it a value, but at the point in your code at which you declare the variable, you may not know what to initialize it to. In this case, you can set the reference variable to Nothing to indicate that it has not been initialized. The null value is useful because you can explicitly check for it later in your code, and if a reference variable is Nothing, you can initialize it by using the New operator, as the following code example shows.
Dim myHouse As Residence = Nothing ... If myHouse Is Nothing Then myHouse = New Residence(...) End If

The null value is itself a reference. There is no corresponding value for value types. This can cause a problem in your code. For example, it may not be easy to determine whether a value type has been initialized (remember that if you try to pass an uninitialized value type into a method, your code will not compile). Because the null value is a reference, the statement that is shown in the following code example is illegal in Visual Basic.
Dim myCurrency As Currency = Nothing ' Illegal

However, Visual Basic defines a modifier that you can use to declare that a variable is a nullable value type. A nullable value type behaves in a similar manner to the original value type, but you can assign the null value to it. You use a question mark (?) or the Nullable type, to indicate that a value type is nullable. Later in your application, you can ascertain whether a nullable variable contains null by testing it in the same way as a reference type, as the following code example shows.
Dim myCurrency? As Currency= Nothing ' Legal ... If myCurrency Is Nothing Then

Creating New Types

6-43

myCurrency = New myCurrency(...) End If

You can assign an expression of the appropriate value type directly to a nullable variable. The following code examples are all legal (remember that Integer is a value type in Visual Basic).
Dim Dim i = i = i? As Integer = Nothing j As Integer = 99 100 ' Copy a value-type constant to a nullable type. j ' Copy a value-type variable to a nullable type.

You should note that the converse is not true. You cannot assign a nullable value to an ordinary valuetype variable. So, given the definitions of variables i and j from the previous example, the statement that is shown in the following code example is not allowed.
j = i ' Illegal

This is because the variable i may contain null and j variable is a value type that cannot contain null. This also means that you cannot use a nullable variable as a parameter to a method that expects an ordinary value type.

Properties of Nullable Types


Nullable types expose a pair of properties that you can use to determine whether a nullable variable has a null value and what its value is: HasValue. This is a Boolean property that indicates whether a nullable type contains a value or is null. If this property is True, the nullable variable has a value; if it is False, the nullable variable is null. Value. This is the value of a variable. You should only attempt to read this value if the HasValue property is True, otherwise, your code will throw an exception.

The following code example shows how to use these properties with a nullable Currency variable.
Dim myCurrency? As Currency = Nothing ... If myCurrency.HasValue Then Console.WriteLine(myCurrency.Value) End If

Note: The Value property of a nullable type is read-only. You can use this property to read the value of a variable, but not to modify it. To update a nullable variable, use an ordinary assignment statement. Question: What is wrong with the following code?
Dim amount As Integer = Nothing If Not amount Is Nothing Then ... End If

6-44

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab: Creating New Types

Objectives
After completing this lab, you will be able to: Use enumerations to specify domains. Use a Structure to model a simple type. Use a class to model a more complex type. Use a nullable Structure.

Introduction
In this lab, you will define an enumeration and then use this type to create variables. You will also define a Structure. Finally, you will define a class and use the Structure as the type of a data member in the class.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

Creating New Types

6-45

Lab Scenario

Fabrikam, Inc. produces a range of highly sensitive measuring devices that can repeatedly measure objects and capture data. You are building an application that supports a machine that stress-tests girders for constructing high-rise buildings, bridges, and other critical structures.

6-46

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 1: Using Enumerations to Specify Domains


In this exercise, you will define enumerations that represent different materials under stress (stainless steel, aluminum, reinforced concrete, and titanium) and the cross-section of the girders (I-Beam, Box, Z-Shaped, and C-Shaped). You will also define another enumeration called TestResult that represents the results of a stress test. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. Open the Enumeration solution. Add enumerations to the StressTest namespace. Retrieve the enumeration values. Display the selection results. Test the solution.

Task 1: Open the Enumerations solution.


Open Microsoft Visual Studio 2010. Open the Enumerations solution in the D:\Labfiles\Lab06\Ex1\Starter folder.

Task 2: Add enumerations to the StressTest namespace.


Review the Task List. Locate and double-click the TODO: - Implement Material, CrossSection, and TestResult enumerations task. This task is located in the StressTestTypes.vb file. In the StressTestTypes.vb file, define a new enumeration named Material. The enumeration should have the following values. StainlessSteel Aluminum ReinforcedConcrete Composite Titanium

Below the Material enumeration, define a new enumeration named CrossSection. The enumeration should have the following values. IBeam Box ZShaped CShaped

Below the CrossSection enumeration, define a new enumeration named TestResult. The enumeration should have the following values. Pass Fail

Build the solution and correct any errors.

Task 3: Retrieve the enumeration values.


In the TestHarness project, display the MainWindow.xaml window.

Creating New Types

6-47

The purpose of the TestHarness project is to enable you to display the values from each of the enumerations. When the application runs, the three lists are populated with the values that are defined for each of the enumerations. The user can select an item from each list, and the application will construct a string from the corresponding enumerations. In the Task List, locate and double-click the TODO: - Retrieve user selections from the UI. task. This task is located in the MainWindow.xaml.vb class. Perform the following actions: Remove the comment, and add code to the ListBox_SelectionChanged method to perform the following tasks. Create a Material object called selectedMaterial and initialize it to the value of the SelectedItem property in the MaterialsListBox list box. Create a CrossSection object called selectedCrossSection and initialize it to the value of the SelectedItem property in the CrossSectionsListBox list box. Create a TestResult object called selectedTestResult and initialize it to the value of the SelectedItem property in the TestResultsListBox list box.

Hint: The SelectedItem property of a ListBox control has the object type. You must cast this property to the appropriate type when you assign it to an enumeration variable.

Task 4: Display the selection results.


In the ListBox_SelectionChanged method, after the code that you added in the previous task, add a statement to create a new StringBuilder object named selectionStringBuilder. Add a Select Case statement to evaluate the selectedMaterial variable. In the Select Case statement, add Case statements for each potential value of the Material enumeration. In each Case statement, add code to append the text "Material: <selectedMaterial>, " to the selectionStringBuilder object. Substitute the text "<selectedMaterial>" in this string with the corresponding value for the selectedMaterial variable that is shown in the following table. Material enumeration value Material.StainlessSteel Material.Aluminum Material.ReinforcedConcrete Material.Composite Material.Titanium <selectedMaterial> string Stainless Steel Aluminum Reinforced Concrete Composite Titanium

Add another Select Case statement to evaluate the selectedCrossSection variable. In this Select Case statement, add Case statements for each potential value of the CrossSection enumeration. In each Case statement, add code to append the text "Cross-section: <selectedCrossSection>," to the selectionStringBuilder object. Substitute the text "<selectedCrossSection>" in this string with the corresponding value for the selectedCrossSection variable that is shown in the following table. Material enumeration value CrossSection.IBeam CrossSection.Box <selectedCrossSection> string I-Beam Box

6-48

Programming in Visual Basic with Microsoft Visual Studio 2010

Material enumeration value CrossSection.ZShaped CrossSection.CShaped

<selectedCrossSection> string Z-Shaped C-Shaped

Add a final Select Case statement to evaluate the selectedTestResult member. In the Select Case statement, add Case statements for each potential value of the TestResult enumeration. In each Case statement, add code to append the text "Result: <selectedTestResult>." to the selectionStringBuilder object. Substitute the text "<selectedTestResult>" in this string with the corresponding value for the selectedTestResult variable that is shown in the following table. Material enumeration value TestResult.Pass TestResult.Fail <selectedTestResult> string Pass Fail

At the end of the ListBox_SelectionChanged method, add code to display the string that is constructed by using the selectionStringBuilder object in the Content property of the TestDetailsLabel control.

Task 5: Test the solution.


Build the application and correct any errors. Run the application. In the MainWindow window, in the Material list, click Titanium, in the CrossSection list, click Box, and then in the Test Result list, click Fail. In the lower part of the window, verify that the label updates with your selections. Experiment by selecting further values from all three lists, and verify that with each change, the label updates to reflect the changes. Close the application, and then return to Visual Studio.

Creating New Types

6-49

Exercise 2: Using a Structure to Model a Simple Type


In this exercise, you will define a type called TestCaseResult that holds the result of a stress test. It will have the following public fields: Result: TestResult ReasonForFailure: String

This type is small, so it is best implemented as a Structure. You will provide a constructor that initializes these fields. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. 6. Open the Structures solution. Add the TestCaseResult structure. Add an array of TestCaseResult objects to the user interface project. Fill the results array with data. Display the array contents. Test the solution.

Task 1: Open the Structures solution.


Open the Structures solution in the D:\Labfiles\Lab06\Ex2\Starter folder.

Task 2: Add the TestCaseResult structure.


Review the Task List. In the Task List, locate and double-click the TODO: - Declare a Structure task. This task is located in the StressTestTypes.vb file. Delete the comment, and then declare a new structure named TestCaseResult. In the TestCaseResult structure, add the following members. A TestResult object named Result A String object named ReasonForFailure

Task 3: Add an array of TestCaseResult objects to the user interface project.


In the TestHarness project, display the MainWindow.xaml window. This project simulates running stress tests and displays the results. It tracks the number of successful and failed tests, and for each failed test, it displays the reason for the failure. In the Task List, locate and double-click the TODO: - Declare a TestCaseResult array task. Remove the comment, and then declare a new array of TestCaseResult objects named results.

Task 4: Fill the results array with data.


In the RunTestsButton_Click method, after the statement that clears the ReasonsListBox control, add code to initialize the results array. Set the array length to 10. Below the statement that creates the array, add code that iterates through the items in the array and populates each one with the value that the shared GenerateResult method of the TestManager class returns. The GenerateResult method simulates running a stress test and returns a TestCaseResult object that contains the result of the test and the reason for any failure.

6-50

Programming in Visual Basic with Microsoft Visual Studio 2010

Task 5: Display the array contents.


Locate the comment, TODO: - Display the TestCaseResult data. Delete the comment, and then add code that iterates through the results array. For each value in the array, perform the following tasks. Evaluate the result value. If the result value is TestResult.Pass, increment the passCount value. If the result value is TestResult.Fail, increment the failCount value, and add the ReasonForFailure string to the ReasonsListBox list box that is displayed in the window.

Note: To add an item to a list box, you use the ListBox.Items.Add method and pass the item to add to the list as a parameter to the method.

Task 6: Test the solution.


Build the application and correct any errors. Run the application. In the MainWindow window, click Run Tests. Verify that the Successes and Failures messages are displayed. Also verify that a message appears in the Failures list, if failures occur. Click Run Tests again to simulate running another batch of tests and display the results of these tests. Close the application, and then return to Visual Studio.

Creating New Types

6-51

Exercise 3: Using a Class to Model a More Complex Type


In this exercise, you will define another type called StressTestCase that represents a stress test case for a girder. This type will be more complex than the TestCaseResult Structure and is best implemented as a class. The StressTestCase class will have the following public data members: GirderMaterial: Material XSection: CrossSection LengthInMm: Integer HeightInMm: Integer WidthInMm: Integer Result: TestCaseResult

You will also define two constructors: a default constructor that initializes these fields (apart from Result) to default values and an overloaded constructor that enables a programmer to specify non-default values. You will then add the following public methods to the class: PerformStressTest. This method will simulate performing a stress test and set the result to indicate whether the test passed or failed, together with a reason for the failure. GetStressTestResult. This method will return the value of the testCaseResult field. ToString. This method will return a representation of the object as a string for display purposes.

The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. 6. 7. 8. 9. Open the Classes solution. Define the StressTestCase class. Add a parameterized constructor and a default constructor to the class. Add the PerformStressTest and GetStressTestResult methods to the class. Override the ToString method to return a custom string representation. Create an array of StressTestCase objects. Display the StressTestCases collection. Test the solution. Examine and run unit tests.

Task 1: Open the Classes solution.


Open the Classes solution in the D:\Labfiles\Lab06\Ex3\Starter folder. Import the code snippets from the D:\Labfiles\Lab06\Snippets folder.

Task 2: Define the StressTestCase class.


In the TestHarness project, display the MainWindow.xaml window. This project is an extended version of the test harness from the previous two exercises. In addition to simulating stress-test results, it displays the details of the girder under test. Review the Task List. In the Task List, locate and double-click the TODO: - Add the StressTestCase class task. Remove the comment, and then add code to declare a public class named StressTestCase with the following public members. You can either type this code manually, or use the Mod06StressTestCaseClass code snippet. A Material object named GirderMaterial

6-52

Programming in Visual Basic with Microsoft Visual Studio 2010

A CrossSection object named XSection An integer named LengthInMm An integer named HeightInMm An integer named WidthInMm A TestCaseResult object named Result

Task 3: Add a parameterized constructor and a default constructor to the class.


Below the member declarations, add a constructor for the StressTestCase class that accepts the following parameters. A Material object named girderMaterial An XSection object named xSection An integer named lengthInMm An integer named heightInMm An integer named widthInMm

In the constructor, add code to store the value for each parameter in the corresponding member. Hint: In the constructor, to make it clear which items are member variables and which items are parameters, use the Me keyword (which represents the current object) with all member variables. Above the constructor, add a default constructor.

Hint: A default constructor is a constructor that accepts no parameters and implements functionality to create a default instance of a class. In the default constructor, initialize the members of the StressTestCase object with default values by using the parameterized constructor and the data that are shown in the following table. Parameter name girderMaterial xSection lengthInMm heightInMm widthInMm Parameter value Material.StainlessSteel CrossSection.IBeam 4000 20 15

Task 4: Add the PerformStressTest and GetStressTestResult methods to the class.


Below the class constructors, add code to declare a new method named PerformStressTest. The PerformStressTest method should take no parameters and should not return a value. This method will simulate performing a stress test and then populate a StressTestCase object with the details of the test.

Creating New Types

6-53

In the PerformStressTest method, create an array of strings called failureReasons that contains the following values. "Fracture detected" "Beam snapped" "Beam dimensions wrong" "Beam warped" "Other"

Add a statement that invokes the Next method of the static Rand method of the Utility class. Pass the value 10 as a parameter.

Note: The Utility.Rand.Next method accepts an integer parameter and then returns a random integer value between zero and the value of the integer parameter. In this case, the method will return an integer between 0 and 9. If the value that the Rand method returns is 9, add code to perform the following tasks. Set the Result.Result member value to TestResult.Fail. Invoke the Utility.Rand.Next method with a parameter value of 5. Store the result in a new integer member named failureCode. Set the Result.ReasonForFailure value to the value in the failureReasons array that the failureCode value indicates.

Note: This code simulates a 10 percent chance of a test case failing. The failureReasons array contains five possible causes of failure, and this code selects one of these causes at random. If the Rand method returns a value other than 9, add code to set the Result.Result member value to TestResult.Pass. Below the PerformStressTest method, add a public method named GetStressTestResult, which accepts no parameters and returns a TestCaseResult object. In the GetStressTestResult method, add code to return a reference to the TestCaseResult member.

Task 5: Override the ToString method to return a custom string representation.


Below the GetStressTestResult method, add a public method named ToString.

Note: This overrides the ToString method that is inherited from the Object type. You will see more about inheritance in a later module. In the ToString method, add code to return a string with the format shown in the following code example, where each value in angle brackets is replaced with the corresponding member in the class.
Material: <girderMaterial>, CrossSection: <crossSection>, Length: <lengthInMm>mm, Height: <heightInMm>mm, Width:<widthInMm>mm.

Hint: Use the String.Format method to build the string.

6-54

Programming in Visual Basic with Microsoft Visual Studio 2010

Task 6: Create an array of StressTestCase objects.


In the Task List, locate and double-click the TODO: - Create an array of sample StressTestCase objects. task. This task is located in the MainWindow.xaml.vb class. Remove the comment, and add a private method named CreateTestCases. The CreateTestCases method should accept no parameters and return an array of StressTestCase objects. In the CreateTestCases method, add code to create an array of StressTestCase objects named stressTestCases. The array should be able to hold 10 objects. Add code to generate 10 StressTestCase objects, and store each of them in the stressTestCases array. Use the following table to determine the parameters to pass to the constructor for each instance. You can either type this code manually, or use the Mod06StressTestCaseClass code snippet. Array position Material 0 1 2 3 4 5 6 7 8 9 Use default constructor Material.Composite Use default constructor Material.Aluminum Use default constructor Material.Titanium Material.Titanium Material.Titanium Use default constructor Material.StainlessSteel CrossSection.Box 3500 100 20 CrossSection.CShaped 3600 CrossSection.ZShaped 4000 CrossSection.Box 5000 150 80 90 20 20 20 CrossSection.Box 3500 100 20 CrossSection.CShaped 3500 100 20 CrossSection Length Height Width

At the end of the method, return the stressTestCases array.

Task 7: Display the StressTestCases collection.


In the Task List, locate the TODO: - Iterate through the StressTestCase samples displaying the results. task, and then double-click this task. This task is located in the RunTestsButton_Click method that runs when the user clicks Run Stress Tests. Remove the comment, and then add code to invoke the CreateTestCases method. Store the result of the method call in a new array of StressTestCase objects named stressTestCases. Add code to create a StressTestCase object named currentTestCase and a TestCaseResult object named currentTestResult. You will add code to instantiate these objects shortly. Add code that iterates through the StressTestCase objects in the stressTestCases array. For each StressTestCase object, add code to perform the following tasks. You can either type this code manually, or use the Mod06IterateTestCases code snippet. Set the currentTestCase object to refer to the StressTestCase object. Invoke the currentTestCase.PerformStressTest method on the currentTestCase object. Add the currentTestCase object to the TestListBox list that is displayed in the window.

Creating New Types

6-55

Invoke the currentTestCase.GetStressTestResult method, and store the result in the currentTestResult object. Add a string to the ResultListBox list box that is displayed in the window. This string should consist of the currentTestResult.Result value and the currentTestResult.ReasonForFailure message.

Task 8: Test the solution.


Build the solution and correct any errors. Run the application. In the MainWindow window, click Run Stress Tests. Verify that the Girder Tested list contains a list of different girder compositions and the Results list contains a series of test results. Click Run Stress Tests again. You should see a different set of results. Close the application, and then return to Visual Studio

Task 9: Examine and run unit tests.


In the Task List, locate and double-click the TODO: - Examine and Run Unit Tests. task. This task is located in the StressTestCaseTest class. Examine the StressTestCaseConstructorTest method. This method uses the parameterized constructor to create a new StressTestCase object that uses defined values. The method then uses a series of Assert statements to ensure that the properties of the created object match the values that are passed to the constructor. Examine the StressTestCaseConstructorTest1 method. This method uses the default constructor to create a new StressTestCase object, passing no parameters. The method then uses a series of Assert statements to ensure that the properties of the created object match the intended default values. Examine the GetStressTestResultTest method. This method creates a new StressTestCase object and then retrieves a TestCaseResult object by calling the StressTestCase.GetStressTestResult method. The test method then uses Assert statements to ensure that the TestCaseResult.Result and TestCaseResult.ReasonForFailure properties contain the expected values. Examine the PerformStressTestTest method. This method creates a StressTestCase object, calls the PerformStressTest method, and then retrieves the TestCaseResult object. The method then checks that, if the test failed, the TestCaseResult.ReasonForFailure member contains some text. If the test passed, the method uses Assert statements to verify that the ReasonForFailure member contains no data. The method iterates 30 times. Examine the ToStringTest method. This method creates a default StressTestCase object, and then verifies that the ToString method of the object returns a string that contains the correct details. Run all the tests in the solution, and verify that all the tests run successfully.

6-56

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 4: Using a Nullable Structure


In this exercise, you will modify the constructor for the StressTestCase class to initialize the Result field to null (to indicate no result yet). However, you cannot set a value-type field or variable to a reference value such as null. Therefore, you will convert the Result field to a nullable field to support null values. You will then modify the methods in the StressTestCase class, and the test harness that invokes the GetStressTestResult method that displays the result of a test case, to dereference the value of this Structure through the Value property. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. 6. 7. 8. Open the NullableStructs solution. Modify the Result field to make it nullable. Modify the parameterized constructor to initialize the TestCaseResult member. Modify the PerformStressTest method. Modify the GetStressTestResult method. Modify the GetStressTestResult method call. Test the solution. Update the unit tests.

Task 1: Open the NullableStructs solution.


Open the NullableStructs solution in the D:\Labfiles\Lab06\Ex4\Starter folder.

Task 2: Modify the Result field to make it nullable.


Review the Task List. In the Task List, locate and double-click the TODO: - Make TestCaseResult nullable task. This task is located in the StressTestTypes class. Remove the comment, and then modify the Result member definition to allow it to store a null value.

Task 3: Modify the parameterized constructor to initialize the TestCaseResult member.


In the StressTestCase parameterized constructor, remove the comment, TODO: Initialize TestCaseResult to null, and then add code to initialize the Result member to Nothing.

Task 4: Modify the PerformStressTest method.


In the PerformStressTest method, remove the comment, TODO: Update the PerformStressTest method and work with the nullable type, and then add code to declare a new TestCaseResult variable named currentTestCase. Modify the If statement to perform the following tasks: In all instances, modify the currentTestCase object rather than the Result member. At the end of the If block, assign the currentTestCase object to the Result member.

Modify the Else block to perform the following tasks: Modify the currentTestCase object rather than the Result member. At the end of the If block, store the currentTestCase object in the Result member.

Task 5: Modify the GetStressTestResult method.


In the GetStressTestResult method, modify the method definition to return a nullable TestCaseResult value.

Creating New Types

6-57

Task 6: Modify the GetStressTestResult method call.


In the Task List, locate and double-click the TODO: - Modify call to GetStressTestResult method to handle nulls. task. Remove the comment, and then modify the code to create a nullable TestCaseResult object named currentTestResult. In the For block, after retrieving the value of the currentTestResult object from the currentStressTest.GetStressTestResult method, add code to check whether the currentTestResult object contains a value. If a value exists, add a string that contains the StressTestResultResult and ReasonForFailure properties to the ResultListBox control.

Task 7: Test the solution.


Build the solution and correct any errors. Run the application. In the MainWindow window, click Run Stress Tests. Close the application, and then return to Visual Studio.

Task 8: Update the unit tests.


In the Task List, locate and double-click the TODO: - Examine and run unit tests updated to deal with nullable type task. This task is located in the StressTestCaseTest class.

Note: Most of the test cases are identical to those in Exercise 3. The only changes are in the GetStressTestResultTest and PerformStressTestTest methods. Examine the GetStressTestResultTest method. This method creates a new StressTestCase object. It then evaluates the HasValue property on the result of the GetStressTestResult method call to verify that the property contains no value. The test then calls the PerformStressTest method, which generates a TestCaseResult value in the StressTestCase object. The test method again evaluates the HasValue property to verify that a value now exists. Examine the changes to the PerformStressTestTest method. This method creates a StressTestCase object and then calls the PerformStressTest method on that object. The method calls the GetStressTestResult method on the StressTestCase object and stores the result in a local nullable TestCaseResult object. The method then uses an Assert statement to evaluate the HasValue property of the TestCaseResult object to verify that the result is not null. The method then evaluates the Value property of the TestCaseResult object to determine whether the result indicates that the stress test failed or passed. If the stress test failed, an Assert statement is used to verify that the ReasonForFailure string contains a value. If the stress test passed, an Assert statement is used to verify that the ReasonForFailure string is null. The method iterates 30 times. Run all the tests in the solution, and verify that all of the tests run successfully. Close Visual Studio.

6-58

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Review

Review Questions
1. 2. 3. What type would you use to model a collection of constant values? At what scope level would you define an enumeration type, if you wanted that type to be accessible to multiple classes? What construct would you use to model a simple custom numeric type?

Creating New Types

6-59

Module Review and Takeaways

Review Questions
1. 2. 3. 4. 5. 6. When you define the first value in an enumeration, the value defaults to the index of zero. How can you change the default index? What is a class? What keyword can you use to split a class definition over multiple files? Is a Boolean variable a value type or a reference type? How can you pass a value type by reference into a method? What is the process called when you explicitly convert a value type to a reference type?

Best Practices Related to Creating and Using Types


Supplement or modify the following best practices for your own work situations: When you use a series of related constants, create an enumeration to encapsulate those constants into an object. Use structures to implement simple concepts whose main feature is their value. Also use structures for small data items where it is just asor nearly asefficient to copy the value as it would be to copy an address. Use classes for more complex data that is too big to copy efficiently.

6-60

Programming in Visual Basic with Microsoft Visual Studio 2010

Encapsulating Data and Methods

7-1

Module 7
Encapsulating Data and Methods
Contents:
Lesson 1: Controlling Visibility of Type Members Lesson 2: Sharing Methods and Data Lab: Encapsulating Data and Methods 7-3 7-12 7-23

7-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

Previous modules have shown you how to add functionality to Microsoft.NET Framework applications by using the existing types and creating your own types. When you create your own types, you rarely want to expose all of the members in that type, because some of these members may represent helper functions and internal state data that are not relevant to other classes. Typically, your types should expose only the fields and methods that other types can use. In addition, all types to this point have been instance typesthey model data and behavior for a specific instance of a type. Sometimes, it is useful to define shared data and behavior that spans all of the instances of a type. This module describes how to use some of the access modifiers that Visual Basic provides to enable you to implement encapsulation. This module also introduces the Shared modifier, which enables you to define members that can be shared over multiple instances of the same type.

Objectives:
After completing this module, you will be able to: Describe how to control the visibility of type members. Describe how to share methods and data.

Encapsulating Data and Methods

7-3

Lesson 1

Controlling Visibility of Type Members

Encapsulation is a fundamental object-oriented principle. It is the ability to hide the private data and inner workings of a type so that it cannot be accessed by code that is defined in other types. A type can expose public members who define its behavior to the outside world, but keep the implementation of this behavior private. This feature isolates the way in which a type operates from the applications that use it and can reduce the scope for any inadvertent dependencies that may otherwise occur. This lesson explains how to use access modifiers to control the visibility of types and members in types.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of encapsulation. Describe the difference between creating public and private members. Describe the difference between creating friend and public types.

7-4

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is Encapsulation?

All applications manipulate data. Many older legacy applications built by using programming languages that are not object-oriented, such as C and COBOL, typically separate the code that processes data from the code that manages and stores the data. Data management code was frequently provided in the form of code libraries, and the data-processing elements of an application were frequently built into the business logic of the application. Several applications that implement different parts of a business system might all use the same data that they access through a code library, but perform their own operations on that data. However, in this scenario, if the format of the data changes, the code library that manages the data may also need to change. All of the applications that use the original code library may need to be refreshed to use the new version, and the logic in these applications may also need to change to handle the new structure of the data. Such changes can be difficult to perform (or even locate), and any applications that are not updated correctly may exhibit bugs and generate errors.

Encapsulation
Encapsulation is the ability of a type to hide its internal data and implementation details, making only specific parts of the type accessible to applications. Encapsulation is an essential object-oriented principle. For example, when you define a class, you should always define the fields as private members so that external code cannot access the fields directly. The only way for external code to interact with an object or class is through a well-defined set of public methods and properties.

Note: Properties provide a structured way to expose the ability to retrieve and set the values of private fields. Properties are covered in a later module in this course.

Encapsulating Data and Methods

7-5

Benefits of Encapsulation
Encapsulation enables you to hide information. When you hide information, such as the internal state and implementation details of a type, the external code focuses only on the useful characteristics of the object. For example, the internal mechanism of a telephone is hidden from users; the wires, switches, and other internal parts of a telephone are encapsulated by its cover and are inaccessible to users. You may not have any idea about how a telephone works internally, but you can still use it. In addition, when you hide the internal state of a type, the client applications cannot modify or corrupt this state. Therefore, it prevents changes that can cause the type to malfunction and produce unexpected results. Using encapsulation, you can also easily change the implementation details of your type; applications that use your type do not need to be rewritten. As long as the public methods and properties that a class exposes do not change, any existing client applications should still work correctly. Question: Discuss your experiences of encapsulation with other students.

7-6

Programming in Visual Basic with Microsoft Visual Studio 2010

Comparing Private and Public Members

Visual Basic provides keywords known as access modifiers, which enable you to specify the access level for types and their members. You use these access modifiers to hide data and methods from applications that consume a type to implement encapsulation. Visual Basic provides several different access modifiers that provide varying degrees of protection. Some of these modifiers apply to types, and others apply to members of a type. This topic focuses on how to use the Private and Public access modifiers to hide and expose members.

Using the Private and Public Modifiers with Type Members


If you do not specify an access modifier for an element declared within an interface, module, class, or structure, the default access level for the member is Public. The member can be accessed by any code outside the type. The Sales class definition in the following code example contains a private field called monthlyProfit, a private method called SetMonthlyProfit, and a private method called GetAnnualProfitForecast.

Note: To use an access modifier, prefix the member declaration with the access modifier that you want to use.
Class Sales Private monthlyProfit As Double Private Sub SetMonthlyProfit(ByVal monthlyProfit As Double) Me.monthlyProfit = monthlyProfit End Sub Private GetAnnualProfitForecast() As Double Return Me.monthlyProfit * 12 End Function

Encapsulating Data and Methods

7-7

End Class

All members are declared as private. Therefore, they are only accessible to other members in the Sales class. The SetMonthlyProfit and GetAnnualProfitForecast methods have access to the monthlyProfit field because the field is declared at class level and therefore is in scope.If you try to use a member that you do not have access to, you will get a compile error. Using the Private access modifier, you can protect the implementation and state of a type from consuming types and thus encapsulate data. However, types are not very useful if they do not expose any members for other types to consume. For example, the Sales class definition in the preceding code example is unusable because there is no entry point into the class; no class can access its data or invoke any of its functionality. To expose members to other types, you can use the Public access modifier. In contrast to the Private access modifier, the Public access modifier is the most permissive access level and does not impose any restrictions. If you declare a member as Public, any other type can access that member. Using the example of the Sales class, if you declare the SetMonthlyProfit and GetAnnualProfitForecast methods as Public, other types can then invoke these methods. The following code example shows that the Program class can now invoke the SetMonthlyProfit and GetAnnualProfitForecast methods.
Class Sales Private monthlyProfit As Double Public Sub SetMonthlyProfit(ByVal monthlyProfit As Double) Me.monthlyProfit = monthlyProfit End Sub Public Function GetAnnualProfitForecast() As Double Return (Me.monthlyProfit * 12) End Function End Class Module Module1 Sub Main() Dim companySales As New Sales() companySales.SetMonthlyProfit(3400) Console.WriteLine(companySales.GetAnnualProfitForecast()) End Sub End Module

When you name private and public members, adopt a consistent naming convention. This course uses Camel case for private fields and Pascal case for public methods. All methods use Pascal case, regardless of whether they are public or private. Your organization may have its own naming conventions that differ from this. Question: You have created a class called Product to encapsulate information about the products that your organization sells. The following code example shows the definition of this class. You want to use this class in an application that creates Product objects and displays their details. What is the main problem with the Product class that may mean that a client application cannot use the Product type in this way?
Public Class Product ' Make these fields private so that an application cannot change 'them after the Product object has been created. Private productID As Integer

7-8

Programming in Visual Basic with Microsoft Visual Studio 2010

Private productName As String ' Public methods that a client application can use to ' get the product ID and name. Public Function GetProductID() As Integer Return Me.productID End Function Public Function GetProductName() As Integer Return Me.productName End Function ' Provide a constructor to enable a client application ' to create a Product object. Sub New(ByVal ID As Integer, ByVal name As String) Me.productID = ID Me.productName = name End Sub End Class

Additional Reading
For more information about the access modifiers, see the Private (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211215&clcid=0x409

For more information about the Public access modifier, see the Public (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211217&clcid=0x409

Encapsulating Data and Methods

7-9

Comparing Friend and Public Types

You have already seen how to use access modifiers to show and hide members in types; however, you can also use access modifiers to show and hide types.

Note: This topic focuses primarily on access modifiers for class definitions, although the same principles can be applied to any type, whether it is a class, a structure, or an enumeration.

Friend Types
The Friend access modifier restricts visibility to only code in types that are defined in the same assembly. The Public access modifier makes the type available to code in all types. The Sales class definition in the following code example is declared by using the Friend access modifier.
Friend Class Sales Private monthlyProfit As Double Public Sub SetMonthlyProfit(ByVal monthlyProfit As Double) Me.monthlyProfit = monthlyProfit End Sub Public Function GetAnnualProfitForecast() As Double Return (Me.monthlyProfit * 12) End Function End Class

Other types in the same assembly can now access the Sales class and use any exposed members. You can also use friend members in a type. Friend methods are available to other types that are part of the same assembly, but are inaccessible to types that are defined in other assemblies. The same rules apply to friend fields and properties.

7-10

Programming in Visual Basic with Microsoft Visual Studio 2010

Public Types
The .NET Framework organizes types into assemblies. An assembly may represent the code for an application, or it may contain a library of types and data that applications can use. The .NET Framework class library contains several assemblies with a large number of reusable types. For example, the System.IO assembly in the .NET Framework class library provides the necessary functionality to interact with the file system. These assemblies would not be much use if they only exposed Friend types because no other assembly would be able to access their functionality. Therefore, when developers build a reusable application programming interface (API), it is common to define Public types. The following code example shows how the Sales class definition has been explicitly assigned the Public access modifier. Other types in other assemblies can now access the Sales class.
Public Class Sales Private monthlyProfit As Double Public Sub SetMonthlyProfit(ByVal monthlyProfit As Double) Me.monthlyProfit = monthlyProfit End Sub Public Function GetAnnualProfitForecast() As Double Return (Me.monthlyProfit * 12) End Function End Class

Private Types
You can also declare types as Private. You can only define a type as Private if it is nested within another type. It is most frequently used to define private enumerations, although you can define classes and structures that you want to use in your own code, but do not want to expose to other types. The following code example shows the public Sales class, which contains a private Revenue structure. The Revenue structure is encapsulated by the Sales class and is not accessible directly.
Public Class Sales Private salesRevenue As Revenue Public Sub SetRevenue(ByVal currency As String, ByVal amount As Double) Me.salesRevenue = New Revenue(currency, amount) End Sub Private Structure Revenue Private currency As String Private amount As Double Public Sub New(ByVal currency As String, ByVal amount As String) Me.currency = currency Me.amount = amount End Sub End Structure End Class

Question: In the Revenue structure that is shown in the preceding code example, the constructor is defined as Public although the type is defined as Private. Does this mean that a type other than the Sales type can invoke this constructor?

Encapsulating Data and Methods

7-11

Additional Reading
For more informationn about the Friend access modifier, see the Friends (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211218&clcid=0x409

7-12

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 2

Sharing Methods and Data

This lesson introduces the concept of shared types and members, which you can use to implement singleton types. This lesson also explains how to extend existing types by using extension methods.

Lesson Objectives:
After completing this lesson, you will be able to: Describe how to create and use shared fields and static local variables. Describe how to create and use shared methods. Describe how to create and use shared constructors. Describe how to create and use extension methods.

Encapsulating Data and Methods

7-13

Creating and Using Shared Fields and Static Local Variables

Until this point, the primary focus has been on how to create and use instance members in types. Instance members typically contain data and implement functionality that is pertinent to a specific instance of a type. When you create an instance of a type, memory is allocated for each field, enabling the instance to hold unique data. For example, in the following code example, the sales2010 object is an instance of the Sales class, and any data that is stored in the sales2010 object is accessible only through the sales2010 instance. If you create a second instance of the Sales class called sales2011, it cannot access any of the instance data in the sales2010 object, and the sales2010 object cannot access its instance data. The following code example shows how data is not shared across instances of the Sales class.
Dim sales2010 As New Sales() sales2010.SetMonthlyProfit(34672) Console.WriteLine(sales2010.GetAnnualProfitForecast()) Dim sales2011 As New Sales() sales2011.SetMonthlyProfit(98675) Console.WriteLine(sales2011.GetAnnualProfitForecast()) ... Class Sales Private monthlyProfit As Double Public Sub SetMonthlyProfit(ByVal monthlyProfit As Double) Me.monthlyProfit = monthlyProfit End Sub Public Function GetAnnualProfitForecast() As Double Return Me.monthlyProfit * 12 End Function End Class

Alternatively, shared fields do not belong to an instance of a type; they belong to the type itself. Memory is allocated independently of any instance, and all references to a shared field refer to the same piece of

7-14

Programming in Visual Basic with Microsoft Visual Studio 2010

memory. You do not need to create an instance of a type to access a shared member of that type. The member is created, and the memory is allocated the first time that you reference it.

Using Shared Fields


To create a shared field, you must use the Shared modifier in the declaration for that field. The following code example shows how to declare a shared field called SalesTaxPercentage and assign the field the value 20.
Class Sales Shared Public SalesTaxPercentage As Double = 20 End Class

You can initialize a shared field in the declaration, as the preceding code example shows, or if the field is public, you can access the field from another type. You do not need to create an instance of the Sales type to access the SalesTaxPercentage field because the field is shared. Instead, you access the field directly on the Sales type by specifying the type name, followed by a period, followed by the field name, as the following code example shows.
Sales.SalesTaxPercentage = 32

Note: You can also initialize shared fields from a constructor, which is covered in a later topic in this module. You cannot access shared fields from an instance of the type, because shared fields belong to the type itself. If you try, you will by default get a compile warning that states that the member cannot be accessed by using an instance reference. For example, the following code example will generate a compile warning.
Dim salesObj As New Sales() salesObj.SalesTaxPercentage = 23 ' Compiler warning. ... Class Sales Shared Public SalesTaxPercentage As Double End Class

However, you can access shared fields from instance methods and constructors. This enables you to share data with multiple instances of the same type. For example, the User class in the following code example represents a user who accesses the Fabrikam, Inc. website. Every time a user accesses the site, a new User object is created, and the shared UsersOnline field is incremented. Other instances of the class can access the data and increment it because the field is shared. Therefore, the UsersOnline field will represent the total number of users online at that point in time.
Class User Shared Friend UsersOnline As Integer Friend Sub New() UsersOnline += 1 End Sub End Class Dim a As New User() Dim b As New User() Dim c As New User()

Encapsulating Data and Methods

7-15

Dim d As New User() Dim totalUsersOnline As Integer = User.UsersOnline ' Returns the value 4

If the UsersOnline field was not shared, data would not be shared, and the UsersOnline field would only ever reach the value of 1, as the following code example shows.
Class User Friend UsersOnline As Integer Friend Sub New() UsersOnline += 1 End Sub End Class Dim Dim Dim Dim a b c d As As As As New New New New User() User() User() User()

Dim totalUsersOnline As Integer = d.UsersOnline ' Returns the value 1

Using Static Local Variables


A static local variable is a variable you use in a procedure, such as a Function, Sub, or Property, but it is static, or shared, which means that it retains its value in between calls to the procedure. However, unlike a shared field, the value is only for a specific instance on the class; it is not shared between instances of the same class. To create a static local variable, you must use the Static modifier in the declaration for that variable. The following code example shows how to declare a static local variable called visits and assign the value 0.
Class Building Sub DoWork() ' Declare and initialize static local variable Static visits As Integer = 0 ' Increment value of static variable visits += 1 ' Display value of static variable Console.WriteLine(visits.ToString) End Sub End Class

You can initialize a static local variable in the declaration, as the preceding code example shows, where it has been declared an initialized in the DoWork method of the Building class. If you create an instance of the Building class and call the DoWork method, as shown in the following example, what will be the output to the console?
Sub Main() Dim apartment As New Building apartment.DoWork() apartment.DoWork() apartment.DoWork() apartment.DoWork() apartment.DoWork() End Sub

7-16

Programming in Visual Basic with Microsoft Visual Studio 2010

The output to the console would be like this:


1 2 3 4 5

So, the value is clearly being kept between calls. But, how does that affect the lifetime of the static local variable? Well, the static variable continues to exist and retains its most recent value, which means that for the next call to the method, the variable is not reinitialized. A static variable continues to exist for the lifetime of the class or module in which is defined. One use of a static local variable is to prevent reentrance into a method until its task is finished. You can achieve this by declaring a local static variable of type Boolean, which is then set to True upon the first call to the method. After the work has been completed, you set the value to False. For each call to the method, you check the value of the Boolean variable, and continue or return based on the current value. Static variables can only be used in procedures in classes and modules, not structures. Question: What happens if you try to access a public shared field through an instance of the type?

Encapsulating Data and Methods

7-17

Creating and Using Shared Methods

You can also use the Shared modifier to create shared methods. Shared methods are very useful and are typically used in utility classes to perform atomic operations that do not rely on instance data. For example, the File class in the System.IO namespace contains several shared methods, such as Exists, to perform atomic file operations.

Using Shared Methods


To define a shared method, you must prefix the method declaration with the Shared modifier, as the following code example shows.
Class Sales Shared Public Function GetMonthlySalesTax( _ ByVal monthlyProfit As Double) As Double ... End Function End Class

If you define a shared method, that method cannot reference any instance members that are declared in that class. Shared methods can only reference other shared members, but instance methods can access both instance and shared members. Typically, instance methods use data that is stored in instance fields, which has been collected during the object's initialization and other method calls. Shared methods can only use data that is stored in shared fields and the data that is passed as parameters in the method's signature. For example, the Sales class in the following code example exposes a GetMonthlySalesTax method that uses data that is passed by the monthlyProfit parameter and the private shared salesTaxPercentage field.
Class Sales Shared Private salesTaxPercentage As Double = 20 Shared Public Function GetMonthlySalesTax( _ ByVal monthlyProfit As Double) As Double

7-18

Programming in Visual Basic with Microsoft Visual Studio 2010

Return (salesTaxPercentage * monthlyProfit) / 100 End Function End Class

The syntax that you use to access a Shared method is different from the syntax that you use to access an instance methodyou do not need to create an instance of the type. To access a Shared method on a class, you use the name of the class, followed by a period, followed by the name of the method. For example, the following code example shows how to access the GetMonthlySalesTax method in the Sales class.
Dim monthlySalesTax As Double = Sales.GetMonthlySalesTax(34267) ... Class Sales Shared Public salesTaxPercentage As Double = 20 Shared Public Function GetMonthlySalesTax( _ ByVal monthlyProfit As Double) As Double Return (salesTaxPercentage * monthlyProfit) / 100 End Function End Class

Question: The Person class definition in the following code example contains functionality to calculate the number of years that a person must work before he or she reaches a set retirement age. The class does not compile; can you identify the problem?
Class Person Private ageLimit As Integer Shared Private Sub New(ByVal ageLimit As Integer) Me.ageLimit = ageLimit End Sub Shared Public Function GetAllNames() As String() Throw New NotImplementedException() End Function End Class

Additional Reading
For more information about the static modifier, see the Shared (Visual basic) page at http://go.microsoft.com/fwlink/?LinkID=211219&clcid=0x409

Encapsulating Data and Methods

7-19

Creating and Using Shared Constructors

Typically, the common language runtime (CLR) implicitly invokes a shared constructor before any code tries to access or invoke a shared member in that type. You define a shared constructor in a type, as the following code example shows.
Class Sales Shared Sub New() ... End Sub End Class

When you use shared constructors in types, you must follow some rules to avoid compilation errors. You must define only a single constructor that is prefixed with the Shared modifier. You cannot explicitly invoke a shared constructor; therefore, the constructor does not need to be accessible outside the type, so it always uses the implicit Public access modifier. When defining the signature for a shared constructor, you cannot specify parameters. In addition, a shared constructor can only reference other shared members.

Implementing the Singleton Design Pattern


Shared constructors have many uses. For example, you can define objects that adhere to the singleton software engineering pattern. The singleton pattern prescribes that your code should be restricted to creating one instance of a type. You then use the instance whenever you require an instance of that type in your application. In the following code example, the Sales type implements the singleton pattern.
Class Sales Shared data As SaleData = Nothing Shared Sub New() If SaleData.WebServerConnectionExists() Then data = SaleData.GetWebServerData()

7-20

Programming in Visual Basic with Microsoft Visual Studio 2010

ElseIf SaleData.LocalDatabaseConnectionExists() data = SaleData.GetDatabaseData() Else Throw New NotSupportedException("No data source could be found.") End If End Sub Shared Public GetAllSalesRegions() As String() Throw New NotImplementedException() End Function End Class

The Sales class contains a constructor that ensures that the data object is initialized before the GetAllSalesRegions method is invoked. The constructor achieves this with some conditional logic, which either initializes the data object or throws an exception. A client application that consumes the Sales class can be certain that the data object has been initialized before it calls the GetAllSalesRegions method. Question: Identify the errors in the class definition in the following code example.
Class Person Private ageLimit As Integer Shared Private Sub New(ByVal ageLimit As Integer) Me.ageLimit = ageLimit End Sub Shared Public Function GetAllNames() As String() Throw New NotImplementedException() End Function End Class

Encapsulating Data and Methods

7-21

Creating and Using Extension Methods

The purpose of extension methods is to provide a way to extend existing types with your own custom functionality that does not affect or break other applications that already use these types. Before the introduction of extension methods, it was necessary to define a new class to wrap the existing functionality and provide the new required method. However, this can affect existing code and introduce breaking changes.

Defining Extension Methods


When you define an extension method, you must do so in a module, and the first parameter in the method's signature determines the type that is being extended. You also need to decorate the method with the ExtensionAttribute attribute to indicate that it is an extension method. At runtime, the parameter is replaced with an instance of the type, and you can manipulate this instance in whatever way you require to implement the logic of the extension method. The following code example shows how to define an extension method called NextRand for the Integer type. The method returns a random number that uses the existing Integer object as a seed. The method also accepts a parameter that represents a maximum value for the new random number.
Imports System.Runtime.CompilerServices Imports System.Text ModuleExtensions <Extension()> Public Function NextRand(ByVal seed As Integer, _ ByVal maxValue As Integer) As Integer Dim randomNumberGenerator As New Random(seed) Return randomNumberGenerator.Next(maxValue) End Function End Module

7-22

Programming in Visual Basic with Microsoft Visual Studio 2010

Using Extension Methods


A client application uses an extension method in the same way that it uses a standard method; there is no visible difference. You can still invoke the method on an instance of an object. The following code example shows how to invoke the global NextRand method.
ModuleModule1 Sub Main() Dim i As Integer = 8 Dim j As Integer = i.NextRand(20) End Sub End Module

Question: Briefly explain the difference between a standard method's signature and a signature for an extension method.

Additional Reading
For more information about extension methods, see the Extension Methods (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211220&clcid=0x409

Encapsulating Data and Methods

7-23

Lab: Encapsulating Data and Methods

Objectives:
After completing this lab, you will be able to: Hide data members in a type by using access modifiers. Use shared members to share data in types. Use extension methods to add functionality to the System.Int64 structure.

Introduction
In this lab, you will use encapsulation to hide information in a class. You will add shared members and methods to a type to share data between instances of the type. Finally, you will add an extension method to a built-in type in the .NET Framework.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

7-24

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Scenario

Fabrikam, Inc. produces a range of highly sensitive measuring devices that can repeatedly measure objects and capture data. You are building an application that drives a machine that stress tests girders for the construction of highrise buildings, bridges, and other critical structures. You have defined types to support this application, but they currently expose all members publicly, which can cause problems. After they are created, the GirderMaterial, XSection, LengthInMm, HeightInMm, and WidthInMm members of a StressTestCase object should be immutable; this guarantees that the test case results that are reported in a test case object match the data for the test.

Encapsulating Data and Methods

7-25

Exercise 1: Hiding Data Members


In this exercise, you will make the fields in the StressTestCase class private and verify that these fields are now inaccessible outside code in the class. The main tasks for this exercise are as follows:

1.
2. 3. 4.

Open the StressTesting solution. Declare fields in the StressTestCase class as private. Build the project and correct errors. Update unit tests to resolve errors.

Task 1: Open the StressTesting solution.


Open Microsoft Visual Studio 2010. Open the StressTesting solution in the D:\Labfiles\Lab07\Ex1\Starter folder.

Task 2: Declare fields in the StressTestCase class as private.


Review the Task List. In the Task List, locate and double-click the TODO: - Modify the StressTestCase class to make members private task. This task is located in the StressTestCase class. In the StressTestCase class, remove the TODO: - Modify the StressTestCase class to make members private comment, and then modify each field definition to make all of the fields private.

Task 3: Build the project and correct errors.


Build the project, and then review the Error List. The project should fail to build because the code in the RunStressTestsButton_Click method in the test harness project attempts to access the fields in the StressTestCase class that are now private. Comment out the code that caused the errors that are shown in the Error List. These errors are caused by six statements in the RunStressTestsButton_Click method.

Task 4: Update unit tests to resolve errors.


On the Build menu, click Build Solution. There should still be some errors. The remaining errors are located in the unit test project. In the Task List, locate and double-click the TODO: - Update unit tests to resolve errors. task. This task is located in the StressTestCaseTest unit test class. In the StressTestCaseConstructorTest method, comment out the five Assert statements that cause errors. Update the method to verify that the constructed object contains the correct member values by performing the following tasks.

Hint: You cannot access the member data directly because you have just declared private members. The ToString method returns a string representation of the object, including the member data. Before you instantiate the target object, declare a new string named expected and populate the string with the following data that represents the expected results of the test.

7-26

Programming in Visual Basic with Microsoft Visual Studio 2010

Material: Composite, CrossSection: CShaped, Length: 5000mm, Height: 32mm, Width: 18mm, No Stress Test Performed

At the end of the method, add an Assert statement that checks whether the expected string matches the output of the target.ToString method. Update the StressTestCaseConstructorTest1 method and resolve the errors by performing the following tasks. Comment out the five existing Assert statements. Before the method creates the target object, create a new string that contains the expected result from a default StressTestCase class.

Material: StainlessSteel, CrossSection: CShaped, Length: 5000mm, Height: 32mm, Width: 18mm, No Stress Test Performed

At the end of the method, add an Assert statement that checks whether the expected string matches the output of the target.ToString method. Rebuild the solution and correct any errors. Run all of the tests in the solution, and then verify that all of the tests execute successfully.

Encapsulating Data and Methods

7-27

Exercise 2: Using Shared Members to Share Data


In this exercise, you will define a structure that holds a pair of private fields to record the total number of tests that are performed and the total number of failures. You will add a private shared member to the StressTestCase class that is based on this structure. You will then modify the PerformStressTest method to increment the fields in this structure as appropriate. Finally, you will add a shared method to the class that returns the value of this structure. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. 6. Open the StressTesting solution. Create a structure to hold the number of successes and failures. Modify the StressTestCase class to contain a TestStatistics object. Display the statistics in the user interface. Test the solution. Examine and run unit tests for the TestStatistics class.

Task 1: Open the StressTesting solution.


Open the StressTesting solution in the D:\Labfiles\Lab07\Ex2\Starter folder. Import the code snippets from the D:\Labfiles\Lab07\Snippets folder.

Task 2: Create a structure to hold the number of successes and failures.


Review the Task List. In the Task List, locate and double-click the TODO: - Create the TestStatistics structure task. This task is located in the StressTestCase class. Delete the TODO: - Create the TestStatistics structure comment, and then define a new public structure named TestStatistics, which has the following private members. An integer named numberOfTestsPerformed. An integer named numberOfFailures.

Add a method to the TestStatistics structure named IncrementTests. The method should accept a Boolean parameter named success, but not return a value. Add code to the method to perform the following tasks. Increment the numberOfTestsPerformed member. If the success parameter is False, increment the numberOfFailures member.

Below the IncrementTests method, add a method named GetNumberOfTestsPerformed. This method should take no parameters and return an integer value. Add code to the method to return the value of the numberOfTestsPerformed member. Below the GetNumberOfTestsPerformed method, add a method named GetNumberOfFailures. The method should take no parameters and return an integer value. Add code to the method to return the value of the numberOfFailures member. Below the GetNumberOfFailures method, add a Friend method named ResetCounters. The method should take no parameters and not return a value. Add code to the method to set both numberOfFailures and numberOfTestsPerformed members to zero. Build the project and correct any errors.

7-28

Programming in Visual Basic with Microsoft Visual Studio 2010

Task 3: Modify the StressTestCase class to contain a TestStatistics object.


In the Task List, locate and double-click the TODO: - Add a TestStatistics field and method to the StressTestCase class task. This task is located in the StressTestCase class. Delete the TODO: - Add a TestStatistics field and method to the StressTestCase class comment, and then declare a new shared private member of type TestStatistics named statistics. Below the statistics member declaration, add a shared public method named GetStatistics. The method should take no parameters, but should return a TestStatistics object. Add code to the method to return the value of the statistics member. Below the GetStatistics method, add a shared public method named ResetStatistics. The method should take no parameters and should not return a value. Add code to the method to invoke the ResetCounters method on the statistics member. In the Task List, locate and double-click the TODO: - Update the PerformStressTest method to handle statistics task. This method is located in the StressTestCase class. Delete the TODO: - Update the PerformStressTest method to handle statistics comment, and in the PerformStressTest method, add code to invoke the IncrementTests method on the statistics member when a test either passes or fails. If the test passes, specify the value True as the argument to the IncrementTests method. If the test fails, specify the value False as the argument to the IncrementTests method.

Task 4: Display the statistics in the user interface.


In the Task List, locate and double-click the TODO: - Update the UI to display statistics. task. This task is located in the MainWindow class, at the end of the RunStressTestsButton_Click method. At the end of the RunStressTestsButton_Click method, delete the comments and add code to perform the following tasks. You can either type this code manually, or use the Mod07TestStatistics code snippet. Create a new TestStatistics object named statistics. Initialize the object with the value that is returned by calling the StressTestCase.GetStatistics method. In the StatisticsLabel1 label, display the message "Number of tests: <tests>, Failures: <failures>", where tests is the number of tests that were executed and failures is the number of tests that failed.

Hint: Set the Content property of a Label control to display a message in that control. Invoke the IncrementTests method on the statistics object and pass True as a parameter. Invoke the static GetStatistics method on the StressTestCase object and store the result in the statistics variable. In the StatisticsLabel2 label, display the message "Number of tests: <tests>, Failures: <failures>", where tests is the number of tests that were executed, and failures is the number of tests that failed.

Note: This demonstrates the principle of passing or returning by value. When the code first calls the GetStatistics method, a copy of the value is returned from the StressTestCase object. Therefore, when the code calls the IncrementTests method, the update is performed on the copied value and not the original value. When the GetStatistics method is called for the second time, another copy of the original value is retrieved; therefore, both labels will display the same value.

Encapsulating Data and Methods

7-29

Task 5: Test the solution.


Build the solution and correct any errors. Run the application. In the MainWindow window, click Run Stress Tests, and then examine the statistics labels, which should both display the same values. Close the MainWindow window, and then return to Visual Studio.

Task 6: Examine and run unit tests for the TestStatistics class.
In the Task List, locate and double-click the TODO: - Examine and run unit tests. task. This task is located in the StressTestClass_TestStatisticsTest file. Examine the GetNumberOfFailuresTest method. This method creates a new TestStatistics object named target and then invokes the IncrementTests method twice, passing false as the parameter. The method then retrieves the number of failures from the TestStatistics object and uses an Assert statement to verify that the value is correct. Examine the GetNumberOfTestsPerformedTest method. This method creates a new TestStatistics object named target and then invokes the IncrementTests method three times. The method then retrieves the number of tests that was performed from the TestStatistics object and uses an Assert statement to verify that the value is correct. Examine the IncrementTestsTest method. This method creates a TestStatistics object named target and then invokes the IncrementTests method on this object four times. The method then retrieves the number of tests that were performed from the target object and uses an Assert statement to verify that the value is correct. Run all of the tests in the solution, and then verify that all of the tests execute successfully.

7-30

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 3: Implementing an Extension Method


In this exercise, you will add a long integer field to the TestCaseResult structure to hold information. You will update the PerformStressTest method in the StressTestCase class to populate this field with simulated results. To display the data in this field as a binary string, you will add an extension method called ToBinaryString to the System.Int64 structure. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. 6. 7. Open the StressTesting solution. Define a new extension method. Modify the TestCaseResult structure to include a long field. Modify the PerformStressTest method. Display the failure data. Test the solution. Examine and run unit tests.

Task 1: Open the StressTesting solution.


Open the StressTesting solution in the D:\Labfiles\Lab07\Ex3\Starter folder. This solution contains a revised copy of the solution from the previous exercise:

Task 2: Define a new extension method.


In the StressTest project, add a new public module named Extensions, in a file named Extensions.vb. Import the System.Runtime.CompilerServices namespace. In the Extensions module, add a new public extension method named ToBinaryString. The method should take a 64-bit integer parameter named i and return a string value. Import the System.Text namespace. In the ToBinaryString method, add code to create a string that holds the binary representation of the 64-bit integer value that is passed in the i integer, and return this string.

Task 3: Modify the TestCaseResult structure to include a long field.


Review the Task List. In the Task List, locate and double-click the TODO: - Modify the TestCaseResult structure task. This task is located in the TestCaseResult structure. In the TestCaseResult structure, delete the comment and add a public field of type Long named FailureData.

Task 4: Modify the PerformStressTest method.


In the Task List, locate and double-click the TODO: - Update the PerformStressTest method task. This task is located in the StressTestCase class, in the PerformStressTest method. In the PerformStressTest method, delete the TODO: - Update the PerformStressTest method comment, and then add code to update the FailureData member of the TestCaseResult object with a random number to simulate the data that is retrieved from the stress-testing equipment.

Encapsulating Data and Methods

7-31

Hint: Use the Rand member of the Utility static class to generate a random number. This method contains a method called Next that returns a random number in a specified range. Pass the value Integer.MaxValue as the parameter to the Next method to generate a random number between 0 and this value. The value Integer.MaxValue field specifies the maximum value that the integer type supports.

Task 5: Display the failure data.


In the Task List, locate and double-click the TODO: - Update the UI to display the binary string task. This task is located in the MainWindow class, in the RunStressTestsButton_Click method. Modify the RunStressTestsButton_Click method to append the binary data that is contained in the FailureData member to the failure information that is displayed in the user interface; append a space character followed by the result of the ToBinaryString method call to the end of the string that is added to the ResultListBox.Items collection.

Task 6: Test the solution.


Build the solution and correct any errors. Run the application. In the MainWindow window, click Run Stress Tests, and then verify that when an error occurs, binary data is displayed after the reason for the failure. Close the MainWindow window, and then return to Visual Studio.

Task 7: Examine and run unit tests.


In the Task List, locate and double-click the TODO: - Review and run unit tests task. This task is located in the ExtensionsTest class. Examine the ToBinaryStringTest method. This method creates a Long variable, i, with the value 8 and then creates a string variable, expected, with the value 1000. The method then invokes the ToBinaryString extension method on the Long variable i and stores the result in a string named actual. The method then uses an Assert statement to verify that the expected and actual values are the same. The method then updates the Long variable i with the value 10266 and the expected variable with the binary representation 10100000011010. Next, it directly calls the ToBinaryString method, passes the Long variable i as a parameter, and stores the result of the method call in the actual variable. The method uses a second Assert statement to verify that the expected and actual values are the same. Run all of the tests in the solution, and then verify that all of the tests execute successfully. Close Visual Studio.

7-32

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Review

Review Questions
1. 2. 3. Which access modifier would you use to stop access of fields from outside the parent type? When declaring a constructor in a shared type, how many parameters can the constructor take? When declaring an extension method, which attribute must you use to decorate the first method?

Encapsulating Data and Methods

7-33

Module Review and Takeaways

Review Questions
1. 2. 3. Briefly explain the purpose of encapsulation. Which access modifier do you use to expose a method to a type in a different assembly? How do you invoke a shared constructor?

Best Practices Related to Encapsulating Data and Methods


Supplement or modify the following best practices for your own work situations: Do not expose the inner workings of your types with the Public and Friend access modifiers. If in doubt, use the Private access modifier. If a type does not need to store instance data, declare the type as static. If you must add functionality to an existing type and do not want to derive a new type, use extension methods.

7-34

Programming in Visual Basic with Microsoft Visual Studio 2010

Inheriting from Classes and Implementing Interfaces

8-1

Module 8
Inheriting from Classes and Implementing Interfaces
Contents:
Lesson 1: Using Inheritance to Define New Reference Types Lesson 2: Defining and Implementing Interfaces Lesson 3: Defining Abstract Classes Lab: Inheriting from Classes and Implementing Interfaces 8-3 8-19 8-29 8-37

8-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

This module introduces inheritance and interfaces in the Microsoft .NET Framework, and how you can use them to simplify complex problems, reduce code duplication, and speed up development. Inheritance is a key concept in an object-oriented language. You can use inheritance, interfaces, and abstract classes to develop object hierarchies in your code. These object hierarchies can help reduce bugs by defining clear contracts for what a class will expose and by providing default implementations where you can sensibly abstract code into a base type.

Objectives
After completing this module, you will be able to: Use inheritance to define new reference types. Define and implement interfaces. Define abstract classes.

Inheriting from Classes and Implementing Interfaces

8-3

Lesson 1

Using Inheritance to Define New Reference Types

This lesson describes inheritance in the .NET Framework and helps you understand how you can use inheritance to develop better code faster, and with fewer bugs. Developing an object hierarchy is an important process. The object hierarchy should be well designed, and you should avoid code duplication. Understanding inheritance in the .NET Framework is fundamental to this process.

Lesson Objectives:
After this lesson, you will be able to: Explain the purpose of inheritance and how it works. Describe the inheritance hierarchy of the .NET Framework. Override and hide methods. Call methods and constructors in a base class. Assign references in an inheritance hierarchy. Explain how polymorphism works. Define sealed classes and methods.

8-4

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is Inheritance?

Inheritance is a key concept in the world of object orientation. You can use inheritance as a tool to avoid repetition when you are defining different classes that have several features in common and are related to each other. Perhaps, they are different subclasses of the same type, each with its own distinguishing featurefor example, managers and manual workers are all employees of a factory. If you were writing an application to simulate the factory, how would you specify that managers and manual workers have several features that are the same, but also other features that are different? For example, they all have an employee reference number and a name, but managers have different responsibilities and perform different tasks from manual workers. As a solution, you might develop a class to represent an employee. This class can include fields to hold information that is common to all employees, such as the employee reference number and name. You can then develop a class to represent a manager and another class to represent a manual worker. Both the manager class and the manual worker class would also need to store the employee reference number and name. Rather than adding duplicate fields to each of these classes, you can simply specify that both of these classes inherit from the employee class, which already has fields to store these values. In addition, there might be some behavior that is common to managers and manual workers. You can implement this behavior as a method in the employee class. The manager and manual worker classes can then inherit this behavior. Using inheritance in this way reduces the need for code duplication, which reduces both development time and the risk of bugs being introduced. You can add additional, specific fields and methods to the manager and manual worker classes to model the different data and behaviors of these types.

Defining a Derived Class


You specify that a class inherits from another class (called a base class) by using the Inherits keyword and providing the name of the base class. Remember that all of the members in the base class are private by default. This means that they cannot be accessed by code in other classes, including classes that inherit from the base class. You can make the members of a base class visible to inheriting classes, but keep them

Inheriting from Classes and Implementing Interfaces

8-5

hidden from other classes that are not part of the inheritance hierarchy by using the Protected keyword, as the following code example shows.
' Base class Class Employee Protected empNum As String Protected empName As String Protected Sub DoWork() ... End Sub End Class ' Inheriting or derived classes Class Manager Inherits Employee Public Sub DoManagementWork() ... End Sub End Class Class ManualWorker Inherits Employee Public Sub DoManualWork() ... End Sub End Class

Finally, Visual Basic supports single inheritance only. You cannot define a class that directly inherits from more than one base class. Question: Which accessor should you use to make class members accessible to child classes?

8-6

Programming in Visual Basic with Microsoft Visual Studio 2010

.NET Framework Inheritance Hierarchy

In the .NET Framework, all types inherit either directly or indirectly from the Object class in the System namespace. The Object class provides functionality that is useful to all types, such as the ToString and Equals methods. When you create a new reference type such as a class, it inherits directly from the Object class, and you do not need to specify this relationship as part of the class definition. When you inherit from another class, such as Employee, you automatically inherit all of the functionality of the Object class too. Value types such as structures inherit from the System.ValueType class, which in turn inherits from the Object class. Enum types inherit from the System.Enum class, which inherits from ValueType. However, unlike classes, this hierarchy is fixed, and you cannot define your own custom inheritance hierarchy with value types; for example, you cannot explicitly specify that a structure inherits from another structure. Question: What types inherit from the Object class?

Additional Reading
For more information about the Object class, see the Object Class page at http://go.microsoft.com/fwlink/?LinkId=192936

Inheriting from Classes and Implementing Interfaces

8-7

Overriding and Hiding Methods

When you use inheritance to define a class, an inheriting class can provide its own methods. It is possible that some of these methods have the same names as those that are inherited from the base class. In these situations, you have to decide whether you want to override the inherited methods or hide them.

Overriding Methods
When you override a method, you provide an implementation that has the same meaning as the original method, but has an implementation that is specific to the class. For example, the Object class provides the ToString method, which returns a representation of an object as a string. However, the default implementation of the ToString method in the Object class simply returns the name of the type as a string. You might override this behavior in the Employee class to return a string that contains the name of the employee. To override a method in a subclass, you use the Overrides keyword, as the following code example shows.
Class Object Public Overridable Function ToString() As String ' Return the type of the object as a string ' (code not shown) ... End Function End Class Class Employee Protected empName As String ... Public Overrides Function ToString() As String Return String.Format("Employee: {0}", empName) End Function End Class

8-8

Programming in Visual Basic with Microsoft Visual Studio 2010

You can only override methods that are marked as Overridable, Overrides, or MustOverride in the base class. Overridable or virtual methods typically provide a default implementation that inheriting classes are expected to replace with their own code. When you define a class that other classes might inherit from, you should decide which methods you will allow to be overridden in this way and declare them as Overridable. Note that when you override a virtual method, you cannot change the protection level of the method; if the method in the base class is protected, the override method must also be protected.

Hiding Methods
You can define methods in an inherited class that have the same name as methods in a base class, even if they are not marked as Overridable, MustOverride, or Overrides. However, this means that there is no relationship between your method and the original method, and your new method hides the original method. In this case, the Visual Basic compiler displays a warning (you might not be aware that a class that you are inheriting from has such a method, so you might want to change the name of your method to avoid this conflict), although your code still compiles. If you are aware that you are hiding a method in a base class, you can turn the compiler warning off by marking the method with the Shadows keyword, as the following code example shows.
Class Employee Protected Sub DoWork() ... End Sub End Class Class Manager Inherits Employee Public Shadows Sub DoWork() ' Hide the DoWork method in the base class ... End Sub ... End Class

When you hide a method, you can change the protection level. For example, you can hide a protected or private method in a base class with a public method that has the same name in an inheriting class. There are rare cases where this practice is required, but it is not recommended as a general practice. Generally, it is better to override a method than to hide ithiding is usually an indication of poor design. Question: What happens if you attempt to hide a method without using the Shadows keyword?

Inheriting from Classes and Implementing Interfaces

8-9

Calling Methods and Constructors in a Base Class

An inheriting class can call methods in a base class by using the MyBase keyword as the method prefix. This feature is useful when you are overriding methods because it enables you to provide your own functionality in addition to invoking the existing functionality that the base class defines. In effect, this extends methods. The following code example shows the DoWork method in the Manager class overriding the DoWork method in the Employee class from which it inherits, but calling the DoWork method in the Employee class at an appropriate point.
Class Employee Protected Overridable Sub DoWork() ... End Sub End Class Class Manager Inherits Employee Protected Overrides Sub DoWork() ' Do processing specific to Managers ... ' Call the DoWork method in the base class MyBase.DoWork() End Sub ... End Class

Without the MyBase keyword, the call to DoWork in the Manager class would simply call the DoWork method in the Manager class recursively.

Calling Base Class Constructors


In addition to the methods that it inherits, a derived class automatically contains all fields from the base class. These fields usually require initialization when an object is created. You typically perform this kind of initialization in a constructor. Remember that all classes have at least one constructor. (If you do not

8-10

Programming in Visual Basic with Microsoft Visual Studio 2010

provide one, the compiler generates a default constructor for you.) It is good practice for a constructor in a derived class to call the constructor for its base class as part of the initialization. You can specify the MyBase keyword to call a base class constructor when you define a constructor for an inheriting class, as the following code example shows.
Class Employee Protected empName As String Public Sub New(ByVal name As String) ' constructor for base class Me.empName = name End Sub ... End Class Class Manager Inherits Employee Protected empGrade As String Public Sub New(ByVal name As String, ByVal grade As String) MyBase.New(name) ' calls Sub New(name) Me.empGrade = grade End Sub

... End Class

If you dont explicitly call a base class constructor in a derived class constructor, the compiler attempts to silently insert a call to the default constructor of the base class before executing the code in the derived class constructor. The following code example is an extract from the previous code example.
Class Manager Inherits Employee Public Sub New(ByVal name As String, ByVal grade As String) ... End Sub ... End Class

The compiler will rewrite this code as the code in the following code example.
Class Manager Inherits Employee Public Sub New(ByVal name As String, ByVal grade As String) MyBase.New() ... End Sub ... End Class

This works if the Employee class has a public default constructor. However, not all classes have a public default constructor, in which case forgetting to call the correct base class constructor results in a compiletime error. The compiler only generates a default constructor if you dont write any nondefault constructors. Question: What happens if you do not call the constructor of a base class in the constructor for your class?

Inheriting from Classes and Implementing Interfaces

8-11

Assigning and Referencing Classes in an Inheritance Hierarchy

The type-checking rules of Visual Basic prevent you from assigning an object of one type to a variable that is declared as a different type. For example, given the definitions of the Employee, Manager, and ManualWorker classes that are shown in the following code example, the code that follows these definitions is illegal.
Class Employee ... End Class Class Manager Inherits Employee ... End Class Class ManualWorker Inherits Employee ... End Class ... ' Manager constructor expects a name and a grade Dim myManager As new Manager("Fred", "VP") Dim myWorker As ManualWorker= myManager' error different types

However, it is possible to refer to an object from a variable of a different type as long as the type that you use is a class that is higher up the inheritance hierarchy. Therefore, the statements in the following code example are legal.
Dim myManager As New Manager("Fred", "VP") Dim myEmployee As Employee = myManager ' legal, Employee is the base class of Manager

8-12

Programming in Visual Basic with Microsoft Visual Studio 2010

This works because the inheritance hierarchy means that you can think of a Manager simply as a special type of Employee; it has everything that an Employee has with a few extra bits that you can define by any methods and fields that you add to the Manager class. You can also make an Employee variable refer to a ManualWorker object. There is one significant limitation, howeverwhen you refer to a Manager or ManualWorker object by using an Employee variable, you can access only methods and fields that are defined by the Employee class. Any additional methods that the Manager or ManualWorker classes define are not visible through the Employee class. This explains why you can assign almost anything to an Object variable. Remember that all classes inherit from System.Object directly or indirectly.

Converting Object References


Although you can assign a Manager object to an Employee variable, the converse is not true; you cannot unreservedly assign an Employee object to a Manager variable. This is because not all Employee objects are Manager objects; some might be ManualWorker objects. However, you can assign an Employee object to a Manager variable as long as you check that the Employee is really a Manager first by using the TryCast keyword or the TypeOf...Is operator, or by using the CType function. The TryCast keyword checks that an object is a reference to a specified type. If it is, TryCast returns a new reference by using this type, otherwise it returns Nothing. You can also convert to assign a reference of one type to a variable of a different type as long as the conversion is valid. However, you should use the TryCast keyword first to verify that the conversion will succeed. Like the TypeOf...Is operator, the TryCast keyword checks that an object is a reference to a specified type and returns True if it is, and False if it is not. If you attempt to perform an invalid cast that the compiler does not detect at compile time, your code will throw an InvalidCastException at runtime. The following code example uses the TypeOf...Is operator to check that myEmployee refers to a Manager object. If it does, the assignment results in myManagerAgain referring to the same Manager object as myManager. If myEmployee refers to some other type of Employee, such as a ManualWorker, the TypeOf...Is operator returns Nothing instead.
Dim myManager As New Manager("Fred", "VP") Dim myEmployee As Employee = myManager ' myEmployee refers to a Manager ... Dim myManagerAgain As Manager = TryCast(myEmployee, Manager) ' OK - myEmployee is a Manager ... Dim myWorker As New ManualWorker("Bert") myEmployee = myWorker ' myEmployee now refers to a ManualWorker ... myManagerAgain = TryCast(myEmployee, Manager) ' returns Nothing - myEmployee is a ManualWorker

Question: What exception is thrown if you attempt to perform an invalid conversion or cast?

Additional Reading
For more information about the TryCast keyword, see the TryCast Operator (Visual Basic) page at http://msdn.microsoft.com/en-us/library/zyy863x8.aspx
For more information about the TypeOf...Is operator, see the TypeOf Operator (Visual Basic) page at http://msdn.microsoft.com/en-us/library/0ec5kw18.aspx

Inheriting from Classes and Implementing Interfaces

8-13

Understanding Polymorphism

Virtual or overridable methods that are defined in classes that share an inheritance hierarchy enable you to call different versions of the same method, based on the type of the object, which is determined dynamically at run time. This is called polymorphism, and is a very powerful feature of object-oriented systems. Consider the example classes in the following code example, which define a variation on the Employee hierarchy.
Class Employee ... Public Overridable Function GetTypeName() As String Return "This is an Employee" End Function End Class Class Manager Inherits Employee ... Public Overrides Function GetTypeName() As String Return "This is a Manager" End Function End Class Class ManualWorker Inherits Employee ... ' Does not override GetTypeName End Class

In this hierarchy, notice that the Overrides keyword is used by the GetTypeName method in the Manager class, and the ManualWorker class does not have a GetTypeName method. In the following code example, what will be displayed by the two Console.WriteLine statements?

8-14

Programming in Visual Basic with Microsoft Visual Studio 2010

Dim myEmployee As Employee Dim myManager As New Manager(...) Dim myWorker As New ManualWorker(...) myEmployee = myManager Console.WriteLine(myEmployee.GetTypeName()) ' Manager myEmployee = myWorker Console.WriteLine(myEmployee.GetTypeName()) ' ManualWorker

You might expect them both to print This is an Employee because each statement calls the GetTypeName method on the myEmployee variable, which is an Employee reference. However, in the first case, myEmployee is actually a reference to a Manager object. The GetTypeName method is defined as virtual or overridable, so the runtime works out that it should call the Manager.GetTypeName method. Therefore, the statement actually prints the message This is a Manager. The second Console.WriteLine statement calls GetTypeName on a ManualWorker object. However, the ManualWorker class does not have a GetTypeName method, so the default method in the Employee class is called, returning the string This is an Employee. Question: When you reference an object by its parent class, which version of a method is called: the version from the base class or the overridden version in the child class?

Additional Reading
For more information about polymorphism, see the Polymorphism page at http://msdn.microsoft.com/en-us/library/z165t2xk(VS.90).aspx

Inheriting from Classes and Implementing Interfaces

8-15

Defining Sealed Classes and Methods

By default, when you define a class, other people who have access to the assembly that contains your class can inherit from it and add their own functionality. However, unless you consciously design a class with the intention of using it as a base class, it is extremely unlikely to function well as a base class. Visual Basic enables you to use the NotInheritable keyword to prevent a class from being used as a base class if you decide that it should not be. The following code example declares the Manager class as NotInheritable.
NotInheritable Class Manager Inherits Employee ... End Class

If any class attempts to use Manager as a base class, a compile-time error will be generated. Note that a sealed (NotInheritable) class cannot declare any virtual or overridable methods. In the .NET Framework, all value types (structures and enumerations) are implicitly sealed.

Sealing Methods
You can also use the NotOverridable keyword to declare that an individual method in an unsealed class is sealed. This means that a derived class cannot then override the sealed method. You can seal only Overrides methods, and you declare them as NotOverridable Overrides, as the following code example shows.
Class Manager Inherits Employee ... Protected NotOverridable Overrides Sub DoWork() ... End Sub End Class

8-16

Programming in Visual Basic with Microsoft Visual Studio 2010

Question: Can you define a class that inherits from the Visual Basic Integer type?

Inheriting from Classes and Implementing Interfaces

8-17

Demonstration: Using Inheritance to Construct New Reference Types

Demonstration Steps
1. 2. 3. Open Microsoft Visual Studio 2010. In Visual Studio 2010, open the UsingInheritanceDemo solution in the D:\Demofiles\Mod8\Demo1\Starter folder. In the Module1.vb file, add a new class called Television. Your code should resemble the following code example.
Class Television End Class

4.

In the Television class, add a Protected Overridable method called SetCurrentChannel. The method should write a message to the console indicating that the television channel has been set. Your code should resemble the following code example.

Class Television Protected Overridable Sub SetCurrentChannel() Console.WriteLine("Channel set.") End Sub End Class

5.

In the Television class, add a Protected method called TurnOn. The method should write a message to the console indicating that the television is on. Your code should resemble the following code example.

Class Television Protected Overridable Sub SetCurrentChannel() Console.WriteLine("Channel set.") End Sub

8-18

Programming in Visual Basic with Microsoft Visual Studio 2010

Protected Sub TurnOn() Console.WriteLine("Television on.") End Sub End Class

6.

In the Module1.vb file, add a new class called WidescreenTV that inherits from the Television class: Your code should resemble the following code example.

Class WidescreenTV Inherits Television End Class

7.

Override the SetCurrentChannel method. The method should write a message to the screen indicating that the channel has been set on the widescreen television. Your code should resemble the following code example.

Class WidescreenTV Inherits Television Protected Overrides Sub SetCurrentChannel() Console.WriteLine("Widescreen channel set.") End Sub End Class

8.

Add a constructor to the WidescreenTV class. The constructor should call the constructor of the base class, call the TurnOn method, call the SetCurrentChannel method, and then call the SetCurrentChannel method of the base class. Your code should resemble the following code example.

Class WidescreenTV Inherits Television Protected Overrides Sub SetCurrentChannel() Console.WriteLine("Widescreen channel set.") End Sub Public Sub New() MyBase.New() TurnOn() SetCurrentChannel() MyBase.SetCurrentChannel() End Sub End Class

9.

Uncomment the code in the Program class that creates an instance of the WidescreenTV class.

10. Run the application with debugging and verify that the following messages appear.
Television on. Widescreen channel set. Channel set.

Question: Which functionality does Visual Studio provide when you implement an interface?

Inheriting from Classes and Implementing Interfaces

8-19

Lesson 2

Defining and Implementing Interfaces

This lesson introduces interfaces and describes how you can use them to standardize your code. Interfaces enable you to define contracts explicitly, defining the methods that your objects expose. You can implement multiple interfaces to indicate that your code can perform several functions. For example, you can implement the IComparable interface to indicate that two objects can be compared for equality, and the IDisposable interface to indicate that an object can be cleaned up.

Lesson Objectives:
After this lesson, you will be able to: Describe the purpose of interfaces. Create and implement an interface. Reference an object by using an interface. Explain explicit and implicit interface implementation.

8-20

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is an Interface?

Inheriting from a class is a powerful mechanism, but the real power of inheritance comes from implementing an interface. An interface does not contain any code or data; it just specifies the methods and properties that a class that inherits from the interface must provide. Using an interface enables you to separate the names and signatures of the methods of a class from the methods implementation. Interfaces act as a contract; they guarantee that any class that implements the interface will expose the members that the interface specifies. This enables you to simplify development and standardize your code. Interfaces enable you to specify functionality that a class should implement. How a class chooses to implement this functionality is the concern of the class and not a property of the interface. Different classes can implement the same interface in different ways, as long as they expose the set of methods that the interface defines. For example, a frequent requirement in the .NET Framework is for classes to define a method that enables them to be compared to determine their relative ordering. What it actually means to compare the value of two objects of a given class depends on the class itself; the Employee class might determine that employee objects should be ranked by grade (two employees with the same grade are equal, but an employee with a grade of "VP" might be considered superior to an employee with a grade of "Worker"), whereas the String class uses an alphanumeric comparison to determine the relative order of string values. To standardize the way in which objects of any given type can be compared, the System namespace in the .NET Framework class library defines the IComparable interface. This interface contains a single method called CompareTo that has the signature in the following code example.
Function CompareTo(ByVal obj As Object) As Integer

A class that implements the IComparable interface must provide the CompareTo method. This method returns zero if the object that is specified as the parameter is considered equal to the object on which the CompareTo method was invoked; a value less than zero if the object is considered to be less than the

Inheriting from Classes and Implementing Interfaces

8-21

object that was specified as the parameter; and a value greater than zero if the object is considered to be greater than the object that is specified as the parameter. It is up to the class that is implementing the interface to provide the actual logic for the CompareTo method. Question: Can you add a default implementation of a method to an interface?

8-22

Programming in Visual Basic with Microsoft Visual Studio 2010

Creating and Implementing an Interface

Syntactically, an interface is similar to a class except that you only declare method signatures and do not provide the code that implements them.

Defining an Interface
To define an interface, you use the Interface keyword. Inside the interface, you declare method signatures exactly as in a class or a structure except that you never specify an access modifier (Public, Private, or Protected), as the following code example shows.
Interface ICalculator Function Add() As Double Function Subtract() As Double Function Multiply() As Double Function Divide() As Double End Interface

Note that, in the example above, the name of the interface begins with an uppercase I. This is a common naming convention rather than an explicit requirement, but the.NET Framework documentation recommends that you adhere to this standard; all interfaces in the System namespace are prefixed in this way.

Implementing an Interface
To implement an interface, you declare a class or structure that inherits from the interface and provides code for every method that the interface defines. Note: Although you cannot create structure types that inherit from other structure types or classes, a structure type can implement an interface.

Inheriting from Classes and Implementing Interfaces

8-23

When you implement an interface, you must ensure that each method matches its corresponding interface method exactly, according to the following rules: The method names and return types match exactly. The order of the parameters, if any, including the ByRef modifier, must match exactly, though the parameter names may differ. All methods implementing an interface must be publicly accessible. However, if you are using explicit interface implementation, the method should not have an access qualifier.

If there is any difference between the interface definition and its declared implementation, the class will not compile. The following code example shows a class that implements the ICalculator interface.
Class Calculator Implements ICalculator, IComparable ' Code to implement ICalculator. #Region "ICalculator Members" Public Function Add() As Double Implements ICalculator.Add Return 0 End Function Public Function Subtract() As Double _ Implements ICalculator.Subtract Return 0 End Function Public Function Multiply() As Double _ Implements ICalculator.Multiply Return 0 End Function Public Function Divide() As Double Implements ICalculator.Divide Return 0 End Function #End Region End Class

A class can only inherit from one other class; however, a class can implement multiple interfaces. If you want to specify that a class implements multiple interfaces, you separate each interface with a comma in the class declaration. When you implement more than one interface, you must ensure that you follow the rules above for every interface that your class implements. Otherwise, your code will not compile. For example, to specify that the Calculator class implements the IComparable interface that was described in the previous topic, you could change the class definition, as the following code example shows.
Class Calculator Implements ICalculator, IComparable ' Code to implement ICalculator. #Region "ICalculator Members" Public Function Add() As Double Implements ICalculator.Add Return 0 End Function Public Function Subtract() As Double _ Implements ICalculator.Subtract

8-24

Programming in Visual Basic with Microsoft Visual Studio 2010

Return 0 End Function Public Function Multiply() As Double _ Implements ICalculator.Multiply Return 0 End Function Public Function Divide() As Double Implements ICalculator.Divide Return 0 End Function #End Region ' Code to implement IComparable. #Region "IComparable members" Public Function CompareTo(ByVal obj As Object) As Integer ... End Function #End Region End Class

Question: When you define an interface, do you add a method body?

Inheriting from Classes and Implementing Interfaces

8-25

Referencing an Object Through an Interface

In the same way that you can reference an object by using a variable that is defined as a class that is higher up an inheritance hierarchy, you can reference an object by using a variable that is defined as an interface that its class implements. Taking the example from the previous topic, you can reference a Calculator object by using an ICalculator variable, as the following code example shows.
Dim myCalculator As New Calculator() Dim iMyCalculator As ICalculator = myCalculator

This works because all Calculator objects implement the ICalculator interface. However, the converse is not true, and you cannot assign an ICalculator object to a Calculator variable without casting it first to verify that it does reference a Calculator object and not some other class that also happens to implement the ICalculator interface. You can use the TryCast keyword and the TypeOf...Is operator to check that an object implements an interface, or to check that an object that an interface references is an instance of a particular class.

Passing Parameters as Interface References


The technique of referencing an object through an interface is useful because it enables you to define methods that can take different types as parameters, as long as the types implement a specified interface. For example, in the following code example, the PerformAnalysis method can take any argument that implements the ICalculator interface.
Function PerformAnalysis(ByVal calculator As ICalculator) As Integer ... End Function

Note that, when you reference an object through an interface, you can invoke only methods that are visible through the interface.

8-26

Programming in Visual Basic with Microsoft Visual Studio 2010

Question: What happens if you attempt to convert or cast an object to an interface that it does not implement?

Inheriting from Classes and Implementing Interfaces

8-27

Demonstration: Creating an Interface

In this demonstration, you will see how to create an interface and implement it in a class.

Demonstration Steps
1. 2. 3. Open Microsoft Visual Studio 2010. In Visual Studio 2010, open the CreatingAnInterfaceDemo solution in the D:\Demofiles\Mod8\Demo2\Starter folder. In the Module1.vb file, add an interface called ITelevision. Your code should resemble the following code example.
Interface ITelevision End Interface

4.

Add a TurnOn method to the interface. Your code should resemble the following code example.

Interface ITelevision Sub TurnOn() End Interface

5.

Add a TurnOff method to the interface. Your code should resemble the following code example.

Interface ITelevision Sub TurnOn() Sub TurnOff() End Interface

6.

Add an IncreaseVolume method to the interface.

8-28

Programming in Visual Basic with Microsoft Visual Studio 2010

Your code should resemble the following code example.


Interface ITelevision Sub TurnOn() Sub TurnOff() Sub IncreaseVolume() End Interface

7.

Add a DecreaseVolume method to the interface. Your code should resemble the following code example.

Interface ITelevision Sub TurnOn() Sub TurnOff() Sub IncreaseVolume() Sub DecreaseVolume() End Interface

8.

In the Module1.vb file, add a class called Television that implements the ITelevision interface. Your code should resemble the following code example.

Class Television Implements ITelevision End Class

9.

In the definition of the Television class, implement the ITelevision interface by using the tools in Visual Studio.

Question: What is the recommended naming convention when defining interfaces?

Inheriting from Classes and Implementing Interfaces

8-29

Lesson 3

Defining Abstract Classes

This lesson introduces abstract classes and provides information on how you can use them to reduce code duplication, speed up development, and reduce the risk of introducing bugs that duplicated code causes. Abstract classes combine some of the properties of object inheritance with some of the properties of interfaces. You can use abstract classes to reduce code duplication by using default implementations of methods.

Lesson Objectives:
After this lesson, you will be able to: Describe an abstract class. Describe an abstract method. Create an abstract class.

8-30

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is an Abstract Class?

An abstract class provides a mechanism to factor out common code that several related classes share into a single class. For example, using the Employees class hierarchy that was shown earlier in this module, you might develop several classes to represent different types of employees, such as Manager and ManualWorker. Depending on the number of types of employees that you need to represent, the number of classes will increase. In situations such as this, it is quite common for some elements of these classes to have the same implementation. As an example, the ManualWorker and Manager classes in the following code example both implement the ISalaried interface and provide the PaySalary method, which is identical in both classes.
Interface ISalaried Sub PaySalary() End Interface Class ManualWorker Inherits Employee Implements ISalaried ... Sub PaySalary() Implements ISalaried.PaySalary Console.WriteLine("Pay salary: {0}", currentSalary) ' Code for paying salary End Sub End Class Class Manager Inherits Employee Implements ISalaried ... Sub PaySalary() Implements ISalaried.PaySalary Console.WriteLine("Pay salary: {0}", currentSalary) ' Same code as ManualWorker for paying salary End Sub

Inheriting from Classes and Implementing Interfaces

8-31

End Class

Duplication in code is a warning sign. If possible, you should refactor the code to avoid this duplication and reduce any maintenance costs. One way to achieve this refactoring is to put the common implementation into a new class that is created specifically for this purpose. In effect, you can insert a new class into the class hierarchy, as the following code example shows.
Class SalariedEmployee Inherits Employee Implements ISalaried ... Sub PaySalary() Implement ISalaried.PaySalary Console.WriteLine("Pay salary: {0}", currentSalary) ' Common code for paying salary End Sub Private currentSalary As Integer End Class Class ManualWorker Inherits SalariedEmployee Implements ISalaried ... End Class Class Manager Inherits SalariedEmployee Implements ISalaried ... End Class

This is a good solution, but one thing is still not quite right: you can create instances of the SalariedEmployee class (and the Employee class for that matter). The SalariedEmployee class exists to provide a common default implementation. Its sole purpose is to be inherited from. The SalariedEmployee class is an abstraction of common functionality rather than an entity in its own right. You don't want developers to be able to instantiate this class. To declare that creating instances of a class is not allowed, you can declare that the class is abstract by using the MustInherit modifier, as the following code example shows.
MustInherit Class SalariedEmployee Inherits Employee Implements ISalaried ... Sub PaySalary() Implement ISalaried.PaySalary() Console.WriteLine("Pay salary: {0}", currentSalary) ' Common code for paying salary End Sub Private currentSalary As Integer End Class

If you try to instantiate a SalariedEmployee object now, the code will not compile, as the following code example shows.
Dim myEmployee As New SalariedEmployee() ' Illegal

Question: Can a method in an abstract class contain a default implementation?

8-32

Programming in Visual Basic with Microsoft Visual Studio 2010

Additional Reading
For more information about the MustInherit modifier, see the MustInherit (Visual Basic) page at http://msdn.microsoft.com/en-us/library/aee8f02w.aspx

Inheriting from Classes and Implementing Interfaces

8-33

What Is an Abstract Method?

An abstract class can contain abstract methods. An abstract method is similar in principle to an Overridable method except that it does not contain a method body. A derived class must override this method. The following code example defines the PayBonus method in the SalariedEmployee class as an abstract method. All employees may share some methods, such as a PaySalary method, but they must provide their own implementation of the PayBonus method because the logic for paying bonuses may be different for different types of employee. An abstract method is useful if it does not make sense to provide a default implementation in the abstract class, and you want to ensure that an inheriting class provides its own implementation of that method.
MustInherit Class SalariedEmployee Inherits Employee Implements ISalariedEmployee MustOverride Sub PayBonus() ... End Class

When you define an abstract method, you use the MustOverride keyword and provide the method signature. Unlike an interface, you can add accessors to abstract methods. Note: If you attempt to add an abstract method to a nonabstract class, your code will not compile. Question: Can an abstract method contain a default implementation?

Additional Reading
For more information about MustOverride members, see the MustOverride (Visual Basic) page at http://msdn.microsoft.com/en-us/library/hyb29zk8.aspx

8-34

Programming in Visual Basic with Microsoft Visual Studio 2010

Demonstration: Creating an Abstract Class

Demonstration Steps
1. 2. 3. Open Microsoft Visual Studio 2010. In Visual Studio 2010, open the CreatingAnAbstractClassDemo solution in the D:\Demofiles\Mod8\Demo3\Starterfolder. In the Module1.vb file, add an abstract class called Television. Your code should resemble the following code example.
MustInherit Class Television End Class

4.

Add a public TurnOn method to the class. The method should write a message to the console indicating that the television is on. Your code should resemble the following code example.

MustInherit Class Television Public Sub TurnOn() Console.WriteLine("Television on.") End Sub End Class

5.

Add a public TurnOff method to the class. The method should write a message to the console indicating that the television is off. Your code should resemble the following code example.

MustInherit Class Television Public Sub TurnOn() Console.WriteLine("Television on.") End Sub

Inheriting from Classes and Implementing Interfaces

8-35

Public Sub TurnOff() Console.WriteLine("Television off.") End Sub End Class

6.

Add a public abstract IncreaseVolume method to the class. Your code should resemble the following code example.

MustInherit Class Television Public Sub TurnOn() Console.WriteLine("Television on.") End Sub Public Sub TurnOff() Console.WriteLine("Television off.") End Sub Public MustOverride Sub IncreaseVolume() End Class

7.

Add a public abstract DecreaseVolume method to the class. Your code should resemble the following code example.

MustInherit Class Television Public Sub TurnOn() Console.WriteLine("Television on.") End Sub Public Sub TurnOff() Console.WriteLine("Television off.") End Sub Public MustOverride Sub IncreaseVolume() Public MustOverride Sub DecreaseVolume() End Class

8.

In the Module1.vb file, add a class called WidescreenTV that inherits from the abstract Television class. Your code should resemble the following code example.

Class WidescreenTV Inherits Television End Class

9.

Override the IncreaseVolume method. The method should write a message to the screen indicating that the volume has increased on the widescreen television. Your code should resemble the following code example.

Class WidescreenTV Inherits Television Public Overrides Sub IncreaseVolume() Console.WriteLine("Volume increased (WidescreenTV).") End Sub End Class

8-36

Programming in Visual Basic with Microsoft Visual Studio 2010

10. Override the DecreaseVolume method. The method should write a message to the screen indicating that the volume has decreased on the widescreen television. Your code should resemble the following code example.
Class WidescreenTV Inherits Television Public Overrides Sub IncreaseVolume() Console.WriteLine("Volume increased (WidescreenTV).") End Sub Public Overrides Sub DecreaseVolume() Console.WriteLine("Volume decreased (WidescreenTV).") End Sub End Class

11. In the Module1.vb file, add a class called TV that inherits from the abstract Television class. Your code should resemble the following code example.
Class TV Inherits Television End Class

12. Override the IncreaseVolume method. The method should write a message to the screen indicating that the volume has increased on the television. Your code should resemble the following code example.
Class TV Inherits Television Public Overrides Sub IncreaseVolume() Console.WriteLine("Volume increased (TV).") End Sub End Class

13. Override the DecreaseVolume method. The method should write a message to the screen indicating that the volume has increased on the television. Your code should resemble the following code example.
Class TV Inherits Television Public Overrides Sub IncreaseVolume() Console.WriteLine("Volume increased (TV).") End Sub Public Overrides Sub DecreaseVolume() Console.WriteLine("Volume decreased (TV).") End Sub End Class

14. Uncomment the code in the Module1module. 15. Run the application with debugging. Question: Can you combine abstract and nonabstract (concrete) methods in an abstract class?

Inheriting from Classes and Implementing Interfaces

8-37

Lab: Inheriting from Classes and Implementing Interfaces

Objectives:
After completing this lab, you will be able to: Define an interface. Implement an interface in a class. Create an abstract class and inherit from this abstract class.

Introduction
In this lab, you will define interfaces and create classes that implement them. You will then factor out common implementation code from the classes into methods in an abstract class and inherit from it.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

8-38

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Scenario

Fabrikam, Inc. produces a range of highly sensitive measuring devices that can repeatedly measure objects and capture data. These devices can be used to detect minuscule changes in objects over time. A measuring device monitors and measures one specific aspect of an object, such as its mass, its size in a given dimension (height, width, or length), or its distance from the measuring device. The data can be captured in metric or imperial units, and the device can convert the data that it has captured between the metric and imperial scales. You have been asked to implement the software to drive these measuring devices.

Inheriting from Classes and Implementing Interfaces

8-39

Exercise 1: Defining an Interface


In this exercise, you will define an interface called IMeasuringDevice with the following public methods: MetricValue. This method will return a decimal that represents the metric value of the most recent measurement that was captured. ImperialValue. This method will return a decimal that represents the imperial value of the most recent measurement that was captured. StartCollecting. This method will start the device running. It will begin collecting measurements and record them. StopCollecting. This method will stop the device. It will cease collecting measurements. GetRawData. This method will retrieve a copy of all of the recent data that the measuring device has captured. The data will be returned as an array of integer values.

The main tasks for this exercise are as follows: 1. 2. Open the starter project. Create the IMeasuringDevice interface.

Task 1: Open the starter project.


Open Microsoft Visual Studio 2010. Import the code snippets from the D:\Labfiles\Lab08\Snippets folder. Open the Module8 solution in the D:\Labfiles\Lab08\Ex1\Starter folder.

Task 2: Create the IMeasuringDevice interface.


Open the IMeasuringDevice.vb code file. Declare the IMeasuringDevice interface. The IMeasuringDevice interface must be accessible to code in other assemblies. Add a method named MetricValue that returns a Decimal value to the interface. The method should take no parameters. Add a comment that describes the purpose of the method. Add a method named ImperialValue that returns a Decimal value to the interface. The method should take no parameters. Add a comment that describes the purpose of the method. Add a method named StartCollecting with a no return type to the interface. This method should take no parameters. Add a comment that describes the purpose of the method. Add a method named StopCollecting with a no return type to the interface. This method should take no parameters. Add a comment that describes the purpose of the method. Add a method named GetRawData that returns an integer array return type to the interface. This method should take no parameters. Add a comment that describes the purpose of the method. Build the solution and correct any errors.

8-40

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 2: Implementing an Interface


In this exercise, you will define the following enumeration: Units: Metric, Imperial

You will then define a class called MeasureLengthDevice that implements the IMeasuringDevice interface and drives a device that measures the length of an object. This class will also include the following private fields: unitsToUse: Units dataCaptured: Integer array mostRecentMeasure: Integer

You will provide a constructor to initialize the fields in the class (the user will specify a parameter that populates unitsToUse). When the device starts running (when the StartCollecting method is called), the device will capture data and store it in the dataCaptured array (you will simulate this in the lab by using the code that is provided). This array has a finite, fixed size; when the device is full, it will wrap around and start to overwrite the oldest data. Each time it takes a new measurement, the device copies this measurement to the mostRecentMeasure field. The GetRawData method will return the contents of the array. The MetricValue and ImperialValue methods will return the value in this field, converted according to the units that are specified in the unitsToUse field. If unitsToUse is Metric, MetricValue simply returns the data and ImperialValue performs a calculation to convert the data to imperial units. Similarly, if unitsToUse is Imperial, ImperialValue simply returns the data and MetricValue performs a calculation to convert the data to metric units. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. Open the starter project. Create the Units enumeration. Create the MeasureLengthDevice class. Update the test harness. Test the MeasureLengthDevice class by using the test harness.

Task 1: Open the starter project.


Open the Module8 solution in the D:\Labfiles\Lab08\Ex2\Starter folder. This solution contains the completed interface from Exercise 1 and skeleton code for Exercise 2.

Task 2: Create the Units enumeration.


The Units enumeration will contain two values, Metric and Imperial. Metric measurements are used in the International System of Units (SI), and include measurements in kilograms and meters. Imperial measurements were originally used in the British Empire, and are similar to customary system units in the United States. Review the Task List. In the Task List, double-click the task TODO: Implement the Units enumeration. This task is located in the UnitsEnumeration.vb file. Remove the TODO comment in the UnitsEnumeration.vb file and declare an enumeration named Units. The enumeration must be accessible from code in different assemblies. Add the values Metric and Imperial to the enumeration.

Inheriting from Classes and Implementing Interfaces

8-41

Comment your code to make it easier for developers who use the enumeration. Build the solution and correct any errors.

Task 3: Create the MeasureLengthDevice class.


In the Task List, double-click the task TODO: Implement the MeasureLengthDevice class. This task is located in the MeasureLengthDevice.vb file. Remove the TODO comment and add a Public class named MeasureLengthDevice. Modify the MeasureLengthDevice class declaration to implement the IMeasuringDevice interface. Generate method stubs for each of the methods in the IMeasuringDevice interface. You can generate the method stubs by placing the cursor immediately after the last letter of the interface name and then press Enter. Bring the DeviceControl namespace into scope. The MeasuringDevice project already contains a reference to the DeviceController project. You are writing code to control a device. However, because the physical device is not available with this lab, the DeviceController project enables you to call methods that control an emulated device. The DeviceController project does not include a visual interface. To control the device, you must use the classes and methods that the project exposes. The DeviceController project is provided complete. You can review the code if you want, but you do not need to modify it. After the method stubs in the MeasureLengthDevice class, add the fields shown in the following table. Field Name unitsToUse dataCaptured mostRecentMeasure Controller measurementType Field Type Units Integer() Integer DeviceController DeviceType Accessor Private Private Private Private Private

DeviceType is an enumeration that contains the values LENGTH and MASS. It is used to specify the type of measurement that the device records. It is defined in the DeviceController project. Modify the measurementType field to make it constant and initialize it to DeviceType.LENGTH. Locate the StartCollecting method and add code to the StartCollecting method to instantiate the controller field by using the shared StartDevice method of the DeviceController class. Pass the value in the measurementType field as the parameter to the StartCollecting method. In the StartCollecting method, call the GetMeasurements method. This method takes no parameters and does not return a value. You will add the GetMeasurements method in the next step. Add the GetMeasurements method to the class, as shown in the following code.

Note: A code snippet is available, called Mod8GetMeasurementsMethod, that you can use to add this method.

8-42

Programming in Visual Basic with Microsoft Visual Studio 2010

The GetMeasurements method retrieves measurements from the emulated device. In this module, you will use the code in the GetMeasurements method to populate the dataCaptured array. This array acts as a fixed-length circular buffer, overwriting the oldest value each time a new measurement is taken. In a later module, you will modify this class to respond to events that the device raises whenever it detects a new measurement. Locate the StopCollecting method, and add a conditional code block that only runs if the controller object is not null. In the conditional code block, add code to call the StopDevice method of the controller object, and then set the controller field to null. Locate the GetRawData method, and then add code to return the dataCaptured array. Locate the MetricValue method and add code to check the current units and, if they are metric, return the value from the mostRecentMeasure field. If the current units are imperial, return the result of multiplying the mostRecentMeasure field by 25.4. You can type in this code manually, or you can use the Mod8MetricValueMethod code snippet. Locate the ImperialValue method and then add code to check the current units and, if they are imperial, return the value from the mostRecentMeasure field. If the current units are metric, return the result of multiplying the mostRecentMeasure field by 0.03937. You can type in this code manually, or you can use the Mod8ImperialValueMethod code snippet. Add to the class a constructor that takes a Units parameter and sets the unitsToUse field to the value specified by this parameter. Build the solution and correct any errors.

Task 4: Update the test harness.


The test harness application for this lab is a simple Windows Presentation Foundation (WPF) application that is designed to test the functionality of the MeasureLengthDevice class that you have just developed. It does not include any exception handling to ensure that it does not hide any exceptions thrown by the class that you have developed. Review the Task List. Open the MainWindow.xaml.vb file by clicking the first TODO: Add code to instantiate the device field item in the Task List. This task is located in the CreateInstanceButton_Click method in the WPF window, and it runs when the user clicks the Create Instance button. In the CreateInstanceButton_Click method, replace both TODO comments with code to instantiate a field called device and set it to an instance of the MeasureLengthDevice class. You must use the appropriate member of the Units enumeration as the parameter for the MeasureLengthDevice constructor. Build the solution and correct any errors.

Task 5: Test the MeasureLengthDevice class by using the test harness.


Start the Exercise2TestHarness application. Select the Imperial radio button, and then click Create MeasureLengthDevice Instance. This button runs the code that you added to instantiate the device field that uses imperial measurements. Click Start Collecting. This button runs the StartCollecting method of the device object that the IMeasuringDevice interface defines. Wait for 10 seconds to ensure that the emulated device has generated some values before you perform the following steps.

Inheriting from Classes and Implementing Interfaces

8-43

Click Get Raw Data. You should see up to 10 values in the list box in the lower part of the window. This is the data that the device emulator has generated. It is stored in the dataCaptured array by the GetMeasurements method in the MeasureLengthDevice class. The dataCaptured array acts as a fixed-length circular buffer. Initially, it contains zero values, but as the device emulator reports measurements, they are added to this array. When the array is full, it wraps around and starts overwriting data, beginning with the oldest measurement. Click Get Metric Value and Get Imperial Value. You should see the metric and imperial value of the most recently generated measurement. Note that a new measurement might have been taken since you clicked the Get Raw Data button. Click Get Raw Data, and then verify that the imperial value that the previous step displayed is listed in the raw data values. (The value can appear at any point in the list.) Click Stop Collecting. Choose Metric, and then click Create MeasureLengthDevice Instance. This action creates a new instance of the device emulator that uses metric measurements. Click Start Collecting. This button starts the new device object. Wait for 10 seconds. Click Get Metric Value and Get Imperial Value to display the metric and imperial value of the latest measurement that the device has taken. Click Get Raw Data, and then verify that the metric value that the previous step displayed is listed in the raw data values. (The value can appear at any point in the list.) Click Stop Collecting. Close the Exercise 2 Test Harness window.

8-44

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 3: Creating an Abstract Class


In this exercise, you will define a class called MeasureMassDevice, which also implements the IMeasuringDevice interface. You will notice that, although the MetricValue and ImperialValue methods are implemented slightly differently from the MeasureLength class; the StartCollecting, StopCollecting, GetRawData, and GetMeasurements methods are identical. Code duplication is never a good thing, and can lead to maintenance difficulties. Consequently, you will create an abstract class called MeasureDataDevice that provides default implementations of the duplicated methods. Students will modify the MeasureLengthDevice and MeasureMassDevice classes to inherit from this class. The main tasks in this exercise are as follows: 1. 2. 3. 4. 5. 6. 7. Open the starter project. Create the MeasureMassDevice class. Update the test harness. Test the MeasureMassDevice class by using the test harness. Create the MeasureDataDevice abstract class. Modify the MeasureLengthDevice and MeasureMassDevice classes to inherit from the MeasureDataDevice abstract class. Test the classes by using the test harness.

Task 1: Open the starter project.


Open the Module8 solution in the D:\Labfiles\Lab08\Ex3\Starter folder. This solution contains the completed interface from Exercise 2 and skeleton code for Exercise 3.

Task 2: Create the MeasureMassDevice class.


Review the Task List. Open the MeasureMassDevice.vb file. Replace the TODO comment with a Public class named MeasureMassDevice. Modify the MeasureMassDevice class declaration to implement the IMeasuringDevice interface. Generate method stubs for each of the methods in the IMeasuringDevice interface. Bring the DeviceControl namespace into scope. The MeasuringDevice project already contains a reference to the DeviceController project. This project implements the DeviceController type, which provides access to the measuring device emulator. After the method stubs that Visual Studio added, add the fields shown in the following table. Field Name unitsToUse dataCaptured mostRecentMeasure controller measurementType Field Type Units Integer() Integer DeviceController DeviceType Accessor Private Private Private Private Private

Inheriting from Classes and Implementing Interfaces

8-45

Modify the measurementType field to make it constant and initialize it to DeviceType.MASS. Locate the StartCollecting method and add code to instantiate the controller field by using the shared StartDevice method of the DeviceController class. Pass the measurementType field as the parameter to the StartDevice method. Add code to call the GetMeasurements method. This method takes no parameters and does not return a value. You will add the GetMeasurements method in the next step. Add the GetMeasurements method to the class, as shown in the following code example.

Note: A code snippet is available, called Mod8GetMeasurementsMethod, that you can use to add this method.
Private Sub GetMeasurements() ReDim dataCaptured(9) System.Threading.ThreadPool.QueueUserWorkItem( Sub() Dim x As Integer = 0 Dim timer As New Random() While Not controller Is Nothing System.Threading.Thread.Sleep(timer.Next(1000, 5000)) dataCaptured(x) = If(Not controller Is Nothing, controller.TakeMeasurement(), dataCaptured(x)) mostRecentMeasure = dataCaptured(x) x += 1 If x = 10 Then x = 0 End If End While End Sub)

End Sub

This is the same method that you defined for the MeasureLengthDevice class. Locate the StopCollecting method and then remove the default method body that Visual Studio inserts, which throws a NotImplementedException exception. Add a conditional code block that only runs if the controller object is not null. In the conditional code block, add code to call the StopDevice method of the controller object, and then set the controller field to null. Locate the GetRawData method and then add code to return the dataCaptured array. Locate the MetricValue method and then add code to check the current units and, if they are metric, return the value from the mostRecentMeasure field. If the current units are imperial, return the result of multiplying the mostRecentMeasure field by 0.4536. Locate the ImperialValue method and then add code to check the current units and, if they are imperial, return the value from the mostRecentMeasure field. If the current units are metric, return the result of multiplying the mostRecentMeasure field by 2.2046. Add to the class a constructor that takes a Units parameter and sets the unitsToUse field to the value specified by this parameter. Build the solution and correct any errors.

8-46

Programming in Visual Basic with Microsoft Visual Studio 2010

Task 3: Update the test harness.


The test harness application in this lab is a modified version of the WPF application that you used in Exercise 2. It is designed to test the functionality of the MeasureLengthDevice and MeasureMassDevice classes. It does not include any exception handling to ensure that it does not hide any exceptions thrown by the class that you have developed. Review the Task List. Open the MainWindow.xaml.vb file by clicking the first TODO: Instantiate the device field by using the new MeasureMassDevice class item in the Task List. In the CreateInstanceButton_Click method, replace both TODO comments with code to instantiate the device field to an instance of the MeasureMassDevice class. You must use the appropriate member of the Units enumeration as the parameter for the MeasureMassDevice constructor. Build the solution and correct any errors.

Task 4: Test the MeasureMassDevice class by using the test harness.


Start the Exercise3TestHarness application. Select the Imperial radio button, in the list, click Mass Device, and then click Create Instance. This button runs the code that you added to instantiate the device field that uses imperial measurements. Click Start Collecting. This button runs the StartCollecting method of the MeasureMassDevice object. Wait for 10 seconds to ensure that the emulated device has generated some values before you perform the following steps. Click Get Metric Value and Get Imperial Value. You should see the metric and imperial value of the most recently generated measurement. Click Get Raw Data and then verify that the imperial value that the previous step displayed is listed in the raw data values. (The value can appear at any point in the list.) Click Stop Collecting. Choose Metric and then click Create Instance. This action creates a new instance of the device emulator that uses metric measurements. Click Start Collecting. This button starts the new device object. Wait for 10 seconds. Click Get Metric Value and Get Imperial Value to display the metric and imperial value of the latest measurement that the device has taken. Click Get Raw Data, and then verify that the metric value that the previous step displayed is listed in the raw data values. (The value can appear at any point in the list.) Click Stop Collecting. Close the Exercise 3 Test Harness window.

Task 5: Create the MeasureDataDevice abstract class.


You have developed two classes, MeasureLengthDevice and MeasureMassDevice. Much of the functionality of these classes is common to both. This code duplication is unnecessary and risks introducing bugs. To reduce the code that is required and the risk of introducing bugs, you need to create an abstract class that will contain the common functionality. Open the MeasureDataDevice.vb file.

Inheriting from Classes and Implementing Interfaces

8-47

Remove the TODO comment and add a MustInherit class named MeasureDataDevice. Modify the MeasureDataDevice class declaration to implement the IMeasuringDevice interface, but do not generate the method stubs. Bring the DeviceControl namespace into scope. In the MeasureDataDevice class, add a Public MustOverride method named MetricValue. This method should return a Decimal value, but not take any parameters. The implementation of the MetricValue method is specific to the type of device being controlled, so you must implement this functionality in the child classes. Declaring the MetricValue method as MustOverride forces child classes to implement this method.

Hint: Look at the code for the MetricValue method for the MeasureLengthDevice and MeasureMassDevice classes. You will observe that they are quite similar, apart from the conversion factors that are used, and you could factor this logic into a method in the abstract MeasureDataDevice class. However, for the sake of this exercise, assume that these methods are totally different. The same note applies to the ImperialValue method that you will define in the next step. In the MeasureDataDevice class, add a Public MustOverride method with a Decimal return type named ImperialValue. Like the MetricValue method, the implementation of the ImperialValue method is specific to the type of device being controlled, so you must implement this functionality in the child classes. In the MeasureLengthDevice.vb file, locate and copy the code for the StartCollecting method, and then add this method to the MeasureDataDevice class. Visual Studio will warn you that the controller variable, the measurementType enumeration, and the GetMeasurements method are not defined. You will add these items to the MeasureDataDevice class in later steps in this task. Copy the StopCollecting method from the MeasureLengthDevice.vb file to the MeasureDataDevice class. Visual Studio will warn you that the controller variable is not defined. Copy the GetRawData method from the MeasureLengthDevice.vb file to the MeasureDataDevice class. Visual Studio will warn you that the dataCaptured variable is not defined. Copy the GetMeasurements method from the MeasureLengthDevice.vb file to the MeasureDataDevice class. Visual Studio will warn you that the dataCaptured, controller, and mostRecentMeasure variables are not defined. Copy the five fields in the following table from the MeasureLengthDevice.vb file to the MeasureDataDevice class. Field Name unitsToUse dataCaptured mostRecentMeasure Field Type Units Integer() Integer Accessor Private Private Private

8-48

Programming in Visual Basic with Microsoft Visual Studio 2010

Field Name controller measurementType

Field Type DeviceController DeviceType

Accessor Private Private

The warnings in the StartCollecting, StopCollecting, GetRawData, and GetMeasurements methods should disappear. In the MeasureDataDevice class, modify the five fields that you added in the previous step to make them visible to classes that inherit from the abstract class. Modify the declaration of the measurementType field so that it is no longer constant and not instantiated when it is declared. Build the solution and correct any errors.

Task 6: Modify the MeasureLengthDevice and MeasureMassDevice classes to inherit


from the MeasureDataDevice abstract class.
In this task, you will remove the duplicated code from the MeasureLengthDevice and MeasureMassDevice classes by modifying them to inherit from the MeasureDataDevice abstract class that you created in the previous task. In the MeasureLengthDevice.vb file, modify the declaration of the MeasureLengthDevice class so that, in addition to implementing the IMeasuringDevice interface, it also inherits from the MeasureDataDevice class. Remove the StartCollecting method from the MeasureLengthDevice class. Remove the StopCollecting method from the MeasureLengthDevice class. Remove the GetRawData method from the MeasureLengthDevice class. Remove the GetMeasurements method from the MeasureLengthDevice class. Remove the fields in the following table from the MeasureLengthDevice class. Field Name unitsToUse dataCaptured mostRecentMeasure controller measurementType Field Type Units Integer() Integer DeviceController DeviceType Accessor Private Private Private Private Private

Modify the constructor to set the measurementType field to DeviceType.LENGTH. Modify the MetricValue method signature to indicate that it overrides the abstract method in the base class. Modify the ImperialValue method signature to indicate that it overrides the abstract method in the base class. In the MeasureMassDevice.vb file, modify the declaration of the MeasureMassDevice class so that it inherits from the MeasureDataDevice class. Remove the StartCollecting method from the MeasureMassDevice class.

Inheriting from Classes and Implementing Interfaces

8-49

Remove the StopCollecting method from the MeasureMassDevice class. Remove the GetRawData method from the MeasureMassDevice class. Remove the GetMeasurements method from the MeasureMassDevice class. Remove the fields in the following table from the MeasureMassDevice class. Field Name unitsToUse dataCaptured mostRecentMeasure controller measurementType Field Type Units Integer() Integer DeviceController DeviceType Accessor Private Private Private Private Private

Modify the constructor to set the measurementType field to DeviceType.MASS. Modify the MetricValue method signature to indicate that it overrides the abstract method in the base class. Modify the ImperialValue method signature to indicate that it overrides the abstract method in the base class. Build the solution and correct any errors.

Task 7: Test the classes by using the test harness.


In this task, you will check that the MeasureLengthDevice and MeasureMassDevice classes still work as expected. Start the Exercise3TestHarness application. Select the Imperial radio button, in the list, click Mass Device, and then click Create Instance. Click Start Collecting. Wait for 10 seconds to ensure that the emulated device has generated some values before you perform the following steps. Click Get Metric Value and Get Imperial Value to display the metric and imperial value of the latest measurement that the device has taken. Click Get Raw Data, and then verify that the imperial value that the previous step displayed is listed in the raw data values. (The value can appear at any point in the list.) Click Stop Collecting. Select the Metric radio button, in the list, click Length Device, and then click Create Instance. Click Start Collecting. This button starts the new device object. Wait for 10 seconds. Click Get Metric Value and Get Imperial Value to display the metric and imperial value of the latest measurement that the device has taken. Click Get Raw Data, and then verify that the metric value that the previous step displayed is listed in the raw data values. (The value can appear at any point in the list.) Click Stop Collecting.

8-50

Programming in Visual Basic with Microsoft Visual Studio 2010

Close the Exercise 3 Test Harness window. Close Visual Studio.

Inheriting from Classes and Implementing Interfaces

8-51

Lab Review

Review Questions
1. 2. What steps are required to implement an interface? How do you implement an abstract class?

8-52

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Review and Takeaways

Review Questions
1. 2. What is the role of the object class in the .NET Framework? What are the advantages of using an abstract class over an interface?

Best Practices Related to Inheritance


Supplement or modify the following best practices for your own work situations: Create an inheritance hierarchy where appropriate to reduce code duplication. Where appropriate, mark methods as Overridable to enable child classes to override them. Where appropriate, mark methods as NotOverridable to prevent child classes from overriding them. Where appropriate, mark classes as NotInheritable to prevent classes from inheriting from them.

Best Practices Related to Interfaces


Supplement or modify the following best practices for your own work situations: Use interfaces wherever possible as a contract that specifies what methods a class will expose. Prefix an interface with an upper case I.

Best Practices Related to Abstract Classes


Supplement or modify the following best practices for your own work situations: Use abstract classes to abstract common functionality and reduce code duplication. Use abstract methods to guarantee that an inheriting class overrides a method.

Managing the Lifetime of Objects and Controlling Resources

9-1

Module9
Managing the Lifetime of Objects and Controlling Resources
Contents:
Lesson 1: Introduction to Garbage Collection Lesson 2: Managing Resources Lab: Managing the Lifetime of Objects and Controlling Resources 9-3 9-16 9-28

9-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

All applications use resources. When you build a Microsoft Visual Basic application, resources fall into two broad categories: managed resources that are handled by the common language runtime (CLR) and unmanaged resources that are maintained by the operating system outside the scope of the CLR. A managed resource is typically an object based on a class defined by using a managed language, such as Visual Basic. Examples of unmanaged resources include items implemented outside the Microsoft .NET Framework, such as Component Object Model (COM) components, file handles, database connections, and network connections. Resource management is important in any applications that you develop. The NET Framework simplifies resource management by automatically reclaiming the resources by a managed object when it is no longer referenced by an application. Managed resources are handled by the .NET Framework garbage collector. However, unmanaged resources are not controlled by the garbage collector; you must take special steps to dispose them properly and prevent them from being held longer than necessary.

Objectives:
After completing this module, you will be able to: Describe how garbage collection works in the .NET Framework. Manage resources effectively in an application.

Managing the Lifetime of Objects and Controlling Resources

9-3

Lesson 1

Introduction to Garbage Collection

Every object that you create has a life cycle, from creation to destruction. When an object is destroyed, its state must be cleaned, and any managed resources used must be reclaimed. In the .NET Framework, the garbage collector performs these tasks. This lesson introduces garbage collection in the .NET Framework.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the life cycle of an object. Explain how memory is allocated for managed resources. Describe the operation of the garbage collector. Define a destructor/finalizer. Explain the use of the GC class.

9-4

Programming in Visual Basic with Microsoft Visual Studio 2010

Object Life Cycle

An object has several distinct stages in its life cycle, which starts at creation and ends in destruction. As a developer, the process that you use to create an object is very simple; you use the New keyword to instantiate the new object. However, behind the scenes, the process is not really this simple. When you create a new object, the following things happen: 1. 2. A block of memory is allocated. This block of memory is big enough to hold the object. The block of memory is converted to an object. The object is initialized.

You can control only the second of these two stepsconverting the block of memory to an object. You can control this step by implementing a constructor. The runtime handles the allocation of memory for managed objects; however, if you call unmanaged libraries, you may need to manually allocate memory for unmanaged objects you create. After an object is created, you can use the properties, methods, and other members.

Destroying an Object
When you have finished with an object, it can be destroyed. You use destruction to reclaim any resources used by that object. Like creation, destruction is a two-phase process: 1. 2. The object is cleaned up; for example, by releasing any unmanaged resources used by the application, such as file handles and database connections. The memory used by the object is reclaimed.

You can control only the first of these stepscleaning up the object and releasing resources. You can control this step by implementing a destructor. The CLR handles the release of memory used by managed objects; however, if you use unmanaged objects, you may need to manually release the memory used by these items.

Managing the Lifetime of Objects and Controlling Resources

9-5

Question: How can you control the creation phase for an object?

9-6

Programming in Visual Basic with Microsoft Visual Studio 2010

Managed Resources in the .NET Framework

The .NET Framework divides the items that a managed application can use into two broad categories: value types and reference types.

Managing Value Types


Value types are managed types that are usually created on the stack. The CLR manages the stack. When an object on the stack goes out of scope, the memory used by that object is reclaimed immediately. For example, at the end of a method, any variables based on value types (created on the stack) defined in that method are destroyed. The stack is a last in, first out (LIFO) structure. The CLR maintains a pointer to the top of the stack. When a value type variable is created, it is placed at the top of the stack and the stack pointer is moved up. When the variable goes out of scope, the stack pointer is moved down again. In this way, new items overwrite old items and memory is reclaimed automatically; memory management is therefore a relatively inexpensive operation.

Managing Reference Types


Reference types are allocated memory from the heap. The heap is a block of memory also controlled by the CLR, separate from the stack. When you create an object, the CLR allocates memory for the object and creates a reference to it on the stack. Unlike a value type, a reference type can have several references to the same object. If you refer to the same object several times, the reference disappears when it goes out of scope, but other references to the same object that are still in scope remain valid. An object can be destroyed, its destructor run, and its resources reclaimed only when the final reference to the object disappears. Consequently, the lifetime of an object is not governed by the scope of any single reference to that object. An important feature of the .NET Framework garbage collector is to monitor an object on the heap and determine when the last reference to that object has disappeared. The object can then be safely destroyed. Determining when an object has no references can be a time-consuming and expensive operation, so the garbage collector performs this task only when it needs to, typically, when the amount of memory available on the heap falls below some threshold.

Managing the Lifetime of Objects and Controlling Resources

9-7

A second function of the garbage collector is to defragment the heap. If an application attempts to create an object for which there is currently insufficient contiguous empty space available on the heap, the garbage collector will attempt to move some existing objects around and compact the resulting free space into a chunk of memory big enough to hold the new object. Again, this can be a computationally expensive task.

Value Types on the Heap


Value types are usually created on the stack. However, there is one scenario where this does not occur. When you develop a class, you can use a value type as a field in that class. When you instantiate an object by using this class, the fields that constitute the object, including any value type fields, are allocated space on the heap. These value type fields remain active until the containing object is destroyed and the garbage collector reclaims the space used by these fields. If you use a value type as a field in a structure, it will be stored as part of the structure. Structures are value types and as such are normally stored on the stack, unless they are elements inside a class. If the value type is an element inside a class, it is stored on the heap as described in the previous paragraph. Question: Is the stack a first in, first out (FIFO), LIFO, first in, last out (FILO), or random access memory store?

Additional Reading
For more information about memory management, see the Object Lifetime: How Objects Are Created and Destroyed (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211286&clcid=0x409

9-8

Programming in Visual Basic with Microsoft Visual Studio 2010

How Does the Garbage Collector Work?

The garbage collector releases resources and memory for objects stored on the heap. The garbage collector runs in its own thread and normally runs automatically, under well-defined circumstances. When the garbage collector runs, other threads in an application are halted because the garbage collector may move objects in memory and must update pointers to the correct addresses for these objects. The garbage collector performs the following steps to reclaim resources: 1. 2. It marks every object as dead; objects are considered dead unless proved otherwise. It starts from objects referenced on the stack, marking referenced objects as alive. It performs this recursively; if an object that is already marked as alive references another object, that object is also marked as alive. The garbage collector includes logic to prevent infinite recursion, for example, where there is a circular reference between two objects. It checks whether any of the objects that have been marked as dead have a destructor that must be run. Running the destructor is referred to as finalization. Any objects that require finalization are moved to a data structure, maintained by the garbage collector, called the freachable queue. The freachable queue stores pointers to objects that require finalization before their resources can be reclaimed. Objects added to the freachable queue are marked as alive because there is now a valid reference to them; the destructor must be run before their memory can be reclaimed. Objects are normally added to the freachable queue only once. Objects marked as alive are moved down the heap to form a contiguous block, defragmenting the heap. References to objects (on the stack and in other objects on the heap) moved by the garbage collector are updated. Other threads resume. On a separate thread, objects added to the freachable queue are finalized. After an object is finalized, the pointer to that object is removed from the freachable queue. Objects are not removed from memory until the next time the garbage collector runs.

3.

4.

5.

6. 7.

Managing the Lifetime of Objects and Controlling Resources

9-9

Question: What is the purpose of the freachable queue?

9-10

Programming in Visual Basic with Microsoft Visual Studio 2010

Defining a Destructor/Finalizer

You can add a destructor to a class to perform any additional application-specific cleanup that is necessary when your class is garbage collected. Important: Defining a destructor adds an overhead to the process of garbage collecting an object; it must be added to the freachable queue so that the garbage collector can run the destructor. The garbage collector automatically manages the memory for managed objects, so you should implement a destructor only if an object references unmanaged resources that must be reclaimed when an object is destroyed. Important: The point at which the destructor runs is not specified, and you cannot guarantee the order in which destructors for different objects will run. Therefore, do not make any assumptions or introduce dependencies between objects in a destructor. Visual Basic does not support destructors in the strictest sense of the word, but a finalizer method, working in conjunction with the garbage collector, will do the same for you. The following restrictions apply to destructors: You cannot add a destructor to a structure or any other value type. Value types are stored on the stack, so garbage collection does not apply. You cannot declare a destructor that takes parameters. The garbage collector calls the destructor, and you have no control to pass parameters to it.

The following code example shows the syntax for adding a destructor or finalizer.
Protected Override Sub Finalize() Try ' Destructor logic Finally

Managing the Lifetime of Objects and Controlling Resources

9-11

MyBase.Finalize() End Try End Sub

Add the destructor logic to a Try block and then call the Finalize method of the base class in the Finally block. This ensures that the Finalize method of the base class is always called, even if your code throws an exception. When a class with a destructor is eligible for garbage collection, it is added to the freachable queue. The finalization thread then runs the finalization code. After an object is finalized, the reference is removed from the freachable queue and the object is eligible for garbage collection again. You should only define a destructor in classes that specifically require this functionality, and omit them from all other classes to avoid this performance hit. Question: Can you add a destructor to a structure?

9-12

Programming in Visual Basic with Microsoft Visual Studio 2010

GC Class

Most of the time, you should let the garbage collector perform operations in its own time as directed by the CLR. However, under some circumstances, you may need to explicitly request that the garbage collector is invoked or modify the way in which it runs. To do this, you can use the GC class. The GC class includes several static methods that you can call from your code. The following table includes some of the more frequently used methods exposed by the GC class. Method Collect Description Forces garbage collection. Notes You should avoid using the Collect method in your code. If you force the garbage collector to run more often than necessary, it may have a negative performance impact on your application. The Collect method is asynchronous; when it returns, there is no guarantee that the garbage collection is complete, or even started, only that the garbage collector will run at the next suitable interval.
GC.Collect()

WaitForPendingFinalizers

Suspends the current thread until all objects in the freachable queue have been finalized.

You can use this method if you specifically need to wait for all objects currently in the freachable queue to finalize.
GC.WaitForPendingFinalizers()

Managing the Lifetime of Objects and Controlling Resources

9-13

Method SupressFinalize

Description Prevents finalization of the object passed as the parameter.

Notes You should always call this method when you implement the dispose pattern. This method can improve performance by preventing finalization code from running twice.
GC.SuppressFinalize(Me)

ReRegisterForFinalize

Requests finalization for an object that has either already been finalized or had finalization suppressed. Informs the runtime that you must allocate a large block of unmanaged memory.

You can use this method if you have suppressed finalization for an object, or an object has already been finalized but you require the runtime to finalize the object again.
GC.ReRegisterForFinalize(Me)

AddMemoryPressure

This method informs the runtime that you are about to allocate a large block of memory and it will free resources where possible. When you use this method, you must specify how much memory you need to allocate. If you need to allocate several blocks of memory, you can call this several times in your application. You should call this method before allocating a large block of unmanaged memory. You should not use this method if you are creating managed objects.
GC.AddMemoryPressure(1000)

RemoveMemoryPressure

Informs the runtime that you have released a large block of unmanaged memory.

This method informs the runtime that you have released a large block of memory and it will reduce the urgency with which it performs garbage collection. When you use this method, you must specify how much memory you have released. If you must release several blocks of memory, you can call this several times in your application. You should call this method after releasing a large block of unmanaged memory. You should not use this if you are destroying managed objects. You should always use the AddMemoryPressure and RemoveMemoryPressure methods together to ensure that you add and remove exactly the same amount of memory pressure.
GC.RemoveMemoryPressure(1000)

Question: How can you inform the runtime that you need to allocate a large block of unmanaged memory?

9-14

Programming in Visual Basic with Microsoft Visual Studio 2010

Additional Reading
For more information about the GC class, see the GC Class page at http://go.microsoft.com/fwlink/?LinkID=211287&clcid=0x409

Managing the Lifetime of Objects and Controlling Resources

9-15

Demonstration: Implementing a Destructor/Finalizer

Demonstration Steps
1. 2. 3. 4. Start Microsoft Visual Studio 2010. Open the Destructor Demo solution in the D:\Demofiles\Mod9\Demo1\Starterfolder. Run the application with debugging. Notice that all the employees are paid the same amount; the pay increases do not reflect the update made to the text file by the destructor. This is because the garbage collector only runs when it needs to, and this program does not create enough objects or use enough memory to cause a collection. Stop debugging. In the Module1.vb file, uncomment the call to the AddMemoryPressure method. Run the application with debugging.

5. 6. 7.

Notice that the results have not changedalthough the application now requires more memory, the application runs faster than the garbage collector finalizes the objects. 8. 9. Stop debugging. In the Module1.vb file, uncomment the call to the WaitForPendingFinalizers method.

10. Run the application with debugging. Notice that the application now works as expected, because the application halts to wait for the finalizer to update the text file before continuing. Question: How can you delay execution of the current thread until all objects in the finalization queue are finalized?

9-16

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 2

Managing Resources

The garbage collector automatically reclaims memory and resources for managed objects. However, if you use unmanaged resources in a class, you must perform additional steps to ensure that they are released appropriately. The dispose pattern is a design pattern that enables you to release unmanaged resources used by a class in a controlled and timely manner. Implementing the dispose pattern in your types will help to ensure that your applications perform well and do not retain unmanaged resources longer than necessary.

Lesson Objectives:
After completing this lesson, you will be able to: Explain the need for resource management in a managed code. Describe the dispose pattern. Manage resources in applications.

Managing the Lifetime of Objects and Controlling Resources

9-17

Why Manage Resources in a Managed Environment?

The garbage collector is concerned with managed objects. It does not understand how to release the resources associated with unmanaged objects. Unmanaged objects refer to objects that are not managed by the .NET Common Language Runtime (CLR), such as COM objects or classic Visual Basic objects. If you reference an unmanaged resource in a class, when you remove the last reference to the class, the unmanaged object will not be destroyed, it will simply be orphaned. The operating system may not clean up the resource until your application terminates. For example, the .NET Framework class library provides the TextWriter class that you can use to open a file on the local file system and write text to that file. The TextWriter class acts as a managed wrapper around text files, which are unmanaged resources controlled by the operating system. When the TextWriter object opens a file, the operating system locks the file to ensure that no other processes can write to the same file. When you have finished using the TextWriter object in your code, you can remove all references to it. This action will destroy the managed TextWriter object, but it may not release the lock because it is part of an unmanaged resource that is not controlled by the garbage collector. You must take additional steps to release this lock; otherwise, if you attempt to create another TextWriter object to write to the same file, it will fail. In addition to unmanaged locks, there are several other problems associated with incorrect resource management. For example, some unmanaged types use in-memory buffers to improve performance and only write to the underlying data source when either the buffer is full or the object is explicitly instructed to. If you fail to flush these buffers when an object is destroyed, the contents of these buffers may be lost. As an example, when you write data to a file by using the TextWriter class, the data may be buffered by the underlying file type. If you destroy the TextWriter object without releasing the resources associated with the file, the buffer may not be flushed correctly, and you may lose data. The TextWriter class provides the Flush method to write the contents of the buffer to the file system, which you can call to ensure that all data is written to the file before you destroy a TextWriter object.

9-18

Programming in Visual Basic with Microsoft Visual Studio 2010

Database connections are another resource that are both expensive to maintain and often limited. Database servers frequently support only a limited number of concurrent connections. If you fail to release database connections when you have finished using them, the available database connections will soon deplete, and your application may throw unexpected exceptions when it tries to connect to the database. If you manage resources correctly and ensure that all unmanaged resources are released when they are no longer required, you can prevent such problems. Question: What types of resources may need to be managed correctly?

Managing the Lifetime of Objects and Controlling Resources

9-19

What Is the Dispose Finalize Pattern?

The dispose pattern is a design pattern that frees resources used by an object. The .NET Framework provides the IDisposable interface, and objects that implement this interface should follow the dispose pattern.

The IDisposable Interface


The IDisposable interface defines a single method called Dispose, which takes no parameters. The Dispose method should release all the unmanaged resources that an object owns. It should also release all resources owned by its base types by calling the Dispose method of the parent type. Visual Basic includes constructs that ensure that resources are released in a timely fashion for classes that implement the IDisposable interface. Many of the classes in the .NET Framework that wrap unmanaged resources, such as the TextWriter class, implement the IDisposable interface. You should also implement the IDisposable interface when you create your own classes that reference unmanaged types.

Tracking Object Disposal


Calling the Dispose method does not destroy an object, and an object remains in existence when the Dispose method completes; the object is destroyed only after the final reference to it is removed and the garbage collector reclaims any remaining resources it is using. Therefore, when you implement the dispose pattern in a class, you should track the disposal status of the object and check whether the Dispose method has already been invoked and the resources released. A common technique is to add an isDisposedBoolean field to your class, set it in the Dispose method, and check it in every other method in your class. If methods in your class are called after disposal, this is normally a mistake, and you should throw an ObjectDisposedException exception. The exception to this rule is the Dispose method itself. You should be able to run the Dispose method multiple times without throwing any exceptions or resulting in an inconsistent state. Your Dispose method should include logic to check the state of resources that are about to be released before it

9-20

Programming in Visual Basic with Microsoft Visual Studio 2010

releases them. The following code example shows an example of a class that incorporates a TextWriter object, and implements the IDisposable interface. This example uses the Dispose method to ensure that the TextWriter object is closed correctly and the underlying file resources are reclaimed.
Class LogFileWriter Implements IDisposable Private isDisposed As Boolean = False Private writer As TextWriter = ... ... Public SubWriteDataToFile(...) ' Check that the current object has not been disposed of If Me.isDisposed Then Throw New ObjectDisposedException(...) End If ... End Sub Public Sub Dispose() Implements IDisposable.Dispose If Not Me.isDisposed Then ' Only close the TextWriter if it is not null ' (in which case it has already been disposed) If Not writer Is Nothing Then writer.Flush() writer.Close() writer = Nothing End If ' Indicate that the object has been disposed of and ' resources have been released isDisposed = True End If End Sub End Class

Calling Dispose from a Destructor


If you must guarantee that the Dispose method is always invoked, you can include it as part of the finalization process performed by the garbage collector. To do this, add a destructor to your class and call the Dispose method. However, remember that finalization is a potentially expensive process, so you should implement this strategy only if you really must.

Disposing of Managed Resources Early


In some cases, you may want to dispose of managed resources in addition to unmanaged resources. This is typically the case if a managed resource is no longer required and is expensive to maintain, such as a large array. The garbage collector will reclaim this memory eventually when your object is destroyed, but you may be able to free the memory used by this array early by setting the reference to this array to null in the Dispose method. Note that this strategy does not guarantee that the memory used by the array will be collected early, only that it may be. The timing depends on the garbage collector. If you invoke the Dispose method from a destructor, in addition to allowing an application to invoke the Dispose method manually, there is little point in trying to dispose of the managed resources more than once. In this case, the recommended approach is to overload the Dispose method and provide an implementation that takes a Boolean flag that indicates whether the Dispose method was called as part of the finalization process or directly by application code. The convention is to pass the value True if the Dispose method is called by an application and False if it is called by a destructor. The overloaded Dispose method should only dispose of managed resources if it was called directly (the parameter is

Managing the Lifetime of Objects and Controlling Resources

9-21

True). When it is false, the managed resources will either have already been disposed of or will be about to be disposed of by the garbage collector anyway. In this case, the Dispose method should only attempt to release unmanaged resources. The public Dispose method, which takes no parameters and is defined as part of the IDisposable interface, can then simply call Dispose(True), and the destructor can call Dispose(false). It is good practice to make the overloaded implementation of the Dispose method Protected and Overridable. In this way, it can only be accessed by code in the class and any child classes, but child classes can override it if they define additional resources that must be disposed of. The overloaded Dispose method should also invoke the Dispose method of any parent class if the parent class implements the dispose pattern. The following code example shows a class that uses this strategy.
Class LogFileWriter Implements IDisposable Private isDisposed As Boolean = False Private writer As TextWriter = ... Private largeArray() As Integer = ... ... Public Sub WriteDataToFile(...) ' Check that the current object has not been disposed of If Me.isDisposed Then Throw New ObjectDisposedException(...) End If ... End Sub Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) End Sub Protected Overrides Sub Finalize() Dispose(False) End Sub Protected Overridable Overloads Sub Dispose( _ ByVal isDisposing As Boolean) If Not Me.isDisposed Then If isDisposing Then ' Release managed resources only if Dispose ' was called by the application largeArray = Nothing ... End If ' Always release unmanaged resources If Not writer Is Nothing Then writer.Flush() writer.Close() writer = Nothing End If ' Indicate that the object has been disposed of and ' resources have been released isDisposed = True End If End Sub

9-22

Programming in Visual Basic with Microsoft Visual Studio 2010

End Class

Suppressing Finalization
When you add a destructor/finalizer to a class, by default, when the garbage collector disposes of resources, the objects are added to the freachable queue for finalization. In your public Dispose method, after you have released all the necessary resources, you should call the SharedSuppressFinalize method of the GC class, passing in the current object. This call marks the object so that the garbage collector does not waste time running the finalization code for your object, which you have already cleaned up. The following code example shows this method.
Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(Me) End Sub

So, the implementation of the Dispose Finalize pattern for the LogFileWriter class should look similar to this example code.
Class LogFileWriter Implements IDisposable Private isDisposed As Boolean = False Private writer As TextWriter = ... ... Public Sub WriteDataToFile(...) ' Check that the current object has not been disposed of If Me.isDisposed Then Throw New ObjectDisposedException(...) End If ... End Sub Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(Me) End Sub Protected Overridable Overloads Sub Dispose( _ ByVal isDisposing As Boolean) If Not Me.isDisposed Then If isDisposing Then ' Only close the TextWriter if it is not null ' (in which case it has already been disposed) If Not writer Is Nothing Then writer.Flush() writer.Close() writer = Nothing End If End If ' Clean up unmanaged resources isDisposed = True End If End Sub Protected Overrides Sub Finalize() Dispose(False) MyBase.Finalize()

Managing the Lifetime of Objects and Controlling Resources

9-23

End Sub End Class

Question: What exception should you throw in your class if you attempt to use it after it has been disposed?

Additional Reading
For more information about the Dispose Finalize pattern, see the How to: Implement the Dispose Finalize Pattern (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211288&clcid=0x409

9-24

Programming in Visual Basic with Microsoft Visual Studio 2010

Managing Resources in Your Applications

Simply using types that implement the IDisposable interface is not sufficient to manage resources; you must remember to invoke the Dispose method in your code. There are several approaches you can use to dispose of an object when you no longer require it: You can manually call the Dispose method at an appropriate point in your code, as the following code example shows.
Dim lfw As New LogFileWriter(...) ... ' Use the LogFileWriter object ... lfw.Dispose()

You can use a Try/Finally block and dispose of your object in the Finally block,as the following code example shows.
Dim lfw As LogFileWriter Try lfw = New LogFileWriter (...) ... ' Use the LogFileWriter object ... Finally If Not lfw Is Nothing Then lfw.Dispose() End If End Try

You can use a Using block to encapsulate your disposable object, as the following code example shows.

Managing the Lifetime of Objects and Controlling Resources

9-25

Using lfw As New LogFileWriter (...) ... ' Use the LogFileWriter object ... End Using

Using a Using block (the third option) is the preferred way of guaranteeing that an object is disposed of when you have finished using it. When you add a Using block to your code, the variables that you define in the Using statement are accessible only in that block. A Using block is exception safe, which means that if the code in the block throws an exception, the runtime will still dispose of the objects specified in the Using statement. To define a Using block, you specify the Using keyword, followed by brackets. Inside the brackets, you declare and initialize the variable that you must use in the block. You then add your code, enclosed in braces after the Using statement. The following code example defines a Using block, which declares and initializes a LogFileWriter variable (as shown previously).
Using lfw As New LogFileWriter (...) ... ' Use the LogFileWriter object ... End Using

The above example is functionally equivalent to the following code example.


Sub SubName() Dim lfw As LogFileWriter lfw = New LogFileWriter (...) ... ' Use the LogFileWriter object ... Finally If Not lfw Is Nothing Then lfw.Dispose() End If End Try End Sub Try

These Sub methods encapsulate the lfw variable and ensure that no other objects reference the lfw object. The object goes out of scope as soon as the flow of control leaves the Sub and therefore makes the object eligible for garbage collection. If a Using statement is not appropriate in your code (if the object does not implement the IDisposable interface, for example), using a Try/Finally block is an exception-safe approach to disposing of an object; if the code in the Try block throws an exception, the Finally block will still run calling the Dispose method. You should aim to use a Try/Finally block wherever a using statement cannot be used. Question: If you do not dispose of an object when you have finished with it, will the runtime call the Dispose method automatically?

9-26

Programming in Visual Basic with Microsoft Visual Studio 2010

Demonstration: Using the Dispose Finalize Pattern

Demonstration Steps
1. 2. 3. Start Visual Studio 2010. Open the Dispose Demo solution in the D:\Demofiles\Mod9\Demo2\Starter\Dispose folder. Modify the Employee class to implement the IDisposable interface. The code should follow best practices. Explain to students that although the call to GC.SuppressFinalize in the Dispose method is unnecessary at present, it is still worth including it, in case a finalizer is added to the Employee class later. Your code should resemble the following code example.
Public Class Employee Implements IDisposable Private name As String Public Sub New(ByVal name As String) Me.name = name End Sub Public Sub PaySalary() If Not Me.disposedValue Then Console.WriteLine("Employee {0} paid.", Me.name) Else Throw New ObjectDisposedException( _ "Employee already disposed.") End If End Sub #Region "IDisposable Support" Private disposedValue As Boolean ' To detect redundant calls ' IDisposable Protected Overridable Sub Dispose(ByVal disposing As Boolean) If Not Me.disposedValue Then If disposing Then

Managing the Lifetime of Objects and Controlling Resources

9-27

' TODO: dispose managed state (managed objects). End If ' TODO: free unmanaged resources (unmanaged objects) and ' override Finalize() below. ' TODO: set large fields to null. End If Me.disposedValue = True End Sub ' TODO: override Finalize() only if Dispose(ByVal disposing As ' Boolean) above has code to free unmanaged resources. 'Protected Overrides Sub Finalize() ' ' Do not change this code. ' ' Put cleanup code in Dispose(ByVal disposing As Boolean) ' 'above. ' Dispose(False) ' MyBase.Finalize() 'End Sub ' This code added by Visual Basic to correctly implement the ' disposable pattern. Public Sub Dispose() Implements IDisposable.Dispose ' Do not change this code. ' Put cleanup code in Dispose(ByVal disposing As Boolean) ' above. Dispose(True) GC.SuppressFinalize(Me) End Sub #End Region End Class

4. 5.

Run the application with debugging. When the application pauses, press Enter.

Question: What is the preferred construct for managing resources in an application?

Additional Reading
For more information about the Using statement, see the Using Statement (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211289&clcid=0x409

9-28

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab: Managing the Lifetime of Objects and Controlling Resources

Objectives:
After completing this lab, you will be able to: Implement the IDisposable interface in a type. Ensure that resources associated with an object are reclaimed through a Using statement.

Introduction
In this lab, you will define a type that implements the IDisposable interface and then reference objects of this type through a Using statement to ensure that they are disposed of correctly.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

Managing the Lifetime of Objects and Controlling Resources

9-29

Lab Scenario

The first version of the family of measuring devices produced by Fabrikam, Inc. recorded data to a local circular buffer on the device, implemented by using an array. However, this array has a fixed, finite size. If the user does not retrieve the data from the device sufficiently often, measurements will be overwritten and lost. You have been asked to develop the software to drive an enhanced version of these devices. The new version supports logging to a file and to the buffer in memory. This should prevent data loss.

9-30

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 1: Implementing the IDisposable Interface


Scenario
In this exercise, you will create a new interface called ILoggingMeasuringDevice that extends the IMeasuringDevice interface and adds the following method: GetLoggingFile. This method will return the name of the file that the device logs data to.

You will modify the MeasureDataDevice abstract class and add the following private field: loggingFileName. This field will contain the name of the file that the device will log data to.

You will implement the GetLoggingFile method in the abstract class to return the name of the file in the loggingFileName field. In the StartCollecting method of the abstract class, you will add code to open the file and record measurements as they are written to the buffer. In the StopCollecting method, you will add code to close the file. You will then extend the abstract class to implement the IDisposable interface. In the Dispose method, you will add code to ensure that the file is closed correctly and its contents are flushed to disk when the object is destroyed. You will modify the constructor for the MeasureMassDevice class that inherits from the MeasureDataDevice abstract class and include a parameter that enables an application to specify a file name. The constructor will use this file name to populate the loggingFileName field. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. Open the starter project. Create the ILoggingMeasuringDevice interface. Modify the MeasureDataDevice class to implement the ILoggingMeasuringDevice interface. Modify the MeasureDataDevice class to implement the IDisposable interface. Modify the MeasureMassDevice class to use logging.

Task 1: Open the starter project.


Open Microsoft Visual Studio 2010. Import the code snippets from the D:\Labfiles\Lab09\Snippets folder. Open the Module9 solution in the D:\Labfiles\Lab09\Ex1\Starter folder.

Task 2: Create the ILoggingMeasuringDevice interface.


In this task, you will develop the ILoggingMeasuringDevice interface. You will develop this new interface, which inherits from the existing IMeasuringDevice interface, rather than edit the existing interface. This will ensure compatibility with existing code. Review the Task List. Open the ILoggingMeasuringDevice.vb file. Remove the TODO comment and declare an interface named ILoggingMeasuringDevice. The interface must be accessible from code in different assemblies. Modify the interface to inherit from the IMeasuringDevice interface.

Managing the Lifetime of Objects and Controlling Resources

9-31

Add a method named GetLoggingFile that returns a String value to the interface. The method should take no parameters. The purpose of this method is to return the file name of the logging file used by the device. Add an XML comment that summarizes the purpose of the method. Build the solution and correct any errors.

Task 3: Modify the MeasureDataDevice class to implement the


ILoggingMeasuringDevice interface.
In this task, you will modify the existing MeasureDataDevice class to implement the ILoggingMeasuringDevice interface. You will add code to enable logging and modify existing methods to use the logging functionality. Open the MeasureDataDevice.vb file. Locate and remove the TODO: Modify this class to implement the ILoggingMeasuringDevice interface instead of the IMeasuringDevice interface. comment above the MeasureDataDevice class declaration. Modify the MeasureDataDevice class to implement the ILoggingMeasuringDevice interface, instead of the IMeasuringDevice interface. In the Task List, locate and double-click the comment TODO: Add fields necessary to support logging. This item opens the relevant line in the MeasureDataDevice.vb file. Remove the TODO comment and add a String field named loggingFileName. This field must be accessible to classes that inherit from this class. This field will store the file name and path for the log file. Add a TextWriter field named loggingFileWriter. This field should only be accessible to code in this class. You will use this object to write to a file. In the Task List, locate and double-click the comment TODO: Add methods to implement the ILoggingMeasuringDevice interface. This item opens the relevant line in the MeasureDataDevice.vb file. Remove the TODO comment and add the GetLoggingFile method defined in the ILoggingMeasuringDevice interface. The method should take no parameters and return the value in the loggingFileName field. In the Task List, locate and double-click the comment TODO: Add code to open a logging file and write an initial entry. This item opens the relevant line in the MeasureDataDevice.vb file. Remove the TODO comment and add the following code to instantiate the loggingFileWriter field. You can either type this code manually, or you can use the Mod9InstantiateLoggingFileWriter code snippet.
' New code to check the logging file is not already open. ' If it is already open then write a log message. ' If not, open the logging file. If loggingFileWriter Is Nothing Then ' Check if the logging file exists - if not create it. If Not File.Exists(loggingFileName) Then loggingFileWriter = File.CreateText(loggingFileName) loggingFileWriter.WriteLine("Log file status checked - Created") loggingFileWriter.WriteLine("Collecting Started") Else loggingFileWriter = New StreamWriter(loggingFileName) loggingFileWriter.WriteLine("Log file status checked - Opened") loggingFileWriter.WriteLine("Collecting Started") End If Else loggingFileWriter.WriteLine("Log file status checked - Already open") loggingFileWriter.WriteLine("Collecting Started")

9-32

Programming in Visual Basic with Microsoft Visual Studio 2010

End If

The code checks whether the loggingFileWriter object has already been instantiated. If it has not, the code instantiates it by checking whether the file specified by the loggingFileName field already exists. If the file exists, the code opens the file; if it does not exist, the code creates a new file. In the Task List, locate and double-click the comment TODO: Add code to write a message to the log file. This item opens the relevant line in the MeasureDataDevice.vb file. Remove the TODO comment and add code to write a message to the log file. Your code should check that the loggingFileWriter object is instantiated before writing the message. In the Task List, locate and double-click the comment TODO: Add code to log each time a measurement is taken. This item opens the relevant line in the MeasureDataDevice.vb file. Remove the TODO comment and add code to write a message to the log file. Your code should check that the loggingFileWriter object is instantiated before writing the message. Build the solution and correct any errors.

Task 4: Modify the MeasureDataDevice class to implement the IDisposable interface.


In this task, you will modify the existing MeasureDataDevice class to implement the IDisposable interface. You will add code to ensure that the TextWriter object that writes messages to the log file is properly closed when an instance of the MeasureDataDevice class is disposed of. At the top of the MeasureDataDevice class, remove the comment TODO: Modify this class to implement the IDisposable interface, and then modify the MeasureDataDevice class to implement the IDisposable interface in addition to the ILoggingMeasuringDevice interface. Use the Implement Interface Wizard to generate method stubs for each of the methods in the IDisposable interface. Move to the end of the MeasureDataDevice class. In the Dispose method overload added, Protected Overridable Sub Dispose(ByVal disposing As Boolean), check that the disposing parameter is set to True. If it is, check if the loggingFileWriter object is not null, write the message Object disposed to the logging file, flush the contents of the loggingFileWriter object, close it, and set the loggingFileWriter variable to Nothing. You can either type this code manually, or you can use the Mod9Disposing code snippet. Locate the Dispose overload, which takes no parameters, and notice the default method body inserted by Visual Basic, to correctly implement the disposable pattern.
Public Sub Dispose() Implements IDisposable.Dispose ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above. Dispose(True) GC.SuppressFinalize(Me) End Sub

Build the solution and correct any errors.

Task 5: Modify the MeasureMassDevice class to use logging.


In this task, you will modify the existing MeasureMassDevice class to set the loggingFileName field when the class is instantiated. Open the MeasureMassDevice.vb file. In the MeasureMassDevice class, remove the comment TODO: Modify the constructor to set the log filename based on a string parameter, and then modify the constructor to take a String parameter called logFileName. In the body of the constructor, set the loggingFileName field to the

Managing the Lifetime of Objects and Controlling Resources

9-33

logFileName parameter. You should also update the XML comments for the constructor to describe the new parameter. Build the solution and correct any errors.

9-34

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 2: Managing Resources Used by an Object


Scenario
In this exercise, you will use a test harness application to test the disposal functionality that you added to the classes in the previous exercise. The test harness is a simple Windows Presentation Foundation (WPF) application. Note that this application does not include exception handling or necessarily follow best practices for implementing a graphical user interface. The main tasks for this exercise are as follows: 1. 2. 3. 4. Open the starter project. Test the logging functionality by using the test harness. Modify the test harness to dispose of objects correctly. Verify that the object is disposed of correctly.

Task 1: Open the starter project.


Open the Module9 solution from the D:\Labfiles\Lab09\Ex2\Starter folder. This solution contains the completed code from Exercise 1 and skeleton code for Exercise 2.

Task 2: Test the logging functionality by using the test harness.


Run the Exercise2TestHarness application. Click Get Measurements. This action causes the application to pause for 20 seconds while some measurements data is generated and then display this data. This pause is necessary because the application waits for measurement data from the emulated device. Note that the measurement data is logged to the D:\Labfiles\Lab09\LogFile.txt file by default. After the application populates the text boxes with data from the emulated device, close the Exercise 2 window. Using Notepad, open the LogFile.txt file in the D:\Labfiles\Lab09 folder. Review the contents of the LogFile.txt file. The file is empty. Although the application has retrieved values from the emulated device and written them to the log file, the TextWriter object caches data in memory and writes to the underlying file system when it is either flushed or closed. When you closed the application, you disposed of the TextWriter object without flushing its in-memory cache to the log file, which is why the file is empty. Close Notepad. Run the Exercise2 Test Harness application in Debug mode, click Get Measurements, and then wait for the data to appear. After the application populates the text boxes with data from the emulated device, click Get Measurements again. The application will throw an unhandled IOException exception. The exception is thrown because each time you click Get Measurements, you create a new instance of the MeasureMassDevice class. Each instance of the MeasureMassDevice class creates its own instance of the TextWriter class to log measurements. The test harness does not currently dispose of the MeasureMassDevice objects after the code run by the Get Measurements button completes. This means that the object is not closed and therefore retains its lock on the log file. When you attempt to create a second instance of the MeasureMassDevice class that uses the same log file, this instance cannot access the file because it is still in use by the first instance.

Managing the Lifetime of Objects and Controlling Resources

9-35

Stop the Exercise 2 application.

Task 3: Modify the test harness to dispose of objects correctly.


In Visual Studio, open the MainWindow.xaml.vb file in the Exercise2 Test Harness project. In the GetMeasurementsButton_Click method, remove the TODO: Modify this method comment in the MainWindow.xaml.vb file. Modify the GetMeasurementsButton_Click method to ensure that the device field is disposed of when the method completes by using a Using block. Build the solution and correct any errors.

Task 4: Verify that the object is disposed of correctly.


Run the Exercise2TestHarness application. Click Get Measurements, and then wait until the data appears. After the application populates the text boxes with data from the emulated device, close the Exercise 2 window. Open the log file in Notepad. Review the contents of the log file. The file now contains the values displayed on the form and status messages generated when the file is opened and closed. When the code for the Get Measurements button completes, it disposes of the MeasureMassDevice instance, which forces the TextWriter object to flush its in-memory cache to the file, and then closes the TextWriter. Close Notepad. Run the Exercise2TestHarness application again. Click Get Measurements, and then wait for the data to appear. 1. In the Exercise 2 window, click Get Measurements again. The application will pause for another 20 seconds.

This time, the application does not throw an exception. This is because the resources are properly disposed of each time you click Get Measurements. When you close the TextWriter object, you release the lock on the file, and a new instance of the TextWriter class can now use the same log file without throwing an exception. Open the log file in Notepad. Review the contents of the log file. The file contains the most recent values displayed on the form. Close Notepad. Close the Exercise 2 window. Close Visual Studio.

9-36

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Review

Review Questions
1. 2. Why do you implement the Dispose Finalize pattern? What is the syntax of the Using statement?

Managing the Lifetime of Objects and Controlling Resources

9-37

Module Review and Takeaways

Review Questions
1. 2. 3. What methods are defined in the IDisposable interface? Where would an instance of the System.String class be stored: on the heap or on the stack? Should you implement the Dispose pattern for every class you develop?

Best Practices Related to Disposing of Unmanaged Objects


Supplement or modify the following best practices for your own work situations: You should implement the dispose pattern whenever your code uses unmanaged resources. You should use a Using statement to ensure disposal of objects wherever possible. Where a Using statement is not appropriate, you should ensure exception-safe disposal of objects by using a Try/Finally block; you should release resources in the Finally block.

9-38

Programming in Visual Basic with Microsoft Visual Studio 2010

Encapsulating Data and Defining Overloaded Operators

10-1

Module 10
Encapsulating Data and Defining Overloaded Operators
Contents:
Lesson 1: Creating and Using Properties Lab A: Creating and Using Properties Lesson 2: Creating and Using Indexers Lab B: Creating and Using Indexers Lesson 3: Overloading Operators Lab C: Overloading Operators 10-3 10-19 10-28 10-35 10-43 10-57

10-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

Nearly every application you develop will require you to develop at least one type to represent some entity. Types typically expose methods and data. A simple approach to exposing data is to make the fields used by your class public; however, this is often bad practiceor at least is not the most secure, efficient, or natural technique. For example, providing an array-like syntax may be a better approach when accessing data in a class that stores a collection of data. Similarly, if a class exposes a member that should have only read-only access, exposing a field publicly provides both read and write access. This module will introduce you to properties and default properties. These are elements of Microsoft Visual Basic that enable you to encapsulate data and expose data appropriately and efficiently. Another syntax you will commonly use is that associated with operators. For example, it is intuitive to write 2 + 3 and expect that the result will be 5. Similarly, you will probably expect "Hello" &"World" to return the concatenated string "Hello World". Many operators have well-defined behavior for the built-in Visual Basic types, but you can also define operators for your own types. This module describes how to implement operators for your types by using overloading.

Objectives:
After completing this module, you will be able to: Explain how properties work and use them to encapsulate data. Describe how to use default properties to provide access to data through an array-like syntax. Describe how to use operator overloading to define operators for your own types.

Encapsulating Data and Defining Overloaded Operators

10-3

Lesson 1

Creating and Using Properties

You can use properties to provide controlled access to the data in a type. This lesson introduces you to properties and shows you how to define them in your types. It also explains why you should use this approach to encapsulate data.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of properties. Implement properties. Explain auto-implemented properties. Instantiate an object by using properties. Define properties in an interface. Describe the best practices relating to properties.

10-4

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is a Property?

A property is a cross between a field and a method. You use field-like syntax to access a property. However, the behavior of a property is more like a method. A property can contain two elements: A Get procedure, which an application can use to read the property value. A Set procedure, which an application can use to change the property value.

Note: Get and Set procedures are often referred to as Get and Set accessors. Properties are a common way of encapsulating data exposed by your class. Normally, a property is mapped to a private field in your type. The field stores the data, and the Get and Set procedures of the property provide a mechanism for accessing that field. You are not obliged to provide both a Get and a Set procedure, so properties have the advantage that you can control whether to make a property readonly, write-only, or make the property readable and writable, which you cannot do by exposing a field. Another advantage of using a property is the ability to validate data. If you expose a field in your type, any other type can read or write to that field. As long as the data is of the right type, any value can be assigned to that field. This is not always logical; sometimes you may need to restrict the range of acceptable values for a field in your type. With a property, you can add logic to the Set procedure to check that a value falls in the expected range before updating the private field. Although properties normally correspond to private fields, there is no requirement for them to do so. The Get procedure of a property can return a calculated value, a constant value, or perform any other operation applicable to your application. Properties will often include additional logic; for example, if you update a file name by using a property, the property may check whether the file is currently in use, and if necessary, rename the file or open a new file according to the requirements of the application.

Encapsulating Data and Defining Overloaded Operators

10-5

Question: How does the behavior of a method differ from a property?

Additional Reading
For more information about properties, see the Property Procedures (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211302&clcid=0x409

10-6

Programming in Visual Basic with Microsoft Visual Studio 2010

Defining a Property

A property has a type and a name, in much the same way as a field. However, the logic for a property is defined by the Get and Set procedures. The Get procedure, like a method, can include any code; however, it must return an object of the type specified by the property, or throw an exception. The Set procedure does not have to perform any functionalthough normally, you update a private field to perform some operation based on the value passed to the property. You do not specify a parameter for the Set procedure; a Set procedure always takes one parameter of the type exposed by the property. You can access the object passed as a parameter to a Set procedure by using the value keyword. The following code example shows how to define a simple property that provides access to a private field. The Get keyword introduces a code block that defines the code that runs when an application reads the property. The Set keyword defines the code block for the logic that runs when an application assigns a value to the property.
Private myStr As String Public Property MyString As String Get Return Me.myStr End Get Set(ByVal value As String) Me.myStr = value End Set End Property

To define a read-only property, you add the ReadOnly keyword to the property declaration.
Public ReadOnly Property MyString As String ... End Property

Encapsulating Data and Defining Overloaded Operators

10-7

To define a write-only property, you add the WriteOnly keyword to the property declaration.
Public WriteOnly Property MyString As String ... End Property

Defining Property Accessibility


When you define a property, you specify the access modifier for that property. The access modifier that you specify for a property is inherited by the Get and Set procedures. You can override the access modifier for either the Get or Set procedure; however, you cannot make a procedure more accessible than the containing property. For example, you cannot make the Get procedure public if the property is Private. The following code example shows how to modify the accessibility level at the procedure level.
Public Property MyString As String Get Return Me.myStr End Get Private Set(ByVal value As String) Me.myStr = value End Set End Property

Using a Property in a Consuming Class


You use a property in a consuming class by using the dot notation in the same way as you access a public field. The following code example shows how to access the MyString property from the previous code example. Internally, the Visual Basic compiler converts all attempts to read the property into calls to the Get procedure and changes all attempts to write the property into calls to the Set procedure.
Dim theClass As New MyObject ' Setting the string calls the Set procedure theClass.MyString = "Property Set." ' Getting the string calls the Get procedure Console.WriteLine(theClass.MyString)

Note: You can define shared properties, but they can only access shared data. Question: How can you enable write access to a property to other types in the same assembly, but read access to a property from a class in any assembly?

10-8

Programming in Visual Basic with Microsoft Visual Studio 2010

Auto-Implemented Properties

When you develop a new type, you may include a data field that you want to expose to applications. If no additional processing or validation is required on that field, it may be tempting to simply expose the field publicly, instead of adding a property to provide access to that field. In this case, exposing a field may not seem like a problem. However, remember that you cannot add code to prevent invalid values in a field, but you can in a property. Whether you need to add validation or other logic to a property when you originally develop a type does not mean that will always be the case. The requirements of your type may change over the lifetime of the application. From a developer's perspective, using a property is exactly the same as using a field; however, this is not true to the compiler. The compiler converts code that accesses a property into a method call to the Get procedure, and it similarly converts writing to a property to a method call to the Set procedure. This has implications for existing applications if you must convert a field to a property at a later date; any application that used the type with the value exposed as a field must be recompiled with the data exposed through a property. If this type is in an assembly used by a number of applications, you may need to rebuild and redeploy a lot of installations. You can avoid this extra work by simply exposing the data through a property when you originally develop the type. Any future changes to the type can then be made without the need to recompile applications that consume your type. Where you must expose a field, and are tempted to simply make the field Public rather than writing a property to get and set the field, you can use auto-implemented properties. Auto-implemented properties provide a simple inline syntax that converts a field to a property. To use auto-implemented properties, you only write the first line of the property declaration.
Public Property Name As String

When you use an auto-implemented property, the compiler creates a private field and automatically generates code to read and write this field, as the following code example shows.

Encapsulating Data and Defining Overloaded Operators

10-9

Private fullName As String Public Property Name As String Get Return Me.fullName End Get Set(ByVal value As String) Me.fullName = value End Set End Property

Note: Auto-implemented properties always define both a Get and Set procedure. Autoimplemented properties are intended for use where otherwise you would simply expose a public field. If you require more specific control over the data, you must write the property manually. It does not make any difference to consuming classes if you change from an auto-implemented property to a manual property in a later build of your code; they are completely interchangeable, unlike properties and fields. Question: What is the benefit of using an auto-implemented property, compared to exposing a public field?

10-10

Programming in Visual Basic with Microsoft Visual Studio 2010

Instantiating an Object by Using Object Initializers

You have previously seen how to use a constructor to instantiate an object and initialize its fields. You can declare several constructors, with different signatures, to enable other developers to Set various combinations of fields in your type to appropriate values; however, this approach is problematic if you have more than a small number of fields or several properties of the same type. The following code example shows a simple class with several constructors.
Class Employee Private empName As String Private empDepartment As String ' Initialize both fields Public Sub New(ByVal name As String, ByVal department As String) Me.empName = name Me.empDepartment = department End Sub ' Initialize name only Public Sub New(ByVal name As String) Me.empName = name End Sub ' Initialize department only Public Sub New(ByVal department As String) Me.empDepartment = department End Sub ... End Class

The intention of the constructors is to enable an application to specify a value for the employee name, department name, or both, when it creates a new Employee object. However, this code will not compile because the compiler cannot distinguish between the two constructors that take a single string parameter.

Encapsulating Data and Defining Overloaded Operators

10-11

If you attempt to instantiate an Employee object by using the code shown in the following code example, the compiler does not know which constructor to use.
' Is "Fred" the name of an employee or a department? Dim myEmployee As New Employee("Fred")

You can resolve this problem by using properties to initialize the object when you instantiate it. This syntax is known as an object initializer. With an object initializer, you create a new object by using a constructor, but you specify the values to assign to properties after the constructor has completed by using property name/value assignment pairs separated by commas, and enclosed in curly braces. The curly braces must be prefixed with the keyword, With, and the property names must be prefixed with a dot (.). The following code example shows how to define a class that supports object initializers and how to create an object by using them.
Class Employee ' Default constructor Public Sub New() ... End Sub ' Constructor that sets the grade of an employee. Public Sub New(ByVal grade As Integer) ... End Sub ' Expose Name and Department as auto-implemented properties. Public Property Name As String Public Property Department As String ... End Class ' Instantiating an object and setting a single property. Dim louisa As New Employee() With {.Department = "Technical"} ' Instantiating an object and setting a single property. ' You do not have to add the brackets to use the default constructor. Dim john As New Employee With {.Name = "John"} ' Instantiating an object and setting a multiple properties. ' Separate properties with a comma. Dim mike As New Employee With { .Name = "Mike", .Department = "Technical" } End Class

In the first example, (louisa), the default constructor is used to create the Employee object. After the object is created and the constructor has finished, the value "Technical" is assigned to the Department property. Note that if you use the default constructor, you can omit the brackets (), as the second example (john) and the third example (mike) illustrate. If the Employee class has a nondefault constructor, you can invoke that together with an object initializer, as the following code example shows. This code example uses the constructor that sets the grade of an employee.
Dim antony As New Employee(2) With {.Name = "Antony", .Department = "Management"}

10-12

Programming in Visual Basic with Microsoft Visual Studio 2010

When you use an object initializer, the constructor logic runs first, and then the properties are set to the values specified in the object initializer. This means that if you set a property in a constructor, and then set the same property in the object initializer, the value from the object initializer will overwrite the value set by the constructor. Hint: You should only define constructors that set any required properties to default values. Classes that consume your type can then override those properties in an object initializer. Question: Why is it important to instantiate required properties to default values in the constructor?

Encapsulating Data and Defining Overloaded Operators

10-13

Defining Properties in an Interface

An interface defines a contract that specifies the methods that a class should implement. An interface can also define properties. However, the implementation details of these properties (such as the fields they reference, if any) are the responsibility of the class. To add a property to an interface, you use the same syntax as an auto-implemented property, except that you cannot specify an access modifier. The following code example shows properties added to an interface.
Interface IPerson Property Name As String ReadOnly Property Age As Integer WriteOnly Property DateOfBirth As DateTime End Interface

The following code example shows the IPerson interface implemented.


Class Person Implements IPerson Public Property Name As String Implements IPerson.Name Get Throw New NotImplementedException() End Get Set(ByVal value As String) Throw New NotImplementedException() End Set End Property Public ReadOnly Property Age As Integer _ Implements IPerson.Age Get Throw New NotImplementedException()

10-14

Programming in Visual Basic with Microsoft Visual Studio 2010

End Get End Property Public WriteOnly DateOfBirth As DateTime _ Implements IPerson.DateOfBirth Set(ByVal value As DateTime) Throw New NotImplementedException() End Set End Property End Class

Question: When should you add a property to an interface?

Encapsulating Data and Defining Overloaded Operators

10-15

Best Practices When Defining and Using Properties

Properties provide an excellent framework for exposing data from types you develop; however, if you do not use properties appropriately, you risk introducing bugs or simply exposing properties that enable consuming classes to perform undesirably. You can mitigate the risks by following some best practices.

Using Properties Appropriately


It would be easy to say you should always expose a property for every field in types that you develop; however, this is not necessarily a good practice. You should carefully consider whether exposing a property is appropriate to the types of operations an application can perform on a data item. For example, if you are developing a type to represent a bank account, a field in the class can represent the balance of the account. It may be tempting to provide a property that enables an application to read and write the account balance, but this does not reflect the real-world operations that a bank typically implements; a bank enables you to deposit some money to increase your balance and to take money out (subject to any necessary overdraft constraints) rather than letting you directly set the balance of your account. Consequently, it is more appropriate to provide Deposit and Withdraw methods. When you design types in your application, you should remember to design those types to expose the functionality required for the specific application. You should not expose every field as a property, unless there is a good reason for exposing the field.

Do Not Implement Get Accessors with Side Effects


A Get procedure should simply retrieve a value and return that value to the consuming application. When you implement a Get procedure, retrieving the value should not impact the value or any other data stored by the type. The only exception to this rule is when you write applications that must adhere to security restrictions. In this case, you can add logic to the Get procedure to log access or to further restrict access according to business requirements.

10-16

Programming in Visual Basic with Microsoft Visual Studio 2010

Use Naming Conventions


Because Visual Basic is case-insensitive, when wrapping a field, you should use a name that varies from the field by more than the case of the initial letter. For example, a field called empName can be encapsulated in a property called Name, as shown in the following code example, defined in a class named Employee.
Private empName As Integer Public Property Name As Integer Get Return Me.empName End Get ... End Property

Question: When would you add logic to a Get procedure that performs functionality other than to return the data?

Encapsulating Data and Defining Overloaded Operators

10-17

Demonstration: Using Properties

Demonstration Steps
1. 2. 3. Start Microsoft Visual Studio 2010. Open the UsingPropertiesDemo solution in the D:\Demofiles\Mod10\Demo1\Starter folder. Open the Employee.vb file, and then review the Employee class. Notice the publicly exposed fields and the constructor that sets the Name field based on the parameter, and the Salary and Department fields to default values. Convert the Name field to a property by using auto-implemented properties: Modify the following line of code.
Public Name As String

4.

Change it to the following line of code.


Public Property Name As String

5.

Convert the Department field to a property by using auto-implemented properties. Modify the following line of code.
Public Department As String

Change it to the following line of code.


Public Property Department As String

6.

Convert the Public Salary field to a Private field and rename it salary. Modify the following line of code.
Public Salary As Integer

Change it to the following line of code.


Private empSalary As Integer

10-18

Programming in Visual Basic with Microsoft Visual Studio 2010

7. 8. 9.

Uncomment the commented Salary property, and then explain how it ensures that an employee can never have a negative salary. Open the Module1.vb file, and then review the Employee class. Uncomment all the code up to and including the first occurrence of the following code.
Console.ReadLine()

Notice how the julie object is created by using the constructor, and explain that the properties are subsequently set by using the dot notation. Notice how the james object is created by using named properties. Emphasize that these named properties are set after the constructor is run, so they take precedence over the default values set by the constructor. 10. Uncomment the remaining code in the file. Notice that the code attempts to set James salary to a negative value. Remind students that the property prevented negative values. 11. Run the application without debugging. 12. When the application pauses, highlight that the application has worked as expected, and the two employees details are displayed correctly, and then press Enter. 13. When the application pauses, highlight that the application has worked as expected, and James salary has been set to 0 instead of a negative value, and then press Enter. 14. Close Visual Studio. Question: If you set a property in a constructor, and you use named properties to set the same property when you instantiate the object, which takes precedence: the value from the constructor or the named property?

Encapsulating Data and Defining Overloaded Operators

10-19

Lab A: Creating and Using Properties

Objectives:
After completing this lab, you will be able to: Define properties in an interface. Implement properties in a class. Use properties exposed by a class.

Introduction
In this lab, you will define properties in an interface and then implement these properties in a class. You will also use a test application to verify that the properties behave as expected.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

10-20

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Scenario

You have been asked to enhance the functionality of the software that drives a number of the scientific devices produced by Fabrikam, Inc. The software for the measuring devices developed in the previous labs must be improved and simplified by using properties to provide controlled access to the private data members of the MeasureDataDevice abstract class. In this way, other developers can write software to manipulate the data exposed by these devices in a variety of ways. Consequently, these developers will no longer be restricted by the limited set of access methods that this class currently provides. In this lab, you will modify the IMeasuringDevice interface and add the following properties: UnitsToUse: A read-only property based on the Units enumeration that exposes the unitsToUse field. DataCaptured: A read-only integer array property that exposes the dataCaptured field. MostRecentMeasure: A read-only integer property that exposes the mostRecentMeasure field. LoggingFileName: A read/write string property that exposes the loggingFileName field.

You will leave the existing methods in the IMeasuringDevice interface intact, because the updated software has to support older applications that still use these methods. You will modify the MeasureDataDevice abstract class from the previous lab and implement the properties. The property Set procedure for the LoggingFileName property will close the existing logging file (if it is open), and then open a new file with the specified name. The remaining properties will simply return the value of the underlying field. You will test the new functionality by using the MeasureMassDevice class.

Encapsulating Data and Defining Overloaded Operators

10-21

Exercise 1: Defining Properties in an Interface


Scenario
In this exercise, you will define an interface called IMeasuringDeviceWithProperties with the following public properties: UnitsToUse. This read-only property will return the units used by the emulated device. DataCaptured. This read-only property will return a copy of all of the recent data that the measuring device has captured. MostRecentMeasure. This read-only property will return the most recent measurement taken by the device. LoggingFileName. This read/write property will return and update the name of the logging file used by the device.

The IMeasuringDeviceWithProperties interface will inherit from the IMeasuringDevice interface; classes that implement the new interface will always be required to implement the IMeasuringDevice interface. The main tasks for this exercise are as follows: 1. 2. Open the starter project. Add properties to the IMeasuringDeviceWithProperties interface.

Task 1: Open the starter project.


Open Microsoft Visual Studio 2010. Import the code snippets from the D:\Labfiles\Lab10\Snippets folder. Open the Module10 solution in the D:\Labfiles\Lab10\LabA\Ex1\Starter folder.

Task 2: Add properties to the IMeasuringDeviceWithProperties interface.


Review the Task List. Open the IMeasuringDeviceWithProperties.vb file. Remove the comment TODO: Add properties to the interface. Add a read-only property named UnitsToUse of type Units to the interface. Add a read-only property named DataCaptured of type Integer() to the interface. Add a read-only property named MostRecentMeasure of type Integer to the interface. Add a read/write property named LoggingFileName of type String to the interface. Build the solution and correct any errors.

10-22

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 2: Implementing Properties in a Class


Scenario
In this exercise, you will modify the existing MeasureDataDevice class (which currently implements the IMeasuringDevice interface) to implement the IMeasuringDeviceWithProperties interface. When you implement the LoggingFileName property, you will implement logic in the Set procedure that checks whether the log file is open, and if it is open, closes the file and opens a new log file with the updated name. The main tasks for this exercise are as follows: 1. 2. Open the starter project. Update the MeasureDataDevice class to implement the IMeasuringDeviceWithProperties interface.

Task 1: Open the starter project.


Open the Module10 solution in the D:\Labfiles\Lab10\LabA\Ex2\Starter folder. This solution contains a completed version of the IMeasuringDeviceWithProperties interface.

Task 2: Update the MeasureDataDevice class to implement the


IMeasuringDeviceWithProperties interface.
Review the Task List. Open the MeasureDataDevice.vb file. Remove the comment TODO: Implement the IMeasuringDeviceWithProperties interface. Modify the class declaration to implement the IMeasuringDeviceWithProperties interface, instead of the ILoggingMeasuringDevice interface. The IMeasuringDeviceWithProperties interface inherits from the ILoggingMeasuringDevice interface, so modifying the declaration will not break compatibility with existing applications; the class can still be cast as an instance of the ILoggingMeasuringDevice interface. Rename the following member variables as specified, by using the Visual Basic Rename refactoring method. New Member Name dataDeviceUnitsToUse dataDeviceDataCaptured dataDeviceMostRecentMeasure dataDeviceController dataDeviceMeasurementType dataDeviceLoggingFileName

Current Member Name unitsToUse dataCaptured mostRecentMeasure controller measurementType loggingFileName

Visual Basic is case insensitive and does not support a member variable and a property with the same name, even if they differ by the casing of one or more characters. You therefore need to rename the member variables, before implementing the properties of the IMeasuringDeviceWithProperties interface.

Encapsulating Data and Defining Overloaded Operators

10-23

Remove the comment TODO: Add properties specified by the IMeasuringDeviceWithProperties interface. Use the Implement Interface Wizard to generate method stubs for each of the methods in the IMeasuringDeviceWithProperties interface. Locate the UnitsToUse property Get procedure, and then add code to return the dataDeviceUnitsToUse field or member. Locate the DataCaptured property Get procedure, and then add code to return the dataDeviceDataCaptured field or member. Locate the MostRecentMeasure property Get procedure, and then add code return the dataDeviceMostRecentMeasure field or member. Locate the LoggingFileName property Get procedure, and then add code to return the dataDeviceLoggingFileName field or member. Modify the Set procedure of the LoggingFileName property as shown in the following code.

Note: A code snippet is available, named Mod10LoggingFileNamePropertySetAccessor, which you can use to add this code.
If loggingFileWriter Is Nothing Then ' If the file has not been opened simply update the file name. dataDeviceLoggingFileName = value Else ' If the file has been opened close the current file first, ' then update the file name and open the new file. loggingFileWriter.WriteLine("Log File Changed") loggingFileWriter.WriteLine("New Log File: {0}", value) loggingFileWriter.Close() ' Now update the logging file and open the new file. dataDeviceLoggingFileName = value ' Check if the logging file exists - if not create it. If Not File.Exists(LoggingFileName) Then loggingFileWriter = File.CreateText(LoggingFileName) loggingFileWriter.WriteLine("Log file status checked - Created") loggingFileWriter.WriteLine("Collecting Started") Else loggingFileWriter = New StreamWriter(LoggingFileName) loggingFileWriter.WriteLine("Log file status checked - Opened") loggingFileWriter.WriteLine("Collecting Started") End If loggingFileWriter.WriteLine("Log File Changed Successfully") End If

The Set procedure for the LoggingFileName property checks whether the log file is currently open. If the log file has not been opened, the Set procedure simply updates the local field. However, if the log file has been opened, the procedure closes the current log file and opens a new log file with the new file name in addition to updating the local field. Build the solution and correct any errors.

10-24

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 3: Using Properties Exposed by a Class


Scenario
In this exercise, you will use a test harness application to test the functionality of the MeasureDataDevice class you developed in the previous exercise. The main tasks for this exercise are as follows: 1. 2. 3. Add the test harness to the solution. Update the test harness. Test the properties by using the test harness.

Task 1: Add the test harness to the solution.


The test harness application for this lab is a simple Windows Presentation Foundation (WPF) application that is designed to test the functionality of the MeasureDataDevice class that you have just modified. It does not include any exception handling to ensure that it does not hide any exceptions thrown by the class that you have developed. Open the Module10 solution in the D:\Labfiles\Lab10\LabA\Ex3\Starter folder. Add the test harness to the solution. The test harness is a project named Exercise3TestHarness, located in the D:\Labfiles\Lab10\LabA\Ex3\Starter\Exercise3TestHarness folder. Set the Exercise3TestHarness project as the startup project for the solution.

Task 2: Update the test harness.


Review the Task List. Review the user interface for the test application. The test harness application includes functionality to enable you to test the properties you developed in the previous exercise. The Start Collecting button creates a new instance of the MeasureMassDevice object and starts collecting measurements from the emulated device. The application includes text boxes that display the output from the application. It also includes an Update button to enable you to update the file name of the log file. Finally, the test harness includes a button to stop the collection of measurements from the emulated device and dispose of the object. Open the MainWindow.xaml.vb file.

Note: In the following steps, you will store values in the Text property of TextBox controls in the WPF window. This is a String property. In some of the steps, you may need to call the ToString method to convert the property to a String. Remove the comment TODO: Add code to set the UnitsTextBox to the current units. Locate the following line of code.
UnitsTextBox.Text = ""

Update the code you located in the previous step to set the Text property of the UnitsTextBox object to the UnitsToUse property of the device object. Remove the comment TODO: Add code to set the MostRecentMeasureTextBox to the value from the device.

Encapsulating Data and Defining Overloaded Operators

10-25

Locate the following line of code.


MostRecentMeasureTextBox.Text = ""

Update the code you located in the previous step to set the Text property of the MostRecentMeasureTextBox object to the MostRecentMeasure property of the device object. Remove the comment TODO: Update to use the LoggingFileName property. Locate the following line of code.
LoggingFileNameTextBox.Text = device.GetLoggingFile().Replace(labFolder, "")

Update the code you located in the previous step to set the Text property of the LoggingFileNameTextBox object to the LoggingFileName property of the device object. Your code should call the Replace method of the string class in the same way as the code you are updating. Remove the comment TODO: Update to use the DataCaptured property. Locate the following line of code.
RawDataValuesListBox.ItemsSource = device.GetRawData()

Update the code you located in the previous step to set the ItemsSource property of the RawDataValuesListBox object to the DataCaptured property of the device object. In the UpdateButton_Click method, remove the comment TODO: Add code to update the log file name property of the device and add code to set the LoggingFileName property of the device object to the concatenation of the labFolder field and the Text property of the LoggingFileNameTextBox control. Build the solution and correct any errors.

Task 3: Test the properties by using the test harness.


Start the Exercise3TestHarness application. Click Start Collecting. This action causes the application to pause for 10 seconds while some measurements data is generated and then display this data. This pause is necessary because the application waits for measurement data from the emulated device. Using Windows Explorer, browse to the D:\Labfiles\Lab10\LabA folder, and then verify that the default logging file, LogFile.txt, has been created. Return to the Exercise 3 window. Wait at least a further 10 seconds to ensure that the emulated device has generated some additional values before you perform the following step. a. Change the log file to LogFile2.txt, and then click Update.

The Update button calls the code you added to set the LoggingFileName property of the device; because the device is running, and therefore logging values to the log file, the code will close the current log file and open a new one with the name you specified. Wait at least 10 seconds to ensure that the emulated device has generated some additional values before you perform the following steps. Using Windows Explorer, browse to the D:\Labfiles\Lab10\LabA folder, and then verify that the new logging file, LogFile2.txt, has been created. Return to the Exercise 3 window, and then click Stop Collecting / Dispose Object. Close the Exercise 3 window. Close Visual Studio.

10-26

Programming in Visual Basic with Microsoft Visual Studio 2010

Using Notepad, open the LogFile.txt file in the D:\Labfiles\Lab10\LabA folder. Review the contents of the LogFile.txt file. The file includes the values originally displayed in the test harness in addition to some values that were not displayed. The file then indicates that the log file has changed and gives the name of the new log file. Open the LogFile2.txt file in the D:\Labfiles\Lab10\LabA folder. Review the contents of the LogFile2.txt file. The file indicates that the log file has changed successfully. The file then includes any measurements taken after the log file changed and finally indicates that collection has stopped and the object was disposed of. Close Notepad.

Encapsulating Data and Defining Overloaded Operators

10-27

Lab Review

Review Questions
1. 2. 3. What is the syntax for declaring a property in an interface? What is the significant difference between auto-implemented properties and standard properties? What happens if you attempt to write to a read only or write only auto-implemented property?

10-28

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 2

Creating and Using Default Properties

A property typically provides access to a single item in a type. However, some types are inherently multivalued, such as an array or a collection. Similarly, an item may contain sub-elements that you want to provide easy access to. For example, you can think of a string as a set of characters, and you may need to provide access to the individual characters in a string field through a property. The most natural syntax for accessing elements in a set is to use array-like notation, and you can provide this access by defining default properties. This lesson introduces you to default properties and describes how you can use default properties to encapsulate data in your applications.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of a default property. Implement a default property. Access data in your applications by using a type that exposes a default property. Describe the differences between a default property and an array. Define a default property in an interface.

Encapsulating Data and Defining Overloaded Operators

10-29

What Is a Default Property?

An indexer or default property provides a mechanism for encapsulating a set of values, in the same way that a property encapsulates a single value. You use a default property to access a single value in a set of values, but you use Get and Set procedures to control how the value is retrieved or set, based on a subscript passed as a parameter to the indexer. The Get and Set procedures use property-like syntax. Accessing a default property uses the same syntax as accessing an array. However, with default properties, you have more flexibility. For example, with a default property, you can use a non-integer type as the subscript, instead of an integer normally used to access an array. The following code example shows the use of a simple indexer for a type called CustomerAddressBook. This type provides a default property that enables an application to retrieve the address of a customer by specifying the ID of that customer. The customer ID is held as a string.
Dim addressBook As CustomerAddressBook = ... ' Use a default property to find the address of a customer. Dim customerAddress As Address = addressBook("a2332")

A type can define overloaded default properties that take different types of parameters. For example, the CustomerAddressBook type could also provide a default property that retrieves a customer address based on an integer reference number, as the following code example shows.
' Find the address of the customer with the specified reference. customerAddress = addressBook(99)

In addition to defining default properties that take different parameters, default properties can also return different types; they do not have to return an instance of the type that defines the indexer. Question: When may you want to add a default property to a type?

10-30

Programming in Visual Basic with Microsoft Visual Studio 2010

Creating a Default Property

Writing a default property is a cross between writing a property and using an array. You use syntax reminiscent of properties to specify the type and Get and Set procedures, but the name of the indexer is typically Item or Index. You specify the types and names of parameters by using array-like notation in brackets. Like a property, a default property can also be read-only (it only has a Get procedure) or write-only (it only has a Set procedure). You can access the default property parameters by name in the procedures, and in the Set procedure, you can use the value keyword to access the value passed to the default property. Parameters passed to a default property are only intended to be used to locate the data item to set or get. In the Get procedure, you return the item found at this location, and in the Set procedure, you store the data specified by the value parameter at this location. The following code example shows a simple default property that enables an application to find the address of a customer, given the customer ID, or update the address. The address is stored in a database, accessed through the database variable.
Class AddressBook Default Public Property Item(ByVal customerID As String) _ As Customer Return database.FindCustomer(customerID) End Get Set(ByVal value As Customer) database.UpdateCustomer(customerID, value) End Set End Property ... End Class Get

Encapsulating Data and Defining Overloaded Operators

10-31

Important: Ensure that you incorporate some type of error-handling strategy to handle the chance of client code passing in an invalid index value.

Note: You cannot define shared default properties. Question: What information should you use as parameters for a default property?

Additional Reading
For more information about using default properties, see the How to: Declare and Call a Default Property in Visual Basic page at http://go.microsoft.com/fwlink/?LinkID=211303&clcid=0x409

10-32

Programming in Visual Basic with Microsoft Visual Studio 2010

Comparing Default Properties and Arrays

To use a default property, you use a similar syntax to that of an array; however, there are several important differences between a default property and an array.

Default Property Subscripts


When you use an array, you access members of that array by using a numeric subscript. For example, you can access the fifth element in an array and use syntax similar to myArray(4) (assuming a zero-based index). With arrays, you can only use numeric subscripts. A default property gives you greater flexibility because you can use nonnumeric subscripts.

Overloading an Default property


You cannot overload an array; the implementation is defined by the runtime, and all classes that inherit from your class cannot change the behavior of that array. However, you have complete control over the behavior of a default property, and classes that inherit from your class can override the default property and provide their own implementation. Question: Should you use a default property or an array if you must pass a value to a method by reference?

Encapsulating Data and Defining Overloaded Operators

10-33

Defining an Default property in an Interface

You can specify a default property in an interface. Any class that implements the interface is then required to implement that default property. To specify a default property in an interface, you add the default property, without an access modifier, optionally specifying ReadOnly or WriteOnly. The following code example shows a default property in an interface.
Interface IEmployeeDatabase Default Property Index(ByVal name As String) As Employee End Interface

You can implement a default property in a class that implements the interface, as shown in the following code example.
Public Class EmployeeDatabase Implements IEmployeeDatabase ... Default Public Overloads Property Index(ByVal name As String) _ As Employee Implements IEmployeeDatabase.Index ... Return emp End Get Set(ByVal value As ... End Set End Property End Class Get

10-34

Programming in Visual Basic with Microsoft Visual Studio 2010

Demonstration: Creating and Using a Default Property

Demonstration Steps
1. 2. 3. Start Visual Studio. Open the CreatingAndUsingAnIndexerDemo solution in the D:\Demofiles\Mod10\Demo2\Starter folder. Open the EmployeeDatabase.vb file, and then review the EmployeeDatabase class.

Notice that the class stores an array of Employee objects. Notice the AddToDatabase method, and then explain how it adds Employee objects to the array and increments a pointer to the top of the array. 4. Uncomment the default property that returns an Employee object. Notice how the default property takes a String parameter called Name and iterates through each employee in the array until it finds one with a matching Name property. It then returns that value. If it does not find a match after iterating over the entire array, it throws an IndexOutOfRangeException. Open the Module1.vb file, uncomment the commented code, and then explain how this code uses the default property to retrieve Employee instances by specifying the employee name. Run the application without debugging.

5. 6.

Notice that the application runs as expected, and the details of the two employees retrieved from the database are displayed correctly, and then press Enter. 7. Close Visual Studio.

Question: Can you develop more than one default property with the same set of parameters?

Encapsulating Data and Defining Overloaded Operators

10-35

Lab B: Creating and Using Default Properties

Objectives:
After completing this lab, you will be able to: Implement a default property to provide access to items in a class. Use a default property to query and modify data.

Introduction
In this lab, you will add a default property to a class. You will then use a test application to verify that the default property functions correctly.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

10-36

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Scenario

The software that drives some devices provides access to the control registers that these devices use internally. You have previously seen how to display the data in these registers by converting the integer data held in them into binary strings. You have now been asked to provide read/write access to the individual bits in a register. In this lab, you will define a new structure called ControlRegister that contains the following members: registerData: A private integer field representing the value of the control register. RegisterData: A read/write property that exposes the registerData field. A default property that provides read/write access to the individual bits in the registerData field by using array-like notation. For example, if DeviceRegister is an instance of the ControlRegister structure, the statement DeviceRegister(2) = 1 will set bit 1 of the registerData field to the value 1, and the statement x = DeviceRegister(3) will return the value of bit 2 in the registerData field. The default property must ensure that all the values assigned are either 0 or 1.

In this lab, you will use binary operators to access bits in a control register. You will use the left-shift operator (<<), the right-shift operator (>>), the Not operator, the And operator, and the Or operator. The following code example shows how to use the And operator and the left-shift operator to check whether the fifth bit is 0 or 1 in a control register.
registerData& (1 << index) If registerData = 3 and index = 5: 1 1 << 5 registerData registerData& (1 << 5) : 0 0 0 0 0 0 0 1 : 0 0 1 0 0 0 0 0 : 0 0 0 0 0 0 1 1 : 0 0 0 0 0 0 0 0

Encapsulating Data and Defining Overloaded Operators

10-37

The result is 0 so the bit was 0. If the fifth bit in the register was 1 the result would have been a value other than 0.

10-38

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 1: Implementing a Default Property to Access Bits in a Control Register


Scenario
In this exercise, you will add a default property to a ControlRegister class that represents a control register. The class will store the value of the control register in an integer field, and you will use binary operators to retrieve and update the bits in the register. The main tasks for this exercise are as follows: 1. 2. Open the starter project. Add a default property to the ControlRegister class.

Task 1: Open the starter project.


Open Microsoft Visual Studio 2010. Open the Module10 solution in the D:\Labfiles\Lab10\LabB\Ex1\Starter folder.

Task 2: Add a default property to the ControlRegister class.


Review the Task List. Open the ControlRegister.vb file. Remove the comment TODO: Add an indexer to enable access to individual bits in the control register and add a Public default property, indexer, to the class. The default property should take an Integer named idx as the parameter and return an Integer. Add a Get procedure to the default property. In the Get procedure, add code to determine whether the bit specified by the idx parameter in the regData object is set to 1 or 0, and return the value of this bit. The regData object should be accessed through the RegisterData property.

Hint: Use the logical And operator and the left-shift operator (<<) to determine whether the result of left-shifting the value in the regData object by the value of the idx object is zero or non-zero. If the result is zero, return 0; otherwise, return 1. You can use the following code example to assist you with this step.
Dim isSet As Boolean = (RegisterData And (1 << idx)) <> 0

Add a Set procedure to the default property. In the Set procedure, add code to verify that the parameter specified is either 1 or 0. Throw an ArgumentException exception with the message Argument must be 1 or 0 if it is not one of these values. In the Set procedure, if value is 1, add code to set the bit specified by the idx object in the regData field to 1; otherwise, set this bit to 0.

Hint: Use the expression (1 << idx) to determine which bit in the integer value to set. Build the solution and correct any errors.

Encapsulating Data and Defining Overloaded Operators

10-39

Exercise 2: Using an Indexer Exposed by a Class


Scenario
In this exercise, you will use a test harness to access bits in the ControlRegister class that you implemented in the previous exercise. The main tasks for this exercise are as follows: 1. 2. 3. Add the test harness to the solution. Update the test harness. Test the ControlRegister class by using the test harness.

Task 1: Add the test harness to the solution.


The test harness application for this lab is a simple console application that is designed to test the functionality of the ControlRegister class to which you have added a default property. It does not include any exception handling to ensure that it does not hide any exceptions thrown by the class you have developed. Open the Module10 solution in the D:\Labfiles\Lab10\LabB\Ex2\Starter folder. Import the code snippets from the D:\Labfiles\Lab10\Snippets folder. Add the test harness to the solution. The test harness is a project named Exercise2TestHarness, located in the D:\Labfiles\Lab10\LabB\Ex2\Starter\Exercise2TestHarness folder. Set the Exercise2TestHarnessproject as the startup project for the solution.

Task 2: Update the test harness.


Review the Task List. Open the Module1.vb file. Remove the TODO comment.
' TODO: Add code to test the ControlRegister class and indexer.

Add code to create a new instance of the ControlRegister class named register. Add code to set the RegisterData property of the register object to 8. Add the following code, which writes the current value for the RegisterData property and uses the default property to write the first eight bits of the ControlRegister object to the console.

Note: A code snippet is available, named Mod10WriteRegisterData, which you can use to add this code.
Console.WriteLine("RegisterData: {0}", register.RegisterData) Console.WriteLine("Bit 0: {0}", register(0).ToString()) Console.WriteLine("Bit 1: {0}", register(1).ToString()) Console.WriteLine("Bit 2: {0}", register(2).ToString()) Console.WriteLine("Bit 3: {0}", register(3).ToString()) Console.WriteLine("Bit 4: {0}", register(4).ToString()) Console.WriteLine("Bit 5: {0}", register(5).ToString()) Console.WriteLine("Bit 6: {0}", register(6).ToString()) Console.WriteLine("Bit 7: {0}", register(7).ToString()) Console.WriteLine()

Add a statement to write the message Set Bit 1 to 1to the console.

10-40

Programming in Visual Basic with Microsoft Visual Studio 2010

Add a statement to set the bit at index 1 in the register object to 1. Add code to write a blank line to the console. Add the following code, which writes the current value for the RegisterData property and uses the default property to write the first eight bits of the ControlRegister object to the console.

Note: You can use the Mod10WriteRegisterData code snippet to add this code.
Console.WriteLine("RegisterData: {0}", register.RegisterData) Console.WriteLine("Bit 0: {0}", register(0).ToString()) Console.WriteLine("Bit 1: {0}", register(1).ToString()) Console.WriteLine("Bit 2: {0}", register(2).ToString()) Console.WriteLine("Bit 3: {0}", register(3).ToString()) Console.WriteLine("Bit 4: {0}", register(4).ToString()) Console.WriteLine("Bit 5: {0}", register(5).ToString()) Console.WriteLine("Bit 6: {0}", register(6).ToString()) Console.WriteLine("Bit 7: {0}", register(7).ToString()) Console.WriteLine()

Add a statement to write the message Set Bit 0 to 1 to the console. Add code to set the bit at index 0 in the register object to 1. Add code to write a blank line to the console. Add the following code, which writes the current value for the RegisterData property and uses the default property to write the first eight bits of the ControlRegister object to the console.

Note: You can use the Mod10WriteRegisterData code snippet to add this code.
Console.WriteLine("RegisterData: {0}", register.RegisterData) Console.WriteLine("Bit 0: {0}", register(0).ToString()) Console.WriteLine("Bit 1: {0}", register(1).ToString()) Console.WriteLine("Bit 2: {0}", register(2).ToString()) Console.WriteLine("Bit 3: {0}", register(3).ToString()) Console.WriteLine("Bit 4: {0}", register(4).ToString()) Console.WriteLine("Bit 5: {0}", register(5).ToString()) Console.WriteLine("Bit 6: {0}", register(6).ToString()) Console.WriteLine("Bit 7: {0}", register(7).ToString()) Console.WriteLine()

Build the solution and correct any errors.

Task 3: Test the ControlRegister class by using the test harness.


Start the Exercise2TestHarness application. Verify that the output from the console appears correctly. The output should resemble the following code example.
RegisterData : 8 Bit 0: 0 Bit 1: 0 Bit 2: 0 Bit 3: 1 Bit 4: 0 Bit 5: 0 Bit 6: 0 Bit 7: 0

Encapsulating Data and Defining Overloaded Operators

10-41

Set Bit 1 to 1 RegisterData : 10 Bit 0: 0 Bit 1: 1 Bit 2: 0 Bit 3: 1 Bit 4: 0 Bit 5: 0 Bit 6: 0 Bit 7: 0 Set Bit 0 to 1 RegisterData : 11 Bit 0: 1 Bit 1: 1 Bit 2: 0 Bit 3: 1 Bit 4: 0 Bit 5: 0 Bit 6: 0 Bit 7: 0

Close the C:\Windows\system32\cmd.exe window. Close Visual Studio.

10-42

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Review

Review Questions
1. 2. Can you overload a default property in a child class? What are some of the advantages of using a default property in your class?

Encapsulating Data and Defining Overloaded Operators

10-43

Lesson 3

Overloading Operators

Many of the built-in types defined by Visual Basic provide operators to enable you to perform some common operations on them. For example, Visual Basic defines operators such as +, -, *, and /,which have a well-defined behavior over numeric data. However, you have also seen that the + operator can work on the string type, when its behavior is quite different; the + operator for strings concatenates strings together. This is an example of an overloaded operator. You can implement overloaded operators for your own types. This lesson shows you how to define and implement operator overloading. It also describes some best practices you should follow when you define operators for your types.

Lesson Objectives:
After completing this lesson, you will be able to: Describe how operator overloading works. Define an overloaded operator. Explain the restrictions when overloading operators. Explain the best practices for operator overloading. Describe how to implement and use conversion operators.

10-44

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is Operator Overloading?

Visual Basic includes several operators that enable you to perform common operations on objects. You can use these operators to construct expressions. The exact behavior of each of the operators is dependent on the type of the object on which you perform the operation. An operator is a special method that takes a set of parameters and returns a value. When you invoke an operator, the operands are passed as parameters to this method, and the value returned by the method is used as the result of the operator. When you overload an operator, you provide your own implementation of this method. Visual Basic defines different categories of operators that you can overload: You cannot overload all the operators defined by Visual Basic. The following table summarizes which operators you can and cannot overload. Operators ^, *, /, \, Mod, +, +, +=, -=, *=, \=, /=, &=, ^=, <<=, >>= <<, >> =, <>, <, >, <=, >=, Like +, & And, Not, Or, Xor, IsFalse, IsTrue AndAlso, OrElse Ability to be overloaded These arithmetic operators can be overloaded. These assignment operators can be overloaded. These bit shift operators can be overloaded. These comparison operators can be overloaded. These concatenation operators can be overloaded. These logical/bitwise operators can be overloaded. The conditional logical operators cannot be overloaded, but they are evaluated by using And and IsFalse, or Or and IsTrue, which can be overloaded.

Encapsulating Data and Defining Overloaded Operators

10-45

Operators () CType

Ability to be overloaded The array indexing operator cannot be overloaded, but you can define default properties. The CType conversion function can be overloaded, but you can also define new conversion operators as described later in this module. These operators and expressions cannot be overloaded.

If, Is, IsNot, AddressOf, GetType, Function, Sub, TypeOf...Is

Question: If you overload the + operator in a type, does the compiler automatically generate an equivalent operator?

10-46

Programming in Visual Basic with Microsoft Visual Studio 2010

Overloading an Operator

To define your own operator behavior, you must overload a selected operator. You use method-like syntax with a return type and parameters, but the name of the method is the keyword Operator together with the symbol for the operator that you are overloading. For example, to overload the + operator, you define a method called Operator +. The following code example shows a user-defined structure named Hour that defines a binary + operator to add together two instances of Hour.
Structure Hour Public Sub New(ByVal initialValue As Integer) Me.value = initialValue End Sub Public Shared operator +(ByVal lhs As Hour, ByVal rhs As Hour) As Hour Return New Hour(lhs.value + rhs.value) End Operator ... Private value As Integer End Structure

Notice the following points about the Operator + method: All operators must be Public. All operators must be Shared. Operators are never polymorphic and cannot use the Overridable, MustOverride, MustInherit, Overrides, or NotInheritable modifier.

Tip: When declaring highly stylized functionality (such as operators), it is useful to adopt a naming convention for the parameters. For example, developers often use lhs and rhs (acronyms for left-hand side and right-hand side, respectively,) for binary operators.

Encapsulating Data and Defining Overloaded Operators

10-47

When you use the + operator on two expressions of type Hour, the Visual Basic compiler automatically converts your code to a call to your Operator + method. Take the following code as an example.
Function Example(ByVal a As Hour, ByVal b As Hour) As Hour Return a + b End Function

The Visual Basic compiler converts the previous code into code that resembles the following code example (this is pseudo code and not legal Visual Basic syntax).
Function Example(ByVal a As Hour, ByVal b As Hour) As Hour Return Hour.Operator +(a,b) ' pseudo code End Function

There is one final rule that you must follow when declaring an operator: at least one of the parameters must always be of the containing type. In the preceding Operator + method example for the Hour class, one of the parameters, a or b, must be an Hour object. In this example, both parameters are Hour objects. Operators follow the usual overloading rules, and you can overload an operator as many times as you want in a class, as long as the Visual Basic compiler can distinguish between each overload (the signatures must be unique in the class). For example, you can define an additional implementation of the Operator + method to add an integer (a number of hours) to an Hour objectthe first parameter can be an Hour object and the second parameter can be an integer object. Question: Does the first operand of an overloaded operator have to be the containing type?

10-48

Programming in Visual Basic with Microsoft Visual Studio 2010

Restrictions When Overloading Operators

When you overload an operator, you can completely control how an operation is performed; however, there are some rules that apply to operators that you cannot change: You cannot change the precedence or associativity of an operator. The precedence and associativity are based on the operator symbol (for example, +) and not on the type (for example, Integer) on which the operator symbol is being used. Hence, the expression a + b * c is always the same as a + (b * c), regardless of the types of a, b, and c. You cannot change the multiplicity (the number of operands) of an operator. For example, * (the symbol for multiplication) is a binary operator (has two operands). If you declare a * operator for your own type, it must be a binary operator. You cannot invent new operator symbols. For example, you cannot create a new operator symbol, such as **, for raising one number to the power of another number. If you must perform an operation for which there is no operator, you must create a method instead. You cannot change the meaning of operators when applied to built-in types. For example, the expression 1 + 2 has a predefined meaning, and you cannot override this meaning. In fact, when you define an operator in your type, at least one of the operands for that operation must be the containing type, so you cannot define an operation where all of the operands are built-in types.

In addition, you must implement the comparison operators in pairs. For example, if you overload the > operator, you must also overload the < operator. If you overload the = operator, you must also overload the <> operator.

Note: If you define the = operator and the <> operator in a class, you should also override the Equals and GetHashCode methods inherited from System.Object (or System.ValueType if you are creating a structure). The Equals method should exhibit exactly the same behavior as the = operator. (You should define one in terms of the other.) The GetHashCode method is used by

Encapsulating Data and Defining Overloaded Operators

10-49

other classes in the Microsoft .NET Framework (for example, when you use an object as a key in a hash table). Question: How can you change the multiplicity of an operator?

Additional Reading
For more information about using the Equals method, see the Object.Equals Method (Object) page at http://go.microsoft.com/fwlink/?LinkId=192954

10-50

Programming in Visual Basic with Microsoft Visual Studio 2010

Best Practices When Overloading Operators

When you define overloaded operators for your types, you should adhere to the following best practices where possible: Do not modify the operands. Define symmetric operators. Define only meaningful operators.

Not Modifying Operands


An operator should never change the values of either of its operands. If any of these operands are reference types, using the operator will change the value of the operand in addition to returning a result (the operator causes a side effect). For example, in the following code example, the Salary class provides the + operator to add a Decimal value to the salAmount field in the class and returns the updated Salary object as the result.
Class Salary Private salAmount As Decimal Public Property Amount As Decimal Get Return Me.salAmount End Get ... End Property Public Sub New(ByVal amt As Decimal) Me.salAmount = amt End Sub ... Public Shared operator +(ByVal sal As Salary, _ ByVal number As Decimal) As Salary

Encapsulating Data and Defining Overloaded Operators

10-51

sal.Amount += number Return sal End Operator End Class

This is poor implementation, because the + operator changes the value of the first operand. When the + operator completes, the salAmount field in the first operand has the same value as the result. In the following code example, the value in newSalary.Amount is 109, but the value in oldSalary.Amount is also 109 when it would be expected to have remained at 99 by most users.

Note: If Salary is a structure rather than a class, this side effect will not occur, because the Salary parameter will be passed to the + operator by value, rather than by reference.
Dim oldSalary As New Salary(99) Dim newSalary As Salary = oldSalary + 10 Console.WriteLine("{0} {1}", oldSalary.Amount, newSalary.Amount) Output -----109 109

Instead, you should return a new object that contains the new value, as the following code example shows.
Class Salary Private salAmount As Decimal Public Property Amount As Decimal Get Return Me.salAmount End Get ... End Property Public Sub New(ByVal amt As Decimal) Me.salAmount = amt End Sub ... Public Shared operator +(ByVal sal As Salary, _ ByVal number As Decimal) As Salary Return New Salary(sal.Amount + number) End Operator End Class

Defining Symmetric Operators


When you define a binary operator, you should avoid imposing an order on the operands. If an operator is commutable, the order in which you specify the operands should not make any logical difference. In the Salary class example, the expressions salary + 99 and 99 + salary should have the same result. Remember that when you overload an operator, the first operand is used as the first parameter and the second operand is used as the second parameter. Therefore, to support both forms of addition, the Salary

10-52

Programming in Visual Basic with Microsoft Visual Studio 2010

class must provide the two implementations of the + operator shown in the following code example. The compiler will not automatically add symmetric operators for you.
Public Shared Operator +(ByVal sal As Salary, _ ByVal number As Double) As Salary Return New Salary(sal.Amount + number) End Operator Public Shared Operator +(ByVal number As Double, _ ByVal sal As Salary) As Salary ' Call the first operator avoid code duplication. Return sal + number End Operator

Note that the second implementation of the + operator simply invokes the first by switching the two operands over. This is good practice because it ensures that the logic defining the operation is held in a single operator, and consequently, is easier to maintain.

Defining Only Meaningful Operators


As with properties, you should define operators only where it is natural and meaningful to do so. For example, you should probably not define the + or operators on a class that models a bank account to add or remove funds from the account. This is because banks generally perform many additional checks when they add or remove funds, and it is not a simple addition or subtraction operation. Instead, you should provide Deposit and Withdraw methods that can encapsulate these checks in a more meaningful manner. Question: Why should you always return a new object rather than update one of the operands?

Encapsulating Data and Defining Overloaded Operators

10-53

Implementing and Using Conversion Operators

A conversion operator converts an expression from one type to another. A conversion can be either implicit or explicit. Implicit conversions occur when changing an expression from one type to a more specific type with no loss of precision. This is called a widening conversion. An operator that implements a widening conversion can be invoked automatically by the compiler without requiring any additional intervention by a programmer. For example, in the following code example, the statement that assigns an integer expression to a double variable should always succeed without losing data because the Integer has a smaller scale and precision than the Double type.
Dim i As Integer = 99 Dim d As Double = i ' Safe, widening conversion from int to double

An explicit conversion occurs when changing from a type to a less specific type where there is the risk of data loss. This is called a narrowing conversion. Because of the potential loss of data, narrowing conversions are not performed automatically, but require the programmer to specify a cast or conversion. Assigning a double value to an integer is an example of a narrowing conversion that requires a cast or conversion, as the following code example shows.
Dim d As Double = 99.9 Dim i As Integer = CType(d, Integer) ' Data loss, narrowing conversion from double to int

In this example, the value stored in d is rounded as part of the conversion, so the result stored in i is 100.

Defining Conversion Operators


The syntax for declaring a user-defined conversion operator is similar to that for declaring an overloaded operator. A conversion operator must be Public and must also be Shared. The name of a conversion operator is always CType, and the type of a conversion operator either Widening (if it implements a widening conversion) or Narrowing (if it implements a narrowing conversion).

10-54

Programming in Visual Basic with Microsoft Visual Studio 2010

The following code example shows a conversion operator that allows an Hour object to be implicitly converted to an Integer. This is a safe conversion because all hours have an equivalent integer value.
Structure Hour ... Public Shared Widening Operator CType(ByVal from As Hour) _ As Integer Return from.value End Operator Private value As Integer End Structure

You declare the type you are converting from (Hour) as the single parameter and the type you are converting to (Integer) after the parameter(s). When you declare a conversion operator, you must specify whether it is an implicit or an explicit conversion operator by using the Widening and Narrowing keywords. You can invoke an implicit conversion operator without requiring a cast or conversion, as the following code example shows.
Class Example Public Sub MyOtherMethod(ByVal parameter As Integer) End Sub Public Sub Main() Dim lunch As New Hour(12) Example.MyOtherMethod(lunch) ' implicit conversion End Sub End Class

The following code example shows an explicit conversion operator that converts an Integer object to an Hour object. Notice that the return type is now Hour and the parameter is an Integer. This is a narrowing operation because not all integer values represent valid hours. The conversion operator builds an Hour object by using the remainder after dividing the integer parameter by 24, as the following code example shows.
Structure Hour ... Public Shared Narrowing Operator CType(ByVal from As Integer) _ As Hour Return New Hour(from) End Operator Public Sub New(ByVal hr As Integer) Me.value = hr Mod 24 End Sub Private value As Integer End Structure

When should you declare a conversion operator as narrowing or widening? If a conversion is always safe, does not run the risk of losing information, and cannot throw an exception, it can be defined as an implicit conversion. Otherwise, it should be declared as a widening conversion.

Encapsulating Data and Defining Overloaded Operators

10-55

Symmetric Operators and Conversions


Conversion operators provide you with an alternative way to resolve the problem of providing symmetric operators. For example, suppose you define the + operator to enable you to add Hour objects to Hour objects, and Hour objects to Integer values. Instead of providing three versions of the + operator (Hour + Hour, Hour + Integer, and Integer + Hour) for the Hour structure, you can provide a single version of Operator + that takes two Hour parameters and an implicit Integer to Hour conversion operator, as the following code example shows.
Structure Hour Public Sub New(ByVal hr As Integer) Me.value = hr Mod 24 End Sub PublicSharedOperator +(ByVal lhs As Hour, ByVal rhs As Hour) _ As Hour Return New Hour(lhs.value + rhs.value) End Operator Public Shared Narrowing Operator CType(ByVal from As Integer) _ As Hour Return New Hour(from) End Operator ... Private value As Integer End Structure

If you add an Hour object to an Integer object (in either order), the Visual Basic compiler automatically converts the Integer object to an Hour object and then calls the + operator with two Hour arguments. Question: When should you use an explicit conversion?

10-56

Programming in Visual Basic with Microsoft Visual Studio 2010

Demonstration: Overloading an Operator

Demonstration Steps
1. 2. 3. Start Visual Studio. Open the OverloadingAnOperator solution in the D:\Demofiles\Mod10\Demo3\Starter folder. Open the EmployeeDatabase.vb file, and then review the EmployeeDatabase class.

Notice that the class stores an array of Employee objects and is the same as in the previous demonstration. 4. Uncomment the + operator that returns an EmployeeDatabase object. Notice how the + operator takes an EmployeeDatabase object and an Employee object as parameters, adds the Employee object to the database, and then returns a reference to the database. Open the Module1.vb file. In the Module1.vb file, uncomment the commented code.

5. 6.

Notice how this code adds several Employee objects to the database by using both the + syntax and the += syntax. 7. 8. Run the application without debugging. Close Visual Studio.

Question: When can you use the += syntax to abbreviate an addition operation?

Encapsulating Data and Defining Overloaded Operators

10-57

Lab C: Overloading Operators

Objectives:
After completing this lab, you will be able to: Define a new type that models a matrix. Implement operators for the matrix type. Use operators defined by the matrix type.

Introduction
In this lab, you will create a new type that models square matrices. You will implement the addition, subtraction, and multiplication operators for this type and test that these operators function correctly.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

10-58

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Scenario

Some of the engineering devices produced by Fabrikam, Inc. must perform calculations that involve matrices. You have been asked to implement a new, reusable type that can perform simple matrix operations. In this lab, you will create a new type called Matrix. This type will implement a simple nn square matrix. The value of n will be specified in the constructor, and the data for the matrix will be held in a twodimensional array. The Matrix type will provide read/write access to the data in the array through an array property. You will implement the following operators for the Matrix type: The *operator will perform matrix multiplication. It will return a new matrix that is the product of multiplying with another matrix provided as an argument. The + operator will perform matrix addition. It will return a new matrix that is the result of adding to another matrix provided as an argument. The - operator will perform matrix subtraction. It will return a new matrix that is the result of subtracting another matrix provided as an argument.

All operators will perform error-checking to ensure that the matrices are compatible. To add matrices, you add each element in one matrix to the corresponding element in the other. To add two matrices, y and z, you calculate each element x(a, b) in the result matrix by adding element y(a, b) to z(a, b). Subtracting matrices is similar; for each element x(a, b) in the result matrix, calculate y(a, b) z(a, b). To multiply matrices, you calculate the sum of the products of the values in each row in one matrix and the values in each column in the other. To calculate each element x(a, b) in the result matrix, you must calculate the sum of the products of every value in row a in the first matrix with every value in column b in the second matrix. For example, to calculate the value placed at x(3,2) in the result matrix, you calculate

Encapsulating Data and Defining Overloaded Operators

10-59

the sum of the products of every value in row 3 in the first matrix with every value in column 2 in the second matrix.

10-60

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 1: Defining the Matrix and MatrixNotCompatibleException Types


Scenario
In this exercise, you will define a Matrix class to represent a matrix. You will add an indexer to the class to enable access to individual data items in the matrix, and you will override the ToString method to return a formatted string that represents the matrix. You will also define a MatrixNotCompatibleException exception class. The MatrixNotCompatibleException class will be used when an operator is applied to two matrices that are incompatible; for example, because they are not the same size. The MatrixNotCompatibleException class will include fields exposed as read-only properties to reference the matrices on which the operation was performed. The fields will be set by using a constructor. The main tasks for this exercise are as follows: 1. 2. 3. Open the starter project. Create a Matrix class. Create a MatrixNotCompatibleException exception class.

Task 1: Open the starter project.


Open Microsoft Visual Studio 2010. Open the Module10 solution in the D:\Labfiles\Lab10\LabC\Ex1\Starter folder. Import the code snippets from the D:\Labfiles\Lab10\Snippets folder.

Task 2: Create a Matrix class.


Review the Task List. Open the Matrix.vb file. Remove the comment TODO: Add the Matrix class, and then add a Public Matrix class to the MatrixOperators namespace. Add a two-dimensional array of integers named data to the Matrix class. Add a Public constructor to the Matrix class. The constructor should take a single integer parameter named size and initialize the data array to a square array by using the value passed to the constructor as the size of each dimension of the array. After the constructor, add the following code to add a default property or an indexer to the class. You can either type this code manually, or you can use the Mod10MatrixClassIndexer code snippet.
Default Public Property Index(ByVal rowIndex As Integer, ByVal columnIndex As Integer) As Integer Get If rowIndex > data.GetUpperBound(0) OrElse columnIndex > data.GetUpperBound(0) Then Throw New IndexOutOfRangeException() Else Return data(rowIndex, columnIndex) End If End Get Set(ByVal value As Integer) If rowIndex >data.GetUpperBound(0) OrElse columnIndex > data.GetUpperBound(0) Then Throw New IndexOutOfRangeException() Else data(rowIndex, columnIndex) = value End If End Set

Encapsulating Data and Defining Overloaded Operators

10-61

End Property

The indexer takes two parameters, one that indicates the row, and another that indicates the column. The indexer checks that the values are in range for the current matrix (that they are not bigger than the matrix) and then returns the value of the indexed item from the data array. After the indexer, add the following code to override the ToString method of the Matrix class. You can either type this code manually, or you can use the Mod10MatrixClassToStringMethod code snippet.

Public Overrides Function ToString() As String Dim builder As New StringBuilder() ' Iterate over every row in the matrix. For x As Integer = 0 To data.GetLength(0) - 1 ' Iterate over every column in the matrix. For y As Integer = 0 To data.GetLength(1) - 1 builder.AppendFormat("{0}" & vbTab, data(x, y)) Next builder.Append(Environment.NewLine) Next Return builder.ToString() End Function

Build the solution and correct any errors.

Task 3: Create a MatrixNotCompatibleException exception class.


Review the Task List. If it is not already open, open the Matrix.vb file. Remove the comment TODO: Add the MatrixNotCompatibleException exception class, and then add a Public MatrixNotCompatibleException class to the MatrixOperators namespace. Modify the MatrixNotCompatibleException class to inherit from the Exception class. Add a field of type Matrix named first to the MatrixNotCompatibleException class and instantiate it to Nothing. Add a field of type Matrix named second to the MatrixNotCompatibleException class and instantiate it to Nothing. Add a read-only property of type Matrix named FirstMatrix to the MatrixNotCompatibleException class, and then add a Get procedure that returns the first field. Add a read-only property of type Matrix named SecondMatrix to the MatrixNotCompatibleException class, and then add a Get procedure that returns the second field. Add the following constructors to the MatrixNotCompatibleException class. You can either type this code manually, or you can use the Mod10MatrixNotCompatibleExceptionClassConstructors code snippet.
Public Sub New() MyBase.New() End Sub Public Sub New(ByVal message As String) MyBase.New(message) End Sub Public Sub New(ByVal message As String, ByVal innerException As Exception) MyBase.New(message, innerException)

10-62

Programming in Visual Basic with Microsoft Visual Studio 2010

End Sub Public Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext) MyBase.New(info, context) End Sub

Add a constructor to the MatrixNotCompatibleException class. The constructor should take two Matrix objects and a String object as parameters. The constructor should use the String object to call the base constructor and instantiate the first and second fields by using the Matrix parameters. Build the solution and correct any errors.

Encapsulating Data and Defining Overloaded Operators

10-63

Exercise 2: Implementing Operators for the Matrix Type


Scenario
In this exercise, you will add addition, subtraction, and multiplication operators to the Matrix class. The operators you add in this exercise will operate only when the two operands are matrices of the same size. You will ensure that the operands are the same sizeif they are not, you will throw a MatrixNotCompatibleException exception. The main tasks for this exercise are as follows: 1. 2. 3. 4. Open the starter project. Add an addition operator to the Matrix class. Add a subtraction operator to the Matrix class. Add a multiplication operator to the Matrix class.

Task 1: Open the starter project.


Open the Module10 solution in the D:\Labfiles\Lab10\LabC\Ex2\Starter folder.

Task 2: Add an addition operator to the Matrix class.


Review the Task List. Open the Matrix.vb file. Replace the comment TODO Add an addition operator to the Matrix class with an overload of the + operator that takes two Matrix objects as parameters and returns an instance of the Matrix class. Add code to the + operator to check that each of the matrices are the same size (the Matrix class only supports square matrices, so you only need to check one dimension of the matrix). If they are not the same size, throw a new MatrixNotCompatibleException exception, by using the matrices and the message Matrices not the same size as parameters. If both matrices are the same size, add code that creates a new instance of the Matrix class named newMatrix and initialize it to a matrix with the same size as either of the source matrices. Add code to iterate over every item in the first matrix. For each item in the first matrix, calculate the sum of this item and the corresponding item in the second matrix, and store the result in the corresponding position in the newMatrix matrix. You can either type this code manually, or you can use the Mod10IterateMatrixRowsOperatorPlus code snippet.

Hint: Use a For loop to iterate over the rows in the first matrix and a nested For loop to iterate over the columns in each row. After the code that calculates the values for the newMatrix object, add a statement that returns the newMatrix object as the result of the + operator. Build the solution and correct any errors.

Task 3: Add a subtraction operator to the Matrix class.


Review the Task List. If it is not already open, open the Matrix.vb file.

10-64

Programming in Visual Basic with Microsoft Visual Studio 2010

Replace the comment TODO Add a subtraction operator to the Matrix class with an overload of the - operator that takes two Matrix objects as parameters and returns an instance of the Matrix class. Add code to the - operator to check that each of the matrices are the same size (the Matrix class only supports square matrices, so you only need to check one dimension of the matrix). If they are not the same size, throw a new MatrixNotCompatibleException exception, by using the matrices and the message Matrices not the same size as parameters. If both matrices are the same size, add code that creates a new instance of the Matrix class named newMatrix and initialize it to a matrix with the same size as either of the source matrices. Add code to iterate over every item in the first matrix. For each item in the first matrix, calculate the difference between this item and the corresponding item in the second matrix, and store the result in the corresponding position in the newMatrix matrix. You can either type this code manually, or you can use the Mod10IterateMatrixRowsOperatorMinus code snippet. After the code that calculates the values for the newMatrix object, add a statement that returns the newMatrix object as the result of the - operator. Build the solution and correct any errors.

Task 4: Add a multiplication operator to the Matrix class.


Review the Task List. If it is not already open, open the Matrix.vb file. Replace the comment TODO Add a multiplication operator to the Matrix class with an overload of the * operator that takes two Matrix objects as parameters and returns an instance of the Matrix class. Add code to the * operator to check that each of the matrices are the same size (the Matrix class only supports square matrices, so you only need to check one dimension of the matrix). If they are not the same size, throw a new MatrixNotCompatibleException exception, by using the matrices and the message "Matrices not the same size" as parameters. Add code to the conditional block that creates a new instance of the Matrix class named newMatrix and initialize it to a matrix with the same size as the source matrices. Add code to iterate over every item in the first matrix and calculate the product of the two matrices, storing the result in the newMatrix matrix. Remember that to calculate each element xa,b in newMatrix, you must calculate the sum of the products of every value in row a in the first matrix with every value in column b in the second matrix. You can either type this code manually, or you can use the Mod10IterateMatrixRowsOperatorMultiply code snippet. After the code that calculates the values for the newMatrix object, add a statement that returns the newMatrix object as the result of the * operator. Build the solution and correct any errors.

Encapsulating Data and Defining Overloaded Operators

10-65

Exercise 3: Testing the Operators for the Matrix Type


Scenario
In this exercise, you will use a test harness to test the operators in the Matrix class that you developed in the previous exercise. The main tasks for this exercise are as follows: 1. 2. 3. Add the test harness to the solution. Add code to test the operators in the Matrix class. Test the matrix operators by using the test harness.

Task 1: Add the test harness to the solution.


The test harness application for this lab is a simple console application that is designed to test the functionality of the Matrix class. It does not include any exception handling to ensure that it does not hide any exceptions thrown by the class you have developed. Open the Module10 solution in the D:\Labfiles\Lab10\LabC\Ex3\Starter folder. Add the test harness to the solution. The test harness is a project named Exercise3TestHarness, located in the D:\Labfiles\Lab10\LabC\Ex3\Starter\Exercise3TestHarness folder. Set the Exercise3TestHarness project as the startup project for the solution.

Task 2: Add code to test the operators in the Matrix class.


Review the Task List. Open the Module1.vb file. Review the Main method. This method creates two 33 square matrices named matrix1 and matrix2 and populates them with sample data. The method then displays their contents to the console by using the ToString method. Remove the TODO comment. Add a statement to write the message Matrix 1 + Matrix 2: to the console. Add a statement to create a new Matrix object named matrix3 and populate it with the sum of the matrix1 and matrix2 objects. Add code to write the contents of the matrix3 matrix to the console, followed by a blank line. Add a statement to write the message Matrix 1 - Matrix 2: to the console. Add code to create a new Matrix object named matrix4 and populate it with the difference between the matrix1 and matrix2 objects (subtract matrix2 from matrix1). Add code to write the contents of the matrix4 matrix to the console, followed by a blank line. Add a statement to write the message Matrix 1 Matrix 2: to the console. Add code to create a new Matrix object named matrix5 and populate it with the product of the matrix1 and matrix2 objects. Add code to write the contents of the matrix5 matrix to the console, followed by a blank line. Build the solution and correct any errors.

10-66

Programming in Visual Basic with Microsoft Visual Studio 2010

Task 3: Test the matrix operators by using the test harness.


Start the Exercise3TestHarness application. Verify that the output from the console appears correctly. The output should resemble the following.
Matrix 1: 1 2 3 4 5 6 7 8 9 Matrix 2: 9 8 7 6 5 4 3 2 1 Matrix 1 + 2: 10 10 10 10 10 10 10 10 10 Matrix 1 - 2: -8 -6 -4 -2 0 2 4 6 8 Matrix 1 x 2: 30 24 18 84 69 54 138 114 90

Close the console window. Close Visual Studio.

Encapsulating Data and Defining Overloaded Operators

10-67

Lab Review

Review Questions
1. 2. Can you declare an operator that is not shared? Can you change the multiplicity of an operator?

10-68

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Review and Takeaways

Review Questions
1. 2. If you are developing a new type and must expose data, how can you expose the data as a property with minimal extra effort? You must develop an application to represent a set of data. You must expose individual members of the data to consuming classes. How can you expose individual members in a dataset to consuming classes? You have overloaded the = operator in a type you are developing. As required by the compiler, and to comply with best practices, you are also going to implement the <> operator. Should you implement the <> operator from scratch, or should you use the = operator that you have already defined, and negate the result?

3.

Best Practices Related to Properties


Supplement or modify the following best practices for your own work situations: Use properties only when a property is appropriate, but do not expose data unnecessarily. Use auto-implemented properties, instead of making a field public, unless there is a very good reason not to.

Best Practices Related to Indexers


Supplement or modify the following best practices for your own work situations: Use a default property to access a data member that is part of a set. A default property is not a method: if you are writing too much code in a default property, consider whether it would be better implemented as a method.

Best Practices Related to Operators


Supplement or modify the following best practices for your own work situations: Implement symmetric operators for commutable operations.

Encapsulating Data and Defining Overloaded Operators

10-69

Do not modify the value of operands in an operator. Define only meaningful operators.

10-70

Programming in Visual Basic with Microsoft Visual Studio 2010

Decoupling Methods and Handling Events

11-1

Module 11
Decoupling Methods and Handling Events
Contents:
Lesson 1: Declaring and Using Delegates Lesson 2: Using Lambda Expressions Lesson 3: Handling Events Lab: Decoupling Methods and Handling Events 11-3 11-9 11-16 11-28

11-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

In this course, you saw how to can call a method to perform an operation by using the name of the method. Sometimes, you will need to call a method determined dynamically at runtime, or you may want to call code that is not available when you develop your type. For example, you may need to call code that other developers have written for applications that consume your type. You can decouple an operation from the method that implements it and write code to determine at run time which method should implement the operation. This module explains how to decouple an operation from the method that implements it and how to use anonymous methods to implement decoupled operations. This module also explains how to use events to inform consuming applications of a change or notable occurrence in a type.

Objectives:
After completing this module, you will be able to: Describe the purpose of delegates and explain how to use a delegate to decouple an operation from the implementing method. Explain the purpose of lambda expressions and describe how to use a lambda expression to define an anonymous method. Explain the purpose of events and describe how to use events to report that something significant has happened in a type that other parts of the application need to be aware of.

Decoupling Methods and Handling Events

11-3

Lesson 1

Declaring and Using Delegates

You can use delegates to decouple an operation from the methods that implement the operation. This lesson explains how to use delegates as an indirect mechanism to invoke one or more methods and how to use anonymous methods to implement a decoupled operation.

Lesson Objectives:
After completing this lesson, you will be able to: Describe why you may want to decouple an operation from the method that implements it and explain how you decouple an operation in Microsoft Visual Basic. Explain how to define a delegate. Invoke methods through a delegate synchronously and asynchronously. Describe and define anonymous methods.

11-4

Programming in Visual Basic with Microsoft Visual Studio 2010

Why Decouple an Operation from a Method?

When you develop applications by using Visual Basic, in most cases, you invoke methods explicitly, by name. The logical operation and the physical implementation of that operation in the form of a method are tightly coupled. However, there are times when this approach is not suitable. For example, you may develop a framework that can invoke different methods to perform an operation depending on criteria determined dynamically when the application runs. One way to implement this functionality is to use a series of If or Select Case statements, but this is a static approach and depend on the methods available when the code is written. A more extensible approach is to use delegates. Another scenario involves callback methods. Third-party vendors who provide assemblies with methods that you can invoke asynchronously frequently enable you to specify a method in your code to run when their method has completed. These third-party vendors are unlikely to know in advance the names of all of your methods, so instead they can provide a delegate that you can associate with one or more of your methods. To put it simply, a delegate is a reference to a method. A delegate defines the signature of the method; for example, a delegate may specify that a method takes two string parameters and returns an integer. At run time, you can associate a delegate with any method that matches this signature. To call the method, you invoke the delegate.

Multicast Operations
Another common use for decoupling an operation from a method is multicast operations. If an operation is multicast, several methods can implement the same operation. You can add references to all of the implementing methods to the delegate, and when the delegate is invoked, the runtime will invoke each method in turn. When you use a multicast operation, the implementing methods are called in sequence according to when they were added to the delegate. However, note that if one of the methods throws an unhandled exception, there is no guarantee that the application will call subsequent methods. Question: If you develop a class library and want to enable developers who write consuming applications to run code after an asynchronous method call completes, how can you provide this functionality?

Decoupling Methods and Handling Events

11-5

Defining a Delegate

A delegate is a reference to one or more methods, so when you define a delegate, you must specify the signature of the delegate. Methods that the delegate refers to must have matching signatures. You define a delegate by using the Delegate keyword. When you define a delegate, you create a new type; this is the same as using the Class or Structure keywords. You can specify an access modifier for a delegate in the same way as you can for any type.

Note: When you use the Delegate keyword to define a new delegate type, the Visual Basic compiler converts the new delegate type to inherit from the MulticastDelegate class. You cannot explicitly inherit from the MulticastDelegate classyou must use the Delegate keyword. The following code example shows two simple delegates, Sub and Function.
Public Delegate Function IsValidDelegate() As Boolean Public Delegate Sub IsValidSubDelegate()

The preceding code example declares a Function delegate named IsValidDelegate. The delegate was made visible to other classes by using the Public access modifier, and it returns a Boolean value and takes no parameters. To use the IsValidDelegate delegate, you must initialize it. The following code example illustrates this.
Public IsValid1 As IsValidDelegate = Nothing Public IsValid2 As IsValidDelegate = AddressOf CheckStateValid Public IsValid3 As New IsValidDelegate(AddressOf CheckControl)

When initializing the delegate instance, you can assign a method reference, or wait till after you have initialized the delegate. You can use the assignment operator = and the AddressOf operator to add method references from a delegate. The following code example shows how to add method references to the IsValid instance of the IsValidDelegate delegate class.

11-6

Programming in Visual Basic with Microsoft Visual Studio 2010

' An instance method that happens to appear in the same class Function CheckStateValid() As Boolean ... End Function ' An instance method that happens to appear in the same class Public Function CheckControl() As Boolean ... End Function ... ' Add a method reference by using the = and AddressOf operator ' Implicitly call the delegate constructor IsValid2 = AddressOf CheckStateValid ' Add a method reference by using the = and AddressOf operator ' Explicitly call the delegate constructor IsValid3 = New IsValidDelegate(AddressOf CheckControl)

To combine method references in a single delegate instance, you can use the shared MulticastDelegate.Combine method. The following code example shows how to combine method references from the IsValid2 and IsValid3 delegate instances in the IsValid1 delegate instance.
' Combine method references by using the shared ' MulticastDelegate.Combine method IsValid1 = MulticastDelegate.Combine(IsValid2, IsValid3)

The following code example shows how to remove method references from the IsValid1 delegate instance.
' Remove a method reference by using the shared ' MulticastDelegate.Remove methods IsValid1 = MulticastDelegate.Remove(IsValid1, IsValid2) ' Remove all method references by using the shared ' MulticastDelegate.RemoveAll method IsValid1 = MulticastDelegate.RemoveAll(IsValid1, IsValid1)

Note: The IsValid1 delegate instance is used as both the source (first) and value (second) parameters for the shared MulticastDelegate.RemoveAll method call. This means that all method references contained in the source parameter is removed and the source parameter and the result are assigned back to the IsValid1 delegate instance. Question: Which of the following are valid scopes to define a delegate: the namespace scope, the class scope, or the method scope?

Additional Reading
For more information about delegates, see the Delegates (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211396&clcid=0x409

Decoupling Methods and Handling Events

11-7

Invoking a Delegate

After you define a delegate and create an instance of that delegate, you can invoke it in code. You can invoke a delegate in the same way as you call a methodby using the delegate instance name followed by any parameters in parentheses. There is one important difference between invoking a delegate and calling a method: a delegate may not reference any methods and may therefore be null. You should always check that a delegate is not null before you invoke it. When a delegate does not reference any methods, it will resolve to null. When the delegate has references to one or more methods, it will not resolve to null. The following code example shows how to invoke the IsValid delegate.
If IsValid IsNot Nothing Then IsValid() End If

Warning: When you invoke a delegate synchronously, if it is multicast, each of the implementing methods is called in order. If your delegate has a return type, the value from the last implementing method is returned to the invoking application. The return values from any other implementing methods are ignored.

Invoking a Delegate Asynchronously


In addition to invoking a delegate synchronously, you can invoke a delegate asynchronously. If you want to invoke a delegate asynchronously, it must reference only a single method; you cannot invoke a multicast delegate asynchronously. The Delegate type supports the asynchronous programming design pattern through the BeginInvoke and EndInvoke methods. An application can call the BeginInvoke method to run the method that a delegate references asynchronously and use the EndInvoke method to capture any data that the delegated method returns.

11-8

Programming in Visual Basic with Microsoft Visual Studio 2010

Question: Why should you always check that a delegate is not null before you invoke it?

Additional Reading
For more information about asynchronous programming, see the Asynchronous Programming Overview page at http://go.microsoft.com/fwlink/?LinkId=192956

Decoupling Methods and Handling Events

11-9

Lesson 2

Using Lambda Expressions

Lambda expressions provide a technique for implementing anonymous methods that are more succinct than you can achieve by using delegates. This lesson explains how to use lambda expressions. It also describes some of the advantages of lambda expressions compared with anonymous methods.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of lambda expressions and define lambda expressions. Define lambda expressions that take parameters. Describe the scope of variables in a lambda expression.

11-10

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is a Lambda Expression?

A lambda expression is an expression that returns a method. These expressions evolved from the world of functional programming, but they are particularly useful for defining anonymous, but strongly typed methods. They have a sound mathematical foundation, and they are widely used throughout Visual Basic, especially when defining Language-Integrated Query (LINQ) expressions.

Note: LINQ is described in detail in a later module. A lambda expression consists of a set of parameters and a body. The body defines a function that may return a value; if it does, the Visual Basic compiler infers the return type from the definition of the method body.

Defining a Lambda Expression


You define a lambda expression by using the Sub or Function keyword. The following code example shows a very simple lambda expression.
Function(x) x * x

You can read this lambda expression as "Given x, calculate x * x." The type of x does not have to be specified, because it will be inferred when the lambda expression is used. In addition, the return type is also inferred. You can reference the lambda expression from a delegate, as the following code example shows.
Delegate Function MyDelegate(ByVal a As Integer) As Integer ... Private myDelegateInstance As MyDelegate= Nothing myDelegateInstance = New MyDelegate(Function(x) x * x)

Decoupling Methods and Handling Events

11-11

In this example, the type of x and the return type in the lambda expression are determined by the delegate, which takes an integer parameter and returns an integer. You can abbreviate the statement that assigns the lambda expression to myDelegateInstance, as the following code example shows.
Delegate Function MyDelegate(ByVal a As Integer) As Integer ... Private myDelegateInstance As MyDelegate = Nothing myDelegateInstance = Function(x) x * x

This syntax is natural and concise. When you invoke the delegate, you specify values for any parameters that the lambda expression takes and the body of the lambda expression runs. You can capture any return value in the same way as calling a regular method. The following code example shows how to invoke a delegate based on the simple lambda expression and delegate shown previously.
Console.WriteLine(myDelegateInstance(10)) ' Displays the value 100

Question: Can you define a lambda expression without using a delegate to reference the expression?

11-12

Programming in Visual Basic with Microsoft Visual Studio 2010

Defining Lambda Expressions

Lambda expressions can take several subtly different forms. Lambda expressions were originally part of a mathematical notation called the Lambda Calculus that provides a notation for describing functions. Although the Visual Basic language has extended the syntax and semantics of the Lambda Calculus in its implementation of lambda expressions, many of the original mathematical principles still apply. The body of a lambda expression can be a simple expression, as shown in the previous topic, or it can be a block of Visual Basic code that defines a method body enclosed in braces. If you define a method body, you can make use of any Visual Basic programming constructs.

Note: Although you can use any Visual Basic code you like in a lambda expression, it is not considered good practice to define code that modifies data that your application uses elsewhere. Purists refer to this as programming with side effects, and these side effects can often be the cause of subtle bugs that are difficult to track down. A lambda expression can take more than one parameter, in which case you specify a parameter list that is enclosed in parentheses, as the following code example shows.
Delegate Function AddDelegate(ByVal a As Integer, ByVal b As Integer) As Integer ... Dim myAddDelegate As AddDelegate = Nothing myAddDelegate = Function(x, y) x + y

A lambda expression can also take zero parameters. In this case, specify an empty parameter list. The following code examples show many of the different forms of lambda expressions available in Visual Basic.
' A simple expression that returns the square of its parameter. ' The type of parameter x is inferred from the context. Function(x) x * x

Decoupling Methods and Handling Events

11-13

' Semantically the same as the preceding expression, but using a ' Visual Basic statement block as a body. Function(x) Return x * x End Function ' A simple expression that returns the value of the parameter divided ' by 2. The type of parameter x is stated explicitly. Function(ByVal x As Integer) x / 2 ' Calling a method. The expression takes no parameters. ' The expression mayor may not return a value. Function() myObject.MyMethod(0) ' Multiple parameters; the compiler infers the parameter types. ' The parameter x is passed by value, so the effect of the += ' operation is local to the expression. Function(x, y) X += 1 Return x \ y End Function ' Multiple parameters with explicit types. ' Parameter x is passed by reference (defined in the Delegate), so the ' effect of the += operation is permanent. Function(x As Integer, y As Integer) x += 1 Return x \ y End Function

Note: Lambda expression can also be defined as Sub methods, where no value is returned. See the next topic, Variable Scope in Lambda Expressions, for an example.

Additional Reading
For more information about lambda expressions, see the Lambda Expressions (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211397&clcid=0x409

11-14

Programming in Visual Basic with Microsoft Visual Studio 2010

Variable Scope in Lambda Expressions

When you define a lambda expression, you can define variables in the body of that expression. The scope of these variables is the lambda expression. When the lambda expression completes, the variable goes out of scope. Code that does not appear in the lambda expression cannot access variables that are defined in that expression. When you define a lambda expression, you can also access variables defined outside that expression. However, the lifetime of these variables is extended until the lambda expression itself goes out of scope, which may have an impact on garbage collection. The following code example shows how a lambda expression that is assigned to a delegate in a method can make use of variables that are defined in that method.
Dim del As MyDelegate = Nothing ... Sub myMethod() Dim count As Integer = 0 del = New MyDelegate(Sub() count += 1 ' Perform operation using count variable ... End Sub)

End Sub

In the preceding code example, if the myMethod method did not instantiate the del delegate, when the method completed normally, the count variable would become eligible for garbage collection. However, if any references to the del delegate exist when the method completes, the count variable will remain. When all references to the del delegate are removed, the garbage collector will mark the count variable for collection. In this case, the data is an integer field that uses very few resources; however, a lambda expression can use any object of any type that is in scope when the expression is defined. If the object is large and uses a lot of resources, this may not be desirable or intended. You should consider carefully

Decoupling Methods and Handling Events

11-15

whether it is strictly necessary before you reference variables that are defined outside the scope of a lambda expression. Question: If you reference an open database connection in a lambda expression, which would normally go out of scope at the end of the method that defines the expression, what happens to the database connection when the method completes?

11-16

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 3

Handling Events

Events enable you to indicate that something significant has happened in your application and other elements in your application may need to be informed of this occurrence. For example, when a user clicks a button in a graphical application, you typically want to run a block of code that performs some action associated with the button click, such as saving data to a file. This lesson explains how to use events and describes some of the best practices you should follow when you implement events in your applications.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of an event. Define an event. Raise and subscribe to an event. Explain some of the best practices associated with using events. Describe the use of events in graphical applications.

Decoupling Methods and Handling Events

11-17

What Is an Event?

In Visual Basic, events are very similar to delegates. In fact, events are based on delegates, although semantically events have a subtly different purpose. A type uses an event to indicate a significant occurrence and arrange for a delegate to be called. On the other hand, any object that has access to a delegate can invoke that delegate; only the type that defines an event can trigger that event. When a type defines an event, other types can define a method that matches the signature of the delegate that is associated with the event. These other types can then subscribe to the event and specify that this method should be run when the event is raised. Classes in the Microsoft .NET Framework use events extensively, including nearly all Windows Presentation Foundation (WPF) controls. You should use events wherever you must develop a type that needs to inform consuming classes about a change in state. Question: What is the difference between a publicly exposed instance of a delegate and a publicly exposed event?

11-18

Programming in Visual Basic with Microsoft Visual Studio 2010

Declaring an Event

Events are used in conjunction with delegates, but you do not need to worry about delegates when declaring an event. There are conventions that specify the standard signature for an event; you should try to follow the conventions wherever possible. The standard convention for an event is that the event should not return a value and should take two parameters. The first parameter should be of type Object named sender. The second parameter should be a derivative from the EventArgs class from the System namespace, named e. If your event does not need to pass any event arguments when the event is invoked, you can use the EventArgs class directly. The following code example shows a typical event.
Public Class MyCls Public Event MyEvent(ByVal sender As Object, ByVal e As EventArgs) End Class

To declare an event, you use the Event keyword. When you define an event, you should normally make the event Public so that consuming classes can see the event. When you define an event, remember that the delegate must be at least as visible as the eventotherwise, consuming classes will not be able to create instances of the delegate and therefore will not be able to subscribe to your event.

Additional Reading
For more information about events, see the Events (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211397&clcid=0x409

Decoupling Methods and Handling Events

11-19

Using Events

After you have defined an event, you can use the event in your application. You can subscribe to the event in consuming types and applications and raise the event in your type.

Subscribing to an Event
To subscribe to an event, you can use the AddHandler statement in conjunction with the AddressOf operator. The AddressOf operator creates a procedure delegate instance that references the specific procedure, saving you the extra work of dealing with a delegate. The following code example shows how to add a subscriber to the MyEvent event.
AddHandler MyEvent, AddressOf myHandlingMethod

In addition to subscribing to an event, you can unsubscribe from an event. To unsubscribe from an event, you use the RemoveHandler statement in conjunction with the AddressOf operator. The following code example shows how to remove a subscriber from the MyEvent event.
RemoveHandler MyEvent, AddressOf myHandlingMethod

You can also use the WithEvents modifier to declare a member variable in a class or module that refers to an instance of a class that can raise events, as the following code shows.
Dim WithEvents myObj As MyCls

When using the WithEvents modifier, you can use the Handles keyword when defining a method that subscribes to an event raised in the object instance. The following code example shows how to add a subscriber to the MyEvent of the MyCls class.
Module Module1 ...

11-20

Programming in Visual Basic with Microsoft Visual Studio 2010

Dim WithEvents myObj As MyCls Sub MyEvent(ByVal sender As Object, ByVal e As EventArgs) _ Handles myObj.MyEvent ' Handle event ... End Sub End Module

Raising an Event
To raise an event, you use the RaiseEvent statement, and the event name followed by any parameter values in parentheses. The following code example shows how to raise an event.
Public Class MyCls ... Public Sub OnMyEvent() Dim args As New EventArgs() ... RaiseEvent MyEvent(Me, args) End Sub End Class

Note: Best practices state that you should use an On method when you raise an event. For more information about On methods, see Best Practices for Using Events later in this module. Question: Can consuming classes raise an event?

Additional Reading
For more information about the WithEvents modifier, see the WithEvents (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211520&clcid=0x409
For more information about the Handles keyword, see the Handles Clause (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211522&clcid=0x409
For more information about how to declare and raise events, see the Walkthrough: Declaring and Raising Events (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211533&clcid=0x409
For more information about handling events, see the Walkthrough: Handling Events (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211536&clcid=0x409

Decoupling Methods and Handling Events

11-21

Best Practices for Using Events

When you develop a type that uses events, you should adhere to best practices as closely as possible. This helps ensure that other developers require minimal additional knowledge to create types that consume your type. The best practices also help ensure that any changes that you make in the future will not cause breaking changes to any applications that already use your type. Use the Standard Event Signature

When you define the delegate for an event, you should use the standard event signature. An event should be a Sub procedure and also specify two parameters: the first parameter should be an object called sender and the second parameter should be a type that derives from the EventArgs class called e. You should define your own class that inherits from the EventArgs class instead of using the EventArgs class, unless you are sure that you will never need to pass any information in the event arguments in the futurein which case, you can use the EventArgs class. When you develop a class to hold the event arguments for your type, it is normal to name the class MyNameEventArgs, where you replace MyName with either the name of the event or a name related to the type of the arguments that are passed. Naming the type based on the data it represents rather than the event for which it is being developed can lead to reusable types, and is therefore often better. Use a Protected Overridable Method to Raise an Event

When you define an event in your type, define a protected overridable method that raises the event. The method should be named according to the name of the event, but prefixed with the word On. For example, the method to raise the MyEvent event would be named OnMyEvent. The On event should take the same parameters as the event and contain any logic needed before raising the event. When you need to raise the event in your type, you can use the On method instead of raising the method directly. This helps reduce code duplication. The On method should be protected and overridable. This enables any classes that derive from your type to modify the process when an event is raised, such as adding validation to a property in the event arguments and throwing an exception if the value is not valid. Using a protected virtual method to raise the exception if the argument does not pass validation checks, you can modify the method in any child

11-22

Programming in Visual Basic with Microsoft Visual Studio 2010

classes if the validation rules change. If you use an On method, the new validation will apply to occurrences where you raise the event in your type and any occurrences where the event is raised in the child class. Do Not Pass Null Values to an Event

When you raise an event, it can be tempting to use null as a parameter. You should avoid passing null and always pass instantiated objects as parameters. For the sender parameter, you should normally use the Me keyword. You should not omit the sender parameter because consuming applications will often depend on this parameter. Question: What is the naming convention for methods that encapsulate the logic associated with raising an event?

Decoupling Methods and Handling Events

11-23

Using Events in Graphical Applications

Graphical applications use a large number of events. Graphical applications typically involve a lot of user interaction. When users click a button, they expect the application to respond immediately. When you develop a graphical application, you can use events to respond immediately to users when they interact with the interface. For example, a button exposes a Click event, and you can add event handlers by subscribing to the Click event. In the event handler, you add code to handle the click event and perform the appropriate logic.

Threading in .NET Framework Applications


When you write a simple application, you use a single thread. All of your code runs sequentially on that thread. If a process takes a long time to complete, the application will pause while it waits for that process to finish. You will often need to develop applications that do not hang while long-running processes complete. In the .NET Framework, you solve this problem by using threads. You can use several threads in your applications. Each thread can run concurrently with other threads, which means that the application no longer needs to wait while a long-running process completes. The long-running process can be performed on a different thread, and the application can continue with other tasks.

User Interface and Threading


Graphical applications in the .NET Framework use threading like every other application. In the .NET Framework, all interaction with the user interface (UI) must be performed on a single thread; often referred to as the UI thread. The UI thread is the only thread that can update the UI or respond to events that the UI raises. You can use other threads with the UI thread to ensure that your graphical application remains responsive; however, you must marshal data between the threads to ensure thread safety. You can manually implement this logic, or you can use the BackgroundWorker thread.

Using the BackgroundWorker Class to Implement Multithreading


The BackgroundWorker class enables you to run code on another thread. The BackgroundWorker class includes events that indicate the progress of the thread. A UI can subscribe to these events and update the screen by using the UI thread. This enables you to build a UI that remains responsive and up to date.

11-24

Programming in Visual Basic with Microsoft Visual Studio 2010

Note: Although the BackgroundWorker class works well with UIs, you are not restricted to using it in this environment. You can employ a BackgroundWork object whenever you need to run code on a separate thread and report the progress of that code. To use the BackgroundWorker class, perform the following steps: 1. 2. Create an instance of the BackgroundWorker class. Add a handler for the DoWork event. The event handler for the DoWork event should contain the code that you need to perform on a separate thread. Optionally, you can use the ReportProgress method of the BackgroundWorker object to report the status of the operation. Optionally, add a handler for the ProgressChanged event. The ProgressChanged event handler runs on the UI thread, so you can add code to update the UI. The ProgressChanged event is raised whenever you call the ReportProgress method. Optionally, add a handler for the RunWorkerCompleted event. This event is raised when the method you associated with the DoWork event completes. The handler for the RunWorkerCompleted event runs on the UI thread, so it can update the UI. If you use the ProgressChanged method, set the WorkerReportsProgress property to True. Call the RunWorkerAsync method of the BackgroundWorker object. This method raises the DoWork event and starts the background thread running by using the method that you specified to subscribe to this event.

3.

4.

5. 6.

The BackgroundWorker class also provides a method called CancelAsync. This method sets a Boolean property called CancellationPending to true and requests that the method running as a result of the DoWork event is terminated. The method that the DoWork event runs should periodically check the CancellationPending property, and if it is true, the method should finish. The following code example shows how you can use the BackgroundWorker class in the Click event handler of a button to run a long-running process without freezing the UI. The example also shows how you can respond to the ProgressChanged event to keep the UI of the application updated.
Imports System.ComponentModel ... Private SubButton1_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Dim bw As New BackgroundWorker() ' Alternatively you can omit the parameter types AddHandler bw.DoWork, Sub(doWorkSender As Object, doWorkArgs As DoWorkEventArgs) bw.ReportProgress(10) ' Perform long running process ' Use the doWorkArgs.Argument property bw.ReportProgress(50) ' Continue long running process bw.ReportProgress(90) End Sub AddHandler bw.ProgressChanged, Sub( progressChangedSender As Object, progressChangedArgs As ProgressChangedEventArgs) ' Update label in UI with progress StatusBox.Content = progressChangedArgs.ProgressPercentage.ToString() End Sub

Decoupling Methods and Handling Events

11-25

AddHandler bw.RunWorkerCompleted, Sub( runWorkerCompletedSender As Object, runWorkerCompletedArgs As RunWorkerCompletedEventArgs) ' Alternatively update the user interface MessageBox.Show("Complete") End Sub bw.WorkerReportsProgress = True bw.RunWorkerAsync() End Sub

Note: The code example shown can also be implemented by using the WithEvents modifier with the Handles keyword. Question: Can you add code to the DoWork event handler to update the user interface directly?

Additional Reading
For more information about how to run an operation in the background, see the How to: Run an Operation in the Background page at http://go.microsoft.com/fwlink/?LinkId=192960

11-26

Programming in Visual Basic with Microsoft Visual Studio 2010

Demonstration: Using Events

Demonstration Steps
1. 2. 3. 4. 5. 6. 7. Start Visual Studio 2010. Open the Module 11, Demo1 starter solution. Open the Heartbeat.vb file, and then review the Heartbeat class. Uncomment the HeartbeatEventArgs class. This class inherits from the EventArgs class and defines a read-only property for the heartbeat count. Uncomment the code that defines an event named Beat. Your class will raise this event each time the heartbeat count is incremented. Uncomment the OnBeat method. This method raises the event. This is a best practice, and this method can be overridden in child classes. Uncomment the code in the Start method that raises the Beat event by using the OnBeat method. You use the Me keyword as the first parameter and create a new instance of the HeartbeatEventArgs class by using the current count as the second parameter. Open the MainWindow.xaml.vb file, and then review the event handlers for the Click events. Uncomment the code in the StartButton_Click method that adds an event handler for the Beat event of the beat object.

8. 9.

10. Uncomment the beat_Beat method. This method handles the Beat event by displaying a message box each time that event is raised. Note the use of the property from the custom event arguments class. 11. Run the application. 12. Click Start. 13. Verify that the application works correctly. Highlight the message boxes when they appear (they should appear every three seconds).

Decoupling Methods and Handling Events

11-27

14. Close the application. 15. Close Visual Studio. Question: Why should you use the Protected modifier instead of the Public modifier as the access modifier for an On method?

11-28

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab: Decoupling Methods and Handling Events

Objectives:
After completing this lab, you will be able to: Raise an event and handle it by using a delegate. Use lambda expressions to abstract methods and actions.

Introduction
In this lab, you will define and raise events and handle them by using delegates. You will use lambda expressions to specify actions to perform and will run these actions by invoking the lambda expressions.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

Decoupling Methods and Handling Events

11-29

Lab Scenario

You need to add further features to the measuring devices that log measurement data. The measuring devices take new measurements when they detect a change in the object being measured. These changes may occur at any time. You need to modify the software that drives these devices to trigger an event each time a new measurement is taken. It must be possible to pause the data collection process from the client application, stop receiving measurements, and then restart the collection process. The rate at which new measurements are received is variable. Therefore, it is not easy to tell whether the device is still functioning. You need to add heartbeat functionality to the devices that fires an event on a regular basis to notify client applications that the device is still working. The heartbeat event should also return a datetime stamp to the client application. The heartbeat interval should be set when the MeasureDataDevice object is created.

11-30

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 1: Raising and Handling Events


In this exercise, you will modify the IMeasuringDevice interface and add an event called NewMeasurementTaken. This event will be triggered whenever the device detects a change and takes a new measurement. You will modify the MeasureDataDevice abstract class from the previous lab and implement this event. The NewMeasurementTaken event will occur after the device has populated the internal buffer with the new measurement and logged it. You will use a BackgroundWorker component to poll for new measurements. The polling for new measurements will take place in the DoWork event, and the ProgressReported event will raise the NewMeasurementTaken event to notify the client application that a new measurement has been taken. You will start the background thread running by using the RunWorkerAsync method, and the device will support cancellation of the background thread by using the CancelWorkerAsync method. You will test the new functionality by using an existing WPF application that creates an instance of the MeasureMassDevice class and trapping the events that it raises by using a delegate. The WPF application should be able to pause and then restart the MeasureMassDevice class. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. 6. 7. 8. 9. Open the Events solution. Create a new interface that extends the IMeasuringDevice interface. Add the NewMeasurementTaken event to the MeasureDataDevice class. Add a BackgroundWorker member to the MeasureDataDevice class. Add the GetMeasurements method to the MeasureDataDevice class. Implement the dataCollector_DoWork method. Implement the dataCollector_ProgressChanged method. Call the GetMeasurements method to start collecting measurements. Call the CancelAsync method to stop collecting measurements.

10. Dispose of the BackgroundWorker object when the MeasureDataDevice object is destroyed. 11. Update the UI to handle measurement events. 12. Implement the device_NewMeasurementTaken event-handling method. 13. Disconnect the event handler. 14. Test the solution.

Task 1: Open the Events solution.


Open Visual Studio 2010. Open the Events solution in the D:\Labfiles\Lab11\Ex1\Starter folder.

Task 2: Create a new interface that extends the IMeasuringDevice interface.


In the MeasuringDevice project, add a new interface named IEventEnabledMeasuringDevice in a file named IEventEnabledMeasuringDevice.vb.

Decoupling Methods and Handling Events

11-31

Note: Creating a new interface that extends an existing interface is good programming practice, because it preserves the structure of the original interface for backward compatibility with preexisting code. All preexisting code can reference the original interface, and new code can reference the new interface and take advantage of any new functionality. Modify the interface definition so that the IEventEnabledMeasuringDevice interface extends the IMeasuringDevice interface. In the IEventEnabledMeasuringDevice interface, add an event named NewMeasurementTaken. The event signature must include a parameter named sender of type Object and a parameter named e of type EventArgs. Build the application to enable Microsoft IntelliSense to reflect your changes.

Task 3: Add the NewMeasurementTaken event to the MeasureDataDevice class.


Review the Task List. Locate the TODO: - Modify the class definition to implement the extended interface task, and then double-click this task. This task is located in the MeasureDataDevice class file. Remove the TODO: - Modify the class definition to implement the extended interface. comment, and then modify the class definition to implement the IEventEnabledMeasuringDevice interface instead of the IMeasuringDevice interface. In the Task List, locate the TODO: - Add the NewMeasurementTaken event task, and then doubleclick this task. This task is located at the end of the MeasureDataDevice class. Remove the TODO: - Add the NewMeasurementTaken event comment, and then declare an event named NewMeasurementTaken by using the same signature as the interface. Below the event, remove the TODO: - Add an OnMeasurementTaken method comment, and then add a protected overridable method named OnNewMeasurementTaken. The method should accept no parameters and have no return type. The MeasureDataDevice class will use this method to raise the NewMeasurementTaken event. In the OnNewMeasurementTaken method, add code to raise the event.

Task 4: Add a BackgroundWorker member to the MeasureDataDevice class.


In the Task List, locate the TODO: - Declare a BackgroundWorker to generate data task, and then double-click this task. This task is located near the top of the MeasureDataDevice class. Remove the TODO: - Declare a BackgroundWorker to generate data comment, and then add a private BackgroundWorker member named dataCollector to the class.

Task 5: Add the GetMeasurements method to the MeasureDataDevice class.


The GetMeasurements method will initialize the dataCollectorBackgroundWorker member to poll for new measurements and raise the NewMeasurementTaken event each time it detects a new measurement. In the Task List, locate the TODO: - Implement the GetMeasurements method task, and then double-click this task. Remove the TODO: - Implement the GetMeasurements method comment, and then add a new private method named GetMeasurements to the class. This method should take no parameters and not return a value. In the GetMeasurements method, add code to perform the following actions.

11-32

Programming in Visual Basic with Microsoft Visual Studio 2010

a. b. c.

Instantiate the dataCollectorBackgroundWorker member. Specify that the dataCollectorBackgroundWorker member supports cancellation. Specify that the dataCollector BackgroundWorker member reports progress while running.

Hint: Set the WorkerSupportsCancellation and WorkerReportsProgress properties. Add the following code to instantiate a DoWorkEventHandler delegate that refers to a method called dataCollector_DoWork. Attach the delegate to the DoWork event property of the dataCollector member. The dataCollector object will call the dataCollector_DoWork method when the DoWork event is raised.
dataCollector.WorkerReportsProgress = True

...

AddHandler dataCollector.DoWork, AddressOf dataCollector_DoWork End Sub ...

Position the mouse pointer over squiggly blue line under the dataCollector_DoWork method name, click the arrow to open the drop-down list, and then click Generate method stub for dataCollector_DoWork in MeasuringDevice.MeasureDataDevice. Using the same technique as in step 4, instantiate a ProgressChangedEventHandler delegate that refers to a method called dataCollector_ProgressChanged. Attach this delegate to the ProgressChanged event property of the dataCollector member. The dataCollector object will call the dataCollector_ProgressChanged method when the ProgressChanged event is raised. Position the mouse pointer over the squiggly blue line under the dataCollector_ProgressChanged method name, click the arrow to open the drop-down list, and then click Generate method stub for dataCollector_ProgressChanged in MeasuringDevice.MeasureDataDevice. Add code to start the dataCollectorBackgroundWorker object running asynchronously.

Task 6: Implement the dataCollector_DoWork method.


At the end of the MeasureDataDevice class, locate the generated dataCollector_DoWork method stub. In the dataCollector_DoWork method, remove the statement that raises the NotImplementedException exception and add code to perform the following actions. a. b. c. Redimension the dataDeviceDataCaptured array to hold 10 items. Define an integer i with an initial value of zero. You will use this variable to track the current position in the dataDeviceDataCaptured array. Add a While loop that runs until the dataCollector.CancellationPending property is False.

In the While loop, add code to perform the following actions. a. Invoke the controller.TakeMeasurement method, and store the result in the dataCaptured array at the position that the integer i indicates. The TakeMeasurement method of the controller object blocks until a new measurement is available. Update the dataDeviceMostRecentMeasure field to contain the value in the dataCaptured array at the position that the integer i indicates. If the value of the disposedValue variable is True, terminate the While loop. This step ensures that the measurement collection stops when the MeasureDataDevice object is destroyed.

b. c.

Decoupling Methods and Handling Events

11-33

Add code to the While loop after the statements that you added in the previous step to perform the following actions. a. b. Check whether the loggingFileWriter property is null. If the loggingFileWriter property is not null, call the loggingFileWriter.WriteLine method, passing a string parameter of the format "Measurement - mostRecentMeasure" where mostRecentMeasure is the value of the dataDeviceMostRecentMeasure variable.

Note: The loggingFileWriter property is a simple StreamWriter object that writes to a text file. This property is initialized in the StartCollecting method. You can use the WriteLine method to write to a StreamWriter object. Add a line of code to the end of the While loop to invoke the dataCollector.ReportProgress method, passing zero as the parameter.

The ReportProgress method raises the ReportProgress event and is used to return the percentage completion of the tasks assigned to the BackgroundWorker object. You can use the ReportProgress event to update progress bars or time estimates in the user interface (UI). In this case, because the task will run indefinitely until canceled, you will use the ReportProgress event as a mechanism to prompt the UI to refresh the display with the new measurement. Add code to the end of the While loop to perform the following actions. a. b. Increment the integer i. If the value of the integer is greater than nine, reset i to zero.

You are using the integer i as a pointer to the next position to write to in the dataDeviceDataCaptured array. This array has space for 10 measurements. When element 9 is filled, the device will start to overwrite data beginning at element 0.

Task 7: Implement the dataCollector_ProgressChanged method.


Locate the dataCollector_ProgressChanged method. This method was generated during an earlier task. It runs when the ProgressChanged event is raised. In this exercise, this event occurs when the dataCollector_DoWork method takes and stores a new measurement. In the event handler, delete the exception code and then invoke the OnNewMeasurementTaken method.

The OnNewMeasurementTaken method raises the NewMeasurementTaken event that you defined earlier. You will modify the UI to subscribe to this event so that when it is raised, the UI can update the displayed information.

Task 8: Call the GetMeasurements method to start collecting measurements.


In the Task List, locate the TODO: - Call the GetMeasurements method task, and then double-click this task. This task is located in the StartCollecting method. Remove the TODO: - Call the GetMeasurements method comment, and add a line of code to invoke the GetMeasurements method.

11-34

Programming in Visual Basic with Microsoft Visual Studio 2010

Task 9: Call the CancelAsync method to stop collecting measurements.


In the Task List, locate the TODO: - Cancel the data collector task, and then double-click this task. This task is located in the StopCollecting method. Remove the TODO: - Cancel the data collector comment and add code to perform the following actions. a. b. Check that the dataCollector member is not null. If the dataCollector member is not null, call the CancelAsync method to stop the work performed by the dataCollectorBackgroundWorker object.

Task 10: Dispose of the BackgroundWorker object when the MeasureDataDevice object
is destroyed.
In the Task List, locate the TODO: - Dispose of the data collector task, and then double-click this task. This task is located in the Dispose method of the MeasureDataDevice class. Remove the TODO: - Dispose of the data collector comment and add code to perform the following actions. a. b. Check that the dataCollector member is not null. If the dataCollector member is not null, call the Dispose method to dispose of the dataCollector instance.

Task 11: Update the UI to handle measurement events.


In the Task List, locate the TODO: - Hook up the event handler to the event task, and then double-click this task. This task is located in the code behind for the MainWindow.xaml window. In the StartCollectingButton_Click method, remove the TODO: - Hook up the event handler to the event comment, and add code to connect the newMeasurementTaken delegate to the NewMeasurementTaken event of the device object. The device object is an instance of the MeasureMassDevice class, which inherits from the MeasureDataDevice abstract class.

Hint: To connect a delegate to an event, use the AddHandler statement and AddressOf operator on the event.

Task 12: Implement the device_NewMeasurementTaken event-handling method.


In the Task List, locate the TODO: - Add the device_NewMeasurementTaken event handler method to update the UI with the new measurement task, and then double-click this task. Remove the TODO: - Add the device_NewMeasurementTaken event handler method to update the UI with the new measurement comment, and add a private event-handler method named device_NewMeasurementTaken. The method should not return a value, but should take the following parameters. An Object object named sender. An EventArgs object named e. In the device_NewMeasurementTaken method, add code to check that the device member is not null. If the device member is not null, perform the following tasks. a. Update the Text property of the MostRecentMeasureTextBox control with the value of the device.MostRecentMeasure property.

Decoupling Methods and Handling Events

11-35

Hint: Use the ToString method to convert the value that the device.MostRecentMeasure property returns from an integer to a string. b. c. d. e. Update the Text property of the MetricValueTextBox control with the value that the device.MetricValue method returns. Update the Text property of the ImperialValueTextBox control with the value that the device.ImperialValue method returns. Reset the RawDataValuesListBox.ItemsSource property to Nothing. Set the RawDataValuesListBox.ItemsSource property to the value that the device.GetRawData method returns.

Note: The final two steps are both necessary to ensure that the data-binding mechanism that the Raw Data box uses on the WPF window updates the display correctly.

Task 13: Disconnect the event handler.


In the Task List, locate the TODO: - Disconnect the event handler task, and then double-click this task. This task is located in the StopCollectingButton_Click method, which runs when the user clicks the Stop Collecting button. Remove the TODO: - Disconnect the event handler comment, and add code to disconnect the newMeasurementTaken delegate from the device.NewMeasurementTaken event.

Hint: To disconnect a delegate from an event, use the RemoveHandler statement and AddressOf operator on the event.

Task 14: Test the solution.


Build the project and correct any errors. Start the application. Click Start Collecting, and verify that measurement values begin to appear in the Raw Data box. The MeasureMassDevice object used by the application takes metric measurements and stores them before raising the NewMeasurementTaken event. The event calls code that updates the UI with the latest information. Continue to watch the Raw Data list box to see the buffer fill with data and then begin to overwrite earlier values. Click StopCollecting, and verify that the UI no longer updates. Click Start Collecting again. Verify that the Raw Data list box is cleared and that new measurement data is captured and displayed. Click Stop Collecting. Close the application, and then return to Visual Studio.

Exercise 2: Using Lambda Expressions to Specify Code


In this exercise, you will declare a new delegate type and a new EventArgs type to support the HeartBeat event. You will modify the IMeasuringDevice interface and the MeasureDataDevice class to generate

11-36

Programming in Visual Basic with Microsoft Visual Studio 2010

the heartbeat by using a BackgroundWorker object. You will specify the code to run on the new thread by using a lambda expression. In the ReportProgress event handler, you will specify the code to notify the client application with another lambda expression. You will handle the HeartBeat event in the WPF application by using a lambda expression. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. 6. 7. 8. 9. Open the Events solution. Define a new EventArgs class to support heartbeat events. Declare a new delegate type. Update the IEventEnabledMeasuringDevice interface. Add the HeartBeat event and HeartBeatInterval property to the MeasureDataDevice class. Use a BackgroundWorker object to generate the heartbeat. Call the StartHeartBeat method when the MeasureDataDevice object starts running. Dispose of the heartBeatTimerBackgroundWorker object when the MeasureDataDevice object is destroyed. Update the constructor for the MeasureMassDevice class.

10. Handle the HeartBeat event in the UI. 11. Test the solution.

Task 1: Open the Events solution.


Open the Events solution in the D:\Labfiles\Lab11\Ex2\Starter folder. Import the code snippets from the D:\Labfiles\Lab11\Snippets folder.

Note: The Events solution in the Ex2 folder is functionally the same as the code that you completed in Exercise 1; however, it includes an updated Task List to enable you to complete this exercise.

Task 2: Define a new EventArgs class to support heartbeat events.


In the MeasuringDevice project, add a new code file named HeartBeatEventArgs.vb. In the HeartBeatEventArgs.vbcode file, make theHeartBeatEventArgs class extend the EventArgs class.

Note: A custom event arguments class can contain any number of properties; these properties store information when the event is raised, enabling an event handler to receive event-specific information when the event is handled. In the HeartBeatEventArgs class, add a DateTime property named TimeStamp. Make the property read-only by making the setter method private. You can either type this code manually, or you can use the Mod11TimeStampProperty code snippet.

Decoupling Methods and Handling Events

11-37

Add a constructor to the HeartBeatEventArgs class. The constructor should accept no arguments and initialize the TimeStamp property to the date and time when the class is constructed. The constructor should also extend the base class constructor.

Task 3: Declare a new delegate type.


At the bottom of the HeartBeatEventArgs class, declare a Public Delegate type named HeartBeatEventHandler. The delegate should refer to a method that does not return a value, but that has the following parameters. An object parameter named sender. A HeartBeatEventArgs parameter named args.

Task 4: Update the IEventEnabledMeasuringDevice interface.


In the Task List, locate the TODO: - Define the new event in the interface task and then doubleclick this task. This task is located in the IEventEnabledMeasuringDevice interface. Remove this comment and add an event called HeartBeat to the interface. The event should specify that subscribers use the HeartBeatEventHandler delegate type to specify the method to run when the event is raised. Position the mouse pointer over the squiggly blue line under the HeartBeatEventHandler name, click the arrow to open the drop-down list, and then click Import 'MeasuringDevice.HeartBeatEventArgs'. Remove the TODO: - Define the HeartBeatInterval property in the interface comment, and then add a read-only integer property called HeartBeatInterval to the interface.

Task 5: Add the HeartBeat event and HeartBeatInterval property to the


MeasureDataDevice class.
In the Task List, locate and double-click the TODO: - Add the HeartBeatInterval property task. This task is located in the MeasureDataDevice class. Remove the TODO: - Add the HeartBeatInterval property comment, and add a protected integer member named heartBeatIntervalTime. Add code to implement the public integer property HeartBeatInterval that the IEventEnabledMeasuringDevice interface defines. The property should return the value of the heartBeatInterval member when the Get accessor method is called. The property should have a Private Set access or method to enable the constructor to set the property. You can either type this code manually, or you can use the Mod11HeartBeatIntervalProperty code snippet. Remove the TODO: - Add the HeartBeat event comment, and add the HeartBeat event that the IEventEnabledMeasuringDevice interface defines. Position the mouse pointer over the squiggly blue line under the HeartBeatEventHandler name, click the arrow to open the drop-down list, and then click Import 'MeasuringDevice.HeartBeatEventArgs'. Remove the TODO: - Add the OnHeartBeat method to fire the event comment, and add a protected overridable method named OnHeartBeat that takes no parameters. In the OnHeartBeat method, add code to raise the HeartBeat event, passing the current object and a new instance of the HeartBeatEventArgs object as parameters.

Task 6: Use a BackgroundWorker object to generate the heartbeat.


Remove the TODO: - Declare the BackgroundWorker to generate the heartbeat comment, and then define a private BackgroundWorker object named heartBeatTimer.

11-38

Programming in Visual Basic with Microsoft Visual Studio 2010

Remove the TODO: - Create a method to configure the BackgroundWorker using Lambda Expressions comment, and declare a private method named StartHeartBeat that accepts no parameters and does not return a value. In the StartHeartBeat method, add code to perform the following actions. a. b. c. Instantiate the heartBeatTimerBackgroundWorker object. Configure the heartBeatTimer object to support cancellation. Configure the heartBeatTimer object to support progress notification.

Add a handler for the heartBeatTimerDoWork event by using a lambda expression to define the actions to be performed. The lambda expression should take two parameters (use the names o and args). In the Lambda expression body, add a While loop that continually iterates and contains code to perform the following actions. You can either type this code manually, or you can use the Mod11DoWorkHandler code snippet. d. e. f. Use the shared Thread.Sleep method to put the current thread to sleep for the length of time that the HeartBeatInterval property indicates. Check the value of the disposedValue property. If the value is True, terminate the loop. Call the heartBeatTimer.ReportProgress method, passing zero as the parameter.

Note: Use the AddHandler statement to specify that the method will handle the DoWork event. Add a handler for the heartBeatTimer.ReportProgress event by using another lambda expression to create the method body. In the lambda expression body, add code to call the OnHeartBeat method, which raises the HeartBeat event. At the end of the StartHeartBeat method, add a line of code to start the heartBeatTimerBackgroundWorker object running asynchronously.

Task 7: Call the StartHeartBeat method when the MeasureDataDevice object starts
running.
In the Task List, locate the TODO: - Call StartHeartBeat from StartCollecting method task, and then double-click this task. This task is located in the StartCollecting method. Remove this comment, and add a line of code to invoke the StartHeartBeat method.

Task 8: Dispose of the heartBeatTimer BackgroundWorker object when the


MeasureDataDevice object is destroyed.
In the Task List, locate the TODO: - dispose of the heartBeatTimer BackgroundWorker task, and then double-click this task. This task is located in the Dispose method. Remove the comment and add code to check that the heartBeatTimerBackgroundWorker object is not null. If the heartBeatTimer object is not null, call the Dispose method of the BackgroundWorker object.

You have now updated the MeasureDataDevice abstract class to implement event handlers by using lambda expressions. To enable the application to benefit from these changes, you must modify the MeasureMassDevice class, which extends the MeasureDataDevice class.

Decoupling Methods and Handling Events

11-39

Task 9: Update the constructor for the MeasureMassDevice class.


Open the MeasureMassDevice class file. At the start of the class, modify the signature of the constructor to take an additional Integer value named heartBeatInterval. Modify the body of the constructor to store the value of the HeartBeatInterval member in the heartBeatInterval member. Below the existing constructor, remove the TODO: Add a chained constructor that calls the previous constructor comment, and add a second constructor that accepts the following parameters. A Units instance named deviceUnits. A String instance named logFileName. Modify the new constructor to call the existing constructor. Pass a value of 1000 as the heartBeatInterval parameter value.

Task 10: Handle the HeartBeat event in the UI.


In the Task List, locate the TODO: - Use a lambda expression to handle the HeartBeat event in the UI task, and then double-click the task. This task is located in the StartCollectingButton_Click method in the code behind the MainWindow window in the Monitor project. Remove the comment and add a lambda expression to handle the device.HeartBeat event. The lambda expression should take two parameters (name them o and args). In the body of the lambda expression, add code to update the HeartBeatTimeStampLabel control with the text "HeartBeat Timestamp: timestamp" where timestamp is the value of the args.TimeStamp property. You can either type this code manually, or you can use the Mod11HeartBeatHandler code snippet.

Hint: Set the Content property of a label to modify the text that the label displays.

Task 11: Test the solution.


Build the project and correct any errors. Start the application. Click StartCollecting, and verify that values begin to appear as before. Also, note that the HeartBeatTimestamp value now updates once per second. Click StopCollecting, and verify that the RawData list box no longer updates. Note that the timestamp continues to update, because your code does not terminate the timestamp heartbeat when you stop collecting. Click Dispose Object and verify that the timestamp no longer updates. Close the application and then return to Visual Studio. Close Visual Studio.

11-40

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Review

Review Questions
1. 2. 3. 4. If you declare an event, when should you use the EventArgs class? What are the advantages of defining an On method to raise an event? What is the primary difference between exposing an instance of a delegate and exposing an event? When you define a lambda expression, what are the rules for using type inference with input parameters?

Decoupling Methods and Handling Events

11-41

Module Review and Takeaways

Review Questions
1. 2. 3. When might it be inappropriate to use a lambda expression? How can you invoke a method asynchronously if it only natively supports being called synchronously? Can lambda expressions use variables declared outside the lambda expression?

Best Practices Related to Using Delegates


Supplement or modify the following best practices for your own work situations: Use the delegate types defined in the .NET Framework instead of developing custom delegate types wherever possible. Use delegates to invoke synchronous methods asynchronously where appropriate; however, you should not omit asynchronous methods from a type where you can implement the asynchronous version of a method more efficiently than using the delegate syntax.

Best Practices Related to Using Lambda Expressions


Supplement or modify the following best practices for your own work situations: Only use a lambda expression if you use the method only once. If you are writing duplicate lambda expressions, you should normally use a named method instead. Do not change an object's state in a lambda expression. Wherever possible, you should write lambda expressions that do not have side effects. Avoid referencing variables defined outside the scope of the lambda expression.

Best Practices Related to Using Events


Supplement or modify the following best practices for your own work situations: Use the standard event signature.

11-42

Programming in Visual Basic with Microsoft Visual Studio 2010

Use a protected overridable method to raise an event. Do not pass Nothing as a parameter when you raise an event.

Using Collections and Building Generic Types

12-1

Module 12
Using Collections and Building Generic Types
Contents:
Lesson 1: Using Collections Lab A: Using Collections Lesson 2: Creating and Using Generic Types Lesson 3: Defining Generic Interfaces and Understanding Variance Lesson 4: Using Generic Methods and Delegates Lab B: Building Generic Types 12-3 12-16 12-22 12-33 12-37 12-47

12-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

In the previous modules of this course, you have seen how to develop types for use in your application, and you created instances of types by using a named instance or by storing several instances in an array. When you develop an application, you will often want to create multiple instances of a type. However, you may not want to specifically name each instance and you may need more flexibility than you can achieve by using arrays. In this module, you will learn about collection classes and how you can use them with greater flexibility than a simple array. The basic collection classes introduce a new problem. Classes that act on other types are often not typesafe. For example, many collection classes frequently use the Object type to store items, and must then be cast or converted back to their original type before they can be used. It is the programmers responsibility to ensure that the correct casts or conversions are performed, and it is easy to introduce errors by casting or converting an item to the wrong type. This module introduces generics and how you can use generic classes to maintain type-integrity and avoid issues that are associated with a lack of type safety.

Objectives:
After completing this module, you will be able to: Use collection classes. Define and use generic types. Define generic interfaces and explain the concepts of covariance and contravariance. Define and use generic methods and delegates.

Using Collections and Building Generic Types

12-3

Lesson 1

Using Collections

You have already seen how to use arrays to aggregate data and store multiple related instances of objects. Arrays are useful, but they have their limitations. For example, they are difficult to resize after you have created them, and the way in which you access an item in an array may not reflect the way in which the real world works. Collections are more flexible. They can dynamically resize themselves as you add or remove data, and they provide a range of strategies that you can use to access the items that they hold. This lesson introduces you to collections and describes how you can use them in your applications.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of a collection. Describe how collection classes work. Describe the common collection classes in Microsoft .NET Framework. Explain collection initializers.

12-4

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is a Collection?

A collection is a type that aggregates objects; it acts as a container for a set of objects. You can create an instance of a collection class and add objects to the collection. You can then access those items by using methods that the collection class provides. Collection classes may appear to be similar to arrays, but in fact, a collection class serves a different purpose to an array and in most circumstances is more flexible. When you define an array, you specify the type of data that the array stores and the size of the array. An array is type-safe, but has a big limitation; when you define an array, you must specify how many items the array can hold. This quantity may not be easy to determine in advance. If you specify too large an array, you will use too much memory; if you specify too small an array, you will run out of space. Arrays work well when you know exactly how many values you need to store. Collections are much more flexible. When you create an instance of a collection class, you do not need to specify the size of the collection. The collection dynamically grows and shrinks according to the volume of data that it stores. This dynamic space management makes collections very powerful; if you used arrays, instead of collections, and needed the array to resize, you would need to write significantly more code to manage the array size. The disadvantage of arrays is the overhead that is associated with performing dynamic memory management. However, you can mitigate this overhead to some extent when you initialize an array; you can specify an initial size that should match the most common requirements for your application.

Object Types in Collections


When you use an array to store several objects, you explicitly specify the type of the data that you want to store in the array. For example, you may have an array of integers or an array of strings. When you store values in the array, or retrieve a value from the array, it is strictly type-safe. If you attempt to store or retrieve data of the wrong type, the compiler detects this problem and fails with an error. When you use a collection, you do not specify the type of data to store. The collection classes store references to other objects by using the System.Object type. This feature enables you to construct collections that store mixed types; for example, you can store String objects and Integer values in the

Using Collections and Building Generic Types

12-5

same collection. When you retrieve an item from the collection, you must cast the item to the appropriate type.

Dimensions in Collections and Arrays


When you define an array, you can specify that it has more than one dimension. Collections do not have dimensions. However, you can imitate a multidimensional collection by storing collections in a collection. Question: You are developing an application that maintains a rolling buffer of 10 readings taken by a device. Would you use an array or a collection to store the values?

12-6

Programming in Visual Basic with Microsoft Visual Studio 2010

Using Collection Classes

The .NET Framework defines a set of collection classes in the System.Collections namespace. Most collections have some similarities; they all implement the ICollection interface. This interface defines a small number of methods and properties, including: CopyTo. This method enables you to copy the contents of a collection to an array. GetEnumerator. This method returns an object called an enumerator that you can use to iterate through the items in the collection. Count. This is a property that indicates the current number of items in the collection.

Some collections also implement the IList interface. This interface defines members that enable you to access the elements in the collection by using array-like notation, in addition to adding and removing members by using methods called Add and Remove. In addition to the members that the ICollection and IList interfaces require, most collection classes expose specific methods and members that underpin their functionality. The names for these methods are not restricted and are normally closely related to the purpose of the collection. For example, the Queue class exposes Enqueue and Dequeue to add and remove items from the queue in a first-in, first-out (FIFO) manner. The Stack class provides Push and Pop methods to enable you to add items in a first-in, last-out (FILO) manner. The collection classes in the System.Collections namespace store System.Object objects, rather than objects of a more specific type. You can add any object to a collection class, but when you retrieve an object from a collection, you must cast the object to its correct type before you can use all the members that the object exposes. If you attempt to cast an object in a collection to the wrong type, your application will throw an InvalidCastException exception. Warning: You should exercise caution when you retrieve objects from a collection to ensure that you cast the objects correctly. Avoid adding objects of different types to the same collection wherever possible.

Using Collections and Building Generic Types

12-7

The following code example shows how to use the ArrayList class (a simple collection class that implements an array that can dynamically resize itself). The ArrayList class implements the IList interface. It provides the Add method that you can use to add an object to the end of the collection, and the RemoveAt method that you can use to delete an item from the collection at a specified position. You can also use the Remove method to search through the collection and delete the first occurrence of a specified item from the collection. As you add items, the collection can grow automatically.
' Create a new ArrayList object Dim list As New ArrayList() ' Add items to the ArrayList collection list.Add(3) list.Add(4) list.Add(6) ' Add a string to the ArrayList collection ' The ArrayList stores objects, not specific ' types so you can add any type to the collection list.Add("String Object") ' Remove an object from the ArrayList collection ' by specifying the object to remove list.Remove(6) ' Remove an object from the ArrayList collection ' by specifying the index from which to remove the item list.RemoveAt(1) ' Use an indexer to access a specific item in the ' collection. Convert the object to its correct type Dim temp As Integer = CType(list(0), Integer)

Iterating Through a Collection


All collections implement the ICollection interface, and this interface defines a method called GetEnumerator. This method returns an object called an enumerator that you can use to quickly iterate through all of the elements in a collection. Visual Basic provides the For Each statement that you can use for this purpose. The For Each statement automatically obtains the enumerator for a collection, and uses this enumerator to fetch each item of the collection in turn, as the following code example shows.
Dim list As New ArrayList() list.Add(99) list.Add(10001) list.Add(25) ... For Each i As Integer In list Console.WriteLine(i) Next

This example creates and populates an ArrayList object that contains a collection of integer values. The For Each statement displays each of these values in turn, in the order in which they occur in the ArrayList object. The syntax of the For Each statement is shown in the following code example.
For Each <control_variable> As <type> In <collection> <For_Each_statement_body> Next

You define a control variable and specify the type of data in the collection. The control variable is set to each item in the collection in turn, and the statements in the body of the For Each loop are performed for

12-8

Programming in Visual Basic with Microsoft Visual Studio 2010

each item. The scope of the control variable is the For Each statement. Note that it is important to specify the same type as the type of data in the collection; the compiler automatically generates code to cast the data that is retrieved from the collection to this type. If you specify the wrong type, your code will throw an InvalidCastException exception at run time.

Collection Initializers
When you create an instance of a collection class, you typically use the Add method to add items to the collection. If you need to add items to the collection as soon as you have created it, you may often end up with code that resembles the following code example.
Dim al As New ArrayList() al.Add("Value") al.Add("Another Value")

An alternative to writing several statements is to use a collection initializer. A collection initializer has a similar syntax to an object initializer; you define the collection type and then add values to the collection in braces, separated by commas, before the semicolon. The following code example shows how you can use a collection initialize, instead of using the Add method.
Dim al As New ArrayList({"Value", "Another Value"})

Notice how the values are added as part of the constructor. You can also use the From keyword, as shown in the following example.
Dim al As New ArrayList() From {"Value", "Another Value"}

You can combine collection initializers with object initializers to add new objects to a collection. The following code example shows how you can combine object initializers with collection initializers. Notice how the keyword With is used when specifying properties of the type being added as part of the collection initializer.
Dim al2 As New ArrayList( { New Person() With {.Name = "James", .Age = 45}, New Person() With {.Name = "Tom", .Age = 31} })

Note: You can only use collection initializers with collection classes that expose an Add method. The compiler uses the Add method to add objects that are specified in the collection initializer to the collection. Collection classes such as the Queue class, which does not expose an Add method, do not support collection initializers. Question: Are collections type-safe? Question: When you use a For Each statement with a collection based on the SortedList type, in what order will the For Each statement return items from the collection? Question: Can you use a collection initializer with the Stack collection class?

Using Collections and Building Generic Types

12-9

Additional Reading
For more information about collection initializers, see the Object and Collection Initializers Overview (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211569&clcid=0x409

12-10

Programming in Visual Basic with Microsoft Visual Studio 2010

Common Collection Classes

The System.Collections namespace contains several general-purpose collection classes. Each of these collection classes is optimized to implement a specific mechanism for aggregating and accessing data.

The ArrayList Collection Class


The ArrayList collection class is similar to an array. You can add items to the array and retrieve items by using a zero-based index. The ArrayList class dynamically increases in size as you add values to the collection. You can use the Capacity property to get or set the current size of the collection. The ArrayList class does not automatically shrink when you remove items from the collection. If you remove a significant number of items from the collection, you can use the TrimToSize method to reduce the size of the collection. Alternatively, you can set the Capacity property to a lower value. The following code example shows a simple use of the ArrayList class.
' Create a new ArrayList object Dim al As New ArrayList() ' Add values to the ArrayList collection al.Add("Value") al.Add("Value 2") al.Add("Value 3") al.Add("Value 4") ' Remove a specific object from the ArrayList collection al.Remove("Value 2") ' Removes "Value 2" ' Remove an object from a specified index al.RemoveAt(2) ' Removes "Value 4" ' Retrieve an object from a specified index Dim valueFromCollection As String = CType(al(1), String)' Returns "Value 3"

Using Collections and Building Generic Types

12-11

The Queue Collection Class


The Queue class is a FIFO data structure. Rather than expose Add and Remove methods, the Queue class exposes Enqueue and Dequeue methods. When you use the Enqueue method on an object, it is automatically added to the end of the collection; when you use the Dequeue method on an object, it is automatically removed from the start of the collection. You can also use the Peek method to retrieve the first item in the queue without removing it. The Queue class grows automatically as objects are added to the collection. If you need to recover memory from the Queue object by reducing the size of the collection, you can use the TrimToSize method. The following code example shows a simple use of the Queue class.
' Create a new Queue object Dim q As New Queue() ' Add values to the Queue collection q.Enqueue("Value") q.Enqueue("Value 2") q.Enqueue("Value 3") q.Enqueue("Value 4") ' Retrieve an object from the Queue collection (Returns "Value") Dim valueFromCollection As String = CType(q.Dequeue(), String)

The Stack Collection Class


The Stack class is a FILO data structure. The Stack class exposes Push and Pop methods to add and remove items. When you use the Push method to add an object to a Stack collection, the object is added to the start of the collection; when you use the Pop method on an object, it is automatically removed from the start of the collection and returned. Like the Queue class, the Stack class provides the Peek method to return the item at the start of the Stack collection without removing it. The Stack class grows automatically as objects are added to the collection. If you need to recover memory from the Stack object by reducing the size of the collection, you can use the TrimToSize method. The following code example shows a simple use of the Stack class.
' Create a new Stack object Dim stk As New Stack() ' Add values to the Stack collection stk.Push("Value") stk.Push("Value 2") stk.Push("Value 3") stk.Push("Value 4") ' Retrieve a value from the Stack collection without ' removing it from the Stack collection (Returns "Value 4"" Dim peekValueFromCollection As String = CType(stk.Peek(), String) ' Retrieve an object from the Stack collection (Returns "Value 4"" Dim valueFromCollection As String = CType(stk.Pop(), String) ' Retrieve another object from Stack collection (Returns "Value 3") Dim valueFromCollection2 As String = CType(stk.Pop(), String)

12-12

Programming in Visual Basic with Microsoft Visual Studio 2010

The Hashtable Collection Class


The Hashtable class enables you to store key and value pairs in a rapid access collection. When you add an item to a Hashtable class by using the Add method, you must provide both a key and a value. The key must be unique in the collection, but the value can be a duplicate. The Hashtable class stores objects based on the hash value of the key. You retrieve a value from a Hashtable class by using an indexer and specifying the key of the value that you want to retrieve. The Hashtable class hashes the key to identify the location of the required value. The Hashtable class is significantly faster than other collections for retrieving an item from a large collection because it searches fewer items to locate the correct value. However, for smaller collections, the overhead of generating a hash every time you add a value to the collection can actually reduce the performance of your application. Therefore, for smaller collections, you should consider using another collection class. The Hashtable class relies on creating hashes of values that are added to the collection; for this reason, you can only add keys to the collection where the type of the key implements the GetHashCode method. Every object includes a default implementation of the GetHashCode method, which is inherited from the System.Object class. However, you may often need to add a more complex hashing algorithm to any types that you develop. When you develop a hashing algorithm for use with a Hashtable collection class, you should use a case-insensitive algorithm. The following code example shows a simple use of the Hashtable class.
' Create a new Hashtable object Dim hashtbl As New Hashtable() ' Add values to the Hashtable collection hashtbl.Add("Key A", "Value 1") hashtbl.Add("Key B", "Value 2") hashtbl.Add("Key C", "Value 3") hashtbl.Add("Key D", "Value 4") ' Remove an item from the Hashtable collection by specifying the key hashtbl.Remove("Key C") ' Retrieve an item from the Hashtable collection by specifying the key ' (Returns "Value 2") Dim valueFromCollection As String = CType(hashtbl("Key B"), String)

The SortedList Collection Class


Like a Hashtable class, the SortedList collection class stores a collection of key/value object pairs. However, values in the collection are sorted by using the key. If you iterate through the data in a SortedList collection, the data will be presented in key order. The following code example shows a simple use of the SortedList class.
' Create a new SortedList object Dim sortedLst As New SortedList() ' Add values to the SortedList collection sortedLst.Add("Key A", "Value 1") sortedLst.Add("Key B", "Value 2") sortedLst.Add("Key C", "Value 3") sortedLst.Add("Key D", "Value 4") ' Remove an item from the SortedList by specifying the key sortedLst.Remove("Key C")

Using Collections and Building Generic Types

12-13

' Retrieve an item from the SortedList collection by specifying ' the key (Returns "Value 2") Dim valueFromCollection As String = CType(sortedLst("Key B"), String) ' Retrieve an item from the SortedList collection ' by specifying the index (Returns "Value 1") Dim valueFromCollection2 As String = _ CType(sortedLst.GetByIndex(0), String)

Question: When would you use a Hashtable collection?

Additional Reading
For more information about the common collection types, see the Commonly Used Collection Types page at http://go.microsoft.com/fwlink/?LinkId=192963

12-14

Programming in Visual Basic with Microsoft Visual Studio 2010

Demonstration: Using Collections

Demonstration Steps
1. 2. 3. 4. 5. 6. 7. 8. 9. Start Microsoft Visual Studio 2010. Open the Module 12, Demo1 starter solution. Open the Module1.vb file, and then review the Module1 module. Uncomment the code that creates a new SortedList collection named people. Uncomment the code that adds a Person object named Richard to the people collection. This code uses an object initializer. Uncomment the code that creates a Person object named louisa. Uncomment the code that adds the louisa object to the people collection. This uses the Add method to add an existing item to an existing collection. Uncomment the code that retrieves a Person object from the people collection by using the name as an indexer. Note the cast from the Object type to the Person type. Uncomment the code that checks whether the personFromCollection field is Nothing, and if it is not Nothing, writes the information to the screen.

10. Uncomment the code that iterates through every item in the people collection. 11. Run the application. 12. When the application pauses, note the data that is returned from the collection and displayed on the screen, and then press Enter. 13. When the application pauses again, note that the details of two people are displayed on the screen, and then press Enter. 14. Close the application. 15. Close Visual Studio.

Using Collections and Building Generic Types

12-15

Question: What is the advantage of using the SortedList class compared to using a multidimensional array?

12-16

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab A: Using Collections

Objectives
After completing this lab, you will be able to use collection classes in applications that you develop.

Introduction
In this lab, you will use a collection to cache data and optimize an algorithm that a method implements.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

Using Collections and Building Generic Types

12-17

Lab Scenario

In an earlier project, you worked on the software for an engineering device that solved simultaneous equations by using Gaussian elimination. The functionality is fine, but the performance needs to be improved. Analysis has shown that the device frequently provides the same sets of input variables, so you have decided to add a caching capability.

12-18

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 1: Optimizing a Method by Caching Data


In this exercise, you will use a Hashtable collection to implement the memorization pattern in the Gaussian elimination method. When the calculation is complete, the result is stored in the Hashtable collection, together with details of the parameters (the key will be a hash of the parameters, and the data will be a structure that holds the parameter values and the calculated result) before the method returns the result to the caller. If the method is called subsequently, the method first checks the Hashtable collection to determine whether the same parameter values have been used before. If they have, the method returns the result from the Hashtable collection, rather than performing the lengthy, possibly time-consuming calculation. The main tasks for this exercise are as follows: 1. 2. 3. Open the Collections solution. Modify the Gauss class to implement the memoization pattern. Test the solution.

Task 1: Open the Collections solution.


Open Microsoft Visual Studio 2010. Open the Collections solution in the D:\Labfiles\Lab12\LabA\Ex1\Starter folder.

Task 2: Modify the Gauss class to implement the memoization pattern.


In the TestHarness project, display the MainWindow.xaml window. The MainWindow window implements a simple test harness to enable you to test the method that you will use to perform Gaussian elimination. This is a Windows Presentation Foundation (WPF) application that enables a user to enter the coefficients for four simultaneous equations that consist of four variables (w, x, y, and z). It then uses Gaussian elimination to find a solution for these equations. The results are displayed in the lower part of the screen. Review the Task List. In the Task List, locate the TODO - Add a Shared Hashtable task, and then double-click this task.

This task is located in the GaussianElimination project, in the Gauss class. At the top of the Gauss.vb file, at the end of the list of Imports statements, add a statement to bring the System.Text namespace into scope. Remove the comment, and then add code to define a shared Hashtable object named results. At the beginning of the SolveGaussian method, before the statements that create a deep copy of the parameters, add code to ensure that the resultsHashtable object is initialized. Create a new instance of this object, if it is currently Nothing. Add code to generate a hash key that is based on the method parameters, by performing the following tasks. a. b. c. d. Define a new StringBuilder object named hashString. Iterate through the coefficients array, and append each value in the array to the hashStringStringBuilder object. Iterate through the rhs array, and append each value in the array to the hashStringStringBuilder object. Define a new string object named hashValue, and initialize it to the value that the hashString.ToString method returns.

Using Collections and Building Generic Types

12-19

Hint: This procedure generates a hash key by simply concatenating the values that are passed into the method. You can use more advanced hashing algorithms to generate better hashes. The System.Security.Cryptography namespace includes many classes that you can use to implement hashing. Add code to check whether the results object already contains a key that has the value in the hashValue string. If it does, return the value that is stored in the Hashtable collection class that corresponds to the hashValue key. If the results object does not contain the hashValue key, the method should use the existing logic in the method to perform the calculation. Hint: A Hashtable object stores and returns values as objects. You must cast the value that is returned from a Hashtable object to the appropriate type before you work with it. In this case, cast the returned value to an array of Double values. In the Task List, locate the TODO - Store the result of the calculation in the Hashtable task, and then double-click this task.

This task is located near the end of the SolveGaussian method. Remove the comment, and then add code to the method to store the rhsCopy array in the Hashtable object, specifying the hashValue object as the key.

Task 3: Test the solution.


w 2 -3 -2 3 Build the solution and correct any errors. Run the application. In the MainWindow window, enter the following equations, and then click Solve. x 1 -1 1 -1 y -1 2 -2 2 z 1 1 0 -2 Result 8 -11 -3 -5

Observe that the operation takes approximately five seconds to complete. w -2 Verify that the following results are displayed. w=4 x = 17 y = 11 z=6 Modify the third equation to match the following equation, and then click Solve again. x 1 y -2 z 3 Result -3

Observe that this operation also takes approximately five seconds to complete. Verify that the following results are displayed.

12-20

Programming in Visual Basic with Microsoft Visual Studio 2010

w = 2 x = 25 y=7 z = 6 Undo the change to the third equation so that all the equations match those in Step 3, and then click Solve. Observe that this time, the operation takes less time to complete because it uses the solution that was generated earlier. Close the application, and then close Visual Studio.

Using Collections and Building Generic Types

12-21

Lab Review

Review Questions
1. 2. What namespace did you need to bring into scope before you could use the Hashtable class? In the lab, you used a very simple hash to add items to the Hashtable object. How could you create a more complex hash?

12-22

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 2

Creating and Using Generic Types

The collection classes in the System.Collections namespace have one major problemthey are not typesafe. The collection classes store references to System.Object objects, rather than instances of a particular type. This risks introducing bugs into your applications because you might attempt to use invalid casts that the compiler cannot detect, and that cause run-time exceptions in your application. Visual Basic includes generic types that enable you to specify which type you want to use with a class. Using generic classes, you can ensure type safety while still developing a flexible solution. This lesson introduces generic types and how you can use them in your applications.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of generic types. Explain how the compiler uses generic types to implement type-safety. Define a generic type. Specify constraints for a type parameter.

Using Collections and Building Generic Types

12-23

What Are Generic Types?

The basic collection classes do not enable you to specify the type of data that the collection can hold, which can lead to many problems. The code in the following code example will compile, but when you run it, it will throw an InvalidCastException exception.
Dim names As New ArrayList() names.Add("Alice") ... Dim data As Integer = CType(names(0), Integer)

There is also the issue of performance. The built-in collections all hold items as the System.Object type. You can use the Object type to refer to any reference type with little overhead, but if you store value types in a collection, the compiler generates code to box the data. When you retrieve a value type from the collection, the compiler generates code to unbox the data. Boxing and unboxing can impose a significant overhead. To work around these limitations, you need to be able to define collection classes that specify the type of the items that they store. You can do this by using generics.

Generics
A generic type is a type that specifies one or more type parameters. A type parameter is similar to a method parameter except that it specifies a type rather than a value. You specify a type parameter when you define a class by using the keyword Of and the type in brackets after the class name. When you instantiate an object based on a generic type, you specify a type to substitute for the type parameter. The .NET Framework defines generic versions of some of the collection classes in the System.Collections.Generics namespace, including the List type as shown in the following code example.
Public Class List(Of T)

12-24

Programming in Visual Basic with Microsoft Visual Studio 2010

The next code example shows how you can use the generic List collection. The List type represents a strongly-typed list of objects that you can access by using an index. The type parameter for the ages collection is an Integer, and you can only store Integer values in this collection. If you attempt to store a different type, the compiler will find the problem and fail to build the application. In addition, you can retrieve the data from the collection without using a cast or conversion.
Dim ages As New List(Of Integer)() ages.Add(10) ages.Add(25) ... Dim data As Integer = ages(0)' No cast/conversion necessary ... ages.Add("Data") ' Compiler error

Question: What are the benefits of using a generic class compared to a nongeneric class?

Additional Reading
For more information about generics, see the Generic Types in Visual Basic (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211569&clcid=0x409

Using Collections and Building Generic Types

12-25

Compiling Generic Types and Type Safety

The replacement of a type parameter for a specified type is not simply a textual replacement mechanism. Instead, the compiler performs a complete semantic substitution. In the case of the List(Of T) type, you can specify any valid type for the type parameter T, as the following code example shows.
Dim names As New List(Of String) names.Add("John") ... Dim name As String = names(0) Dim listOfLists As New List(Of List(Of String))() listOfLists.Add(names) ... Dim data As List(Of String) = listOfLists(0)

The first example creates a List collection of string objects, while the second creates a List collection that contains List collections of String objects. For the names variable, the compiler also generates the version of the Add method in the following code example.
Public Sub Add(ByVal item As String)

For the listOfLists variable, the compiler generates a different version of the Add method, as the following code example shows.
Public Sub Add(ByVal item As List(Of String))

This means that you cannot call the Add method of either List object and pass a parameter that has the wrong type without the compiler generating an error. The same rationale applies to all the other methods and properties that the generic List class implements. Effectively, when you define a variable by using a generic type, you create an entirely new type, and the compiler generates strongly-typed methods and properties for this type.

12-26

Programming in Visual Basic with Microsoft Visual Studio 2010

This also affects performance when you use a value type as the type parameter. In the following code example, the compiler generates yet another version of the Add method that takes an Integer parameter.
Dim ages As New List(Of Integer)() ages.Add(10) ' The compiler generates the following method: 'Public Sub Add(ByVal item As Integer)

When you call the Add method and provide an integer value, there is no need for the compiler to generate code to box or unbox this value. Question: When the compiler compiles an application that uses a generic type, it generates a concrete version of the generic class. How can you call the concrete version in your application?

Using Collections and Building Generic Types

12-27

Defining a Custom Generic Type

To define a custom generic type, you add one or more type parameters to the type definition. To define type parameters, you add angle brackets immediately after the class name. In the angle brackets, you specify names for each of the type parameters. You can use as many type parameters as necessary in a comma-delimited list. You can then use the names that you specified for your type parameters, instead of concrete types in your class. The following code example shows a custom generic type called PrintableCollection that uses a type parameter called TItem. The intention is that the PrintableCollection type defines a collection of items that can be easily formatted for printing and display purposes. The PrintableCollection class defines a method called Insert that takes a parameter of type TItem. Internally, the class uses TItem just like any other type. In this case, it creates an array of TItem objects, and the Insert method inserts the value that is specified by its parameter into this array.
Class PrintableCollection(Of TItem) Private data() As TItem Private index As Integer ... Public Sub Insert(ByVal item As TItem) ... data(index) = item ... End Sub End Class

You can use your custom generic type by providing a type for the type parameter. The following code example shows how to use the generic PrintableCollection class to create and use a printable collection of Person structures.
Structure Person ... End Structure

12-28

Programming in Visual Basic with Microsoft Visual Studio 2010

... Dim employeeList As New PrintableCollection(Of Person)() Dim employee As New Person(...) employeeList.Insert(employee)

Note: You can define generic structures in addition to generic classes.

Using Collections and Building Generic Types

12-29

Adding Constraints to Generic Types

When you define a generic type, you may need to restrict the types that can be used for the type parameters to ensure that they match certain criteria or conform to specific requirements. For example, if you define the PrintableCollection class that was shown in the previous topic, you can specify that this class can only be used with types that know how to format and print themselves by implementing an interface called IPrintable that defines a method called Print. In Visual Basic, you can use constraints to restrict the types that you can use with your generic classes and other types. To add a constraint, you use the As keyword as part of the class definition. The following code example shows how to specify that the type parameter for the PrintableCollection type must implement the IPrintable interface.
Class PrintableCollection(TItem As IPrintable) Private data() As TItem ... Public Sub PrintItems() For Each item As TItem In data item.Print() ' item implements IPrintable, so it is safe ' to call the Print method. Next End Sub End Class

You can add an As clause for each type parameter that you define, and you can use multiple constraints on each type. You define multiple constraints by separating each constraint with a comma, enclosed in curly brackets {}. The following table summarizes the clauses that you can use to constrain a generic type. Constraint Of T As Structure Description The type argument must be a value type. Any value type, except Nullable, can be specified.

12-30

Programming in Visual Basic with Microsoft Visual Studio 2010

Constraint Of T As Class Of T As New Of T As <base class name>

Description The type argument must be a reference type; this also applies to any class, interface, delegate, or array type. The type argument must have a public default constructor. When it is used together with other constraints, the New constraint must be specified last. The type argument must be, or derive from, the specified base class.

Of T As <interface The type argument must be, or implement, the specified interface. Multiple name> interface constraints can be specified. The constraining interface can also be generic. Of T As U The type argument that is supplied for T must be, or derive from, the argument that is supplied for U. This is called a naked type constraint.

The following code example shows how to constrain two type parameters. The T type parameter must implement the IComparable and IDisposable interfaces. The Y type parameter must directly or indirectly derive from T (or be the same as T), and provide a default constructor.
Class NewClass(Of T As {IComparable, IDisposable}, Y As {T, New}) End Class

Question: How can you ensure that when an instance of a generic class is created, a reference type is used for the type parameter?

Additional Reading
For more information about constraints, see the Type List (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211571&clcid=0x409

Using Collections and Building Generic Types

12-31

Demonstration: Defining a Generic Type

Demonstration Steps
1. 2. 3. Start Visual Studio. Open the Module 12, Demo2 starter solution. Open the Printer.vb file, and then review the Printer class. The Printer class is a generic class that takes a single type parameter named DocumentType. The purpose of the class is to represent a printer that is capable of printing a specific type of document. 4. Uncomment the code that creates a new generic Queue object named printQueue by using the DocumentType type parameter to specify the type of the Queue object. This collection class stores items in a FIFO manner. Uncomment the AddDocumentToQueue method. This method uses the generic type parameter to define the types that can be used as a parameter. Uncomment the PrintDocuments method. This method removes items from the queue and calls the Print method on each item. Open the Module1.vb file, and then review the Module1 module. The Module1 module class currently creates three Report objects and three ReferenceGuide objects. The Report and ReferenceGuide classes both implement the IPrintable interface, but are not related in any other way. 8. 9. Uncomment the code that creates a new instance of the Printer class by specifying the Report type and adds three reports to the print queue. Uncomment the code that calls the PrintDocuments method on the reportPrinter object. This calls the method from the Printer class and removes each item from the print queue.

5. 6. 7.

10. Uncomment the code that creates a new instance of the Printer class by specifying the ReferenceGuide type and adds three reference guides to the print queue.

12-32

Programming in Visual Basic with Microsoft Visual Studio 2010

11. Uncomment the code that calls the PrintDocuments method on the referenceGuidePrinter object. This calls the method from the Printer class and removes each item from the print queue. 12. Run the application. 13. Close the application. 14. Close Visual Studio. Question: What happens if you attempt to use the wrong type when you call a method that uses a generic type?

Using Collections and Building Generic Types

12-33

Lesson 3

Defining Generic Interfaces and Understanding Variance

You can use generics to define interfaces in addition to classes and structures. However, you must remember that the .NET Framework uses strong typing. When you develop a generic interface, the strong typing can be restrictive, preventing casting between seemingly compatible types that implement these interfaces. This restriction is known as invariance. This lesson describes the problems that invariant generic interfaces can cause, and how you can define generic interfaces that can overcome some of these restrictions by using covariance and contravariance.

Lesson Objectives:
After completing this lesson, you will be able to: Define generic interfaces. Explain invariance and how it affects the way in which you can assign references to objects.

12-34

Programming in Visual Basic with Microsoft Visual Studio 2010

Defining Generic Interfaces

In addition to defining generic classes and structures, you can also define generic interfaces. A generic interface is like any other interface, except that you can specify type parameters and use those types in the members that you define in the interface. Like generic classes and structures, generic interfaces can define constraints on type parameters. You can implement a generic interface in a generic class and use a generic interface to reference a class like you would with a normal class and interface. The following code example shows a generic interface that is implemented in a generic class.
Interface IPrinter(Of DocumentType As IPrintable(Of DocumentType)) Sub PrintDocument(ByVal document As DocumentType) Function PreviewDocument(ByVal document As DocumentType) As PrintPreview End Interface Class Printer(Of DocumentType As IPrintable(Of DocumentType)) Public Sub PrintDocument(ByVal document As DocumentType) ' Send document to printer PrintService.Print(CType(document, IPrintable)) End Sub Public Function PreviewDocument(ByVal document As DocumentType) As PrintPreview ' Return a new PrintPreview object Return New PrintPreview(CType(document, IPrintable)) End Function End Class

Question: How can you ensure that types that are used with a generic interface can be compared?

Using Collections and Building Generic Types

12-35

What Is Invariance?

You have previously seen that you can use the Object type to hold a value or reference of any other type. For example, the code in the following code example is completely legal.
Dim myString As String = "Hello" Dim myObject As Object = myString

Remember that in inheritance terms, the String class is derived from the Object class, so all strings are objects. Now consider the generic interface and class in the following code example.
Interface IWrapper(Of T) Sub SetData(ByVal data As T) Function GetData() As T End Interface Class Wrapper(Of T) Implements IWrapper(Of T) Private storedData As T Sub SetData(ByVal data As T) Implements IWrapper(Of T).SetData Me.storedData = data End Sub Function GetData() As T Implements IWrapper(Of T).GetData Return Me.storedData End Function End Class

The Wrapper(Of T) class provides a simple wrapper around a specified type. The IWrapper interface defines the SetData method that the Wrapper(Of T) class implements to store the data, and the

12-36

Programming in Visual Basic with Microsoft Visual Studio 2010

GetData method that the Wrapper(Of T) class implements to retrieve the data. You can create an instance of this class and use it to wrap a string as shown in the following code example.
Dim stringWrapper As New Wrapper(Of String)() Dim storedStringWrapper As IWrapper(Of String) = stringWrapper storedStringWrapper.SetData("Hello") Console.WriteLine("Stored value is {0}",storedStringWrapper.GetData())

The code creates an instance of the Wrapper(Of String) type. It references the object through the IWrapper(Of String) interface to call the SetData method. (The Wrapper(Of T) type implements its interfaces explicitly, so you must call the methods through an appropriate interface reference.) The code also calls the GetData method through the IWrapper(Of String) interface. If you run this code, it generates the message Stored value is Hello. Now look at the line of code in the following code example.
Dim storedObjectWrapper As IWrapper(Of object) = stringWrapper

This statement is similar to the one that creates the IWrapper(Of String) reference in the previous code example, the difference being that the type parameter is Object rather than String. Is this code legal? Remember that all strings are objects (you can assign a string value to an object reference, as shown earlier). However, if you try to run it, it fails at run time with an InvalidCastException exception. The problem is that although all strings are objects, the converse is not true; not all objects are strings. If this statement was allowed, you could write similar code to that shown in the following code example, which ultimately attempts to store a Window object in a String field (the Window type is the class used to build a window in a WPF application).
Dim storedObjectWrapper As IWrapper(Of Object) = CType(stringWrapper, IWrapper(Of Object)) Dim myWindow As New Window() storedObjectWrapper.SetData(myWindow)

The IWrapper(Of T) interface is said to be invariant. You cannot assign an IWrapper(Of A) object to a reference of type IWrapper(Of B), even if type A is derived from type B. By default, Visual Basic implements this restriction to ensure the type-safety of your code. Question: When you define a generic interface, by default, is it invariant, contravariant, or covariant?

Additional Reading
For more information about variant generic interfaces, see the Creating Variant Generic Interfaces (C# and Visual Basic) page at http://go.microsoft.com/fwlink/?LinkId=192968

Using Collections and Building Generic Types

12-37

Lesson 4

Using Generic Methods and Delegates

Generic classes that consume other classes in a type-safe manner are a powerful feature of the .NET Framework. However, you may also need to develop generic methods or delegates. Generic methods and delegates are similar to generic classes in that developers who write consuming applications must specify a type parameter when they use a method or delegate. This lesson introduces you to the generic delegates that the .NET Framework provides, and how to use generic methods and delegates in your applications.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of generic methods and delegates. Use the common generic delegate types that the .NET Framework provides. Define generic methods. Use generic methods.

12-38

Programming in Visual Basic with Microsoft Visual Studio 2010

What Are Generic Methods and Delegates?

Like generic types, generic methods and delegates accept a type parameter, which you can use in the parameter list and return type for the method or delegate. Generic methods enable you to define methods that perform an action on an object while maintaining type safety. The following code example shows a simple method that adds a report to a queue for printing.
Sub AddToQueue(ByVal rpt As Report) printQueue.Add(rpt) End Sub

You also require a method to add a reference guide (a different type of document) to a print queue. You can define the method as in the following code example.
Sub AddToQueue(ByVal refGuide As ReferenceGuide) printQueue.Add(refGuide) End Sub

Although this approach is type-safe, it adds significant extra work for the developer and risks introducing new bugs, or potentially duplicating any bugs that already exist. Using a generic method with a type parameter for the parameter removes this duplication while maintaining type safety; you define the type for the method when you develop the application, and the application will not compile if you attempt to use an incompatible type with the method. The following code example shows a generic method that provides type-safe functionality while reducing the amount of development by the developer and reducing duplicate code and the associated risks of introducing bugs.
Sub AddToQueue(Of T)(ByVal document As T) printQueue.Add(report) End Sub

Using Collections and Building Generic Types

12-39

Although you can use generic methods where you want to provide the method implementation, there may be times when you want to define a delegate based on unknown types. You can define a generic delegate by using type parameters that are similar to using type parameters in a method declaration. Question: When should you use a generic method?

12-40

Programming in Visual Basic with Microsoft Visual Studio 2010

Using the Generic Delegate Types Included in the .NET Framework

The .NET Framework includes several built-in generic delegates. You should use the built-in delegates wherever appropriate in your code, instead of defining custom delegates. The two main generic delegates are the Action and Func delegates.

The Action Delegate


The Action delegate is a delegate that you can use with method calls that do not return a value. You can use the Action delegate, instead of declaring custom delegates for methods with no return type. The .NET Framework includes several generic overloads of the Action delegate. These overloads enable you to specify the types of parameters that are passed to the delegate; with each overload, there are an increased number of parameters that you can specify. The Action delegate has overloads to support between zero and 16 parameters (in previous versions of the .NET Framework, the Action delegate only supported up to four parameters). The following code example shows how to use the Action delegate with a simple lambda expression.
' Define a delegate by using the generic Action delegate Dim myDelegate As Action(Of String, Integer) ' Add a handler for the delegate by using a lambda expression myDelegate = Sub(param1, param2) Console.WriteLine("{0} : {1}", param1, _ param2.ToString()) End Sub ' Check if a handler has been assigned If Not myDelegate Is Nothing Then ' Invoke the delegate myDelegate("Value", 5) End If

Using Collections and Building Generic Types

12-41

The Func Delegate


The Func delegate is very similar to the Action delegate, but with one major difference: the Func delegate returns a value. When you use the Func delegate, you must always specify the type of the return value, in addition to the types of parameters. Like the Action delegate, the Func delegate supports between zero and 16 parameters. When you use a Func delegate, the last type parameter is always the type of the return value. The following code example shows how to use the Func delegate with a simple lambda expression.
' Define a delegate by using the generic Func generic delegate Dim myDelegateAs Func(Of String, Integer, String)= Nothing ' Add a handler for the delegate by using a lambda expression. myDelegate = Function(param1, param2) Return String.Format("{0} : {1}", param1, _ param2.ToString()) End Function ' Check if a handler has been assigned If Not myDelegate Is Nothing Then Dim returnedValue As String returnedValue = myDelegate("Value", 5) Console.WriteLine(returnedValue) End If

Note: The Action and Func delegates use contravariance for input type parameters. For the result output type parameter, the Func delegate uses covariance. Variance in the Action and Func delegates is new in the .NET Framework 4. Question: When might you choose not to use the generic delegates that the .NET Framework provides?

12-42

Programming in Visual Basic with Microsoft Visual Studio 2010

Defining a Generic Method

To define a generic method, you use a type parameter that is enclosed in angle brackets, before you specify the parameter list. When you use type parameters, you specify an identifier for each type parameter that your method requires. You can use these identifiers, instead of a concrete type in the parameter list for the method, the return type, and anywhere in the method body that it is required. You can add constraints to the type parameters by using the same syntax as you would use for constraining the types that you can use for a generic class. The following code example shows a generic method that has two type parameters, and includes a constraint on the ResultType type parameter.
Function MyMethod(Of Parameter1Type, ResultType As New)( _ ByVal param1 As Parameter1Type) As ResultType Dim result As New ResultType() Return result End Function

Question: Can you use variance when you define a generic method?

Using Collections and Building Generic Types

12-43

Using Generic Methods

You use a generic method like any other method, except that you must provide type parameters in addition to other parameters. When you use a generic method, the compiler checks the types that you are using with the method to ensure that the method is type-safe. For example, if you specify that you are using a string type and pass an integer to the method, the compiler will return an error as if you had attempted to pass the wrong parameter to a normal method. The following code example shows how to invoke a generic method by specifying the type parameter.
' Generic method Function PerformUpdate(Of T)(ByVal input As T) As T Dim output As T = ... ' Update parameter Return output End Function ... ' Generic method usage. Dim result As String = PerformUpdate(Of String)("Test") Dim result2 As Integer = PerformUpdate(Integer)(1)

When you compile an application that uses a generic method, the compiler generates versions of the generic method for each combination of type parameters that your application uses. The following code example shows how the compiler converts the generic method from the previous code example to nongeneric methods.
Function PerformUpdate(ByVal input As String) As String Dim output As String = ... ' Update parameter Return output End Function Function PerformUpdate(ByVal input As Integer) As Integer Dim output As Integer = ... ' Update parameter

12-44

Programming in Visual Basic with Microsoft Visual Studio 2010

Return output End Function

When the compiler compiles your application, it generates the equivalents of these methods to Microsoft intermediate language (MSIL); the compiler converts your generic method calls to calls to the concrete methods. You cannot call these methods directly. Question: When you define a generic method, how many type parameters can you specify?

Using Collections and Building Generic Types

12-45

Demonstration: Defining a Generic Delegate

Demonstration Steps
1. 2. 3. Start Visual Studio. Open the Module 12, Demo3 starter solution. Open the Printer.vb file, and then review the Printer class. The Printer class is a generic class that takes a single type parameter named DocumentType. The purpose of the class is to represent a printer that is capable of printing a specific type of document. 4. 5. 6. 7. Uncomment the code that defines the DocumentAddingToQueueDelegate type. Note that the delegate includes a type parameter. Uncomment the code that defines the DocumentAddingToQueue event. Note that the event uses the generic delegate that you defined in the previous step. Uncomment the OnDocumentAddingToQueue method. This method is used to raise the event. In the AddDocumentToQueue method, uncomment the code that raises the OnDocumentAddingToQueue event and uses the response to determine whether to add the item to the print queue. Open the Module1.vb file, and then review the Module1 module. Uncomment the code that adds a handler for the OnDocumentAddingToQueue event, and uncomment the handler method. The handler method displays a message box and returns a value based on the response.

8. 9.

10. Run the application. 11. In the Reference Guide Printing dialog box, click OK. 12. In the Reference Guide Printing dialog box, click Cancel. 13. In the Reference Guide Printing dialog box, click OK.

12-46

Programming in Visual Basic with Microsoft Visual Studio 2010

14. When the application pauses, verify that only two documents have printed, because you canceled the second document. 15. Close the application. 16. Open the Printer.vb file. 17. Uncomment the DocumentPrintedEventArgs generic class, and notice how this class inherits from the EventArgs class and exposes a Document property. This will be used in an event that will notify subscribers that a document has been printed, and provide the document that printed as an argument. 18. Uncomment the code that defines the DocumentPrinted event by using the Action generic delegate. This event uses an instance of the Printer generic class and an instance of the DocumentPrintedEventArgs generic class as parameters. 19. Uncomment the OnDocumentPrinted method. This method raises the DocumentPrinted event. 20. In the PrintDocuments method, uncomment the code that calls the OnDocumentPrinted method. This raises the DocumentPrinted event whenever a document is printed. 21. Open the Module1.vb file, and then review the Module1 module. 22. Uncomment the code that adds a handler for the DocumentPrinted event, and uncomment the handler method. The handler method displays a message box. 23. Run the application. 24. In the Reference Guide Printing dialog box, click OK. 25. In the Reference Guide Printing dialog box, click Cancel. 26. In the Reference Guide Printing dialog box, click OK. 27. When the application pauses, verify that only two documents printed, because you canceled the second document, and then press Enter. 28. In the Report Printed dialog box, click OK three times. 29. When the application pauses, verify that only three documents printed. 30. Close the application. 31. Close Visual Studio. Question: How does defining a generic delegate differ from defining a nongeneric delegate?

Using Collections and Building Generic Types

12-47

Lab B: Building Generic Types

Objectives:
After completing this lab, you will be able to: Define a generic interface. Implement a generic interface. Develop a simple application to test a generic interface. Implement a generic method.

Introduction
In this lab, you will build a generic collection type and write a generic method that can be used to populate an instance of this type.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

12-48

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Scenario

One of the devices that Fabrikam, Inc. produces captures a large amount of data and has to sort this data. You have been asked to design and build the software that can store the data that the device captures, and present it in an ordered manner as quickly as possible. The software must be able to store data of any type that supports valid comparisons. The software implements an ordered binary tree, with methods that insert data into the appropriate place so that when the tree is traversed, the data is presented in a sorted order. A binary tree is a recursive (self-referencing) data structure that can either be empty or contain three elements: a datum, which is typically referred to as the node, and two subtrees, which are themselves binary trees. The two subtrees are conventionally called the left subtree and the right subtree because they are typically depicted to the left and right of the node, respectively. Each left subtree or right subtree is either empty or contains a node and other subtrees. In theory, the whole structure can continue endlessly. The following image shows the structure of a small binary tree.

Using Collections and Building Generic Types

12-49

Binary trees are ideal for sorting data. If you start with an unordered sequence of objects of the same type, you can construct an ordered binary tree and then walk through the tree to visit each node in an ordered sequence. The following code example shows the algorithm for inserting an itemI into an ordered binary treeT.
If the tree, T, is emptyThen Construct a new tree T with the new item I as the node, and empty left and right subtrees Else Examine the value of the current node, N, of the tree, T If the value of N is greater than that of the new item, IThen If the left subtree of T is emptyThen Construct a new left subtree of T with the item I as the node, and empty left and right subtrees Else Insert Iinto the left subtree of T End If Else If the right subtree of T is emptyThen Construct a new right subtree of T with the item I as the node, and empty left and right subtrees Else Insert Iinto the right subtree of T End If End If End If

Notice that this algorithm is recursive, calling itself to insert the item into the left or right subtree depending on how the value of the item compares with the current node in the tree.

12-50

Programming in Visual Basic with Microsoft Visual Studio 2010

Note: The definition of the expression greater than depends on the type of data in the item and node. For numeric data, greater than can be a simple arithmetic comparison; and for text data, it can be a string comparison. However, other forms of data must be given their own means of comparing values. In this exercise, you will use the CompareTo method of the IComparable interface to compare elements. If you start with an empty binary tree and an unordered sequence of objects, you can iterate through the unordered sequence, inserting each object into the binary tree by using this algorithm, resulting in an ordered tree. The following image shows the steps in the process for constructing a tree from a set of five integers.

After you have built an ordered binary tree, you can display its contents in sequence by visiting each node in turn and printing the value found. The algorithm for achieving this task is also recursive, as the following code example shows.
If the left subtree is not empty Then Display the contents of the left subtree End If Display the value of the node If the right subtree is not emptyThen Display the contents of the right subtree End If

The following image shows the steps in the process for displaying the tree. Notice that the integers are now displayed in ascending order.

Using Collections and Building Generic Types

12-51

12-52

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 1: Defining a Generic Interface


In this exercise, you will define an interface for a generic binary tree called IBinaryTree. The interface will specify the following methods: Add. This method will add an item to the tree. Remove. This method will remove the first item with the specified value from the tree. WalkTree. This method will display the contents of the tree, in sorted order.

The main tasks for this exercise are as follows: 1. 2. Open the GenericTypes solution. Define the generic IBinaryTree interface.

Task 1: Open the GenericTypes solution.


Open Microsoft Visual Studio 2010. Import the code snippets from the D:\Labfiles\Lab12\LabB\Snippets folder. Open the GenericTypes solution in the D:\Labfiles\Lab12\LabB\Ex1\Starter folder.

Task 2: Define the generic IBinaryTree interface.


Review the Task List. In the Task List, locate the TODO: Define the IBinaryTree interface task, and then double-click this task. This task is located in the IBinaryTree.vb file. Define a new generic public interface named IBinaryTree. This interface should take a single type parameter named TItem. Specify that the type parameter must implement the generic IComparable interface. In the IBinaryTree interface, define the following public methods. An Add method, which takes a TItem object named newItem as a parameter and does not return a value A Remove method, which takes a TItem object named itemToRemove as a parameter and does not return a value A WalkTree method, which takes no parameters and does not return a value

Build the solution and correct any errors.

Using Collections and Building Generic Types

12-53

Exercise 2: Implementing a Generic Interface


In this exercise, you will create the generic binary tree type called BinaryTree that implements the IBinaryTree interface. The main tasks for this exercise are as follows: 1. 2. Open the GenericTypes solution. Create the Tree class.

Task 1: Open the GenericTypes solution.


Open the GenericTypes solution in the D:\Labfiles\Lab12\LabB\Ex2\Starter folder. This solution is a modified version of the solution from Exercise 1.

Task 2: Create the Tree class.


In the BinaryTree project, add a new class named Tree. Modify the Tree class definition. This class should be a public generic class that takes a single type parameter named TItem and implements the IBinaryTree interface. The TItem type parameter must implement the generic IComparable interface. Add the following automatic properties to the Tree class. A TItem property named NodeData A generic Tree(Of TItem) property named LeftTree A generic Tree(Of TItem) property named RightTree

Add a public constructor to the Tree class. The constructor should take a single TItem parameter named nodeValue. The constructor should initialize the NodeData member by using the nodeValue parameter, and then set the LeftTree and RightTree members to Nothing. After the constructor, define a method named Add. This method should take a TItem object as a parameter, but not return a value. In the Add method, add code to insert the newItem object into the tree in the appropriate place by performing the following tasks. You can type this code manually, or use the Mod12Add code snippet. Compare the value of the newItem object with the value of the NodeData property. Both items implement the IComparable interface, so use the CompareTo method of the NodeData property. The CompareTo method returns zero if both items have the same value, a positive value if the value of the NodeData property is greater than the value of the newItem object, and a negative value if the value of the NodeData property is less than the value of the newItem object. If the value of the newItem object is less than the value of the NodeData property, perform the following actions to insert a newItem object into the left subtree. If the LeftTree property is Nothing, initialize it and pass the newItem object to the constructor. If the LeftTree property is not Nothing, recursively call the Add method of the LeftTree property and pass the newItem object as the parameter. If the value of the newItem object is greater than or equal to the value of the NodeData property, perform the following actions to insert the newItem object into the right subtree. If the RightTree property is Nothing, initialize it and pass the value of the newItem object to the constructor.

12-54

Programming in Visual Basic with Microsoft Visual Studio 2010

If the RightTree property is not Nothing, recursively call the Add method of the RightTree property and pass the newItem object as the parameter.

After the Add method, add another public method named WalkTree that does not take any parameters and does not return a value. In the WalkTree method, add code that traverses each node in the tree in order and displays the value that each node holds by performing the following tasks. You can type this code manually, or use the Mod12WalkTree code snippet. If the value of the LeftTree property is not Nothing, recursively call the WalkTree method on the LeftTree property. Display the value of the NodeData property to the console by using a Console.WriteLine statement. If the value of the RightTree property is not Nothing, recursively call the WalkTree method on the RightTree property.

After the WalkTree method, add the Remove method to delete a value from the tree, as the following code example shows. It is not necessary for you to fully understand how this method works, so you can either type this code manually, or use the Mod12Remove code snippet.
Public Sub Remove(ByVal itemToRemove As TItem) Implements IBinaryTree(Of TItem).Remove ' Cannot remove null. If itemToRemove Is Nothing Then Return ' Check if the item could be in the left tree. If Me.NodeData.CompareTo(itemToRemove) > 0 AndAlso Not Me.LeftTree Is Nothing

Then

' Check the left tree. ' Check 2 levels down the tree - cannot remove ' 'this', only the LeftTree or RightTree properties. If Me.LeftTree.NodeData.CompareTo(itemToRemove) = 0 Then ' The LeftTree property has no children - set the ' LeftTree property to null. If Me.LeftTree.LeftTree Is Nothing AndAlso Me.LeftTree.RightTree Is Nothing Then Me.LeftTree = Nothing Else ' Remove LeftTree. RemoveNodeWithChildren(Me.LeftTree) End If Else ' Keep looking - call the Remove method recursively. Me.LeftTree.Remove(itemToRemove) End If End If ' Check if the item could be in the right tree.? If Me.NodeData.CompareTo(itemToRemove) < 0 AndAlso Not Me.RightTree Is Nothing

Then

' Check the right tree. ' Check 2 levels down the tree - cannot remove ' 'this', only the LeftTree or RightTree properties. If Me.RightTree.NodeData.CompareTo(itemToRemove) = 0 Then ' TheRightTree property has no children set the ' RightTree property to null. If Me.RightTree.LeftTree Is Nothing AndAlso Me.RightTree.RightTree Is Nothing Then Me.RightTree = Nothing Else ' Remove the RightTree. RemoveNodeWithChildren(Me.RightTree) End If

Using Collections and Building Generic Types

12-55

' Keep looking - call the Remove method recursively. Me.RightTree.Remove(itemToRemove) End If End If ' This will only apply at the root node. If Me.NodeData.CompareTo(itemToRemove) = 0 Then ' No children - do nothing, a tree must have at least ' one node. If Me.LeftTree Is Nothing AndAlso Me.RightTree Is Nothing Then Return Else ' The root node has children. RemoveNodeWithChildren(Me) End If End If End Sub

Else

After the Remove method, add the RemoveNodeWithChildren method to remove a node that contains children from the tree, as the following code example shows. This method is named by the Remove method. Again, it is not necessary for you to understand how this code works, so you can either type this code manually, or use the Mod12RemoveNodeWithChildren code snippet.
Private Sub RemoveNodeWithChildren(ByVal node As Tree(Of TItem)) ' Check whether the node has children. If node.LeftTreeIs Nothing AndAlso node.RightTree Is Nothing Then Throw New ArgumentException("Node has no children") End If ' The tree node has only one child - replace the ' tree node with its child node. If node.LeftTreeIs Nothing Xor node.RightTree Is Nothing Then If node.LeftTreeIs Nothing Then node.CopyNodeToThis(node.RightTree) Else node.CopyNodeToThis(node.LeftTree) End If Else ' The tree node has two children - replace the tree node's value ' with its "in order successor" node value and then remove the ' in order successor node. ' Find the in order successor the leftmost descendant of ' its RightTree node. Dim successor As Tree(Of TItem) = GetLeftMostDescendant(node.RightTree) ' Copy the node value from the in order successor. node.NodeData = successor.NodeData ' Remove the in order successor node. If node.RightTree.RightTree Is Nothing AndAlso node.RightTree.LeftTree Is Nothing Then node.RightTree = Nothing ' The successor node had no ' children. Else node.RightTree.Remove(successor.NodeData) End If End If End Sub

After the RemoveNodeWithChildren method, add the CopyNodeToThis method, as the following code example shows. The RemoveNodeWithChildren method calls this method to copy another node's property values into the current node. You can either type this code manually, or use the Mod12CopyNodeToThis code snippet.

12-56

Programming in Visual Basic with Microsoft Visual Studio 2010

Private Sub CopyNodeToThis(ByVal node As Tree(Of TItem)) Me.NodeData = node.NodeData Me.LeftTree = node.LeftTree Me.RightTree = node.RightTree End Sub

After the CopyNodeToThis method, add the GetLeftMostDescendant method, as the following code example shows. The RemoveNodeWithChildren method also calls this method to retrieve the leftmost descendant of a tree node. You can either type this code manually, or use the Mod12GetLeftMostDescendant code snippet.
Private Function GetLeftMostDescendant(ByVal node As Tree(Of TItem)) As Tree(Of TItem) While Not node.LeftTree Is Nothing node = node.LeftTree End While Return node End Function

Build the solution and correct any errors.

Using Collections and Building Generic Types

12-57

Exercise 3: Implementing a Test Harness for the BinaryTree Project


In this exercise, you will modify the test harness to use the BinaryTree project. The main tasks for this exercise are as follows: 1. 2. 3. 4. Open the GenericTypes solution. Import the TestHarness project. Complete the test harness. Test the BinaryTree project.

Task 1: Open the GenericTypes solution.


Open the GenericTypes solution in the D:\Labfiles\Lab12\LabB\Ex3\Starter folder.

Task 2: Import the TestHarness project.


Import the TestHarness project in the D:\Labfiles\Lab12\LabB\Ex3\Starter\TestHarness folder into the GenericTypes solution. Set the TestHarness project as the startup project.

Task 3: Complete the test harness.


Open the Module1.vb file. At the top of the Module1.vb file, add a statement to bring the BinaryTree namespace into scope. In the Main method, add code to instantiate a new IBinaryTree object named tree, using Integer as the type parameter. Pass the value 5 to the constructor. This code creates a new binary tree of integers and adds an initial node that contains the value 5. Add code to the Main method to add the following values to the tree, in the following order, 1, 4, 7, 3, 4. Add code to the Main method to perform the following actions. Print the message "Current Tree: " to the console, and then invoke the WalkTree method on the tree object. Print the message "Add 15" to the console, and then add the value 15 to the tree. Print the message "Current Tree: " to the console, and then invoke the WalkTree method on the tree object. Print the message "Remove 5" to the console, and then remove the value 5 from the tree. Print the message "Current Tree: " to the console, and then invoke the WalkTree method on the tree object. Pause at the end of the method until Enter is pressed.

Build the solution and correct any errors.

Task 4: Test the BinaryTree project.


Run the application. Verify that the output in the console window resembles the following code example. Note that the data in the binary tree is sorted and is displayed in ascending order.
Current Tree: 1 3

12-58

Programming in Visual Basic with Microsoft Visual Studio 2010

4 4 5 7 Add 15 Current Tree: 1 3 4 4 5 7 15 Remove 5 Current Tree: 1 3 4 4 7 15

Press Enter to close the console window, and then return to Visual Studio.

Using Collections and Building Generic Types

12-59

Exercise 4: Implementing a Generic Method


In this exercise, you will define a generic method called BuildTree that creates an instance of the generic binary tree. The method will take a ParamArray array of a type that a type parameter specifies, and construct the binary tree by using the data in this array. The binary tree will be returned from the method. The main tasks for this exercise are as follows: 1. 2. 3. Open the GenericTypes solution. Create the BuildTree method. Modify the test harness to use the BuildTree method.

Task 1: Open the GenericTypes solution.


Open the GenericTypes solution in the D:\Labfiles\Lab12\LabB\Ex4\Starter folder.

Note: The GenericTypes solution in the Ex4 folder is functionally the same as the code that you completed in Exercise 3. However, it includes an updated task list and a new test project to enable you to complete this exercise.

Task 2: Create the BuildTree method.


Review the Task List. In the Task List, locate the TODO: - Add the BuildTree generic method task, and then double-click this task. This task is located at the end of the Tree class. Remove the TODO: - Add the BuildTree generic method comment, and then add a public shared generic method named BuildTree to the Tree class. The type parameter for the method should be named TreeItem, and the method should return a generic Tree(Of TreeItem) object. The TreeItem type parameter must represent a type that implements the generic IComparable interface. The method should take two parameters: a TreeItem object named nodeValue and a ParamArray array of TreeItem objects named values. In the BuildTree method, add code to construct a new Tree object by using the data that is passed in as the parameters by performing the following actions. Define a new Tree object named integerTree by using the TreeItem type parameter, and initialize the new Tree object by using the nodeValue parameter. Iterate through the values array, and add each value in the array to the integerTree object. Return the testTree object at the end of the method.

Task 3: Modify the test harness to use the BuildTree method.


In the Task List, locate the TODO: - Modify the test harness to use the BuildTree method task, and then double-click this task. This task is located in the Main method of the Module1.vb class file in the TestHarness project. In the Main method, remove the existing code that instantiates the tree object and adds the first five values to the tree. Replace this code with a statement that calls the BuildTree method to create a new Tree object named integerTree, based on the integer type, with the following integer values, 1, 4, 7, 3, 4, and 5. Rename current uses of the tree object to integerTree. Build the solution and correct any errors.

12-60

Programming in Visual Basic with Microsoft Visual Studio 2010

Run the application. Verify that the output in the console window resembles the following code example.
Current Tree: 1 3 4 4 5 7 Add 15 Current Tree: 1 3 4 4 5 7 15 Remove 5 Current Tree: 1 3 4 4 7 15

Press Enter to close the console window. Close Visual Studio.

Using Collections and Building Generic Types

12-61

Lab Review

Review Questions
1. 2. In the lab, you defined a generic interface with a type parameter. How did you constrain the types that can be used with a class that implements this interface? In the lab, you used the name TItem for the type parameter of the Tree class, and the name TreeItem for the static generic method in the class. Why did you not use the same name in both instances?

12-62

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Review and Takeaways

Review Questions
1. 2. 3. What are the main advantages of using a collection class, instead of an array? How do generic collection classes differ from nongeneric collection classes? When would you use a generic type, instead of a nongeneric type?

Best Practices Related to Collections


Supplement or modify the following best practices for your own work situations: Use collections, instead of arrays, where you do not know the size of the collection in advance. Use Hashtable objects for large key-value pair collections, but avoid them for smaller collections.

Best Practices Related to Generic Types


Supplement or modify the following best practices for your own work situations: Use generic types wherever possible to improve type safety. Use constraints on generic types to provide control over types that are used with your generic classes.

Best Practices Related to Generic Methods and Delegates


Supplement or modify the following best practices for your own work situations: Use the Action and Func generic delegates, instead of custom delegates, wherever possible.

Building and Enumerating Custom Collection Classes

13-1

Module 13
Building and Enumerating Custom Collection Classes
Contents:
Lesson 1: Implementing a Custom Collection Class Lesson 2: Adding an Enumerator to a Custom Collection Class Lab: Building and Enumerating Custom Collection Classes 13-3 13-16 13-27

13-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

When you develop applications, you often need to store collections of objects. In many circumstances, you can use the collection classes that the Microsoft .NET Framework includes; however, sometimes these collection classes do not provide the functionality that you require. For example, you may need to store objects in a sorted order that is based on a custom sorting algorithm. This module introduces you to custom collection classes. It also explains how you can develop collection classes that support the language constructs that Visual Basic provides, such as enumeration and collection initialization.

Objectives:
After completing this module, you will be able to: Implement a custom collection class. Define an enumerator in a custom collection class.

Building and Enumerating Custom Collection Classes

13-3

Lesson 1

Implementing a Custom Collection Class

The .NET Framework provides a range of collection classes that enable you to store and retrieve data by using a variety of semantics. For example, the Queue class enables you to store and retrieve data in a firstin, first-out (FIFO) manner, and the Stack class implements a last-in, first-out (LIFO) mechanism. Other collection classes, such as the Hashtable class, enable you to store data and access it by using a key in the form of a dictionary. If none of the collection classes that the .NET Framework class library provides meet the specific requirements of an application, you can implement your own custom collection class. The Visual Basic language provides several features that are intended to be used with collections. When you develop a custom collection class, you must ensure that you implement the necessary functionality to support these features. For example, you may need to ensure that collection initializers can be used with your collection and you can iterate over the items in a collection. This lesson introduces you to custom collection classes. It also introduces you to the interfaces that the .NET Framework includes, which you can implement to provide this support.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of a custom collection class. Describe the generic interfaces that the .NET Framework defines, which should be implemented by your custom collection classes. Implement a simple collection class. Implement a dictionary collection class.

13-4

Programming in Visual Basic with Microsoft Visual Studio 2010

What Are Custom Collection Classes?

A custom collection class is a class that enables you to store and retrieve a collection of objects and that implement the various interfaces that the .NET Framework defines. By implementing the interfaces that the .NET Framework defines, custom collection classes can provide support for language constructs. For example, to use a collection class with the For Each construct, the collection class must provide an enumerator. The IEnumerable interface defines the GetEnumerator method, and you should implement this interface. In addition, an enumerator must provide methods that the IEnumerator interface defines, which an application can use to move through the items in a collection in an orderly manner. If you must support Language-Integrated Query (LINQ) queries, you should implement the ICollection interface. This interface extends the IEnumerable interface with methods to convert the collection into a queryable object.

Note: LINQ is described in more detail in a later module. Depending on the features that you want to provide in a custom collection class, you can also implement other interfaces. For example, if you must provide access to elements in a collection by using an index, you should implement the IList interface. The .NET Framework includes both generic and nongeneric versions of the collection interfaces. Depending on your requirements, you can choose to develop either a generic or a nongeneric custom collection class. Nongeneric custom collection classes are typically designed to work with a specific type, which may limit their use in other applications. Alternatively, you can design them to store and retrieve System.Object items, which may have an impact on performance and type safety. Unless you have a specific reason to implement a nongeneric collection class, you should normally use a generic collection class. Question: When might you develop a custom collection class?

Building and Enumerating Custom Collection Classes

13-5

Generic Collection Interfaces in the .NET Framework

The .NET Framework includes several interfaces that you should implement when you develop custom collection classes. The .NET Framework provides both generic and nongeneric versions of these interfaces. The principal interfaces that the .NET Framework provides are: The ICollection(Of T) interface. The IEnumerable(Of T) interface. The IList(Of T) interface. The IDictionary(Of TKey, TValue) interface.

The ICollection(Of T) and IEnumerable(Of T) Interfaces


The ICollection(Of T) interface is the base interface for all generic collection classes. You should implement the ICollection(Of T) interface in every collection class that you develop. The ICollection(Of T) interface inherits from the IEnumerable(Of T) interface, which is covered in the next lesson; when you implement the ICollection(Of T) interface, you must also implement the IEnumerable(Of T) interface. The ICollection(Of T) interface defines the following methods: Add. This method should add a new item to the collection. You must expose a public Add method if you need to use collection initializers with your type.

Note: To support collection initializers, your type must also implement the IEnumerable interface. The IEnumerable interface is covered in the next lesson. Clear. This method should remove all items from the collection. Contains. This method should return a value to indicate whether a particular item exists in the collection. CopyTo. This method copies the collection to a specified array.

13-6

Programming in Visual Basic with Microsoft Visual Studio 2010

Remove. This method removes a single item from the collection.

In addition, the ICollection(Of T) interface also defines the following properties: Count. This property indicates how many items are in the collection. IsReadOnly. This property indicates whether the collection is read-only.

The IList(Of T) Interface


The IList(Of T) interface is the base interface for linear collections that enable you to access elements by using an index. The IList(Of T) interface inherits from the ICollection(Of T) interface. The IList(Of T) interface defines the following methods: IndexOf. This method returns a value that indicates the index in the collection of the first occurrence of a particular value. Insert. This method inserts a new item at the specified index in the collection. RemoveAt. This method removes a single item from the collection at the specified index.

In addition, the IList(Of T) interface also defines the Item property. This is the default property or indexer that enables you to get or set the value at a specified index in the collection.

The IDictionary(Of TKey, TValue) Interface


The IDictionary(Of TKey, TValue) interface is the base interface for dictionary-based collections. Dictionary-based collections store key and value pair combinations; when you add an item to a dictionary collection, you must specify a key and the value. The key must be unique in the collection. To retrieve an item from a dictionary collection, you specify the key to use. The IDictionary(Of TKey, TValue) interface inherits from the ICollection(Of T) interface. However, remember that the ICollection(Of T) interface takes a single type parameter, but the IDictionary (Of TKey, TValue) interface takes two type parameters. The .NET Framework provides the KeyValuePair(Of TKey, TValue) generic type to help resolve this mismatch. When you develop a generic collection class, each item in the collection can be represented as a KeyValuePair(Of TKey, TValue) object. You use this type as the type parameter when you implement the ICollection(Of T) interface in a dictionary class. The IDictionary(Of TKey, TValue) interface defines the following methods: Add. This overloaded method adds a new item to the collection with the specified key and value. ContainsKey. This method returns a Boolean value to indicate whether the collection contains an item with a particular key. GetEnumerator. This overloaded method returns an enumerator of KeyValuePair(Of TKey, TValue) objects. The next lesson discusses how to implement enumerators in more detail. Remove. This overloaded method removes an item with a specified key from the collection. TryGetValue. This method returns a Boolean value to indicate whether the method succeeds. If the method is successful, it sets the value of an output parameter to the value that is associated with the specified key.

In addition, the IDictionary(Of T) interface also defines the following properties: Item. This property is an indexer that enables you to get or set the value in the collection, based on the specified key. Keys. This property returns a collection, referenced by using the ICollection(Of T) interface, of the keys in the collection.

Building and Enumerating Custom Collection Classes

13-7

Values. This property returns a collection, referenced by using the ICollection(Of T) interface, of the values in the collection.

Question: When you develop a custom collection class, which interface must you always implement?

Additional Reading
For more information about the ICollection(Of T) interface, see the ICollection(Of T) Interface page at http://go.microsoft.com/fwlink/?LinkId=192970
For more information about the IList(Of T) interface, see the IList(Of T) Interface page at http://go.microsoft.com/fwlink/?LinkId=192971
For more information about the IDictionary(Of TKey, TValue) interface, see the IDictionary(Of TKey, TValue) Interface page at http://go.microsoft.com/fwlink/?LinkId=192972

13-8

Programming in Visual Basic with Microsoft Visual Studio 2010

Implementing a Simple Custom Collection Class

A simple collection class stores single values, instead of key/value pairs. To implement a simple custom collection class, you define a generic class that should implement an appropriate structure to store items. In some situations, you may use the collection types that the .NET Framework includes from within your type; in other scenarios, you may define custom types to store the items that are added to the collection. You must implement the ICollection(Of T) interface in your type. If you build a linear collection, you should also implement the IList(Of T) interface. The following code example shows a simple collection class that implements a double-ended queue. It provides methods that enable you to enqueue and dequeue items from either end. It uses a List(Of T) object internally to store the items in the collection.
Public Class DoubleEndedQueue(Of T) Implements ICollection(Of T), IList(Of T) ' Define a List(Of T) object to store items that are added ' to the collection Private items As List(Of T) ' Add a constructor to the class Public Sub New() items = New List(Of T)() End Sub ' Methods for enqueuing and dequeuing items Public Sub EnqueueItemAtStart(ByVal item As T) ' The implementation details are not shown ... End Sub Public Function DeQueueItemFromStart() As T ' The implementation details are not shown ... End Function

Building and Enumerating Custom Collection Classes

13-9

Public Sub EnqueueItemAtEnd(ByVal item As T) ' The implementation details are not shown ... End Sub Public Function DeQueueItemFromEnd() As T ' The implementation details are not shown ... End Function #Region "ICollection(Of T) Members" ' Define an Add method that adds an item to the collection Public Sub Add(ByVal item As T) Implements ICollection(Of T).Add items.Add(item) End Sub ' Define a Clear method that removes all items from the collection Public Sub Clear() Implements ICollection(Of T).Clear items.Clear() End Sub ' Define a Contains method that returns a bool object ' according to whether the collection contains a particular item Public Function Contains(ByVal item As T) As Boolean _ Implements ICollection(Of T).Contains Return items.Contains(item) End Function ' Define a CopyTo method that copies the entire contents of the ' collection to an array. The array is provided as a parameter, ' and the first item from the collection should be stored in the ' index that is passed as a parameter to the method Public Sub CopyTo(ByVal arr() As T, ByVal arrIndex As Integer) _ Implements ICollection(Of T).CopyTo items.CopyTo(arr, arrIndex) End Sub ' Define a read-only Count property that returns the number of ' items in the collection. Public ReadOnly Property Count As Integer _ Implements ICollection(Of T).Count Return items.Count End Get End Property ' Define a read-only IsReadOnly property that returns whether ' the collection is read-only Public ReadOnly Property IsReadOnly As Boolean _ Implements ICollection(Of T).IsReadOnly Return False End Get End Property ' Define a Remove method that removes the specified item from ' the collection and returns a bool object to indicate whether ' the item was removed successfully Public Function Remove(ByVal item As T) As Boolean _ Implements ICollection(Of T).Remove Get Get

13-10

Programming in Visual Basic with Microsoft Visual Studio 2010

Return items.Remove(item) End Function #End Region #Region "IEnumerable(Of T) Members" ' Define a GetEnumerator method, which returns an ' IEnumerator(Of T) object Public Function GetEnumerator1() As IEnumerator(Of T) _ Implements IEnumerable(Of T).GetEnumerator ' This is not implemented. Enumerators are covered in the ' next lesson Throw New NotImplementedException() End Function #End Region #Region "IEnumerable Members" ' Add an IEnumerable.GetEnumerator method. This is a nongeneric ' version of the GetEnumerator method. Public Function GetEnumerator() As IEnumerator _ Implements IEnumerable.GetEnumerator ' Always return the result of calling the GetEnumerator method Return GetEnumerator() End Function #End Region #Region "IList(Of T) Members" ' Add an IndexOf method that returns the index of the first ' occurrence of an item in the collection Public Function IndexOf(ByVal item As T) As Integer _ Implements IList(Of T).IndexOf Return items.IndexOf(item) End Function ' Add an Insert method that adds an item to the collection at a ' specified index. Public Sub Insert(ByVal index As Integer, ByVal item As T) _ Implements IList(Of T).Insert items.Insert(index, item) End Sub ' Add a RemoveAt method that removes an item from the collection ' at the specified index. Public Sub RemoveAt(ByVal index As Integer) _ Implements IList(Of T).RemoveAt items.RemoveAt(index) End Sub ' Add an indexer that enables read/write access to items in the ' collection, based on the specified index. Default Public Property Item(ByVal idx As Integer) As T _ Implements IList(Of T).Item Get Return items(idx) End Get Set(ByVal value As T) items(idx) = value End Set

Building and Enumerating Custom Collection Classes

13-11

End Property #End Region End Class

Question: You develop an application that stores data in a file in a custom format. The file structure is predefined and uses a structure that is similar to, but not the same as, XML. Data that is stored in the file is stored in a specified order, and the application needs to both read and write to the file. How can you use a custom collection class to make it easier to use the data file in the application?

13-12

Programming in Visual Basic with Microsoft Visual Studio 2010

Implementing a Dictionary Collection Class

You implement a dictionary collection class in almost the same way that you implement a simple collection classthe difference is that you should implement the IDictionary(Of TKey, TValue) interface, instead of the IList(Of T) interface. The IDictionary(Of TKey, TValue) interface inherits from the ICollection(Of T) interface, so you must also implement that interface. The following code example shows a dictionary collection class that uses a Dictionary object as an internal data store. An ordinary Dictionary collection requires that the items that are added to the collection have a unique key value, and the Add method of the IDictionary interface throws an ArgumentException exception if a duplicate key is detected. The IntelligentDictionary collection class in the following code example implements an additional method called AddItem. This method takes the same parameters as the Add method of the IDictionary interface, but if it detects a duplicate key, it generates a new random key and tries again. The key that is used is passed back as the return value.
Public Class IntelligentDictionary(Of TKey, TValue) Implements IDictionary(Of TKey, TValue) ' Define a Dictionary(Of TKey, TValue) object, or some other ' data store to store items that are added to the collection Private items As Dictionary(Of TKey, TValue) ' Define a constructor for the class Public Sub New() items = New Dictionary(Of TKey, TValue)() End Sub ' Add a key/value pair to the dictionary ' Detect a key clash, and generate a new random key if necessary ' Return the key that is used Public Function AddItem(ByVal key As TKey, _ ByVal value As TValue) As TKey ' The implementation details are not shown ...

Building and Enumerating Custom Collection Classes

13-13

End Function #Region "IDictionary(Of TKey, TValue) Members" ' Define an Add method that adds a new item with the specified ' key and value to the collection Public Sub Add(ByVal key As TKey, ByVal value As TValue) _ Implements IDictionary(Of TKey, TValue).Add items.Add(key, value) End Sub ' Define a ContainsKey method that returns a Boolean object ' if the collection contains a particular key Public Function ContainsKey(ByVal key As TKey) As Boolean _ Implements IDictionary(Of TKey, TValue).ContainsKey Return items.ContainsKey(key) End Function ' Define a Keys property that returns a collection of keys ' that are stored in the collection Public ReadOnly Property Keys As ICollection(Of TKey) _ Implements IDictionary(Of TKey, TValue).Keys Return items.Keys End Get End Property ' Define a Remove method that removes an item with the specified ' key from the collection and returns a Boolean value that ' indicates whether the method succeeded Public Function Remove(ByVal key As TKey) As Boolean _ Implements IDictionary(Of TKey, TValue).Remove Return items.Remove(key) End Function ' Define a TryGetValue method that returns a Boolean value to ' indicate whether the method was successful, and if it was, sets ' the value of the output parameter to the value that corresponds ' to the specified key Public Function TryGetValue(ByVal key As TKey, _ ByRef value As TValue) As Boolean _ Implements IDictionary(Of TKey, TValue).TryGetValue Return items.TryGetValue(key, value) End Function ' Define a Values property that returns an ICollection object ' of the values that are stored in the collection Public ReadOnly Property Values As ICollection(Of TValue) _ Implements IDictionary(Of TKey, TValue).Values Return items.Values End Get End Property ' Define an indexer that reads/writes the value for an item in ' the collection, based on the key specified Default Public Property Item(ByVal key As TKey) As TValue _ Implements IDictionary(Of TKey, TValue).Item Get Get

13-14

Programming in Visual Basic with Microsoft Visual Studio 2010

Return items(key) End Get Set(ByVal value As TValue) items(key) = value End Set End Property #End Region #Region "ICollection(KeyValuePair(Of TKey, TValue)) Members" ' Define an overload of the Add method to implement the ' ICollection(Of T) interface. Use KeyValuePair(Of TKey, TValue) ' generic type as the type parameter Public Sub Add(ByVal item As KeyValuePair(Of TKey, TValue)) _ Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Add ' Call the Add method that was already defined by using the ' properties of the item parameter as the parameters for the ' Add method Add(item.Key, item.Value) End Sub ' Define a Clear method that removes all of the items from the ' collection. Public Sub Clear() _ Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Clear items.Clear() End Sub ' Define an overload of the Contains method to implement the ' ICollection(Of T) interface. Use KeyValuePair(Of TKey, TValue) ' generic type as the type parameter Public Function Contains( _ ByVal item As KeyValuePair(Of TKey, TValue)) As Boolean _ Implements ICollection(Of KeyValuePair(Of TKey, _ TValue)).Contains Return CType(items, ICollection( _ Of KeyValuePair(Of TKey, TValue))).Contains(item) End Function ' Define a CopyTo method that copies the items in the ' collection to an array that is passed as a parameter. The array ' has one dimension and stores instances of the ' KeyValuePair(Of TKey, TValue) type, not instances of the TKey or ' TValue types Public Sub CopyTo(ByVal arr As KeyValuePair(Of TKey, TValue)(), _ ByVal arrIndex As Integer) _ Implements ICollection(Of KeyValuePair( _ Of TKey, TValue)).CopyTo CType(items, ICollection(Of KeyValuePair(Of TKey, _ TValue))).CopyTo(arr, arrIndex) End Sub ' Define a Count property that returns the number of items in ' the collection Public ReadOnly Property Count As Integer _ Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Count Get Return items.Count End Get

Get

Building and Enumerating Custom Collection Classes

13-15

End Property ' Define an IsReadOnly property that returns a Boolean value ' to indicate whether the collection is read-only Public ReadOnly Property IsReadOnly() As Boolean _ Implements ICollection(Of KeyValuePair(Of TKey, _ TValue)).IsReadOnly Return False End Get End Property ' Add a Remove method that removes an item from the collection, ' based on the KeyValuePair(Of TKey, TValue) object that is ' passed as a parameter Public Function Remove(ByVal item As KeyValuePair(Of TKey, _ TValue)) As Boolean _ Implements ICollection(Of KeyValuePair(Of TKey, _ TValue)).Remove Return items.Remove(item.Key) End Function #End Region #Region "IEnumerable(Of KeyValuePair(Of TKey, TValue)) Members" ' Define a GetEnumerator method that returns an ' IEnumerator(KeyValuePair(Of TKey, TValue)) object Public Function GetEnumerator1() As _ IEnumerator(Of KeyValuePair(Of TKey, TValue)) _ Implements IEnumerable(Of KeyValuePair(Of TKey, _ TValue)).GetEnumerator ' Enumerators are discussed in the next lesson Throw New NotImplementedException() End Function #End Region #Region "IEnumerable Members" ' Add a IEnumerable.GetEnumerator method to implement the ' nongeneric IEnumerable interface Public Function GetEnumerator() As IEnumerator _ Implements IEnumerable.GetEnumerator ' This should always return the result of calling the generic ' GetEnumerator method Return GetEnumerator() End Function #End Region End Class Get

Question: You develop a dictionary collection class and implement the IDictionary(Of TKey, TValue) interface. You must use the underlying Dictionary(Of TKey, TValue) class as an internal data store. When you implement the CopyTo method that the interface requires, you discover that the generic dictionary class does not expose a public CopyTo method. The generic dictionary class implements the CopyTo method that the interface defines explicitly. Implementing the interface explicitly avoids exposing the CopyTo method when referencing a collection by using the Dictionary(Of TKey, TValue) type. When might you use this approach in collections that you develop?

13-16

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 2

Adding an Enumerator to a Custom Collection Class

Visual Basic provides the For Each statement to enable you to iterate through the contents of a collection. The For Each statement generates an enumerator object for a collection that provides the means to obtain each item in turn from the collection. When you define a custom collection class, you must provide a mechanism that the For Each statement can use to generate this enumerator. This lesson introduces you to enumerators and explains how to implement enumerators in your custom collection classes.

Lesson Objectives:
After completing this lesson, you will be able to: Explain the purpose of an enumerator. Describe the IEnumerable(Of T) interface. Describe the IEnumerator(Of T) interface. Implement an enumerator manually. Implement an enumerator by using an iterator.

Building and Enumerating Custom Collection Classes

13-17

What Is an Enumerator?

An enumerator is a type that provides a method that returns each item in a collection in an order that is appropriate to the collection. With some collections, the order is not important. For example, with a list class, the sequence in which items are added to the list governs the order. In other collection classes, the order is more significant. For example, in the SortedList class, the items are returned in ascending order according to the key that was specified when the items were added to the collection. You can think of an enumerator as a pointer to items in a collection. When you instantiate an enumerator, you can initialize it to refer to the first item in the collection. You can retrieve the value that is found in the collection through this reference, and then you can move the pointer to reference the next item, in sequence. When you use a For Each statement in your application, the For Each statement uses an enumerator that your class generates to retrieve each of the items from the collection. You can implement the enumerator to return items from your collection class in the order that is most appropriate to your collection class. An enumerator provides read-only access to a collection in a single direction. You may want to enable applications to iterate through items in your collection class in more than one order. For example, you may want to support forward and reverse iteration. You can implement multiple enumerators in a collection class. When an application uses the collection class by using a For Each class, it automatically uses the default enumerator for your class, which by convention performs forward iteration. However, you can also manually invoke a named enumerator to enable your application to iterate through the collection in a different sequence. Question: An enumerator provides read-only access to items in a collection. Why would it be inappropriate to permit write access to items that are accessed by using an enumerator?

13-18

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is the IEnumerable(Of T) Interface?

A collection class that supports enumeration must implement the IEnumerable(Of T) interface. The IEnumerable(Of T) interface defines a single method called GetEnumerator. The GetEnumerator method returns an IEnumerator(Of T) object that provides the logic that the For Each statement requires. When you implement the IEnumerable(Of T) interface, the GetEnumerator method should expose the default enumerator for your collection. You can define additional enumerators in your type; however, you must provide additional methods or properties in your collection class to enable consuming applications to access these other enumerators. The IEnumerable(Of T) interface inherits from the IEnumerable interface. The IEnumerable interface is a nongeneric version of the IEnumerable(Of T) interface with its own nongeneric GetEnumerator method. This interface and this method are provided for backward compatibility with older applications that do not use generics. However, if you do not need to provide this degree of support, it is common practice to throw a NotImplementedException exception in the GetEnumerator method of the IEnumerable interface to ensure that applications use the generic, type-safe version of the GetEnumerator method. In addition to exposing an enumerator from your type, you must also implement the IEnumerable interface if your type must support collection initializers. To support collection initializers, you must implement the IEnumerable interface and define an Add method in your type. The following code example shows an implementation of the IEnumerable(Of T) interface in a collection class. The collection class also implements a method called Backwards that generates an enumerator that you can use to iterate through the collection in reverse order. Note that to implement an additional enumerator, you return an IEnumerable(Of T) object. This object can reference the same data as the collection, but it must implement the GetEnumerator method to generate an enumerator that returns the data in the appropriate sequence.
Public Class CustomCollection(Of T)

Building and Enumerating Custom Collection Classes

13-19

Implements IEnumerable(Of T) Public Function Backwards() As IEnumerable(Of T) ' The implementation details are not shown '... End Function #Region "IEnumerable(Of T) Members" Public Function GetEnumerator() As IEnumerator(Of T) _ Implements IEnumerable(Of T).GetEnumerator ' The implementation details are not shown '... End Function #End Region #Region "IEnumerable Members" Public Function GetEnumerator1() As IEnumerator _ Implements IEnumerable.GetEnumerator Throw New NotImplementedException() End Function #End Region ... End Class

The following code example shows how you can use a default enumerator and the Backwards enumerator.
' The CustomCollection class implements the IEnumerable(Of T) ' interface. ' The CustomCollection class also exposes a Backwards method. ' The Backwards method returns an IEnumerable(Of T) instance. Dim intCollection As New CustomCollection(Of Integer)() intCollection.Add(3) intCollection.Add(5) intCollection.Add(8) intCollection.Add(2) intCollection.Add(9) intCollection.Add(1) intCollection.Add(0) ' This For Each statement uses the default enumerator. For Each temp As Integer In intCollection '... Next For Each temp As Integer In intCollection.Backwards() '... Next

Question: How does the For Each statement use the IEnumerable(Of T)interface?

Additional Reading
For more information about the IEnumerable(Of T) interface, see the IEnumerable(Of T) Interface page at http://go.microsoft.com/fwlink/?LinkId=192973

13-20

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is the IEnumerator(Of T) Interface?

The enumerator object that the GetEnumerator method of the IEnumerator(Of T) interface returns must implement the IEnumerator(Of T) interface. This interface defines the following members: Function MoveNext As Boolean ReadOnly Property Current As T Sub Reset

When you instantiate an enumerator, it does not initially reference any items in the collection. You use the MoveNext method to move to the first item in the collection. This method returns a Boolean value: it returns True if it successfully finds an item to reference; otherwise, it returns False. You can then retrieve the item by using the Current property. This property is read-only; you cannot use it to update data. You can then move to the next item by calling the MoveNext method and use the Current property to access this item. You can access the Current property repeatedly without calling the MoveNext method; however, you will obtain multiple references to the same item. The Reset method resets the internal state of the enumerator so that a subsequent call to the MoveNext method will place the enumerator over the first item in the collection again. The fact that the Current property is read-only has an effect on the control variable in a For Each loop. In the following code example, the datum variable is populated by using the Current property. This variable is also read-only, and your code will fail to compile if you attempt to modify it explicitly.
For Each datum As Integer In integerCollectionObject ... datum = datum / 2 ' Compilation error ... Next

Note that it is also an error to access the Current property before you call the MoveNext method at least once after you have created the enumerator or called the Reset method. In this situation, the enumerator should throw an InvalidOperationException exception. Using the Current property should also throw an

Building and Enumerating Custom Collection Classes

13-21

exception if the last call to the MoveNext method returns false, because this indicates that you have moved past the end of the collection. The For Each statement prevents you from doing this accidentally, but you can also access the methods of an enumerator directly; in this case, you should be prepared to catch and handle these exceptions. An enumerator remains valid as long as the collection is unchanged. If the collection changesfor example, elements are added, modified, or deletedthe enumerator is irrecoverably invalidated, and the next call to the MoveNext or Reset method should throw an InvalidOperationException exception. If the collection is modified between calls to the MoveNext method and accessing the Current property, the Current property returns the element that it is set to, even if the enumerator is already invalidated. Question: How does the MoveNext method indicate to the caller that advancing any further would result in an invalid state because the enumerator has reached the end of the collection?

Additional Reading
For more information about the IEnumerator(Of T) interface, see the IEnumerator(Of T) Interface page at http://go.microsoft.com/fwlink/?LinkId=192974

13-22

Programming in Visual Basic with Microsoft Visual Studio 2010

Implementing an Enumerator Manually

When you manually implement the IEnumerator(Of T) interface, you must provide implementations for the Current property, the MoveNext method, and the Reset method. The following code example shows how you can implement the IEnumerator(Of T) interface manually in a custom collection class. The collection class stores data internally in an array. The MoveNext method maintains an index into the array, and the Current property returns the item that is located at this index.
Public Class CustomIterator(Of T) Implements IEnumerator(Of T) ' An integer object to hold the current position of the enumerator Private pointer As Integer = -1 ' An array that stores data internally Private vals() As T Public Sub New(ByVal data() As T) Me.vals = data Me.pointer = -1 End Sub #Region "IEnumerator(Of T) Members" ' Define a Current property to return the current item ' You should include validation to ensure that the pointer is ' currently valid Public ReadOnly Property Current As T _ Implements IEnumerator(Of T).Current Get ' Check that the state is valid If pointer <> -1 Then Return vals(pointer) Else ' Throw an exception if the current state ' is not valid

Building and Enumerating Custom Collection Classes

13-23

Throw New InvalidOperationException() End If End Get End Property #End Region #Region "IEnumerator Members" ' Define a Current property for the IEnumerable interface ' Return the Current property from the type-safe generic ' IEnumerator(Of T) interface Public ReadOnly Property Current1 As Object _ Implements IEnumerator.Current Return Me.Current End Get End Property ' Define a MoveNext method that advances the enumerator to the ' next item in the collection Public Function MoveNext() As Boolean _ Implements IEnumerator.MoveNext ' You should check that the enumerator will not pass the end ' of the collection if you advance it If pointer < vals.Length - 1 Then pointer += 1 ' Return true if the enumerator successfully advances Return True Get

' Return false if the enumerator will pass the end of the ' collection if you advance it Return False End If End Function ' Define a Reset method that restores the enumerator to the ' initial state, before the first item Public Sub Reset() Implements IEnumerator.Reset pointer = -1 End Sub #End Region #Region "IDisposable Support" ' Implementation not shown #End Region End Class

Else

Question: In the code example on the slide that is associated with this topic, the Get procedure checks that the value of the pointer variable is not 1 before it returns a value. The value of 1 is the initial value for the pointer. Why would 1 be the initial value, and why does the Reset method set the pointer back to 1?

13-24

Programming in Visual Basic with Microsoft Visual Studio 2010

Implementing a Reverse Enumerator by Using an Iterator

You can use an iterator to implement a backwards or reverse enumerator, if your collection class does not support this. An iterator is a block of code that yields or returns an ordered sequence of values. In addition, an iterator is not a member of an enumerable class; instead, it specifies the sequence that an enumerator should use to return its values. In other words, an iterator is just a description of the enumeration sequence that the Visual Basic compiler can use to create its own enumerator. The BasicCollection(Of T) class in the following code example illustrates how to implement an iterator. The class uses a List(Of T) object to hold data and provides the FillList method to populate this list. Note that the BasicCollection(Of T) class implements the IEnumerable(Of T) interface. The GetEnumerator method is implemented by using an iterator.
Public Class BasicCollection(Of T) Implements IEnumerable(Of T) Private data As New List(Of T)() Private currentItem As T Public Sub New() End Sub Public Sub New(ByVal data As List(Of T)) Me.data = data End Sub Public Sub FillList(ByVal ParamArray items() As T) For Each itm As T In items data.Add(itm) Next End Sub Private Function Calculate() As Boolean Return True End Function

Building and Enumerating Custom Collection Classes

13-25

Function GetEnumerator() As IEnumerator(Of T) _ Implements IEnumerable(Of T).GetEnumerator ' Pass the current list as an array to the iterator ' and return to caller Return New CustomIterator(Of T)(data.ToArray) End Function Function GetEnumerator1() As IEnumerator _ Implements IEnumerable.GetEnumerator Return Me End Function End Class

If you examine the GetEnumerator method, you should notice that it returns an IEnumerator(Of T) type by copying the current list to an array and passing it to a new instance of the CustomIterator, which is subsequently returned to the caller. Instead, it loops through the items in the data array and returns each item in turn. The code in the GetEnumerator method defines an iterator, which internally uses the CustomIterator, which implements the IEnumerator(Of T) interface that contains a Current property and a MoveNext method. You can invoke the enumerator that the iterator generates in the usual manner, as the following code example shows.
Dim bc As New BasicCollection(Of String)() bc.FillList("Twas", "brillig", "and", "the", "slithy", "toves") For Each word As String in bc Console.WriteLine(word) Next

This code simply displays the contents of the bc object in the order that the following code example shows.
Twas, brillig, and, the, slithy, toves

If you want to provide alternative iteration mechanisms to present the data in a different sequence, you can implement additional properties that implement the IEnumerable interface and use an iterator to return data. For example, the following code example shows the Backwards property of the BasicCollection(Of T) class, which emits the data in the list in reverse order.
Public Class BasicCollection(Of T) Implements IEnumerable(Of T) ... Public ReadOnly Property Backwards() As IEnumerable(Of T) Get Dim tempData As New List(Of T) For i As Integer = data.Count - 1 To 0 Step -1 tempData.Add(data(i)) Next Return tempData End Get End Property End Class

13-26

Programming in Visual Basic with Microsoft Visual Studio 2010

The following code example shows how to invoke this property.


Dim bc As New BasicCollection(Of String)() bc.FillList("Twas", "brillig", "and", "the", "slithy", "toves") For Each word As String in bc.Reverse Console.WriteLine(word) Next

This code displays the contents of the bc object in reverse order, as the following code example shows.
toves, slithy, the, and, brillig, Twas

Question: How can you use an iterator in conjunction with a property to expose an enumerator?

Building and Enumerating Custom Collection Classes

13-27

Lab: Building and Enumerating Custom Collection Classes

Objectives:
After completing this lab, you will be able to: Implement the IList(Of T) interface in a generic collection class. Create an enumerator that implements the IEnumerator(Of T) interface. Implement an enumerator by using an iterator.

Introduction
In this lab, you will implement the IList(Of T) interface in a custom collection class. You will then add enumerators to this collection class.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

13-28

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Scenario

The Tree class that is used to store and present sorted data works well, but it does not currently support the complete functionality that is expected of collection classes in the .NET Framework. Many of the engineering applications that Fabrikam, Inc. builds require this functionality. You have been asked to extend the BinaryTree class and add the necessary features.

Building and Enumerating Custom Collection Classes

13-29

Exercise 1: Implementing the IList(Of TItem) Interface


Collection classes that enable objects to be accessed by index should implement the IList(Of T) interface. The Tree collection class already provides some of this functionality, but you must implement the additional methods that this interface specifies. In this exercise, you will implement the generic IList(Of T) interface in the BinaryTree class. You will add the following methods and properties to the class: Add. This method will add an item to the tree. The item will be inserted into the appropriate place, depending on its value. Clear. This method will return an empty BinaryTree object. Contains. This method will return a Boolean value to indicate whether the binary tree contains the specified item. CopyTo. This method will throw a NotSupportedException exception. GetEnumerator. This method will throw a NotImplementedException exception in this exercise, but it will be implemented in Exercise 2. IndexOf. This method will search through the binary tree for a specified item and return its index, or 1 if the item is not found. Insert. This method will throw a NotSupportedException exception, because it is not appropriate for sorted data. Remove. This method will remove the specified item from the binary tree. RemoveAt. This method will remove the item at the specified index from the binary tree. Count. This method will return the number of items in the binary tree. IsReadOnly. This property will always return the value false. Item. This property will return the item at the specified index. The Set procedure for this property will throw a NotSupportedException exception.

The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. Open the CustomCollections solution. Modify the Tree class to implement the IList(Of TItem) interface. Add support for indexing items in the Tree class. Implement the IList(Of T) interface methods and properties. Use the BinaryTreeTestHarness application to test the solution.

Task 1: Open the CustomCollections solution.


Open Microsoft Visual Studio 2010. Open the CustomCollections solution in the D:\Labfiles\Lab13\Ex1\Starter folder. Import the code snippets from the D:\Labfiles\Lab13\Snippets folder.

Task 2: Modify the Tree class to implement the IList(Of TItem) interface.
Review the Task List. In the Task List, locate the TODO: - Implement the generic IList(Of TItem) interface task, and then double-click this task.

This task is located in the Tree class.

13-30

Programming in Visual Basic with Microsoft Visual Studio 2010

Remove the TODO: - Implement the generic IList(Of TItem) interface comment, and then modify the class definition to implement the generic IList interface. Specify the value TItem as the type parameter (this is the type parameter that the Tree class references). Add method and property stubs that implement the IList interface.

Visual Studio generates method stubs for each method that is defined in the interface, and adds them to the end of the class file. You will add code to complete some of these methods later in this exercise.

Task 3: Add support for indexing items in the Tree class.


In the Task List, locate the TODO: - Add a member to define node position task, and then doubleclick this task. Remove the TODO: - Add a member to define node position comment, and then add code to define a private integer member named position. In the class constructor, add code to initialize the position member to 1.

Note: The position member is the index for items in the tree. When you add or remove items from the tree, you will invalidate the position member of any following elements in the tree. By setting the position member to 1, you indicate to the tree that the index has become invalid. When the application attempts to use the index to perform an action, and encounters a negative value, the application can rebuild the index by invoking the IndexTree method that you will add later. At the beginning of the implementation of the IBinaryTree.Add method, add code to set the position member to 1. In the Task List, locate the TODO: - Set the position member to -1 task, and then double-click this task. This task is located in the Remove method. Remove the TODO: - Set the position member to -1 comment, and then add code to set the position member to 1. In the Task List, locate the TODO: - Add methods to enable indexing the tree task, and then double-click this task. This task is located at the end of the Tree class, in the Utility methods code region. Delete the TODO: - Add methods to enable indexing the tree comment, and then add a method named IndexTree. This method should accept an integer parameter named index, and return an integer value. Add code to the method to perform the following actions. You can type this code manually, or use the Mod13IndexTreeMethod code snippet: If the LeftTree property is not Nothing, call the IndexTree method of the LeftTree property and assign the result to the index parameter. Pass the current value of the index parameter to the IndexTree method. Update the local position member with the value of the index parameter. Increment the index parameter. If the RightTree property is not Nothing, call the IndexTree method of the RightTree property and assign the result to the index parameter. Pass the current value of the index parameter to the IndexTree method. At the end of the method, return the value of the index parameter.

Building and Enumerating Custom Collection Classes

13-31

After the IndexTree method, add a private method named GetItemAtIndex. This method should accept an integer parameter named index, and return a Tree(Of TItem) object. In the method, add code to perform the following actions. You can type this code manually, or use the Mod13GetItemAtIndexMethod code snippet: If the value of the position member is 1, call the local IndexTree method. Pass 0 as the parameter to the IndexTree method. If the value of the position member is greater than the value of the index parameter, call the GetItemAtIndex method of the LeftTree property and return the value that is generated. Pass the value of the index parameter to the GetItemAtIndex method. If the value of the position member is less than the value of the index parameter, call the GetItemAtIndex method of the RightTree property and return the value that is generated. Pass the value of the index parameter to the GetItemAtIndex method. At the end of the method, return a reference to the current object (Me). After the GetItemAtIndex method, add a private method named GetCount. This method should accept an integer parameter named accumulator, and return an integer value. Add code to the method to perform the following actions. You can type this code manually, or use the Mod13GetCountMethod code snippet. If the LeftTree property is not Nothing, call the GetCount method of the LeftTree property and store the result in the accumulator variable. Pass the current value of the accumulator variable to the GetCount method. Increment the value in the accumulator parameter. If the RightTree property is not Nothing, call the GetCount method of the RightTree property and store the result in the accumulator parameter. Pass the current value of the accumulator parameter to the GetCount method. At the end of the method, return the value of the accumulator variable.

Task 4: Implement the IList(Of T) interface methods and properties.


Locate the IndexOf method. This method accepts a TItem object named item, and returns an integer value. This method should iterate through the tree and return a value that indicates the index of the TItem object in the tree. Replace the code in the IndexOf method with code to perform the following actions. You can type this code manually, or use the Mod13IndexOf code snippet. If the item parameter is Nothing, return the value 1. If the value of the position member is 1, call the IndexTree method and pass the value 0 as a parameter to the IndexTree method. Compare the value of the item parameter to the local NodeData property value. If the value of the item parameter is less than the value in the NodeData property, and if the LeftTree property is Nothing, return 1. Otherwise, return the result of a recursive call to the LeftTree.IndexOf method, passing the item value to the IndexOf method. If the value of the item parameter is greater than the value in the NodeData property, and if the RightTree property is Nothing, return 1. Otherwise, return the result of a recursive call to the RightTree.IndexOf method, passing the item value to the IndexOf method.

13-32

Programming in Visual Basic with Microsoft Visual Studio 2010

Hint: Use the CompareTo method to compare the value in the item parameter and the value in the NodeData property. At the end of the method, return the value of the local position member. Locate the indexer or default property. The Item property should return the TItem object at the index specified by the index parameter. Replace the code in the Get procedure with code to perform the following actions. If the value of the index parameter is less than zero, or greater than the value of the Count property, throw an ArgumentOutOfRangeException exception with the following parameters. A string value, "index" The index parameter value A string value, "Indexer out of range" At the end of the Get procedure, call the GetItemAtIndex method. Pass the value of the index variable to the GetItemAtIndex method. Return the value of the NodeData property from the item that is retrieved by calling the GetItemAtIndex method. Locate the Clear method. This method accepts no parameters, and does not return a value. This method should clear the contents of the tree and return it to a default state. Replace the code in the Clear method with code to perform the following actions: Set the LeftTree property to Nothing. Set the RightTree property to Nothing. Set the NodeData property to the default value for a TItem object by assigning Nothing. Locate the Contains method. This method accepts a TItem parameter, item, and returns a Boolean value. This method should iterate through the tree and return a Boolean value that indicates whether a node that matches the value of the item parameter exists in the tree. Replace the code in the Contains method with code to perform the following actions. You can type this code manually, or use the Mod13Contains code snippet. If the value of the NodeData property is the same as the value of the item parameter, return True. If the value of the NodeData property is greater than the value of the item parameter, and if the LeftTree property is not Nothing, return the result of a recursive call to the LeftTree.Contains method, passing the item parameter to the Contains method. If the value of the NodeData property is less than the value of the item parameter, and if the RightTree property is not Nothing, return the result of a recursive call to the RightTree.Contains method, passing the item parameter to the Contains method. At the end of the method, return False. Locate the Count property. This property is read-only and should return an integer that represents the total number of items in the tree. Replace the code in the Get procedure with code to invoke the GetCount method, by passing 0 to the method call. Return the value that the GetCount method calculates.

Building and Enumerating Custom Collection Classes

13-33

Locate the IsReadOnly property. This property should return a Boolean value that signifies whether the tree is read-only.

Replace the code in the Get procedure with a statement that returns the Boolean value, False. Locate the Remove1 method and rename it to RemoveItem. This method accepts a TItem parameter named item, and returns a Boolean value. This method should check whether a node with a value that matches the item parameter exists in the tree, and if so, remove the item from the tree. If an item is removed, the method should return True; otherwise, the method should return False.

Note: This version of the Remove method was named Remove1, because otherwise it would clash with the Remove method that implements the method of the same name for the IBinaryTree interface. In the RemoveItem method, replace the existing code with statements that perform the following actions: If the tree contains a node that matches the value in the item parameter, call the local RemoveItem method, and then return True. At the end of the method, return False. Build the solution and correct any errors.

Task 5: Use the BinaryTreeTestHarness application to test the solution.


In the BinaryTreeTestHarness project, open the Module1.vb file and examine the Main method. The BinaryTreeTestHarness project contains code that you will use to test the completed BinaryTree class. You will continue to extend the BinaryTree class in the following exercises, so the BinaryTree class is not currently complete. For this reason, this exercise does not use some methods in the test harness. The Main method contains method calls to each of the test methods that you are about to examine. Examine the TestIntegerTree method. The TestIntegerTree method tests the Remove and Contains methods, and the indexer functionality of the BinaryTree class. First, the method invokes the CreateATreeOfIntegers method to build a sample tree that contains 10 values. Then, the method invokes the WalkTree method, which prints each node value to the console in numerical order.

Note: The CreateATreeOfIntegers method creates a Tree object that contains the values 10, 5, 11, 5, 12, 15, 0, 14, 8, and 10 in the order that the method adds them. The method then invokes the Count method and prints the result to the console. The method casts the tree to an ICollection object, and then calls the Remove method to remove the value 11 from the tree. The method again prints the result of the Count method to the console to prove that an item has been removed.

13-34

Programming in Visual Basic with Microsoft Visual Studio 2010

Note: The BinaryTree method contains two Remove methods, and in this case, the test method should invoke the interface-defined ICollection.Remove method. To enable the test method to do this, it must cast the Tree object to an ICollection object. The method then tests the Contains method by invoking the Contains method with the value 11 (which has just been removed), and then 12 (which is known to exist in the list). Finally, the method tests the tree indexer by first retrieving the index of the value 5 in the tree and printing the index to the console, and then using the same index to retrieve the value 5 from that position in the tree. Examine the TestDeleteRootNodeInteger method. The TestDeleteRootNodeInteger method tests the functionality of the Remove method when it attempts to remove the tree root node. When the root node value is removed from the tree, the next available node should be copied into its place to enable the tree to continue to function. In this test, the root node has the value 10. There is a second node with the value 10, so the Remove method must be invoked twice to remove both values. The method first invokes the CreateATreeOfIntegers method to build a sample tree, and then prints the tree to the console by invoking the WalkTree method. The method then casts the Tree object to an ICollection object, and then invokes the Remove method twice to remove both values of 10. Finally, the method again invokes the WalkTree method to verify that the tree still functions correctly. Examine the TestStringTree method. This method uses similar logic to the TestIntegerTree method to test the Count, Remove, Contains, and indexer method functionality. This method uses a BinaryTree object that contains the string values "k203", "h624", "p936", "h624", "a279", "z837", "e762", "r483", "d776", and "k203". In this test, the Remove method is tested by using the "p936" string value, and the indexer is tested by using the "h624" string value. Examine the TestDeleteRootNodeString method. This method uses similar logic to the TestDeleteRootNodeInteger method to test the Remove method functionality, using the same string-based tree as the TestStringTree method. In this test, the "k203" string value is removed twice to test root node removal. Examine the TestTestResultTree method. This method uses similar logic to the TestIntegerTree and TestStringTree methods to test the Count, Remove, Contains, and indexer method functionality, but it uses a BinaryTree object based on the TestResult type.

Note: The TestResult class implements the IComparable interface, and uses the Deflection property to compare instances of the TestResult object. Therefore, items in this tree are indexed by their Deflection property value. In this case, the Remove method is tested with the TestResult object that has a Deflection value of 226. The indexer is tested with the TestResult object that has a Deflection value of 114. Examine the TestDeleteRootNodeTestResult method.

Building and Enumerating Custom Collection Classes

13-35

This method uses similar logic to the TestDeleteRootNodeInteger and TestDeleteRootNodeString methods to test the Remove method functionality, using the same TestResult-based tree as the TestTestResultTree method. In this test, the TestResult object that has a Deflection value of 190 is removed twice to test root node removal. Run the BinaryTreeTestHarness application. Verify that the output in the console window resembles the following code example.
TestIntegerTree() WalkTree() -12 -8 0 5 5 10 10 11 14 15 Count: 10 Remove(11) Count: 9 Contains(11): False Contains(-12): True IndexOf(5): 3 tree(3): 5

Note that: The console shows the output of the TestIntegerTree method. The tree is displayed in numerical order by the WalkTree method. Initially, the list contains 10 items. After the Remove method is called, the tree contains nine items. The Remove method removes the value 11, so the result of the Contains method is False. Note also that the Contains method verifies the presence of the value 12. The IndexOf method reports that the value 5 is in position 3 in the list. This is confirmed by retrieving the value in position 3, which is shown to be 5. Press Enter, and then verify that the output in the console window resembles the following code example.
TestDeleteRootNodeInteger() Before -12 -8 0 5 5 10 10 11

13-36

Programming in Visual Basic with Microsoft Visual Studio 2010

14 15 Remove 5 twice After -12 -8 0 10 10 11 14 15

Note that the tree shows two instances of the value 5 in the first list. Then, after those values are removed, the list no longer contains them. Also note that after removing the root node value, the tree retains the remaining values and continues to function as expected. Press Enter, and then verify that the output in the console window matches the following output.
TestStringTree() WalkTree() a279 d776 e762 h624 h624 k203 k203 p936 r483 z837 Count: 10 Remove("p936") Count: 9 Contains("p936"): False Contains("a279"): True IndexOf("h624"): 3 stringTree(3): h624

This is the same test as the one you performed in step 9, but it is performed by using string data. Items in the list are displayed in alphabetical order. Press Enter, and then verify that the output in the console window matches the following output.
TestDeleteRootNodeString() Before a279 d776 e762 h624 h624 k203 k203 p936

Building and Enumerating Custom Collection Classes

13-37

r483 z837 Remove k203 twice After a279 d776 e762 h624 h624 p936 r483 z837

Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestTestResultTree() WalkTree() Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Count: 10 Remove(def266) Count: 9 Contains(def266): False Contains(def0): True IndexOf(def114): 3 Tree(3): Deflection: 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

This test is the same as the one you performed in steps 9 and 11, but this test is based on TestResult objects. Items are displayed in numerical order based on the value of the Deflection property. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestDeleteRootNodeTestResults() Before Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

13-38

Programming in Visual Basic with Microsoft Visual Studio 2010

Remove def190 twice After Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

Press Enter to return to Visual Studio.

Building and Enumerating Custom Collection Classes

13-39

Exercise 2: Implementing an Enumerator by Writing Code


The BinaryTree class must support the Visual Basic For Each statement to iterate through the contents of a BinaryTree object. You will implement the GetEnumerator method of the IEnumerable(Of T) interface in the BinaryTree class to return an enumerator that the For Each statement can use. You will implement the Dispose, MoveNext, and Reset methods and the Current property. You will then modify the generic version of the GetEnumerator method of the BinaryTree class to return an instance of this enumerator, and test the enumerator by using a For Each loop. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. 6. 7. 8. Open the CustomCollections solution. Create the TreeEnumerator class. Add class-level variables and a constructor. Add a method to populate the queue. Implement the IEnumerator(Of T) and IEnumerator methods. Implement the IDisposable interface. Modify the Tree class to return a TreeEnumerator object. Use the BinaryTreeTestHarness application to test the solution.

Task 1: Open the CustomCollections solution.


Open the CustomCollections solution in the D:\Labfiles\Lab13\Ex2\Starter folder.

Note: The CustomCollections solution in the Ex2 folder is functionally the same as the code that you completed in Exercise 1. However, it includes an updated Task List and an updated test harness to enable you to complete this exercise.

Task 2: Create the TreeEnumerator class.


In the BinaryTree project, add a new class named TreeEnumerator. This class should implement the IEnumerator interface, and should take a type parameter, TItem, where the TItem type implements the IComparable interface.

Task 3: Add class-level variables and a constructor.


In the TreeEnumerator class, add the following members: A Tree(Of TItem) object named currentData, initialized to Nothing. This member will store the initial Tree object data that is passed to the class when it is constructed, and it will be used to populate the internal queue with data. The data is also stored to enable the internal queue to reset. A TItem object named currentItem, initialized to a default TItem object, by assigning Nothing. This member will store the last item that is removed from the queue. A private Queue(Of TItem) object named enumData, initialized to Nothing. This member holds an internal queue of items that the enumerator will iterate over. You will populate this queue with the items in the Tree object.

13-40

Programming in Visual Basic with Microsoft Visual Studio 2010

Add a constructor. The constructor should accept a Tree(Of TItem) parameter named data, and should initialize the currentData member with the value of this parameter.

Task 4: Add a method to populate the queue.


Below the constructor, add a new private method named Populate. The method should accept a Queue(Of TItem) parameter named enumQueue, and a Tree(Of TItem) parameter named populatedTree. It should not return a value. Add code to the method to perform the following actions. If the LeftTree property of the populatedTree parameter is not Nothing, recursively call the Populate method, passing the enumQueue parameter and the populatedTree.LeftTree property as parameters to the method. Add the populatedTree.NodeData property value of the populatedTree parameter to the enumQueue queue. If the RightTree property of the populatedTree parameter is not Nothing, recursively call the Populate method, passing the enumQueue parameter and the populatedTree.RightTree property as parameters to the method.

This code walks the tree and fills the queue with each item that is found, in order.

Task 5: Implement the IEnumerator(Of T) and IEnumerator methods.


In the class definition, place the cursor at the end of the line of code, Implements IEnumerator(Of TItem), and then press Enter. Visual Studio will generate stubs for the methods and properties that the IEnumerator(Of), IEnumerator, and IDisposable interfaces expose. Locate the Current property. This property should return the last TItem object that was removed from the queue. In the Get procedure of the Current property, replace the existing code with code to perform the following actions. If the enumData member is Nothing, throw a new InvalidOperationException exception with the message "Use MoveNext before calling Current". Return the value of the currentItem member.

Locate the Current1 property. This property should return the value of the Current property.

In the Get procedure of the Current1 property, add the following code, Return Me.Current. Locate the MoveNext method. The method accepts no parameters and returns a Boolean value. The MoveNext method should ensure that the internal queue is initialized, retrieve the next item from the internal queue, and then store it in the currentItem property. If the operation succeeds, the method returns True, otherwise, it returns False.

In the MoveNext method, replace the existing code with code to perform the following actions. If the enumData object is Nothing, create a new queue object, and then invoke the Populate method, passing the new Queue object and the currentData member as parameters to the method call. If the enumData object contains any values, retrieve the first item in the queue, store it in the currentItem member, and then return the Boolean value True.

Building and Enumerating Custom Collection Classes

13-41

At the end of the method, return the Boolean value, False.

Locate the Reset method. This method accepts no parameters, and does not return a value. This method should reset the enumerator to its initial state. You do this by repopulating the internal queue with the data from the Tree object.

In the Reset method, replace the existing code with code that invokes the Populate method, passing the enumData and currentData members as parameters to the method. Build the solution and correct any errors.

Task 6: Implement the IDisposable interface.


In the TreeEnumerator class, locate the Dispose method. This method accepts no parameters and does not return a value. The method should dispose of the class, relinquishing any resources that may not be reclaimed if they are not disposed of explicitly, such as file streams and database connections.

Note: The Queue object does not implement the IDisposable interface, so you will use the Dispose method of the TreeEnumerator class to clear the queue of any data. In the Dispose method, replace the existing code with code that clears the enumQueue queue object.

Hint: Use the Clear method of the Queue class to empty a Queue object. Build the solution and correct any errors.

Task 7: Modify the Tree class to return a TreeEnumerator object.


In the Task List, locate the TODO: - Update the Tree class to return the TreeEnumerator class task, and then double-click this task. This task is located in the Tree class. Remove the comment. In the GetEnumerator method, add code that creates and initializes a new TreeEnumerator object. Specify the TItem type as the type parameter, and pass the current object as the parameter to the TreeEnumerator constructor. Return the TreeEnumerator object that is created. Build the solution and correct any errors.

Task 8: Use the BinaryTreeTestHarness application to test the solution.


In the BinaryTreeTestHarness project, open the Module1.vb file. This version of the BinaryTreeTestHarness project contains the same code and performs the same tests as in Exercise 1. However, it has been updated to test the enumerator functionality that you just added. Examine the TestIteratorsIntegers method. This method tests the iterator functionality that you just implemented, by using the same integer tree as in Exercise 1. The method builds the tree by invoking the CreateATreeOfIntegers method, and then uses a For Each statement to iterate through the list and print each value to the console. The

13-42

Programming in Visual Basic with Microsoft Visual Studio 2010

method then attempts to iterate through the tree in reverse order, and print each item to the console.

Note: You will add the functionality to enable reverse iteration of the tree in the next exercise. It is expected that attempting to reverse the tree will throw a NotImplementedException exception. The TestIteratorsIntegers method will catch this exception when it occurs, and print a message to the console. Examine the TestIteratorsStrings method. This method uses similar logic to the TestIteratorsIntegers method to test the iterator functionality of the BinaryTree object, but it uses the same string-based tree as the one you used in Exercise 1. The method uses the CreateATreeOfStrings method to build the tree, iterates through the tree, and then prints all items to the console. This method also attempts to display the data in the tree in reverse order, and will encounter a NotImplementedException exception (you will implement this feature in the next exercise). Examine the TestIteratorsTestResults method. This method uses similar logic to the TestIteratorsIntegers and TestIteratorsStrings methods to test the iterator functionality of the BinaryTree object. It uses a TestResult-based tree by invoking the CreateATreeOfTestResults method as in Exercise 1. Run the BinaryTreeTestHarness application. Verify that the output in the console window matches the following code example.
TestIntegerTree() WalkTree() -12 -8 0 5 5 10 10 11 14 15 Count: 10 Remove(11) Count: 9 Contains(11): False Contains(-12): True IndexOf(5): 3 tree(3): 5

This output matches the TestIntegerTree method output from Exercise 1, and confirms that you have not compromised existing functionality by adding the iterator functionality. Press Enter, and then verify that the output in the console window matches the following output.

Building and Enumerating Custom Collection Classes

13-43

TestDeleteRootNodeInteger() Before -12 -8 0 5 5 10 10 11 14 15 Remove 5 twice After -12 -8 0 10 10 11 14 15

This output matches the TestDeleteRootNodeInteger method output from Exercise 1, and again confirms that the existing functionality works as expected. Press Enter, and then verify that the output in the console window matches the following output.
TestIteratorsIntegers() In ascending order -12 -8 0 5 5 10 10 11 14 15 In descending order Not Implemented. You will implement this functionality in Exercise 3

Note that the items in the list are displayed in numerical order, and note that the Reverse method displays a message that indicates that the Reverse functionality is not yet implemented. Press Enter, and then verify that the output in the console window matches the following output.
TestStringTree() WalkTree() a279 d776 e762 h624 h624 k203 k203 p936 r483

13-44

Programming in Visual Basic with Microsoft Visual Studio 2010

z837 Count: 10 Remove("p936") Count: 9 Contains("p936"): False Contains("a279"): True IndexOf("h624"): 3 Tree(3): h624

This output matches the TestStringTree method output from Exercise 1. Press Enter, and then verify that the output in the console window matches the following output.
TestDeleteRootNodeString() Before a279 d776 e762 h624 h624 k203 k203 p936 r483 z837 Remove k203 twice After a279 d776 e762 h624 h624 p936 r483 z837

This output matches the TestDeleteRootNodeString method output from Exercise 1. Press Enter, and then verify that the output in the console window matches the following output.
TestIteratorsStrings() In ascending order a279 d776 e762 h624 h624 k203 k203 p936 r483 z837 In descending order Not Implemented. You will implement this functionality in Exercise 3

Building and Enumerating Custom Collection Classes

13-45

Note that this represents the same test as you performed in step 8. It uses string data to verify the iterator functionality, and all items are displayed in alphabetical order. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestTestResultTree() WalkTree() Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Count: 10 Remove(def266) Count: 9 Contains(def266): False Contains(def0): True IndexOf(def114): 3 Tree(3): Deflection: 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

This output matches the TestTestResultTree method output from Exercise 1. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestDeleteRootNodeTestResults() Before Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

Remove def190 twice After Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

13-46

Programming in Visual Basic with Microsoft Visual Studio 2010

This output matches the TestDeleteRootNodeTestResults method output from Exercise 1. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestIteratorsTestResults() In ascending order Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 Deflection: 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 Deflection: 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 Deflection: 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 Deflection: 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 Deflection: 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 Deflection: 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 Deflection: 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 Deflection: 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 Deflection: 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010 In descending order Not Implemented. You will implement this functionality in Exercise 3

Note that this represents the same test as you performed in steps 8 and 11. It uses TestResult object data to verify the iterator functionality, and all items are displayed in numerical order based on the value of the Deflection property. Press Enter to return to Visual Studio.

Building and Enumerating Custom Collection Classes

13-47

Exercise 3: Implementing an Enumerator by Using an Iterator


The existing enumerator enables an application to iterate through the contents of a BinaryTree object in ascending order. However, some applications need to be able to iterate through a BinaryTree object in descending order. You have been asked to add a second enumerator to the BinaryTree class that can help to perform this task. You will add an enumerator that enables a program to iterate through a BinaryTree object in reverse order. You will implement this enumerator by using an iterator. The main tasks for this exercise are as follows: 1. 2. 3. Open the CustomCollections solution. Add an enumerator to return an enumerator that iterates through data in reverse order. Use the BinaryTreeTestHarness application to test the solution.

Task 1: Open the CustomCollections solution.


Open the CustomCollections solution in the D:\Labfiles\Lab13\Ex3\Starter folder.

Note: The CustomCollections solution in the Ex3 folder is functionally the same as the code that you completed in Exercise 2. However, it includes an updated Task List and an updated test harness to enable you to complete this exercise.

Task 2: Add an enumerator to return an enumerator that iterates through data in reverse
order.
Review the Task List. In the Task List, locate the TODO: - Add a method to return the list in reverse order task, and then double-click this task. This task is located at the end of the Tree class. Remove the task comment, and then add a new public method named Reverse. The method should accept no parameters, and return an IEnumerable collection based on the TItem type parameter. Add code to the method to perform the following actions. You can type this code manually or use the Mod13Reverse code snippet. Create and initialize an instance named tempQueue of type Queue(Of TItem). If the RightTree property is not Nothing, iterate through the items that are returned by calling the Reverse method of the RightTree property, and then add each item that is found, to tempQueue. Return the value in the NodeData property to tempQueue. If the LeftTree property is not Nothing, iterate through the items that are returned by calling the Reverse method of the LeftTree property, and then add each item that is found, to tempQueue. Return the tempQueue object.

Note: You can use the Enqueue method of the Queue class to add an item.

13-48

Programming in Visual Basic with Microsoft Visual Studio 2010

Build the solution and correct any errors.

Task 3: Use the BinaryTreeTestHarness application to test the solution.


In the BinaryTreeTestHarness project, open the Module1.vb file. This version of the BinaryTreeTestHarness project contains the same code and performs the same tests as in Exercise 2. Now that you have implemented the Reverse method in the BinaryTree object, the test application should not encounter the NotImplementedException exception in the TestIteratorsIntegers, TestIteratorsStrings, and TestIteratorsTestResults methods. Run the BinaryTreeTestHarness application. Verify that the output in the console window matches the following code example.
TestIntegerTree() WalkTree() -12 -8 0 5 5 10 10 11 14 15 Count: 10 Remove(11) Count: 9 Contains(11): False Contains(-12): True IndexOf(5): 3 tree(3): 5

This output matches the TestIntegerTree method output from Exercises 1 and 2, and confirms that you have not compromised existing functionality by adding the reverse iterator functionality. Press Enter, and then verify that the output in the console window matches the following output.
TestDeleteRootNodeInteger() Before -12 -8 0 5 5 10 10 11 14 15 Remove 5 twice After

Building and Enumerating Custom Collection Classes

13-49

-12 -8 0 10 10 11 14 15

This output matches the TestDeleteRootNodeInteger method output from Exercises 1 and 2, and again confirms that the existing functionality works as expected. Press Enter, and then verify that the output in the console window matches the following output.
TestIteratorsIntegers() In ascending order -12 -8 0 5 5 10 10 11 14 15 In descending order 15 14 11 10 10 5 5 0 -8 -12

This output is similar to the TestIteratorsIntegers method in Exercise 2, but the Reverse method is now implemented, so the tree is also displayed in descending numerical order. Press Enter, and then verify that the output in the console window matches the following output.
TestStringTree() WalkTree() a279 d776 e762 h624 h624 k203 k203 p936 r483 z837 Count: 10 Remove("p936") Count: 9

13-50

Programming in Visual Basic with Microsoft Visual Studio 2010

Contains("p936"): False Contains("a279"): True IndexOf("h624"): 3 Tree(3): h624

This output matches the TestStringTree method output from Exercises 1 and 2. Press Enter, and then verify that the output in the console window matches the following output.
TestDeleteRootNodeString() Before a279 d776 e762 h624 h624 k203 k203 p936 r483 z837 Remove k203 twice After a279 d776 e762 h624 h624 p936 r483 z837

This output matches the TestDeleteRootNodeString method output from Exercises 1 and 2. Press Enter, and then verify that the output in the console window matches the following output.
TestIteratorsStrings() In ascending order a279 d776 e762 h624 h624 k203 k203 p936 r483 z837 In descending order z837 r483 p936 k203 k203 h624 h624 e762 d776 a279

Building and Enumerating Custom Collection Classes

13-51

This test uses string data to verify the iterator functionality, and all items are displayed in alphabetical order, and then reverse alphabetical order. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestTestResultTree() WalkTree() Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Count: 10 Remove(def266) Count: 9 Contains(def266): False Contains(def0): True IndexOf(def114): 3 Tree(3): Deflection: 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

This output matches the TestTestResultTree method output from Exercises 1 and 2. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestDeleteRootNodeTestResults() Before Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

Remove def190 twice After Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

13-52

Programming in Visual Basic with Microsoft Visual Studio 2010

This output matches the TestDeleteRootNodeTestResults method output from Exercises 1 and 2. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestIteratorsTestResults() In ascending order Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 Deflection: 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 Deflection: 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 Deflection: 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 Deflection: 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 Deflection: 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 Deflection: 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 Deflection: 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 Deflection: 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 Deflection: 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010 In descending order Deflection: 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010 Deflection: 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 Deflection: 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 Deflection: 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 Deflection: 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 Deflection: 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 Deflection: 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 Deflection: 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 Deflection: 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010

This test uses TestResult object data to verify iterator functionality. Therefore, all items are displayed in numerical order based on the value of the Deflection property, and then the list is reversed to display data in descending numerical order based on the value of the Deflection property. Press Enter to return to Visual Studio. Close Visual Studio.

Building and Enumerating Custom Collection Classes

13-53

Lab Review

Review Questions
1. In the lab, you implemented the IList(Of T) interface. When would you use the IList(Of T) interface in a custom class?

13-54

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Review and Takeaways

Review Questions
1. 2. What are the advantages of developing a custom collection class? You want to ensure that your custom collection class can be enumerated by using a For Each statement, and you need to support collection initializers with your type. What steps would you take to achieve this? You develop a custom collection class. Applications that consume your class need to iterate over the data that is stored in your collection class in several different ways and obtain data in different orders. How would you implement this functionality in a custom collection class, and how would you provide a default order for iterating over the data in your collection class?

3.

Best Practices Related to Developing Custom Collection Classes


Supplement or modify the following best practices for your own work situations: Implement the appropriate interfaces to ensure that your class is compatible with the standard collection-handling constructs in Visual Basic. Always use the generic interfaces in preference to nongeneric interfaces when you develop a custom collection class.

Best Practices Related to Implementing Enumerators


Supplement or modify the following best practices for your own work situations: Implement the IEnumerable(Of T) interface to define a default enumerator for your type. Expose additional enumerators by using a property or a method in your custom collection class. Implement enumerators by using iterators to minimize the possibility of errors in your code.

Using LINQ to Query Data

14-1

Module 14
Using LINQ to Query Data
Contents:
Lesson 1: Using the LINQ Extension Methods and Query Operators Lesson 2: Building Dynamic LINQ Queries and Expressions Lab: Using LINQ to Query Data 14-3 14-24 14-37

14-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

This module introduces you to Language-Integrated Query (LINQ) queries and explains how you can use them to process data in your Microsoft .NET Framework applications. This module also explains the difference between shared and dynamic LINQ queries, and describes how you can use dynamic LINQ to create highly flexible queries that you build at run time.

Objectives:
After completing this module, you will be able to: Describe how to use the LINQ extension methods and query operators. Describe how to build dynamic LINQ queries and expressions.

Using LINQ to Query Data

14-3

Lesson 1

Using the LINQ Extension Methods and Query Operators

This lesson introduces you to the LINQ feature of the.NET Framework. By using LINQ, you can abstract the mechanism that an application uses to query data from the application code. This lesson introduces you to some of the fundamental concepts and features that will enable you to use LINQ in your applications.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the purpose of LINQ. Describe how to use the Select extension method to query data and build an enumerable result. Describe how to use the Where extension method to filter data. Describe how to order data. Describe how to group data and perform aggregate calculations. Describe how to join data from different data sets. Describe how the LINQ operators correspond to the LINQ extension methods. Describe how LINQ uses deferred evaluation, and describe how you can force early evaluation of a LINQ query.

14-4

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is the Purpose of LINQ?

Most applications perform some kind of data processing. This processing may be trivial; for example, an application may retrieve a list of application configuration settings from a file. However, it can also be complex; for example, an application may perform a bulk update to 5 million records in a relational database. Historically, the logic to perform such operations has been tightly coupled to the application architecture and to the structure of the data. This means that if the structure of the data changes, you may also need to make considerable changes to the data-processing logic that handles this data. However, when you build .NET Framework applications, you can take advantage of LINQ. LINQ simplifies the development of data-processing logic by providing features that abstract the mechanisms that are required to query data from the code in applications. LINQ uses a syntax that is similar to the Structured Query Language (SQL) statements that are used to query relational databases. By using LINQ, you provide a high-level description of the data that the application should retrieve, but you do not need to indicate exactly how to retrieve the data. A LINQ provider takes this description and generates the appropriate code for you. A LINQ provider is a component that implements the necessary interfaces to expose querying capabilities to your code. Each LINQ provider is customized to work with a particular type of data source. The following list highlights some of the available LINQ providers: LINQ to XML LINQ to Objects LINQ to Entities LINQ to SQL LINQ to DataSet

Using LINQ to Query Data

14-5

Note: You can also extend LINQ by creating your own providers. Creating custom providers is beyond the scope of this course. Question: Briefly summarize the purpose of LINQ.

Additional Reading
For more information about LINQ, see the Language-Integrated Query (LINQ) page at http://go.microsoft.com/fwlink/?LinkId=192977

14-6

Programming in Visual Basic with Microsoft Visual Studio 2010

Querying Data and Building a Result Set

LINQ provides various functions that enable you to query data and build an in-memory data set. LINQ exposes these functions through a series of extension methods that are found in the System.Linq namespace.
Note: This topic focuses on how to use LINQ functionality through extension methods that types in the System.Linq namespace define. The alternative approach is to use LINQ operators, which are covered later in this lesson. One of the most fundamental functions in LINQ is the ability to project data from a collection. For example, you may have a list of Customer objects, and you may want to derive a second list that contains only each customers last name. You can use a For Each statement to iterate through the entire collection and extract the required data, as the following code example shows.
Dim customers As New Customer New Customer New Customer New Customer New Customer New Customer New Customer IEnumerable(Of Customer) = { With {.FirstName = "Luka", .LastName = "Abrus", .Age = 41}, With {.FirstName = "Syed", .LastName = "Abbas", .Age = 23}, With {.FirstName = "Keith", .LastName = "Harris", .Age = 59}, With {.FirstName = "David", .LastName = "Pelton", .Age = 41}, With {.FirstName = "John", .LastName = "Peoples", .Age = 23}, With {.FirstName = "Toni", .LastName = "Poe", .Age = 29}, With {.FirstName = "Jeff", .LastName = "Price", .Age = 23}}

Dim customerLastNames As New List(Of String)() For Each cust As Customer In customers) customerLastNames.Add(cust.LastName) Next

A much simpler and more intuitive solution is to use the Select extension method that LINQ provides.

Using LINQ to Query Data

14-7

Using the Select Extension Method


The Select extension method is available on any enumerable collection class that implements the generic IQueryable(Of T) or IEnumerable(Of T) interfaces. The simplest form of the Select method takes a generic delegate that identifies the data to project. The simplest way to implement this delegate is to use a lambda expression. The following code example shows how you can use the Select extension method to derive a list of last names from an array of Customer objects.
Dim customerLastNames As IEnumerable(Of String) = customers.Select(Function(cust) cust.LastName)

The value that the Select method returns is a reference to an enumerable collection. You can iterate through this collection to fetch and process the data. The following code example iterates through the collection that the previous Select method call returned, and then displays the results.
For Each name As String In customerLastNames Console.WriteLine(name) Next

Using the Select Extension Method with Anonymous Types


If you must return data from more than one property, you can generate an anonymous type. An anonymous type is a type without a name that the compiler automatically generates. The compiler implicitly creates the type, and the type does not require an explicit declaration that you would expect with a standard type, such as a class. As with any other type, you still use the New keyword to instantiate an instance, but then you provide a pair of braces that define the names of the fields and values that you want the type to contain. The compiler infers the types of those fields from the values that you provide. The following code example shows how to use the Select extension method to extract both the FirstName and LastName properties into an anonymous type and return an enumerable collection of this type. The code then iterates through the collection and displays the results.
Note: In this example, the name of each field in the anonymous type matches the name of the field that is retrieved from the Customer type. However, you can give the fields in an anonymous type any valid identifier; they do not have to be the same as the field names in the underlying type.
Dim customerNames = customers.Select(Function(cust) _ New With {.FirstName = cust.FirstName, .LastName = cust.LastName}) For Each customer In customerNames Console.WriteLine("{0} {1}", customer.FirstName, _ customer.LastName) Next

Note that in this example, you do not know the name of the type that the Select method returns, so the customer Names and customer variables are undefined until the compiler takes over. Question: In the following code example, what does the employeeDetails array contain?
Dim employeeDetails = employees.Select(Function(empl) empl.ID)

14-8

Programming in Visual Basic with Microsoft Visual Studio 2010

Additional Reading
For more information about LINQ, see the Introduction to LINQ in Visual Basic page at http://go.microsoft.com/fwlink/?LinkID=211620&clcid=0x409

Using LINQ to Query Data

14-9

Filtering Data

The Select extension method enables you to specify the fields that you want to return from an enumerable collection. However, you may want to restrict the items that are returned. LINQ enables you to filter data by using the Where extension method.

Using the Where Extension Method


Syntactically, the Where extension method is similar to the Select extension method in that the method expects a delegate and returns an IEnumerable object. The delegate should take an instance of the data that is evaluated, and it should return a Boolean value to indicate whether this data should be included in the enumerable result set. As with the Select method, the simplest way to provide this delegate is to use a lambda expression. The Where extension method returns an enumerable result; therefore, you can apply the Select extension method to this result and apply a projection. The following code example shows how to use the Where extension method to return a collection of last names for customers who are over 25 years old.
Dim customerLastNames = customers.Where(Function(cust) cust.Age > 25). Select(Function(cust)cust.LastName)

In the preceding example, the extension methods are applied as follows: 1. The Where extension method is applied to the customers array to perform filtering; it returns an IEnumerable object that contains only records that match the condition. The records that are returned include every field in the Customer object. The Select extension method is then applied, which specifies that only the LastName property should be returned. It is important that you order the extension method calls correctly; otherwise, you may produce a query that does not compile or returns unexpected results. In the previous example, if you called the Where extension method after the Select extension method, the query would not compile. This is

2.

14-10

Programming in Visual Basic with Microsoft Visual Studio 2010

because the lambda expression in the Where extension method refers to the Age field in the Customer object, but the Select extension method only projects the LastName field. If the Select extension method projected the entire Customer object, the query would compile and run as normal. Question: What does the result object represent in the following code example?
Dim customers As { New Customer New Customer New Customer New Customer New Customer New Customer New Customer } IEnumerable(Of Customer) = With With With With With With With {.FirstName {.FirstName {.FirstName {.FirstName {.FirstName {.FirstName {.FirstName = = = = = = = "Luka", .LastName = "Abrus", .Age = 41}, "Syed", .LastName = "Abbas", .Age = 23}, "Keith", .LastName = "Harris", .Age = 59}, "David", .LastName = "Pelton", .Age = 25}, "John", .LastName = "Peoples", .Age = 37}, "Toni", .LastName = "Poe", .Age = 29}, "Jeff", .LastName = "Price", .Age = 74}

Dim result = customers.Where(Function(cust) cust.LastName = "Poe")

Using LINQ to Query Data

14-11

Ordering Data

As you have seen, you can filter rows of data and select specific fields from types; you can also return data in a specific order. If you are familiar with SQL, you may have used the OrderBy clause. LINQ exposes similar functionality with the OrderBy, OrderByDescending, ThenBy, and ThenByDescending extension methods.
Note: Most extension methods, such as Select, Where, and OrderBy, are generic methods. This means that the compiler works out what types to use based on the context, but occasionally, you might need to specify the appropriate type parameters if there is potential ambiguity.

Using the OrderBy and OrderByDescending Extension Methods


The OrderBy and OrderByDescending extension methods enable you to sort data by a specific field in either ascending or descending order. Similar to other extension methods, these two methods expect a delegate that identifies the field or an expression to sort the data. The following code example shows how to use the OrderBy extension method to sort an array of Customer objects by the FirstName field in ascending order.
Dim sortedCustomers = customers.OrderBy(Function(cust) cust.FirstName)

The following code example shows how to use the OrderByDescending extension method to sort an array of Customer objects by the FirstName field into descending order.
Dim sortedCustomers = customers.OrderByDescending(Function(cust) cust.FirstName)

14-12

Programming in Visual Basic with Microsoft Visual Studio 2010

Using the ThenBy and ThenByDescending Extension Methods


Although the OrderBy and OrderByDescending extension methods enable you to perform basic sorting, sometimes, you may want to perform additional sorting in the same statement. The OrderBy and OrderByDescending extension methods return an IOrderedEnumerable object, which exposes two additional extension methods: the ThenBy and ThenByDescending extension methods. The ThenBy and ThenByDescending extension methods enable you to specify additional sort keys for data that has the same value for the initial sort key. The following code example shows how to use the OrderBy and ThenBy extension methods. The example uses the OrderBy method to sort the results by the FirstName field and then uses the ThenBy method to sort the records by the Age field.
Dim sortedCustomers = customers.OrderBy(Function(cust)cust.FirstName). ThenBy(Function(cust) cust.Age)

The ThenByDescending extension method enables you to apply an additional descending sort sequence, as the following code example shows.
Dim sortedCustomers = customers.OrderByDescending(Function(cust)cust.FirstName). ThenByDescending(Function(cust) cust.Age)

Question: Which extension method would you use to sort an array of strings into descending order?

Using LINQ to Query Data

14-13

Grouping Data and Performing Aggregate Calculations

LINQ provides several methods that enable you to calculate an aggregated result across an enumerable collection. These methods include Average, Count, Max, and Min. The Average, Max, and Min methods can take a delegate that specifies the field over which to calculate the aggregate value. The Count method can take a delegate that specifies a predicate to evaluate and only includes an item if the predicate returns the value True. However, it is common to use the Count method simply to determine the total number of rows in a collection, and this delegate is frequently omitted (all the aggregate methods are overloaded). The following code example shows how to use each of these methods over an array of Customer objects. The example displays the total number of customers, together with the average, minimum, and maximum ages.
Dim customers As New Customer New Customer New Customer New Customer New Customer New Customer New Customer } IEnumerable(Of Customer) = { With {.FirstName = "Luka", .LastName = "Abrus", .Age = 41}, With {.FirstName = "Syed", .LastName = "Abbas", .Age = 23}, With {.FirstName = "Keith", .LastName = "Harris", .Age = 59}, With {.FirstName = "David", .LastName = "Pelton", .Age = 29}, With {.FirstName = "John", .LastName = "Peoples", .Age = 37}, With {.FirstName = "Toni", .LastName = "Poe", .Age = 29}, With {.FirstName = "Jeff", .LastName = "Price", .Age = 74}

Console.WriteLine( "Count:{0}" & vbTab & vbTab & "Average age:{1}" & vbTab & vbTab & "Lowest:{2}" & vbTab & vbTab & "Highest:{3}", customers.Count(), customers.Average(Function(cust)cust.Age), customers.Min(Function(cust)cust.Age), customers.Max(Function(cust)cust.Age))

14-14

Programming in Visual Basic with Microsoft Visual Studio 2010

Grouping Data
You can perform aggregate calculations directly against an enumerable collection, but you may often want to calculate an aggregate value for different groups of data; for example, you may want to determine how many customers are inside specific age ranges. You can divide an enumerable collection into groups by using the GroupBy extension method. The GroupBy method expects a delegate that indicates how to group the data; the delegated method returns a selector, and all the items that have the same value for this selector are placed in the same group. You can then perform the aggregate methods over each group. The value that the GroupBy method returns is an enumerable collection of objects that implement the IGrouping interface. The IGrouping interface represents a collection of items that have a common key value, and you can access this key by using the Key property. The following code example uses the GroupBy method to split customers into groups that are determined by their age. The example then displays the total number of customers in each group and the selector that identifies the group. Note that the value of the selector is available through the Key property of the group. The code example orders the groups by using this key and also displays the key as part of the output.
Dim customersGroupedByAgeRange = customers.GroupBy(Function(cust) If cust.Age < 20 Then Return "age < 20" End If If cust.Age >= 20 AndAlso cust.Age < 40 Then Return "age >= 20 and < 40" End If If cust.Age >= 40 AndAlso cust.Age < 60 Then Return "age >= 40 and < 60" End If If cust.Age >= 60 Then Return "age >= 60" End If Return "Error" End Function) For Each cust In customersGroupedByAgeRange.OrderBy(Function(cust1) cust1.Key)) Console.WriteLine("{0}" & vbTab & vbTab & "{1}", cust.Key, cust.Count()) Next

The results that this code generates resemble the following code example.
age >= 20 and < 40 age >= 40 and < 60 age >= 60 4 2

Eliminating Duplicates in Aggregate Calculations


Aggregate methods such as Count or Average include every matching item in their calculations. However, sometimes, you may want to discard any duplicate values from these calculations. The query in the following code example counts the number of ages that are found in the customers array.
Console.WriteLine(customers.Select(Function(cust) cust.Age).Count())

Using LINQ to Query Data

14-15

If two or more customers have the same age, this age is counted twice. You can eliminate duplicate values by using the Distinct extension method before you perform the aggregation, as the following code example shows.
Console.WriteLine(customers.Select(Function(cust) cust.Age).Distinct().Count())

Question: In the following code example, what does the result object contain?
Dim customers As { New Customer New Customer New Customer New Customer New Customer New Customer New Customer } IEnumerable(Of Customer) = With With With With With With With {.FirstName {.FirstName {.FirstName {.FirstName {.FirstName {.FirstName {.FirstName = = = = = = = "Luka", .LastName = "Abrus", .Age = 41}, "Syed", .LastName ="Abbas", .Age = 23}, "Keith", .LastName ="Harris", .Age = 59}, "David", .LastName = "Pelton", .Age = 41}, "John", .LastName = "Peoples", .Age = 23}, "Toni", .LastName = "Poe", .Age = 29}, "Jeff", .LastName = "Price", .Age = 23}

Dim result = customers.Count(Function(cust) cust.LastName.StartsWith("P"))

14-16

Programming in Visual Basic with Microsoft Visual Studio 2010

Joining Data from Different Data Sets

You can join together data that is held in different collections to perform composite queries as long as there is a logical key field that is common to both collections. LINQ provides the Join extension method for this purpose. For example, the following code example declares two arrays. The first array contains several customers and includes their first name, last name, age, and the name of the company that employs them. The second array contains several companies and includes the company name and the country where the company is based.
Dim customers As IEnumerable(Of Customer) = {New Customer With {.FirstName = "Luka", .LastName = "Abrus", .Age = 41, .CompanyName = "Contoso"}, New Customer With {.FirstName = "Syed", .LastName = "Abbas", .Age = 23, .CompanyName = "Fabrikam"}, New Customer With {.FirstName = "Keith", .LastName = "Harris", .Age = 59, .CompanyName = "Contoso"}, New Customer With {.FirstName = "David", .LastName = "Pelton", .Age = 41, .CompanyName = "Contoso"}, New Customer With {.FirstName = "John", .LastName = "Peoples", .Age = 23, .CompanyName = "Contoso"}, New Customer With {.FirstName = "Toni", .LastName = "Poe", .Age = 29, .CompanyName = "Fabrikam"}, New Customer With {.FirstName = "Jeff", .LastName = "Price", .Age = 23, .CompanyName = "Fabrikam"} } Dim companies As IEnumerable(Of Company) = { New Company With {.CompanyName = "Contoso", .Country = "United Kingdom"}, New Company With {.CompanyName = "Fabrikam", .Country = "United States"} }

Using LINQ to Query Data

14-17

With the Join extension method, you can derive an enumerable collection that contains data from both the customers and companies arrays, joined across the CompanyName field.

Using the Join Extension Method to Join Multiple Data Sets


To use the Join extension method, you provide the following parameters: The enumerable collection with which you want to join. A method that identifies the common fields that the Select extension method identifies. A method that identifies the common key fields on which to join the different data sets. A method that identifies the fields that you require from the enumerable result set that the Join extension method returns.

The following code example shows how you can use the Join extension method to join the customers and companies arrays over the CompanyName field and display a customer's first and last name, and the country in which the customer resides.
Dim customersAndCompanies = customers.Join( companies, Function(custs) custs.CompanyName, Function(comps) comps.CompanyName, Function(custs, comps) _ New With {custs.FirstName, custs.LastName, comps.Country}) For Each item IncustomersAndCompanies Console.WriteLine(item) Next

The Join method returns an IQueryable collection that you can use in conjunction with other LINQ extension methods, such as Select, Where, and OrderBy. Question: In the following code example, what does the result object contain?
Dim phones As IEnumerable(Of CellPhone) = { New CellPhone With {.ID = 1, .Make = "...", .Model = "...", .NetworkID = 1}, New CellPhone With {.ID = 2, .Make = "...", .Model = "...", .NetworkID = 1}, New CellPhone With {.ID = 3, .Make = "...", .Model = "...", .NetworkID = 2} } Dim networks As IEnumerable(Of CellPhoneNetwork) = { New CellPhoneNetwork With {.ID = 1, .Name = "..."}, New CellPhoneNetwork With {.ID = 2, .Name = "..."} } Dim result = phones.Select(Function(p) p).Join( networks, Function(p) p.NetworkID, Function(n) n.ID, Function(p, n) New With {p.Make, p.Model, n.Name})

14-18

Programming in Visual Basic with Microsoft Visual Studio 2010

Using Visual Basic LINQ Query Operators

The LINQ extension methods and types that are defined in the System.Linq namespace are very powerful, but the syntax can be quite cumbersome, and it is easy to introduce subtle errors that are difficult to spot and correct. The designers of Visual Basic recognized this fact and have provided an alternative approach to the use of LINQ through a series of LINQ query operators that are part of the Visual Basic language. The LINQ query operators provide simple shorthand syntax that is reminiscent of SQL clauses; these operators expose the same functionality as the equivalent extension methods. At compile time, the shorthand syntax is compiled into the equivalent code that uses the LINQ extension methods. For example, you have seen how to use the Select extension method to project a list of last names from an array of Customer objects, as the following code example shows.
Dim customerLastNames As IEnumerable(Of String) = customers.Select(Function(cust) cust.LastName)

However, you can use the From, In, and Select query operators to achieve the same result, as the following code example shows.
Dim customerLastNames As IEnumerable(Of String) = From cust In customers Select cust.LastName

The query operator approach is much more intuitive and easier to follow. You can read this statement as "from a customer record in the customer collection, select the LastName field."
Note: If you are familiar with SQL, notice that the order of the clauses in a LINQ query are different; the From clause always precedes the Select clause.

Using LINQ to Query Data

14-19

As with extension methods, you can take advantage of anonymous types and derive new entities to store a subset of fields from the original data set. The following code example shows how to use query operators and anonymous types.
Dim custs= From cust In customers Select cust.FirstName, cust.LastName

LINQ Extension Methods vs. Visual Basic LINQ Query Operators


There is an equivalent query operator for each LINQ extension method. The following list compares the syntax of the Where, OrderBy, GroupBy, and Join extension methods, and shows the equivalent query operator: The following code examples illustrate the Where extension method and the Where query operator.
[Where extension method example] Dim customersOver25 = customers.Where(Function(cust) cust.Age > 25) [Where query operator example] Dim customersOver25 = From cust In customers Where cust.Age > 25 Select cust

Note: When you use the query operators, the Select clause is mandatory. The preceding example uses a Select clause that retrieves the entire set of fields in each matching Customer object. The following code examples illustrate the OrderBy extension method and the orderby query operator.
[OrderBy extension method example] Dim sortedCustomers = customers.OrderBy(Function(cust)cust.FirstName) [Order By query operator example] Dim sortedCustomers = From cust In customers Order By cust.FirstName Select cust

The following code examples illustrate the GroupBy extension method and the Group By query operator.
[GroupBy extension method example] Dim customersGroupedByAge = customers.GroupBy(Function(cust) cust.Age) [Group By query operator example] Dim customersGroupedByAge = From cust In customers Group custBycust.Age Into Group

14-20

Programming in Visual Basic with Microsoft Visual Studio 2010

The following code examples illustrate the Join extension method and the Join query operator.
[Join extension method example] Dim customersAndCountries = customers.Join( companies, Function(cust) cust.CompanyName, Function(comp) comp.CompanyName, Function(cust, comp) New With { cust.FirstName, cust.LastName, comp.Country }) [Join query operator example] Dim customersAndCountries1 = From cust In customers Join comp In companies On cust.CompanyNameEqualscomp.CompanyName Select New With {cust.FirstName, cust.LastName, comp.Country}

You can also perform aggregate calculations on the results that LINQ queries that use query operators return. A query that uses the LINQ query operators returns an enumerable result set. To use the aggregate methods, such as Count, Max, Min, and Distinct, you must wrap the LINQ query in parentheses and then apply the appropriate method. The following code example shows how to use the Count extension method with a simple query that uses query operators.
Dim customerCount = (From cust In customers Select cust).Count()

You can adopt the same approach with the other summary extension methods. The following list provides examples of how to use the Max, Min, and Distinct extension methods with query operators: The following code example illustrates how to use the Max extension method to get the age of the oldest customer.
Dim maxAge = (From cust In customers Select cust.Age).Max()

The following code example illustrates how to use the Min extension method to get the age of the youngest customer.
Dim minAge = (From cust In customers Select cust.Age).Min()

The following code example illustrates how to use the Distinct extension method to get a list of all of the possible ages with duplicate values removed.
Dim possibleAges = (From cust In customers Select cust.Age).Distinct()

Question: In the following code examples, are both queries equivalent?


Dim payments As IEnumerable(Of Double) =

Using LINQ to Query Data

14-21

{232.12, 8378.53, 66.01, 4312.11, 156.00} Dim queryOperatorResult = (From payment in payments Where payment > 100 AndAlso payment < 500 Select payment).Max() Dim extensionMethodResult = payments.Where(Function(payment) payment > 100 AndAlso payment < 500).Max()

Additional Reading
For more information about LINQ query operators, see the Basic Query Operations (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211621&clcid=0x409

14-22

Programming in Visual Basic with Microsoft Visual Studio 2010

Deferred and Early Evaluation of Queries

When you use LINQ to define an enumerable collection either by using the LINQ extension methods or by using query operators, the application does not build the collection at the time that the LINQ extension methods or query operators are executed; the data is retrieved only when you iterate over the collection. This means that the data in the original collection can change after you execute a LINQ query, but before you retrieve the data that the query identifies; you will always fetch the most up-to-date data. For example, the query in the following code example defines an enumerable collection of U.S. companies.
Dim usCompanies = From a In companies Where String.Equals(a.Country, "United States") Select a.CompanyName

The data in the companies array is not retrieved, and any conditions that are specified in the where clause are not evaluated until you iterate through the usCompanies collection, as the following code example shows.
For Each name As String In usCompanies Console.WriteLine(name) Next

If you modify the data in the companies array after you define the usCompanies collection, but before you iterate through the collection (for example, if you add a new company that is based in the United States), you will see this new data. This strategy is referred to as deferred evaluation. You can also force evaluation of a LINQ query early and generate a shared, cached collection. This collection is a copy of the original data and will not change if the data in the collection changes. LINQ provides the ToList method to build a shared List object that contains a cached copy of the data, as the following code example shows.
Dim usCompanies = From a In companies.ToList() Where String.Equals(a.Country, "United States")

Using LINQ to Query Data

14-23

Select a.CompanyName

This time, the list of companies is fixed when you define the query. If you add more U.S. companies to the companies array, you will not see them when you iterate through the usCompanies collection. LINQ also provides the ToArray method that stores the cached collection as an array. Question: In your application, you construct a LINQ query to retrieve all the employee records from a database. In your code, after the LINQ query, you add a For Each statement to iterate through each record. You start to debug and step over the LINQ query and then pause before you enter the For Each statement. You then make a change to one of the employee records in the database. Finally, you return to the Microsoft Visual Studio debugger and continue to step into the For Each statement. Will the change that you made to the employee record be visible when you iterate through the results?

Additional Reading
For more information about LINQ queries and deferred execution, see the LINQ Query Samples - Query Execution page at http://go.microsoft.com/fwlink/?LinkID=211622&clcid=0x409

14-24

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 2

Building Dynamic LINQ Queries and Expressions

By using LINQ, you can query data from application code without worrying about the implementation of the underlying data source. However, with shared LINQ queries, you must provide the details of the query that you need to perform when you compile the application. You can also construct LINQ queries dynamically. This approach enables you to optimize queries by shaping them to the exact requirements that the user of the application specifies. This lesson describes how to build dynamic LINQ queries by using the types in the System.Linq.Expressions namespace in the .NET Framework.

Lesson Objectives:
After completing this lesson, you will be able to: Explain the purpose of a dynamic LINQ query. Describe expression trees and explain how you can use them to construct lambda expressions to build dynamic LINQ queries. Explain how to use reflection to obtain metadata for a type to use in a dynamic lambda expression. Describe the expression types. Build and compile a dynamic lambda expression and use it in a LINQ query.

Using LINQ to Query Data

14-25

What Is a Dynamic LINQ Query?

LINQ provides a very powerful mechanism that enables you to decouple the business logic of an application from the logic that is required to retrieve the data that the application uses. You can use shared LINQ queries in most situations, but there might be occasions when the various query criteria, the order in which data is required, or even the data to be retrieved is not known until runtime. For example, the form of a query may depend on user input or the results of some other processing. For example, you may need to query a database table that stores the results of tests that students take. This table might contain fields that hold the student's name, the name of the test, and the test score for that student. Using this simple scheme, you can develop an application to query the database and apply filters to the data. You can perform simple queries by using shared LINQ. However, suppose that you need to make the application more flexible. For example, you may want to filter the results based on students who achieved a score in a particular range for one or more tests, and sort them by a selection of keys (ascending/descending by test name, score, student name, or any other combination). On some occasions, you might want to retrieve all three fields from the database, but on others, you may be interested only in a subset of the fields. One option is to write several queries and then use If statements or Select Case statements to select the query to use; however, you must write a lot of code, and this approach increases the risk that you will introduce bugs. Another option is to retrieve all the data into a collection in memory, and then programmatically fetch the appropriate data from this collection, but this approach is also potentially error-prone and obviates the rationale for employing LINQ. Using a dynamic LINQ query helps to solve this problem. When you develop a dynamic LINQ query, you build a representation of the query in the form of an expression tree, compile the expression tree at runtime, and then run it. The query then exactly represents the user's needs, without the need to develop several queries or manually process the data in memory. Question: When might you use a dynamic LINQ query, instead of a shared LINQ query?

14-26

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is an Expression Tree?

An expression tree is a data structure that represents an expression that a query uses. A simple expression might be a simple constant or a reference to a variable or a property of an object. You can combine trees that represent simple expressions into more complex trees that involve operators. For example, consider the expression x > 3. This expression consists of two smaller expressions (the member variable x of object o and the constant 3) that are combined into a bigger expression by using the greater than operator. The following diagram shows the way in which you can visualize the expression.
> / / / Member: o.x \ \

\ Constant: 3

The corresponding expression tree for this expression consists of a MemberExpression object that references the x field in the o object and a ConstantExpression object that represents the constant value 3; these objects are combined into a BinaryExpression object that compares them by using the greater than operator. You can see from this structure that expression trees are naturally recursive. An expression tree can represent a constant or a member, or it can combine expression trees together by using operators. As an example, the expression tree in the following diagram represents the expression o.x> 3 AndAlso o.y< 6.
AndAlso / / / ---------/ \

----------

><

\ \ \

\ \ / \ Member: o.x Constant: 3

/ Member: o.y

/ /

\ Constant: 6

Using LINQ to Query Data

14-27

An expression tree provides a very flexible mechanism for developing an expression that you can use as part of a LINQ query. You can construct an expression tree that represents a lambda expression and then reference this lambda expression in a LINQ query. You can compile the expression tree that represents a lambda expression at runtime and then invoke this expression in the same way as an ordinary lambda expression. Question: How can you extend the tree in the following diagram to satisfy the expression (o.x > 3 AndAlso o.y> 6) OrElse o. y > 20?
&& / / / ---------/ \

----------

>>

\ \ \

\ \ / \ Member: o.x Constant: 3

/ \ Member: o.y Constant: 6

/ /

Additional Reading
For more information about expression trees, see the Expression Trees (C# and Visual Basic) page at http://go.microsoft.com/fwlink/?LinkId=192982

14-28

Programming in Visual Basic with Microsoft Visual Studio 2010

Expression Types

Every node in an expression tree is an expression object. The System.Linq.Expressions namespace in the .NET Framework class library defines expression types that you can use to represent any valid Visual Basic expression. All these types inherit from the Expression class in the System.Linq.Expressions namespace.
Note: If you develop a Windows Presentation Foundation (WPF) application, you should be aware that WPF defines an Expression type. You will need to use the Expression type from the System.Linq.Expressions namespace, so it is useful to create an alias for the namespace and use the alias to avoid ambiguity. The following code example shows how to add an alias for the System.Linq.Expressions namespace named Expressions.
Imports Expressions = System.Linq.Expressions

Combining Expressions by Using the Expression Class


You build simple expressions by using the appropriate expression type. You can then combine these simple expressions into more complex expressions by using the Expression class. The Expression class is an abstract type that acts as the parent of all of the various expression types. The Expression class also provides a large number of shared factory methods for combining expressions together into new expression trees by using any of the operators that are available in the Visual Basic language. For example, the Expression.Add factory method takes two Expression objects and generates a new expression tree that combines them by using the addition operator (+).
Note: The following sections describe some of the commonly used expression types and the shared factory methods that you use to construct them. There are many other expression types available in the System.Linq.Expressions namespace.

Using LINQ to Query Data

14-29

The BinaryExpression Class


The BinaryExpression class represents an expression that uses a binary operator, such as +, -, >, or <, to combine child expressions. Methods such as Expression.Add, Expression.Subtract, Expression.GreaterThan, and Expression.LessThan return a BinaryExpression object. The following code example shows how to create a BinaryExpression object that adds two values.
' Assume that expression1 and expression2 are expression tree objects Dim myExp As BinaryExpression = Expression.Add(expression1, expression2)

The ConstantExpression Class


You represent constant values by using the ConstantExpression class. To create a ConstantExpression object, you use the Expression.Constant shared method. You must provide the valueand optionally the type as a System.Type objectas arguments to the Constant method. Note that you can use the GetType operator to return the type of a type. The following code example shows how to define a ConstantExpression expression for the integer value .
Dim constant As ConstantExpression = Expression.Constant(5, GetType(Integer))

The MemberExpression Class


The MemberExpression class enables you to reference a property or field of an object. For example, you can use a MemberExpression object to represent the x.member expression in the binary expression x.member > 20. To create an instance of the MemberExpression class, you use the shared MakeMemberAccess method of the Expression class. The MakeMemberAccess method takes two arguments: a reference to the object that contains the member in the form of an Expression object and a reference to the member itself in the form of a System.Reflection.MemberInfo object. The Expression object that represents the object can be a reference to a parameter that is passed to a lambda expression (this is described later in this topic), or it can be a Constant expression if you need to refer to an object that was created outside a lambda expression.
Note: You can use reflection to create the MemberInfo object that represents a member of an object. Obtaining member references by using reflection is covered in the next topic. The following code example shows how to create a MemberExpression object to access a property of an object called myData of type MyType.
' Assume that the propertyInfo object is a valid instance of the ' MemberInfo class that references a field that the MyType type ' exposes Dim myData As MyType = ... Dim member As MemberExpression = Expression.MakeMemberAccess( Expression.Constant(myData), propertyInfo)

Occasionally, you may need to access a shared member of a type. In this case, you can specify null instead of a reference to a specific instance of an object as the first parameter to the MakeMemberAccess

14-30

Programming in Visual Basic with Microsoft Visual Studio 2010

method. The following code example shows how to create a MemberExpression object to access a shared property.
' Assume that the propInfo object is a valid MemberInfo object ' that references a shared property of a type Dim sharedProperty As MemberExpression = Expression.MakeMemberAccess(Nothing, propInfo)

The UnaryExpression Class


The UnaryExpression class represents expressions that are based on unary operations; that is, operations that act only on a single argument. An example of a unary operation is negating a value. To create a unary expression that negates a value, you use the shared Negate method of the Expression class, as the following code example shows.
' Assume that the parameter is a valid ParameterExpression object Dim negation As Expressions.UnaryExpression = Expressions.Expression.Negate(parameter)

You can also use the following methods to create a UnaryExpression object: ArrayLength. This method creates an expression that returns the length of a one-dimensional array that is provided as the parameter. Convert. This method creates an expression that converts the object that the first parameter specifies to the type that is specified by using a System.Type object as the second parameter. ConvertChecked. This method creates an expression that converts the object that the first parameter specifies to the type that is specified by using a System.Type object as the second parameter, and it performs overflow checking on the conversion. Negate. This method creates an expression that negates the value of an expression. NegateChecked. This method creates an expression that negates the value of an expression and checks for numeric overflow. Not. This method creates an expression that performs a bitwise NOT operation on the parameter. Quote. This method creates an expression that returns a constant value of the type of the parameter. TypeAs. This method creates an expression that performs an explicit reference or boxing conversion and returns null if the conversion fails. UnaryPlus. This method creates an expression that performs a unary plus operation.

Building Lambda Expressions


Lambda expressions contain two main elements: a list of parameters and a body that can return a value that is based on a calculation that involves these parameters. For example, the following code example shows a lambda expression that takes a single parameter called x. The body of the lambda expression is a binary expression that is based on this parameter.
Function(x) x > 2

You use the generic Expression(Of TDelegate) type to construct an expression tree that represents a lambda expression. The TDelegate type parameter should reference a delegate that matches the signature of the lambda expression. In this example, you can use the type Func(Of Integer, Boolean), which represents a delegate that takes a single integer parameter and returns a Boolean value.

Using LINQ to Query Data

14-31

You use the Expression.Parameter method to build an expression tree that represents a parameter to a lambda expression. This method expects the type of the parameter and a name as arguments. You can then construct a BinaryExpression object that references this parameter and performs the specified calculation. Finally, you can combine these two expression trees into a lambda expression by using the generic Expression.Lambda method, specifying the type of the delegate that the lambda expression references as the type parameter. The following code example shows how to build an expression tree for the lambda expression in the preceding code example.
Dim lambda As Expression(Of Func(Of Integer, Boolean)) = Nothing Dim param As ParameterExpression = Expression.Parameter(GetType(Integer), "x") Dim two As ConstantExpression = Expression.Constant(2, GetType(Integer)) Dim body As BinaryExpression = Expression.GreaterThan(param, two) lambda = Expression.Lambda(Of Func(Of Integer, Boolean))(body, param) Console.WriteLine(lambda.ToString())

The following code example shows the output that this code generates. Please note that this is the Visual C# lambda syntax.
x => (x > 2)

Question: How would you define an expression that checks whether a member named x is equal to the constant value 24?

Additional Reading
For more information about the expression types in the System.Linq.Expressions namespace, see the System.Linq.Expressions Namespace page at http://go.microsoft.com/fwlink/?LinkId=192983
For more information about the Expression class, see the Expression Class page at http://go.microsoft.com/fwlink/?LinkId=192984

14-32

Programming in Visual Basic with Microsoft Visual Studio 2010

Obtaining Type Information at Run Time

When Option Strict is turned on, the Visual Basic compiler enforces type-safety and generates compiler errors if your code is not type-safe. When you use expression trees, type-safety is checked at runtime after an expression has been constructed, but before it is evaluated. Some Expression methods, such as MakeMemberAccess, expect you to provide type information about the member that is referenced. Other methods, such as Expression.Constant and Expression.Parameter, expect you to provide information about the type of the constant or parameter that is involved in an expression. You can use reflection to obtain this type information dynamically.

The System.Type Type


The simplest way to obtain information about a type is to use the GetType operator. This operator expects a type as its argument and returns a System.Type object that contains detailed metadata that describes this type. The common language runtime (CLR) can use this metadata to perform type checking. Using reflection to obtain type information is a computationally expensive operation. If you only need to reference a type once, you can use the GetType keyword inline; however, if you need to reference the same type multiple times, you should create an instance of the Type type and use that instance instead. The following code example shows how to create an instance of the Type type to represent the String type.
Dim stringType As Type = GetType(String)

You can then reference the stringType object to specify the type of ParameterExpression object, as the following code example shows.
Dim param As ParameterExpression = Expression.Parameter(stringType, "data")

Using LINQ to Query Data

14-33

The MemberInfo Type


The Type class enables you to reference a type; however, you may often need to access a member in an object, rather than the object. To reference a member of a type, you can use the MemberInfo class. The MemberInfo class is defined in the System.Reflection namespace. To create a MemberInfo object, you use the instance methods that the Type class exposes. There are a large number of these methods, including GetField and GetProperty, which you can use to obtain information about the fields and properties that a type exposes. Many of these methods require you to specify the name of the member as a string. However, be warned that if you provide an invalid member name, the method will return null. The following code example shows how to create a MemberInfo object to represent the Length property of a string object. You can use the stringLength object as the MemberInfo argument to the MakeMemberAccess method and construct an expression tree that compares the length of a string to a specific value.
Dim Dim Dim Dim stringType As Type = GetType(String) stringLength As MemberInfo = stringType.GetProperty("Length") data As String = "Hello, World!" length As MemberExpression = Expression.MakeMemberAccess( Expression.Constant(data), stringLength) Dim maxLength As ConstantExpression = Expression.Constant(25) Dim compareLength As BinaryExpression = Expression.GreaterThanOrEqual( length, maxLength) Console.WriteLine(compareLength.ToString())

The following code example shows the output that this code generates.
("Hello, World!".Length >= 25)

Question: What is the result of calling the GetProperty method on an instance of the Type class that represents the String type and providing the string "ToString" as the argument?

14-34

Programming in Visual Basic with Microsoft Visual Studio 2010

Compiling and Running a Dynamic LINQ Query

An expression tree is simply a data structure. To use an expression tree at runtime, you must compile it. You can achieve this by using the Compile method of the Expression(Of TDelegate) type. The Compile method takes an expression tree that represents a lambda expression and returns a TDelegate object that you can invoke. If the lambda expression defines an element of a LINQ query, you simply iterate over the results that the query returns to run the expression.
Note: You are not confined to consuming dynamic lambda expressions in LINQ queries; you can use lambda expressions that you have constructed dynamically anywhere that you can use an ordinary, shared lambda expression. To run a dynamic lambda expression in these situations, you can use the DynamicInvoke method of the TDelegate object that the Compile method returns. The following code example shows a complete example of how to build a LINQ query dynamically and the results that you will see when you run it. The example iterates over TestScore objects that are held in a List(Of TestScore) collection. It retrieves all test scores with a mark of more than 50 and displays the names of the candidates with these scores, in alphabetical order.
Public Class TestScore Public Property Score As Integer Public Property Name As String End Class ... Dim scores As New { New TestScore New TestScore New TestScore New TestScore New TestScore New TestScore List(Of TestScore)() From With With With With With With {.Score {.Score {.Score {.Score {.Score {.Score = = = = = = 90, .Name = "Mike"}, 60, .Name = "Louisa"}, 85, .Name = "Antony"}, 100, .Name = "Richard"}, 45, .Name = "Jason"}, 35, .Name = "Tom"},

Using LINQ to Query Data

14-35

New New New New New

TestScore TestScore TestScore TestScore TestScore

With With With With With

{.Score {.Score {.Score {.Score {.Score

= = = = =

96, 26, 71, 91, 34,

.Name .Name .Name .Name .Name

= = = = =

"Chris"}, "Adam"}, "Charles"}, "Alison"}, "John"}

' The following code generates a LINQ query that is equivalent ' to the following: ' ' Dim passes = scores.Where( _ ' Function(testResult) testResult.Score > 50). ' OrderBy(Function(testResult) testResult.Name). ' Select(Function(testResult) testResult.Name) ' ' Note that this query involves three lambda expressions: ' ' 1. The lambda expression for the Where method takes a TestScore ' object and returns a Boolean value. ' 2. The lambda expression for the OrderBy method takes a TestScore ' object and returns a string (the data to order by). ' 3. The lambda expression for the Select method takes a TestScore ' object and returns a string (the candidate names). ' Build the lambda expression for the Where method: ' Function(testResult) testResult.Score > 50 Dim testScoreType As Type = GetType(TestScore) Dim testResultParam As ParameterExpression = Expression.Parameter(testScoreType, "testResult") Dim scoreProperty As MemberInfo = testScoreType.GetProperty("Score") Dim valueInScoreProperty As MemberExpression = Expression.MakeMemberAccess(testResultParam, scoreProperty) Dim fifty As ConstantExpression = Expression.Constant(50, GetType(Integer)) Dim scoreGreaterThanFifty As BinaryExpression = Expression.GreaterThan(valueInScoreProperty, fifty) Dim whereExpression As Expression(Of Func(Of TestScore, Boolean)) = Expression(Of Func(Of TestScore, Boolean)).Lambda( _ Of Func(Of TestScore, Boolean)) _ (scoreGreaterThanFifty, testResultParam) ' Build the lambda expression for the OrderBy method: ' Function(testResult) testResult.Name Dim nameProperty As MemberInfo = testScoreType.GetProperty("Name") Dim valueInNameProperty As MemberExpression = Expression.MakeMemberAccess(testResultParam, nameProperty) Dim orderByExpression As Expression(Of Func(Of TestScore, String)) = Expression(Of Func(Of TestScore, String)).Lambda( _ Of Func(Of TestScore, String)) _ (valueInNameProperty, testResultParam) ' Build the lambda expression for the Select method: ' Function(testResult) testResult.Name Dim selectExpression As Expression(Of Func(Of TestScore, String)) = Expression(Of Func(Of TestScore, String)).Lambda( _ Of Func(Of TestScore, String)) (valueInNameProperty, testResultParam) ' Compile the lambda expressions, starting with the Where expression Dim passingScores As IEnumerable(Of TestScore) =

14-36

Programming in Visual Basic with Microsoft Visual Studio 2010

scores.Where(whereExpression.Compile()) ' Now append the OrderBy expression passingScores = passingScores.OrderBy(orderByExpression.Compile()) ' Finally, add the Select expression Dim passes As IEnumerable(Of String) = passingScores.Select( selectExpression.Compile()) ' Run the query and display the results For Each pass In passes Console.WriteLine(pass) Next

The following code example shows the output that this code generates.
Alison Antony Charles Chris Louisa Mike Richard

Question: What is the purpose of the Compile method?

Additional Reading
For more information about using expression trees to build dynamic queries, see the How to: Use Expression Trees to Build Dynamic Queries (C# and Visual Basic) page at http://go.microsoft.com/fwlink/?LinkId=192985

Using LINQ to Query Data

14-37

Lab: Using LINQ to Query Data

Objectives:
After completing this lab, you will be able to: Use the LINQ query operators to retrieve data from an enumerable collection. Use expression trees and the LINQ extension methods to build dynamic LINQ queries.

Introduction
In this lab, you will use the LINQ query operators to retrieve data from a collection. You will then examine how to construct a LINQ query dynamically and optimize it for better performance.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

14-38

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Scenario

Fabrikam, Inc. produces a range of highly sensitive measuring devices that can repeatedly measure objects and capture data. Fabrikam, Inc. has a large number of analytical applications that analyze data. This data is held in files that various measuring devices have generated. However, the logic in many of these applications is convoluted, and the applications themselves are difficult to use. You have been asked to build a more user-friendly application to analyze the results of one specific set of data: the results of girder stress tests. This data consists of the following fields: The date of the test The temperature at which the test was recorded The stress that was applied to the girder The deflection of the girder that this stress caused

This application must enable users to filter the data that they want to view according to the criteria that they specify.

Using LINQ to Query Data

14-39

Exercise 1: Using the LINQ Query Operators


In this exercise, you will write a program that uses the LINQ query operators to retrieve and display data. The data is provided in a binary file. The application will read this data into a BinaryTree object and present a WPF window that enables the user to specify criteria for viewing the data. The window will then fetch and display all the matching data from the BinaryTree object in date order. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. 6. 7. 8. 9. Open the starter solution. Declare variables to specify the stress data file name and the Tree object. Add a method to read the test data. Read the test data by using a BackgroundWorker object. Define the LINQ query. Run the query. Run the query by using a BackgroundWorker object. Display the results. Test the solution.

Task 1: Open the starter solution.


Open Microsoft Visual Studio 2010. Import the code snippets from the D:\Labfiles\Lab14\Snippets folder. Open the StressDataAnalyzer solution in the D:\Labfiles\Lab14\Ex1\Starter folder. Examine the user interface (UI) for the StressDataAnalyzer application. Note the following features of the application. The stress test data is generated by a stress test device. The data is stored in a binary data file, and this application reads the data from this file when the application starts to run. The application holds the data in memory by using a Tree object. The UI contains two main areas. The upper area enables the user to specify criteria to match stress data. The lower area displays the data. The stress test data criteria are: i. ii. The date that the test was performed. The temperature at which the test was performed.

iii. The stress that was applied during the test. iv. The deflection that resulted from applying the stress. Each criterion is specified as a range by using the slider controls. After selecting the criteria to match, the user clicks Display to generate a LINQ query that fetches the matching data from the Tree object in memory and shows the results.

Task 2: Declare variables to specify the stress data file name and the Tree object.
Review the Task List. In the Task List, locate the TODO: - Declare filename and tree variables task, and then doubleclick this task. This task is located in the DataAnalyzer.xaml.vb class.

14-40

Programming in Visual Basic with Microsoft Visual Studio 2010

Delete the TODO: - Declare filename and tree variables comment, and then add code to declare the following variables: A private constant String object named stressDataFilename. Initialize the object with the string "D:\Labfiles\Lab14\StressData.dat". This is the name of the data file that holds the stress data. A private Tree object named stressData that is based on the TestResult type. This Tree object will hold the data that is read from the stress data file. Initialize this object to null.

The TestResult type is a structure that contains the following four fields, corresponding to the data for each stress test record: TestDate. This is a DateTime field that contains the date on which the stress test was performed. Temperature. This is a Short field that contains the temperature, in Kelvin, at which the test was performed. AppliedStress. This is another Short field that specifies the stress, in kiloNewtons (kN), that was applied during the test. Deflection. This is another Short field that specifies the deflection of the girder, in millimeters (mm), when the stress was applied.

The TestResult type implements the IComparable interface. The comparison of test data is based on the value of the Deflection field.

Task 3: Add a method to read the test data.


In the Task List, locate the TODO: - Add a method to read the contents of the StressData file task, and then double-click this task. Delete the TODO: - Add a method to read the contents of the StressData file comment, and then add the method in the following code example, which is named ReadTestData. This method reads the stress data from the file and populates the Tree object. It is not necessary for you to fully understand how this method works, so you can either type this code manually, or you can use the Mod14ReadTestData code snippet.
Private Sub ReadTestData() Try ' Open a stream over the file that holds the test data. Using readStream As FileStream = File.Open(stressDataFilename, FileMode.Open) ' The data is serialized as TestResult instances. ' Use a BinaryFormatter object to read the stream and ' deserialize the data. Dim formatter As New BinaryFormatter() Dim initialNode As TestResult = CType(formatter.Deserialize(readStream), TestResult) ' Create the binary tree and use the first item retrieved ' as the root node. (Note: The tree will likely be ' unbalanced, because it is probable that most nodes will ' have a value that is greater than or equal to the value in ' this root node - this is because of the way in which the ' test results are generated and the fact that the TestResult ' class uses the deflection as the discriminator when it ' compares instances.) stressData = New Tree(Of TestResult)(initialNode) ' Read the TestResult instances from the rest of the file ' and add them into the binary tree. While readStream.Position < readStream.Length Dim data As TestResult = CType(formatter.Deserialize(readStream), TestResult)

Using LINQ to Query Data

14-41

stressData.Insert(data) End While End Using Catch ex As Exception MessageBox.Show(ex.Message) End Try End Sub

Task 4: Read the test data by using a BackgroundWorker object.


In the Window_Loaded method, add code to perform the following tasks: a. b. Create a BackgroundWorker object named workerThread. Configure the workerThread object; the object should not report progress or support cancellation.

In the Window_Loaded method, add an event handler for the workerThread.DoWork event, by using a lambda expression. When the event is raised, the event handler should invoke the ReadTestData method. Add an event handler for the workerThread.RunWorkerComplete event, by using a lambda expression. When the event is raised, the event handler should perform the following tasks: a. b. Enable the DisplayResultsButton control. Display the message 'Ready' in the StatusMessageItem Status Bar Item on the status bar at the lowermost part of the Windows Presentation Foundation (WPF) window.

Hint: Set the Content property of a status bar item to display a message in that item. At the end of the Window_Loaded method, add code to perform the following tasks: a. b. Start the workerThread BackgroundWorker object running asynchronously. Display the message "Reading Test Data" in the StatusMessageItem item on the status bar at the lowermost part of the WPF window.

Task 5: Define the LINQ query.


1. In the Task List, locate the TODO: - Define the LINQ query task, and then double-click this task. This task is located in the CreateQuery method. Replace the existing code in the method with code that defines an IEnumerable(Of TestResult) object called query. Initialize the query variable with a LINQ query that retrieves all the TestResult objects in the stressData tree that meet the following criteria. The query should order returned values by the TestDate property. The query should evaluate each object by using the following criteria. You can either type this code manually, or you can use the Mod14GetTestResultObjects code snippet. The value of the TestDate property is greater than or equal to the dateStart parameter value. The value of the TestDate property is less than or equal to the dateEnd parameter value. The value of the Temperature property is greater than or equal to the temperatureStart parameter value. The value of the Temperature property is less than or equal to the temperatureEnd parameter value.

14-42

Programming in Visual Basic with Microsoft Visual Studio 2010

The value of the AppliedStress property is greater than or equal to the appliedStressStart parameter value. The value of the AppliedStress property is less than or equal to the appliedStressEnd parameter value. The value of the Deflection property is greater than or equal to the deflectionStart parameter value. The value of the Deflection property is less than or equal to the deflectionEnd parameter value.

At the end of the method, return the query object. Build the solution and correct any errors.

Task 6: Run the query.


In the Task List, locate the TODO: - Execute the LINQ query task, and then double-click this task. This task is located in the FormatResults method. This method takes an enumerable collection of TestResult objects as a parameter and generates a string that contains a formatted list of TestResult objects. The parameter is the item that the CreateQuery method returns. Iterating through this list runs the LINQ query. Delete the TODO: - Execute the LINQ query comment, and then add code to the FormatResults method to perform the following task: For each item that the query returns, format and append the details of each item to the builder StringBuilder object. Each item should be formatted to display the following properties in double-tab delimited format. i. ii. iii. iv. TestDate Temperature AppliedStress Deflection

Build the solution and correct any errors.

Task 7: Run the query by using a BackgroundWorker object.


In the Task List, locate the TODO: - Add a BackgroundWorker DoWork event handler to invoke the query operation task, and then double-click this task. This task is located in the DisplayResultsButton_Click method. This method calls the CreateQuery method to generate the LINQ query that matches the criteria that the user specifies, and it then runs the query to generate and format the results by using a BackgroundWorker object called workerThread. Delete the TODO: - Add a BackgroundWorker DoWork event handler to invoke the query operation comment, and then define an event handler for the workerThread.DoWork event, by using a lambda expression. Add code to the event handler to invoke the FormatResults method, passing the query object as the parameter to the method. Store the value that the method returns in the Result parameter of the DoWork event handler. Build the solution and correct any errors.

Task 8: Display the results.


Below the event handler for the DoWork event, add an event handler for the workerThread.RunWorkerComplete event, by using a lambda expression. Add code to the event handler to perform the following tasks. a. Update the ResultsTextBox.Text property with the value of the Result parameter of the RunWorkerComplete event handler.

Using LINQ to Query Data

14-43

b. c.

Enable the DisplayResultsButton button. Update the StatusMessageItem status bar item to "Ready".

Build the solution and correct any errors.

Task 9: Test the solution


Run the application. If you receive an error message stating The process cannot access the file 'D:\Labfiles\Lab14\StressData.dat' because it is being used by another process", click OK. Click Display, and make a note of the Time (ms) value that is displayed next to the Display button. Click Display two more times. The times for these operations will probably be less than the time that the initial query took because the various internal data structures have already been initialized. Make a note of these times.

Note: The time that is displayed is the time that is required to fetch the data by using the LINQ query, but not the time that is taken to format and display this data. This is why the "Fetching results" message appears for several seconds after the data has been retrieved. When the query is complete, examine the contents of the box in the lower part of the window. The search should return 40,641 values. Use the DatePicker and slider controls to modify the search criteria to the values in the following table, and then click Display again. Criteria Test Date Temperature Value From 02/01/2009 To 02/28/2009 From 250 to 450

When the query is complete, examine the contents of the box in the lower part of the window. The search should return 1,676 values. Note the time that it took to complete the searchthe time should be less than the times that you recorded in Step 3. Keep a note of these values for comparison in Exercise 2. Close the Stress Data Analyzer window, and then return to Visual Studio. Currently, any search through the data uses all four criteriadate, temperature, applied stress, and deflectionregardless of the values that are specified in the UI. If the user does not change the default values for any criteria, the LINQ query that the application generates still contains criteria for each field. This is rather inefficient. However, you can construct dynamic LINQ queries to enable you to generate a custom query that is based only on the criteria that are specified at run time. You will implement this functionality in the next exercise.

14-44

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 2: Building Dynamic LINQ Queries


In this exercise, you will extend the WPF application to enable users to specify the sort sequence and limit the number of rows that are retrieved. You will modify the application to build a dynamic LINQ query that matches the users' specifications, run it, and display the results. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. 6. 7. 8. Open the StressDataAnalyzer solution. Dynamically build a lambda expression for the query criteria. Dynamically build the date expression tree. Dynamically build numeric expression trees. Combine the expression trees. Build a lambda expression for the OrderBy statement. Examine the CreateQuery method. Test the solution.

Task 1: Open the StressDataAnalyzer solution.


Open the StressDataAnalyzer solution in the D:\Labfiles\Lab14\Ex2\Starter folder. Review the Task List. Examine the modified UI for the StressDataAnalyzer application. Note the following features of the application. The UI is an extended version of that used in Exercise 1. The user can specify which criteria to apply by using check boxes. Any criteria that are not selected are not included in the LINQ query. The user can change the order in which the data is displayed by selecting the appropriate option button in the Order By section of the window. The user can limit the number of items that a query returns by selecting the Limit check box and by using the slider control to specify the number of items.

Task 2: Dynamically build a lambda expression for the query criteria.


In the Task List, locate the TODO: - Complete the BuildLambdaExpressionForQueryCriteria method task, and then double-click this task. This task is located in the BuildLambdaExpressionForQueryCriteria method. The BuildLambdaExpressionForQueryCriteria method dynamically constructs a lambda expression from the values that are passed in as parameters. There are 12 parameters, which are divided into four groups. The dateRangeSpecified parameter is a Boolean value that indicates whether the user has selected the date criteria in the window, and the startDate and endDate parameters contain the start date and end date values that the user specifies. If the dateRangeSpecified parameter is False, the date is not included in the criteria for matching stress data. The same logic applies to the remaining parameters. The value that the BuildLambdaExpressionForQueryCriteria method returns is an Expression object. The Expression type represents a strongly typed lambda expression as a data structure in the form of an expression tree. The type parameter is a delegate that indicates the form of the lambda expression. In the BuildLambdaExpressionForQueryCriteria method, the lambda expression takes a TestResult object and returns a Boolean value that indicates whether this object should be included in the results that are generated by running the lambda expression.

Using LINQ to Query Data

14-45

The existing code in this method creates a reference to an Expression object named lambda. You will add code to populate this object with an expression tree that represents a lambda expression that matches the query criteria that the 12 parameters specify. If the user does not specify any query criteria, this method returns a null value.
Note: The Expression type is located in the System.Linq.Expressions namespace. The application creates an alias for this namespace called Expressions. You cannot refer to the Expression type without the qualifying namespace in a WPF application because the WPF assemblies also contain a type called Expression. Delete the TODO: - Complete the BuildLambdaExpressionForQueryCriteria method comment, and then add code to perform the following tasks. a. Create a Type reference for the TestResult type named testResultType.

Hint: Creating a type reference in this way enables you to repeatedly refer to an object type without repeatedly calling the GetType method. The GetType method is a relatively costly method compared to retrieving an object reference. b. Create an Expressions.ParameterExpression object named itemBeingQueried by using the Expressions.Expression.Parameter shared method. Specify the testResultType type reference as the type of the parameter, and use the string "item" as the name of the parameter.

Hint: The string that is passed as the second parameter to the method call defines how your lambda expression will refer to the object that is being queried. In this example, one part of the resultant expression will resemble "item.TestDate >= startDate". Add code to the method to create the following Expressions.BinaryExpression objects; each object should have an initial value of Nothing. dateCondition temperatureCondition appliedStressCondition deflectionCondition

You will populate these expression objects with query criteria that match the parameters that are passed in to the method. You will then combine these expression objects together to form the complete lambda expression tree. Add code to the method to invoke the BuildDateExpressionBody method, and store the result in the dateCondition object. Pass the following values as parameters to the method call: dateRangeSpecified startDate endDate testResultType itemBeingQueried

14-46

Programming in Visual Basic with Microsoft Visual Studio 2010

Note: The BuildDateExpressionBody method returns a BinaryExpression object that checks the stress test data against the startDate and endDate values. You will update the BuildDateExpressionBody method in the following task. Add code to the method to invoke the BuildNumericExpressionBody method, and store the result in the temperatureCondition object. Pass the following values as parameters to the method call: temperatureRangeSpecified fromTemperature toTemperature testResultType A string that contains the value "Temperature" itemBeingQueried

Note: The BuildNumericExpressionBody method also returns a BinaryExpression object that will form part of the dynamic LINQ query. In this case, the data that this part of the query checks will contain numeric data, rather than a DateTime value, and the name of the field that is being checked is Temperature. You will update the BuildNumericExpressionBody method later in the lab. Add code to the method to invoke the BuildNumericExpressionBody method, and store the result in the appliedStressCondition object. Pass the following values as parameters to the method call: appliedStressRangeSpecified fromStressRange toStressRange testResultType A string that contains the value "AppliedStress" itemBeingQueried

Add code to the method to invoke the BuildNumericExpressionBody method, and store the result in the deflectionCondition object. Pass the following values as parameters to the method call: deflectionRangeSpecified fromDeflection toDeflection testResultType A string that contains the value "Deflection" itemBeingQueried

Add code to the method to invoke the BuildLambdaExpressionBody method, and store the result in a new Expressions.Expression object named body. Pass the dateCondition, temperatureCondition, appliedStressCondition, and deflectionCondition objects as parameters to the method.

Note. The BuildLambdaExpressionBody method takes the four expression objects, each of which evaluates a single property in a TestResult object, and combines them into a complete lambda

Using LINQ to Query Data

14-47

expression that evaluates all the properties that the user specifies criteria for. You will complete the BuildLambdaExpressionBody method later in the lab. Add code to the method to invoke the Expression.Lambda generic method, and store the response in the lambda object. The Expression.Lambda method should construct a lambda expression from the body of the lambda expressions in the bodyExpression object and the itemBeingQueriedParameterExpression object. Specify the delegate type Func(Of TestResult, Boolean) as the type parameter of the method.

Hint: The shared Expression.Lambda method constructs an expression tree that represents a completed lambda expression, including the data that is being queried by the expression. Build the project and correct any errors.

Task 3: Dynamically build the date expression tree.


In the Task List, locate the TODO: - Complete the BuildDateExpressionBody method task, and then double-click this task. This task is located in the BuildDateExpressionBody method. The existing code in this method defines a BinaryExpression object named dateCondition. This object will be used to return the expression tree that evaluates date values. The method then checks that the dateRangeSpecified parameter is True. You will add code to this conditional statement to build an expression tree that is equivalent to the condition in the following code example.
item.TestDate >= startDate AndAlso item.TestDate <= endDate

If the user did not specify any date criteria, this method returns a null expression tree. Delete the TODO: - Complete the BuildDateExpressionBody method comment, and then add code to create a new MemberInfo object named testDateProperty. Call the GetProperty method to generate code that retrieves the TestDate property from the testResultType type parameter. Pass the string "TestDate" as the parameter to the GetProperty method. Add code to the method to create a MemberExpression object named testDateMember. Populate the object with the value that is returned by calling the Expression.MakeMemberAccess method, passing the itemBeingQueried parameter and the testDateProperty value as parameters to the method.

Note: A MemberExpression object is an expression that represents access to a property of the object that is being queried. In this case, the object represents the item.TestDate property. Add code to create an Expressions.ConstantExpression object named lowerDate, and populate the object with the result of calling the Expression.Expressions.Constant method. Pass the startDate parameter as a parameter to the method call.

Note: A ConstantExpression object is an expression that represents the results of evaluating a constant value. In this case, the object represents the value in the startDate variable. Add code to create an Expressions.BinaryExpression object named lowerDateCondition, and populate the object with the result of calling the Expressions.Expression.GreaterThanOrEqual method. Pass the testDateMember and lowerDate objects as parameters to the method call.

14-48

Programming in Visual Basic with Microsoft Visual Studio 2010

Note: The GreaterThanOrEqual method generates a binary expression that combines the testDateMember object (representing the "Me.startDate" portion of the expression) and the lowerDate object (representing the "startDate" portion of the expression) to generate a tree for the expression "Me.startDate >= startDate". Using the same principles that you saw in Steps 4 and 5, add code to perform the following tasks: a. b. Create a ConstantExpression object named upperDate by passing the endDate parameter as a parameter to the method call. Create a BinaryExpression object named upperDateCondition by invoking the Expression.LessThanOrEqual method. Pass the testDateMember and upperDate objects as parameters to the method call.

Note: This code should build the second part of the date evaluation expression, which represents "endDate <= testDateMember". Add code to combine the expressions in the lowerDate and upperDateExpressionTree objects into a single Boolean expression tree that returns True if both conditions are True, or False otherwise; call the Expressions.Expression.AndAlso shared method to combine the expressions together, and store the result in the dateCondition object.

Note: The Expressions.Expression.AndAlso method combines the two discrete expressions that you just created, "item.TestDate>= startDate" and "Item.TestDate <= endDate" into a BinaryExpression object that represents the expression "item.TestDate >= startDate AndAlso Item.TestDate <= endDate". Build the project and correct any errors.

Task 4: Dynamically build numeric expression trees.


In the Task List, locate the TODO: - Complete the BuildNumericExpressionBody method task, and then double-click this task. This task is located in the BuildNumericExpressionBody method. The existing code in this method defines a BinaryExpression object named booleanCondition. This object will be used to return the expression tree that evaluates conditions based on Short integer values. You will add code to this conditional statement to build an expression tree that is equivalent to the expression in the following code example, where propertyName represents the value of the propertyName parameter.
item.PropertyName >= lowerRange AndAlso item.PropertyName <= upperRange

Delete the TODO: - Complete the BuildNumericExpressionBody method comment, and then add code to generate the first half of the expression by performing the following tasks. a. Create a new MemberInfo object named testProperty. Call the GetProperty method to generate code that retrieves the property that the propertyName parameter specifies from the testResultType type parameter. Create a MemberExpression object named testMember that represents access to the property that the testProperty object specifies; call the shared Expression.MakeMemberAccess method, passing the itemBeingQueried parameter and the testProperty object as parameters. Create a ConstantExpression object named lowerValue by invoking the Expression.Constant method. Pass the lowerRange parameter as a parameter to the method call.

b.

c.

Using LINQ to Query Data

14-49

d.

Create a BinaryExpression object named lowerValueCondition, which combines the testMember and lowerValue expression objects into a GreaterThanOrEqual binary expression.

Hint: Your code should build the first half of the target expression, which represents "item.PropertyName >= lowerRange", where PropertyName represents the value of the propertyName parameter. Your code should use similar syntax to that used to generate the expression in Task 3. Add code to generate the second half of the target expression by performing the following tasks: a. Create a ConstantExpression object named upperValue by invoking the shared Expression.Constant method. Pass the upperRange parameter as a parameter to the method call. Create a BinaryExpression object named upperValueCondition, which combines the testMember and upperValue expression objects into a LessThanOrEqual binary expression.

b.

Hint: Your code should build the second half of the target expression, which represents "item.PropertyName <=upperRange", where PropertyName represents the value of the propertyName parameter. Your code should again use similar syntax to that used to generate the expression in Task 3. At the end of the method, add code to set the booleanCondition object to an expression that combines the lowerValueCondition and upperValueCondition expressions by using the shared Expressions.Expression.AndAlso method. Build the project and correct any errors.

Task 5: Combine the expression trees.


In the Task List, locate the TODO: - Complete the BuildLambdaExpressionBody method task, and then double-click this task. This task is located in the BuildLambdaExpressionBody method. This method takes four parameters that define the expression trees for each of the possible criteria that the user can enter. If any criteria are missing, the corresponding expression tree is null. The purpose of this method is to combine these expression trees together into an overall expression tree that includes all the criteria that the user enters. The existing code in this method creates an Expression object named body. This object will contain the combined binary expressions that form the body of the LINQ query. If the user did not specify any criteria, the body object is assigned an expression tree that contains the Boolean constant True. This expression tree causes all items to be retrieved. Delete the TODO: - Complete the BuildLambdaExpressionBody method comment, and then add code to check whether the dateCondition parameter is null. If not, set the body object to the dateCondition expression tree. Add code to check whether the temperatureCondition parameter is null, and if not, perform the following tasks: a. b. If the body object is null, set the body object to the temperatureCondition expression tree. If the body object is not null, combine the expression tree in the body object with the expression tree in the temperatureCondition parameter by using the shared Expressions.Expression.AndAlso method. Assign the result to the body object.

Repeat the logic in Step 3 and add the expression tree that the appliedStressCondition parameter defines to the body expression tree.

14-50

Programming in Visual Basic with Microsoft Visual Studio 2010

Repeat the logic in Step 3 and add the expression tree that the deflectionCondition parameter defines to the body expression tree. Build the project and correct any errors.

Task 6: Build a lambda expression for the OrderBy statement.


In the Task List, locate the TODO: - Create the type reference and ParameterExpression in the BuildLambdaExpressionForOrderBy method task, and then double-click this task. This task is located in the BuildLambdaExpressionForOrderBy method. The purpose of this method is to construct an expression that specifies the order in which the data should be retrieved. The parameter to this method is a value from the OrderByKey enumeration. This enumeration is defined as part of the application and contains the following values: ByDate, ByTemperature, ByAppliedStress, ByDeflection, and None. The following code example shows the form of the lambda expression that this method generates.
item => item.Property

In this example, Property references the property from the TestResult type that corresponds to the parameter that is passed into the method. If the user does not specify a sort key, this method returns a null value. Delete the TODO: - Create the type reference and ParameterExpression in the BuildLambdaExpressionForOrderBy method comment, and then add code to the method to create the ParameterExpression object that defines the parameter for the lambda expression by performing the following tasks.

Note: You will need to create a Type reference to the TestResult object, and the lambda expression should refer to the object item. a. b. Create a Type reference named testResultType by using the GetType operator and passing a TestResult object as a parameter. Create a ParameterExpression object named itemBeingQueried by using the shared Expressions.Expression.Parameter method. Specify the testResultType object and a string that contains the text "item" as parameters In the BuildLambdaExpressionForOrderBy method, replace the TODO: - Create a MemberExpression and MemberInfo object comment with code to perform the following tasks. Create a MemberExpression object named sortKey, and initialize this object to null. Create a MemberInfo object named propty, and initialize this object to null.

c. d.

Replace the TODO: - Evaluate the obKey parameter to determine the property to sort by comment with code to evaluate the obKey parameter. Use the GetProperty method of the testResultType variable to generate code that retrieves the corresponding property value from the item that is specified as the parameter to the lambda expression. Store the result in the property variable. The following table lists the name of each property to use, depending on the value of the obKey parameter. You can either type this code manually, or you can use the Mod14SelectCaseobKey code snippet. obKey value ByDate ByTemperature testResultType property to use "TestDate" "Temperature"

Using LINQ to Query Data

14-51

obKey value ByAppliedStress ByDeflection

testResultType property to use "AppliedStress" "Deflection"

Note: Near the beginning of the BuildLambdaExpressionForOrderBy method, a conditional statement prevents the method from performing this code if the obKey parameter has the value OrderByKey.None; therefore, you do not need to check for this value. Replace the TODO: -Construct the expression that specifies the OrderBy field comment with code that retrieves the value that the property variable specifies from the item that the itemBeingQueried variable specifies. To do this, call the shared Expressions.Expression.MakeMemberAccess method, and pass the itemBeingQueried expression tree and the property object as parameters to this method. Replace the TODO: - Create a UnaryExpression object to convert the sortKey object to a ValueType comment with code to create a new UnaryExpression object named convert by invoking the shared Expressions.Expression.Convert method. Pass the sortKey object and the type of the ValueType type as parameters to the method call. This step is necessary because the possible sort keys are all value types, and they must be converted to ValueType objects for the ordering to function correctly. Replace the TODO: - Create the OrderBy lambda expression comment with code to combine the converted unary expression that contains the sort key and the itemBeingQueried variable into a lambda expression by using the shared Expression.Lambda generic method. Specify the type Func(Of TestResult, ValueType) as the type parameter to the Lambda method; the resulting lambda expression takes a TestResult object as the parameter and returns a ValueType object. Build the project and correct any errors.

Task 7: Examine the CreateQuery method.


In the Task List, locate the TODO: - Examine the CreateQuery method task, and then double-click this task. This task is located in the CreateQuery method. This method is the starting point for the lambda expression generation. The method accepts parameters that indicate which query criteria the lambda expression should include, and the upper and lower ranges for each of these criteria. The method first calls the BuildLambdaExpressionForQueryCriteria method to construct a lambda expression that incorporates the query criteria. It then calls the BuildLambdaExpressionForOrderBy method to construct the lambda expression that defines the sort order for retrieving the data. Note that, at this point, it is possible that either of these expressions may still be null if the user either did not specify any criteria, or did not specify a sort key. After the method creates the expression objects, it creates an IEnumerable generic collection named query that is based on the TestResult type, and it initializes the object with the data in the stressData parameter. If the lambda expression that specifies the query criteria is not null, the method then filters the data in the IEnumerable collection by invoking the Where LINQ extension method on the collection. The parameter to the Where method is the lambda expression that contains the query criteria. Note that the Compile method of an Expression(Of TDelegate) object converts the expression tree into a compiled lambda expression that the common language runtime (CLR) can execute.

14-52

Programming in Visual Basic with Microsoft Visual Studio 2010

If the lambda expression that defines the sort order is not null, this method then applies this lambda expression to the IEnumerable collection by using the OrderBy LINQ extension method. As before, the Compile method converts the expression tree that defines the sort key into code that can be executed by using the CLR. If the user specifies that the query should return a limited number of rows, the Take LINQ extension method is applied to the IEnumerable collection with the limit that the user specifies. Finally, the IEnumerable collection is returned to the caller. Note that this method does not run the LINQ query. This action occurs in the DisplayResultsButton_Click method, when the code calls the Count method of the IEnumerable collection.

Task 8: Test the solution.


Run the application. In the Stress Data Analyzer window, click Display to display all results with no query criteria, sort key, or limit to the number of items that are returned. Note the time that it takes to execute the query.

Note: This test is different from the test that you performed at the end of the first exercise. In the original application, the LINQ query used a lambda expression that contained criteria for all properties, whereas this test does not use any criteria. Therefore, the operation should be faster. Select the Test Date and Temperature check boxes, modify the search criteria to the values in the following table, and then click Display again. Criteria Test Date Temperature Value From 02/01/2009 To 02/28/2009 From 250 to 450

When the query is complete, examine the contents of the box in the lower part of the window. The search should return 1,676 values, as in the test in Exercise 1. However, the time it takes to execute the query should again be less than the time that you recorded in Exercise 1. Clear the Test Date and Temperature check boxes, and then select the Limit? check box. Set the limit value to 2,000, and then click Display. Note that when the number of rows is reduced, the time it takes to execute the query is substantially reduced. In the Order By section, select Temperature, and then click Display again. Note that the expression takes substantially longer to execute when a sort key is included in the expression. Close the Stress Data Analyzer window, and then return to Visual Studio. Close Visual Studio.

Using LINQ to Query Data

14-53

Lab Review

Review Questions
1. 2. In Exercise 1 of the lab, did the application perform deferred or early evaluation of the LINQ query? In Exercise 2, which shared method did you use to construct an expression tree that represented a complete lambda expression?

14-54

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Review and Takeaways

Review Questions
1. 2. 3. 4. Which query operator would you use to implement filtering functionality on an enumerable collection? Which extension method would you use to remove duplicate values from an enumerable collection? How can you force early evaluation of a LINQ query? When would you use an expression tree in an application?

Best Practices Related to Using LINQ


Supplement or modify the following best practices for your own work situations: Use LINQ queries rather than manually writing your own code to retrieve data and to help reduce dependencies that your applications have on the structure that data sources use. Use anonymous types to model the data that queries return, instead of creating new types.

Best Practices Related to Using Dynamic LINQ


Supplement or modify the following best practices for your own work situations: Use dynamic LINQ queries where you require flexibility rather than developing several variants of a query, which risks introducing errors and duplicate code. Use the Type and MemberInfo classes to reference types and type members. Use the GetType method and the Get methods (such as GetProperty) minimally to avoid excessive performance issues.

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-1

Module15
Integrating Visual Basic Code with Dynamic Languages and COM Components
Contents:
Lesson 1: Integrating Visual Basic Code with Ruby and Python Lesson 2: Accessing COM Components from Visual Basic Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components 15-24 15-3 15-14

15-2

Programming in Visual Basic with Microsoft Visual Studio 2010

Module Overview

Integration with other technologies is a key feature of the Microsoft.NET Framework. Previous versions of the .NET Framework enabled you to combine components that were developed by using different languages that have compilers that the .NET Framework supports. The .NET Framework 4 now supports integration of components built by using dynamic languages. This enables you to re-use items built by using a wide range of scripting languages that are not easily accessible from Microsoft Visual Basic code. In addition, previous versions of the .NET Framework have always enabled you to integrate Component Object Model (COM) services and components into your managed applications. The integration did however, require a good understanding of the differences between the way in which the common language runtime (CLR) and the COM environment operated. The new features of Visual Basic 2010 have simplified the way in which you can invoke COM components, so it is easier for you to re-use these items in a Visual Basic application. This module describes how to integrate code written by using a dynamic language such as Ruby and Python, or technologies such as COM, into a Visual Basic application.

Objectives
After completing this module, you will be able to: Integrate Ruby and Python code into a Visual Basic application. Invoke COM components and services from a Visual Basic application.

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-3

Lesson 1

Integrating Visual Basic Code with Ruby and Python

The .NET Framework enables you to build solutions that can combine components and services together that have been developed by using different languages and technologies. If you build Visual Basic applications, this feature enables you to re-use existing codeyou do not have to rewrite this code as Visual Basic components and services. Previous versions of the .NET Framework provided support, through the CLR, for simple integration between Visual Basic and the other managed languages that the .NET Framework supports. For example, you can write a class in Microsoft Visual Basic, compile it into an assembly, and then reference this assembly and use the class in a Visual Basic application. The Visual Basic application is not aware of the language used to build the class. The compilers for each of the managed languages (Visual Basic, Visual C#, Microsoft Visual C++, Microsoft Visual F#, and so on) all convert code written by using these languages into common intermediate language (CIL). When you run an application, the .NET Framework runtime converts the CIL code into machine instructions, and then runs them. However, not all modern computer languages are compiled. There are a large number of scripting languages currently in use that are interpreted at runtime. In previous versions of the .NET Framework, it was not easy to integrate code, written by using these languages, into a Visual Basic application. The .NET Framework 4 has resolved this issue with the dynamic language runtime (DLR).

Lesson Objectives:
After completing this lesson, you will be able to: Explain the purpose of the DLR. Use the Object type to specify that the DLR should perform late binding on an object. Instantiate a dynamic object. Invoke methods and access properties of a dynamic object, and pass a dynamic object as a method parameter.

15-4

Programming in Visual Basic with Microsoft Visual Studio 2010

What Is the Dynamic Language Runtime?

Strongly Typing
Visual Basic is a strongly typed language, when Option Strict is turned on. When you create a variable, you specify the type of that variable, and you can only invoke methods and access members that this type defines. If you try to call a method that the type does not implement, your code will not compile. This is good because it catches a large number of possible errors early, before you even run your code. However, strong typing becomes a problem if you want to incorporate objects that languages such as Ruby and Python define, and which are interpreted rather than compiled into your Visual Basic code. It is very difficult, if not impossible, for the Visual Basic compiler to verify that any members that you access in your Visual Basic code exist in these objects. In addition, if you call a method on a Ruby or Python object, the Visual Basic compiler cannot check that you have passed the correct number of parameters and that each parameter has the appropriate type.

Type Conversion
The types that Visual Basic and the .NET Framework define mostly have a different internal representation from those that Ruby and Python use. For example, if you call a Ruby method that returns an integer, this integer must be converted from the representation that Ruby uses to the one that Visual Basic expects. A similar problem arises if you pass an integer as a parameter from a Visual Basic application into a Ruby method; the integer must be converted from the Visual Basic representation to that of Ruby. The process of converting data between formats is known as marshaling, and it is a problem familiar to any developer who has built applications that invoke COM components. The solution is to use an intermediary layer. In the .NET Framework 4, this intermediary layer is called the DLR. In addition to marshaling data between languages, the DLR also provides many of the services that the compiler provides with a strongly typed language. For example, when you invoke a method on a Ruby or Python object, the DLR checks at runtime that this method call is valid.

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-5

The DLR is not tied to a specific set of languages; it implements an architecture that is based on language binders. A language binder is a component that slots into the DLR and understands how to invoke methods in a specified language and how to marshal and unmarshal data between the different formats that the language and the .NET Framework expect. The binder also performs a certain amount of checking, such as verifying that an object exposes a method that is being invoked, and the parameters and return types are valid. The .NET Framework 4 provides binders for IronPython, IronRuby, COM (which you can use to access COM components, such as those in the 2010Microsoft Office system), Microsoft JScript, and the .NET Framework. Question: What is the purpose of the DLR?

Additional Reading
For more information about the DLR, see the Dynamic Language Runtime Overview page at http://go.microsoft.com/fwlink/?LinkId=192986

15-6

Programming in Visual Basic with Microsoft Visual Studio 2010

Using Dynamic Types

The DLR performs its work at runtime. This means that any type checking for objects referenced through the DLR is deferred until your application runs.

Note: The concept of dynamic types and objects, and deferred type checking is well known to most Visual Basic developers, under a different name, namely late binding. How do you indicate in a Visual Basic application that type checking for an object should be deferred in this way? The answer lies in using the System.Object type, and ensuring that Option Strict is turned off. You use the Object keyword in exactly the same way that you use any other type. For example, the following code example creates a variable called dynamicObject by using the Object type.
Dim dynamicObject As Object

Visual Basic binds to objects from the DLR and dynamic languages by using the IDynamicMetaObjectProvider interface. When a late-bound call is made to an object that implements the IDynamicMetaObjectProvider interface, Visual Basic binds to the dynamic object by using that interface. However, when a late-bound call is made to an object that does not implement the IDynamicMetaObjectProvider interface, Visual Basic binds to the object by using the standard latebinding capabilities of the Visual Basic runtime. At runtime, the DLR uses the appropriate binder to validate your code, instantiate objects, invoke methods, and marshal and unmarshal data. If you attempt to call an invalid method or reference a nonexistent field in a dynamic object, you will not know about it until runtime, when it will throw a RuntimeBinderException exception.

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-7

The same consideration applies when you use a variable that is declared as Object in an expression. The following code example will compile, but may not run if the DLR detects that the object that is referenced does not support the * operator.
Dim dynamicObject As Object ... dynamicObject *= 2

Additional Reading
For more information about the dynamic objects, see the Working with Dynamic Objects (Visual Basic) page at http://go.microsoft.com/fwlink/?LinkID=211784&clcid=0x409

15-8

Programming in Visual Basic with Microsoft Visual Studio 2010

Instantiating a Dynamic Object

The appropriate language runtime creates and manages dynamic objects. The DLR acts as a bridge between the CLR that Visual Basic applications use and the runtime that hosts the dynamic types that you use. Objects that are implemented by using a dynamic language run in unmanaged space and are controlled by the runtime that is specific to the language. For example, the Python runtime is responsible for resolving references to Python objects in a Python script and instantiating them. Consequently, in most cases, you must provide a mechanism that a Visual Basic application can use to instantiate a dynamic object. A common technique is to provide a factory method that hides the details of object creation from the calling environment. The following code example shows a Python script that defines a simple Python class called Customer. In common with many dynamic scripting languages, Python supports global functions, which are functions that are not members of a specific class. The following script includes a global function called GetNewCustomer that creates a new instance of the Customer class and returns it. The GetNewCustomer function takes three parameters that are passed to the constructor (the __init__ function) in the Customer class. The __init__ function uses these parameters to populate three fields called custID, custName, and custTelephone. The Customer class also defines a method called __str__ that returns a string representation of the object

Note: The __str__ method is similar to the ToString method in the .NET Framework and is called whenever a Python method attempts to print or display an object as a string.
class Customer: def __init__(self, id, name, telephone): self.custID = id self.custName = name self.custTelephone = telephone

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-9

def __str__(self): return str.format("ID: {0}\tName: {1}\tTelephone: {2}", self.custID, self.custName, self.custTelephone) def GetNewCustomer(id, name, telephone): return Customer(id, name, telephone)

A Visual Basic application can call the GetNewCustomer function through the DLR and specify values for the customer ID, name, and telephone number. The DLR will marshal these arguments to the Python runtime and call the GetNewCustomer method. The Python runtime executes the GetNewCustomer method and creates the Customer object. When the GetNewCustomer function returns the reference to the Customer object, the DLR unmarshals this reference back to the Visual Basic application.

Accessing the Runtime for a Dynamic Language


To invoke the GetNewCustomer method (or any other factory method) through the DLR, you must provide the DLR with information about the dynamic language that is invoked and the runtime binder to use. The techniques for achieving this depend on the implementation of the dynamic language and the binder. If you use IronPython (an implementation of Python that is integrated into the .NET Framework, and is available on the CodePlex website), you can use the types in the IronPython.dll assembly to create an instance of the IronPython runtime by using the static CreateEngine method of the Python class. The reference to the IronPython runtime is returned as a ScriptEngine object (the .NET Framework provides the ScriptEngine type to enable access to dynamic scripting languages). The following code example illustrates this.
Dim pythonEngine As ScriptEngine = Python.CreateEngine()

Note: You must add references to the IronPython and Microsoft.Scripting assemblies to use the Python and ScriptEngine types. These assemblies are located in the IronPython installation folder. In addition, make sure that Option Strict is turned off for the code file(s) or the project. You can then use the ExecuteFile method of the ScriptEngine object to load a script that contains Python code. For example, if the Customer class shown earlier is implemented in a Python script called Customer.py, you can use the following code example to load this script.
Dim pythonScript As Object = pythonEngine.ExecuteFile("Customer.py")

The ExecuteFile method returns a ScriptScope object, but this code stores the result in a dynamic object. The principal purpose of the ScriptScope type is to provide a context to create and invoke dynamic objects and act as the entry point into the DLR for managed code. You can use this object to invoke the functions defined in the Python script, as the following code example shows.
Dim customer As Object = pythonScript.GetNewCustomer(99, "Fred", "111")

Note: Remember when you use the Object type, the compiler cannot perform any type checking when you build the application, and instead, defers resolution until runtime. If the pythonScript variable was declared as a ScriptScope object, the application would not build, because the ScriptScope type does not contain a method called GetNewCustomer.

15-10

Programming in Visual Basic with Microsoft Visual Studio 2010

The preceding code example illustrates one way to start the Python runtime. As an alternative, you can use the static CreateRuntime method of the Python class and invoke the UseFile method to specify the name of a Python script to run, as the following code example shows.
Dim pythonScript As Object = Python.CreateRuntime().UseFile("Customer.py")

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-11

Invoking and Using a Dynamic Object

In the code example shown in the previous topic, the value returned by the GetNewCustomer method is a reference to a Python Customer object. Notice that this reference is stored in a variable that is declared as Object. You can invoke methods and access properties of this object through the DLR by using the ordinary Visual Basic "dot" notation. The following code example displays the values in the custID and the custName properties of a Customer object.
Dim customer As Object = pythonScript.GetNewCustomer(99, "Fred", "111") ... Console.WriteLine("ID: {0}" & vbTab & "Name: {1}", customer.custID, customer.custName)

Passing Dynamic Objects as Parameters


In many circumstances, you can also pass a dynamic object as an argument to a Visual Basic method. However, be aware that when your code runs, the DLR may use the dynamically determined type of the argument to ascertain which version of a method to run if the method is overloaded. In the following code example, the TestClass class provides an overloaded version of a method called TestMethod; one version takes an integer parameter and the other takes an Object object. The code shows two calls to the TestMethod method; they both specify an Object object as the argument. However, in the first case, the DLR determines that the argument is really an integer and not a reference to an object that is defined elsewhere, so it follows the Visual Basic overload-resolution rules and invokes the version of the TestMethod method that takes an integer parameter. In the second case, the argument is not an integer, so the version of the TestMethod method that takes a dynamic object as the parameter, runs.

Note: When you reference a dynamic object in this way, the DLR follows a well-defined process to determine the type. It first examines the object to determine whether it is a COM component;

15-12

Programming in Visual Basic with Microsoft Visual Studio 2010

if so, it passes the dynamic reference to the COM object. Failing that, the DLR determines whether the object is a reference to some other dynamic type that is accessed by using a binder (such as a Python or Ruby object). Again, if this is the case, the object is passed as a dynamic reference. If the object is not a COM component or a reference to a dynamic type, the DLR determines that it must be a reference to a .NET Framework type, and it uses a technique called reflection to determine its real type. The DLR then uses the determined type to choose which overload to call.
Class TestClass Public Sub TestMethod(ByVal x As Integer) Console.WriteLine("Parameter is an integer") End Sub Public Sub TestMethod(ByVal x As Object) Console.WriteLine("Parameter is dynamic") End Sub End Class ... Dim testObject As New TestClass() Dim data As Object = 99 testObject.TestMethod(data) ' calls TestMethod(ByVal x As Integer)

Dim data2 As Object = "Hello" testObject.TestMethod(data2) ' calls TestMethod(ByVal x As Object)

It is important to realize that any method that takes a dynamic parameter cannot make any assumptions about the real type of that parameter or the data that it contains.

Limitations of Dynamic Objects


The current implementation of the DLR has some limitations. Specifically, you cannot call an extension method through a dynamic reference, and you cannot pass anonymous methods or lambda expressions as arguments to a method of a dynamic object. Question: Why do you think that you cannot pass a lambda expression as an argument to a method of a dynamic object?

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-13

Demonstration: Calling Python Code from Visual Basic

Demonstration Steps
1. 2. Open the Python file, CustomerDB.py in the D:\Demofiles\Mod15 folder. Walk through the code. Point out the following items in the Python file: 3. 4. 5. 6. 7. 8. 9. The Customer class The CustomerDB class The GetNewCustomer method The GetCustomerDB method

Close the Python file. Start Visual Studio. Open the PythonInteroperability solution in the D:\Demofiles\Mod15 folder. View the code for the project. Walk through the code in the Main procedure. View the references that the solution uses. Build and run the application.

10. Close the console window. 11. Close Visual Studio.

15-14

Programming in Visual Basic with Microsoft Visual Studio 2010

Lesson 2

Accessing COM Components from Visual Basic

COM is a well-established technology, and a large base of useful COM components is now available. Microsoft makes extensive use of COM throughout the Windows operating system. Likewise, many other organizations have made a considerable investment in COM. It may prove costly and timeconsuming for most organizations to re-implement existing functionality based on COM components by using the .NET Framework. COM components run in unmanaged space, but you can integrate these components into your .NET Framework applications by using COM Interop.

Lesson Objectives:
After completing this lesson, you will be able to: Describe the issues that may arise when interoperating with COM from Visual Basic. Add a COM interop assembly to a Visual Basic application. Instantiate a COM object from a Visual Basic application. Pass parameters from Visual Basic into a method exposed by a COM object and handle values returned by a COM method. Explain how to configure an application to enable deployment that does not require a primary interop assembly (PIA).

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-15

Interoperating with COM from a Visual Basic Application

Visual Basic code runs by using the CLR. COM components run in the unmanaged environment by using the facilities that the operating system provides. To incorporate a COM component into a Visual Basic application, you must perform the following tasks: Instantiate the COM component. To do this, you must request that the operating system creates an unmanaged object. Call methods on the COM component. COM components are object-oriented items, and they expose methods that you can call. However, COM uses a different set of data types from Visual Basic, so when you call a method on a COM object, you must convert the arguments that you provide from the format that Visual Basic uses to the format that COM requires. Similarly, when a COM method may return a value, you must be prepared to convert this value from the COM format back into the format that Visual Basic expects. Destroy the COM component when you have finished with it. When you create a managed object by using Visual Basic, the CLR destroys it and reclaims the resources that it uses. COM objects run outside the auspices of the CLR, so you must explicitly destroy them.

Fortunately, the .NET Framework provides tools that can simplify these tasks. You can create, manage, and communicate with a COM component from a Visual Basic application by using a runtime callable wrapper (RCW). The RCW acts as a proxy to the COM object. It hides the differences between the COM and. the NET Framework runtimes. The overall aim of RCW is to make the COM object appear the same as any other ordinary managed object to a Visual Basic application.

How the CLR Interacts with a COM Object


The CLR creates an RCW from an assembly that contains metadata that describes the COM component. This assembly is referred to as an interop assembly. The interop assembly describes the custom COM interfaces and classes that the component implements.

15-16

Programming in Visual Basic with Microsoft Visual Studio 2010

A Visual Basic application uses the interop assembly to determine the types and methods that the COM component exposes. When a Visual Basic application attempts to instantiate an object that a class in the interop assembly defines, the CLR locates and loads the library for the COM component and creates an instance of this component in unmanaged space. The CLR then creates the RCW to communicate with the COM object on behalf of the Visual Basic application. The COM object sends any responses to the Visual Basic application through the RCW (the COM object views the RCW as the client and is unaware that it is simply a proxy for a managed application). The RCW exposes a managed interface to the methods in the COM component. A managed application calls these methods. The RCW converts arguments that a Visual Basic application provides into the format that the COM component expects and then invokes the corresponding method in the COM component. Data that a COM component returns is marshaled through the RCW to convert it back into the format that Visual Basic expects, before it is passed to your application. Question: Why is it necessary to create an RCW to invoke a COM component from a Visual Basic application?

Additional Reading
For more information about marshaling COM data types, see the COM Data Types page at http://go.microsoft.com/fwlink/?LinkID=211785&clcid=0x409

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-17

Creating a COM Interop Assembly

To use an RCW to call a COM object, you must first create an interop assembly that contains the metadata that describes the COM classes and interface in the component. The .NET Framework and Visual Studio 2010 provide several tools that you can use to create an interop assembly.

Note: To create an interop assembly by using the tools that Visual Studio 2010 or the .NET Framework provide, you must use a COM component that provides a type library. A type library is typically either provided as a type library file that has a .tlb suffix or is embedded in the dynamic-link library (DLL) that implements the COM component. If the COM component does not provide a type library, you must either define the interop assembly manually, or use late binding and reflection to discover the methods and interfaces that the COM component exposes at run time.

Creating an Interop Assembly by Using Visual Studio 2010


You can create an interop assembly in Visual Studio 2010 by using the Add Reference command on the Project menu. In the Add Reference dialog box, click the COM tab, and then select the appropriate COM component. The COM tab lists the COM components and type libraries that are recorded in the Windows registry of the local computer. You can select any of these items, or you can click the Browse tab and search for the DLL that implements the COM component on the hard disk. When you select a component, Visual Studio discovers the interfaces, methods, and classes that the component implements, and then creates the interop assembly. The interop assembly is added as a reference to your project.

Creating an Interop Assembly at a Command Prompt


As an alternative to using Visual Studio, the .NET Framework software development kit (SDK) provides the Tlbimp utility, which you can use at a command prompt to create an interop assembly. The Tlbimp utility

15-18

Programming in Visual Basic with Microsoft Visual Studio 2010

imports type information that describes the COM component, either from a type library file that is provided with the COM component or from the DLL that implements the COM component.

Note: If the COM component does not provide a type library, but provides a description in a COM interface definition language (IDL) file, you can use the Microsoft IDL compiler (midl.exe) that is provided with the platform SDK to generate a type library. To create an interop assembly at a command prompt, open a Visual Studio 2010 Command Prompt window, move to the folder that contains the DLL or type library file for the COM component, and then run the Tlbimp utility by providing the name of the DLL or type library as an argument. By default, the Tlbimp utility creates an assembly that is based on the name of the COM component (which may not be the same as the name of the DLL or type library file). However, you can modify this behavior by using the /out flag and specifying a different file name. You can also provide flags that indicate other options, such as the /keyfile flag. This flag specifies the name of a key file for signing the assembly, if you want to deploy it to the global assembly cache (GAC), as the following code example shows.
Tlbimp MyTypeLib.tlb /out:InteropAssembly.dll /keyfile:Keys.snk

After you have created the interop assembly, you can manually add it as a reference to your Visual Basic project.

Creating a Primary Interop Assembly


Vendors who produce COM components may also provide the necessary interop assemblies that are required to use these components from a Visual Basic application. For example, Microsoft supplies interop assemblies that enable you to invoke Microsoft Office components from Visual Basic. However, it is important that you verify that these interop assemblies are correct and have been obtained from a reputable source. Manufacturers should sign their assemblies to enable them to be identified and to prevent them from being tampered with. A signed interop assembly for a set of COM components is referred to as a PIA. You specify that an interop assembly is a PIA by using the /primary switch to the Tlbimp utility, as the following code example shows.
Tlbimp MyTypeLib.tlb /primary /out:MyPIA.dll /keyfile:Keys.snk

Note: A COM type library that is imported as an assembly and signed by someone other than the publisher of the original type library cannot be a PIA. Only the publisher of a type library can produce a true PIA, which becomes the unit of official type definitions for interoperating with the underlying COM types. Question: Why are PIAs important?

Additional Reading
For more information about using the Tlbimp utility, see the Tlbimp.exe (Type Library Importer) page at http://go.microsoft.com/fwlink/?LinkId=192988

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-19

Instantiating a COM Component by Using a Runtime Callable Wrapper

You use the interop assembly to create an RCW that instantiates and controls the lifetime of a COM object. You can invoke the methods of a COM object through an RCW in the same way that you invoke methods in an ordinary managed object. The RCW hides the fact that the object is a COM object and marshals method parameters and return values.

Creating a COM Object


An interop assembly contains a managed version of each COM interface that is defined in the type library for the COM component. A COM type library also defines one or more classes (called COM coclasses) that an application can instantiate and that implement one or more of the COM interfaces. The interop assembly defines a managed wrapper around each coclass and gives the wrapper the same name as the coclass, but adds the Class suffix. The interop assembly also contains a managed version of each COM interface. You create a COM object by creating an instance of the managed coclass, but you reference the class through the appropriate managed interface. In a typical COM component, a single coclass may act as the entry point into the object model that the component implements. In this situation, the coclass typically provides factory methods to create the other types of object that implement other COM interfaces that the type library provides. You use these factory methods through the RCW to instantiate managed wrappers around these types. For example, the COM library that Microsoft provides for Microsoft Office Excel defines a coclass called ApplicationClass that acts as the entry point into the Office Excel object model. It also defines a managed wrapper around the corresponding COM interface that this coclass implements, called Application. You instantiate a COM type through the RCW by using the managed wrapper that is associated with the COM interface. For example, to start Office Excel, you create a new instance of the Application object.

Note: To instantiate a COM object, always use the managed wrapper for the interface that a coclass implements, rather than the managed version of the coclass itself. For example, when you

15-20

Programming in Visual Basic with Microsoft Visual Studio 2010

use Office Excel, create an instance of the Application type, rather than the ApplicationClass type. The COM library for Office Excel also defines coclasses and interfaces for other objects that the Office Excel object model implements, and it provides factory methods for instantiating these objects. In the COM Office Excel object model, the Application object contains a collection of Workbook objects, and a Workbook object can contain Worksheet and Chart objects. You can create a new Workbook object by calling the Add method of the Workbook property that the Application COM object exposes through the RCW, and you can create a new Worksheet object and a new Chart object in a workbook by calling the Add method of the Worksheets or Charts properties respectively. The following code example shows some instances.
' Namespace generated by the Excel PIA for the Excel COM library Imports Excel = Microsoft.Office.Interop.Excel ... Dim excelApp As New Excel.Application() Dim excelWB As Excel.Workbook = excelApp.Workbooks.Add() Dim excelWS As Excel.Worksheet = excelWB.Worksheets.Add() Dim excelChart As Excel.Chart = excelWB.Charts.Add() ...

This code shows the COM object model that Office Excel exposes. Other applications, components, and services will expose their own models that may be similar or quite different from the model that Office Excel uses. The key point to note is that as far as your code is concerned, all the statements that create COM objects look like ordinary Visual Basic method calls. The code that interacts with the COM subsystem is hidden by the RCW. Question: What is the difference between instantiating a Visual Basic object and a COM object in a Visual Basic application?

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-21

Calling Methods on a COM Object

After you create a COM object, you can interact with it through the RCW to call its methods and set and query its properties. The methods and properties that are available depend on the COM object. You use the same syntax as you use to access a Visual Basic object. The RCW marshals data between your Visual Basic code and the COM object.

Handling Optional Parameters


Optional parameters can be handled in different ways, when performing COM interop; you can pass the value Type.Missing as one or more arguments, or simply omit the parameter. Many COM components are written by using languages that support optional parameters. When you pass the value Type.Missing for or omit a parameter to a COM method, the RCW omits the argument, and the default value for the method parameter is used instead. For example, the ChartWizard method of the Chart type in the Office Excel object model takes a significant number of parameters, all of which have default values. If you want to explicitly use any of these default values, you can use code that resembles the following code example.
Dim excelChart As Excel.Chart = excelWB.Charts.Add() excelChart.ChartWizard(dataRange, Excel.XlChartType.xlLine, Type.Missing, Excel.XlRowCol.xlColumns, 1, 1, Type.Missing, "Speed versus Distance", "Speed", "Distance", Type.Missing)

Visual Basic 2010 supports named arguments and optional parameters, and RCW defines all optional parameters to COM methods as optional Visual Basic parameters with a default value of Type.Missing. When you call a method of a COM component, you can specify parameters by name and omit any arguments that you want to leave at their default values. The syntax is exactly the same as calling a Visual Basic method that implements default parameter values.

15-22

Programming in Visual Basic with Microsoft Visual Studio 2010

RCW implements default parameter values for the ChartWizard method (it sets them to Type.Missing). Any parameters that you omit are propagated as missing values to the Office Excel COM library and are given their default values, as the following code example shows.
Dim excelChart As Excel.Chart = excelWB.Charts.Add() excelChart.ChartWizard(Title:= "Speed versus Distance", Source:=dataRange, Gallery:= Excel.XlChartType.xlLine, PlotBy:= Excel.XlRowCol.xlColumns, CategoryLabels:= 1, SeriesLabels:= 1, ValueTitle:= "Distance", CategoryTitle := "Speed")

Named parameters have the additional advantage of making your code easier to understand and maintain. Question: If you omit an argument to a COM method, what value does RCW use as the default?

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-23

Deploying Without a Primary Interop Assembly

Some PIAs for large COM libraries and components may define a large number of types and methods. Consequently, the PIAs may also be quite large. In addition, prior to the .NET Framework 4, you had to ensure that each PIA that an application used was deployed correctly on each computer that you installed the application on. Deploying a PIA requires administrator privileges. The .NET Framework 4 now supports intelligent, PIA-less deployment of applications. When you add a PIA to an application at design time, the PIA exposes a property called Embed Interop Types. If you set this property to true, when you build the application, the interop types from that PIA that you use in your application will be compiled into the assembly for your application. Consequently, you do not need to deploy the PIA with your application. In addition, the assembly for your application will contain only the types from the PIA that your application uses; any unreferenced types are omitted. This ensures that the size of the application assembly is kept as small as possible. Question: If you specify PIA-less deployment, when you deploy an application that uses a COM component, is it still necessary to deploy the COM component, too?

15-24

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

Objectives:
After completing this lab, you will be able to: Instantiate an object defined by using a dynamic language, and invoke its methods from a Visual Basic application. Instantiate a COM component and invoke its methods from a Visual Basic application.

Introduction
In this lab, you use the DLR to access objects defined in an IronRuby or IronPython script from a Visual Basic application. You will also use COM interop to instantiate, and use a COM component from a Visual Basic application.

Lab Setup
For this lab, you will use the available virtual machine environment. Before you begin the lab, you must: Start the 10550A-GEN-DEV virtual machine, and then log on by using the following credentials: User name: Student Password: Pa$$w0rd

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-25

Lab Scenario

Fabrikam, Inc. makes use of technologies and programming languages other than Visual Basic to drive some of the devices that it develops. In addition, Fabrikam, Inc. incorporates features from software products such as Microsoft Office into some of its applications. You have been asked to integrate some components written by using these technologies into the Visual Basic software that supports the various devices.

15-26

Programming in Visual Basic with Microsoft Visual Studio 2010

Exercise 1: Integrating Code Written by Using a Dynamic Language into a Visual Basic Application
Scenario
Fabrikam, Inc. has a sizable collection of Python and Ruby scripts that contain proven and thoroughly tested code. Although Visual Basic is now the development language of choice for Fabrikam, Inc., it will be expensive and time-consuming to reimplement and fully test these scripts by using Visual Basic. Instead, you have been asked to integrate the functionality that these scripts provide directly into your Visual Basic applications. You will use the DLR to invoke an IronRuby script and an IronPython script (scripts for both languages are provided), create objects by using the types that are defined in these scripts, and call methods that these objects expose. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. 6. Examine the Python and Ruby code. Open the starter project. Create a Python object and call Python methods. Test the Python code. Create a Ruby object and call Ruby methods. Test the Ruby code.

Task 1: Examine the Python and Ruby code.


Open Microsoft Visual Studio 2010. Using Notepad, open the Shuffler.py file in the D:\Labfiles\Lab15\Python folder. In Notepad, examine the Python code.

The Shuffler.py file contains a Python class named Shuffler that provides a method named Shuffle. The Shuffle method takes a parameter named data that contains a collection of items. The Shuffle method implements the Fisher-Yates-Durstenfeld algorithm to randomly shuffle the items in the data collection. The Python class also exposes a function named CreateShuffler that creates a new instance of the Shuffler class. You will use this method from Microsoft Visual Basic to create a Shuffler object. Close Notepad. Using Notepad, open the Trapezoid.rb file in the D:\Labfiles\Lab15\Ruby folder. In Notepad, examine the Ruby code.

The Trapezoid.rb file contains a Ruby class named Trapezoid that models simple trapezoids. The constructor expects the angle of the lower-left vertex, the length of the base, the length of the top, and the height of the trapezoid. The lengths of the remaining sides and angles are calculated.

Note: The Trapezoid class models a subset of possible trapezoids. The length of the base must be greater than the length of the top, and the specified vertex must be an acute angle. The lengths of the sides, the angles of each vertex, and the height are exposed as properties. The to s method returns a string representation of the trapezoid.

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-27

Note: The to_s method is the Ruby equivalent of the ToString method in Microsoft .NET Framework. The Ruby binder in the dynamic language runtime (DLR) automatically translates a call to the ToString method on a Ruby object to a call to the to_s method. The area method calculates the area of the trapezoid. The Ruby file also provides a function named CreateTrapezoid that creates a new instance of the Trapezoid class. Close Notepad.

Task 2: Open the starter project.


Open the DynamicLanguageInterop solution in the D:\Labfiles\Lab15\Starter folder. To the TestDynamicLanguageInterop project, add a reference to the DynamicLanguageInterop project.

Task 3: Create a Python object and call Python methods.


Examine the InteropTestWindow.xaml file. This window contains two tabs, labeled Python Test and Ruby Test. The Python Test tab enables you to type values into the Data box and specify whether this is text or numeric data. When you click Shuffle, the data will be packaged into an array and passed to the Shuffle method of a Python Shuffler object. The shuffled data will be displayed in the Shuffled Data box. The functionality to create the Python object and call the Shuffle method has not yet been implemented; you will do this in this task. To the DynamicLanguageInterop project, add references to the assemblies listed in the following table. The DLR uses these assemblies to provide access to the IronPython runtime. Assembly IronPython Path C:\Program Files (x86)\ IronPython 2.6 for .NET 4.0\ IronPython.dll

IronPython.Modules C:\Program Files (x86)\ IronPython 2.6 for .NET 4.0\ IronPython.Modules.dll Microsoft.Dynamic C:\Program Files (x86)\ IronPython 2.6 for .NET 4.0\ Microsoft.Dynamic.dll C:\Program Files (x86)\ IronPython 2.6 for .NET 4.0\ Microsoft.Scripting.dll

Microsoft.Scripting

Review the Task List. In the Task List, locate the TODO: Add Namespaces containing IronPython and IronRuby runtime support and interop types task, and then double-click this task. This task is located near the top of the InteropTestWindow.xaml.vb file. This is the code behind the InteropTestWindow window.

15-28

Programming in Visual Basic with Microsoft Visual Studio 2010

After the comment, add Imports statements to bring the IronPython.Hosting and Microsoft.Scripting.Hosting namespaces into scope. In the InteropTestWindow class, examine the string constants near the start of the class. In particular, note the pythonLibPath and pythonCode strings. The pythonLibPath constant specifies the folder where the Python libraries are installed. The Shuffler class makes use of a Python library named random that is located in this folder. The pythonCode constant specifies the name and location of the Python script that contains the Shuffler class.

In the Task List, locate the TODO: Create an instance of the Python runtime, and add a reference to the folder holding the "random" module task, and then double-click this task. This task is located in the ShuffleData method. The ShuffleButton_Click method calls the ShuffleData method when the user clicks the Shuffle Data button. The ShuffleButton_Click method gathers the user input from the form and parses it into an array of objects. It then passes this array to the ShuffleData method. The purpose of the ShuffleData method is to create a Python Shuffler Python object and then call the Shuffle method by using the array as a parameter. When the ShuffleData method finishes, the ShuffleButton_Click method displays the shuffled data in the Windows Presentation Foundation (WPF) window.

After the TODO comment, add code that performs the following tasks: a. b. Create a ScriptEngine object named pythonEngine by using the shared CreateEngine method of the Python class. Obtain a reference to the search paths that the Python runtime uses; call the GetSearchPaths method of the pythonEngine object and store the result in an ICollection(Of String) collection object named paths. Add the path that is specified in the pythonLibPath string to the paths collection. Set the search paths that the pythonEngine object uses to the paths collection; use the SetSearchPaths method.

c. d.

After the comment TODO: Run the script and create an instance of the Shuffler class by using the CreateShuffler method in the script, add code that performs the following tasks: a. Create an object named pythonScript. Initialize this object with the value that is returned by calling the ExecuteFile method of the pythonEngine object. Specify the pythonCode constant as the parameter to this method. This statement causes the Python runtime to load the Shuffler.py script. The pythonScript object contains a reference to this script that you can use to invoke functions and access classes that are defined in this script. b. Create another object named pythonShuffler. Call the CreateShuffler method of the pythonScript object and store the result in the pythonShuffler object. This statement invokes the CreateShuffler function in the Python script. This function creates an instance of the Shuffler class and returns it. The pythonShuffler object then holds a reference to this object.

Note: The pythonScript variable is of type Object, so Microsoft IntelliSense does not display the CreateShuffler method.

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-29

After the comment TODO: Shuffle the data, add code that calls the Shuffle method of the pythonShuffler object. Pass the data array as the parameter to the Shuffle method. This statement runs the Shuffle method in the Python object. The DLR marshals the data array into a Python collection and then invokes the Shuffle method. When the method completes, the DLR unmarshals the shuffled collection back into the data array.

Build the application and correct any errors.

Task 4: Test the Python code.


Run the application. In the Dynamic Language Interop Tests window, on the Python Test tab, in the Data box, type some random words that are separated by spaces. Click the Text option button, and then click Shuffle. Verify that the shuffled version of the data appears in the Shuffled Data box. Click Shuffle again. The data should be shuffled again and appear in a different sequence. Replace the text in the Data box with integer values, click Integer, and then click Shuffle. Verify that the numeric data is shuffled. Close the Dynamic Language Interop Tests window, and then return to Visual Studio.

Task 5: Create a Ruby object and call Ruby methods.


Examine the Ruby Test tab in the InteropTestWindow.xaml file. The Ruby Test tab enables you to specify the dimensions of a trapezoid (the angle of the first vertex, the length of the base, the length of the top, and the height) by using a series of slider controls. When you click the Visualize button, the application will create an instance of the Ruby Trapezoid class and display a graphical representation in the canvas in the lower part of the window. The dimensions and area of the trapezoid will be displayed in the text block that is to the right. The functionality to create the Ruby object and calculate its area and dimensions has not yet been implemented; you will do this in this task. To the DynamicLanguageInterop project, add references to the assemblies listed in the following table. The DLR uses these assemblies to provide access to the IronRuby runtime. Assembly IronRuby IronRuby.Libraries Review the Task List. In the Task List, locate the TODO: Add Namespaces containing IronPython and IronRuby runtime support and interop types task, and then double-click this task. Add an Imports statement to bring the IronRuby namespace into scope. In the InteropTestWindow class, examine the rubyCode string constant near the start of the class. The rubyCode constant specifies the name and location of the Ruby script that contains the Trapezoid class. Path C:\Program Files (x86)\IronRuby 1.0v4\ bin\IronRuby.dll C:\Program Files (x86)\IronRuby 1.0v4\ bin\IronRuby.Libraries.dll

15-30

Programming in Visual Basic with Microsoft Visual Studio 2010

In the Task List, locate the TODO: Retrieve the values specified by the user. These values are used to create the trapezoid task, and then double-click this task. This task is located in the VisualizeButton_Click method. This method is named when the user clicks the Visualize button, after the user has specified the data for the trapezoid. After the TODO comment, add code that performs the following tasks: a. Create an integer variable named vertexAInDegrees. Initialize this variable with the value of the vertexA slider control.

Hint: Use the Value property of a slider control to read the value. This value is returned as a Double value, so use a cast to convert it to an integer. This cast is safe because the slider controls are configured to return integer values in a small range, so no data will be lost. b. c. d. Create an integer variable named lengthSideAB. Initialize this variable with the value of the sideAB slider control. Create an integer variable named lengthSideCD. Initialize this variable with the value of the sideCD slider control. Create an integer variable named heightOfTrapezoid. Initialize this variable with the value of the height slider control.

After the comment TODO: Call the CreateTrapezoid method and build a trapezoid object, add a statement that creates an Object variable named trapezoid and initializes it with the value that the CreateTrapezoid method returns. Pass the variables vertexAInDegrees, lengthSideAB, lengthSideCD, and heightOfTrapezoid as arguments to the CreateTrapezoid method. You will implement the CreateTrapezoid method in a later step. This method will create an instance of the Ruby Trapezoid class by using the specified data and return it.

After the comment TODO: Display the lengths of each side, the internal angles, and the area of the trapezoid, add a statement that calls the DisplayStatistics method. Pass the trapezoid object and the TrapezoidStatisticsTextBlock control as parameters to this method. You will implement the DisplayStatistics method in a later step. This method will call the to_s and area methods of the Ruby Trapezoid class and display the results in the trapezoidStatistics text block on the right of the Ruby Test tab in the WPF window.

After the comment TODO: Display a graphical representation of the trapezoid, add a statement that calls the RenderTrapezoid method. Pass the trapezoid object and the trapezoidCanvas canvas control as parameters to this method. The RenderTrapezoid method is already complete. This method queries the properties of the Ruby Trapezoid object and uses them to draw a representation of the trapezoid on the canvas in the lower part of the window.

In the Task List, locate the TODO: Create an instance of the Ruby runtime task, and then doubleclick this task. This task is located in the CreateTrapezoid method. At the start of this method, remove the statement that throws the NotImplementedException exception. After the comment, add a statement that creates a ScriptRuntime object named rubyRuntime. Initialize the rubyRuntime variable with the value that the shared CreateRuntime method of the Ruby class returns. After the comment TODO: Run the Ruby script that defines the Trapezoid class, add a statement that creates a variable of type Object named rubyScript. Initialize the rubyScript variable with the

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-31

value that the UseFile method of the rubyRuntime object returns. Pass the rubyCode constant as the parameter to the UseFile method. This statement causes the Ruby runtime to load the Trapezoid.rb script. The rubyScript object contains a reference to this script that you can use to invoke functions and access classes that are defined in this script. After the comment TODO: Call the CreateTrapezoid method in the Ruby script to create a trapezoid object, add a statement that creates a variable of type Object named rubyTrapezoid. Initialize the rubyTrapezoid variable with the value that the CreateTrapezoid method of the rubyScript object returns. Pass the vertexAInDegrees, lengthSideAB, lengthSideCD, and heightOfTrapezoid variables as parameters to the CreateTrapezoid method. This statement invokes the CreateTrapezoid function in the Ruby script. The DLR marshals the arguments that are specified and passes them as parameters to the CreateTrapezoid function. This function creates an instance of the Trapezoid class and returns it. The rubyTrapezoid object then holds a reference to this object.

Note: The rubyScript variable is of type Object, so IntelliSense does not display the CreateTrapezoid method. After the comment TODO: Return the trapezoid object, add a statement that returns the value in the rubyTrapezoid variable. In the Task List, locate the TODO: Use a StringBuilder object to construct a string holding the details of the trapezoid task, and then double-click this task. This task is located in the DisplayStatistics method. After the comment, add a statement that creates a new StringBuilder object named builder. After the comment TODO: Call the to_s method of the trapezoid object to return the details of the trapezoid as a string, add a statement that calls the ToString method of the trapezoid variable and appends the result to the end of the builder object. The DLR automatically converts the ToString method call into a call to the to_s method in the Ruby object. The to_s method constructs a Ruby string, which is unmarshaled into a .NET Framework string. After the comment TODO: Calculate the area of the trapezoid object by using the area method of the trapezoid class, add code that calls the area method of the trapezoid variable, converts the result into a string, and appends this string to the end of the builder object. After the comment TODO: Display the details of the trapezoid in the TextBlock control, add a statement that sets the Text property of the trapezoidStatistics control to the string that is constructed by the builder object. Build the application and correct any errors.

Task 6: Test the Ruby code.


Run the application. In the Dynamic Language Interop Tests window, click the Ruby Test tab. Set the Vertex A slider to 75, set the Length of Base slider to 200, set the Length of Top slider to 100, set the Height slider to 150, and then click Visualize.

15-32

Programming in Visual Basic with Microsoft Visual Studio 2010

Verify that a representation of the trapezoid is displayed in the canvas in the lower half of the window and the statistics for the trapezoid appear in the text block that is to the right. The area of the trapezoid should be 22,500. Experiment with different values for the slider controls, and then click Visualize. If you specify values that are outside the range for the set of trapezoids that the Trapezoid class can model, a message box should be displayed to indicate the problem. This error message is raised by the constructor in the Trapezoid class. The DLR catches the error and converts it into a .NET Framework Exception object. The VisualizeButton_Click method caches this exception and displays the error in a message box. Close the Dynamic Language Interop Tests window, and then return to Visual Studio.

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-33

Exercise 2: Using a COM Component from a Visual Basic Application


Scenario
One of the WPF applications that analyze data that is retrieved from measuring devices needs to use this data to plot a range of graphs. Rather than write your own routines to plot and draw graphs, you will use the wide range of graphing capabilities already available through Office Excel. You will use COM interop from a Visual Basic application to invoke Office Excel and generate a graph with data that the Visual Basic application provides. The main tasks for this exercise are as follows: 1. 2. 3. 4. 5. 6. 7. Examine the data files. Open the starter project and examine the StressData type. Examine the GraphWindow test harness. Copy data to an Office Excel worksheet. Generate an Office Excel graph. Complete the test harness. Test the application.

Task 1: Examine the data files.


Using Windows Explorer, browse to the D:\Labfiles\Lab15 folder, and then verify that this folder contains the following three text files. 298K.txt 318K.txt 338K.txt

Using Notepad, open the 298K.txt file. This file contains results from the deflection tests for steel girders that were subjected to various pressures at a temperature of 298 Kelvin. The number on a line by itself at the top of the file is the temperature at which the tests were performed (298). The remaining lines contain pairs of numbers; the numbers in each pair are separated by a comma. These numbers are the pressure applied, which is measured in kiloNewtons (kN), and the deflection of the girder, which is measured in millimeters.

Close Notepad. Using Notepad, open the 318K.txt file. This file is in the same format as the 298K.txt file. It contains the results of deflection tests that were performed at a temperature of 318 Kelvin. Notice that the final few lines do not contain any deflection data because the test was halted at a force of 1,000 kN.

Close Notepad. Using Notepad, open the 338K.txt file. This file is similar to the other two. It contains the results of deflection tests that were performed at a temperature of 338 Kelvin. The test was halted at a force of 800 kN.

Close Notepad.

15-34

Programming in Visual Basic with Microsoft Visual Studio 2010

Task 2: Open the starter project and examine the StressData type.
Using Visual Studio, open the GenerateGraph solution in the D:\Labfiles\Lab15\Starter folder. Open the StressData.vb file. The StressData type acts as a container for the stress data for a given temperature. It contains the following public properties. Temperature. This is a short value that records the temperature of the test. Data. This is a Dictionary collection that holds the data. The stress value is used as the key into the dictionary, and the item data is the deflection. The StressData class also overrides the ToString method, which returns a formatted string that lists the stress test data that is stored in the object.

Task 3: Examine the GraphWindow test harness.


Open the GraphWindow.xaml file. This window provides a simple test harness for reading the data from the data files and invoking Microsoft Office Excel to generate a graph by using this data. When users click Get Data, they are prompted for the data file to load. The file is read into a new StressData object, and the contents of the file are displayed in the TreeView control that occupies the main part of the window. A user can click Get Data multiple times and load multiple files; they will all be read in and displayed. The StressData objects are stored in a List collection that is held in a private field in the GraphWindow class and is named graphData. This code has already been written for you. When a user clicks Graph, the data in the graphData collection will be used to generate an Office Excel graph. The information in each StressData object will be transferred to an Office Excel worksheet, and a line graph will then be generated to show the stress data for each temperature. A user can quickly examine this graph and spot any trends in the failure of girders. Open the GraphWindow.xaml.vb code file. Locate the populateFromFile method. This method uses a StreamReader object to read and parse the stress data from a file that is specified as a parameter, and it populates a StressData object that is also specified as a parameter. This method is complete. Locate the displayData method. This method takes a populated StressData object and displays the items in this object in the TreeView control in the window. This method is also complete. Locate the GetDataButton_Click method. This method runs when the user clicks the Get Data button. It uses an OpenFileDialog object to prompt the user for the name of a data file and then passes the file name together with a new StressData object to the populateFromFile method. It then adds the populated StressData object to the graphData collection before it calls the displayData method to add the data to the TreeView control in the window. This method is complete. Locate the GenerateGraphButton_Click method.

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-35

This method runs when the user clicks the Generate button. It prompts the user for the name of an Office Excel workbook to create. It will then create this new workbook and copy the data in the graphData collection into a worksheet in this workbook before it generates a graph. This method is not complete. You will add the missing functionality and complete the transferDataToExcelSheet and generateExcelChart helper methods that this code will use.

Task 4: Copy data to an Office Excel worksheet.


Add a reference to the Microsoft Excel 14.0 Object Library to the GenerateGraph project. This is the COM object library that implements the Office Excel object model. Review the Task List. In the Task List, locate the TODO: Add the Microsoft.Office.Interop.Excel namespace task, and then double-click this task. This task is located near the top of the GraphWindow.xaml.vb file. Bring the Microsoft.Office.Interop.Excel namespace into scope, and give it an alias of Excel. This alias helps you to distinguish items in this namespace and avoid name clashes without having to specify the full namespace in ambiguous object references. Locate the transferDataToExcelSheet method. The GenerateGraphButton_Click method will call this method. It takes three parameters: An Excel.Worksheet object named excelWS. This object is a reference to the Office Excel worksheet that you will copy the data to. An Excel.Range object named dataRange. This is an output parameter. You will use this object to indicate the area of the worksheet that contains the data after it has been copied. A List(Of StressData) object named excelData. This is a collection of StressData objects that contain the data that you will copy to the Office Excel worksheet. This method returns True if it successfully copies the data to the Office Excel worksheet, and False if an exception occurs. In the transferDataToExcelSheet method, after the comment TODO: Copy the data for the applied stresses to the first column in the worksheet, add code that performs the following tasks: a. b. c. Declare an integer variable named rowNum and initialize it to 1. Declare an integer variable named colNum and initialize it to 1. Set the value of the cell at location rowNum, colNum in the excelWS worksheet object to the text "Applied Stress".

Hint: You can use the Cells property to read and write a cell in an Excel worksheet object. This property acts like a two-dimensional array. d. Use a For Each loop to iterate through the keys in the first StressData object in the excelData collection.

Hint: Remember that the StressData object contains a Dictionary property named Data, and the key values in this dictionary are the applied stresses for the test (100, 200, 300, up to 1,500 kN). You can use the Keys property of a Dictionary object to obtain a collection of keys that you can iterate through.

15-36

Programming in Visual Basic with Microsoft Visual Studio 2010

e.

In the body of the For Each loop, increment the rowNum variable, and store the value of each key found in the cell at location rowNum, colNum in the excelWS worksheet object.

Locate the comment TODO: Give each column a header that specifies the temperature. This comment is located in a For Each loop that iterates over each item in the excelData collection. These items are StressData objects, and each StressData object contains the data for the tests for a given temperature. When complete, the code in this For Each loop will copy the data for each StressData object to a new column in the excelWS worksheet object, and each column will have a header that specifies the temperature.

After the comment, add code that performs the following tasks. a. b. c. Increment the colNum variable so that it refers to the next column in the worksheet. Set the rowNum variable to 1. Retrieve the temperature from the deflectionDataStressData object, format it as a string with the letter "K" appended to the end (for Kelvin), and store this string in the cell at location rowNum, colNum in the excelWS worksheet object.

Locate the comment TODO: Only copy the deflection value if it is not null. This comment is located in a nested For Each loop that iterates over each value in a StressData object. Remember that not all stresses have a deflection value. Where this occurs, the data in the StressData object is null. The If statement detects whether the current deflection value is null.

After the comment, in the body of the If statement, add code that performs the following tasks: a. b. Increment the rowNum variable so that it refers to the next row in the worksheet. Copy the value of the deflection variable (that contains the deflection data) into the cell at location rowNum, colNum in the excelWS worksheet object.

Locate the comment TODO: Specify the range of cells in the spreadsheet containing the data in the dataRange variable. This comment is located after all the For Each loops have completed and all the data has been copied to the worksheet.

After the comment, add a statement that populates the dataRange variable with information about the set of cells that have been filled. Hint: You can determine the boundaries of the filled area of an Office Excel worksheet by querying the UsedRange property. This property returns an Excel.Range object.

Build the solution and correct any errors.

Task 5: Generate an Office Excel graph.


Locate the generateExcelChart method. The GenerateGraphButton_Click method will call this method after the data has been copied to the Office Excel worksheet. It takes three parameters: A String object named fileName. When the graph has been created, the method will save the Office Excel workbook to a file by using this file name. An Excel.Workbook object named excelWB. This is a reference to the Office Excel workbook containing the Office Excel worksheet that contains the data to use for the graph.

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-37

An Excel.Range object named dataRange. This range specifies the location in the Office Excel worksheet that contains the data to use for the graph. In the generateExcelChart method, after the comment TODO: Generate a line graph based on the data in the dataRange range, add code that performs the following tasks. a. Add a new chart object to the Office Excel workbook, and store a reference to this chart object in an Excel.Chart variable named excelChart.

Hint: You can create a new chart by using the Add method of the Charts property of an Office Excel workbook object. The Add method takes no parameters and returns a reference to the chart object. b. Call the ChartWizard method of the chart object to generate the chart. The following table lists the parameters that you should specify. Value "Applied Stress (kN) versus Deflection (mm)" dataRange Excel.XlChartType.xlLine Excel.XlRowCol.xlColumns 1 1 "Deflection" "Applied Stress"

Parameter name Title Source Gallery PlotBy CategoryLabels SeriesLabels ValueTitle CategoryTitle

After the comment TODO: Save the Excel workbook, add a statement that saves the Office Excel workbook by using the value in the fileName parameter.

Hint: Use the SaveAs method of the Office Excel Workbook object to save a workbook. This method takes a parameter named Filename that specifies the name of the file to use. Build the solution and correct any errors.

Task 6: Complete the test harness.


Return to the GenerateGraphButton_Click method. After the comment TODO: If the user specifies a valid file name, start Excel and create a new workbook and worksheet to hold the data, add code to perform the following tasks. a. b. c. d. Create a new Excel.Application object named excelApp. Make the application visible on the user's desktop by setting the Visible property of the excelApp object to True. (By default, Office Excel will run in the background.) Set the AlertBeforeOverwriting property of the excelApp object to False. This ensures that the SaveAs method always saves the workbook. Set the DisplayAlerts property of the excelApp object to False.

15-38

Programming in Visual Basic with Microsoft Visual Studio 2010

e.

Create a new workbook, and store a reference to this workbook in an Excel.Workbook variable named excelWB.

Hint: You can create a new workbook by using the Add method of the Workbooks property of an Excel.Application object. This method takes no parameters and returns a reference to the new workbook. f. Create a variable named excelWS of type Excel.Worksheet and set it as the active worksheet in the new workbook.

Hint: You can obtain a reference to the active worksheet in an Office Excel workbook by using the ActiveSheet property. After the comment TODO: Copy the data from the graphData variable to the new worksheet and generate a graph, add code to perform the following tasks: a. b. Create an Excel.Range object named dataRange and initialize it to null. Call the transferDataToExcelSheet method, and pass the excelWS object the dataRange object, and the graphData variable as parameters. Note that the dataRange object should be an output parameter. If the value that the transferDataToExcelSheet method returns is True, call the generateExcelChart method. Pass the FileName property of the saveDialog object, the excelWB object, and the dataRange object as parameters.

c.

At the end of the GenerateGraphButton_Click method, in the finally block, after the comment TODO: Close Excel and release any resources, add code to check whether the excelApp variable is null; if it is not, close the Office Excel application. Hint: Use the Quit method of an Excel.Application object to close Office Excel.

Build the solution and correct any errors.

Task 7: Test the application.


Start the application in Debug mode. In the Graphing Data window, click Get Data. In the Graph Data dialog box, click the 298K.txt file, and then click Open. In the Graphing Data window, in the tree view, expand the Temperature: 298K node. Verify that the data has been correctly loaded. Repeat steps 2, 3, and 4 and load the data in the 318K.txt and 338K.txt files. Verify that the tree view lists the data from all three files.

Note: The displayData method displays the value 1 for any missing deflection data. Click Graph. In the Graph Data dialog box, accept the default file name, StressData.xlsx, for the name of the Office Excel workbook to be generated, and then click Save.

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-39

You will see Office Excel start to run and your data copied across to a new worksheet. You will also briefly see the graph that is generated before the workbook is saved and Office Excel closes. Using Windows Explorer, browse to the D:\Labfiles\Lab15 folder. Verify that this folder contains the Office Excel workbook StressData.xlsx. Double-click the StressData.xlsx file to start Office Excel and open the workbook. The workbook should contain a chart that displays the stress test results by using the data and settings that you specified. In Office Excel, click the Sheet1 tab. This is the worksheet that your code generated. The first column contains the applied stress values, and the remaining three columns contain the deflections recorded at each of the three temperatures. Close Office Excel. Close the Graphing Data window. Close Visual Studio.

15-40

Programming in Visual Basic with Microsoft Visual Studio 2010

Lab Review

Review Questions
1. 2. 3. Which component is responsible for translating Visual Basic method calls to a Python object into Python method calls? Which component is responsible for translating values that a Python method returns into a format that a Visual Basic application can use? How did you create the interop assembly that your application used, to interact with Office Excel?

Integrating Visual Basic Code with Dynamic Languages and COM Components

15-41

Module Review and Takeaways

Review Question
1. What is the difference between an interop assembly and an RCW?

Best Practices Related to Integrating Visual Basic Code with Dynamic Languages
Supplement or modify the following best practices for your own work situations: Program defensively. Do not assume when you build an application that uses scripts that are based on dynamic languages that those scripts will be well behaved. Be prepared to catch and handle exceptions that scripts cause. Be prepared to catch and handle exceptions that are caused by missing scripts or unexpected versions of the runtime for the dynamic language. Only use scripts from trusted sources.

Best Practices Related to Accessing COM Components from Visual Basic


Supplement or modify the following best practices for your own work situations: Ensure that any COM components that an application uses are installed and available on the computer that runs your application. Be prepared to catch and handle exceptions that missing COM components cause. Use PIA-less deployment. Only use COM components that trusted sources provide.

15-42

Programming in Visual Basic with Microsoft Visual Studio 2010

Course Evaluation

Your evaluation of this course will help Microsoft understand the quality of your learning experience. Please work with your training provider to access the course evaluation form. Microsoft will keep your answers to this survey private and confidential and will use your responses to improve your future learning experience. Your open and honest feedback is valuable and appreciated.

Lab: Introducing Basic and the .NET Framework

L1-1

Module 1: Introducing Visual Basic and the .NET Framework

Lab: Introducing Basic and the .NET Framework


Exercise 1: Creating a Simple Console Application
Task 1: Create a new Console Application project
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010. Create a new console application project named ConsoleApplication in the D:\Labfiles\Lab01\Ex1\Starter folder. a. b. c. d. In Visual Studio, on the File menu, click New Project. In the New Project dialog box, in the Installed Templates pane, expand Visual Basic, and then click Windows. In the Templates pane, click Console Application. Specify the following values for each of the properties in the dialog box, and then click OK. Name: ConsoleApplication Location: D:\Labfiles\Lab01\Ex1\Starter Solution name: ConsoleApplication Create directory for solution: Select the check box.

Task 2: Add code to read user input and write output to the console
1. In the Main procedure in the Module1 module, add code to read a line of text from the keyboard and store it in a string variable named line. Your code should resemble the following code in bold.
Sub Main() ' Buffer to hold each line as it's read in Dim line As String line = Console.ReadLine() ' Loop until no more input (Ctrl-Z in a console or end-of-file) While Not line Is Nothing ' Format the data line = line.Replace(",", " y:") line = "x:" & line ' Write the results out to the console window Console.WriteLine(line) line = Console.ReadLine() End While End Sub

This code uses the Console.ReadLine method to read the input, and includes comments with each line of code that indicates its purpose. 2. Add code to write the text back to the console by using the Console.WriteLine method. Your code should resemble the following code in bold.

L1-2

Lab: Introducing Basic and the .NET Framework

Sub Main() ' Buffer to hold each line as it's read in Dim line As String line = Console.ReadLine() ' Write the results out to the console window Console.WriteLine(line) End Sub

3.

Build the application. On the Build menu, click Build Solution. Correct any errors.

4.

Run the application and verify that it works as expected. You should be able to enter a line of text and see that line written to the console. a. b. c. d. Press Ctrl+F5. In the console window, type some text, and then press Enter. Verify that the text that you typed is echoed to the console. Press Enter to return to Visual Studio.

Task 3: Modify the program to read and echo text until end-of-file is detected
1. In the Main procedure, modify the statement and comment shown in bold in the following code example, which reads a line of text from the keyboard.
Sub Main() ' Buffer to hold each line as it's read in Dim line As String line = Console.ReadLine() ' Loop until no more input (Ctrl-Z in a console or end-of-file) While Not line Is Nothing line = Console.ReadLine() End While ' Write the results out to the console window Console.WriteLine(line) End Sub

This code incorporates the statement into a While loop that repeatedly reads text from the keyboard until the Console.ReadLine method returns Nothing (this happens when the Console.ReadLine method detects the end of a file, or the user presses Ctrl+Z). 2. Move the Console.WriteLine statement into the body of the While loop as shown in bold in the following code example. This statement echoes each line of text that the user has entered.
Sub Main() ' Buffer to hold each line as it's read in Dim line As String line = Console.ReadLine() ' Loop until no more input (Ctrl-Z in a console or end-of-file) While Not line Is Nothing ' Write the results out to the console window Console.WriteLine(line) line = Console.ReadLine() End While

Lab: Introducing Basic and the .NET Framework

L1-3

End Sub

3.

Build the application. On the Build menu, click Build Solution. Correct any errors.

4.

Run the application and verify that it works as expected. You should be able to repeatedly enter lines of text and see those lines echoed to the console. The application should only stop when you press Ctrl+Z. a. b. c. d. e. f. g. Press Ctrl+F5. In the console window, type some text, and then press Enter. Verify that the text that you typed is echoed to the console. Type some more text, and then press Enter again. Verify that this line is also echoed to the console. Press Ctrl+Z, and then verify that the application finishes. Press Enter to return to Visual Studio.

Task 4: Add code to format the data and display it


1. In the body of the While loop, add the statement and comment shown in bold before the Console.WriteLine statement in the following code example.
Sub Main() ' Buffer to hold each line as it's read in Dim line As String line = Console.ReadLine() ' Loop until no more input (Ctrl-Z in a console or end-of-file) While Not line Is Nothing ' Format the data line = line.Replace(",", " y:") ' Write the results out to the console window Console.WriteLine(line) line = Console.ReadLine() End While End Sub

This code replaces each occurrence of the comma character "," in the input read from the keyboard and replaces it with the text y:. It uses the Replace method of the line string variable. The code then assigns the result back to the line variable. 2. Add the statement shown in bold in the following code example to the code in the body of the While loop.
Sub Main() ' Buffer to hold each line as it's read in Dim line As String line = Console.ReadLine() ' Loop until no more input (Ctrl-Z in a console or end-of-file) While Not line Is Nothing ' Format the data line = line.Replace(",", " y:") line = "x:" & line

L1-4

Lab: Introducing Basic and the .NET Framework

' Write the results out to the console window Console.WriteLine(line) line = Console.ReadLine() End While End Sub

This code adds the prefix x: to the line variable by using the string concatenation operator &, before the Console.WriteLine statement. The code then assigns the result back to the line variable. 3. Build the application. 4. On the Build menu, click Build Solution. Correct any errors.

Run the application and verify that it works as expected. The application expects input that looks like the following code example.
23.54367,25.6789

Your code should format the output to look like the following code example.
x:23.54367 y:25.6789

a. b. c. d. e. f. g.

Press Ctrl+F5. In the console window, type 23.54367,25.6789 and then press Enter. Verify that the text x:23.54367, y:25.6789 is displayed on the console. Type some more text that consists of pairs of numbers that are separated by a comma, and then press Enter again. Verify that this data is correctly formatted and displayed on the console. Press Ctrl+Z. Press Enter to return to Visual Studio.

Task 5: Test the application by using a data file


1. Add the DataFile.txt file that contains the sample data to the project. This file is located in the D:\Labfiles\Lab01\Ex1\Starter folder, and you can copy it by pointing to Add, and then click Existing Item on the context menu, which is accessible by right-clicking the project in Solution Explorer. The file should be copied to the build output folder when the project is built. You can do this by setting the Build Action property to None, and the Copy to Output property to Copy Always, for the DataFile.txt item in Solution Explorer. a. b. In Solution Explorer, right-click the ConsoleApplication project, point to Add, and then click Existing Item. In the Add Existing Item ConsoleApplication dialog box, move to the D:\Labfiles\Lab01\Ex1\Starter folder, select All Files (*.*) in the drop-down list box adjacent to the File name text box, click DataFile.txt, and then click Add. In Solution Explorer, select DataFile.txt. In the Properties window, change the Build Action property to None, and then change the Copy to Output property to Copy Always.

c. 2.

Rebuild the application. On the Build menu, click Rebuild Solution.

Lab: Introducing Basic and the .NET Framework

L1-5

3.

Open a Visual Studio Command Prompt window, and then move to the D:\Labfiles\Lab01\Ex1\Starter\ConsoleApplication\bin\Debug folder. a. b. Click Start, point to All Programs, click Microsoft Visual Studio 2010, click Visual Studio Tools, and then click Visual Studio Command Prompt (2010). In the Visual Studio Command Prompt (2010) window, browse to the D:\Labfiles\Lab01\Ex1\Starter\ConsoleApplication \ConsoleApplication\bin\Debug folder.

4.

Run the ConsoleApplication application and redirect input to come from DataFile.txt. You can do this by typing in ConsoleApplication < DataFile.txt in the Command Prompt window. Verify that the output that is generated looks like the following code example.
x:23.8976 y:12.3218 x:25.7639 y:11.9463 x:24.8293 y:12.2134

In the Command Prompt window, type the command in the following code example, and then press Enter.
ConsoleApplication < DataFile.txt

5. 6.

Close the Command Prompt window, and then return to Visual Studio. Modify the project properties to redirect input from the DataFile.txt file when the project is run by using Visual Studio. You can do this by typing in < DataFile.txt in the Command line arguments text box, on the Debug page, of the Project Designer. a. b. c. d. In Solution Explorer, right-click ConsoleApplication, and then click Properties. On the Debug page, in the Command line arguments text box, type < DataFile.txt. On the File menu, click Save All. Close the ConsoleApplication properties window.

7.

Run the application in Debug mode from Visual Studio. On the Debug menu, click Start Debugging.

The application will run, but the console window will close immediately after the output is generated. This is because Visual Studio only prompts the user to close the console window when a program is run without debugging. When a program is run in the Debug mode, Visual Studio automatically closes the console window as soon as the program finishes. 8. Set a breakpoint on the End Sub statement that signals the end of the Main procedure. 9. In the Module1.vb file, move the cursor to the End Sub statement for the Main procedure, right-click, point to Breakpoint, and then click Insert Breakpoint.

Run the application again in the Debug mode. Verify that the output that is generated is the same as the output that is generated when the program runs from the command line. a. On the Debug menu, click Start Debugging.

The application will run, and then the program will stop when control reaches the end of the Main procedure and Visual Studio has the focus. b. On the Windows taskbar, click the icon for ConsoleApplication.exe.

L1-6

Lab: Introducing Basic and the .NET Framework

c. d.

Verify that the output that is displayed in the ConsoleApplication.exe window is the same as before. Return to Visual Studio, and then on the Debug menu, click Continue. The application will finish.

Exercise 2: Creating a WPF Application


Task 1: Create a new WPF application project
Create a new project named WpfApplication in the D:\Labfiles\Lab01\Ex2 \Starter folder by using the Windows Presentation Foundation (WPF) Application project template. a. b. c. d. In Visual Studio, on the File menu, click New Project. In the New Project dialog box, in the Project Types pane, expand Visual Basic, and then click Windows. In the Templates pane, click WPF Application. Specify the following values for each of the properties in the dialog box, and then click OK. Name: WpfApplication Location: D:\Labfiles\Lab01\Ex2\Starter Solution name: WpfApplication Create directory for solution: Select the check box

Task 2: Create the user interface


1. Add TextBox, Button, and TextBlock controls to the MainWindow window. Place them anywhere in the window. a. b. c. d. e. f. g. 2. Open the Toolbox. Expand the Common WPF Controls section if it is not already open. Click and drag the TextBox control to anywhere in the MainWindow window. Open the Toolbox. Click and drag the Button control to anywhere in the MainWindow window. Open the Toolbox. Click and drag the TextBlock control to anywhere in the MainWindow window.

Using the Properties window, set the properties of each control by using the values in the following table. Leave any other properties at their default values. Property Name Name Height HorizontalAlignment Margin VerticalAlignment Width Property Value TestTextBox 28 Left 12,12,0,0 Top 302

Control Type TextBox

Lab: Introducing Basic and the .NET Framework

L1-7

Control Type Button

Property Name Name Content Height HorizontalAlignment Margin VerticalAlignment Width

Property Value TestButton Format Data 23 Left 320,17,0,0 Top 80 FormattedTextTextBlock 238 Left 14,50,0,0

TextBlock

Name Height HorizontalAlignment Margin Text VerticalAlignment Width

Top 384

a. b. c. d. e. f. g. h. 3.

In the MainWindow window, click the TextBox control. In the Properties window, click the textBox1 text adjacent to the TextBox prompt, and then change the name to TestTextBox. In the list of properties in the Properties window, locate the Height property, and then change it to 28. Repeat this process for the remaining properties of the TextBox control. In the MainWindow window, click the Button control. Follow the procedure described in steps b to e to set the specified properties for this control. In the MainWindow window, click the TextBlock control. Follow the procedure described in steps b to e to set the specified properties for this control.

The MainWindow window should appear as the following image.

L1-8

Lab: Introducing Basic and the .NET Framework

Task 3: Add code to format the data that the user enters
1. Create an event handler for the Click event of the button. a. b. c. 2. In the MainWindow window, click the Button control. In the Properties window, click the Events tab. In the list of events, double-click the Click event.

Add code to the event-handler method that reads the contents of the TextBox control into a string variable named line, formats this string in the same way as the console application in Exercise 1, and then displays the formatted result in the TextBlock control. Notice that you can access the contents of a TextBox control and a TextBlock control by using the Text property. Your code should resemble the following code.
Private Sub TestButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles TestButton.Click ' Copy the contents of the TextBox into a string Dim line As String = TestTextBox.Text ' Format the data in the string line = line.Replace(",", " y:") line = "x:" + line ' Store the results in the TextBlock FormattedTextTextBlock.Text = line End Sub

3.

Build the solution, and then correct any errors. On the Build menu, click Rebuild Solution.

4.

Run the application and verify that it works in a similar manner to the original console application in Exercise 1.

Lab: Introducing Basic and the .NET Framework

L1-9

a. b. c. 5.

Press Ctrl+F5. In the MainWindow window, type 23.654,67.823 into the TextBox control, and then click Format Data. Verify that x:23.654 y:67.823 appears in the TextBlock control below the TextBox control.

Close the MainWindow window, and then return to Visual Studio.

Task 4: Modify the application to read data from a file


1. Create an event handler for the Window_Loaded event. This event occurs when the window is about to be displayed, just after the application has started up. You can do this by clicking the title bar of the MainWindow window in the MainWindow.xaml file, and then double-clicking the Loaded event in the list of events in the Properties window. a. b. c. d. 2. Display the MainWindow.xaml file. Click the title bar of the MainWindow window. In the Properties window, click the Events tab. In the list of events, double-click the Loaded event.

In the event-handler method, add the code shown in bold in the following code example.
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded ' Buffer to hold a line read from the file on standard input Dim line As String line = Console.ReadLine() ' Loop until the end of the file While Not line Is Nothing ' Format the data in the buffer line = line.Replace(",", " y:") line = "x:" & line & vbNewLine ' Put the results into the TextBlock FormattedTextTextBlock.Text &= line line = Console.ReadLine() End While End Sub

This code reads text from the standard input, formats it in the same manner as Exercise 1, and then appends the results to the end of the TextBlock control. It continues to read all text from the standard input until end-of-file is detected. Notice that you can use the &= operator to append data to the Text property of a TextBlock control, and you can add the newline character, vbNewLine, between lines for formatted output to ensure that each item appears on a new line in the TextBlock control. 3. Perform the following steps to modify the project settings to redirect standard input to come from the DataFile.txt file. A copy of this file is available in the D:\Labfiles\Lab01\Ex2\Starter folder. a. b. In Solution Explorer, right-click the WpfApplication project, point to Add, and then click Existing Item. In the Add Existing Item WpfApplication dialog box, move to the D:\Labfiles\Lab01\Ex2\Starter folder, select All Files (*.*) in the drop-down list box adjacent to the File name text box, click DataFile.txt, and then click Add.

L1-10

Lab: Introducing Basic and the .NET Framework

c. d. e. f. g. 4.

In Solution Explorer, select DataFile.txt. In the Properties window, change the Build Action property to None, and then change the Copy to Output property to Copy Always. In Solution Explorer, right-click the WpfApplication project, and then click Properties. On the Debug tab, in the Command line arguments: text box, type < DataFile.txt On the File menu, click Save All. Close the WpfApplication properties window.

Build and run the application in the Debug mode. Verify that when the application starts, it reads the data from DataFile.txt and displays in the TextBlock control the results in the following code example.
x:23.8976 y:12.3218 x:25.7639 y:11.9463 x:24.8293 y:12.2134

5.

On the Debug menu, click Start Debugging.

Close the MainWindow window, and then return to Visual Studio.

Exercise 3: Verifying the Application


Task 1: Modify the data in the DataFile.txt file
Modify the contents of the DataFile.txt file as the following code example shows.
1.2543,0.342 32525.7639,99811.9463 24.8293,12.2135 23.8976,12.3218 25.7639,11.9463 24.8293,12.2135

a. b. c. d.

In Solution Explorer, double-click DataFile.txt. Edit the data in the file so that it resembles the data shown. On the File menu, click Save All. Close the DataFile.txt window.

Note: There must be a blank line at the end of DataFile.txt.

Task 2: Step through the application by using the Visual Studio 2010 debugger
1. Set a breakpoint at the start of the Window_Loaded event handler. a. b. c. Display the MainWindow.xaml.vb file. Scroll down to the Window_Loaded event. Right-click the statement in the following code, point to Breakpoint, and then click Insert Breakpoint.

Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded

Lab: Introducing Basic and the .NET Framework

L1-11

2.

Start the application running in the Debug mode. On the Debug menu, click Start Debugging.

Note: When the application runs the Window_Loaded event handler, it reaches the breakpoint and drops into Visual Studio. The method declaration is highlighted. 3. Step into the second statement in the Window_Loaded method that contains executable code. a. b. On the Debug menu, click Step Into, or press F8. Repeat step a.

Note: The While statement should be highlighted. This is because the statement that declares the line variable does not contain any executable code. 4. Examine the value of the line variable. It should be 1.2543,0.342. This is the text from the first line of the DataFile.txt file. 5. In the Locals window, verify that the value of line is 1.2543,0.342.

Step into the next statement. On the Debug menu, click Step Into, or press F8.

The cursor moves to the line in the following code example.


line = line.Replace(",", " y:")

6.

Step into the next statement. On the Debug menu, click Step Into, or press F8.

7.

Examine the value of the line variable. It should now be 1.2543 y:0.342. This is the result of calling the Replace method and assigning the result back to line. In the Locals window, verify that the value of line is 1.2543 y:0.342.

8.

Step into the next statement. On the Debug menu, click Step Into, or press F8.

9.

Examine the value of the line variable. It should now be x:1.2543 y:0.342 . This is the result of prefixing the text x: to line and suffixing a newline character. The latter is displayed as a space. In the Locals window, verify that the value of line is x:1.2543 y:0.342 .

10. Step into the next statement. a. b. On the Debug menu, click Step Into, or press F8. Repeat step a.

Note: The cursor moves to the end of the While loop. 11. In the Immediate window, examine the value of the Text property of the FormattedTextTextBlock control. It should contain the same text as the line variable.

L1-12

Lab: Introducing Basic and the .NET Framework

Note: If the Immediate window is not visible, press Ctrl+Alt+I. a. In the Immediate window, type the expression in the following code example (including the question mark), and then press Enter.

?FormattedTextTextBlock.Text

b.

Verify that the text "x:1.2534 y:0.342 " is displayed.

12. Set another breakpoint at the end of the While loop. Right-click End While, point to Breakpoint, and then click Insert Breakpoint.

13. Continue running the programming for the next iteration of the While loop. It should stop when it reaches the breakpoint at the end of the loop. On the Debug menu, click Continue, or press F5.

14. Examine the value of the line variable. It should now be x:32525.7639 y:99811.9463. This is the data from the second line of DataFile.txt. In the Locals window, verify that the value of line is x:32525.7639 y:99811.9463.

15. Remove the breakpoint from the end of the While loop. Right-click End While, point to Breakpoint, and then click Delete Breakpoint.

16. Continue the programming running. The Window_Loaded method should now run to completion and display the MainWindow window. The TextBlock control should contain all of the data from DataFile.txt, formatted correctly. a. b. On the Debug menu, click Continue, or press F5. Verify that the TextBlock control displays the formatted results for every line in the DataFile.txt file.

17. Close the MainWindow window, and then return to Visual Studio.

Exercise 4: Generating Documentation for an Application


Task 1: Open the starter project
In Visual Studio, open the WpfApplication solution located in the D:\Labfiles\Lab01\Ex4\Starter folder. This solution is a working copy of the solution from Exercise 2. a. b. In Visual Studio, on the File menu, click Open Project. Browse to the D:\Labfiles\Lab01\Ex4\Starter folder, click WpfApplication.sln, and then click Open.

Task 2: Add XML comments to the application


1. Display the MainWindow.xaml.vb file. 2. In Solution Explorer, expand MainWindow.xaml, and then double-click MainWindow.xaml.vb.

Add the XML comment in the following code example before the MainWindow class declaration. There should be no blank line between the class declaration and the last line of the XML comment.
''' <summary> ''' WPF application to read and format data ''' </summary>

Lab: Introducing Basic and the .NET Framework

L1-13

''' <remarks></remarks>

In MainWindow.xaml.vb, immediately before the class declaration, type the following XML comment.
''' <summary> ''' WPF application to read and format data ''' </summary> ''' <remarks></remarks> Class MainWindow

3.

Add the XML comment in the following code example before the TestButton_Click method. There should be no blank line between the method declaration and the last line of the XML comment.
''' ''' ''' ''' ''' ''' ''' ''' <summary> Read a line of data entered by the user. Format the data and display the results in the FormattedTextTextBlock control. </summary> <param name="sender"></param> <param name="e"></param> <remarks></remarks>

In MainWindow.xaml.vb, immediately before the TestButton_Click method declaration, type the following XML comment.
''' <summary> ''' Read a line of data entered by the user. ''' Format the data and display the results in the ''' FormattedTextTextBlock control. ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> ''' <remarks></remarks> Private Sub TestButton_Click(ByVal sender As System.Object...

4.

Add the XML comment in the following code example before the Window_Loaded method. There should be no blank line between the method declaration and the last line of the XML comment.
''' ''' ''' ''' ''' ''' ''' ''' <summary> After the Window has loaded, read data from the standard input. Format each line and display the results in the FormattedTextTextBlock control. </summary> <param name="sender"></param> <param name="e"></param> <remarks></remarks>

In MainWindow.xaml.vb, immediately before the Window_Loaded method declaration, type the following XML comment.
''' <summary> ''' After the Window has loaded, read data from the standard input. ''' Format each line and display the results in the ''' FormattedTextTextBlock control. ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> ''' <remarks></remarks> Private Sub Window_Loaded(ByVal sender As System.Object...

L1-14

Lab: Introducing Basic and the .NET Framework

5.

Save MainWindow.xaml.vb. The saved MainWindow.xaml.vb should resemble the following example code.
''' <summary> ''' WPF application to read and format data ''' </summary> ''' <remarks></remarks> Class MainWindow ''' <summary> ''' Read a line of data entered by the user. ''' Format the data and display the results in the ''' FormattedTextTextBlock control. ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> ''' <remarks></remarks> Private Sub TestButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles TestButton.Click ' Copy the contents of the TextBox into a string Dim line As String = TestTextBox.Text ' Format the data in the string line = line.Replace(",", " y:") line = "x:" + line ' Store the results in the TextBlock FormattedTextTextBlock.Text = line End Sub ''' <summary> ''' After the Window has loaded, read data from the standard input. ''' Format each line and display the results in the ''' FormattedTextTextBlock control. ''' </summary> ''' <param name="sender"></param> ''' <param name="e"></param> ''' <remarks></remarks> Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded ' Buffer to hold a line read from the file on standard input Dim line As String line = Console.ReadLine() ' Loop until the end of the file While Not line Is Nothing ' Format the data in the buffer line = line.Replace(",", " y:") line = "x:" & line & vbNewLine ' Put the results into the TextBlock FormattedTextTextBlock.Text &= line line = Console.ReadLine() End While End Sub End Class

Task 3: Generate an XML comments file


1. Set the project properties to generate an XML documentation file when the project is built. a. b. In Solution Explorer, right-click the WpfApplication project, and then click Properties. On the Compile tab, ensure the Generate XML documentation file check box is selected.

Lab: Introducing Basic and the .NET Framework

L1-15

c. d. 2.

On the File menu, click Save All. Close the WpfApplication properties window.

Build the solution, and then correct any errors. On the Build menu, click Rebuild Solution.

3.

Verify that an XML comments file named WpfApplication.xml has been generated in the D:\Labfiles\Lab01\Ex4\Starter\WpfApplication\bin\Debug folder, and then examine it. a. b. Using Windows Explorer, browse to the D:\Labfiles\Lab01\Ex4\Starter\WpfApplication \bin\Debug folder. Double-click the WpfApplication.xml file. The file should be displayed in Internet Explorer. Verify that it contains the text for the XML comments that you added to the WPF application (it will also contain other comments that Visual Studio has generated). Close Internet Explorer.

c. 4.

Copy the WpfApplication.xml file to the D:\Labfiles\Lab01\Ex4\HelpFile folder.

Task 4: Generate a .chm file


1. Open a Windows Command Prompt window as Administrator. The Administrator password is Pa$$w0rd. a. b. 2. 3. Click Start, point to All Programs, click Accessories, right-click Command Prompt, and then click Run as administrator. In the User Account Control dialog box, in the Password text box, type Pa$$w0rd, and then click Yes.

Browse to the D:\Labfiles\Lab01\Ex4\HelpFile folder. Copy the sandcastle.config file from the C:\Program Files (x86)\Sandcastle\Presentation \vs2005\configuration folder, to the current folder by running the following command:
copy "C:\Program Files (x86) \Sandcastle\Presentation\vs2005\configuration\sandcastle.config"

a.

In the Command Prompt window, type the command in the following code example, and then press Enter.

copy "C:\Program Files (x86) \Sandcastle\Presentation\vs2005\configuration\sandcastle.config"

4.

Use Notepad to edit the builddoc.cmd script, and then edit the input variable to D:\Labfiles\Lab01\Ex4\Starter\WpfApplication\bin\Debug \WpfApplication.exe. b. In the Command Prompt window, type the command in the following code example, and then press Enter.

notepad builddoc.cmd

c.

Edit line 13 so it looks like the following code example.

set input="D:\Labfiles\Lab01\Ex4\Starter\WpfApplication\bin\Debug\WpfApplication.exe"

d.

Save the file and close Notepad.

L1-16

Lab: Introducing Basic and the .NET Framework

5.

Run the builddoc.cmd script. a. In the Command Prompt window, type the command in the following code example.

builddoc.cmd

b. 6.

Press Enter when the script prompts you to do so.

Open the test.chm file that the builddoc.cmd script generates in the D:\Labfiles\Lab01 \Ex4\HelpFile\Output folder. a. b. Using Windows Explorer, browse to the D:\Labfiles\Lab01 \Ex4\HelpFile\Output folder. Double-click the test.chm file.

7. 8.

Browse documentation that is generated for your application, and then close test.chm. Close Visual Studio. In Visual Studio, on the File menu, click Exit.

Lab: Using Visual Basic Programming Constructs

L2-1

Module 2: Using Visual Basic Programming Constructs

Lab: Using Visual Basic Programming Constructs


Exercise 1: Calculating Square Roots with Improved Accuracy
Task 1: Create a new WPF application project.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Create a new project named, SquareRoots, in the D:\Labfiles\Lab02\Ex1\Starter folder, by using the WPF Application project template. a. b. c. d. In Visual Studio, on the File menu, click New Project. In the New Project dialog box, in the left pane, expand Visual Basic, and then click Windows. In the Templates pane, click WPF Application. Specify the following values for each of the properties in the dialog box, and then click OK: Name: SquareRoots Location: D:\Labfiles\Lab02\Ex1\Starter Solution name: SquareRoots Create directory for solution: Select the check box.

Task 2: Create the user interface.


1. Add a TextBox, a Button, and two Label controls to the MainWindow window. Place them anywhere in the window. a. b. c. d. e. f. g. h. i. 2. Open the Toolbox. Expand the Common WPF Controls section of the Toolbox, if it is not already open. Double-click the TextBox control. Open the Toolbox. Double-click the Button control. Open the Toolbox. Double-click the Label control. Open the Toolbox. Double-click the Label control.

Using the Properties window, set the properties of each control by using the values in the following table. Leave any other properties at their default values. Property Name Name Property Value InputTextBox

Control Type TextBox

L2-2

Lab: Using Visual Basic Programming Constructs

Control Type

Property Name Height HorizontalAlignment Margin Text VerticalAlignment Width

Property Value 28 Left 12,12,0,0 0.00 Top 398 CalculateButton Calculate 23 Right 0,11,12,0 Top 75 FrameworkLabel 0.00 (Using .NET Framework) 28 Left 12,41,0,0 Top 479 NewtonLabel 0.00 (Using Newton) 28 Left 12,75,0,0 Top 479

Button

Name Content Height HorizontalAlignment Margin VerticalAlignment Width

Label

Name Content Height HorizontalAlignment Margin VerticalAlignment Width

Label

Name Content Height HorizontalAlignment Margin VerticalAlignment Width

a. b.

In the MainWindow window, click the TextBox control. In the Properties window, click the text, textBox1, adjacent to the TextBox prompt, and then change the name to InputTextBox.

Lab: Using Visual Basic Programming Constructs

L2-3

c. d. e. f. g. h. i. j.

In the list of properties in the Properties window, locate the Height property, and then change it to 28. Repeat this process for the remaining properties of the TextBox control. In the MainWindow window, click the Button control. Follow the procedure described in steps b to d, to set the specified properties for this control. In the MainWindow window, click one of the Label controls. Follow the procedure described in steps b to d, to set the specified properties for this control. In the MainWindow window, click the other Label control. Follow the procedure described in steps b to d, to set the specified properties for this control.

The MainWindow window should look like the following image.

Task 3: Calculate square roots by using the Math.Sqrt method of the .NET Framework.
1. Create an event handler for the Click event of the button: a. b. c. 2. In the MainWindow window, click the Button control. In the Properties window, click the Events tab. In the list of events, double-click the Click event.

In the CalculateButton_Click method, add code to read the data that the user enters in the InputTextBox control, and then convert it into a Double. Store the double value in a variable named, numberDouble. Use the TryParse method of the Double type to perform the conversion. If the text that the user enters is not valid, display a message box with the text, "Please enter a double.", and then execute a Return statement to exit the method.

Note: You can display a message in a message box by using the MessageBox.Show method.

L2-4

Lab: Using Visual Basic Programming Constructs

Add the code in the following code example to the CalculateButton_Click method.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles CalculateButton.Click ' Get a double from the TextBox Dim numberDouble As Double If Not Double.TryParse(InputTextBox.Text, numberDouble) Then MessageBox.Show("Please enter a double") Return End If End Sub

3.

Check that the value that the user enters is a positive number. If it is not, display a message box with the text, "Please enter a positive number.", and then return from the method. Append the statements in the following code example to the CalculateButton_Click method, after the code that you added in the previous step.
Private Sub CalculateButton_Click(ByVal System.Windows.RoutedEventArgs) Handles ... ' Check that the user has entered a If numberDouble <= 0 Then MessageBox.Show("Please enter a Return End If End Sub sender As System.Object, ByVal e As CalculateButton.Click positive number positive number")

4.

Calculate the square root of the value in the numberDouble variable by using the Math.Sqrt method. Store the result in a double variable named, squareRoot. Append the statements in the following code example to the CalculateButton_Click method, after the code that you added in the previous step.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles CalculateButton.Click ... ' Use the .NET Framework Math.Sqrt method Dim squareRoot As Double = Math.Sqrt(numberDouble) End Sub

5.

Format the value in the squareRoot variable by using the layout shown in the following code example, and then display it in the FrameWorkLabel Label control.
99.999 (Using the .NET Framework)

Use the String.Format method to format the result. Set the Content property of a Label control to display the formatted result. Append the statements in the following code example to the CalculateButton_Click method, after the code that you added in the previous step.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles CalculateButton.Click ... ' Format the result and display it FrameworkLabel.Content = String.Format("{0} (Using the .NET Framework)", squareRoot)

Lab: Using Visual Basic Programming Constructs

L2-5

End Sub

At this point, your code should resemble the following code example.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles CalculateButton.Click ' Get a double from the TextBox Dim numberDouble As Double If Not Double.TryParse(InputTextBox.Text, numberDouble) Then MessageBox.Show("Please enter a double") Return End If ' Check that the user has entered a positive number If numberDouble <= 0 Then MessageBox.Show("Please enter a positive number") Return End If ' Use the .NET Framework Math.Sqrt method Dim squareRoot As Double = Math.Sqrt(numberDouble) ' Format the result and display it FrameworkLabel.Content = String.Format("{0} (Using the .NET Framework)", squareRoot) End Sub

6.

Build and run the application to test your code. Use the test values that are shown in the following table, and then verify that the correct square roots are calculated and displayed (ignore the "Using Newton" label for the purposes of this test). Expected result 5 25 0.0001 Message box appears with the message "Please enter a positive number." Message box appears with the message "Please enter a double." 3.16227766016838 2.96647939483827 1.4142135623731 1.4142135623731 a. b. c. Press Ctrl+F5. Enter the first value in the Test value column in the table in the TextBox control, and then click Calculate. Verify that the result matches the text in the Expected result column.

Test value 25 625 0.00000001 10 Fred 10 8.8 2.0 2

L2-6

Lab: Using Visual Basic Programming Constructs

d. 7.

Repeat steps b and c for each row in the table.

Close the application and return to Visual Studio.

Task 4: Calculate square roots by using Newton's method.


1. In the CalculateButton_Click method, after the code that you added in the previous task, create a decimal variable named, numberDecimal. Initialize this variable with the data that the user enters in the InputTextBox control, but convert it into a Decimal this time (previously, you read it as a Double). If the text that the user enters is not valid, display a message box with the text, "Please enter a decimal.", and then run a Return statement to exit the method. Append the code in the following code example to the end of the CalculateButton_Click method.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles CalculateButton.Click ... ' Newton's method for calculating square roots ' Get user input as a decimal Dim numberDecimal As Decimal If Not Decimal.TryParse(InputTextBox.Text, numberDecimal) Then MessageBox.Show("Please enter a decimal") Return End If End Sub

Note: This step is necessary because the Decimal and Double types have different ranges. A number that the user enters that is a valid Double might be out of range for the Decimal type. 2. Declare a decimal variable named, delta, and initialize it to the value of the expression, Math.Pow(10, 28). This is the smallest value that the Decimal type supports, and you will use this value to determine when the answer that is generated by using Newton's method is sufficiently accurate. When the difference between two successive estimates is less than this value, you will stop.

Note: The Math.Pow method returns a double. You will need to use the Convert.ToDecimal method to convert this value to a decimal before you assign it to the delta variable. Your code should resemble the following code example.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles CalculateButton.Click ... ' Specify 10 to the power of -28 as the minimum delta between ' estimates. This is the minimum range supported by the decimal ' type. When the difference between 2 estimates is less than this ' value, then stop. Dim delta As Decimal = Convert.ToDecimal(Math.Pow(10, -28)) End Sub

3.

Declare another Decimal variable named, guess, and initialize it with the initial guess at the square root. This initial guess should be the result of dividing the value in numberDecimal by 2.

Lab: Using Visual Basic Programming Constructs

L2-7

Your code should resemble the following code example.


Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles CalculateButton.Click ... ' Take an initial guess at an answer to get started Dim guess As Decimal = numberDecimal / 2 End Sub

4.

Declare another decimal variable named, result. You will use this variable to generate values for each iteration of the algorithm, based on the value from the previous iteration. Initialize the result variable to the value for the first iteration by using the expression, ((numberDecimal / guess) + guess) / 2. Your code should resemble the following code example.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles CalculateButton.Click ... ' Estimate result for the first iteration Dim result as Decimal = ((numberDecimal / guess) + guess / 2) End Sub

5.

Add a While loop to generate further refined guesses. The body of the While loop should assign result to guess, and generate a new value for result by using the expression, ((numberDecimal / guess) + guess) / 2. The While loop should terminate when the difference between result and guess is less than or equal to delta.

Note: Use the Math.Abs method to calculate the absolute value of the difference between result and guess. Using Newton's algorithm, it is possible for the difference between the two variables to alternate between positive and negative values as it diminishes. Consequently, if you do not use the Math.Abs method, the algorithm might terminate early with an inaccurate result. Your code should resemble the following code example.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles CalculateButton.Click ... ' While the difference between values for each current iteration ' is not less than delta, then perform another iteration to ' refine the answer. While Math.Abs(result - guess) > delta ' Use the result from the previous iteration ' as the starting point guess = result ' Try again result = ((numberDecimal / guess) + guess) / 2 End While End Sub

6.

When the While loop has terminated, format and display the value in the result variable in the NewtonLabel control. Format the data in a similar manner to the previous task. Your code should resemble the following code example.

L2-8

Lab: Using Visual Basic Programming Constructs

Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles CalculateButton.Click ... ' Display the result NewtonLabel.Content = String.Format("{0} (Using Newton)", result) End Sub

Your completed code should resemble the following code.


Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles CalculateButton.Click ' Get a double from the TextBox Dim numberDouble As Double If Not Double.TryParse(InputTextBox.Text, numberDouble) Then MessageBox.Show("Please enter a double") Return End If ' Check that the user has entered a positive number If numberDouble <= 0 Then MessageBox.Show("Please enter a positive number") Return End If ' Use the .NET Framework Math.Sqrt method Dim squareRoot As Double = Math.Sqrt(numberDouble) ' Format the result and display it FrameworkLabel.Content = String.Format("{0} (Using the .NET Framework)", squareRoot) ' Newton's method for calculating square roots ' Get user input as a decimal Dim numberDecimal As Decimal If Not Decimal.TryParse(InputTextBox.Text, numberDecimal) Then MessageBox.Show("Please enter a decimal") Return End If ' Specify 10 to the power of -28 as the minimum delta between ' estimates. This is the minimum range supported by the decimal ' type. When the difference between 2 estimates is less than this ' value, then stop. Dim delta As Decimal = Convert.ToDecimal(Math.Pow(10, -28)) ' Take an initial guess at an answer to get started Dim guess As Decimal = numberDecimal / 2 ' Estimate result for the first iteration Dim result As Decimal = ((numberDecimal / guess) + guess / 2) ' While the difference between values for each current iteration ' is not less than delta, then perform another iteration to ' refine the answer. While Math.Abs(result - guess) > delta

Lab: Using Visual Basic Programming Constructs

L2-9

' Use the result from the previous iteration ' as the starting point guess = result ' Try again result = ((numberDecimal / guess) + guess) / 2 End While ' Display the result NewtonLabel.Content = String.Format("{0} (Using Newton)", result) End Sub

Task 5: Test the application.


1. Build and run the application in Debug mode to test your code. Use the test values shown in the following table, and verify that the correct square roots are calculated and displayed. Compare the value in the two labels, and then verify that the square roots that are calculated by using Newton's method are more accurate than those calculated by using the Math.Sqrt method. .NET Framework 5 25 0.0001 3.16227766016838 2.96647939483827 1.4142135623731 1.4142135623731 a. b. c. 2. Newton's algorithm 5.000000000000000000000000000 25.000000000000000000000000000 0.0001000000000000000000000000 3.1622776601683793319988935444 2.9664793948382651794845589763 1.4142135623730950488016887242 1.4142135623730950488016887242

Test value 25 625 0.00000001 10 8.8 2.0 2

On the Debug menu, click Start Debugging, or press F5. Enter the first value in the Test value column in the table in the TextBox control, and then click Calculate. Repeat step b for each row in the table.

As a final test, try the value 0.0000000000000000000000000001 (27 zeroes after the decimal point). Can you explain the result? The program halts and reports that DivideByZeroException was unhandled. This exception occurs because the value that is specified is smaller than the range of values that the Decimal type allows, so the value in the guess variable is 0. You will see how to catch and handle exceptions in a later module.

3.

Close the application and return to Visual Studio: On the Debug menu, click Stop Debugging.

Exercise 2: Converting Integer Numeric Data to Binary


Task 1: Create a new WPF application project.
Create a new project named, IntegerToBinary, in the D:\Labfiles\Lab02\Ex2\Starter folder, by using the WPF Application project template.

L2-10

Lab: Using Visual Basic Programming Constructs

a. b. c. d.

In Visual Studio, on the File menu, click New Project. In the New Project dialog box, in the Project Types pane, expand Visual Basic, and then click Windows. In the Templates pane, click WPF Application. Specify the following values for each of the properties in the dialog box, and then click OK. Name: IntegerToBinary Location: D:\Labfiles\Lab02\Ex2\Starter Solution name: IntegerToBinary Create directory for solution: Select the check box.

Task 2: Create the user interface.


1. Add a TextBox, Button, and Label control to the MainWindow window. Place them anywhere in the window. a. b. c. d. e. f. g. h. 2. In Solution Explorer, double-click MainWindow.xaml. Open the Toolbox. Expand the Common WPF Controls section of the Toolbox, if it is not already open. Double-click the TextBox control. Open the Toolbox. Double-click the Button control. Open the Toolbox. Double-click the Label control.

Using the Properties window, set the properties of each control by using the values in the following table. Leave any other properties at their default values. Property Name Height HorizontalAlignment Margin Text VerticalAlignment Width Value InputTextBox 28 Left 12,12,0,0 0 Top 120 ConvertButton Convert 23 Left

Control TextBox

Button

Name Content Height HorizontalAlignment

Lab: Using Visual Basic Programming Constructs

L2-11

Control

Property Margin VerticalAlignment Width

Value 138,12,0,0 Top 75 BinaryLabel 0 28 Left 12,41,0,0 Top 120

Label

Name Content Height HorizontalAlignment Margin VerticalAlignment Width

a. b. c. d. e. f. g. h. i. j.

In the MainWindow window, click the TextBox control. In the Properties window, click the text, textBox1, adjacent to the TextBox prompt, and then change the name to InputTextBox. In the list of properties in the Properties window, locate the Height property, and then change it to 28. Repeat this process for the remaining properties of the TextBox control. In the MainWindow window, click the Button control. Follow the procedure described in steps b to d to set the specified properties for this control. In the MainWindow window, click one of the Label controls. Follow the procedure described in steps b to d to set the specified properties for this control. In the MainWindow window, click the other Label control. Follow the procedure described in steps b to d to set the specified properties for this control.

The MainWindow window should look like the following image.

L2-12

Lab: Using Visual Basic Programming Constructs

Task 3: Add code to generate the binary representation of an integer value.


1. Create an event handler for the Click event of the button. a. b. c. 2. In the MainWindow window, click the Button control. In the Properties window, click the Events tab. In the list of events, double-click the Click event.

In the ConvertButton_Click method, add code to read the data that the user enters in the InputTextBox control, and then convert it into an Integer type. Store the integer value in a variable named, i. Use the TryParse method of the Integer type to perform the conversion. If the text that the user enters is not valid, display a message box with the text, "TextBox does not contain an integer.", and then execute a Return statement to exit the method. Add the code in the following code example to the ConvertButton_Click method.
Private Sub ConvertButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles ConvertButton.Click ' Get the integer entered by the user Dim i As Integer If Not Integer.TryParse(InputTextBox.Text, i) Then MessageBox.Show("TextBox does not contain an integer") Return End If End Sub

3.

Check that the value that the user enters is not a negative number (the integer-to-binary conversion algorithm does not work for negative numbers). If it is negative, display a message box with the text, "Please enter a positive number or zero.", and then return from the method: Append the statements in the following code example to the ConvertButton_Click method, after the code that you added in the previous step.
Private Sub ConvertButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles ConvertButton.Click ...

Lab: Using Visual Basic Programming Constructs

L2-13

' Check that the user has not entered a negative number If i < 0 Then MessageBox.Show("Please enter a positive number or zero") Return End If End Sub

4.

Declare an integer variable named, remainder, and initialize it to zero. You will use this variable to hold the remainder after dividing i by 2 during each iteration of the algorithm. Your code should resemble the following code example.
Private Sub ConvertButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles ConvertButton.Click ... ' Remainder will hold the remainder after dividing i by 2 ' after each iteration of the algorithm Dim remainder As Integer = 0 End Sub

5.

Declare a StringBuilder variable named, binary, and instantiate it. You will use this variable to construct the string of bits that represent i as a binary value. Import the System.Text namespace by using the Error Correction Options dialog box. You can do this by placing the cursor over the last character, r, in the text, StringBuilder, move the cursor over the exclamation icon, click the dropdown arrow, and then click Import 'System.Text'. Your code should resemble the following code example.
Private Sub ConvertButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles ConvertButton.Click ... ' Binary will be used to construct the string of bits ' that represent i as a binary value Dim binary As New StringBuilder() End Sub

a. b. c. 6.

Append the code in bold formatting to the ConvertButton_Click method. Place the cursor over the last character, r, in the text, StringBuilder, move the cursor over the exclamation icon, click the drop-down arrow, and then click Import 'System.Text'. Notice the statement, Imports System.Text, is added to the top of the MainWindow.xaml.vb file.

Add a Do Until loop that performs the following tasks: a. b. c. Calculates the remainder after dividing i by 2, and then stores this value in the remainder variable. Divides i by 2, by using the integer division operator (\). Prefixes the value of remainder to the start of the string being constructed by the binary variable.

Terminate the Do Until loop when i is equal to zero.

Note: To prefix data into a StringBuilder object, use the Insert method of the StringBuilder class, and then insert the value of the data at position 0.

L2-14

Lab: Using Visual Basic Programming Constructs

Your code should resemble the following code example.


Private Sub ConvertButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles ConvertButton.Click ... ' Generate the binary representation of i Do Until i = 0 remainder = i Mod 2 i = i \ 2 binary.Insert(0, remainder) Loop End Sub

7.

Display the value in the binary variable in the BinaryLabel Label control.

Note: Use the ToString method to retrieve the string that the StringBuilder object constructs. Set the Content property of the Label control to display this string. Your code should resemble the following code example.
Private Sub ConvertButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles ConvertButton.Click ... ' Display the result BinaryLabel.Content = binary.ToString() End Sub

Your completed code should resemble the following code.


Private Sub ConvertButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles ConvertButton.Click ' Get the integer entered by the user Dim i As Integer If Not Integer.TryParse(InputTextBox.Text, i) Then MessageBox.Show("TextBox does not contain an integer") Return End If ' Check that the user has not entered a negative number If i < 0 Then MessageBox.Show("Please enter a positive number or zero") Return End If ' Remainder will hold the remainder after dividing i by 2 ' after each iteration of the algorithm Dim remainder As Integer = 0 ' Binary will be used to construct the string of bits ' that represent i as a binary value Dim binary As New StringBuilder() ' Generate the binary representation of i Do Until i = 0 remainder = i Mod 2 i = i \ 2 binary.Insert(0, remainder)

Lab: Using Visual Basic Programming Constructs

L2-15

Loop ' Display the result BinaryLabel.Content = binary.ToString() End Sub

Task 4: Test the application.


1. Build and run the application to test your code. Use the test values shown in the following table, and verify that the binary representations are generated and displayed. Expected result

Test value 0 1 1 10.5 Fred 4 999 65535 65536 a. b. c. d. 2. Press Ctrl+F5.

1 Message box appears with the message, "Please enter a positive number or zero." Message box appears with the message, "TextBox does not contain an integer." Message box appears with the message "TextBox does not contain an integer." 100 1111100111 1111111111111111 10000000000000000

Enter the first value in the Test value column in the table in the TextBox control, and then click Convert. Verify that the result matches the text in the Expected result column. Repeat steps b and c for each row in the table.

Close the application and return to Visual Studio: On the Debug menu, click Stop Debugging.

Exercise 3: Multiplying Matrices


Task 1: Open the MatrixMultiplication project and examine the starter code.
1. Open the MatrixMultiplication project located in the D:\Labfiles\Lab02\Ex3\Starter folder. a. b. c. 2. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab02\Ex3\Starter folder. Click MatrixMultiplication.sln, and then click Open.

Examine the user interface that the MainWindow window defines. In Solution Explorer, double-click MainWindow.xaml.

The user interface contains three Grid controls, three ComboBox controls, and a Button control.

L2-16

Lab: Using Visual Basic Programming Constructs

When the application runs, the first Grid control, labeled, Matrix 1, represents the first matrix, and the second Grid control, labeled, Matrix 2, represents the second matrix. The user can specify the dimensions of the matrices by using the ComboBox controls, and then enter data into each cell in them. There are several rules that govern the compatibility of matrices to be multiplied together, and Matrix 2 is automatically configured to have an appropriate number of rows based on the number of columns in Matrix 1. When the user clicks the Calculate button, Matrix 1 and Matrix 2 are multiplied together, and the result is displayed in the Grid control labeled, Result Matrix. The dimensions of the result are determined by the shapes of Matrix 1 and Matrix 2. The following image shows the completed application running. The user has multiplied a 23 matrix with a 32 matrix, and the result is a 33 matrix.

Task 2: Define the matrix arrays and populate them with the data in the Grid controls.
1. In Visual Studio, review the Task List. You can view the Task List from the View menu, where you point to Other Windows, and then click Task List. If the Task List displays User Tasks, in the dropdown list box, click Comments. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List displays User Tasks, in the drop-down list box, click Comments.

Open the MainWindow.xaml.vb file. In Solution Explorer, expand MainWindow.xaml, and then double-click MainWindow.xaml.vb.

Lab: Using Visual Basic Programming Constructs

L2-17

3.

At the top of the MainWindow class, remove the comment TODO: Task 2 declare variables, and then add statements that declare three two-dimensional arrays named, matrix1, matrix2, and result. The type of the elements in these arrays should be Double, and the size of each dimension should be set to 0, because the arrays will be dynamically sized, based on the input that the user provides. The first dimension will be set to the number of columns, and the second dimension will be set to the number of rows. Your code should resemble the following code.
Class MainWindow ' Declare three arrays of doubles to hold the 3 matrices: ' The two input matrices and the result matrix Private matrix1(0, 0) as Double Private matrix2(0, 0) as Double Private result(0, 0) as Double ... End Class

4. 5.

In the Task List, double-click the task TODO: Task 2 Copy data from input Grids. This task is located in the CalculateButton_Click method. In the CalculateButton_Click method, remove the comment TODO: Task 2 Copy data from input Grids. Add two statements that call the getValuesFromGrid method. This method (provided in the starter code) expects the name of a Grid control and the name of an array to populate with data from that Grid control. In the first statement, specify that the method should use the data in Matrix1Grid to populate matrix1. In the second statement, specify that the method should use the data from Matrix2Grid to populate matrix2. Your code should resemble the following code.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) ' Retrieve the contents of the first two grids ' into the first two matrices getValuesFromGrid(Matrix1Grid, matrix1) getValuesFromGrid(Matrix2Grid, matrix2) ... End Sub

6.

Remove the comment TODO: Task 2 Get the matrix dimensions. Declare three integer variables named, m1columns_m2rows, m1rows, and m2columns. Initialize m1columns_m2rows with the number of columns in the matrix1 array (this is also the same as the number of rows in the matrix2 array) by using the GetLength method of the first dimension of the array. Initialize m1rows with the number of rows in the matrix1 array by using the GetLength method of the second dimension of the array. Initialize m2columns with the number of columns in the matrix2 array. Your code should resemble the following code.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) ... ' Discover the dimensions of the input matrices ' (Remember that the number of columns in the first matrix will ' always be the same as the number of rows in the second matrix) Dim m1columns_m2rows As Integer = matrix1.GetLength(0) Dim m1rows As Integer = matrix1.GetLength(1) Dim m2columns As Integer = matrix2.GetLength(0) ... End Sub

L2-18

Lab: Using Visual Basic Programming Constructs

Task 3: Multiply the two input matrices and calculate the result.
1. In the CalculateButton_Click method, delete the comment TODO: Task 3 Calculate the result. Define a For loop that iterates through all the rows in the matrix1 array. The dimensions of an array are integers, so use an integer variable named, row, as the control variable in this For loop. Leave the body of the For loop blank; you will add code to this loop in the next step. Your code should resemble the following code.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) ... ' Calculate the value for each cell in the result matrix For row As Integer = 0 To m1rows - 1 Next ... End Sub

2.

In the body of the For loop, add a nested For loop that iterates through all the columns in the matrix2 array. Use an integer variable named, column, as the control variable in this For loop. Leave the body of this For loop blank. Your code should resemble the following code.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) ... ' Calculate the value for each cell in the result matrix For row As Integer = 0 To m1rows - 1 For column As Integer = 0 To m2columns - 1 Next Next ... End Sub

3.

The contents of each cell in the result array are calculated by adding the product of each item in the row identified by the row variable in matrix1 with each item in the column identified by the column variable in matrix2. You will require another loop to perform this calculation, and a variable to store the result as this loop calculates it. In the inner For loop, declare a Double variable named, accumulator, and then initialize it to zero. Your code should resemble the following code.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) ... ' Calculate the value for each cell in the result matrix For row As Integer = 0 To row < m1rows For column As Integer = 0 To m2columns - 1 ' Initialize the value for the result cell Dim accumulator As Double = 0 Next Next ... End Sub

Lab: Using Visual Basic Programming Constructs

L2-19

4.

Add another nested For loop after the declaration of the accumulator variable. This loop should iterate through all the columns in the current row in the matrix1 array. Use an integer variable named, cell, as the control variable in this For loop. Leave the body of this For loop blank. Your code should resemble the following code.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) ... ' Calculate the value for each cell in the result matrix For row As Integer = 0 To row < m1rows For column As Integer = 0 To m2columns - 1 ' Initialize the value for the result cell Dim accumulator As Double = 0 ' Iterate over the columns in the row in matrix1 For cell As Integer = 0 To m1columns_m2rows - 1 Next Next

Next ... End Sub

5.

In the body of this For loop, multiply the value in matrix1(cell, row) with the value in matrix2(column, cell), and then add the result to accumulator. Your code should resemble the following code.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) ... ' Calculate the value for each cell in the result matrix For row As Integer = 0 To row < m1rows For column As Integer = 0 To column < m2columns ' Initialize the value for the result cell Dim accumulator As Double = 0 ' Iterate over the columns in the row in matrix1 For cell As Integer = 0 To m1columns_m2rows - 1 ' Multiply the value in the current column in the ' current row in matrix1 with the value in the ' current row in the current column in matrix2 and ' add the result to accumulator accumulator += matrix1(cell, row) * matrix2(column, cell) Next Next

Next ... End Sub

6.

After the closing of the innermost For loop, store the value in the accumulator in the result array. The value should be stored in the cell that the column and row variables have identified. Your code should resemble the following code.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) ... ' Calculate the value for each cell in the result matrix

L2-20

Lab: Using Visual Basic Programming Constructs

For row As Integer = 0 To row < m1rows For column As Integer = 0 To m2columns - 1 ' Initialize the value for the result cell Dim accumulator As Double = 0 ' Iterate over the columns in the row in matrix1 For cell As Integer = 0 To m1columns_m2rows - 1 ' Multiply the value in the current column in the ' current row in matrix1 with the value in the ' current row in the current column in matrix2 and ' add the result to accumulator accumulator += matrix1(cell, row) * matrix2(column, cell) Next I result(column, row) = accumulator Next

Next ... End Sub

Task 4: Display the results and test the application.


1. In the CalculateButton_Click method, delete the comment TODO: Task 4 Display the result. The starter code contains a method named, initializeGrid, which displays the contents of an array in a Grid control in the WPF window. Add a statement that calls this method. Specify that the method should use the grid3 Grid control to display the contents of the result array. Your code should resemble the following code.
Private Sub CalculateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) ... ' Display the results of your calculation in the third Grid initializeGrid(ResultMatrixGrid, result) End Sub

2.

Build the solution and correct any errors: On the Build menu, click Build Solution. Correct any errors.

3.

Run the application. Press Ctrl+F5.

4.

In the MainWindow window, define Matrix 1 as a 32 matrix and define Matrix 2 as a 33 matrix.

Note: The number of rows in the Matrix 2 matrix is determined by the number of columns in the Matrix 1 matrix. a. b. c. 5. In the first ComboBox control, select Matrix 1: 3 Columns. In the second ComboBox control, select Matrix 1: 2 Rows. In the third ComboBox control, select Matrix 2: 3 Columns.

Specify the values for the cells in the matrices as shown in the following tables.

Matrix 1

Lab: Using Visual Basic Programming Constructs

L2-21

Matrix 1 1 3 Matrix 2 2 4 6 6. 8 10 12 14 16 18 5 7 9 11

Click Calculate. Verify that the Result matrix displays the values as shown in in the following table.

Result 32 44 7. 50 86 68 128

Change the data in Matrix 2 as shown in the following table.

Matrix 2 1 0 0 8. 0 1 0 0 0 1

Click Calculate. Verify that the Result matrix displays the values as shown in the following table.

Result 1 3 5 7 9 11

Matrix 2 is an example of an identity matrix. When you multiply a matrix by an identity matrix, the result is the same data as defined by the original matrix (it is the matrix equivalent of multiplying a value by 1 in regular arithmetic). In this case, the values in the Result matrix are the same as those in Matrix 1. 9. Change the data in Matrix 2 again, as shown in the following table.

Matrix 2 1 0 0 0 1 0 0 0 1

L2-22

Lab: Using Visual Basic Programming Constructs

10. Click Calculate. Verify that the Result matrix displays the values in the following table. Result 1 3 5 7 9 11

This time, the values in Result are the same as those in Matrix 1, except that the sign of each element is inverted (Matrix 2 is the matrix equivalent of 1 in regular arithmetic). 11. Close the MainWindow window. 12. Close Visual Studio: On the File menu, click Exit.

Lab: Declaring and Calling Methods

L3-1

Module 3: Declaring and Calling Methods

Lab: Declaring and Calling Methods


Exercise 1: Calculating the Greatest Common Divisor of Two Integers by Using Euclids Algorithm
Task 1: Open the starter project.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Import the code snippets from the D:\Labfiles\Lab03\Snippets folder.

You can import a code snippet from the Tools menu, where you click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language drop-down, click Visual Basic, and then click Add. Browse to the D:\Labfiles\Lab03\Snippets folder, click Select Folder, and then click OK. a. b. c. d. e. 3. In Visual Studio, on the Tools menu, click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language drop-down, click Visual Basic. In the Code Snippets Manager dialog box, click Add. In the Code Snippets Directory dialog box, move to the D:\Labfiles \Lab03\Snippets folder, and then click Select Folder. In the Code Snippets Manager dialog box, click OK.

Open the Euclid solution in the D:\Labfiles\Lab03\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, move to the D:\Labfiles\Lab03\Ex1 \Starter folder, click Euclid.sln, and then click Open.

Task 2: Implement Euclids algorithm.


1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list, click Comments.

Use the Task List window to navigate to the TODO: Exercise 1, Task 2 task.

This task is located in the GCDAlgorithms.vb file. 3. In the GCDAlgorithms class, remove the TODO: Exercise 1, Task 2 comment and declare a Public Shared method named FindGCDEuclid. The method should accept two integer parameters named a and b and return an integer value.

Your code should resemble the following code.


Public Class GCDAlgorithms Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer) As Integer End Function

L3-2

Lab: Declaring and Calling Methods

End Class

4.

In the FindGCDEuclid method, add code that calculates and returns the greatest common divisor (GCD) of the values specified by the parameters a and b by using Euclid's algorithm.

Euclids algorithm works as follows: a. b. c. If a is zero, the GCD of a and b is b. Otherwise, repeatedly subtract b from a (when a is greater than b) or subtract a from b (when b is greater than a) until b is zero. The GCD of the two original parameters is the new value in a.

Your code should resemble the following code.


Public Class GCDAlgorithms Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer) As Integer If a = 0 Then Return b While b <> 0 If a > b Then a = a - b Else b = b - a End If End While Return a End Function End Class

Task 3: Test the FindGCDEuclid method.


1. Use the Task List window to navigate to the TODO: Exercise 1, Task 3 task. This task is located in the MainWindow.xaml.vb file. This is the code-behind file for a WPF window that you will use to test the FindGCDEuclid method and display the results. 2. In the Task List window, double-click TODO: Exercise 1, Task 3.

Remove the TODO: Exercise 1, Task 3 comment, add code to call the static FindGCDEuclid method of the GCDAlgorithms class, and display the results in the ResultEuclidLabel control. In the method call, use the firstNumber and secondNumber variables as arguments (these variables contain values that the user enters in the WPF window). Finally, the result should be formatted as the following example.

Euclid: result

Hint: Set the Content property of a label control to display data in a label. Use the String.Format method to create a formatted string. Your code should resemble the following code.
... If sender Is FindGCD3Button Then ' Euclid for two integers ' Invoke the FindGCD method and display the result Me.ResultEuclidLabel.Content =

Lab: Declaring and Calling Methods

L3-3

End If ...

String.Format("Euclid: {0}", GCDAlgorithms.FindGCDEuclid(firstNumber, secondNumber))

3.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

4.

Run the GreatestCommonDivisor application. On the Debug menu, click Start Debugging.

5. 6.

In the GreatestCommonDivisor application, in the MainWindow window, in the first text box, type 2806. In the second text box, type 345 and then click Find GCD (2 Integers). The result of 23 should be displayed, as the following screen shot shows.

7.

Use the window to calculate the GCD for the values that are specified in the following table, and verify that the results that are displayed match those in the table. Second number 0 10 10 100 100 Result 0 10 5 25 2

First number 0 0 25 25 26

L3-4

Lab: Declaring and Calling Methods

First number 27 8.

Second number 100

Result 1

Close the GreatestCommonDivisor application. On the Debug menu, click Stop Debugging.

Task 4: Create a unit test for the FindGCDEuclid method.


1. Open the GCDAlgorithms.vb file. 2. In Solution Explorer, double-click GCDAlgorithms.vb.

In the GCDAlgorithms class, create a unit test for the FindGCDEuclid method. Create a new Test Project named GCD Test Project project to hold the unit test. a. b. In the GCDAlgorithms class, right-click the FindGCDEuclid method, and then click Create Unit Tests. In the Create Unit Tests dialog box, ensure that the FindGCDEuclid(System.Int32, System.Int32) check box is selected, ensure that the Output project is set to Create a new Visual Basic test project, and then click OK. In the New Test Project dialog box, in the Enter a name for your new project, type GCD Test Project and then click Create.

c. 3.

In the GCD Test Project project, in the GCDAlgorithmsTest.vb file, locate the FindGCDEuclidTest method. In the Code Editor window, in the GCDAlgorithmsTest class, locate the FindGCDEuclidTest method.

4.

In the FindGCDEuclidTest method, set the a variable to 2806, set the b variable to 345, set the expected variable to 23, and then remove the Assert.Inconclusive method call.

Your code should resemble the following code.


... <TestMethod()> _ Public Sub FindGCDEuclidTest() Dim a As Integer = 2806 ' TODO: Initialize to an appropriate value Dim b As Integer = 345 ' TODO: Initialize to an appropriate value Dim expected As Integer = 23 ' TODO: Initialize to an appropriate value Dim actual As Integer actual = GCDAlgorithms.FindGCDEuclid(a, b) Assert.AreEqual(expected, actual) End Sub ...

5.

Open the Test View window and refresh the display if the unit test is not listed. a. On the Test menu, point to Windows, and then click Test View.

Note: If the You have made changes to your tests dialog box appears, click OK. b. 6. In the Test View window, click the Refresh button.

Run the FindGCDEuclidTest test and verify that the test ran successfully.

Lab: Declaring and Calling Methods

L3-5

a. b.

In the Test View window, right-click FindGCDEuclidTest, and then click Run Selection. In the Test Results window, verify that the FindGCDEuclidTest test passed.

Exercise 2: Calculating the GCD of Three, Four, or Five Integers


Task 1: Open the starter project.
Open the Euclid solution in the D:\Labfiles\Lab03\Ex2\Starter folder. This solution contains a revised copy of the code from Exercise 1. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, move to the D:\Labfiles\Lab03\Ex2 \Starter folder, click Euclid.sln, and then click Open.

Note: If the You have made changes to your tests dialog box appears, click OK.

Task 2: Add overloaded methods to the GCDAlgorithms class.


1. In Visual Studio, review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

Use the Task List window to navigate to the TODO: Exercise 2, Task 2 task. In the Task List window, double-click TODO: Exercise 2, Task 2.

3.

In the GCDAlgorithms class, remove the TODO: Exercise 2, Task 2 comment, and then declare an overloaded version of the FindGCDEuclid method. The method should accept three integer parameters named a, b, and c and return an integer value.

Your code should resemble the following code.


... Public Class GCDAlgorithms ... Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer End Function End Class ...

4.

In the new method, add code that uses the original FindGCDEuclid method to find the GCD for the parameters a and b. Store the result in a new variable named d.

Your code should resemble the following code.


... Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer Dim d As Integer = FindGCDEuclid(a, b) End Function ...

L3-6

Lab: Declaring and Calling Methods

5.

Add a second call to the original FindGCDEuclid method to find the GCD for variable d and parameter c. Store the result in a new variable named e.

Your code should resemble the following code.


... Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer Dim d As Integer = FindGCDEuclid(a, b) Dim e As Integer = FindGCDEuclid(d, c) End Function ...

6.

Add code to return the parameter e from the FindGCDEuclid method.

Your code should resemble the following code.


... Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer Dim d As Integer = FindGCDEuclid(a, b) Dim e As Integer = FindGCDEuclid(d, c) Return e End Function ...

7.

Declare another overloaded version of the FindGCDEuclid method. The method should accept four integer parameters named a, b, c, and d and return an integer value. Use the other FindGCDEuclid method overloads to find the GCD of these parameters and return the result.

Your code should resemble the following code.


... Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer, ByVal d As Integer) As Integer Dim e As Integer = FindGCDEuclid(a, b, c) Dim f As Integer = FindGCDEuclid(e, d) Return f End Function ...

8.

Declare another overloaded version of the FindGCDEuclid method. The method should accept five integer parameters named a, b, c, d, and e, and return an integer value. Use the other FindGCDEuclid method overloads to find the GCD of these parameters and return the result.

Your code should resemble the following code.


... Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer, ByVal d As Integer, ByVal e As Integer) As Integer Dim f As Integer = FindGCDEuclid(a, b, c, d) Dim g As Integer = FindGCDEuclid(f, e) Return g End Function ...

At the end of this task, the GCDAlgorithms class should resemble the following code example.

Lab: Declaring and Calling Methods

L3-7

... Public Class GCDAlgorithms ... Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer) As Integer Dim d As Integer = FindGCDEuclid(a, b) Dim e As Integer = FindGCDEuclid(d, c) Return e End Function Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer, ByVal d As Integer) As Integer Dim e As Integer = FindGCDEuclid(a, b, c) Dim f As Integer = FindGCDEuclid(e, d) Return f End Function Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer, ByVal c As Integer, ByVal d As Integer, ByVal e As Integer) As Integer Dim f As Integer = FindGCDEuclid(a, b, c, d) Dim g As Integer = FindGCDEuclid(f, e) Return g End Function ...

Task 3: Test the overloaded methods.


1. Use the Task List window to navigate to the TODO: Exercise 2, Task 3 task. This task is located in the code for the WPF window that you can use to test your code. 2. In the Task List window, double-click TODO: Exercise 2, Task 3.

Remove the TODO: Exercise 2, Task 3 comment, locate the ElseIf sender Is FindGCD3Button Then block and modify the statement that sets the Content property of the ResultEuclidLabel control to "N/A" as follows. a. b. Call the FindGCDEuclid overload that accepts three parameters and pass the variables firstNumber, secondNumber, and thirdNumber as arguments. Display the results in the ResultEuclidLabel label control. The result should be formatted as the following code example shows.

Euclid: result

Your code should resemble the following code.


... ElseIf sender Is FindGCD3Button Then ' Euclid for three integers Me.ResultEuclidLabel.Content = String.Format("Euclid: {0}", GCDAlgorithms.FindGCDEuclid( firstNumber, secondNumber, thirdNumber)) Me.ResultSteinLabel.Content = "N/A" ...

L3-8

Lab: Declaring and Calling Methods

3.

Locate the ElseIf sender Is FindGCD3Button Then block, the ElseIf sender Is FindGCD4Button Then block, and the ElseIf sender Is FindGCD5Button Then block, and modify the statements that set the Content property of the ResultEuclidLabel control to "N/A". Call the appropriate FindGCDEuclid overload by using the firstNumber, secondNumber, thirdNumber, fourthNumber, and fifthNumber variables as arguments. Display the results in the ResultEuclidLabel label control.

Your code should resemble the following code.


... Private Sub FindGCDButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) ... ' Call the overloaded methods for 3, 4 and 5 integers ElseIf sender Is FindGCD3Button Then ' Euclid for three integers Me.ResultEuclidLabel.Content = String.Format("Euclid: {0}", GCDAlgorithms.FindGCDEuclid( firstNumber, secondNumber, thirdNumber)) Me.ResultSteinLabel.Content = "N/A" ElseIf sender Is FindGCD4Button Then ' Euclid for four integers Me.ResultEuclidLabel.Content = String.Format("Euclid: {0}", GCDAlgorithms.FindGCDEuclid( firstNumber, secondNumber, thirdNumber, fourthNumber)) Me.ResultSteinLabel.Content = "N/A" ElseIf sender Is FindGCD5Button Then ' Euclid for five integers Me.ResultEuclidLabel.Content = String.Format("Euclid: {0}", GCDAlgorithms.FindGCDEuclid(firstNumber, secondNumber, thirdNumber, fourthNumber, fifthNumber)) Me.ResultSteinLabel.Content = "N/A" End If End Sub ...

4.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

5.

Run the GreatestCommonDivisor application. Press Ctrl+F5.

6.

In the GreatestCommonDivisor application, in the MainWindow window, type the values 7396, 1978, 1204, 430, and 258 and then click Find GCD (5 Integers).

Verify that the result 86 is displayed. 7. First Number 2806 0 0 Use the window to calculate the GCD for the values that are specified in the following table and verify that the results that are displayed match those in the table. Second Number 345 0 0 Third Number 0 0 0 Fourth Number 0 0 0

Fifth Number 0 0 1

Result 23 0 1

Lab: Declaring and Calling Methods

L3-9

First Number 12 13 14 15 16 0

Second Number 24 24 24 24 24 24

Third Number 36 36 36 36 36 36

Fourth Number 48 48 48 48 48 48

Fifth Number 60 60 60 60 60 60

Result 12 1 2 3 4 12

8.

Close the GreatestCommonDivisor application. In the MainWindow window, click the Close button.

Task 4: Create unit tests for the overloaded methods.


1. In Visual Studio, review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

Use the Task List window to navigate to the TODO: Exercise 2, Task 4 task. In the Task List window, double-click TODO: Exercise 2, Task 4.

3.

Remove the TODO: Exercise 2, Task 4 comment and add a test method named FindGCDEuclidTest1.

Your code should resemble the following code.


... <TestMethod()> Public Sub FindGCDEuclidTest1() End Sub ...

4.

In the FindGCDEuclidTest1 method, declare four variables named a, b, c, and expected and assign them values 7396, 1978, 1204, and 86, respectively.

Your code should resemble the following code.


<TestMethod()> Public Sub FindGCDEuclidTest1() Dim a As Integer = 7396 Dim b As Integer = 1978 Dim c As Integer = 1204 Dim expected As Integer = 86 End Sub

5.

Declare a variable named actual, and assign it the result of a call to the FindGCDEuclid method call. Use the variables a, b, and c as arguments.

L3-10

Lab: Declaring and Calling Methods

Your code should resemble the following code.


<TestMethod()> Public Sub FindGCDEuclidTest1() Dim a As Integer = 7396 Dim b As Integer = 1978 Dim c As Integer = 1204 Dim expected As Integer = 86 Dim actual As Integer = GCDAlgorithms.FindGCDEuclid(a, b, c) End Sub

6.

Call the shared AreEqual method of the Assert class, and pass the expected and actual variables as arguments.

Your code should resemble the following code.


<TestMethod()> Public Sub FindGCDEuclidTest1() Dim a As Integer = 7396 Dim b As Integer = 1978 Dim c As Integer = 1204 Dim expected As Integer = 86 Dim actual As Integer = GCDAlgorithms.FindGCDEuclid(a, b, c) Assert.AreEqual(expected, actual) End Sub

7.

Repeat steps 46 to create two more test methods to test the other FindGCDEuclid method overloads. Create test methods named FindGCDEuclidTest2 and FindGCDEuclidTest3. Use the values 7396, 1978, 1204, and 430 for the FindGCDEuclidTest2 method, and the values 7396, 1978, 1204, 430, and 258 for the FindGCDEuclidTest3 method. The result should be 86 in both cases.

Your code should resemble the following code.


... <TestClass()> _ Public Class GCDAlgorithmsTest ... <TestMethod()> Public Sub FindGCDEuclidTest1() Dim a As Integer = 7396 Dim b As Integer = 1978 Dim c As Integer = 1204 Dim expected As Integer = 86 Dim actual As Integer = GCDAlgorithms.FindGCDEuclid(a, b, c) Assert.AreEqual(expected, actual) End Sub <TestMethod()> Public Sub FindGCDEuclidTest2() Dim a As Integer = 7396 Dim b As Integer = 1978 Dim c As Integer = 1204 Dim d As Integer = 430 Dim expected As Integer = 86 Dim actual As Integer = GCDAlgorithms.FindGCDEuclid(a, b, c, d) Assert.AreEqual(expected, actual) End Sub <TestMethod()> Public Sub FindGCDEuclidTest3() Dim a As Integer = 7396

Lab: Declaring and Calling Methods

L3-11

Dim b As Integer = 1978 Dim c As Integer = 1204 Dim d As Integer = 430 Dim e As Integer = 258 Dim expected As Integer = 86 Dim actual As Integer = GCDAlgorithms.FindGCDEuclid(a, b, c, d, e) Assert.AreEqual(expected, actual) End Sub End Class

8.

Open the Test View window and refresh the display if the unit test is not listed. a. b. c. On the Test menu, point to Windows, and then click Test View. If the You have made changes to your tests dialog box appears, click OK. In the Test View window, click the Refresh button.

9.

Run the FindGCDEuclidTest, FindGCDEuclidTest1, FindGCDEuclidTest2, and FindGCDEuclidTest3 tests and verify that the tests ran successfully. a. b. In the Test View window, select the FindGCDEuclidTest, FindGCDEuclidTest1, FindGCDEuclidTest2, and FindGCDEuclidTest3 tests, right-click, and then click Run Selection. In the Test Results window, verify that the tests passed.

Exercise 3: Comparing the Efficiency of Two Algorithms


Task 1: Open the starter project.
Open the Stein solution in the D:\Labfiles\Lab03\Ex3\Starter folder. This solution contains a revised copy of the code from Exercise 2. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, move to the D:\Labfiles\Lab03\Ex3 \Starter folder, click Stein.sln, and then click Open.

Note: If the You have made changes to your tests dialog box appears, click OK.

Task 2: Implement Steins algorithm.


1. Open the GCDAlgorithms.vb file. 2. In Solution Explorer, double-click GCDAlgorithms.vb.

At the end of the GCDAlgorithms class, remove the TODO: comment and declare a shared Public method named FindGCDStein. The method should accept two integer parameters named u and v and return an integer value.

Your code should resemble the following code.


... Public Class GCDAlgorithms ... Public Shared Function FindGCDStein(ByVal u As Integer, ByVal v As Integer) As Integer End Function End Class

L3-12

Lab: Declaring and Calling Methods

3.

In the FindGCDStein method, add the code in the following code example, which calculates and returns the GCD of the values that are specified by the parameters u and v by using Stein's algorithm. You can either type this code manually, or use the Mod03Stein code snippet. To use the Mod03Stein snippet, add a blank line, type Mod03Stein, and then press TAB.

Note: For the purposes of this exercise, it is not necessary for you to understand this code. However, if you have time, you may like to compare this method with the algorithm that is described in the exercise scenario. Note that this code uses the left-shift (<<) and right-shift (>>) operators to perform fast multiplication and division by 2. If you left-shift an integer value by one place, the result is the same as multiplying the integer value by 2. Similarly, if you right-shift an integer value by one place, the result is the same as dividing the integer value by 2. In addition, the Or operator performs a bitwise OR operation between two integer values. Consequently, if either u or v is zero, the expression u And v is a fast way of returning the value of whichever variable is nonzero, or zero if both are zero. Similarly, the And operator performs a bitwise AND operation, so the expression u And 1 is a fast way to determine whether the value of u is odd or even.
Public Shared Function FindGCDStein(ByVal u As Integer, ByVal v As Integer) As Integer Dim k As Integer = 0 ' Step 1. ' gcd(0, v) = v, because everything divides zero, ' and v is the largest number that divides v. ' Similarly, gcd(u, 0) = u. gcd(0, 0) is not typically ' defined, but it is convenient to set gcd(0, 0) = 0. If u = 0 OrElse v = 0 Then Return u Or v End If ' Step 2. ' If u and v are both even, then gcd(u, v) = 2gcd(u/2, v/2), ' because 2 is a common divisor. Do While ((u Or v) And 1) = 0 u >>= 1 v >>= 1 k += 1 Loop ' Step 3. ' If u is even and v is odd, then gcd(u, v) = gcd(u/2, v), ' because 2 is not a common divisor. ' Similarly, if u is odd and v is even, ' then gcd(u, v) = gcd(u, v/2). While (u And 1) = 0 u >>= 1 End While ' ' ' ' ' ' ' ' ' ' Step 4. If u and v are both odd, and u v, then gcd(u, v) = gcd((u v)/2, v). If both are odd and u < v, then gcd(u, v) = gcd((v u)/2, u). These are combinations of one step of the simple Euclidean algorithm, which uses subtraction at each step, and an application of step 3 above. The division by 2 results in an integer because the difference of two odd numbers is even.

Lab: Declaring and Calling Methods

L3-13

Do

While (v And 1) = 0 v >>= 1 End While

' Loop x

' Now u and v are both odd, so diff(u, v) is even. ' Let u = min(u, v), v = diff(u, v)/2. If (u < v) Then v -= u Else Dim diff As Integer = u - v u = v v = diff End If v >>= 1 ' Step 5. ' Repeat steps 34 until u = v, or (one more step) ' until u = 0. ' In either case, the result is (2^k) * v, where k is ' the number of common factors of 2 found in step 2. Loop While v <> 0 u <<= k Return u End Function

Task 3: Test the FindGCDStein method.


1. Open the MainWindow.xaml.vb file. 2. In Solution Explorer, expand MainWindow.xaml, and then double-click MainWindow.xaml.vb.

In the MainWindow class, in the FindGCDButton_Click method, locate the TODO: Exercise 3, Task 2 comment. Remove this comment and replace the statement that sets the Content property of the ResultSteinLabel control with code that calls the FindGCDStein method by using the variables firstNumber and secondNumber as arguments. Display the results in the ResultSteinLabel control. The result should be formatted as the following code example shows.

Stein: result

Your code should resemble the following code.


If sender Is FindGCDButton Then ' Euclid for two integers Me.ResultEuclidLabel.Content = String.Format("Euclid: {0}", GCDAlgorithms.FindGCDEuclid(firstNumber, secondNumber)) ' Call the method that implements Stein's algorithm and display the results Me.ResultSteinLabel.Content = String.Format("Stein: {0}", GCDAlgorithms.FindGCDStein(firstNumber, secondNumber)) ...

3.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

4.

Run the GreatestCommonDivisor application. Press Ctrl+F5.

5.

In the GreatestCommonDivisor application, in the MainWindow window, in the first two boxes, type the values 298467352 and 569484 and then click Find GCD (2 Integers).

L3-14

Lab: Declaring and Calling Methods

Verify that the value 4 is displayed in both labels. 6. Close the GreatestCommonDivisor application. 7. In the MainWindow window, click the Close button.

Open the GCDAlgorithmsTest.vb file. In Solution Explorer, double-click GCDAlgorithmsTest.vb.

8.

At the end of the GCDAlgorithmsTest class, locate the TODO: Exercise 3, Task 2 comment, remove the comment, and then add a test method named FindGCDSteinTest.

Your code should resemble the following code.


... <TestMethod()> Public Sub FindGCDSteinTest() End Sub ...

9.

In the FindGCDSteinTest method, declare three variables named u, v, and expected and assign them values 298467352, 569484, and 4, respectively.

Your code should resemble the following code.


<TestMethod()> Public Sub FindGCDSteinTest() Dim u As Integer = 298467352 Dim v As Integer = 569484 Dim expected As Integer = 4 End Sub

10. Declare a variable named actual and assign it the result of a call to the FindGCDStein method call. Use the variables u and v as arguments. Your code should resemble the following code.
<TestMethod()> Public Sub FindGCDSteinTest() Dim u As Integer = 298467352 Dim v As Integer = 569484 Dim expected As Integer = 4 Dim actual As Integer = GCDAlgorithms.FindGCDStein(u, v) End Sub

11. Call the shared AreEqual method of the Assert class, and pass the expected and actual variables as arguments. Your code should resemble the following code.
<TestMethod()> Public Sub FindGCDSteinTest() Dim u As Integer = 298467352 Dim v As Integer = 569484 Dim expected As Integer = 4 Dim actual As Integer = GCDAlgorithms.FindGCDStein(u, v) Assert.AreEqual(expected, actual) End Sub

12. Open the Test View window and refresh the display if the unit test is not listed.

Lab: Declaring and Calling Methods

L3-15

a. b. c.

On the Test menu, point to Windows, and then click Test View. If the You have made changes to your tests dialog box appears, click OK. In the Test View window, click the Refresh button.

13. Run the FindGCDSteinTest test and verify that the test ran successfully. a. b. In the Test View window, right-click the FindGCDSteinTest test, and then click Run Selection. In the Test Results window, verify that the test passed.

Task 4: Add code to test the performance of the algorithms.


1. Open the GCDAlgorithms.vb file. 2. In Solution Explorer, double-click GCDAlgorithms.vb.

In the GCDAlgorithms class, locate the FindGCDEuclid method that accepts two parameters and modify the method signature to take a reference parameter named time of type Long.

Your code should resemble the following code.


... Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer, ByRef time As Long) As Integer ... End Function ...

3.

At the start of the FindGCDEuclid method, add code to initialize the time parameter to zero, create a new Stopwatch object named sw and start the stop watch.

The Stopwatch class is useful for timing code. The Start method starts an internal timer running. You can subsequently use the Stop method to halt the timer, and establish how long the interval was between starting and stopping the timer by querying the ElapsedMilliseconds or ElapsedTicks properties. Your code should resemble the following code.
... Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer, ByRef time As Long) As Integer time = 0 Dim sw As New Stopwatch() sw.Start() ... End Function ...

4.

At the end of the FindGCDEuclid method, before the Return statement, add code to stop the Stopwatch object, and set the time parameter to the number of elapsed ticks of the Stopwatch object.

Your code should resemble the following code.


... Public Shared Function FindGCDEuclid(ByVal a As Integer, ByVal b As Integer, ByRef time As Long) As Integer time = 0 Dim sw As New Stopwatch() sw.Start() ... sw.Stop() time = sw.ElapsedTicks

L3-16

Lab: Declaring and Calling Methods

Return a; End Function ...

5. 6.

Comment out the other FindGCDEuclid method overloads. Modify the FindGCDStein method to include the time reference parameter and add code to record the time each method takes to run. Note that the FindGCDStein method contains two Return statements, and you should record the time before each one.

Your code should resemble the following code.


... Public Shared FindGCDStein(ByVal u As Integer, ByVal v As Integer, ByRef time As Long) As Integer time = 0 Dim sw = new Stopwatch() sw.Start() Dim k As Integer = 0 ' Step 1. ' gcd(0, v) = v, because everything divides zero, and ' v is the largest number that divides v. ' Similarly, gcd(u, 0) = u. gcd(0, 0) is not typically ' defined, but it is convenient to set gcd(0, 0) = 0. If u = 0 OrElse v = 0 Then sw.Stop() time = sw.ElapsedTicks return u Or v End If ... sw.Stop() time = sw.ElapsedTicks Return u End Function ...

7.

Open the MainWindow.xaml.vb file. In Solution Explorer, expand MainWindow.xaml, and then double-click MainWindow.xaml.vb.

8.

In the FindGCDButton_Click method, modify each of the calls to the FindGCDEuclid method and the FindGCDStein method to use the updated method signatures, as follows. a. b. c. For calling the Euclid algorithm, create a variable named timeEuclid of type Long. For calling the Stein algorithm, create a variable named timeStein of type Long Format the results displayed in the labels as the following code example shows.

[Euclid] Euclid: result, Time (ticks): result [Stein] Stein: result, Time (ticks): result

Your code should resemble the following code.


... If sender Is FindGCDButton Then ' Euclid for two integers and graph Dim timeEuclid As Long

Lab: Declaring and Calling Methods

L3-17

Dim timeStein As Long Me.ResultEuclidLabel.Content = String.Format("Euclid: {0}, Time (ticks): {1}", GCDAlgorithms.FindGCDEuclid(firstNumber, secondNumber, timeEuclid), timeEuclid) Me.ResultSteinLabel.Content = String.Format("Stein: {0}, Time (ticks): {1}", GCDAlgorithms.FindGCDStein(firstNumber, secondNumber, timeStein), timeStein)

...

9.

Comment out the code that calls the overloaded versions of the FindGCDEuclid method.

Your code should resemble the following code.


... ElseIf sender Is FindGCD3Button Then ' Euclid for three integers 'Me.ResultEuclidLabel.Content = ' String.Format("Euclid: {0}", ' GCDAlgorithms.FindGCDEuclid( ' firstNumber, ' secondNumber, ' thirdNumber)) Me.ResultSteinLabel.Content = "N/A" ElseIf sender Is FindGCD4Button Then ' Euclid for four integers 'Me.ResultEuclidLabel.Content = ' String.Format("Euclid: {0}", ' GCDAlgorithms.FindGCDEuclid( ' firstNumber, secondNumber, thirdNumber, fourthNumber)) Me.ResultSteinLabel.Content = "N/A" ElseIf sender Is FindGCD5Button Then ' Euclid for five integers 'Me.ResultEuclidLabel.Content = ' String.Format("Euclid: {0}", ' GCDAlgorithms.FindGCDEuclid(firstNumber, secondNumber, ' thirdNumber, fourthNumber, fifthNumber)) Me.ResultSteinLabel.Content = "N/A" End If ...

10. Open the GCDAlgorithmsTest.vb file. In Solution Explorer, double-click GCDAlgorithmsTest.vb.

11. Modify the FindGCDEuclidTest and FindGCDSteinTest methods to use the new method signatures. Comment out the methods FindGCDEuclidTest1, FindGCDEuclidTest2, and FindGCDEuclidTest3. Your code should resemble the following code.
<TestClass()> _ Public Class GCDAlgorithmsTest ... <TestMethod()> _ Public Sub FindGCDEuclidTest() Dim a As Integer = 2806 Dim b As Integer = 345 Dim time As Long Dim expected As Integer = 23 Dim actual As Integer actual = GCDAlgorithms.FindGCDEuclid(a, b, time) Assert.AreEqual(expected, actual) End Sub ...

L3-18

Lab: Declaring and Calling Methods

<TestMethod()> Public Sub FindGCDSteinTest() Dim u As Integer = 298467352 Dim v As Integer = 569484 Dim time As Long Dim expected As Integer = 4 Dim actual As Integer = GCDAlgorithms.FindGCDStein(u, v, time) Assert.AreEqual(expected, actual) End Sub End Class

12. Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

13. Run the GreatestCommonDivisor application. Press Ctrl+F5.

14. In the GreatestCommonDivisor application, in the MainWindow window, in the first two boxes, type the values 298467352 and 569484, and then click Find GCD (2 Integers). The result of 4 should be displayed. The time reported for Euclid's algorithm should be at least three times more than that for Stein's algorithm.

Note: The bigger the difference between the two values, the more efficient Stein's algorithm becomes compared with Euclid's. If you have time, try experimenting with different values. 15. Close the GreatestCommonDivisor application. In the MainWindow window, click the Close button.

16. Open the Test View window and refresh the display if the unit test is not listed. a. b. On the Test menu, point to Windows, and then click Test View. In the Test View window, click the Refresh button.

17. Run the FindGCDEuclidTest and FindGCDSteinTest methods and verify that the tests ran successfully. a. b. In the Test View window, select the FindGCDEuclidTest and FindGCDSteinTest tests, right-click, and then click Run Selection. In the Test Results window, verify that the tests passed.

Exercise 4: Displaying Results Graphically


Task 1: Open the starter project.
Open the Charting solution in the D:\Labfiles\Lab03\Ex4\Starter folder. This solution contains a revised copy of the code from Exercise 3. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, move to the D:\Labfiles\Lab03\Ex4 \Starter folder, click Charting.sln, and then click Open.

Task 2: Display the algorithm timings graphically.


1. Open the MainWindow.xaml.vb file.

Lab: Declaring and Calling Methods

L3-19

2.

In Solution Explorer, expand MainWindow.xaml, and then double-click MainWindow.xaml.vb.

In the FindGCDButton_Click method, locate the Call DrawGraph comment and add a call to the DrawGraph method by using the timeEuclid and timeStein variables as parameters.

Your code should resemble the following code.


... If sender Is FindGCDButton Then ' Euclid and Stein for two integers and graph ... DrawGraph(timeEuclid, timeStein) ...

3.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

4.

Run the GreatestCommonDivisor application. Press Ctrl+F5.

5.

In the GreatestCommonDivisor application, in the MainWindow window, in the first two boxes, type the values 298467352 and 569484 and then click Find GCD (2 Integers). The result of 4 should be displayed. The time reported for both algorithms should be represented by a simple bar graph in the window. Close the GreatestCommonDivisor application. In the MainWindow window, click the Close button.

6.

Task 3: Modify the DrawGraph method.


1. In the MainWindow class, locate the DrawGraph method and add the following three optional parameters. a. b. c. A parameter named orientation of type Orientation with a default value of Orientation.Horizontal. A parameter named colorEuclid of type String with a default value of Red. A parameter named colorStein of type String with a default value of Blue.

Your code should resemble the following code.


... Private Sub DrawGraph(ByVal euclidTime As Long, ByVal steinTime As Long, Optional ByVal orient As Orientation = Orientation.Horizontal, Optional ByVal colorEuclid As String = "Red", Optional ByVal colorStein As String = "Blue") ... End Sub ...

2. 3.

In the DrawGraph method, locate the Use optional orientation parameter comment, and remove the existing declaration of the orient variable. Locate the Use optional color parameters comment, and modify the assignment of the bEuclid and bStein variables to use the optional parameters in the method signature. To do this, you will need to use the BrushConverter class and the ConvertFromString instance method as shown in the following code example.

... Private Sub DrawGraph(ByVal euclidTime As Long, ByVal steinTime As Long, Optional ByVal orient As Orientation = Orientation.Horizontal,

L3-20

Lab: Declaring and Calling Methods

Optional ByVal colorEuclid As String = "Red", Optional ByVal colorStein As String = "Blue") ... Dim bc = New BrushConverter() Dim bEuclid As Brush = CType(bc.ConvertFromString(colorEuclid), Brush) Dim bStein As Brush = CType(bc.ConvertFromString(colorStein), Brush) ... End Sub ...

4.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

5.

Run the GreatestCommonDivisor application. Press Ctrl+F5.

6.

In the GreatestCommonDivisor application, in the MainWindow window, in the first two boxes, type the values 298467352 and 569484 and then click Find GCD (2 Integers). The graph should be displayed as before, except the DrawGraph method call is now using the default parameter values, and the graph is displayed as a pair of red and blue vertical bars. Close the GreatestCommonDivisor application. In the MainWindow window, click the Close button.

7.

Task 4: Modify the code that calls the DrawGraph method.


1. In the FindGCDButton_Click method, locate the Modify the call to Drawgraph to use the optional parameters comment, and modify the DrawGraph method call to use the orient, colorEuclid, and colorStein optional parameters as follows. a. b. c. orientset to the selected value of the ChartOrientationListBox control. colorEuclidset to the selected item of the EuclidColorListBox control. colorSteinset to the selected item of the SteinColorListBox control.

These list boxes are already included in the user interface; they appear in the lower part of the window. The user can select the values in these list boxes to change the appearance of the graph that is displayed. Your code should resemble the following code.
... If sender Is FindGCDButton Then ' Euclid for two integers and graph ... ' Get the preferred colors and orientation Dim selectedEuclidColor As String = CType(Me.EuclidColorListBox.SelectedItem, ListBoxItem).Content.ToString() Dim selectedSteinColor As String = CType(Me.SteinColorListBox.SelectedItem, ListBoxItem).Content.ToString() Dim orient As Orientation If Me.ChartOrientationListBox.SelectedIndex = 0 Then orient = Orientation.Vertical Else orient = Orientation.Horizontal End If

Lab: Declaring and Calling Methods

L3-21

...

DrawGraph(timeEuclid, timeStein, orient:=orient, colorStein:=selectedSteinColor, colorEuclid:=selectedEuclidColor)

2.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

3.

Run the GreatestCommonDivisor application. Press Ctrl+F5.

4. 5.

In the GreatestCommonDivisor application, in the MainWindow window, in the first two boxes, type the values 298467352 and 569484. In the Euclid list, select Green, in the Stein list, select Black, in the Orientation list, select Horizontal, and then click Find GCD (2 Integers). The graph should be displayed with the specified colors and direction. Close the GreatestCommonDivisor application. In the MainWindow window, click the Close button.

6.

Exercise 5: Solving Simultaneous Equations (optional)


Task 1: Open the starter project.
1. Open the SimultaneousEquations solution in the D:\Labfiles\Lab03\Ex5 \Starter folder. a. b. 2. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab03\Ex5 \Starter folder, click SimultaneousEquations.sln, and then click Open.

Open the MainWindow.xaml file. In Solution Explorer, expand the GaussianElimination project, and then double-click MainWindow.xaml.

This is a different application from the one that the previous exercises have used. It is a WPF application that enables a user to enter the coefficients for four simultaneous equations that contain four variables (w, x, y, and z), and then uses Gaussian Elimination to find a solution for these equations. The results are displayed in the lower part of the screen.

Task 2: Create methods to copy arrays.


1. Open the Gauss.vb file. In Solution Explorer, double-click Gauss.vb.

This file contains a class named Gauss that provides a method named SolveGaussian. This method takes two arrays as parameters. A two-dimensional array of Double values containing the coefficients for the variables w, x, y, and z specified by the user for each equation. An array of Double values containing the result of each equation specified by the user (the value to the right of the equal sign).

L3-22

Lab: Declaring and Calling Methods

The method returns an array of Double values that will be populated with the values of w, x, y, and z that provide the solutions to these equations. You will implement the body of this method in this exercise. 2. In the Gauss class, locate the TODO: Exercise 5, Task 2 comment. Remove this comment and declare a shared Private method named DeepCopy1D. The method should accept and return a Double array.

The SolveGaussian method will make a copy of the arrays passed in as parameters to avoid changing the original data that the user provided. Your code should resemble the following code.
... Private Shared Function DeepCopy1D(ByVal arr() as Double) As Double() End Function ...

3.

In the DeepCopy1D method, add code to create a deep copy of the one-dimensional array that was passed into the method. Your code should perform the following tasks. a. b. c. Create and initialize an array with the same number of columns as the array that was passed in. Copy the values in the array that was passed as a parameter into the new array. Return the new array.

Your code should resemble the following code.


... Private Shared Function DeepCopy1D(ByVal arr() as Double) As Double() ' Get dimensions Dim columns As Integer = arr.GetLength(0) ' Initialize a new array Dim newArray() As Double ReDim newArray(columns) ' Copy the values For I As Integer = 0 To columns - 1 newArray(i) = arr(i) Next Return newArray End Function ...

4.

In the Gauss class, declare another shared Private method named DeepCopy2D. The method should accept and return a two-dimensional Double array.

Your code should resemble the following code.


... Private Shared Function DeepCopy2D(ByVal arr(,) As Double) As double(,) End Function ...

5.

In the DeepCopy2D method, add code to create a deep copy of the two-dimensional array that was passed into the method. Your code should do the following.

Lab: Declaring and Calling Methods

L3-23

a. b. c.

Create and initialize an array with the same number of columns and rows as the array that was passed in. Copy the values in the array that was passed in as the parameter into the new array. Return the new array.

Your code should resemble the following code.


... Private Shared Function DeepCopy2D(ByVal arr(,) As Double) As double(,) ' Get dimensions Dim columns As Integer = arr.GetLength(0) Dim rows As Integer = arr.GetLength(1) ' Initialize a new array Dim newArray(,) As Double ReDim newArray(columns, rows) ' Copy the values For i As Integer = 0 To columns -1 For j As Integer = 0 To rows -1 newArray(i, j) = arr(i, j) Next Next Return newArray End Function ...

Task 3: Convert the equations to triangular form.


1. In the SolveGaussian method, use the DeepCopy1D and DeepCopy2D methods to create deep copies of the rhs and coefficients arrays.

Your code should resemble the following code.


... Public Shared Function SolveGaussian(ByVal coefficients(,) As Double, ByVal rhs(,) As Double) As Double() ' TODO: Exercise 5, Task 3 ' Make deep copies of the coefficients and rhs arrays Dim a(,) As Double = DeepCopy2D(coefficients) Dim b() As Double = DeepCopy1D(rhs) ... End Function ...

2.

Locate the Convert the equation to triangular form comment and add code to convert the equations represented by the copies of the coefficients and rhs arrays into triangular form.

Note: The Gauss class defines a constant integer named numberOfEquations that specifies the number of coefficients that the application can resolve. Your code should resemble the following code.
... ' TODO: Exercise 5, Task 3 ' Convert the equations to triangular form

L3-24

Lab: Declaring and Calling Methods

Dim x, sum As Double For k As Integer = 0 to numberOfEquations Try For i As Integer = k + 1 To numberOfEquations -1 x = a(i, k) / a(k, k) For j As Integer = k + 1 To numberOfEquations -1 A(i, j) = a(i, j) a(k, j) * x Next b(i) = b(i) b(k) * x Next Catch e As DivideByZeroException Console.WriteLine(e.Message) End Try

Next ...

Task 4: Perform back substitution.


In the Gauss class, in the SolveGaussian method, locate the Perform the back substitution and return the result comment, and then add code to perform back substitution. To do this, you will need to work back from the equation with one unknown and substituting the values calculated at each stage to solve the remaining equations. Your code should resemble the following code.
... ' TODO: Exercise 5, Task 4 ' Perform the back substitution and return the result b(numberOfEquations 1) = b(numberOfEquations 1) / a(numberOfEquations - 1, numberOfEquations 1) For i As Integer = numberOfEquations 2 To 0 Step -1 sum = b(i) For j As Integer = i + 1 To numberOfEquations -1 sum = sum a(i, j) * b(j) Next Next b(i) = sum / a(i, i)

Return b ...

Task 5: Test the solution.


1. Open the MainWindow.xaml.vb file. 2. In Solution Explorer, expand MainWindow.xaml, and then double-click MainWindow.xaml.vb.

In the MainWindow class, locate the TODO: Exercise 5, Task 5 comment, and add code to call the SolveGaussion method. Use the coefficients and rhs variables as parameters and set the answers array to the result.

Your code should resemble the following code.


... Private Sub SolveButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)

Lab: Declaring and Calling Methods

L3-25

... ' TODO: Exercise 5, Task 5 ' Invoke the SolveGaussian method answers = Gauss.SolveGaussian(coefficients, rhs) ... End Sub ...

3.

Run the GaussianElimination application. Press Ctrl+F5.

4. w 2 -3 -2 3

In the MainWindow window, enter the following equations, and then click Solve. x 1 -1 1 -1 y -1 2 -2 2 z 1 1 0 -2 Result 8 -11 -3 -5

Verify that the following results are displayed: w=4 x = 17 y = 11 z=6 5. 6. Experiment with other equations. Note that not all systems of equations have a solution. How does your code handle this situation? Close the MainWindow window. 7. In the MainWindow window, click the Close button.

Close Visual Studio. In Visual Studio, on the File menu, click Exit.

L3-26

Lab: Declaring and Calling Methods

Lab: Handling Exceptions

L4-1

Module 4: Handling Exceptions

Lab: Handling Exceptions


Exercise 1: Making a Method Fail-Safe
Task 1: Open the Failsafe solution and run the application.
1. Open Microsoft Visual Studio 2010: 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Open the Failsafe solution in the D:\Labfiles\Lab04\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab04\Ex1\Starter folder, click Failsafe.sln, and then click Open.

3.

Set the SwitchTestHarness project as the startup project. In Solution Explorer, right-click SwitchTestHarness, and then click Set as StartUp Project.

4.

Run the Failsafe project and repeatedly click Shutdown until an exception occurs.

Note: The Switch class is designed to randomly throw an exception, so you may not encounter an exception the first time you click the button. Repeatedly click the Shutdown button until an exception occurs. a. b. c. d. On the Debug menu, click Start Debugging, or press F5. In the MainWindow window, click Shutdown. If an exception is thrown, examine the unhandled exception message that appears in Visual Studio. Otherwise repeat step b. In Visual Studio, on the Debug menu, click Stop Debugging.

Task 2: Examine the Switch class.


1. If it is not already open, open the Switch.vb file in Visual Studio. a. b. 2. In Solution Explorer, in the Failsafe solution, expand the SwitchDevice project. Right-click Switch.vb, and then click View Code.

Examine the Switch class. Note that the class contains several methods, each of which is capable of throwing at least one exception, dependent on the outcome of a random number generation. Toward the end of the file, note the definitions of each of the custom exceptions that the Switch class can throw. These are very basic exception classes that simply encapsulate an error message.

Task 3: Handle the exceptions that the Switch class throws.


The SwitchTestHarness project contains a reference to the SwitchDevice class and invokes each method in the Switch class to simulate polling multiple sensors and diagnostic devices. Currently, the project contains no exception handling, so when an exception occurs, the application will fail. You must add

L4-2

Lab: Handling Exceptions

exception-handling code to the SwitchTestHarness project, to protect the application from exceptions that the Switch class throws. 1. Open the MainWindow.xaml.vb file in Visual Studio. a. b. 2. 3. In Solution Explorer, expand the SwitchTestHarness project. Expand MainWindow.xaml, right-click MainWindow.xaml.vb, and then click View Code.

In the MainWindow class, locate the ShutDownButton_Click method. This method runs when the user clicks Shutdown. Remove the comment, TODO: - Add exception handling, and then locate the Step 1 - disconnect from the Power Generator and Step 2 - Verify the status of the Primary Coolant System comments. Enclose the code between these comments in a Try/Catch block that catches the SwitchDevices.PowerGeneratorCommsException exception. This is the exception that the DisconnectPowerGenerator method can throw. Your code should resemble the following code.
... ' Step 1 - disconnect from the Power Generator Try If sd.DisconnectPowerGenerator() = SwitchDevices.SuccessFailureResult.Fail Then Me.textBlock1.Text &= vbNewLine & "Step 1: Failed to disconnect power generation system" Else Me.textBlock1.Text &= vbNewLine & "Step 1: Successfully disconnected power generation system" End If Catch ex As SwitchDevices.PowerGeneratorCommsException End Try ' Step 2 - Verify the status of the Primary Coolant System ...

4.

In the Catch block, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 1:" and then the contents of the Message property of the exception. The Message property contains the error message that the Switch object specified when it threw the exception.

Hint: To append a line of text to a TextBlock control, use the &= operator on the Text property of the control. Your code should resemble the following code.
... Catch ex As SwitchDevices.PowerGeneratorCommsException Me.textBlock1.Text &= vbNewLine & "*** Exception in step 1: " & ex.Message End Try ...

5.

Enclose the code between the Step 2 - Verify the status of the Primary Coolant System and Step 3 - Verify the status of the Backup Coolant System comments in a Try/Catch block, which catches the SwitchDevices.CoolantPressureReadException and SwitchDevices.CoolantTemperatureReadException exceptions. In each exception handler,

Lab: Handling Exceptions

L4-3

following the same pattern as step 3, print a message on a new line in the textBlock1 control (note that this is step 2, not step 1 of the shutdown process). Your code should resemble the following code.
... ' Step 2 - Verify the status of the Primary Coolant System Try Select Case sd.VerifyPrimaryCoolantSystem() Case SwitchDevices.CoolantSystemStatus.OK Me.textBlock1.Text &= vbNewLine & "Step 2: Primary coolant system OK" Case SwitchDevices.CoolantSystemStatus.Check Me.textBlock1.Text &= vbNewLine & "Step 2: Primary coolant system requires manual check" Case SwitchDevices.CoolantSystemStatus.Fail Me.textBlock1.Text &= vbNewLine & "Step 2: Problem reported with primary coolant system" End Select Catch ex As SwitchDevices.CoolantPressureReadException Me.textBlock1.Text &= vbNewLine & "*** Exception in step 2: " & ex.Message Catch ex As SwitchDevices.CoolantTemperatureReadException Me.textBlock1.Text &= vbNewLine & "*** Exception in step 2: " & ex.Message End Try ' Step 3 - Verify the status of the Backup Coolant System ...

6.

Enclose the code between the Step 3 - Verify the status of the Backup Coolant System and Step 4 - Record the core temperature prior to shutting down the reactor comments in a Try/Catch block, which catches the SwitchDevices.CoolantPressureReadException and SwitchDevices.CoolantTemperatureReadException exceptions. In each exception handler, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 3:" and then the contents of the Message property of the exception. Your code should resemble the following code.
... ' Step 3 - Verify the status of the Backup Coolant System Try Select Case sd.VerifyBackupCoolantSystem() Case SwitchDevices.CoolantSystemStatus.OK Me.textBlock1.Text &= vbNewLine & "Step 3: Backup coolant system OK" Case SwitchDevices.CoolantSystemStatus.Check Me.textBlock1.Text &= vbNewLine & "Step 3: Backup coolant system requires manual check" Case SwitchDevices.CoolantSystemStatus.Fail Me.textBlock1.Text &= vbNewLine & "Step 3: Backup reported with primary coolant system" End Select Catch ex As SwitchDevices.CoolantPressureReadException Me.textBlock1.Text &= vbNewLine & "*** Exception in step 3: " & ex.Message Catch ex As SwitchDevices.CoolantTemperatureReadException Me.textBlock1.Text &= vbNewLine & "*** Exception in step 3: " & ex.Message End Try ' Step 4 - Record the core temperature prior to shutting down the reactor

L4-4

Lab: Handling Exceptions

...

7.

Enclose the code between the Step 4 - Record the core temperature prior to shutting down the reactor and Step 5 - Insert the control rods into the reactor comments in a Try/Catch block, which catches the SwitchDevices.CoreTemperatureReadException exception. In the exception handler, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 4:" and then the contents of the Message property of the exception. Your code should resemble the following code.
... ' Step 4 - Record the core temperature prior to shutting down the reactor Try Me.textBlock1.Text &= vbNewLine & "Step 4: Core temperature before shutdown: " & sd.GetCoreTemperature() Catch ex As SwitchDevices.CoreTemperatureReadException Me.textBlock1.Text &= vbNewLine & "*** Exception in step 4: " & ex.Message End Try ' Step 5 - Insert the control rods into the reactor ...

8.

Enclose the code between the Step 5 - Insert the control rods into the reactor and Step 6 Record the core temperature after shutting down the reactor comments in a Try/Catch block, which catches the SwitchDevices.RodClusterReleaseException exception. In the exception handler, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 5:" and then the contents of the Message property of the exception. Your code should resemble the following code.
... ' Step 5 - Insert the control rods into the reactor Try If sd.InsertRodCluster() = SwitchDevices.SuccessFailureResult.Success Then Me.textBlock1.Text &= vbNewLine & "Step 5: Control rods successfully inserted" Else Me.textBlock1.Text &= vbNewLine & "Step 5: Control rod insertion failed" End If Catch ex As SwitchDevices.RodClusterReleaseException Me.textBlock1.Text &= vbNewLine & "*** Exception in step 5: " & ex.Message End Try ' Step 6 - Record the core temperature after shutting down the reactor ...

9.

Enclose the code between the Step 6 - Record the core temperature after shutting down the reactor and Step 7 - Record the core radiation levels after shutting down the reactor comments in a Try/Catch block, which catches the SwitchDevices.CoreTemperatureReadException exception. In the exception handler, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 6:" and then the contents of the Message property of the exception. Your code should resemble the following code.
... ' Step 6 - Record the core temperature after shutting down the reactor Try

Lab: Handling Exceptions

L4-5

Me.textBlock1.Text &= vbNewLine & "Step 6: Core temperature after shutdown: " & sd.GetCoreTemperature() Catch ex As SwitchDevices.CoreTemperatureReadException Me.textBlock1.Text &= vbNewLine & "*** Exception in step 6: " & ex.Message End Try ' Step 7 - Record the core radiation levels after shutting down the reactor ...

10. Enclose the code between the Step 7 - Record the core radiation levels after shutting down the reactor and Step 8 - Broadcast "Shutdown Complete" message comments in a Try/Catch block, which catches the SwitchDevices.CoreRadiationLevelReadException exception. In the exception handler, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 7:" and then the contents of the Message property of the exception. Your code should resemble the following code.
... ' Step 7 - Record the core radiation levels after shutting down the reactor Try Me.textBlock1.Text &= vbNewLine & "Step 7: Core radiation level after shutdown: " & sd.GetRadiationLevel() Catch ex As SwitchDevices.CoreRadiationLevelReadException Me.textBlock1.Text &= vbNewLine & "*** Exception in step 7: " & ex.Message End Try ' Step 8 - Broadcast "Shutdown Complete" message ...

11. Enclose the two statements after Step 8 - Broadcast "Shutdown Complete" message comments in a Try/Catch block, which catches the SwitchDevices.SignallingException exception. In each exception handler, add code to append a new line of text to the textBlock1 control with the message "*** Exception in step 8:" and then the contents of the Message property of the exception. Your code should resemble the following code.
... ' Step 8 - Broadcast "Shutdown Complete" message Try sd.SignalShutdownComplete() Me.textBlock1.Text &= vbNewLine & "Step 8: Broadcasting shutdown complete message" Catch ex As SwitchDevices.SignallingException Me.textBlock1.Text &= vbNewLine & "*** Exception in step 8: " & ex.Message End Try Me.textBlock1.Text &= vbNewLine & "Test sequence complete: " & DateTime.Now.ToLongTimeString() ...

12. Build the solution and correct any errors. On the Build menu, click Build Solution.

L4-6

Lab: Handling Exceptions

Task 4: Test the application.


Run the application, and then click the Shutdown button. Examine the messages displayed in the MainWindow window, and verify that exceptions are now caught and reported.

Note: The Switch class randomly generates exceptions as before, so you may not see any exception messages the first time you click the button. Repeat the process of clicking the button and examining the output until you see exception messages appear. a. b. c. On the Debug menu, click Start Debugging. In the MainWindow window, click Shutdown. Read through the messages that are displayed in the window, and verify that where an exception occurred, a message appears that states "*** Exception in step x :message", where x is a step number, and message is an exception message. Close the application and return to Visual Studio.

d.

Exercise 2: Detecting an Exceptional Condition


Task 1: Open the MatrixMultiplication solution.
1. In Visual Studio, open the MatrixMultiplication solution in the D:\Labfiles\Lab04\Ex2\Starter folder. a. b. 2. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab04\Ex2\Starter folder, click MatrixMultiplication.sln, and then click Open.

Open the Matrix.vb file, and then locate the MatrixMultiply method. In Solution Explorer, in the MatrixMultiplication project, right-click Matrix.vb, and then click View Code.

3.

Examine the MatrixMultiply method. The MatrixMultiply method performs the arithmetic to multiply together the two matrices passed as parameters and return the result. Currently, the method accepts matrices of any size, and performs no validation of data in the matrices before calculating the results. You will add checks to ensure that the two matrices are compatible (the number of columns in the first matrix is equal to the number of rows in the second matrix), and that no value in either matrix is a negative number. If the matrices are not compatible, or either of them contain a negative value, the method must throw an exception.

Task 2: Add code to throw exceptions in the MatrixMultiply method.


1. In the MatrixMultiply method, locate and remove the comment, TODO: Evaluate input matrices for compatibility. Below the comment block, add code to perform the following actions. a. b. Compare the number of columns in matrix1 to the number of rows in matrix2. Throw an ArgumentException exception if the values are not equal. The exception message should specify that the number of columns and rows should match.

Lab: Handling Exceptions

L4-7

Hint: You can obtain the number of columns in a matrix by examining the length of the first dimension. You can obtain the number of rows in a matrix by examining the length of the second dimension. Your code should resemble the following code.
... ' Check the matrices are compatible If matrix1.GetLength(0) <> matrix2.GetLength(1) Then Throw New ArgumentException( "The number of columns in the first matrix must be the same as the number of rows in the second matrix") End If ' Get the dimensions ...

2.

Locate and remove the comment, TODO: Evaluate matrix data points for invalid data. At this point, the method iterates through the data points in each matrix, multiplying the value in each cell in matrix1 against the value in the corresponding cell in matrix2. Add code below the comment block to perform the following actions. a. b. Check that the value in the current column and row of matrix1 is greater than zero. The cell and row variables contain the column and row that you should examine. Throw an ArgumentException exception if the value is not greater than zero. The exception should contain the message, "Matrix1 contains an invalid entry in cell(x, y)." where x and y are the column and row values of the cell.

Hint: Use the String.Format method to construct the exception message. Your code should resemble the following code.
... ' Throw exceptions if either matrix contains a negative entry If matrix1(cell, row) < 0 Then Throw New ArgumentException(String.Format( "Matrix1 contains an invalid entry in cell({0}, {1})", cell, row)) End If accumulator += matrix1(cell, row) * matrix2(column, cell) ...

3.

Add another block of code to check that the value in the current column and row of matrix2 is greater than zero. If it is not, throw an ArgumentException exception with the message, "Matrix2 contains an invalid entry in cell(x, y).". The column and cell variables contain the column and row that you should examine. Your code should resemble the following code.
... End If If matrix2(column, cell) < 0 Then Throw New ArgumentException(String.Format(

L4-8

Lab: Handling Exceptions

End If

"Matrix2 contains an invalid entry in cell({0}, {1}).", column, cell))

accumulator += matrix1(cell, row) * matrix2(column, cell) ...

Task 3: Handle the exceptions that the MatrixMultiply method throws.


1. Open the MainWindow Windows Presentation Foundation (WPF) window in the Design View window and examine the window. This window provides the user interface that enables the user to enter the data for the two matrices to be multiplied. The user clicks the Calculate button to calculate and display the result. 2. In Solution Explorer, in the MatrixMultiplication project, double-click MainWindow.xaml.

Open the code file for the MainWindow WPF window. In Solution Explorer, in the MatrixMultiplication project, expand MainWindow.xaml, right-click MainWindow.xaml.vb, and then click View Code.

3. 4.

In the MainWindow class, locate the ButtonCalculate_Click method. This method runs when the user clicks the Calculate button. In the ButtonCalculate_Click method, locate the line of code that invokes the Matrix.MatrixMultiply method, and enclose this line of code in a Try/Catch block that catches an ArgumentException exception named, ex. Your code should resemble the following code.
... ' Do the multiplication - checking for exceptions Try result = Matrix.MatrixMultiply(matrix1, matrix2) Catch ex As ArgumentException End Try ' Show the results ...

5.

In the Catch block, add a statement that displays a message box that contains the contents of the Message property of the exception object.

Hint: You can use the MessageBox.Show method to display a message box. Specify the message to display as a string passed in as a parameter to this method. Your code should resemble the following code.
... Catch ex As ArgumentException MessageBox.Show(ex.Message) End Try ...

6.

Build the solution and correct any errors. On the Build menu, click Build Solution.

Lab: Handling Exceptions

L4-9

7.

Start the application without debugging. Press Ctrl+F5.

8.

In the MainWindow window, in the first drop-down list box, select Matrix 1: 2 Columns, in the second drop-down list box, select Matrix 1: 2 Rows, and then in the third drop-down list box, select Matrix 2: 2 Columns. This creates a pair of 2 2 matrices initialized with zeroes.

9.

Enter some non-negative values in the cells in both matrices, and then click Calculate. Verify that the result is calculated and displayed, and that no exceptions occur.

10. Enter one or more negative values in the cells in either matrix, and then click Calculate again. Verify that the appropriate exception message is displayed, and that it identifies the matrix and cell that is in error. 11. Close the MainWindow window and return to Visual Studio. The application throws and catches exceptions, so you need to test that the application functions as expected. Although you can test for negative data points by using the application interface, the user interface does not let you create arrays of different dimensions. Therefore, you have been provided with unit test cases that will invoke the MatrixMultiply method with data that will cause exceptions. These tests have already been created; you will just run them to verify that your code works as expected.

Task 4: Implement test cases and test the application.


1. In the Matrix Unit Test Project, open the MatrixTest class, and then examine the MatrixMultiplyTest1 method. The MatrixMultiplyTest1 method creates four matrices: matrix1, matrix2, expected, and actual. The matrix1 and matrix2 matrices are the input matrices that are passed to the MatrixMultiply method during the test. The expected matrix contains the expected result of the matrix multiplication, and the actual matrix stores the result of the MatrixMultiply method call. The method invokes the MatrixMultiply method before using a series of Assert statements to verify that the expected and actual matrices are identical. This test method is complete and requires no further work. 2. In Solution Explorer, expand Matrix Unit Test Project, and then double-click MatrixText.vb.

Examine the MatrixMultiplyTest2 method. This method creates two compatible matrices, but matrix2 contains a negative value. This should cause the MatrixMultiply method to throw an exception. The MatrixMultiplyTest2 method is prefixed with the ExpectedException attribute, indicating that the test method expects to cause an ArgumentException exception. If the test does not cause this exception, it will fail.

3.

Examine the MatrixMultiplyTest3 method. This method creates two incompatible matrices and passes them to the MatrixMultiply method, which should throw an ArgumentException exception as a result. Again, the method is prefixed with the ExpectedException attribute, indicating that the test will fail if this exception is not thrown.

4.

Run all tests in the solution, and verify that all tests run correctly. a. b. On the Build menu, click Build Solution. On the Test menu, point to Run, and then click All Tests in Solution.

L4-10

Lab: Handling Exceptions

c. 5.

Wait for the tests to run, and then in the Test Results window, verify that all tests passed.

Close Visual Studio. In Visual Studio, on the File menu, click Exit.

Lab: Reading and Writing Files

L5-1

Module 5: Reading and Writing Files

Lab: Reading and Writing Files


Exercise 1: Building a Simple File Editor
Task 1: Open the SimpleEditor project.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Open the SimpleEditor solution in the D:\Labfiles\Lab05\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab05\Ex1\Starter folder, click SimpleEditor.sln, and then click Open.

Task 2: Display a dialog box to accept a file name from the user.
1. Display the MainWindow.xaml window. In Solution Explorer, expand the FileEditor project, and then double-click MainWindow.xaml.

The MainWindow window implements a very simple text editor. The main part of the window contains a text box that a user can use to display and edit text. The Open button enables the user to open a file, and the Save button enables the user to save the changes to the text back to a file. You will add the code that implements the logic for these two buttons. 2. Review the Task List. a. b. 3. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List, or press Ctrl+Alt+K. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

Locate and double-click the task, TODO: - Implement a method to get the file name. This task is located in the MainWindow.xaml.vb class file.

4.

Delete the comment, and then define a new private method named, GetFileName, which accepts no parameters and returns a string value that holds the file name that the user specified. Your code should resemble the following code.
... Private Function GetFileName() As String End Function ...

5.

In the method body, declare a new string member named, fName, and then initialize it with the String.Empty value. Your code should resemble the following code.
... Private Function GetFileName() As String Dim fName As String = String.Empty End Function

L5-2

Lab: Reading and Writing Files

...

6.

At the top of the file, add a statement to bring the Microsoft.Win32 namespace into scope. Your code should resemble the following code.
Imports Microsoft.Win32 ...

7.

In the GetFileName method, after the statement that declares the fName variable, add code to the method to perform the following actions. a. b. c. d. Create a new instance of the OpenFileDialog dialog box, named openFileDlg. Set the InitialDirectory property of openFileDlg to point to the D:\Labfiles\Lab05\Ex1\Starter folder. Set the value of the DefaultExt property of openFileDlg to ".txt". Set the value of the Filter property of openFileDlg to "Text Documents (.txt)|*.txt".

Your code should resemble the following code.


... Dim fName As String = String.Empty Dim openFileDlg As New OpenFileDialog() openFileDlg.InitialDirectory = "D:\Labfiles\Lab05\Ex1\Starter" openFileDlg.DefaultExt = ".txt" openFileDlg.Filter = "Text Documents (.txt)|*.txt" End Function ...

8.

Add code to perform the following tasks. a. Call the ShowDialog method of openFileDlg, and then save the result.

Note: The value that ShowDialog returns is a nullable Boolean value, so save the result in a nullable Boolean variable. b. If the result is True, assign the value of the FileName property of openFileDlg to the fName variable.

Your code should resemble the following code.


... Dim result As Boolean = openFileDlg.ShowDialog()

If result Then fName = openFileDlg.FileName End Function ...

9.

At the end of the method, return the value in the fName variable. Your code should resemble the following code.
...

Lab: Reading and Writing Files

L5-3

If result Then fName = openFileDlg.FileName Return fName End Function ...

Task 3: Implement a new class to read and write text to a file.


1. Add a new class named, TextFileOperations, to the FileEditor project. You will use this class to wrap some common file operations. This scheme enables you to change the way in which files are read from or written to without affecting the rest of the application. a. b. 2. In Solution Explorer, right-click the FileEditor project, point to Add, and then click Class. In the Add New Item - FileEditor dialog box, in the Name box, type TextFileOperations.vb, and then click Add.

At the top of the class file, add a statement to bring the System.IO namespace into scope. Your code should resemble the following code.
Imports System.IO Public Class TextFileOperations ... End Class

3.

In the TextFileOperations class, add a shared public method named, ReadTextFileContents. The method should accept a string parameter named, fileName, and return a string object. Your code should resemble the following code.
... Public Class TextFileOperations Public Shared Function ReadTextFileContents(ByVal filename As String) As String End Function End Class ...

4.

In the ReadTextFileContents method, add code to return the entire contents of the text file whose path is specified in the fileName parameter.

Hint: Use the shared ReadAllText method of the File class. Your code should resemble the following code.
... Public Shared Function ReadTextFileContents(ByVal filename As String) As String Return File.ReadAllText(fileName) End Function ...

5.

Below the ReadTextFileContents method, add a public shared method named, WriteTextFileContents. The method should not return a value type, and should accept the following parameters. A string parameter named fileName

L5-4

Lab: Reading and Writing Files

A string parameter named text

Your code should resemble the following code.


... Return File.ReadAllText(fileName) End Function Public Shared Sub WriteTextFileContents(ByVal fileName As String, ByVal text As String) End Sub End Class ...

6.

In the WriteTextFileContents method, add code to write the text that is contained in the text parameter to the file that is specified in the fileName parameter.

Hint: Use the shared WriteAllText method of the File class. Your code should resemble the following code.
... Public Shared Sub WriteTextFileContents(ByVal fileName As String, ByVal text As String) File.WriteAllText(fileName, text) End Sub ...

7.

Build the solution and correct any errors. On the Build menu, click Build Solution. Review the Error list and check for any errors.

Task 4: Update the MainWindow event handlers to consume the TextFileOperations


class.
1. In the Task List, locate and double-click the task, TODO: - Update the OpenButton_Click method. This task is located in the OpenButton_Click method of the MainWindow class. 2. Remove the comment, and then add code to perform the following tasks. a. b. Invoke the GetFileName method. Store the result of the method in the fileName member. If fileName is not an empty string, call the shared ReadTextFileContents method of the TextFileOperations class, and then pass fileName as the parameter. Store the result in the Text property of the editor TextBox control in the Windows Presentation Foundation (WPF) window.

Your code should resemble the following code.


... Private Sub OpenButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) ' Call GetFileName to get the name of the file to load fileName = GetFileName() ' Populate the editor text box with the file contents If fileName <> String.Empty Then editor.Text =

Lab: Reading and Writing Files

L5-5

End If End Sub ...

TextFileOperations.ReadTextFileContents(fileName)

3.

In the Task List, locate and double-click the task, TODO: - Update the SaveButton_Click method. This task is located in the SaveButton_Click method of the MainWindow class.

4.

In the SaveButton_Click method, remove the comment, and then add code to perform the following tasks. a. b. Check that the fileName member is not an empty string. If fileName is not an empty string, call the shared WriteTextFileContents method of the TextFileOperations class. Pass fileName and the Text property of the editor TextBox control as the parameters.

Your code should resemble the following code.


... Private Sub SaveButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) ' Write the contents of the editor TextBox back to the file If fileName <> String.Empty Then TextFileOperations.WriteTextFileContents(fileName, editor.Text) End If End Sub ...

5.

Build the solution and correct any errors. On the Build menu, click Build Solution.

6.

Start the application without debugging. Press Ctrl+F5.

7. 8. 9.

In the MainWindow window, click Open. In the Open dialog box, browse to the D:\Labfiles\Lab05\Ex1\Starter folder, click Commands.txt, and then click Open. In the MainWindow window, verify that the text in the following code example is displayed in the editor TextBox control.
Move x, 10 Move y, 20 If x < y Add x, y If x > y & x < 20 Sub x, y Store 30

This is the text from the Commands.txt file. 10. Change the Store 30 line to Save 50, and then click Save. 11. Close the MainWindow window. 12. Using Windows Explorer, browse to the D:\Labfiles\Lab05\Ex1\Starter folder. 13. Open the Commands.txt file by using Notepad. In Windows Explorer, right-click Commands.txt, point to Open with, and then click Notepad.

14. In Notepad, verify that the last line of the file contains the text Save 50.

L5-6

Lab: Reading and Writing Files

15. Close Notepad and return to Visual Studio.

Task 5: Implement test cases.


1. In the Task List, locate and double-click the task, TODO: - Complete Unit Tests. This task is located in the TextFileOperationsTest class. 2. 3. Remove the comment. Examine the ReadTextFileContentsTest1 method, and then uncomment the commented line. This method creates three strings. The fileName string contains the path of a prewritten file that contains specific content. The expected string contains the contents of the prewritten file, including formatting and escape characters. The actual string is initialized by calling the ReadTextFileContents method that you just implemented.

The test method then uses an Assert statement to verify that the expected and actual strings are the same. 4. Uncomment the fourth line of code, to enable the method to call the FileEditor.TextFileOperations.ReadTextFileContents method.

Examine the WriteTextFileContentsTest1 method, and then uncomment the commented line. This method creates two strings. a. b. The fileName string contains the path of a nonexistent file, which the method will create when run. The text string contains some text that the method will write to the file.

The method calls the WriteTextFileContents method, passing the fileName and text strings as parameters. This creates the file at the specified location, and writes to the file. The method then creates a further string, expected, by calling the File.ReadAllText method and reading the text from the written file. The method then checks that the text string and the expected string are the same, before deleting the file that was created during the test. 5. Uncomment the third line of code, to enable the method to call the FileEditor.TextFileOperations.WriteTextFileContents method.

Run all tests in the solution, and verify that all tests run correctly. a. b. c. On the Build menu, click Build Solution. On the Test menu, point to Run, and then click All Tests in Solution. Wait for the tests to run, and then in the Test Results window, verify that all tests passed.

Exercise 2: Making the Editor XML Aware


Task 1: Open the starter project.
Open the SimpleEditor solution in the D:\Labfiles\Lab05\Ex2\Starter folder. This project is a revised version of the SimpleEditor project from Exercise 1. a. In Visual Studio, on the File menu, click Open Project

Lab: Reading and Writing Files

L5-7

b.

In the Open Project dialog box, browse to the D:\Labfiles\Lab05\Ex2\Starter folder, click SimpleEditor.sln, and then click Open.

Task 2: Add a new method to filter XML characters to the TextFileOperations class.
1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

In the Task List, locate and double-click the TODO: - Implement a new method in the TextFileOperations class task. This task is located in the TextFileOperations class.

3.

Remove the comment, and then add a new shared public method named, ReadAndFilterTextFileContents. The method should accept a string parameter named, fileName, and return a string value. Your code should resemble the following code.
... Public Shared Function ReadAndFilterTextFileContents(ByVal fileName As String) As String End Function ...

4.

In the ReadAndFilterTextFileContents method, add the following local variables. A StringBuilder object named, fileContents, initialized to a new instance of the StringBuilder class. An integer variable called, charCode.

Your code should resemble the following code.


... Public Shared Function ReadAndFilterTextFileContents(ByVal fileName As String) As String Dim fileContents As New StringBuilder() Dim charCode As Integer End Function ...

5.

Add a statement that instantiates a StreamReader object, named fileReader, by using the fileName parameter. Your code should resemble the following code.
... Dim fileContents As New StringBuilder() Dim charCode As Integer

Dim fileReader As New StreamReader(fileName) End Function ...

6.

Add a While statement that reads each character in the StreamReader object until the end of the file is reached.

L5-8

Lab: Reading and Writing Files

Hint: Use the Read method of the StreamReader class to read the next character from a stream. This method returns 1 if there is no more data. Your code should resemble the following code.
... Dim fileReader As New StreamReader(fileName) charCode = fileReader.Read() While charCode <> -1 charCode = fileReader.Read() End While

...

7.

In the While block, add a Select Case statement that evaluates the charCode variable. In the Select Case statement, add Case statements for each of the characters in the following table. In each statement, append the fileContents StringBuilder object with the alternative representation shown in the table. charCode 34 38 39 60 62 Standard representation " (straight quotation mark) & (ampersand) ' (apostrophe) < (less than) > (greater than) Alternative representation &quot; &amp; &apos; &lt; &gt;

Your code should resemble the following code.


... charCode = fileReader.Read() While charCode <> -1 Select Case charCode Case 34 ' " fileContents.Append("&quot;") Case 38 ' & fileContents.Append("&amp;") Case 39 ' ' fileContents.Append("&apos;") Case 60 ' < fileContents.Append("&lt;") Case 62 ' > fileContents.Append("&gt;") End Select charCode = fileReader.Read() End While ...

Lab: Reading and Writing Files

L5-9

8.

Add a default Case statement that appends the actual character read from the stream to the fileContents StringBuilder object.

Note: The Read method returns the value read from the file as an integer and stores it in the charCode variable. You must cast this variable to a character before you append it to the end of the StringBuilder object. Use the ChrW Visual Basic function to return the character associated
with the specified character code.

Your code should resemble the following code.


... Case 62 ' > fileContents.Append("&gt;") Case Else fileContents.Append(CType(ChrW(charCode), Char)) End Select

charCode = fileReader.Read() End While ...

9.

At the end of the method, return the contents of the fileContents StringBuilder object as a string. Your code should resemble the following code.
... Public Shared Function ReadAndFilterTextFileContents(ByVal fileName As String) As String ... Return fileContents.ToString() End Function ...

10. Build the solution and correct any errors. On the Build menu, click Build Solution.

Task 3: Update the user interface to invoke the new method.


1. In the Task List, locate and double-click the TODO: - Update the UI to use the new method task. This task is located in the OpenButton_Click method of the MainWindow.xaml.vb class. 2. Delete the comment, and then modify the line of code that calls the TextFileOperations.ReadTextFileContents method to call the TextFileOperations.ReadAndFilterTextFileContents method instead. Pass the fileName field as the parameter, and then save the result in the Text property of the editor TextBox control. Your code should resemble the following code.
... If filename <> String.Empty Then ' Call the new read file contents method editor.Text = TextFileOperations.ReadAndFilterTextFileContents(filename) End If ...

3.

Build the solution and correct any errors.

L5-10

Lab: Reading and Writing Files

4.

On the Build menu, click Build Solution.

Start the application without debugging. Press Ctrl+F5.

5. 6. 7.

In the MainWindow window, click Open. In the Open dialog box, browse to the D:\Labfiles\Lab05\Ex2\Starter folder, click Commands.txt, and then click Open. In the MainWindow window, verify that the text in the following code example is displayed in the editor TextBox control.
Move x, 10 Move y, 20 If x &lt; y Add x, y If x &gt; y &amp; x &lt; 20 Sub x, y Store 30

This is the text from the Commands.txt file. Notice that the <, >, and & characters have been replaced with the text &lt;, &gt;, and &amp;. 8. Close the MainWindow window and return to Visual Studio.

Task 4: Implement test cases.


1. In the Task List, locate and double-click the TODO: - Complete Unit Tests task. This task is located in the TextFileOperationsTest class. 2. Examine the ReadAndFilterTextFileContentsTest method, and then uncomment the commented line. This method creates three strings. The filename string contains the path of a prewritten file that contains specific content. The expected string contains the contents of the prewritten file, including formatting and escape characters. The actual string is initialized by calling the ReadAndFilterTextFileContents method that you just implemented.

The test method then uses an Assert statement to verify that the expected and actual strings are the same. This method is complete, and requires no further work. 3. Uncomment the fourth line of code, and modify to enable the method to call the FileEditor.TextFileOperations.ReadAndFilterTextFileContents method.

Run all tests in the solution, and verify that all tests run correctly. a. b. c. On the Build menu, click Build Solution. On the Test menu, point to Run, and then click All Tests in Solution. Wait for the tests to run, and then in the Test Results window, verify that all tests passed.

4.

Close Visual Studio. In Visual Studio, on the File menu, click Exit.

Lab: Creating New Types

L6-1

Module 6: Creating New Types

Lab: Creating New Types


Exercise 1: Using Enumerations to Specify Domains
Task 1: Open the Enumerations solution.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Open the Enumerations solution in the D:\Labfiles\Lab06\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab06\Ex1\Starter folder, click Enumerations.sln, and then click Open.

Task 2: Add enumerations to the StressTest namespace.


1. Review the Task List. a. b. 2. 3. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

Locate and double-click the TODO: - Implement Material, CrossSection, and TestResult enumerations. task. This task is located in the StressTestTypes.vb file. In the StressTestTypes.vb file, define a new enumeration named Material. The enumeration should have the following values. StainlessSteel Aluminum ReinforcedConcrete Composite Titanium

Your code should resemble the following code.


Public Enum Material StainlessSteel Aluminum ReinforcedConcrete Composite Titanium End Enum ...

4.

Below the Material enumeration, define a new enumeration named CrossSection. The enumeration should have the following values. IBeam Box ZShaped CShaped

Lab: Creating New Types

L6-2

Your code should resemble the following code.


Public Enum CrossSection IBeam Box ZShaped CShaped End Enum

5.

Below the CrossSection enumeration, define a new enumeration named TestResult. The enumeration should have the following values. Pass Fail

Your code should resemble the following code.


Public Enum TestResult Pass Fail End Enum

6.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 3: Retrieve the enumeration values.


1. In the TestHarness project, display the MainWindow.xaml window. In Solution Explorer, expand TestHarness, and then double-click MainWindow.xaml.

The purpose of the TestHarness project is to enable you to display the values from each of the enumerations. When the application runs, the three lists are populated with the values that are defined for each of the enumerations. The user can select an item from each list, and the application will construct a string from the corresponding enumerations. 2. In the Task List, locate and double-click the TODO: - Retrieve user selections from the UI. task. This task is located in the MainWindow.xaml.vb class. Perform the following actions: a. b. c. d. Remove the comment, and add code to the ListBox_SelectionChanged method to perform the following tasks. Create a Material object called selectedMaterial and initialize it to the value of the SelectedItem property in the MaterialsListBox list box. Create a CrossSection object called selectedCrossSection and initialize it to the value of the SelectedItem property in the CrossSectionsListBox list box. Create a TestResult object called selectedTestResult and initialize it to the value of the SelectedItem property in the TestResultsListBox list box.

Hint: The SelectedItem property of a ListBox control has the object type. You must cast this property to the appropriate type when you assign it to an enumeration variable. Your code should resemble the following code.
...

Lab: Creating New Types

L6-3

If MaterialsListBox.SelectedIndex = -1 OrElse CrossSectionsListBox.SelectedIndex = -1 OrElse TestResultsListBox.SelectedIndex = -1 Then Return Dim selectedMaterial As Material = CType(MaterialsListBox.SelectedItem, Material) Dim selectedCrossSection As CrossSection = CType(CrossSectionsListBox.SelectedItem, CrossSection) Dim selectedTestResult As TestResult = CType(TestResultsListBox.SelectedItem, TestResult) ...

Task 4: Display the selection results.


1. In the ListBox_SelectionChanged method, after the code that you added in the previous task, add a statement to create a new StringBuilder object named selectionStringBuilder.

Your code should resemble the following code.


... Dim selectedTestResult As TestResult = CType(TestResultsListBox.SelectedItem, TestResult) Dim selectionStringBuilder As New StringBuilder() ...

2.

Add a Select Case statement to evaluate the selectedMaterial variable. In the Select Case statement, add Case statements for each potential value of the Material enumeration. In each Case statement, add code to append the text "Material: <selectedMaterial>, " to the selectionStringBuilder object. Substitute the text "<selectedMaterial>" in this string with the corresponding value for the selectedMaterial variable that is shown in the following table. <selectedMaterial> string Stainless Steel Aluminum Reinforced Concrete Composite Titanium

Material enumeration value Material.StainlessSteel Material.Aluminum Material.ReinforcedConcrete Material.Composite Material.Titanium Your code should resemble the following code.

... Select Case selectedMaterial Case Material.StainlessSteel selectionStringBuilder.Append("Material: Stainless Steel, ") Case Material.Aluminum selectionStringBuilder.Append("Material: Aluminum, ") Case Material.ReinforcedConcrete selectionStringBuilder.Append ("Material: Reinforced Concrete, ") Case Material.Composite selectionStringBuilder.Append("Material: Composite, ") Case Material.Titanium selectionStringBuilder.Append("Material: Titanium, ") End Select ...

3.

Add another Select Case statement to evaluate the selectedCrossSection variable. In this Select Case statement, add Case statements for each potential value of the CrossSection enumeration. In

Lab: Creating New Types

L6-4

each Case statement, add code to append the text "Cross-section: <selectedCrossSection>," to the selectionStringBuilder object. Substitute the text "<selectedCrossSection>" in this string with the corresponding value for the selectedCrossSection variable that is shown in the following table. Material enumeration value CrossSection.IBeam CrossSection.Box CrossSection.ZShaped CrossSection.CShaped Your code should resemble the following code.
... Select Case selectedCrossSection Case CrossSection.IBeam selectionStringBuilder.Append("Cross-section: Case CrossSection.Box selectionStringBuilder.Append("Cross-section: Case CrossSection.ZShaped selectionStringBuilder.Append("Cross-section: Case CrossSection.CShaped selectionStringBuilder.Append("Cross-section: End Select ...

<selectedCrossSection> string I-Beam Box Z-Shaped C-Shaped

I-Beam, ") Box, ") Z-Shaped, ") C-Shaped, ")

4.

Add a final Select Case statement to evaluate the selectedTestResult member. In the Select Case statement, add Case statements for each potential value of the TestResult enumeration. In each Case statement, add code to append the text "Result: <selectedTestResult>." to the selectionStringBuilder object. Substitute the text "<selectedTestResult>" in this string with the corresponding value for the selectedTestResult variable that is shown in the following table. <selectedTestResult> string Pass Fail

Material enumeration value TestResult.Pass TestResult.Fail Your code should resemble the following code.

... Select Case selectedTestResult Case TestResult.Pass selectionStringBuilder.Append("Result: Pass.") Case TestResult.Fail selectionStringBuilder.Append("Result: Fail.") End Select ...

5.

At the end of the ListBox_SelectionChanged method, add code to display the string that is constructed by using the selectionStringBuilder object in the Content property of the TestDetailsLabel control.

Your code should resemble the following code.

Lab: Creating New Types

L6-5

... Private Sub ListBox_SelectionChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs) ... TestDetailsLabel.Content = selectionStringBuilder.ToString() End Sub ...

Task 5: Test the solution.


1. Build the application and correct any errors. 2. On the Build menu, click Build Solution. Correct any errors.

Run the application. Press Ctrl+F5.

3.

In the MainWindow window, in the Material list, click Titanium, in the CrossSection list, click Box, and then in the Test Result list, click Fail.

In the lower part of the window, verify that the label updates with your selections. 4. 5. Experiment by selecting further values from all three lists, and verify that with each change, the label updates to reflect the changes. Close the application, and then return to Visual Studio.

Exercise 2: Using a Structure to Model a Simple Type


Task 1: Open the Structures solution.
Open the Structures solution in the D:\Labfiles\Lab06\Ex2\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab06\Ex2\Starter folder, click Structures.sln, and then click Open.

Task 2: Add the TestCaseResult structure.


1. Review the Task List. a. b. 2. 3. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

In the Task List, locate and double-click the TODO: - Declare a Structure task. This task is located in the StressTestTypes.vb file. Delete the comment, and then declare a new structure named TestCaseResult. In the TestCaseResult structure, add the following members. A TestResult object named Result A String object named ReasonForFailure

Your code should resemble the following code.


... Public Structure TestCaseResult Public Result As TestResult Public ReasonForFailure As String

Lab: Creating New Types

L6-6

End Structure ...

Task 3: Add an array of TestCaseResult objects to the user interface project.


1. In the TestHarness project, display the MainWindow.xaml window. In Solution Explorer, expand TestHarness, and then double-click MainWindow.xaml.

This project simulates running stress tests and displays the results. It tracks the number of successful and failed tests, and for each failed test, it displays the reason for the failure. 2. 3. In the Task List, locate and double-click the TODO: - Declare a TestCaseResult array task. Remove the comment, and then declare a new array of TestCaseResult objects named results.

Your code should resemble the following code.


Class MainWindow Private results() As TestCaseResult End Class ...

Task 4: Fill the results array with data.


1. In the RunTestsButton_Click method, after the statement that clears the ReasonsListBox control, add code to initialize the results array. Set the array length to 10.

Your code should resemble the following code.


... Private Sub RunTestsButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) ReasonsListBox.Items.Clear() ReDim results(10) ' Fill the array with 10 TestCaseResult objects. Dim passCount As Integer = 0 ... End Sub ...

2.

Below the statement that creates the array, add code that iterates through the items in the array and populates each one with the value that the shared GenerateResult method of the TestManager class returns. The GenerateResult method simulates running a stress test and returns a TestCaseResult object that contains the result of the test and the reason for any failure.

Your code should resemble the following code.


... For i As Integer = 0 To results.Length - 1 Results(i) = TestManager.GenerateResult() Next

...

Task 5: Display the array contents.


1. Locate the comment, TODO: - Display the TestCaseResult data. Delete the comment, and then add code that iterates through the results array. For each value in the array, perform the following tasks. a. Evaluate the result value. If the result value is TestResult.Pass, increment the passCount value.

Lab: Creating New Types

L6-7

b.

If the result value is TestResult.Fail, increment the failCount value, and add the ReasonForFailure string to the ReasonsListBox list box that is displayed in the window.

Note: To add an item to a list box, you use the ListBox.Items.Add method and pass the item to add to the list as a parameter to the method. Your code should resemble the following code.
... For i As Integer = 0 To results.Length -1 If results(i).Result = TestResult.Pass Then passCount += 1 Else failCount += 1 ReasonsListBox.Items.Add(results(i).ReasonForFailure) End If Next ...

Task 6: Test the solution.


1. Build the application and correct any errors. 2. On the Build menu, click Build Solution. Correct any errors.

Run the application. Press Ctrl+F5.

3.

In the MainWindow window, click Run Tests. Verify that the Successes and Failures messages are displayed. Also verify that a message appears in the Failures list, if failures occur.

4. 5.

Click Run Tests again to simulate running another batch of tests and display the results of these tests. Close the application, and then return to Visual Studio.

Exercise 3: Using a Class to Model a More Complex Type


Task 1: Open the Classes solution.
1. Open the Classes solution in the D:\Labfiles\Lab06\Ex3\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab06\Ex3\Starter folder, click Classes.sln, and then click Open.

2. Import the code snippets from the D:\Labfiles\Lab06\Snippets folder. a. c. d. e. f. In Visual Studio, on the Tools menu, click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language drop-down, click Visual Basic. In the Code Snippets Manager dialog box, click Add. In the Code Snippets Directory dialog box, move to the D:\Labfiles \Lab06\Snippets folder, and then click Select Folder. In the Code Snippets Manager dialog box, click OK.

Task 2: Define the StressTestCase class.


1. In the TestHarness project, display the MainWindow.xaml window.

Lab: Creating New Types

L6-8

In Solution Explorer, expand the TestHarness project, and then double-click MainWindow.xaml.

This project is an extended version of the test harness from the previous two exercises. In addition to simulating stress-test results, it displays the details of the girder under test. 2. Review the Task List. a. b. 3. 4. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

In the Task List, locate and double-click the TODO: - Add the StressTestCase class task. Remove the comment, and then add code to declare a public class named StressTestCase with the following public members. You can either type this code manually, or use the Mod06StressTestCaseClass code snippet. a. b. c. d. e. f. A Material object named GirderMaterial A CrossSection object named XSection An integer named LengthInMm An integer named HeightInMm An integer named WidthInMm A TestCaseResult object named Result.

Your code should resemble the following code.


... Public Class StressTestCase Public GirderMaterial As Material Public XSection As CrossSection Public LengthInMm As Integer Public HeightInMm As Integer Public WidthInMm As Integer Public Result As TestCaseResult End Class ...

To use the Mod06StressTestCaseClass snippet, add a blank line, type Mod06StressTestCaseClass, and then press Tab.

Task 3: Add a parameterized constructor and a default constructor to the class.


1. Below the member declarations, add a constructor for the StressTestCase class that accepts the following parameters. a. b. c. d. e. A Material object named girderMaterial An XSection object named xSection An integer named lengthInMm An integer named heightInMm An integer named widthInMm

In the constructor, add code to store the value for each parameter in the corresponding member.
Hint: In the constructor, to make it clear which items are member variables and which items are parameters, use the Me keyword (which represents the current object) with all member variables.

Lab: Creating New Types

L6-9

Your code should resemble the following code.


... Public Sub New(ByVal girderMaterial As Material, ByVal xSection As CrossSection, ByVal lengthInMm As Integer, ByVal heightInMm As Integer, ByVal widthInMm As Integer) Me.GirderMaterial = girderMaterial Me.XSection = xSection Me.LengthInMm = lengthInMm Me.HeightInMm = heightInMm Me.WidthInMm = widthInMm End Sub ...

2.

Above the constructor, add a default constructor.

Hint: A default constructor is a constructor that accepts no parameters and implements functionality to create a default instance of a class. In the default constructor, initialize the members of the StressTestCase object with default values by using the parameterized constructor and the data that are shown in the following table. Parameter name girderMaterial xSection lengthInMm heightInMm widthInMm Your code should resemble the following code.
... Public Result As TestCaseResult Public Sub New() Me.New(Material.StainlessSteel, CrossSection.IBeam, 4000, 20, 15) End Sub ...

Parameter value Material.StainlessSteel CrossSection.IBeam 4000 20 15

Task 4: Add the PerformStressTest and GetStressTestResult methods to the class.


1. Below the class constructors, add code to declare a new method named PerformStressTest. The PerformStressTest method should take no parameters and should not return a value. This method will simulate performing a stress test and then populate a StressTestCase object with the details of the test. Your code should resemble the following code.
... Public Class StressTestCase ... Public Sub PerformStressTest() End Sub

Lab: Creating New Types

L6-10

End Class ...

2.

In the PerformStressTest method, create an array of strings called failureReasons that contains the following values. "Fracture detected" "Beam snapped" "Beam dimensions wrong" "Beam warped" "Other"

Your code should resemble the following code.


... Public Sub PerformStressTest() Dim failureReasons() As String = { "Fracture detected", "Beam snapped", "Beam dimensions wrong", "Beam warped", "Other" } End Sub ...

3.

Add a statement that invokes the Next method of the static Rand method of the Utility class. Pass the value 10 as a parameter.

Note: The Utility.Rand.Next method accepts an integer parameter and then returns a random integer value between zero and the value of the integer parameter. In this case, the method will return an integer between 0 and 9. If the value that the Rand method returns is 9, add code to perform the following tasks. a. b. c. Set the Result.Result member value to TestResult.Fail. Invoke the Utility.Rand.Next method with a parameter value of 5. Store the result in a new integer member named failureCode. Set the Result.ReasonForFailure value to the value in the failureReasons array that the failureCode value indicates.

Note: This code simulates a 10 percent chance of a test case failing. The failureReasons array contains five possible causes of failure, and this code selects one of these causes at random. Your code should resemble the following code.
... If Utility.Rand.Next(10) = 9 Then Result.Result = TestResult.Fail Dim failureCode As Integer = Utility.Rand.Next(5) Result.ReasonForFailure = failureReasons(failureCode) End If

Lab: Creating New Types

L6-11

...

4.

If the Rand method returns a value other than 9, add code to set the Result.Result member value to TestResult.Pass.

Your code should resemble the following code.


... If Utility.Rand.Next(10) = 9 Then ... Else Result.Result = TestResult.Pass End If ...

5.

Below the PerformStressTest method, add a public method named GetStressTestResult, which accepts no parameters and returns a TestCaseResult object.

Your code should resemble the following code.


... Public Class StressTestCase ... Public Function GetStressTestResult() As TestCaseResult End Function End Class ...

6.

In the GetStressTestResult method, add code to return a reference to the TestCaseResult member.

Your code should resemble the following code.


... Public Function GetStressTestResult() As TestCaseResult Return Result End Function ...

Task 5: Override the ToString method to return a custom string representation.


1. Below the GetStressTestResult method, add a public method named ToString.

Note: This overrides the ToString method that is inherited from the Object type. You will see more about inheritance in a later module.
... Public Class StressTestCase ... Public Overrides Function ToString() As String End Function End Class ...

2.

In the ToString method, add code to return a string with the format shown in the following code example, where each value in angle brackets is replaced with the corresponding member in the class.

Lab: Creating New Types

L6-12

Material: <girderMaterial>, CrossSection: <crossSection>, Length: <lengthInMm>mm, Height: <heightInMm>mm, Width:<widthInMm>mm.

Hint: Use the String.Format method to build the string. Your code should resemble the following code.
... Public Class StressTestCase ... Public Overrides Function ToString() As String Return String.Format("Material: {0}, CrossSection: {1}, Length: {2}mm, Height: {3}mm, Width: {4}mm", GirderMaterial.ToString(),XSection.ToString(), LengthInMm, HeightInMm, WidthInMm) End Function End Class ...

Task 6: Create an array of StressTestCase objects.


1. 2. In the Task List, locate and double-click the TODO: - Create an array of sample StressTestCase objects. task. This task is located in the MainWindow.xaml.vb class. Remove the comment, and add a private method named CreateTestCases. The CreateTestCases method should accept no parameters and return an array of StressTestCase objects.

Your code should resemble the following code.


Class MainWindow ... Private Function CreateTestCases() As StressTestCase() End Function End Class

3.

In the CreateTestCases method, add code to create an array of StressTestCase objects named stressTestCases. The array should be able to hold 10 objects.

Your code should resemble the following code.


... Private Function CreateTestCases() As StressTestCase() Dim stressTestCases(9) As StressTestCase End Function ...

4.

Add code to generate 10 StressTestCase objects, and store each of them in the stressTestCases array. Use the following table to determine the parameters to pass to the constructor for each instance. You can either type this code manually, or use the Mod06StressTestCaseClass code snippet.

Array position 0 1

Material Use default constructor Material.Composite

CrossSection

Length

Height

Width

CrossSection.CShaped

3500

100

20

Lab: Creating New Types

L6-13

Array position 2 3 4 5 6 7 8 9

Material Use default constructor Material.Aluminum Use default constructor Material.Titanium Material.Titanium Material.Titanium Use default constructor Material.StainlessSteel

CrossSection

Length

Height

Width

CrossSection.Box

3500

100

20

CrossSection.CShaped CrossSection.ZShaped CrossSection.Box

3600 4000 5000

150 80 90

20 20 20

CrossSection.Box

3500

100

20

Your code should resemble the following code.


Private Function CreateTestCases() As StressTestCase() ... stressTestCases(0) = New StressTestCase() stressTestCases(1) = New StressTestCase(Material.Composite, CrossSection.CShaped, 3500, 100, 20) stressTestCases(2) = New StressTestCase() stressTestCases(3) = New StressTestCase(Material.Aluminum, CrossSection.Box, 3500, 100, 20) stressTestCases(4) = New StressTestCase() stressTestCases(5) = New StressTestCase(Material.Titanium, CrossSection.CShaped, 3600, 150, 20) stressTestCases(6) = New StressTestCase(Material.Titanium, CrossSection.ZShaped, 4000, 80, 20) stressTestCases(7) = New StressTestCase(Material.Titanium, CrossSection.Box, 5000, 90, 20) stressTestCases(8) = New StressTestCase() stressTestCases(9) = New StressTestCase(Material.StainlessSteel, CrossSection.Box, 3500, 100, 20) End Function

5.

To use the Mod06StressTestCaseClass snippet, on a blank line, type Mod06StressTestCaseClass, and then press Tab.

At the end of the method, return the stressTestCases array.

Your code should resemble the following code.


Class MainWindow ... Private Function CreateTestCases() As StressTestCase() ... Return stressTestCases End Function End Class

Task 7: Display the StressTestCases collection.


1. In the Task List, locate the TODO: - Iterate through the StressTestCase samples displaying the results. task, and then double-click this task. This task is located in the RunTestsButton_Click method that runs when the user clicks Run Stress Tests.

Lab: Creating New Types

L6-14

2.

Remove the comment, and then add code to invoke the CreateTestCases method. Store the result of the method call in a new array of StressTestCase objects named stressTestCases.

Your code should resemble the following code.


... Private Sub RunTestsButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) TestListBox.Items.Clear() ResultListBox.Items.Clear() Dim stressTestCases() As StressTestCase = CreateTestCases() End Sub ...

3.

Add code to create a StressTestCase object named currentTestCase and a TestCaseResult object named currentTestResult. You will add code to instantiate these objects shortly.

Your code should resemble the following code.


... Private ... Dim Dim Dim End Sub ... Sub RunTestsButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) stressTestCases() As StressTestCase = CreateTestCases() currentTestCase As StressTestCase currentTestResult As TestCaseResult

4.

Add code that iterates through the StressTestCase objects in the stressTestCases array. For each StressTestCase object, add code to perform the following tasks. You can either type this code manually, or use the Mod06IterateTestCases code snippet. a. b. c. d. e. Set the currentTestCase object to refer to the StressTestCase object. Invoke the currentTestCase.PerformStressTest method on the currentTestCase object. Add the currentTestCase object to the TestListBox list that is displayed in the window. Invoke the currentTestCase.GetStressTestResult method, and store the result in the currentTestResult object. Add a string to the ResultListBox list box that is displayed in the window. This string should consist of the currentTestResult.Result value and the currentTestResult.ReasonForFailure message.

Your code should resemble the following code.


... For i As Integer = 0 To stressTestCases.Length -1 currentTestCase = stressTestCases(i) currentTestCase.PerformStressTest() TestListBox.Items.Add(currentTestCase) currentTestResult = currentTestCase.GetStressTestResult() ResultListBox.Items.Add(currentTestResult.Result & " " & currentTestResult.ReasonForFailure) Next ...

To use the Mod06IterateTestCases snippet, on a blank line, type Mod06IterateTestCases, and then press Tab.

Lab: Creating New Types

L6-15

Task 8: Test the solution.


1. Build the solution and correct any errors. 2. On the Build menu, click Build Solution. Correct any errors.

Run the application. Press Ctrl+F5.

3.

In the MainWindow window, click Run Stress Tests. Verify that the Girder Tested list contains a list of different girder compositions and the Results list contains a series of test results.

4. 5.

Click Run Stress Tests again. You should see a different set of results. Close the application, and then return to Visual Studio

Task 9: Examine and run unit tests.


1. 2. In the Task List, locate and double-click the TODO: - Examine and Run Unit Tests. task. This task is located in the StressTestCaseTest class. Examine the StressTestCaseConstructorTest method. This method uses the parameterized constructor to create a new StressTestCase object that uses defined values. The method then uses a series of Assert statements to ensure that the properties of the created object match the values that are passed to the constructor. 3. Examine the StressTestCaseConstructorTest1 method. This method uses the default constructor to create a new StressTestCase object, passing no parameters. The method then uses a series of Assert statements to ensure that the properties of the created object match the intended default values. 4. Examine the GetStressTestResultTest method. This method creates a new StressTestCase object and then retrieves a TestCaseResult object by calling the StressTestCase.GetStressTestResult method. The test method then uses Assert statements to ensure that the TestCaseResult.Result and TestCaseResult.ReasonForFailure properties contain the expected values. 5. Examine the PerformStressTestTest method. This method creates a StressTestCase object, calls the PerformStressTest method, and then retrieves the TestCaseResult object. The method then checks that, if the test failed, the TestCaseResult.ReasonForFailure member contains some text. If the test passed, the method uses Assert statements to verify that the ReasonForFailure member contains no data. The method iterates 30 times. 6. Examine the ToStringTest method. This method creates a default StressTestCase object, and then verifies that the object's ToString method returns a string that contains the correct details. 7. Run all the tests in the solution, and verify that all the tests run successfully. a. b. c. On the Build menu, click Build Solution. On the Test menu, point to Run, and then click All Tests in Solution. Wait for the tests to run, and in the Test Results window, verify that all the tests passed.

Lab: Creating New Types

L6-16

Exercise 4: Using a Nullable Structure


Task 1: Open the NullableStructs solution.
Open the NullableStructs solution in the D:\Labfiles\Lab06\Ex4\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab06\Ex4\Starter folder, click NullableStructs.sln, and then click Open.

Task 2: Modify the Result field to make it nullable.


1. Review the Task List. a. b. 2. 3. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

In the Task List, locate and double-click the TODO: - Make TestCaseResult nullable task. This task is located in the StressTestTypes class. Remove the comment, and then modify the Result member definition to allow it to store a null value.

Your code should resemble the following code.


... Public Result? As TestCaseResult ...

Task 3: Modify the parameterized constructor to initialize the TestCaseResult member.


In the StressTestCase parameterized constructor, remove the comment, TODO: Initialize TestCaseResult to null, and then add code to initialize the Result member to Nothing.

Your code should resemble the following code.


Public Sub New(ByVal girderMaterial As Material, ByVal xSection As CrossSection, ByVal lengthInMm As Integer, ByVal heightInMm As Integer, ByVal widthInMm As Integer) Me.GirderMaterial = girderMaterial Me.XSection = xSection Me.LengthInMm = lengthInMm Me.HeightInMm = heightInMm Me.WidthInMm = widthInMm Me.Result = Nothing End Sub

Task 4: Modify the PerformStressTest method.


1. In the PerformStressTest method, remove the comment, TODO: Update the PerformStressTest method and work with the nullable type, and then add code to declare a new TestCaseResult variable named currentTestCase.

Your code should resemble the following code.


... Public Sub PerformStressTest() Dim currentTestCase As New TestCaseResult() Dim failureReasons() As String = {"Fracture detected", "Beam snapped", "Beam dimensions wrong", "Beam warped", "Other"}

Lab: Creating New Types

L6-17

... End Sub ...

2.

Modify the If statement to perform the following tasks: a. b. In all instances, modify the currentTestCase object, rather than the Result member. At the end of the If block, assign the currentTestCase object to the Result member.

Your code should resemble the following code.


... Public Sub PerformStressTest() ... If Utility.rand.Next(10) = 9 Then currentTestCase.Result = TestResult.Fail currentTestCase.ReasonForFailure = failureReasons(Utility.rand.Next(5)) Result = currentTestCase ... End Sub ...

3.

Modify the Else block to perform the following tasks: a. b. Modify the currentTestCase object, rather than the Result member. At the end of the If block, store the currentTestCase object in the Result member.

Your code should resemble the following code.


... Public Sub PerformStressTest() ... Else currentTestCase.Result = TestResult.Pass Result = currentTestCase End If End Sub ...

Task 5: Modify the GetStressTestResult method.


In the GetStressTestResult method, modify the method definition to return a nullable TestCaseResult value.

Your code should resemble the following code.


... Public Function GetStressTestResult() As TestCaseResult? ... End Function ...

Task 6: Modify the GetStressTestResult method call.


1. 2. In the Task List, locate and double-click the TODO: - Modify call to GetStressTestResult method to handle nulls. task. Remove the comment, and then modify the code to create a nullable TestCaseResult object named currentTestResult.

Lab: Creating New Types

L6-18

Your code should resemble the following code.


... Dim currentStressTest As StressTestCase Dim currentTestResult As TestCaseResult? For i As Integer = 0 To stressTestCases.Length - 1 ...

3.

In the For block, after retrieving the value of the currentTestResult object from the currentStressTest.GetStressTestResult method, add code to check whether the currentTestResult object contains a value. If a value exists, add a string that contains the StressTestResult Result and ReasonForFailure properties to the ResultListBox control.

Your code should resemble the following code.


... For i As Integer = 0 To stressTestCases.Length - 1 currentTestCase = stressTestCases(i) currentTestCase.PerformStressTest() TestListBox.Items.Add(currentTestCase) currentTestResult = currentTestCase.GetStressTestResult() If currentTestResult.HasValue Then ResultListBox.Items.Add( currentTestResult.Value.Result.ToString() & " " & currentTestResult.Value.ReasonForFailure) End If

Next

Task 7: Test the solution.


1. Build the solution and correct any errors. 2. On the Build menu, click Build Solution. Correct any errors.

Run the application. Press Ctrl+F5.

3.

In the MainWindow window, click Run Stress Tests. Verify that the application functions in the same way as before.

4.

Close the application, and then return to Visual Studio.

Task 8: Update the unit tests.


1. In the Task List, locate and double-click the TODO: - Examine and run unit tests updated to deal with nullable type task. This task is located in the StressTestCaseTest class.

Note: Most of the test cases are identical to those in Exercise 3. The only changes are in the GetStressTestResultTest and PerformStressTestTest methods. 2. Examine the GetStressTestResultTest method. This method creates a new StressTestCase object. It then evaluates the HasValue property on the result of the GetStressTestResult method call to verify that the property contains no value. The test then calls the PerformStressTest method, which generates a TestCaseResult value in the StressTestCase object. The test method again evaluates the HasValue property to verify that a value now exists.

Lab: Creating New Types

L6-19

3.

Examine the changes to the PerformStressTestTest method. This method creates a StressTestCase object and then calls the PerformStressTest method on that object. The method calls the GetStressTestResult method on the StressTestCase object and stores the result in a local nullable TestCaseResult object. The method then uses an Assert statement to evaluate the HasValue property of the TestCaseResult object to verify that the result is not null. The method then evaluates the Value property of the TestCaseResult object to determine whether the result indicates that the stress test failed or passed. If the stress test failed, an Assert statement is used to verify that the ReasonForFailure string contains a value. If the stress test passed, an Assert statement is used to verify that the ReasonForFailure string is null. The method iterates 30 times.

4.

Run all the tests in the solution, and verify that all the tests run successfully. a. b. c. On the Build menu, click Build Solution. On the Test menu, point to Run, and then click All Tests in Solution. Wait for the tests to run, and in the Test Results window, verify that all the tests passed.

5.

Close Visual Studio. On the File menu, click Exit.

Lab: Creating New Types

L6-20

Lab: Encapsulating Data and Methods

L7-1

Module 7: Encapsulating Data and Methods

Lab: Encapsulating Data and Methods


Exercise 1: Hiding Data Members
Task 1: Open the StressTesting solution.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Open the StressTesting solution in the D:\Labfiles\Lab07\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab07\Ex1\Starter folder, click StressTesting.sln, and then click Open.

Task 2: Declare fields in the StressTestCase class as private.


1. Review the Task List. a. b. 2. 3. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

In the Task List, locate and double-click the TODO: - Modify the StressTestCase class to make members private task. This task is located in the StressTestCase class. In the StressTestCase class, remove the TODO: - Modify the StressTestCase class to make members private comment, and then modify each field definition to make all of the fields private. Your code should resemble the following code.
... ''' ''' ''' ''' <summary> Girder material type (enumeration type) </summary> <remarks></remarks>

Private GirderMaterial As Material ''' <summary> ''' Girder cross-section (enumeration type) ''' </summary> ''' <remarks></remarks> Private XSection As CrossSection ''' <summary> ''' Girder length in millimeters ''' </summary> ''' <remarks></remarks> Private LengthInMm As Integer ''' <summary> ''' Girder height in millimeters ''' </summary> ''' <remarks></remarks> Private HeightInMm As Integer ''' <summary>

Lab: Encapsulating Data and Methods

L7-2

''' Girder width in millimeters ''' </summary> ''' <remarks></remarks> Private WidthInMm As Integer ''' <summary> ''' Details of test result (structure type) ''' </summary> ''' <remarks></remarks> Private Result? As TestCaseResult ...

Task 3: Build the project and correct errors.


1. Build the project, and then review the Error List. The project should fail to build because the code in the RunStressTestsButton_Click method in the test harness project attempts to access the fields in the StressTestCase class that are now private. a. b. c. 2. On the Build menu, click Build Solution. If the Error List is not automatically displayed, on the View menu, click Error List. If the Error List is not showing errors, in the Error List pane, click Errors.

Comment out the code that caused the errors that are shown in the Error List. These errors are caused by six statements in the RunStressTestsButton_Click method. a. b. In the Error List, double-click the first error. This error is located in the StressTest Test Harness solution, in the MainWindow.xaml.vb file. In the MainWindow class, in the RunStressTestsButton_Click method, comment out the six lines of code that raise errors.

Your code should resemble the following code.


... Private Sub RunStressTestsButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) ... 'Dim m As Material = stc.GirderMaterial 'Dim c As CrossSection = stc.XSection 'Dim l As Integer = stc.LengthInMm 'Dim h As Integer = stc.HeightInMm 'Dim w As Integer = stc.WidthInMm 'tcr = stc.Result.Value stc.PerformStressTest() ... End Sub ...

Task 4: Update unit tests to resolve errors.


1. On the Build menu, click Build Solution. There should still be some errors. The remaining errors are located in the unit test project. 2. 3. In the Task List, locate and double-click the TODO: - Update unit tests to resolve errors. task. This task is located in the StressTestCaseTest unit test class. In the StressTestCaseConstructorTest method, comment out the five Assert statements that cause errors.

Lab: Encapsulating Data and Methods

L7-3

Your code should resemble the following code.


... Public Sub StressTestCaseConstructorTest() ... 'Assert.AreEqual(Material.Composite, target.GirderMaterial) 'Assert.AreEqual(CrossSection.CShaped, target.XSection) 'Assert.AreEqual(5000, target.LengthInMm) 'Assert.AreEqual(32, target.HeightInMm) 'Assert.AreEqual(18, target.WidthInMm) End Sub ...

4.

Update the method to verify that the constructed object contains the correct member values by performing the following tasks. Hint: You cannot access the member data directly because you have just declared private members. The ToString method returns a string representation of the object, including the member data. a. Before you instantiate the target object, declare a new string named expected and populate the string with the following data that represents the expected results of the test.

Material: Composite, CrossSection: CShaped, Length: 5000mm, Height: 32mm, Width: 18mm, No Stress Test Performed

Your code should resemble the following code.


Public Sub StressTestCaseConstructorTest() ... Dim expected As String = "Material: Composite, CrossSection: CShaped, Length: 5000mm, Height: 32mm, Width: 18mm, No Stress Test Performed" Dim target As New StressTestCase(girderMaterial, xSection, lengthInMm, heightInMm, widthInMm) ... End Sub

b.

At the end of the method, add an Assert statement that checks whether the expected string matches the output of the target.ToString method.

Your code should resemble the following code.


Public Sub StressTestCaseConstructorTest() ... Dim target As New StressTestCase(girderMaterial, xSection, lengthInMm, heightInMm, widthInMm) ... Assert.AreEqual(expected, target.ToString()) End Sub

5.

Update the StressTestCaseConstructorTest1 method and resolve the errors by performing the following tasks. a. b. Comment out the five existing Assert statements. Before the method creates the target object, create a new string that contains the expected result from a default StressTestCase class.

Lab: Encapsulating Data and Methods

L7-4

Material: StainlessSteel, CrossSection: CShaped, Length: 5000mm, Height: 32mm, Width: 18mm, No Stress Test Performed

c.

At the end of the method, add an Assert statement that checks whether the expected string matches the output of the target.ToString method.

Your code should resemble the following code.


Public Sub StressTestCaseConstructorTest1() Dim expected As String = "Material: StainlessSteel, CrossSection: IBeam, Length: 4000mm, Height: 20mm, Width: 15mm, No Stress Test Performed" Dim target As New StressTestCase() 'Assert.AreEqual(Material.StainlessSteel, target.GirderMaterial) 'Assert.AreEqual(CrossSection.IBeam, target.XSection) 'Assert.AreEqual(4000, target.LengthInMm) 'Assert.AreEqual(20, target.HeightInMm) 'Assert.AreEqual(15, target.WidthInMm) Assert.AreEqual(expected, target.ToString()) End Sub

6.

Rebuild the solution and correct any errors. On the Build menu, click Build Solution.

7.

Run all of the tests in the solution, and then verify that all of the tests execute successfully. a. b. On the Test menu, point to Run, and then click All Tests in Solution. Wait for the tests to run, and in the Test Results window, verify that all of the tests pass.

Exercise 2: Using Static Members to Share Data


Task 1: Open the StressTesting solution.
1. Open the StressTesting solution in the D:\Labfiles\Lab07\Ex2\Starter folder. a. b. 2. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab07\Ex2\Starter folder, click StressTesting.sln, and then click Open.

Import the code snippets from the D:\Labfiles\Lab07\Snippets folder. a. b. c. d. e. In Visual Studio, on the Tools menu, click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language drop-down list, click Visual Basic. In the Code Snippets Manager dialog box, click Add. In the Code Snippets Directory dialog box, move to the D:\Labfiles \Lab07\Snippets folder, and then click Select Folder. In the Code Snippets Manager dialog box, click OK.

Task 2: Create a structure to hold the number of successes and failures.


1. Review the Task List. a. b. If the Task List is not automatically displayed, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

Lab: Encapsulating Data and Methods

L7-5

2. 3.

In the Task List, locate and double-click the TODO: - Create the TestStatistics structure task. This task is located in the StressTestCase class. Delete the TODO: - Create the TestStatistics structure comment, and then define a new public structure named TestStatistics, which has the following private members. a. b. An integer named numberOfTestsPerformed. An integer named numberOfFailures.

Your code should resemble the following code.


... Public Structure TestStatistics Private numberOfTestsPerformed As Integer Private numberOfFailures As Integer End Structure

4.

Add a method to the TestStatistics structure named IncrementTests. The method should accept a Boolean parameter named success, but not return a value. Add code to the method to perform the following tasks. a. b. Increment the numberOfTestsPerformed member. If the success parameter is False, increment the numberOfFailures member.

Your code should resemble the following code.


Public Structure TestStatistics ... Private numberOfFailures As Integer Public Sub IncrementTests(ByVal success As Boolean) numberOfTestsPerformed += 1 If Not success Then numberOfFailures += 1 End If End Sub End Structure

5.

Below the IncrementTests method, add a method named GetNumberOfTestsPerformed. This method should take no parameters and return an integer value. Add code to the method to return the value of the numberOfTestsPerformed member. Your code should resemble the following code.
Public Structure TestStatistics ... Public Function GetNumberOfTestsPerformed() As Integer Return numberOfTestsPerformed End Function End Structure

6.

Below the GetNumberOfTestsPerformed method, add a method named GetNumberOfFailures. The method should take no parameters and return an integer value. Add code to the method to return the value of the numberOfFailures member. Your code should resemble the following code.
Public Structure TestStatistics ...

Lab: Encapsulating Data and Methods

L7-6

Public Function GetNumberOfFailures() As Integer Return numberOfFailures End Function End Structure

7.

Below the GetNumberOfFailures method, add a Friend method named ResetCounters. The method should take no parameters and not return a value. Add code to the method to set both numberOfFailures and numberOfTestsPerformed members to zero. Your code should resemble the following code.
Public Structure TestStatistics ... Friend Sub ResetCounters() numberOfFailures = 0 numberOfTestsPerformed = 0 End Sub End Structure

8.

Build the project and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 3: Modify the StressTestCase class to contain a TestStatistics object.


1. 2. In the Task List, locate and double-click the TODO: - Add a TestStatistics field and method to the StressTestCase class task. This task is located in the StressTestCase class. Delete the TODO: - Add a TestStatistics field and method to the StressTestCase class comment, and then declare a new shared private member of type TestStatistics named statistics. Your code should resemble the following code.
Public Class StressTestCase ... Private Result? As TestCaseResult Private Shared statistics As TestStatistics ... End Class

3.

Below the statistics member declaration, add a shared public method named GetStatistics. The method should take no parameters, but should return a TestStatistics object. Add code to the method to return the value of the statistics member. Your code should resemble the following code.
Public Class StressTestCase ... Private Shared statistics As TestStatistics Public Shared Function GetStatistics() As TestStatistics Return statistics End Function ... End Class

4.

Below the GetStatistics method, add a shared public method named ResetStatistics. The method should take no parameters and should not return a value. Add code to the method to invoke the ResetCounters method on the statistics member.

Lab: Encapsulating Data and Methods

L7-7

Your code should resemble the following code.


Public Class StressTestCase ... Public Shared Function GetStatistics() As TestStatistics Return statistics End Function Public Shared Sub ResetStatistics() statistics.ResetCounters() End Sub ... End Class

5. 6.

In the Task List, locate and double-click the TODO: - Update the PerformStressTest method to handle statistics task. This method is located in the StressTestCase class. Delete the TODO: - Update the PerformStressTest method to handle statistics comment, and in the PerformStressTest method, add code to invoke the IncrementTests method on the statistics member when a test either passes or fails. If the test passes, specify the value True as the argument to the IncrementTests method. If the test fails, specify the value False as the argument to the IncrementTests method. Your code should resemble the following code.
Public Sub PerformStressTest() ... If Utility.Rand.Next(10) = 9 Then ... tcr.ReasonForFailure = failureReasons(Utility.Rand.Next(5)) statistics.IncrementTests(False) Else tcr.result = TestResult.Pass statistics.IncrementTests(True) End If ... End Sub

Task 4: Display the statistics in the user interface.


1. 2. In the Task List, locate and double-click the TODO: - Update the UI to display statistics. task. This task is located in the MainWindow class, at the end of the RunStressTestsButton_Click method. At the end of the RunStressTestsButton_Click method, delete the comments and add code to perform the following tasks. You can either type this code manually, or use the Mod07TestStatistics code snippet. a. b. Create a new TestStatistics object named statistics. Initialize the object with the value that is returned by calling the StressTestCase.GetStatistics method. In the StatisticsLabel1 label, display the message "Number of tests: <tests>, Failures: <failures>", where tests is the number of tests that were executed, and failures is the number of tests that failed.

Hint: Set the Content property of a Label control to display a message in that control. c. Invoke the IncrementTests method on the statistics object, and pass True as a parameter.

Lab: Encapsulating Data and Methods

L7-8

d. e.

Invoke the static GetStatistics method on the StressTestCase object, and store the result in the statistics variable. In the StatisticsLabel2 label, display the message "Number of tests: <tests>, Failures: <failures>", where tests is the number of tests that were executed, and failures is the number of tests that failed.

Note: This demonstrates the principle of passing or returning by value. When the code first calls the GetStatistics method, a copy of the value is returned from the StressTestCase object. Therefore, when the code calls the IncrementTests method, the update is performed on the copied value and not the original value. When the GetStatistics method is called for the second time, another copy of the original value is retrieved; therefore, both labels will display the same value. Your code should resemble the following code.
Private Sub RunStressTestsButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) ... Dim statistics As TestStatistics = StressTestCase.GetStatistics() StatisticsLabel1.Content = String.Format( "Number of tests: {0}, Failures: {1}", statistics.GetNumberOfTestsPerformed(), statistics.GetNumberOfFailures()) statistics.IncrementTests(true) statistics = StressTestCase.GetStatistics() StatisticsLabel2.Content = String.Format( "Number of tests: {0}, Failures: {1}", statistics.GetNumberOfTestsPerformed(), statistics.GetNumberOfFailures()) End Sub

To use the Mod07TestStatistics snippet, on a blank line, type Mod07TestStatistics and then press Tab.

Task 5: Test the solution.


1. Build the solution and correct any errors. 2. On the Build menu, click Build Solution. Correct any errors.

Run the application. Press Ctrl+F5.

3. 4.

In the MainWindow window, click Run Stress Tests, and then examine the statistics labels, which should both display the same values. Close the MainWindow window, and then return to Visual Studio.

Task 6: Examine and run unit tests for the TestStatistics class.
1. 2. In the Task List, locate and double-click the TODO: - Examine and run unit tests. task. This task is located in the StressTestClass_TestStatisticsTest file. Examine the GetNumberOfFailuresTest method. This method creates a new TestStatistics object named target and then invokes the IncrementTests method twice, passing false as the parameter. The method then retrieves the number of failures from the TestStatistics object and uses an Assert statement to verify that the value is correct. 3. Examine the GetNumberOfTestsPerformedTest method.

Lab: Encapsulating Data and Methods

L7-9

This method creates a new TestStatistics object named target and then invokes the IncrementTests method three times. The method then retrieves the number of tests that was performed from the TestStatistics object and uses an Assert statement to verify that the value is correct. 4. Examine the IncrementTestsTest method. This method creates a TestStatistics object named target and then invokes the IncrementTests method on this object four times. The method then retrieves the number of tests that were performed from the target object and uses an Assert statement to verify that the value is correct. 5. Run all of the tests in the solution, and then verify that all of the tests execute successfully. a. b. On the Test menu, point to Run, and then click All Tests in Solution. Wait for the tests to run, and in the Test Results window, verify that all of the tests pass.

Exercise 3: Implementing an Extension Method


Task 1: Open the StressTesting solution.
Open the StressTesting solution in the D:\Labfiles\Lab07\Ex3\Starter folder. This solution contains a revised copy of the solution from the previous exercise: a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab07\Ex3\Starter folder, click StressTesting.sln, and then click Open.

Task 2: Define a new extension method.


1. In the StressTest project, add a new public module named Extensions, in a file named Extensions.vb. a. b. 2. In Solution Explorer, right-click the StressTest project, point to Add, and then click Module. In the Add New Item - StressTest dialog box, in the Name box, type Extensions and then click Add.

Import the System.Runtime.CompilerServices namespace.


Imports System.Runtime.CompilerServices

3.

In the Extensions module, add a new public extension method named ToBinaryString. The method should take a 64-bit integer parameter named i and return a string value. Your code should resemble the following code.
Public Module Extensions <Extension()> Public Function ToBinaryString(ByVal i As Long) As String End Function End Module

4.

Import the System.Text namespace.


Imports System.Runtime.CompilerServices Imports System.Text ...

5.

In the ToBinaryString method, add code to create a string that holds the binary representation of the 64-bit integer value that is passed in the i integer, and return this string.

Lab: Encapsulating Data and Methods

L7-10

Your code should resemble the following code.


<Extension()> Public Function ToBinaryString(ByVal i As Long) As String Dim remainder As Long = 0 Dim binary As New StringBuilder("") While i > 0 remainder = i Mod 2 i = i \ 2 binary.Insert(0, remainder) End While Return binary.ToString() End Function

Task 3: Modify the TestCaseResult structure to include a long field.


1. Review the Task List. a. b. 2. 3. If the Task List is not automatically displayed, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

In the Task List, locate and double-click the TODO: - Modify the TestCaseResult structure task. This task is located in the TestCaseResult structure. In the TestCaseResult structure, delete the comment and add a public field of type Long named FailureData. Your code should resemble the following code.
Public Structure TestCaseResult ... Public ReasonForFailure As String Public FailureData As Long End Structure

Task 4: Modify the PerformStressTest method.


1. 2. In the Task List, locate and double-click the TODO: - Update the PerformStressTest method task. This task is located in the StressTestCase class, in the PerformStressTest method. In the PerformStressTest method, delete the TODO: - Update the PerformStressTest method comment, and then add code to update the FailureData member of the TestCaseResult object with a random number to simulate the data that is retrieved from the stress-testing equipment. Hint: Use the Rand member of the Utility static class to generate a random number. This method contains a method called Next that returns a random number in a specified range. Pass the value Integer.MaxValue as the parameter to the Next method to generate a random number between 0 and this value. The value Integer.MaxValue field specifies the maximum value that the integer type supports. Your code should resemble the following code.
Public Sub PerformStressTest() ...

Lab: Encapsulating Data and Methods

L7-11

tcr.ReasonForFailure = failureReasons(Utility.Rand.Next(5)) tcr.FailureData = Utility.Rand.Next(Integer.MaxValue) statistics.IncrementTests(False) Else ... End Sub

Task 5: Display the failure data.


1. 2. In the Task List, locate and double-click the TODO: - Update the UI to display the binary string task. This task is located in the MainWindow class, in the RunStressTestsButton_Click method. Modify the RunStressTestsButton_Click method to append the binary data that is contained in the FailureData member to the failure information that is displayed in the user interface; append a space character followed by the result of the ToBinaryString method call to the end of the string that is added to the ResultListBox.Items collection. Your code should resemble the following code.
Private Sub RunStressTestsButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) ... For i As Integer = 0 To stressTestCases.Length - 1 ... If stc.GetStressTestResult().HasValue Then tcr = CType(stc.GetStressTestResult().Value, TestCaseResult) ' Modified in Exercise 3 to use extension method ResultListBox.Items.Add(tcr.result.ToString() & " " & tcr.ReasonForFailure & " " & tcr.FailureData.ToBinaryString()) End If Next ... End Sub

Task 6: Test the solution.


1. Build the solution and correct any errors. 2. On the Build menu, click Build Solution. Correct any errors.

Run the application. Press Ctrl+F5.

3. 4.

In the MainWindow window, click Run Stress Tests, and then verify that when an error occurs, binary data is displayed after the reason for the failure. Close the MainWindow window, and then return to Visual Studio.

Task 7: Examine and run unit tests.


1. 2. In the Task List, locate and double-click the TODO: - Review and run unit tests task. This task is located in the ExtensionsTest class. Examine the ToBinaryStringTest method. This method creates a Long variable, i, with the value 8 and then creates a string variable, expected, with the value 1000. The method then invokes the ToBinaryString extension method on the Long

Lab: Encapsulating Data and Methods

L7-12

variable i and stores the result in a string named actual. The method then uses an Assert statement to verify that the expected and actual values are the same. The method then updates the Long variable i with the value 10266 and the expected variable with the binary representation 10100000011010. Next, it directly calls the ToBinaryString method, passes the Long variable i as a parameter, and stores the result of the method call in the actual variable. The method uses a second Assert statement to verify that the expected and actual values are the same. 3. Run all of the tests in the solution, and then verify that all of the tests execute successfully. a. b. 4. On the Test menu, point to Run, and then click All Tests in Solution. Wait for all of the tests to run, and in the Test Results window, verify that all of the tests pass.

Close Visual Studio. In Visual Studio, on the File menu, click Exit.

Lab: Inheriting from Classes and Implementing Interfaces

L8-1

Module 8: Inheriting from Classes and Implementing Interfaces

Lab: Inheriting from Classes and Implementing Interfaces


Exercise 1: Defining an Interface
Task 1: Open the starter project.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Import the code snippets from the D:\Labfiles\Lab08\Snippets folder. a. b. c. d. e. In Visual Studio, on the Tools menu, click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language drop-down, click Visual Basic. In the Code Snippets Manager dialog box, click Add. In the Code Snippets Directory dialog box, browse to the D:\Labfiles\Lab08\Snippets folder, and then click Select Folder. In the Code Snippets Manager dialog box, click OK.

3.

Open the Module8 solution in the D:\Labfiles\Lab08\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, in the File name box, browse to the D:\Labfiles\Lab08\Ex1\Starter folder, click Module8.sln, and then click Open.

Task 2: Create the IMeasuringDevice interface.


1. Open the IMeasuringDevice.vb code file. 2. In Solution Explorer, double-click IMeasuringDevice.vb.

Declare the IMeasuringDevice interface. The IMeasuringDevice interface must be accessible to code in other assemblies.

Your code should resemble the following code.


Public Interface IMeasuringDevice End Interface

3.

Add a method named MetricValue that returns a Decimal value to the interface. The method should take no parameters. Add a comment that describes the purpose of the method.

Your code should resemble the following code.


Public Interface IMeasuringDevice ''' <summary> ''' Converts the raw data collected by the measuring device ''' into a metric value. ''' </summary> ''' <returns>The latest measurement from the device converted ''' to metric units.</returns> ''' <remarks></remarks> Function MetricValue() As Decimal

Lab: Inheriting from Classes and Implementing Interfaces

L8-2

End Interface

4.

Add a method named ImperialValue that returns a Decimal value to the interface. The method should take no parameters. Add a comment that describes the purpose of the method. Add the code in the following code example to the interface.

''' <summary> ''' Converts the raw data collected by the measuring device into an ''' imperial value. ''' </summary> ''' <returns>The latest measurement from the device converted to ''' imperial units.</returns> ''' <remarks></remarks> Function ImperialValue() As Decimal

5.

Add a method named StartCollecting with a no return type to the interface. This method should take no parameters. Add a comment that describes the purpose of the method. Add the code in the following code example to the interface.

''' ''' ''' ''' Sub

<summary> Starts the measuring device. </summary> <remarks></remarks> StartCollecting()

6.

Add a method named StopCollecting with a no return type to the interface. This method should take no parameters. Add a comment that describes the purpose of the method. Add the method in the following code example to the interface.

''' ''' ''' ''' Sub

<summary> Stops the measuring device. </summary> <remarks></remarks> StopCollecting()

7.

Add a method named GetRawData that returns an integer array return type to the interface. This method should take no parameters. Add a comment that describes the purpose of the method. Add the method in the following code example to the interface.

''' <summary> ''' Enables access to the raw data from the device in whatever units ''' are native to the device. ''' </summary> ''' <returns>The raw data from the device in native format.</returns> ''' <remarks></remarks> Function GetRawData() As Integer()

8.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

At the end of this exercise, your code should resemble the following code.
Public Interface IMeasuringDevice ''' <summary> ''' Converts the raw data collected by the measuring device ''' into a metric value.

Lab: Inheriting from Classes and Implementing Interfaces

L8-3

''' </summary> ''' <returns>The latest measurement from the device converted ''' to metric units.</returns> ''' <remarks></remarks> Function MetricValue() As Decimal ''' <summary> ''' Converts the raw data collected by the measuring device into an ''' imperial value. ''' </summary> ''' <returns>The latest measurement from the device converted to ''' imperial units.</returns> ''' <remarks></remarks> Function ImperialValue() As Decimal ''' ''' ''' ''' Sub ''' ''' ''' ''' Sub <summary> Starts the measuring device. </summary> <remarks></remarks> StartCollecting() <summary> Stops the measuring device. </summary> <remarks></remarks> StopCollecting()

''' <summary> ''' Enables access to the raw data from the device in whatever units ''' are native to the device. ''' </summary> ''' <returns>The raw data from the device in native format.</returns> ''' <remarks></remarks> Function GetRawData() As Integer() End Interface

Exercise 2: Implementing an Interface


Task 1: Open the starter project.
Open the Module8 solution in the D:\Labfiles\Lab08\Ex2\Starter folder. This solution contains the completed interface from Exercise 1 and skeleton code for Exercise 2. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, in the File name box, browse to the D:\Labfiles\Lab08\Ex2\Starter folder, click Module8.sln, and then click Open.

Task 2: Create the Units enumeration.


The Units enumeration will contain two values, Metric and Imperial. Metric measurements are used in the International System of Units (SI), and include measurements in kilograms and meters. Imperial measurements were originally used in the British Empire, and are similar to customary system units in the United States. 1. Review the Task List. a. b. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

Lab: Inheriting from Classes and Implementing Interfaces

L8-4

2.

In the Task List, double-click the task TODO: Implement the Units enumeration. This task is located in the UnitsEnumeration.vb file. In the Task List, double-click TODO: Implement the Units enumeration.

3.

Remove the TODO comment in the UnitsEnumeration.vb file and declare an enumeration named Units. The enumeration must be accessible from code in different assemblies.

Your code should resemble the following code.


Public Enum Units End Enum

4.

Add the values Metric and Imperial to the enumeration.

Your code should resemble the following code.


Public Enum Units Metric Imperial End Enum

5.

Comment your code to make it easier for developers who use the enumeration.

Your code should resemble the following code.


''' <summary> ''' Public enumeration used in measuring device classes to specify the ''' units used by the device. ''' </summary> ''' <remarks></remarks> Public Enum Units Metric Imperial End Enum

6.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 3: Create the MeasureLengthDevice class.


1. In the Task List, double-click the task TODO: Implement the MeasureLengthDevice class. This task is located in the MeasureLengthDevice.vb file. 2. In the Task List, double-click the task TODO: Implement the MeasureLengthDevice class.

Remove the TODO comment and add a Public class named MeasureLengthDevice.

Your code should resemble the following code.


Public Class MeasureLengthDevice End Class

3.

Modify the MeasureLengthDevice class declaration to implement the IMeasuringDevice interface.

Your code should resemble the following code.


Public Class MeasureLengthDevice Implements IMeasuringDevice

4.

Generate method stubs for each of the methods in the IMeasuringDevice interface.

Lab: Inheriting from Classes and Implementing Interfaces

L8-5

You can generate the method stubs by placing the cursor immediately after the last letter of the interface name and then press Enter. 5. Place the cursor immediately after Implements IMeasuringDevice, and then press Enter.

Bring the DeviceControl namespace into scope.

The MeasuringDevice project already contains a reference to the DeviceController project. You are writing code to control a device. However, because the physical device is not available with this lab, the DeviceController project enables you to call methods that control an emulated device. The DeviceController project does not include a visual interface. To control the device, you must use the classes and methods that the project exposes. The DeviceController project is provided complete. You can review the code if you want, but you do not need to modify it. At the start of the file, add the statement in the following code.

Imports DeviceControl

6.

After the method stubs in the MeasureLengthDevice class, add the fields shown in the following table. Field Type Units Integer() Integer DeviceController DeviceType Accessor Private Private Private Private Private

Field Name unitsToUse dataCaptured mostRecentMeasure controller measurementType

DeviceType is an enumeration that contains the values LENGTH and MASS. It is used to specify the type of measurement that the device records. It is defined in the DeviceController project. Your code should resemble the following code.
Private Private Private Private Private unitsToUse As Units dataCaptured() As Integer mostRecentMeasure As Integer controller As DeviceController measurementType As DeviceType

7.

Modify the measurementType field to make it constant and initialize it to DeviceType.LENGTH.

Your modified code should resemble the following code.


Private Const measurementType As DeviceType = DeviceType.LENGTH

8.

Locate the StartCollecting method and add code to the StartCollecting method to instantiate the controller field by using the shared StartDevice method of the DeviceController class. Pass the value in the measurementType field as the parameter to the StartCollecting method.

Your code should resemble the following code.


Public Sub StartCollecting() Implements IMeasuringDevice.StartCollecting controller = DeviceController.StartDevice(measurementType)

Lab: Inheriting from Classes and Implementing Interfaces

L8-6

End Sub

9.

In the StartCollecting method, call the GetMeasurements method. This method takes no parameters and does not return a value. You will add the GetMeasurements method in the next step.

Your code should resemble the following code.


Public Sub StartCollecting() Implements IMeasuringDevice.StartCollecting controller = DeviceController.StartDevice(measurementType) GetMeasurements() End Sub

10. Add the GetMeasurements method to the class, as shown in the following code. Note: A code snippet is available, called Mod8GetMeasurementsMethod, that you can use to add this method.
Private Sub GetMeasurements() ReDim dataCaptured(9) System.Threading.ThreadPool.QueueUserWorkItem( Sub() Dim x As Integer = 0 Dim timer As New Random() While Not controller Is Nothing System.Threading.Thread.Sleep(timer.Next(1000, 5000)) dataCaptured(x) = If(Not controller Is Nothing, controller.TakeMeasurement(), dataCaptured(x)) mostRecentMeasure = dataCaptured(x) x += 1 If x = 10 Then x = 0 End If End While End Sub)

End Sub

To use the Mod8GetMeasurementsMethod snippet, add a blank line immediately after the StartCollecting method, type Mod8GetMeasurementsMethod and then press Tab.

The GetMeasurements method retrieves measurements from the emulated device. In this module, you will use the code in the GetMeasurements method to populate the dataCaptured array. This array acts as a fixed-length circular buffer, overwriting the oldest value each time a new measurement is taken. In a later module, you will modify this class to respond to events that the device raises whenever it detects a new measurement. 11. Locate the StopCollecting method, and add a conditional code block that only runs if the controller object is not null. Your code should resemble the following code.
Public Sub StopCollecting() Implements IMeasuringDevice.StopCollecting If Not controller Is Nothing Then End If End Sub

Lab: Inheriting from Classes and Implementing Interfaces

L8-7

12. In the conditional code block, add code to call the StopDevice method of the controller object, and then set the controller field to null. Your code should resemble the following code.
Public Sub StopCollecting() Implements IMeasuringDevice.StopCollecting If Not controller Is Nothing Then controller.StopDevice() controller = Nothing End If End Sub

13. Locate the GetRawData method, and then add code to return the dataCaptured array. Your code should resemble the following code.
Public Function GetRawData() As Integer() Implements IMeasuringDevice.GetRawData Return dataCaptured End Function

14. Locate the MetricValue method and add code to check the current units and, if they are metric, return the value from the mostRecentMeasure field. If the current units are imperial, return the result of multiplying the mostRecentMeasure field by 25.4. You can type in this code manually, or you can use the Mod8MetricValueMethod code snippet. Your code should resemble the following code.
Public Function MetricValue() As Decimal Implements IMeasuringDevice.MetricValue Dim metricMostRecentMeasure As Decimal If unitsToUse = Units.Metric Then metricMostRecentMeasure = Convert.ToDecimal(mostRecentMeasure) Else ' Imperial measurements are in inches. ' Multiply imperial measurement by 25.4 to convert from ' inches to millimeters. ' Convert from an integer value to a decimal. Dim decimalImperialValue As Decimal = Convert.ToDecimal(mostRecentMeasure) Dim conversionFactor As Decimal = 25.4 metricMostRecentMeasure = decimalImperialValue * conversionFactor End If Return metricMostRecentMeasure End Function

To use the Mod8MetricValueMethod snippet, on a blank line, type Mod8MetricValueMethod and then press Tab.

Note: This code performs the process of converting from imperial to metric step by step. You can perform this conversion in a single statement as shown below. However, you should consider that code should be as self-documenting as possible so that it can be maintained more easily.
Public Function MetricValue() As Decimal Return If(unitsToUse = Units.Metric), CType(mostRecentMeasure, Decimal), CType(mostRecentMeasure * 25.4, Decimal)

Lab: Inheriting from Classes and Implementing Interfaces

L8-8

End Function

15. Locate the ImperialValue method, and then add code to check the current units and, if they are imperial, return the value from the mostRecentMeasure field. If the current units are metric, return the result of multiplying the mostRecentMeasure field by 0.03937. You can type in this code manually, or you can use the Mod8ImperialValueMethod code snippet. Your code should resemble the following code.
Public Function ImperialValue() As Decimal Implements IMeasuringDevice.ImperialValue Dim imperialMostRecentMeasure As decimal If unitsToUse = Units.Imperial Then imperialMostRecentMeasure = Convert.ToDecimal(mostRecentMeasure) Else ' Metric measurements are in millimeters. ' Multiply metric measurement by 0.03937 to convert from ' millimeters to inches. ' Convert from an integer value to a decimal. Dim decimalMetricValue As Decimal = Convert.ToDecimal(mostRecentMeasure) Dim conversionFactor As Decimal = 0.03937 imperialMostRecentMeasure = decimalMetricValue * conversionFactor End If Return imperialMostRecentMeasure End Function

To use the Mod8ImperialValueMethod snippet, on a blank line, type Mod8ImperialValueMethod and then press Tab.

16. Add to the class a constructor that takes a Units parameter and sets the unitsToUse field to the value specified by this parameter. Your code should resemble the following code.
Public Class MeasureLengthDevice Implements IMeasuringDevice Public Sub New(ByVal deviceUnits As Units) unitsToUse = deviceUnits End Sub ... End Class

17. Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

At the end of this task, your code should resemble the following code.
Imports DeviceControl Public Class MeasureLengthDevice Implements IMeasuringDevice Private Private Private Private unitsToUse As Units dataCaptured() As Integer mostRecentMeasure As Integer controller As DeviceController

Lab: Inheriting from Classes and Implementing Interfaces

L8-9

Private Const measurementType As DeviceType = DeviceType.LENGTH Public Sub New(ByVal deviceUnits As Units) unitsToUse = deviceUnits End Sub Public Function GetRawData() As Integer() Implements IMeasuringDevice.GetRawData Return dataCaptured End Function Public Function ImperialValue() As Decimal Implements IMeasuringDevice.ImperialValue Dim imperialMostRecentMeasure As Decimal If unitsToUse = Units.Imperial Then imperialMostRecentMeasure = Convert.ToDecimal(mostRecentMeasure) Else ' Metric measurements are in millimeters. ' Multiply metric measurement by 0.03937 to convert from ' millimeters to inches. ' Convert from an integer value to a decimal. Dim decimalMetricValue As Decimal = Convert.ToDecimal(mostRecentMeasure) Dim conversionFactor As Decimal = 0.03937D imperialMostRecentMeasure = decimalMetricValue * conversionFactor End If Return imperialMostRecentMeasure End Function Public Function MetricValue() As Decimal Implements IMeasuringDevice.MetricValue Dim metricMostRecentMeasure As Decimal If unitsToUse = Units.Metric Then metricMostRecentMeasure = Convert.ToDecimal(mostRecentMeasure) Else ' Imperial measurements are in inches. ' Multiply imperial measurement by 25.4 to convert from ' inches to millimeters. ' Convert from an integer value to a decimal. Dim decimalImperialValue As Decimal = Convert.ToDecimal(mostRecentMeasure) Dim conversionFactor As Decimal = 25.4D metricMostRecentMeasure = decimalImperialValue * conversionFactor End If Return metricMostRecentMeasure End Function Public Sub StartCollecting() Implements IMeasuringDevice.StartCollecting controller = DeviceController.StartDevice(measurementType) GetMeasurements() End Sub Public Sub StopCollecting() Implements IMeasuringDevice.StopCollecting If Not controller Is Nothing Then controller.StopDevice() controller = Nothing End If End Sub

Lab: Inheriting from Classes and Implementing Interfaces

L8-10

Private Sub GetMeasurements() ReDim dataCaptured(9) System.Threading.ThreadPool.QueueUserWorkItem( Sub() Dim x As Integer = 0 Dim timer As New Random() While Not controller Is Nothing System.Threading.Thread.Sleep(timer.Next(1000, 5000)) dataCaptured(x) = If(Not controller Is Nothing, controller.TakeMeasurement(), dataCaptured(x)) mostRecentMeasure = dataCaptured(x) x += 1 If x = 10 Then x = 0 End If End While End Sub)

End Sub End Class

Task 4: Update the test harness.


The test harness application for this lab is a simple Windows Presentation Foundation (WPF) application that is designed to test the functionality of the MeasureLengthDevice class that you have just developed. It does not include any exception handling to ensure that it does not hide any exceptions thrown by the class that you have developed. 1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

Open the MainWindow.xaml.vb file by clicking the first TODO: Add code to instantiate the device field item in the Task List. This task is located in the CreateInstanceButton_Click method in the WPF window, and it runs when the user clicks the Create Instance button. In the Task List, double-click the first TODO: Add code to instantiate the device field item.

3.

In the CreateInstanceButton_Click method, replace both TODO comments with code to instantiate a field called device and set it to an instance of the MeasureLengthDevice class. You must use the appropriate member of the Units enumeration as the parameter for the MeasureLengthDevice constructor.

Your code should resemble the following code.


Private Sub CreateInstanceButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) If MetricChoiceRadioButton.IsChecked Then device = new MeasureLengthDevice(Units.Metric) Else device = new MeasureLengthDevice(Units.Imperial) End If End Sub

4.

Build the solution and correct any errors.

Lab: Inheriting from Classes and Implementing Interfaces

L8-11

On the Build menu, click Build Solution. Correct any errors.

Task 5: Test the MeasureLengthDevice class by using the test harness.


1. Start the Exercise2TestHarness application. 2. 3. 4. 5. Press Ctrl+F5.

Select the Imperial radio button, and then click Create MeasureLengthDevice Instance. This button runs the code that you added to instantiate the device field that uses imperial measurements. Click Start Collecting. This button runs the StartCollecting method of the device object that the IMeasuringDevice interface defines. Wait for 10 seconds to ensure that the emulated device has generated some values before you perform the following steps. Click Get Raw Data. You should see up to 10 values in the list box in the lower part of the window. This is the data that the device emulator has generated. It is stored in the dataCaptured array by the GetMeasurements method in the MeasureLengthDevice class. The dataCaptured array acts as a fixed-length circular buffer. Initially, it contains zero values, but as the device emulator reports measurements, they are added to this array. When the array is full, it wraps around and starts overwriting data, beginning with the oldest measurement. Click Get Metric Value and Get Imperial Value. You should see the metric and imperial value of the most recently generated measurement. Note that a new measurement might have been taken since you clicked the Get Raw Data button. Click Get Raw Data, and then verify that the imperial value that the previous step displayed is listed in the raw data values. (The value can appear at any point in the list.) Click Stop Collecting. Choose Metric, and then click Create MeasureLengthDevice Instance. This action creates a new instance of the device emulator that uses metric measurements.

6.

7. 8. 9.

10. Click Start Collecting. This button starts the new device object. 11. Wait for 10 seconds. 12. Click Get Metric Value and Get Imperial Value to display the metric and imperial value of the latest measurement that the device has taken. 13. Click Get Raw Data, and then verify that the metric value that the previous step displayed is listed in the raw data values. (The value can appear at any point in the list.) 14. Click Stop Collecting. 15. Close the Exercise 2 Test Harness window.

Exercise 3: Creating an Abstract Class


Task 1: Open the starter project.
Open the Module8 solution in the D:\Labfiles\Lab08\Ex3\Starter folder. This solution contains the completed interface from Exercise 2 and skeleton code for Exercise 3. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, in the File name box, browse to the D:\Labfiles\Lab08\Ex3\Starter folder, click Module8.sln, and then click Open.

Lab: Inheriting from Classes and Implementing Interfaces

L8-12

Task 2: Create the MeasureMassDevice class.


1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

Open the MeasureMassDevice.vb file. In Solution Explorer, double-click MeasureMassDevice.vb.

3.

Replace the TODO comment with a Public class named MeasureMassDevice.

Your code should resemble the following code.


Public Class MeasureMassDevice End Class

4.

Modify the MeasureMassDevice class declaration to implement the IMeasuringDevice interface.

Your code should resemble the following code.


Public Class MeasureMassDevice Implements IMeasuringDevice End Class

5.

Generate method stubs for each of the methods in the IMeasuringDevice interface. Place the cursor immediately after Implements IMeasuringDevice, and then press Enter.

6.

Bring the DeviceControl namespace into scope. At the start of the file, add the statement in the following code.

Imports DeviceControl

The MeasuringDevice project already contains a reference to the DeviceController project. This project implements the DeviceController type, which provides access to the measuring device emulator. 7. After the method stubs that Visual Studio added, add the fields shown in the following table. Field Type Units Integer() Integer DeviceController DeviceType Accessor Private Private Private Private Private

Field Name unitsToUse dataCaptured mostRecentMeasure controller measurementType

Your code should resemble the following code.


Private unitsToUse As Units Private dataCaptured() As Integer

Lab: Inheriting from Classes and Implementing Interfaces

L8-13

Private mostRecentMeasure As Integer Private controller As DeviceController Private measurementType As DeviceType

8.

Modify the measurementType field to make it constant and initialize it to DeviceType.MASS.

Your modified code should resemble the following code.


Private Const measurementType As DeviceType = DeviceType.MASS

9.

Locate the StartCollecting method, and add code to instantiate the controller field by using the shared StartDevice method of the DeviceController class. Pass the measurementType field as the parameter to the StartDevice method.

Your code should resemble the following code.


Public Sub StartCollecting() controller = DeviceController.StartDevice(measurementType) End Sub

10. Add code to call the GetMeasurements method. This method takes no parameters and does not return a value. You will add the GetMeasurements method in the next step. Your code should resemble the following code.
Public Sub StartCollecting() controller = DeviceController.StartDevice(measurementType) GetMeasurements() End Sub

11. Add the GetMeasurements method to the class, as shown in the following code example. Note: A code snippet is available, called Mod8GetMeasurementsMethod, that you can use to add this method.
Private Sub GetMeasurements() ReDim dataCaptured(9) System.Threading.ThreadPool.QueueUserWorkItem( Sub() Dim x As Integer = 0 Dim timer As New Random() While Not controller Is Nothing System.Threading.Thread.Sleep(timer.Next(1000, 5000)) dataCaptured(x) = If(Not controller Is Nothing, controller.TakeMeasurement(), dataCaptured(x)) mostRecentMeasure = dataCaptured(x) x += 1 If x = 10 Then x = 0 End If End While End Sub)

End Sub

To use the Mod8GetMeasurementsMethod snippet, add a blank line immediately after the StartCollecting method, type Mod8GetMeasurementsMethod and then press Tab.

Lab: Inheriting from Classes and Implementing Interfaces

L8-14

This is the same method that you defined for the MeasureLengthDevice class. 12. Locate the StopCollecting method and then remove the default method body that Visual Studio inserts, which throws a NotImplementedException exception. Add a conditional code block that only runs if the controller object is not null. Your code should resemble the following code.
Public Sub StopCollecting() If Not controller Is Nothing Then End If End Sub

13. In the conditional code block, add code to call the StopDevice method of the controller object, and then set the controller field to null. Your code should resemble the following code.
Public Sub StopCollecting() If Not controller Is Nothing Then controller.StopDevice() controller = Nothing End If End Sub

14. Locate the GetRawData method, and then add code to return the dataCaptured array. Your code should resemble the following code.
Public Function GetRawData() As Integer() Return dataCaptured End Function

15. Locate the MetricValue method, and then add code to check the current units and, if they are metric, return the value from the mostRecentMeasure field. If the current units are imperial, return the result of multiplying the mostRecentMeasure field by 0.4536. Your code should resemble the following code.
Public Function MetricValue() As Decimal Dim metricMostRecentMeasure As Decimal If unitsToUse = Units.Metric Then metricMostRecentMeasure = Convert.ToDecimal(mostRecentMeasure) Else ' Imperial measurements are in pounds. ' Multiply imperial measurement by 0.4536 to convert from ' pounds to kilograms. ' Convert from an integer value to a decimal. Dim decimalImperialValue As Decimal = Convert.ToDecimal(mostRecentMeasure) Dim conversionFactor As Decimal = 0.4536D metricMostRecentMeasure = decimalImperialValue * conversionFactor End If Return metricMostRecentMeasure End Function

Lab: Inheriting from Classes and Implementing Interfaces

L8-15

16. Locate the ImperialValue method and then add code to check the current units and, if they are imperial, return the value from the mostRecentMeasure field. If the current units are metric, return the result of multiplying the mostRecentMeasure field by 2.2046. Your code should resemble the following code.
Public Function ImperialValue() As Decimal Dim imperialMostRecentMeasure As Decimal If unitsToUse = Units.Imperial Then imperialMostRecentMeasure = Convert.ToDecimal(mostRecentMeasure) Else ' Metric measurements are in kilograms. ' Multiply metric measurement by 2.2046 to convert from ' kilograms to pounds. ' Convert from an integer value to a decimal. Dim decimalMetricValue As Decimal = Convert.ToDecimal(mostRecentMeasure) Dim conversionFactor As Decimal = 2.2046D imperialMostRecentMeasure = decimalMetricValue * conversionFactor End If Return imperialMostRecentMeasure End Function

17. Add to the class a constructor that takes a Units parameter and sets the unitsToUse field to the value specified by this parameter. Your code should resemble the following code.
Public Class MeasureMassDevice Implements IMeasuringDevice Public Sub New(ByVal deviceUnits As Units) unitsToUse = deviceUnits End Sub ... End Class

18. Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

At the end of this task, your code should resemble the following code.
Imports DeviceControl Public Class MeasureMassDevice Implements IMeasuringDevice Private Private Private Private Private unitsToUse As Units dataCaptured() As Integer mostRecentMeasure As Integer controller As DeviceController Const measurementType As DeviceType = DeviceType.MASS

Public Sub New(ByVal deviceUnits As Units) unitsToUse = deviceUnits End Sub

Lab: Inheriting from Classes and Implementing Interfaces

L8-16

Public Function GetRawData() As Integer() Implements IMeasuringDevice.GetRawData Return dataCaptured End Function Public Function ImperialValue() As Decimal Implements IMeasuringDevice.ImperialValue Dim imperialMostRecentMeasure As Decimal If unitsToUse = Units.Imperial Then imperialMostRecentMeasure = Convert.ToDecimal(mostRecentMeasure) Else ' Metric measurements are in kilograms. ' Multiply metric measurement by 2.2046 to convert from ' kilograms to pounds. ' Convert from an integer value to a decimal. Dim decimalMetricValue As Decimal = Convert.ToDecimal(mostRecentMeasure) Dim conversionFactor As Decimal = 2.2046D imperialMostRecentMeasure = decimalMetricValue * conversionFactor End If Return imperialMostRecentMeasure End Function Public Function MetricValue() As Decimal Implements IMeasuringDevice.MetricValue Dim metricMostRecentMeasure As Decimal If unitsToUse = Units.Metric Then metricMostRecentMeasure = Convert.ToDecimal(mostRecentMeasure) Else ' Imperial measurements are in pounds. ' Multiply imperial measurement by 0.4536 to convert from ' pounds to kilograms. ' Convert from an integer value to a decimal. Dim decimalImperialValue As Decimal = Convert.ToDecimal(mostRecentMeasure) Dim conversionFactor As Decimal = 0.4536D metricMostRecentMeasure = decimalImperialValue * conversionFactor End If Return metricMostRecentMeasure End Function Public Sub StartCollecting() Implements IMeasuringDevice.StartCollecting controller = DeviceController.StartDevice(measurementType) GetMeasurements() End Sub Private Sub GetMeasurements() ReDim dataCaptured(9) System.Threading.ThreadPool.QueueUserWorkItem( Sub() Dim x As Integer = 0 Dim timer As New Random() While Not controller Is Nothing System.Threading.Thread.Sleep(timer.Next(1000, 5000)) dataCaptured(x) = If(Not controller Is Nothing, controller.TakeMeasurement(), dataCaptured(x))

Lab: Inheriting from Classes and Implementing Interfaces

L8-17

mostRecentMeasure = dataCaptured(x) x += 1 If x = 10 Then x = 0 End If End While End Sub)

End Sub

Public Sub StopCollecting() Implements IMeasuringDevice.StopCollecting If Not controller Is Nothing Then controller.StopDevice() controller = Nothing End If End Sub End Class

Task 3: Update the test harness.


The test harness application in this lab is a modified version of the WPF application that you used in Exercise 2. It is designed to test the functionality of the MeasureLengthDevice and MeasureMassDevice classes. It does not include any exception handling to ensure that it does not hide any exceptions thrown by the class that you have developed. 1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

Open the MainWindow.xaml.vb file by clicking the first TODO: Instantiate the device field by using the new MeasureMassDevice class item in the Task List. In the Task List, double-click the first TODO: Instantiate the device field by using the new MeasureMassDevice class item.

3.

In the CreateInstanceButton_Click method, replace both TODO comments with code to instantiate the device field to an instance of the MeasureMassDevice class. You must use the appropriate member of the Units enumeration as the parameter for the MeasureMassDevice constructor.

Your code should resemble the following code.


Case "Mass Device" If MetricChoiceRadioButton.IsChecked Then device = new MeasureMassDevice(Units.Metric) Else device = new MeasureMassDevice(Units.Imperial) End If

4.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 4: Test the MeasureMassDevice class by using the test harness.


1. Start the Exercise3TestHarness application. Press Ctrl+F5.

Lab: Inheriting from Classes and Implementing Interfaces

L8-18

2. 3. 4. 5. 6. 7. 8. 9.

Select the Imperial radio button, in the list, click Mass Device, and then click Create Instance. This button runs the code that you added to instantiate the device field that uses imperial measurements. Click Start Collecting. This button runs the StartCollecting method of the MeasureMassDevice object. Wait for 10 seconds to ensure that the emulated device has generated some values before you perform the following steps. Click Get Metric Value and Get Imperial Value. You should see the metric and imperial value of the most recently generated measurement. Click Get Raw Data, and then verify that the imperial value that the previous step displayed is listed in the raw data values. (The value can appear at any point in the list.) Click Stop Collecting. Choose Metric, and then click Create Instance. This action creates a new instance of the device emulator that uses metric measurements. Click Start Collecting. This button starts the new device object.

10. Wait for 10 seconds. 11. Click Get Metric Value and Get Imperial Value to display the metric and imperial value of the latest measurement that the device has taken. 12. Click Get Raw Data, and then verify that the metric value that the previous step displayed is listed in the raw data values. (The value can appear at any point in the list.) 13. Click Stop Collecting. 14. Close the Exercise 3 Test Harness window.

Task 5: Create the MeasureDataDevice abstract class.


You have developed two classes, MeasureLengthDevice and MeasureMassDevice. Much of the functionality of these classes is common to both. This code duplication is unnecessary and risks introducing bugs. To reduce the code that is required and the risk of introducing bugs, you need to create an abstract class that will contain the common functionality. 1. Open the MeasureDataDevice.vb file. 2. In Solution Explorer, double-click MeasureDataDevice.vb.

Remove the TODO comment and add a MustInherit class named MeasureDataDevice.

Your code should resemble the following code.


Public MustInherit Class MeasureDataDevice End Class

3.

Modify the MeasureDataDevice class declaration to implement the IMeasuringDevice interface, but do not generate the method stubs.

Your code should resemble the following code.


Public MustInherit Class MeasureDataDevice Implements IMeasuringDevice

4.

Bring the DeviceControl namespace into scope. At the start of the file, add the statement in the following code example.

Lab: Inheriting from Classes and Implementing Interfaces

L8-19

Imports DeviceControl

5.

In the MeasureDataDevice class, add a Public MustOverride method named MetricValue. This method should return a Decimal value, but not take any parameters. The implementation of the MetricValue method is specific to the type of device being controlled, so you must implement this functionality in the child classes. Declaring the MetricValue method as MustOverride forces child classes to implement this method.

Hint: Look at the code for the MetricValue method for the MeasureLengthDevice and MeasureMassDevice classes. You will observe that they are quite similar, apart from the conversion factors that are used, and you could factor this logic into a method in the abstract MeasureDataDevice class. However, for the sake of this exercise, assume that these methods are totally different. The same note applies to the ImperialValue method that you will define in the next step. Your code should resemble the following code.
Public MustInherit Class MeasureDataDevice Implements IMeasuringDevice Public MustOverride Function MetricValue() As Decimal Implements IMeasuringDevice.MetricValue End Class

6.

In the MeasureDataDevice class, add a Public MustOverride method with a Decimal return type named ImperialValue. Like the MetricValue method, the implementation of the ImperialValue method is specific to the type of device being controlled, so you must implement this functionality in the child classes. Your code should resemble the following code.

Public MustInherit Class MeasureDataDevice Implements IMeasuringDevice Public MustOverride Function MetricValue() As Decimal Implements IMeasuringDevice.MetricValue Public MustOverride Function ImperialValue() As Decimal Implements IMeasuringDevice.ImperialValue End Class

7.

In the MeasureLengthDevice.vb file, locate and copy the code for the StartCollecting method, and then add this method to the MeasureDataDevice class. a. b. In Solution Explorer, double-click MeasureLengthDevice.vb. In the MeasureLengthDevice.vb file, locate and select the following code, and then press Ctrl+C.

Public Sub StartCollecting() Implements IMeasuringDevice.StartCollecting controller = DeviceController.StartDevice(measurementType) GetMeasurements() End Sub

c. d.

Return to the MeasureDataDevice.vb file. In the MeasureDataDevice class, add two blank lines after the declaration in the following code example.

Lab: Inheriting from Classes and Implementing Interfaces

L8-20

Public MustOverride Function ImperialValue() As Decimal

e.

Press Ctrl+V.

Visual Studio will warn you that the controller variable, the measurementType enumeration, and the GetMeasurements method are not defined. You will add these items to the MeasureDataDevice class in later steps in this task. 8. Copy the StopCollecting method from the MeasureLengthDevice.vb file to the MeasureDataDevice class. a. b. In Solution Explorer, double-click MeasureLengthDevice.vb. In the MeasureLengthDevice.vb file, locate and select the following code, and then press Ctrl+C.

Public Sub StopCollecting() Implements IMeasuringDevice.StopCollecting If Not controller Is Nothing Then controller.StopDevice() controller = Nothing End If End Sub

c. d. e.

Return to the MeasureDataDevice.vb file. In the MeasureDataDevice class, add two blank lines after the StartCollecting method. Press Ctrl+V.

Visual Studio will warn you that the controller variable is not defined. 9. Copy the GetRawData method from the MeasureLengthDevice.vb file to the MeasureDataDevice class. a. b. In Solution Explorer, double-click MeasureLengthDevice.vb. In the MeasureLengthDevice.vb file, locate and select the following code, and then press Ctrl+C.

Public Function GetRawData() As Integer() Implements IMeasuringDevice.GetRawData Return dataCaptured End Function

c. d. e.

Return to the MeasureDataDevice.vb file. In the MeasureDataDevice class, add two blank lines after the StopCollecting method. Press Ctrl+V.

Visual Studio will warn you that the dataCaptured variable is not defined. 10. Copy the GetMeasurements method from the MeasureLengthDevice.vb file to the MeasureDataDevice class. a. b. In Solution Explorer, double-click MeasureLengthDevice.vb. In the MeasureLengthDevice.vb file, locate and select the following code, and then press Ctrl+C.

Private Sub GetMeasurements() ReDim dataCaptured(9) System.Threading.ThreadPool.QueueUserWorkItem( Sub() Dim x As Integer = 0 Dim timer As New Random()

Lab: Inheriting from Classes and Implementing Interfaces

L8-21

While Not controller Is Nothing System.Threading.Thread.Sleep(timer.Next(1000, 5000)) dataCaptured(x) = If(Not controller Is Nothing, controller.TakeMeasurement(), dataCaptured(x)) mostRecentMeasure = dataCaptured(x) x += 1 If x = 10 Then x = 0 End If End While End Sub)

End Sub

c. d. e.

Return to the MeasureDataDevice.vb file. In the MeasureDataDevice class, add two blank lines after the GetRawData method. Press Ctrl+V.

Visual Studio will warn you that the dataCaptured, controller, and mostRecentMeasure variables are not defined. 11. Copy the five fields in the following table from the MeasureLengthDevice.vb file to the MeasureDataDevice class. Field Name unitsToUse dataCaptured mostRecentMeasure controller measurementType a. b. Field Type Units Integer() Integer DeviceController DeviceType Accessor Private Private Private Private Private

In Solution Explorer, double-click MeasureLengthDevice.vb. In the MeasureLengthDevice.vb file, locate and select the following code, and then press Ctrl+C.
unitsToUse As Units dataCaptured() As Integer mostRecentMeasure As Integer controller As DeviceController Const measurementType As DeviceType = DeviceType.LENGTH

Private Private Private Private Private

c. d.

Return to the MeasureDataDevice.vb file. In the MeasureDataDevice class, place the cursor after the class declaration, and then press Ctrl+V.

The warnings in the StartCollecting, StopCollecting, GetRawData, and GetMeasurements methods should disappear. 12. In the MeasureDataDevice class, modify the five fields that you added in the previous step to make them visible to classes that inherit from the abstract class. Change each of the accessors from Private to Protected. Your code should resemble the following code.

Lab: Inheriting from Classes and Implementing Interfaces

L8-22

Protected Protected Protected Protected Protected

unitsToUse As Units dataCaptured() As Integer mostRecentMeasure As Integer controller As DeviceController Const measurementType As DeviceType = DeviceType.LENGTH

13. Modify the declaration of the measurementType field so that it is no longer constant and not instantiated when it is declared. Modify the last line of code in the previous code example so that it resembles the following code.

Protected measurementType As DeviceType

14. Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 6: Modify the MeasureLengthDevice and MeasureMassDevice classes to inherit


from the MeasureDataDevice abstract class.
In this task, you will remove the duplicated code from the MeasureLengthDevice and MeasureMassDevice classes by modifying them to inherit from the MeasureDataDevice abstract class that you created in the previous task. 1. In the MeasureLengthDevice.vb file, modify the declaration of the MeasureLengthDevice class so that, in addition to implementing the IMeasuringDevice interface, it also inherits from the MeasureDataDevice class. In the MeasureLengthDevice.vb file, change the class declaration as shown in the following code example.

Public Class MeasureLengthDevice Inherits MeasureDataDevice Implements IMeasuringDevice

2.

Remove the StartCollecting method from the MeasureLengthDevice class. Remove the following code.

Public Sub StartCollecting() Implements IMeasuringDevice.StartCollecting controller = DeviceController.StartDevice(measurementType) GetMeasurements() End Sub

3.

Remove the StopCollecting method from the MeasureLengthDevice class. Remove the following code.

Public Sub StopCollecting() Implements IMeasuringDevice.StopCollecting If Not controller Is Nothing Then controller.StopDevice() controller = Nothing End If End Sub

4.

Remove the GetRawData method from the MeasureLengthDevice class. Remove the following code.

Public Function GetRawData() As Integer() Implements IMeasuringDevice.GetRawData

Lab: Inheriting from Classes and Implementing Interfaces

L8-23

Return dataCaptured End Function

5.

Remove the GetMeasurements method from the MeasureLengthDevice class. Remove the following code.

Private Sub GetMeasurements() ReDim dataCaptured(9) System.Threading.ThreadPool.QueueUserWorkItem( Sub() Dim x As Integer = 0 Dim timer As New Random() While Not controller Is Nothing System.Threading.Thread.Sleep(timer.Next(1000, 5000)) dataCaptured(x) = If(Not controller Is Nothing, controller.TakeMeasurement(), dataCaptured(x)) mostRecentMeasure = dataCaptured(x) x += 1 If x = 10 Then x = 0 End If End While End Sub)

End Sub

6.

Remove the fields in the following table from the MeasureLengthDevice class. Field Type Units Integer() Integer DeviceController DeviceType Accessor Private Private Private Private Private

Field Name unitsToUse dataCaptured mostRecentMeasure controller measurementType

Remove the declarations in the following code.


unitsToUse As Units dataCaptured() As Integer mostRecentMeasure As Integer controller As DeviceController Const measurementType As DeviceType = DeviceType.LENGTH

Private Private Private Private Private

7.

Modify the constructor to set the measurementType field to DeviceType.LENGTH. Modify the code to resemble the following code.

Public Sub New(ByVal deviceUnits As Units) unitsToUse = deviceUnits measurementType = DeviceType.LENGTH End Sub

Lab: Inheriting from Classes and Implementing Interfaces

L8-24

8.

Modify the MetricValue method signature to indicate that it overrides the abstract method in the base class. Modify the code to resemble the following code.

Public Overrides Function MetricValue() As Decimal ... End Function

9.

Modify the ImperialValue method signature to indicate that it overrides the abstract method in the base class. Modify the code to resemble the following code.

Public Overrides Function ImperialValue() As Decimal ... End Function

10. In the MeasureMassDevice.vb file, modify the declaration of the MeasureMassDevice class so that it inherits from the MeasureDataDevice class. On the MeasureMassDevice.vb tab, change the class declaration as shown in the following code.

Public Class MeasureMassDevice Inherits MeasureDataDevice Implements IMeasuringDevice

11. Remove the StartCollecting method from the MeasureMassDevice class. Remove the following code.

Public Sub StartCollecting() Implements IMeasuringDevice.StartCollecting controller = DeviceController.StartDevice(measurementType) GetMeasurements() End Sub

12. Remove the StopCollecting method from the MeasureMassDevice class. Remove the following code.

Public Sub StopCollecting() Implements IMeasuringDevice.StopCollecting If Not controller Is Nothing Then controller.StopDevice() controller = Nothing End If End Sub

13. Remove the GetRawData method from the MeasureMassDevice class. Remove the following code.

Public Function GetRawData() As Integer() Implements IMeasuringDevice.GetRawData Return dataCaptured End Function

14. Remove the GetMeasurements method from the MeasureMassDevice class. Remove the following code.

Private Sub GetMeasurements()

Lab: Inheriting from Classes and Implementing Interfaces

L8-25

ReDim dataCaptured(9) System.Threading.ThreadPool.QueueUserWorkItem( Sub() Dim x As Integer = 0 Dim timer As New Random() While Not controller Is Nothing System.Threading.Thread.Sleep(timer.Next(1000, 5000)) dataCaptured(x) = If(Not controller Is Nothing, controller.TakeMeasurement(), dataCaptured(x)) mostRecentMeasure = dataCaptured(x) x += 1 If x = 10 Then x = 0 End If End While End Sub)

End Sub

15. Remove the fields in the following table from the MeasureMassDevice class. Field Name unitsToUse dataCaptured mostRecentMeasure controller measurementType Field Type Units Integer() Integer DeviceController DeviceType Accessor Private Private Private Private Private

Remove the following code.


unitsToUse As Units dataCaptured() As Integer mostRecentMeasure As Integer controller As DeviceController Const measurementType As DeviceType = DeviceType.MASS

Private Private Private Private Private

16. Modify the constructor to set the measurementType field to DeviceType.MASS. Modify the code to resemble the following code.

Public Sub New(ByVal deviceUnits As Units) unitsToUse = deviceUnits measurementType = DeviceType.MASS End Sub

17. Modify the MetricValue method signature to indicate that it overrides the abstract method in the base class. Modify the code to resemble the following code.

Public Overrides Function MetricValue() As Decimal ... End Function

Lab: Inheriting from Classes and Implementing Interfaces

L8-26

18. Modify the ImperialValue method signature to indicate that it overrides the abstract method in the base class. Modify the code to resemble the following code.

Public Overrides Function ImperialValue() As Decimal ... End Function

19. Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 7: Test the classes by using the test harness.


In this task, you will check that the MeasureLengthDevice and MeasureMassDevice classes still work as expected. 1. Start the Exercise3TestHarness application. 2. 3. 4. 5. 6. 7. 8. 9. Press Ctrl+F5.

Select the Imperial radio button, in the list, click Mass Device, and then click Create Instance. Click Start Collecting. Wait for 10 seconds to ensure that the emulated device has generated some values before you perform the following steps. Click Get Metric Value and Get Imperial Value to display the metric and imperial value of the latest measurement that the device has taken. Click Get Raw Data, and then verify that the imperial value that the previous step displayed is listed in the raw data values. (The value can appear at any point in the list.) Click Stop Collecting. Select the Metric radio button, in the list, click Length Device, and then click Create Instance. Click Start Collecting. This button starts the new device object.

10. Wait for 10 seconds. 11. Click Get Metric Value and Get Imperial Value to display the metric and imperial value of the latest measurement that the device has taken. 12. Click Get Raw Data, and then verify that the metric value that the previous step displayed is listed in the raw data values. (The value can appear at any point in the list.) 13. Click Stop Collecting. 14. Close the Exercise 3 Test Harness window. 15. Close Visual Studio. In Visual Studio, on the File menu, click Exit.

Lab: Managing the Lifetime of Objects and Controlling Resources

L9-1

Module 9: Managing the Lifetime of Objects and Controlling Resources

Lab: Managing the Lifetime of Objects and Controlling Resources


Exercise 1: Implementing the IDisposable Interface
Task 1: Open the starter project.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Import the code snippets from the D:\Labfiles\Lab09\Snippets folder. a. b. c. d. e. In Visual Studio, on the Tools menu, click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language drop-down, click Visual Basic. In the Code Snippets Manager dialog box, click Add. In the Code Snippets Directory dialog box, browse to the D:\Labfiles\Lab09\Snippets folder, and then click Select Folder. In the Code Snippets Manager dialog box, click OK.

3.

Open the Module9 solution in the D:\Labfiles\Lab09\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, in the File name box, browse to the D:\Labfiles\Lab09\Ex1\Starter folder, click Module9.sln, and then click Open.

Task 2: Create the ILoggingMeasuringDevice interface.


In this task, you will develop the ILoggingMeasuringDevice interface. You will develop this new interface, which inherits from the existing IMeasuringDevice interface, rather than edit the existing interface. This will ensure compatibility with existing code. 1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

Open the ILoggingMeasuringDevice.vb file. In Solution Explorer, double-click ILoggingMeasuringDevice.vb.

3.

Remove the TODO comment and declare an interface named ILoggingMeasuringDevice. The interface must be accessible from code in different assemblies.

Your code should resemble the following code.


Public Interface ILoggingMeasuringDevice End Interface

4.

Modify the interface to inherit from the IMeasuringDevice interface.

L9-2

Lab: Managing the Lifetime of Objects and Controlling Resources

Your code should resemble the following code.


Public Interface ILoggingMeasuringDevice Inherits IMeasuringDevice

5.

Add a method named GetLoggingFile that returns a String value to the interface. The method should take no parameters. The purpose of this method is to return the file name of the logging file used by the device. Add an XML comment that summarizes the purpose of the method.

Your code should resemble the following code.


Public Interface ILoggingMeasuringDevice Inherits IMeasuringDevice ''' <summary> ''' Returns the file name of the logging file for the device. ''' </summary> ''' <returns>The file name for the logging file.</returns> ''' <remarks></remarks> Function GetLoggingFile() As String End Interface

6.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 3: Modify the MeasureDataDevice class to implement the


ILoggingMeasuringDevice interface.
In this task, you will modify the existing MeasureDataDevice class to implement the ILoggingMeasuringDevice interface. You will add code to enable logging and modify existing methods to use the logging functionality. 1. Open the MeasureDataDevice.vb file. In Solution Explorer, double-click MeasureDataDevice.vb. 2. Locate and remove the TODO: Modify this class to implement the ILoggingMeasuringDevice interface instead of the IMeasuringDevice interface. comment above the MeasureDataDevice class declaration. Modify the MeasureDataDevice class to implement the ILoggingMeasuringDevice interface, instead of the IMeasuringDevice interface.

Your code should resemble the following code.


' TODO: Modify this class to implement the IDisposable interface. Public MustInherit Class MeasureDataDevice Implements ILoggingMeasuringDevice ... End Class

3. 4.

In the Task List, locate and double-click the comment TODO: Add fields necessary to support logging. This item opens the relevant line in the MeasureDataDevice.vb file. Remove the TODO comment and add a String field named loggingFileName. This field must be accessible to classes that inherit from this class. This field will store the file name and path for the log file.

Your code should resemble the following code.


... Protected measurementType As DeviceType

Lab: Managing the Lifetime of Objects and Controlling Resources

L9-3

Protected loggingFileName As String ...

5.

Add a TextWriter field named loggingFileWriter. This field should only be accessible to code in this class. You will use this object to write to a file.

Your code should resemble the following code.


... Protected loggingFileName As String Private loggingFileWriter As TextWriter ...

6.

In the Task List, locate and double-click the comment TODO: Add methods to implement the ILoggingMeasuringDevice interface. This item opens the relevant line in the MeasureDataDevice.vb file. Remove the TODO comment and add the GetLoggingFile method defined in the ILoggingMeasuringDevice interface. The method should take no parameters and return the value in the loggingFileName field.

7.

Your code should resemble the following code.


... End Sub Public Function GetLoggingFile() As String Implements ILoggingMeasuringDevice.GetLoggingFile Return loggingFileName End Function ...

8. 9.

In the Task List, locate and double-click the comment TODO: Add code to open a logging file and write an initial entry. This item opens the relevant line in the MeasureDataDevice.vb file. Remove the TODO comment and add the following code to instantiate the loggingFileWriter field. You can either type this code manually, or you can use the Mod9InstantiateLoggingFileWriter code snippet.

' New code to check the logging file is not already open. ' If it is already open then write a log message. ' If not, open the logging file. If loggingFileWriter Is Nothing Then ' Check if the logging file exists - if not create it. If Not File.Exists(loggingFileName) Then loggingFileWriter = File.CreateText(loggingFileName) loggingFileWriter.WriteLine("Log file status checked - Created") loggingFileWriter.WriteLine("Collecting Started") Else loggingFileWriter = New StreamWriter(loggingFileName) loggingFileWriter.WriteLine("Log file status checked - Opened") loggingFileWriter.WriteLine("Collecting Started") End If Else loggingFileWriter.WriteLine("Log file status checked - Already open") loggingFileWriter.WriteLine("Collecting Started") End If

L9-4

Lab: Managing the Lifetime of Objects and Controlling Resources

The code checks whether the loggingFileWriter object has already been instantiated. If it has not, the code instantiates it by checking whether the file specified by the loggingFileName field already exists. If the file exists, the code opens the file; if it does not exist, the code creates a new file. To use the code snippet, delete the TODO comment, type Mod9InstantiateLoggingFileWriter and then press the TAB key.

10. In the Task List, locate and double-click the comment TODO: Add code to write a message to the log file. This item opens the relevant line in the MeasureDataDevice.vb file. 11. Remove the TODO comment and add code to write a message to the log file. Your code should check that the loggingFileWriter object is instantiated before writing the message. Your code should resemble the following code.
Public Sub StopCollecting() Implements IMeasuringDevice.StopCollecting If Not controller Is Nothing Then controller.StopDevice() controller = Nothing End If ' New code to write to the log. If Not loggingFileWriter Is Nothing Then loggingFileWriter.WriteLine("Collecting Stopped.") End If End Sub

12. In the Task List, locate and double-click the comment TODO: Add code to log each time a measurement is taken. This item opens the relevant line in the MeasureDataDevice.vb file. 13. Remove the TODO comment and add code to write a message to the log file. Your code should check that the loggingFileWriter object is instantiated before writing the message. Your code should resemble the following code.
While True System.Threading.Thread.Sleep(timer.Next(1000, 5000)) dataCaptured(x) = controller.TakeMeasurement() mostRecentMeasure = dataCaptured(x) If Not loggingFileWriter Is Nothing Then loggingFileWriter.WriteLine("Measurement Taken: {0}", mostRecentMeasure.ToString()) End If x += 1 If x = 10 Then x = 0 End If End While

14. Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 4: Modify the MeasureDataDevice class to implement the IDisposable interface.


In this task, you will modify the existing MeasureDataDevice class to implement the IDisposable interface. You will add code to ensure that the TextWriter object that writes messages to the log file is properly closed when an instance of the MeasureDataDevice class is disposed of.

Lab: Managing the Lifetime of Objects and Controlling Resources

L9-5

1.

At the top of the MeasureDataDevice class, remove the comment TODO: Modify this class to implement the IDisposable interface, and then modify the MeasureDataDevice class to implement the IDisposable interface in addition to the ILoggingMeasuringDevice interface.

Your code should resemble the following code.


Public MustInherit Class MeasureDataDevice Implements ILoggingMeasuringDevice, IDisposable ... End Class

2.

Use the Implement Interface Wizard to generate method stubs for each of the methods in the IDisposable interface. Place the cursor immediately after Implements ILoggingMeasuringDevice, IDisposable and then press Enter.

3.

Move to the end of the MeasureDataDevice class. In the Dispose method overload added, Protected Overridable Sub Dispose(ByVal disposing As Boolean), check that the disposing parameter is set to True. If it is, check if the loggingFileWriter object is not null, write the message Object disposed to the logging file, flush the contents of the loggingFileWriter object, close it, and set the loggingFileWriter variable to Nothing. You can either type this code manually, or you can use the Mod9Disposing code snippet.

Your code should resemble the following code.


Protected Overridable Sub Dispose(ByVal disposing As Boolean) If Not Me.disposedValue Then If disposing Then ' TODO: dispose managed state (managed objects). ' Check that the log file is closed; if it is not closed, log ' a message and close it. If Not loggingFileWriter Is Nothing Then loggingFileWriter.WriteLine("Object Disposed") loggingFileWriter.Flush() loggingFileWriter.Close() loggingFileWriter = Nothing End If End If below. ' TODO: free unmanaged resources (unmanaged objects) and override Finalize()

' TODO: set large fields to null. End If Me.disposedValue = True End Sub

4.

To use the code snippet, immediately after If disposing Then, type Mod9Disposing and then press the TAB key.

Locate the Dispose overload, which takes no parameters, and notice the default method body inserted by Visual Basic, to correctly implement the disposable pattern.

Public Sub Dispose() Implements IDisposable.Dispose ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above. Dispose(True) GC.SuppressFinalize(Me) End Sub

5.

Build the solution and correct any errors.

L9-6

Lab: Managing the Lifetime of Objects and Controlling Resources

On the Build menu, click Build Solution. Correct any errors.

At the end of this task, the MeasuringDataDevice class should resemble the following code example.
Imports System.IO Imports DeviceControl Public MustInherit Class MeasureDataDevice Implements ILoggingMeasuringDevice, IDisposable Protected unitsToUse As Units Protected dataCaptured() As Integer Protected mostRecentMeasure As Integer Protected controller As DeviceController Protected measurementType As DeviceType Protected loggingFileName As String Private loggingFileWriter As TextWriter ''' <summary> ''' Converts the raw data collected by the measuring device into a metric value. ''' </summary> ''' <returns>The latest measurement from the device converted to metric units.</returns> ''' <remarks></remarks> Public MustOverride Function MetricValue() As Decimal Implements IMeasuringDevice.MetricValue ''' <summary> ''' Converts the raw data collected by the measuring device into an imperial value. ''' </summary> ''' <returns>The latest measurement from the device converted to imperial units.</returns> ''' <remarks></remarks> Public MustOverride Function ImperialValue() As Decimal Implements IMeasuringDevice.ImperialValue ''' <summary> ''' Starts the measuring device. ''' </summary> ''' <remarks></remarks> Public Sub StartCollecting() Implements IMeasuringDevice.StartCollecting controller = DeviceController.StartDevice(measurementType) ' New code to check the logging file is not already open. ' If it is already open then write a log message. ' If not, open the logging file. If loggingFileWriter Is Nothing Then ' Check if the logging file exists - if not create it. If Not File.Exists(loggingFileName) Then loggingFileWriter = File.CreateText(loggingFileName) loggingFileWriter.WriteLine("Log file status checked - Created") loggingFileWriter.WriteLine("Collecting Started") Else loggingFileWriter = New StreamWriter(loggingFileName) loggingFileWriter.WriteLine("Log file status checked - Opened") loggingFileWriter.WriteLine("Collecting Started") End If Else loggingFileWriter.WriteLine("Log file status checked - Already open") loggingFileWriter.WriteLine("Collecting Started") End If GetMeasurements() End Sub

Lab: Managing the Lifetime of Objects and Controlling Resources

L9-7

''' <summary> ''' Stops the measuring device. ''' </summary> ''' <remarks></remarks> Public Sub StopCollecting() Implements IMeasuringDevice.StopCollecting If Not controller Is Nothing Then controller.StopDevice() controller = Nothing End If ' New code to write to the log. If Not loggingFileWriter Is Nothing Then loggingFileWriter.WriteLine("Collecting Stopped.") End If End Sub ''' <summary> ''' Enables access to the raw data from the device in whatever units are native to the device. ''' </summary> ''' <returns>The raw data from the device in native format.</returns> ''' <remarks></remarks> Public Function GetRawData() As Integer() Implements IMeasuringDevice.GetRawData Return dataCaptured End Function Private Sub GetMeasurements() ReDim dataCaptured(9) System.Threading.ThreadPool.QueueUserWorkItem( Sub() Dim x As Integer = 0 Dim timer As New Random() While True System.Threading.Thread.Sleep(timer.Next(1000, 5000)) dataCaptured(x) = controller.TakeMeasurement() mostRecentMeasure = dataCaptured(x) If Not loggingFileWriter Is Nothing Then loggingFileWriter.WriteLine("Measurement Taken: {0}", mostRecentMeasure.ToString()) End If x += 1 If x = 10 Then x = 0 End If End While End Sub)

End Sub

Public Function GetLoggingFile() As String Implements ILoggingMeasuringDevice.GetLoggingFile Return loggingFileName End Function #Region "IDisposable Support" Private disposedValue As Boolean ' To detect redundant calls ' IDisposable Protected Overridable Sub Dispose(ByVal disposing As Boolean)

L9-8

Lab: Managing the Lifetime of Objects and Controlling Resources

If Not Me.disposedValue Then If disposing Then ' TODO: dispose managed state (managed objects). ' Check that the log file is closed; if it is not closed, log ' a message and close it. If Not loggingFileWriter Is Nothing Then loggingFileWriter.WriteLine("Object Disposed") loggingFileWriter.Flush() loggingFileWriter.Close() loggingFileWriter = Nothing End If End If below. ' TODO: free unmanaged resources (unmanaged objects) and override Finalize()

' TODO: set large fields to null. End If Me.disposedValue = True End Sub

' TODO: override Finalize() only if Dispose(ByVal disposing As Boolean) above has code to free unmanaged resources. 'Protected Overrides Sub Finalize() ' ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above. ' Dispose(False) ' MyBase.Finalize() 'End Sub ' This code added by Visual Basic to correctly implement the disposable pattern. Public Sub Dispose() Implements IDisposable.Dispose ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above. Dispose(True) GC.SuppressFinalize(Me) End Sub #End Region End Class

Task 5: Modify the MeasureMassDevice class to use logging.


In this task, you will modify the existing MeasureMassDevice class to set the loggingFileName field when the class is instantiated. 1. Open the MeasureMassDevice.vb file. 2. In Solution Explorer, double-click MeasureMassDevice.vb.

In the MeasureMassDevice class, remove the comment TODO: Modify the constructor to set the log filename based on a string parameter, and then modify the constructor to take a String parameter called logFileName. In the body of the constructor, set the loggingFileName field to the logFileName parameter. You should also update the XML comments for the constructor to describe the new parameter.

Your code should resemble the following code.


''' ''' ''' ''' ''' ''' <summary> Construct a new instance of the MeasureMassDevice class. </summary> <param name="deviceUnits">Specifies the units used natively by the device.</param> <param name="logFileName">Specifies the required file name used for logging in the class.</param>

Lab: Managing the Lifetime of Objects and Controlling Resources

L9-9

''' <remarks></remarks> Public Sub New(ByVal deviceUnits As Units, ByVal logFileName As String) unitsToUse = deviceUnits measurementType = DeviceType.MASS loggingFileName = logFileName End Sub

3.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Exercise 2: Managing Resources Used by an Object


Task 1: Open the starter project.
Open the Module9 solution from the D:\Labfiles\Lab09\Ex2\Starter folder. This solution contains the completed code from Exercise 1 and skeleton code for Exercise 2. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, in the File name box, browse to the D:\Labfiles\Lab09\Ex2\Starter folder, click Module9.sln, and then click Open.

Task 2: Test the logging functionality by using the test harness.


1. Run the Exercise2TestHarness application. 2. Press Ctrl+F5.

Click Get Measurements. This action causes the application to pause for 20 seconds while some measurements data is generated and then display this data. This pause is necessary because the application waits for measurement data from the emulated device.

Note that the measurement data is logged to the D:\Labfiles\Lab09\LogFile.txt file by default. 3. 4. After the application populates the text boxes with data from the emulated device, close the Exercise 2 window. Using Notepad, open the LogFile.txt file in the D:\Labfiles\Lab09 folder. a. b. c. 5. Click Start, point to All Programs, click Accessories, and then click Notepad. In Notepad, on the File menu, click Open. In the Open dialog box, in the File name box, browse to the D:\Labfiles\Lab09\ folder, click LogFile.txt, and then click Open.

Review the contents of the LogFile.txt file.

The file is empty. Although the application has retrieved values from the emulated device and written them to the log file, the TextWriter object caches data in memory and writes to the underlying file system when it is either flushed or closed. When you closed the application, you disposed of the TextWriter object without flushing its in-memory cache to the log file, which is why the file is empty. 6. Close Notepad. 7. On the File menu, click Exit.

Run the Exercise2 Test Harness application in Debug mode, click Get Measurements, and then wait for the data to appear. a. b. In Visual Studio, on the Debug menu, click Start Debugging. In the Exercise 2 window, click Get Measurements.

L9-10

Lab: Managing the Lifetime of Objects and Controlling Resources

8.

After the application populates the text boxes with data from the emulated device, click Get Measurements again.

The application will throw an unhandled IOException exception. The exception is thrown because each time you click Get Measurements, you create a new instance of the MeasureMassDevice class. Each instance of the MeasureMassDevice class creates its own instance of the TextWriter class to log measurements. The test harness does not currently dispose of the MeasureMassDevice objects after the code run by the Get Measurements button completes. This means that the object is not closed and therefore retains its lock on the log file. When you attempt to create a second instance of the MeasureMassDevice class that uses the same log file, this instance cannot access the file because it is still in use by the first instance. 9. Stop the Exercise 2 application. On the Debug menu, click Stop Debugging.

Task 3: Modify the test harness to dispose of objects correctly.


1. In Visual Studio, open the MainWindow.xaml.vb file in the Exercise2 Test Harness project. 2. In Solution Explorer, expand the Exercise2TestHarness project, expand MainWindow.xaml, and then double-click MainWindow.xaml.vb.

In the GetMeasurementsButton_Click method, remove the TODO: Modify this method comment in the MainWindow.xaml.vb file. Modify the GetMeasurementsButton_Click method to ensure that the device field is disposed of when the method completes by using a Using block.

Your code should resemble the following code.


Private Sub GetMeasurementsButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Using device As New MeasureMassDevice(Units.Metric, "D:\Labfiles\Lab09\LogFile.txt") device.StartCollecting() System.Threading.Thread.Sleep(20000) LoggingFileNameTextBox.Text = device.GetLoggingFile() MetricValueTextBox.Text = device.MetricValue().ToString() ImperialValueTextBox.Text = device.ImperialValue().ToString() RawDataValuesListBox.ItemsSource = device.GetRawData() device.StopCollecting() End Using End Sub

3.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 4: Verify that the object is disposed of correctly.


1. Run the Exercise2TestHarness application. 2. 3. 4. On the Debug menu, click Start Debugging.

Click Get Measurements, and then wait until the data appears. After the application populates the text boxes with data from the emulated device, close the Exercise 2 window. Open the log file in Notepad. a. b. Click Start, point to All Programs, click Accessories, and then click Notepad. In Notepad, on the File menu, click Open.

Lab: Managing the Lifetime of Objects and Controlling Resources

L9-11

c. 5.

In the Open dialog box, in the File name box, browse to the D:\Labfiles\Lab09\ folder, click LogFile.txt, and then click Open.

Review the contents of the log file.

The file now contains the values displayed on the form and status messages generated when the file is opened and closed. When the code for the Get Measurements button completes, it disposes of the MeasureMassDevice instance, which forces the TextWriter object to flush its in-memory cache to the file, and then closes the TextWriter. 6. Close Notepad. 7. On the File menu, click Exit.

Run the Exercise2TestHarness application again. On the Debug menu, click Start Debugging.

8. 9.

Click Get Measurements, and then wait for the data to appear. In the Exercise 2 window, click Get Measurements again. The application will pause for another 20 seconds.

This time, the application does not throw an exception. This is because the resources are properly disposed of each time you click Get Measurements. When you close the TextWriter object, you release the lock on the file, and a new instance of the TextWriter class can now use the same log file without throwing an exception. 10. Open the log file in Notepad. a. b. c. Click Start, point to All Programs, click Accessories, and then click Notepad. In Notepad, on the File menu, click Open. In the Open dialog box, in the File name box, browse to the D:\Labfiles\Lab09\ folder, click LogFile.txt, and then click Open.

11. Review the contents of the log file. The file contains the most recent values displayed on the form. 12. Close Notepad. On the File menu, click Exit.

13. Close the Exercise 2 window. 14. Close Visual Studio. On the File menu, click Exit.

L9-12

Lab: Managing the Lifetime of Objects and Controlling Resources

Lab A: Creating and Using Properties

L10A-1

Module 10: Encapsulating Data and Defining Overloaded Operators

Lab A: Creating and Using Properties


Exercise 1: Defining Properties in an Interface
Task 1: Open the starter project.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Import the code snippets from the D:\Labfiles\Lab10\Snippets folder. a. b. c. d. In Visual Studio, on the Tools menu, click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language list, select Visual Basic, and then click Add. In the Code Snippets Directory dialog box, browse to the D:\Labfiles \Lab10\Snippets folder, and then click Select Folder. In the Code Snippets Manager dialog box, click OK.

3.

Open the Module10 solution in the D:\Labfiles\Lab10\LabA\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab10\LabA \Ex1\Starter folder, click Module10.sln, and then click Open.

Task 2: Add properties to the IMeasuringDeviceWithProperties interface.


1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

Open the IMeasuringDeviceWithProperties.vb file. In Solution Explorer, double-click IMeasuringDeviceWithProperties.vb.

3.

Remove the comment TODO: Add properties to the interface. Delete the following line of code.
' TODO: Add properties to the interface.

4.

Add a read-only property named UnitsToUse of type Units to the interface. Your code should resemble the following code.
Interface IMeasuringDeviceWithProperties Inherits ILoggingMeasuringDevice ReadOnly Property UnitsToUse As Units End Interface

5.

Add a read-only property named DataCaptured of type Integer() to the interface.

L10A-2

Lab A: Creating and Using Properties

Your code should resemble the following code.


Interface IMeasuringDeviceWithProperties Inherits ILoggingMeasuringDevice ReadOnly Property UnitsToUse As Units ReadOnly Property DataCaptured As Integer() End Interface

6.

Add a read-only property named MostRecentMeasure of type Integer to the interface. Your code should resemble the following code.
Interface IMeasuringDeviceWithProperties Inherits ILoggingMeasuringDevice ReadOnly Property UnitsToUse As Units ReadOnly Property DataCaptured As Integer() ReadOnly Property MostRecentMeasure As Integer End Interface

7.

Add a read/write property named LoggingFileName of type String to the interface. Your code should resemble the following code.
Interface IMeasuringDeviceWithProperties Inherits ILoggingMeasuringDevice ReadOnly Property UnitsToUse As Units ReadOnly Property DataCaptured As Integer() ReadOnly Property MostRecentMeasure As Integer Property LoggingFileName As String End Interface

8.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Exercise 2: Implementing Properties in a Class


Task 1: Open the starter project.
Open the Module10 solution in the D:\Labfiles\Lab10\LabA\Ex2\Starter folder. This solution contains a completed version of the IMeasuringDeviceWithProperties interface. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab10\LabA \Ex2\Starter folder, click Module10.sln, and then click Open.

Task 2: Update the MeasureDataDevice class to implement the


IMeasuringDeviceWithProperties interface.
1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

Open the MeasureDataDevice.vb file. In Solution Explorer, double-click MeasureDataDevice.vb.

Lab A: Creating and Using Properties

L10A-3

3.

Remove the comment TODO: Implement the IMeasuringDeviceWithProperties interface. Delete the following line of code.
' TODO: Implement the IMeasuringDeviceWithProperties interface.

4.

Modify the class declaration to implement the IMeasuringDeviceWithProperties interface, instead of the ILoggingMeasuringDevice interface. The IMeasuringDeviceWithProperties interface inherits from the ILoggingMeasuringDevice interface, so modifying the declaration will not break compatibility with existing applications; the class can still be cast as an instance of the ILoggingMeasuringDevice interface. Your code should resemble the following code.
Public MustInherit Class MeasureDataDevice Implements IDisposable, IMeasuringDeviceWithProperties ... End Class

5.

Rename the following member variables as specified, by using the Visual Basic Rename refactoring method. New Member Name dataDeviceUnitsToUse dataDeviceDataCaptured dataDeviceMostRecentMeasure dataDeviceController dataDeviceMeasurementType dataDeviceLoggingFileName

Current Member Name unitsToUse dataCaptured mostRecentMeasure controller measurementType loggingFileName

Visual Basic is case insensitive and does not support a member variable and a property with the same name, even if they differ by the casing of one or more characters. You therefore need to rename the member variables, before implementing the properties of the IMeasuringDeviceWithProperties interface. a. b. c. 6. Right-click unitsToUse, and then click Rename. In the Rename dialog box, in the New name box, type dataDeviceUnitsToUse, and then click OK. Repeat steps a and b for the dataCaptured, mostRecentMeasure, controller, measurementType, and loggingFileName members.

Remove the comment TODO: Add properties specified by the IMeasuringDeviceWithProperties interface. Delete the following line of code.

You will use the Implement Interface Wizard in the next step to add the properties.
' TODO: Add properties specified by the IMeasuringDeviceWithProperties interface.

L10A-4

Lab A: Creating and Using Properties

7.

Use the Implement Interface Wizard to generate method stubs for each of the methods in the IMeasuringDeviceWithProperties interface. Place the cursor immediately after Implements IDisposable, IMeasuringDeviceWithProperties, and then press Enter.

8.

Locate the UnitsToUse property Get procedure, and then add code to return the dataDeviceUnitsToUse field or member. Your code should resemble the following code.
Public ReadOnly Property UnitsToUse As Units Implements IMeasuringDeviceWithProperties.UnitsToUse Get Return dataDeviceUnitsToUse End Get End Property

9.

Locate the DataCaptured property Get procedure, and then add code to return the dataDeviceDataCaptured field or member. Your code should resemble the following code.
Public ReadOnly Property DataCaptured As Integer() Implements IMeasuringDeviceWithProperties.DataCaptured Get Return dataDeviceDataCaptured End Get End Property

10. Locate the MostRecentMeasure property Get procedure, and then add code return the deviceDataMostRecentMeasure field or member. Your code should resemble the following code.
Public ReadOnly Property MostRecentMeasure As Integer Implements IMeasuringDeviceWithProperties.MostRecentMeasure Get Return dataDeviceMostRecentMeasure End Get End Property

11. Locate the LoggingFileName property Get procedure, and then add code to return the deviceDataLoggingFileName field or member. Your code should resemble the following code.
Public Property LoggingFileName As String Implements IMeasuringDeviceWithProperties.LoggingFileName Get Return dataDeviceLoggingFileName End Get Set(ByVal value As String) End Set End Property

12. Modify the Set procedure of the LoggingFileName property as shown in the following code. Note: A code snippet is available, named Mod10LoggingFileNamePropertySetAccessor, which you can use to add this code.

Lab A: Creating and Using Properties

L10A-5

If loggingFileWriter Is Nothing Then ' If the file has not been opened simply update the file name. dataDeviceLoggingFileName = value Else ' If the file has been opened close the current file first, ' then update the file name and open the new file. loggingFileWriter.WriteLine("Log File Changed") loggingFileWriter.WriteLine("New Log File: {0}", value) loggingFileWriter.Close() ' Now update the logging file and open the new file. dataDeviceLoggingFileName = value ' Check if the logging file exists - if not create it. If Not File.Exists(LoggingFileName) Then loggingFileWriter = File.CreateText(LoggingFileName) loggingFileWriter.WriteLine("Log file status checked - Created") loggingFileWriter.WriteLine("Collecting Started") Else loggingFileWriter = New StreamWriter(LoggingFileName) loggingFileWriter.WriteLine("Log file status checked - Opened") loggingFileWriter.WriteLine("Collecting Started") End If loggingFileWriter.WriteLine("Log File Changed Successfully") End If

To use the Mod10LoggingFileNamePropertySetAccessor snippet, after the opening brace of the Set procedure, type Mod10LoggingFileNamePropertySetAccessor, and then press TAB.

The Set procedure for the LoggingFileName property checks whether the log file is currently open. If the log file has not been opened, the Set procedure simply updates the local field. However, if the log file has been opened, the procedure closes the current log file and opens a new log file with the new file name, in addition to updating the local field. 13. Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Exercise 3: Using Properties Exposed by a Class


Task 1: Add the test harness to the solution.
The test harness application for this lab is a simple Windows Presentation Foundation (WPF) application that is designed to test the functionality of the MeasureDataDevice class that you have just modified. It does not include any exception handling to ensure that it does not hide any exceptions thrown by the class that you have developed. 1. Open the Module10 solution in the D:\Labfiles\Lab10\LabA\Ex3\Starter folder. a. b. 2. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab10\LabA \Ex3\Starter folder, click Module10.sln, and then click Open.

Add the test harness to the solution. The test harness is a project named Exercise3TestHarness, located in the D:\Labfiles\Lab10\LabA\Ex3 \Starter\Exercise3TestHarness folder. a. In Solution Explorer, right-click the Solution 'Module 10' node, point to Add, and then click Existing Project.

L10A-6

Lab A: Creating and Using Properties

b.

In the Add Existing Project dialog box, browse to the D:\Labfiles\Lab10 \LabA\Ex3\Starter\Exercise3TestHarness folder, click the Exercise3TestHarness.vbproj project file, and then click Open.

3.

Set the Exercise3TestHarness project as the startup project for the solution. In Solution Explorer, right-click Exercise3TestHarness, and then click Set as StartUp Project.

Task 2: Update the test harness.


1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

Review the user interface for the test application. In Solution Explorer, double-click MainWindow.xaml.

The test harness application includes functionality to enable you to test the properties you developed in the previous exercise. The Start Collecting button creates a new instance of the MeasureMassDevice object and starts collecting measurements from the emulated device. The application includes text boxes that display the output from the application. It also includes an Update button to enable you to update the file name of the log file. Finally, the test harness includes a button to stop the collection of measurements from the emulated device and dispose of the object. 3. Open the MainWindow.xaml.vb file. In Solution Explorer, expand MainWindow.xaml, and then double-click MainWindow.xaml.vb.

Note: In the following steps, you will store values in the Text property of TextBox controls in the WPF window. This is a String property. In some of the steps, you may need to call the ToString method to convert the property to a String. 4. Remove the comment TODO: Add code to set the UnitsTextBox to the current units. Delete the following line of code.
' TODO: Add code to set the UnitsTextBox to the current units.

5.

Locate the following line of code.


UnitsTextBox.Text = ""

6.

Update the code you located in the previous step to set the Text property of the UnitsTextBox object to the UnitsToUse property of the device object. Your code should resemble the following code.
UnitsTextBox.Text = device.UnitsToUse.ToString()

7.

Remove the comment TODO: Add code to set the MostRecentMeasureTextBox to the value from the device. Delete the following line of code.
' TODO: Add code to set the MostRecentMeasureTextBox to the value from the device.

Lab A: Creating and Using Properties

L10A-7

8.

Locate the following line of code.


MostRecentMeasureTextBox.Text = ""

9.

Update the code you located in the previous step to set the Text property of the MostRecentMeasureTextBox object to the MostRecentMeasure property of the device object. Your code should resemble the following code.
MostRecentMeasureTextBox.Text = device.MostRecentMeasure.ToString()

10. Remove the comment TODO: Update to use the LoggingFileName property. Delete the following line of code.
' TODO: Update to use the LoggingFileName property.

11. Locate the following line of code.


LoggingFileNameTextBox.Text = device.GetLoggingFile().Replace(labFolder, "")

12. Update the code you located in the previous step to set the Text property of the LoggingFileNameTextBox object to the LoggingFileName property of the device object. Your code should call the Replace method of the string class in the same way as the code you are updating. Your code should resemble the following code.
LoggingFileNameTextBox.Text = device.LoggingFileName.Replace(labFolder, "")

13. Remove the comment TODO: Update to use the DataCaptured property. Delete the following line of code.
' TODO: Update to use the DataCaptured property.

14. Locate the following line of code.


RawDataValuesListBox.ItemsSource = device.GetRawData()

15. Update the code you located in the previous step to set the ItemsSource property of the RawDataValuesListBox object to the DataCaptured property of the device object. Your code should resemble the following code.
RawDataValuesListBox.ItemsSource = device.DataCaptured

16. In the UpdateButton_Click method, remove the comment TODO: Add code to update the log file name property of the device and add code to set the LoggingFileName property of the device object to the concatenation of the labFolder field and the Text property of the LoggingFileNameTextBox control. Your code should resemble the following code.
If Not device Is Nothing Then device.LoggingFileName = labFolder & LoggingFileNameTextBox.Text End

17. Build the solution and correct any errors.

L10A-8

Lab A: Creating and Using Properties

On the Build menu, click Build Solution. Correct any errors.

Task 3: Test the properties by using the test harness.


1. Start the Exercise3TestHarness application. 2. Press Ctrl+F5.

Click Start Collecting. This action causes the application to pause for 10 seconds while some measurements data is generated and then display this data. This pause is necessary because the application waits for measurement data from the emulated device. Using Windows Explorer, browse to the D:\Labfiles\Lab10\LabA folder, and then verify that the default logging file, LogFile.txt, has been created. a. b. In the taskbar, click the Windows Explorer icon. In Windows Explorer, browse to the D:\Labfiles\Lab10\LabA folder.

3.

4.

Return to the Exercise 3 window. Wait at least a further 10 seconds to ensure that the emulated device has generated some additional values before you perform the following step. Change the log file to LogFile2.txt, and then click Update. In the Logging File box, type LogFile2.txt and then click Update.

The Update button calls the code you added to set the LoggingFileName property of the device; because the device is running, and therefore logging values to the log file, the code will close the current log file and open a new one with the name you specified. 5. 6. 7. 8. 9. Wait at least 10 seconds to ensure that the emulated device has generated some additional values before you perform the following steps. Using Windows Explorer, browse to the D:\Labfiles\Lab10\LabA folder, and then verify that the new logging file, LogFile2.txt, has been created. Return to the Exercise 3 window, and then click Stop Collecting / Dispose Object. Close the Exercise 3 window. Close Visual Studio. In Visual Studio, on the File menu, click Exit.

10. Using Notepad, open the LogFile.txt file in the D:\Labfiles\Lab10\LabA folder. a. b. c. Click Start, point to All Programs, click Accessories, and then click Notepad. In Notepad, on the File menu, click Open. In the Open dialog box, in the File name box, browse to the D:\Labfiles\Lab10\LabA folder, click LogFile.txt, and then click Open.

11. Review the contents of the LogFile.txt file. The file includes the values originally displayed in the test harness in addition to some values that were not displayed. The file then indicates that the log file has changed and gives the name of the new log file. 12. Open the LogFile2.txt file in the D:\Labfiles\Lab10\LabA folder. a. b. On the File menu, click Open. In the Open dialog box, in the File name box, click LogFile2.txt, and then click Open.

13. Review the contents of the LogFile2.txt file.

Lab A: Creating and Using Properties

L10A-9

The file indicates that the log file has changed successfully. The file then includes any measurements taken after the log file changed and finally indicates that collection has stopped and the object was disposed of. 14. Close Notepad. On the File menu, click Exit.

L10A-10

Lab A: Creating and Using Properties

Lab B: Creating and Using Default Properties

L10B-1

Module 10: Encapsulating Data and Defining Overloaded Operators

Lab B: Creating and Using Default Properties


Exercise 1: Implementing a Default Property to Access Bits in a Control Register
Task 1: Open the starter project.
1. Open Microsoft Visual Studio 2010. a. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Open the Module10 solution in the D:\Labfiles\Lab10\LabB\Ex1\Starter folder. b. c. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab10\LabB \Ex1\Starter folder, click Module10.sln, and then click Open.

Task 2: Add a default property to the ControlRegister class.


1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

Open the ControlRegister.vb file. In Solution Explorer, double-click ControlRegister.vb.

3.

Remove the comment TODO: Add an indexer to enable access to individual bits in the control register and add a Public default property, indexer, to the class. The default property should take an Integer named idx as the parameter and return an Integer.

Your code should resemble the following code.


Default Public Property Index(ByVal idx As Integer) As Integer End Property

4.

Add a Get procedure to the default property. In the Get procedure, add code to determine whether the bit specified by the idx parameter in the regData object is set to 1 or 0 and return the value of this bit. The regData object should be accessed through the RegisterData property.

Hint: Use the logical And operator and the left-shift operator (<<) to determine whether the result of left-shifting the value in the regData object by the value of the idx object is zero or non-zero. If the result is zero, return 0; otherwise, return 1. You can use the following code example to assist you with this step.
Dim isSet As Boolean = (RegisterData And (1 << idx)) <> 0

Your code should resemble the following code.


Default Public Property Index(ByVal idx As Integer) As Integer Get Dim isSet As Boolean = (RegisterData And (1 << idx)) <> 0

L10B-2

Lab B: Creating and Using Default Properties

Return If(isSet, 1, 0) End Get End Property

5.

Add a Set procedure to the default property. In the Set procedure, add code to verify that the parameter specified is either 1 or 0. Throw an ArgumentException exception with the message Argument must be 1 or 0 if it is not one of these values.

Your code should resemble the following code.


Default Public Property Index(ByVal idx As Integer) As Integer Get Dim isSet As Boolean = (RegisterData And (1 << idx)) <> 0 Return If(isSet, 1, 0) End Get Set(ByVal value As Integer) If value <> 0 AndAlso value <> 1 Then Throw New ArgumentException("Argument must be 1 or 0") End If End Set End Property

6.

In the Set procedure, if value is 1, add code to set the bit specified by the idx object in the regData field to 1; otherwise, set this bit to 0.

Hint: Use the compound assignment operators |= and &= to set a specified bit in an integer value to 1 or 0. Use the expression (1 << idx) to determine which bit in the integer value to set. Your code should resemble the following code.
Default Public Property Index(ByVal idx As Integer) As Integer Get Dim isSet As Boolean = (RegisterData And (1 << idx)) <> 0 Return If(isSet, 1, 0) End Get Set(ByVal value As Integer) If value <> 0 AndAlso value <> 1 Then Throw New ArgumentException("Argument must be 1 or 0") End If If value = 1 Then RegisterData = RegisterData Or (1 << idx) Else RegisterData = RegisterData And Not (1 << idx) End If End Set End Property

7.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Lab B: Creating and Using Default Properties

L10B-3

Exercise 2: Using a Default Property Exposed by a Class


Task 1: Add the test harness to the solution.
The test harness application for this lab is a simple console application that is designed to test the functionality of the ControlRegister class to which you have added a default property. It does not include any exception handling to ensure that it does not hide any exceptions thrown by the class you have developed. 1. Open the Module10 solution in the D:\Labfiles\Lab10\LabB\Ex2\Starter folder. a. b. 2. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab10\LabB \Ex2\Starter folder, click Module10.sln, and then click Open.

Import the code snippets from the D:\Labfiles\Lab10\Snippets folder. a. b. c. d. In Visual Studio, on the Tools menu, click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language list, select Visual Basic, and then click Add. In the Code Snippets Directory dialog box, browse to the D:\Labfiles \Lab10\Snippets folder, and then click Select Folder. In the Code Snippets Manager dialog box, click OK.

3.

Add the test harness to the solution. The test harness is a project named Exercise2TestHarness, located in the D:\Labfiles\Lab10\LabB\Ex2 \Starter\Exercise2TestHarness folder. a. b. In Solution Explorer, right-click the Solution 'Module 10' node, point to Add, and then click Existing Project. In the Add Existing Project dialog box, browse to the D:\Labfiles\Lab10 \LabB\Ex2\Starter\Exercise2TestHarness folder, click the Exercise2TestHarness.vbproj project file, and then click Open.

4.

Set the Exercise2TestHarness project as the startup project for the solution. In Solution Explorer, right-click Exercise2TestHarness, and then click Set as Startup Project.

Task 2: Update the test harness.


1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

Open the Module1.vb file. In Solution Explorer, double-click Module1.vb.

3.

Remove the TODO comment. Delete the following line of code.

' TODO: Add code to test the ControlRegister class and indexer.

4.

Add code to create a new instance of the ControlRegister class named register.

Your code should resemble the following code.

L10B-4

Lab B: Creating and Using Default Properties

Dim register As New ControlRegisterIndexing.ControlRegister()

5.

Add code to set the RegisterData property of the register object to 8.

Your code should resemble the following code.


Dim register As New ControlRegisterIndexing.ControlRegister() register.RegisterData = 8

6.

Add the following code, which writes the current value for the RegisterData property and uses the default property to write the first eight bits of the ControlRegister object to the console.

Note: A code snippet is available, named Mod10WriteRegisterData, which you can use to add this code.
Console.WriteLine("RegisterData: {0}", register.RegisterData) Console.WriteLine("Bit 0: {0}", register(0).ToString()) Console.WriteLine("Bit 1: {0}", register(1).ToString()) Console.WriteLine("Bit 2: {0}", register(2).ToString()) Console.WriteLine("Bit 3: {0}", register(3).ToString()) Console.WriteLine("Bit 4: {0}", register(4).ToString()) Console.WriteLine("Bit 5: {0}", register(5).ToString()) Console.WriteLine("Bit 6: {0}", register(6).ToString()) Console.WriteLine("Bit 7: {0}", register(7).ToString()) Console.WriteLine()

7.

To use the Mod10WriteRegisterData snippet, on a blank line, type Mod10WriteRegisterData, and then press TAB.

Add a statement to write the message Set Bit 1 to 1 to the console.

Your code should resemble the following code.


Dim register As New ControlRegisterIndexing.ControlRegister() register.RegisterData = 8 Console.WriteLine("RegisterData: {0}", register.RegisterData) Console.WriteLine("Bit 0: {0}", register(0).ToString()) Console.WriteLine("Bit 1: {0}", register(1).ToString()) Console.WriteLine("Bit 2: {0}", register(2).ToString()) Console.WriteLine("Bit 3: {0}", register(3).ToString()) Console.WriteLine("Bit 4: {0}", register(4).ToString()) Console.WriteLine("Bit 5: {0}", register(5).ToString()) Console.WriteLine("Bit 6: {0}", register(6).ToString()) Console.WriteLine("Bit 7: {0}", register(7).ToString()) Console.WriteLine() Console.WriteLine("Set Bit 1 to 1")

8.

Add a statement to set the bit at index 1 in the register object to 1.

Your code should resemble the following code.


... Console.WriteLine("Set Bit 1 to 1") Register(1) = 1

9.

Add code to write a blank line to the console.

Your code should resemble the following code.

Lab B: Creating and Using Default Properties

L10B-5

... Console.WriteLine("Set Bit 1 to 1") Register(1) = 1 Console.WriteLine()

10. Add the following code, which writes the current value for the RegisterData property and uses the default property to write the first eight bits of the ControlRegister object to the console. Note: You can use the Mod10WriteRegisterData code snippet to add this code.
Console.WriteLine("RegisterData: {0}", register.RegisterData) Console.WriteLine("Bit 0: {0}", register(0).ToString()) Console.WriteLine("Bit 1: {0}", register(1).ToString()) Console.WriteLine("Bit 2: {0}", register(2).ToString()) Console.WriteLine("Bit 3: {0}", register(3).ToString()) Console.WriteLine("Bit 4: {0}", register(4).ToString()) Console.WriteLine("Bit 5: {0}", register(5).ToString()) Console.WriteLine("Bit 6: {0}", register(6).ToString()) Console.WriteLine("Bit 7: {0}", register(7).ToString()) Console.WriteLine()

To use the Mod10WriteRegisterData snippet, on a blank line, type Mod10WriteRegisterData, and then press the TAB key.

11. Add a statement to write the message Set Bit 0 to 1 to the console. Your code should resemble the following code.
Console.WriteLine("Set Bit 1 to 1") Register(1) = 1 Console.WriteLine() Console.WriteLine("RegisterData: {0}", register.RegisterData) Console.WriteLine("Bit 0: {0}", register(0).ToString()) Console.WriteLine("Bit 1: {0}", register(1).ToString()) Console.WriteLine("Bit 2: {0}", register(2).ToString()) Console.WriteLine("Bit 3: {0}", register(3).ToString()) Console.WriteLine("Bit 4: {0}", register(4).ToString()) Console.WriteLine("Bit 5: {0}", register(5).ToString()) Console.WriteLine("Bit 6: {0}", register(6).ToString()) Console.WriteLine("Bit 7: {0}", register(7).ToString()) Console.WriteLine() Console.WriteLine("Set Bit 0 to 1")

12. Add code to set the bit at index 0 in the register object to 1. Your code should resemble the following code.
... Console.WriteLine("Set Bit 0 to 1") Register(0) = 1

13. Add code to write a blank line to the console. Your code should resemble the following code.
... Console.WriteLine("Set Bit 0 to 1") Register(0) = 1

L10B-6

Lab B: Creating and Using Default Properties

Console.WriteLine()

14. Add the following code, which writes the current value for the RegisterData property and uses the default property to write the first eight bits of the ControlRegister object to the console. Note: You can use the Mod10WriteRegisterData code snippet to add this code.
Console.WriteLine("RegisterData: {0}", register.RegisterData) Console.WriteLine("Bit 0: {0}", register(0).ToString()) Console.WriteLine("Bit 1: {0}", register(1).ToString()) Console.WriteLine("Bit 2: {0}", register(2).ToString()) Console.WriteLine("Bit 3: {0}", register(3).ToString()) Console.WriteLine("Bit 4: {0}", register(4).ToString()) Console.WriteLine("Bit 5: {0}", register(5).ToString()) Console.WriteLine("Bit 6: {0}", register(6).ToString()) Console.WriteLine("Bit 7: {0}", register(7).ToString()) Console.WriteLine()

To use the Mod10WriteRegisterData snippet, on a blank line, type Mod10WriteRegisterData, and then press the TAB key.

15. Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 3: Test the ControlRegister class by using the test harness.


1. Start the Exercise2TestHarness application. 2. Press Ctrl+F5.

Verify that the output from the console appears correctly. The output should resemble the following code example.

RegisterData : 8 Bit 0: 0 Bit 1: 0 Bit 2: 0 Bit 3: 1 Bit 4: 0 Bit 5: 0 Bit 6: 0 Bit 7: 0 Set Bit 1 to 1 RegisterData : 10 Bit 0: 0 Bit 1: 1 Bit 2: 0 Bit 3: 1 Bit 4: 0 Bit 5: 0 Bit 6: 0 Bit 7: 0 Set Bit 0 to 1 RegisterData : 11 Bit 0: 1 Bit 1: 1

Lab B: Creating and Using Default Properties

L10B-7

Bit Bit Bit Bit Bit Bit

2: 3: 4: 5: 6: 7:

0 1 0 0 0 0

3. 4.

Close the C:\Windows\system32\cmd.exe window. Close Visual Studio. In Visual Studio, on the File menu, click Exit.

L10B-8

Lab B: Creating and Using Default Properties

Lab C: Overloading Operators

L10C-1

Module 10: Encapsulating Data and Defining Overloaded Operators

Lab C: Overloading Operators


Exercise 1: Defining the Matrix and MatrixNotCompatibleException Types
Task 1: Open the starter project.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Open the Module10 solution in the D:\Labfiles\Lab10\LabC\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab10\LabC \Ex1\Starter folder, click Module10.sln, and then click Open.

3.

Import the code snippets from the D:\Labfiles\Lab10\Snippets folder. a. b. c. d. In Visual Studio, on the Tools menu, click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language list, select Visual Basic, and then click Add. In the Code Snippets Directory dialog box, browse to the D:\Labfiles \Lab10\Snippets folder, and then click Select Folder. In the Code Snippets Manager dialog box, click OK.

Task 2: Create a Matrix class.


1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

Open the Matrix.vb file. In Solution Explorer, double-click Matrix.vb.

3.

Remove the comment TODO: Add the Matrix class, and then add a Public Matrix class to the MatrixOperators namespace.

Your code should resemble the following code.


Namespace MatrixOperators Public Class Matrix ' TODO: Add an addition operator to the Matrix class. ' TODO: Add a subtraction operator to the Matrix class. ' TODO: Add a multiplication operator to the Matrix class. End Class ' TODO: Add the MatrixNotCompatibleException exception class. End Namespace

4.

Add a two-dimensional array of integers named data to the Matrix class.

L10C-2

Lab C: Overloading Operators

Your code should resemble the following code.


Public Class Matrix Private data(,) As Integer ' TODO: Add an addition operator to the Matrix class. ' TODO: Add a subtraction operator to the Matrix class. ' TODO: Add a multiplication operator to the Matrix class. End Class

5.

Add a Public constructor to the Matrix class. The constructor should take a single integer parameter named size and initialize the data array to a square array by using the value passed to the constructor as the size of each dimension of the array.

Your code should resemble the following code.


Public Class Matrix Private data(,) As Integer Public Sub New(ByVal size As Integer) ReDim data(size - 1, size - 1) End Sub ' TODO: Add an addition operator to the Matrix class. ' TODO: Add a subtraction operator to the Matrix class. ' TODO: Add a multiplication operator to the Matrix class. End Class

6.

After the constructor, add the following code to add a default property or an indexer to the class. You can either type this code manually, or you can use the Mod10MatrixClassIndexer code snippet.

Default Public Property Index(ByVal rowIndex As Integer, ByVal columnIndex As Integer) As Integer Get If rowIndex > data.GetUpperBound(0) OrElse columnIndex > data.GetUpperBound(0) Then Throw New IndexOutOfRangeException() Else Return data(rowIndex, columnIndex) End If End Get Set(ByVal value As Integer) If rowIndex > data.GetUpperBound(0) OrElse columnIndex > data.GetUpperBound(0) Then Throw New IndexOutOfRangeException() Else data(rowIndex, columnIndex) = value End If End Set End Property

The indexer takes two parameters, one that indicates the row, and another that indicates the column. The indexer checks that the values are in range for the current matrix (that they are not bigger than the matrix) and then returns the value of the indexed item from the data array. To use the code snippet, type Mod10MatrixClassIndexer, and then press TAB.

Lab C: Overloading Operators

L10C-3

7.

After the indexer, add the following code to override the ToString method of the Matrix class. You can either type this code manually, or you can use the Mod10MatrixClassToStringMethod code snippet.

Public Overrides Function ToString() As String Dim builder As New StringBuilder() ' Iterate over every row in the matrix. For x As Integer = 0 To data.GetLength(0) - 1 ' Iterate over every column in the matrix. For y As Integer = 0 To data.GetLength(1) - 1 builder.AppendFormat("{0}" & vbTab, data(x, y)) Next builder.Append(Environment.NewLine) Next Return builder.ToString() End Function

8.

To use the code snippet, type Mod10MatrixClassToStringMethod, and then press TAB.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 3: Create a MatrixNotCompatibleException exception class.


1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

If it is not already open, open the Matrix.vb file. In Solution Explorer, double-click Matrix.vb.

3.

Remove the comment TODO: Add the MatrixNotCompatibleException exception class, and then add a Public MatrixNotCompatibleException class to the MatrixOperators namespace.

Your code should resemble the following code.


Namespace MatrixOperators Public Class Matrix ... End Class Public Class MatrixNotCompatibleException End Class End Namespace

4.

Modify the MatrixNotCompatibleException class to inherit from the Exception class.

Your code should resemble the following code.


Public Class MatrixNotCompatibleException Inherits Exception End Class

5.

Add a field of type Matrix named first to the MatrixNotCompatibleException class and instantiate it to Nothing.

L10C-4

Lab C: Overloading Operators

Your code should resemble the following code.


Public Class MatrixNotCompatibleException Inherits Exception Dim first As Matrix = Nothing End Class

6.

Add a field of type Matrix named second to the MatrixNotCompatibleException class and instantiate it to Nothing.

Your code should resemble the following code.


Public Class MatrixNotCompatibleException Inherits Exception Dim first As Matrix = Nothing Dim second As Matrix = Nothing End Class

7.

Add a read-only property of type Matrix named FirstMatrix to the MatrixNotCompatibleException class, and then add a Get procedure that returns the first field.

Your code should resemble the following code.


Public Class MatrixNotCompatibleException Inherits Exception Dim first As Matrix = Nothing Dim second As Matrix = Nothing Public ReadOnly Property FirstMatrix As Matrix Get Return first End Get End Property End Class

8.

Add a read-only property of type Matrix named SecondMatrix to the MatrixNotCompatibleException class, and then add a Get procedure that returns the second field.

Your code should resemble the following code.


Public Class MatrixNotCompatibleException Inherits Exception Dim first As Matrix = Nothing Dim second As Matrix = Nothing Public ReadOnly Property FirstMatrix As Matrix Get Return first End Get End Property Public ReadOnly Property SecondMatrix As Matrix Get Return second End Get End Property End Class

Lab C: Overloading Operators

L10C-5

9.

Add the following constructors to the MatrixNotCompatibleException class. You can either type this code manually, or you can use the Mod10MatrixNotCompatibleExceptionClassConstructors code snippet.

Public Sub New() MyBase.New() End Sub Public Sub New(ByVal message As String) MyBase.New(message) End Sub Public Sub New(ByVal message As String, ByVal innerException As Exception) MyBase.New(message, innerException) End Sub Public Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext) MyBase.New(info, context) End Sub

To use the code snippet, type Mod10MatrixNotCompatibleExceptionClassConstructors, and then press TAB.

10. Add a constructor to the MatrixNotCompatibleException class. The constructor should take two Matrix objects and a String object as parameters. The constructor should use the String object to call the base constructor and instantiate the first and second fields by using the Matrix parameters. Your code should resemble the following code.
Public Sub New(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix, ByVal message As String) MyBase.New(message) first = matrix1 second = matrix2 End Sub

11. Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

At the end of the exercise, your code should resemble the following code example.
Imports System.Text Imports System.Runtime.Serialization Namespace MatrixOperators Public Class Matrix Private data(,) As Integer Public Sub New(ByVal size As Integer) ReDim data(size - 1, size - 1) End Sub Default Public Property Index(ByVal rowIndex As Integer, ByVal columnIndex As Integer) As Integer Get If rowIndex > data.GetUpperBound(0) OrElse columnIndex > data.GetUpperBound(0) Then Throw New IndexOutOfRangeException() Else Return data(rowIndex, columnIndex) End If End Get

L10C-6

Lab C: Overloading Operators

Set(ByVal value As Integer) If rowIndex > data.GetUpperBound(0) OrElse columnIndex > data.GetUpperBound(0) Then Throw New IndexOutOfRangeException() Else data(rowIndex, columnIndex) = value End If End Set End Property Public Overrides Function ToString() As String Dim builder As New StringBuilder() ' Iterate over every row in the matrix. For x As Integer = 0 To data.GetLength(0) - 1 ' Iterate over every column in the matrix. For y As Integer = 0 To data.GetLength(1) - 1 builder.AppendFormat("{0}" & vbTab, data(x, y)) Next Next builder.Append(Environment.NewLine)

Return builder.ToString() End Function ' TODO: Add an addition operator to the Matrix class. ' TODO: Add a subtraction operator to the Matrix class. ' TODO: Add a multiplication operator to the Matrix class. End Class Public Class MatrixNotCompatibleException Inherits Exception Dim first As Matrix = Nothing Dim second As Matrix = Nothing Public ReadOnly Property FirstMatrix As Matrix Get Return first End Get End Property Public ReadOnly Property SecondMatrix As Matrix Get Return second End Get End Property Public Sub New() MyBase.New() End Sub Public Sub New(ByVal message As String) MyBase.New(message) End Sub Public Sub New(ByVal message As String, ByVal innerException As Exception) MyBase.New(message, innerException) End Sub Public Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)

Lab C: Overloading Operators

L10C-7

MyBase.New(info, context) End Sub String) Public Sub New(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix, ByVal message As MyBase.New(message)

first = matrix1 second = matrix2 End Sub End Class End Namespace

Exercise 2: Implementing Operators for the Matrix Type


Task 1: Open the starter project.
1. Open the Module10 solution in the D:\Labfiles\Lab10\LabC\Ex2\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, in the File name box, browse to the D:\Labfiles\Lab10\LabC\Ex2\Starter folder, click Module10.sln, and then click Open.

Task 2: Add an addition operator to the Matrix class.


1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

Open the Matrix.vb file. In Solution Explorer, double-click Matrix.vb.

3.

Replace the comment TODO: Add an addition operator to the Matrix class with an overload of the + operator that takes two Matrix objects as parameters and returns an instance of the Matrix class.

Your code should resemble the following code.


Public Shared Operator +(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) End Operator

4.

Add code to the + operator to check that each of the matrices are the same size (the Matrix class only supports square matrices, so you only need to check one dimension of the matrix). If they are not the same size, throw a new MatrixNotCompatibleException exception, by using the matrices and the message Matrices not the same size as parameters.

Your code should resemble the following code.


Public Shared Operator +(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) If matrix1.data.GetLength(0) = matrix2.data.GetLength(0) Then Else Throw New MatrixNotCompatibleException(matrix1, matrix2, "Matrices not the same size") End If End Operator

L10C-8

Lab C: Overloading Operators

5.

If both matrices are the same size, add code that creates a new instance of the Matrix class named newMatrix and initialize it to a matrix with the same size as either of the source matrices.

Your code should resemble the following code.


Public Shared Operator +(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) If matrix1.data.GetLength(0) = matrix2.data.GetLength(0) Then Dim newMatrix As New Matrix(matrix1.data.GetLength(0)) Else Throw New MatrixNotCompatibleException(matrix1, matrix2, "Matrices not the same size") End If End Operator

6.

Add code to iterate over every item in the first matrix. For each item in the first matrix, calculate the sum of this item and the corresponding item in the second matrix, and store the result in the corresponding position in the newMatrix matrix. You can either type this code manually, or you can use the Mod10IterateMatrixRowsOperatorPlus code snippet.

Hint: Use a For loop to iterate over the rows in the first matrix and a nested For loop to iterate over the columns in each row. Your code should resemble the following code.
Public Shared Operator +(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) If matrix1.data.GetLength(0) = matrix2.data.GetLength(0) Then Dim newMatrix As New Matrix(matrix1.data.GetLength(0)) ' Iterate over every row in the matrix. For x As Integer = 0 To matrix1.data.GetLength(0) - 1 ' Iterate over every column in the matrix. For y As Integer = 0 To matrix1.data.GetLength(1) - 1 newMatrix.data(x, y) = matrix1.data(x, y) + matrix2.data(x, y) Next Next Else Throw New MatrixNotCompatibleException(matrix1, matrix2, "Matrices not the same size") End If End Operator

7.

To use the code snippet, type Mod10IterateMatrixRowsOperatorPlus, and then press TAB.

After the code that calculates the values for the newMatrix object, add a statement that returns the newMatrix object as the result of the + operator.

Your code should resemble the following code.


Public Shared Operator +(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) If matrix1.data.GetLength(0) = matrix2.data.GetLength(0) Then Dim newMatrix As New Matrix(matrix1.data.GetLength(0)) ' Iterate over every row in the matrix. For x As Integer = 0 To matrix1.data.GetLength(0) - 1 ' Iterate over every column in the matrix. For y As Integer = 0 To matrix1.data.GetLength(1) - 1 newMatrix.data(x, y) = matrix1.data(x, y) + matrix2.data(x, y) Next Next Return newMatrix

Lab C: Overloading Operators

L10C-9

Else Throw New MatrixNotCompatibleException(matrix1, matrix2, "Matrices not the same size") End If End Operator

8.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 3: Add a subtraction operator to the Matrix class.


1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

If it is not already open, open the Matrix.vb file. In Solution Explorer, double-click Matrix.vb.

3.

Replace the comment TODO: Add a subtraction operator to the Matrix class with an overload of the - operator that takes two Matrix objects as parameters and returns an instance of the Matrix class.

Your code should resemble the following code.


Public Shared Operator -(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) End Operator

4.

Add code to the - operator to check that each of the matrices are the same size (the Matrix class only supports square matrices, so you only need to check one dimension of the matrix). If they are not the same size, throw a new MatrixNotCompatibleException exception, by using the matrices and the message Matrices not the same size as parameters.

Your code should resemble the following code.


Public Shared Operator -(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) If matrix1.data.GetLength(0) = matrix2.data.GetLength(0) Then Else Throw New MatrixNotCompatibleException(matrix1, matrix2, "Matrices not the same size") End If End Operator

5.

If both matrices are the same size, add code that creates a new instance of the Matrix class named newMatrix and initialize it to a matrix with the same size as either of the source matrices.

Your code should resemble the following code.


Public Shared Operator -(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) If matrix1.data.GetLength(0) = matrix2.data.GetLength(0) Then Dim newMatrix As New Matrix(matrix1.data.GetLength(0)) Else Throw New MatrixNotCompatibleException(matrix1, matrix2, "Matrices not the same size") End If End Operator

L10C-10

Lab C: Overloading Operators

6.

Add code to iterate over every item in the first matrix. For each item in the first matrix, calculate the difference between this item and the corresponding item in the second matrix, and store the result in the corresponding position in the newMatrix matrix. You can either type this code manually, or you can use the Mod10IterateMatrixRowsOperatorMinus code snippet.

Your code should resemble the following code.


Public Shared Operator -(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) If matrix1.data.GetLength(0) = matrix2.data.GetLength(0) Then Dim newMatrix As New Matrix(matrix1.data.GetLength(0)) ' Iterate over every row in the matrix. For x As Integer = 0 To matrix1.data.GetLength(0) - 1 ' Iterate over every column in the matrix. For y As Integer = 0 To matrix1.data.GetLength(1) - 1 newMatrix.data(x, y) = matrix1.data(x, y) - matrix2.data(x, y) Next Next Else Throw New MatrixNotCompatibleException(matrix1, matrix2, "Matrices not the same size") End If End Operator

7.

To use the code snippet, type Mod10IterateMatrixRowsOperatorMinus, and then press TAB.

After the code that calculates the values for the newMatrix object, add a statement that returns the newMatrix object as the result of the - operator.

Your code should resemble the following code.


Public Shared Operator -(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) If matrix1.data.GetLength(0) = matrix2.data.GetLength(0) Then Dim newMatrix As New Matrix(matrix1.data.GetLength(0)) ' Iterate over every row in the matrix. For x As Integer = 0 To matrix1.data.GetLength(0) - 1 ' Iterate over every column in the matrix. For y As Integer = 0 To matrix1.data.GetLength(1) - 1 newMatrix.data(x, y) = matrix1.data(x, y) - matrix2.data(x, y) Next Next Return newMatrix Else Throw New MatrixNotCompatibleException(matrix1, matrix2, "Matrices not the same size") End If End Operator

8.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 4: Add a multiplication operator to the Matrix class.


1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, click Task List. If the Task List is displaying User Tasks, in the drop-down list box click Comments.

If it is not already open, open the Matrix.vb file.

Lab C: Overloading Operators

L10C-11

3.

In Solution Explorer, double-click Matrix.vb.

Replace the comment TODO: Add a multiplication operator to the Matrix class with an overload of the * operator that takes two Matrix objects as parameters and returns an instance of the Matrix class.

Your code should resemble the following code.


Public Shared Operator *(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) End Operator

4.

Add code to the * operator to check that each of the matrices are the same size (the Matrix class only supports square matrices, so you only need to check one dimension of the matrix). If they are not the same size, throw a new MatrixNotCompatibleException exception, by using the matrices and the message "Matrices not the same size" as parameters.

Your code should resemble the following code.


Public Shared Operator *(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) If matrix1.data.GetLength(0) = matrix2.data.GetLength(0) Then Else Throw New MatrixNotCompatibleException(matrix1, matrix2, "Matrices not the same size") End If End Operator

5.

Add code to the conditional block that creates a new instance of the Matrix class named newMatrix and initialize it to a matrix with the same size as the source matrices.

Your code should resemble the following code.


Public Shared Operator *(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) If matrix1.data.GetLength(0) = matrix2.data.GetLength(0) Then Dim newMatrix As New Matrix(matrix1.data.GetLength(0)) Else Throw New MatrixNotCompatibleException(matrix1, matrix2, "Matrices not the same size") End If End Operator

6.

Add code to iterate over every item in the first matrix and calculate the product of the two matrices, storing the result in the newMatrix matrix. Remember that to calculate each element xa,b in newMatrix, you must calculate the sum of the products of every value in row a in the first matrix with every value in column b in the second matrix. You can either type this code manually, or you can use the Mod10IterateMatrixRowsOperatorMultiply code snippet.

Your code should resemble the following code.


Public Shared Operator *(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) If matrix1.data.GetLength(0) = matrix2.data.GetLength(0) Then Dim newMatrix As New Matrix(matrix1.data.GetLength(0)) ' Iterate over every row in the matrix. For x As Integer = 0 To matrix1.data.GetLength(0) - 1 ' Iterate over every column in the matrix. For y As Integer = 0 To matrix1.data.GetLength(1) - 1 Dim temp As Integer = 0 For z As Integer = 0 To matrix1.data.GetLength(0) - 1 temp += matrix1.data(x, z) * matrix2.data(z, y) Next

L10C-12

Lab C: Overloading Operators

Next Else Throw New MatrixNotCompatibleException(matrix1, matrix2, "Matrices not the same size") End If End Operator

Next

newMatrix.data(x, y) = temp

7.

To use the code snippet, type Mod10IterateMatrixRowsOperatorMultiply, and then press TAB.

After the code that calculates the values for the newMatrix object, add a statement that returns the newMatrix object as the result of the * operator.

Your code should resemble the following code.


Public Shared Operator *(ByVal matrix1 As Matrix, ByVal matrix2 As Matrix) If matrix1.data.GetLength(0) = matrix2.data.GetLength(0) Then Dim newMatrix As New Matrix(matrix1.data.GetLength(0)) ' Iterate over every row in the matrix. For x As Integer = 0 To matrix1.data.GetLength(0) - 1 ' Iterate over every column in the matrix. For y As Integer = 0 To matrix1.data.GetLength(1) - 1 Dim temp As Integer = 0 For z As Integer = 0 To matrix1.data.GetLength(0) - 1 temp += matrix1.data(x, z) * matrix2.data(z, y) Next Next newMatrix.data(x, y) = temp

Next

Return newMatrix Else Throw New MatrixNotCompatibleException(matrix1, matrix2, "Matrices not the same size") End If End Operator

8.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Exercise 3: Testing the Operators for the Matrix Type


Task 1: Add the test harness to the solution.
The test harness application for this lab is a simple console application that is designed to test the functionality of the Matrix class. It does not include any exception handling to ensure that it does not hide any exceptions thrown by the class you have developed. 1. Open the Module10 solution in the D:\Labfiles\Lab10\LabC\Ex3\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab10\LabC \Ex3\Starter folder, click Module10.sln, and then click Open.

Lab C: Overloading Operators

L10C-13

2.

Add the test harness to the solution. The test harness is a project named Exercise3TestHarness, located in the D:\Labfiles\Lab10\LabC\Ex3 \Starter\Exercise3TestHarness folder. a. b. In Solution Explorer, right-click the Solution 'Module 10' node, point to Add, and then click Existing Project. In the Add Existing Project dialog box, browse to the D:\Labfiles\Lab10 \LabC\Ex3\Starter\Exercise3TestHarness folder, click the Exercise3TestHarness.vbproj project file, and then click Open.

3.

Set the Exercise3TestHarness project as the startup project for the solution. In Solution Explorer, right-click Exercise3TestHarness, and then click Set as Startup Project.

Task 2: Add code to test the operators in the Matrix class.


1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

Open the Module1.vb file. In Solution Explorer, double-click Module1.vb.

3.

Review the Main method.

This method creates two 33 square matrices named matrix1 and matrix2 and populates them with sample data. The method then displays their contents to the console by using the ToString method. 4. Remove the TODO comment. Delete the following line of code.

' TODO: Add code to test the operators in the Matrix class.

5.

Add a statement to write the message Matrix 1 + Matrix 2: to the console.

Your code should resemble the following code.


Console.WriteLine("Matrix 1 + Matrix 2:")

6.

Add a statement to create a new Matrix object named matrix3 and populate it with the sum of the matrix1 and matrix2 objects.

Your code should resemble the following code.


Dim matrix3 As MatrixOperators.Matrix = matrix1 + matrix2

7.

Add code to write the contents of the matrix3 matrix to the console, followed by a blank line.

Your code should resemble the following code.


Console.WriteLine(matrix3.ToString()) Console.WriteLine()

8.

Add a statement to write the message Matrix 1 - Matrix 2: to the console.

Your code should resemble the following code.

L10C-14

Lab C: Overloading Operators

Console.WriteLine("Matrix 1 - Matrix 2:")

9.

Add code to create a new Matrix object named matrix4 and populate it with the difference between the matrix1 and matrix2 objects (subtract matrix2 from matrix1).

Your code should resemble the following code.


Dim matrix4 As MatrixOperators.Matrix = matrix1 - matrix2

10. Add code to write the contents of the matrix4 matrix to the console, followed by a blank line. Your code should resemble the following code.
Console.WriteLine(matrix4.ToString()) Console.WriteLine()

11. Add a statement to write the message Matrix 1 Matrix 2: to the console. Your code should resemble the following code.
Console.WriteLine("Matrix 1 x 2:")

12. Add code to create a new Matrix object named matrix5 and populate it with the product of the matrix1 and matrix2 objects. Your code should resemble the following code.
Dim matrix5 As MatrixOperators.Matrix = matrix1 * matrix2

13. Add code to write the contents of the matrix5 matrix to the console, followed by a blank line. Your code should resemble the following code.
Console.WriteLine(matrix5.ToString()) Console.WriteLine()

14. Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 3: Test the matrix operators by using the test harness.


1. Start the Exercise3TestHarness application. 2. Press Ctrl+F5.

Verify that the output from the console appears correctly. The output should resemble the following.

Matrix 1: 1 2 3 4 5 6 7 8 9 Matrix 2: 9 8 7 6 5 4 3 2 1 Matrix 1 + 2: 10 10 10 10 10 10 10 10 10

Lab C: Overloading Operators

L10C-15

Matrix 1 - 2: -8 -6 -4 -2 0 2 4 6 8 Matrix 1 x 2: 30 24 18 84 69 54 138 114 90

3. 4.

Close the console window. Close Visual Studio. In Visual Studio, on the File menu, click Exit.

L10C-16

Lab C: Overloading Operators

Lab 11: Decoupling Methods and Handling Events

L11-1

Module 11: Decoupling Methods and Handling Events

Lab 11: Decoupling Methods and Handling Events


Exercise 1: Raising and Handling Events
Task 1: Open the Events solution.
1. Open Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Open the Events solution in the D:\Labfiles\Lab11\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab11\Ex1 \Starter folder, click Events.sln, and then click Open.

Task 2: Create a new interface that extends the IMeasuringDevice interface.


1. In the MeasuringDevice project, add a new interface named IEventEnabledMeasuringDevice in a file named IEventEnabledMeasuringDevice.vb. a. b. c. In Solution Explorer, right-click the MeasuringDevice project, point to Add, and then click New Item. In the Add New Item - MeasuringDevice dialog box, under Installed Templates, click Code, and in the template list, click Interface. In the Name box, type IEventEnabledMeasuringDevice.vb, and then click Add.

Note: Creating a new interface that extends an existing interface is good programming practice, because it preserves the structure of the original interface for backward compatibility with preexisting code. All preexisting code can reference the original interface, and new code can reference the new interface and take advantage of any new functionality. 2. Modify the interface definition so that the IEventEnabledMeasuringDevice interface extends the IMeasuringDevice interface.

Your code should resemble the following code.


... Public Interface IEventEnabledMeasuringDevice Inherits IMeasuringDevice End Interface ...

3.

In the IEventEnabledMeasuringDevice interface, add an event named NewMeasurementTaken. The event signature must include a parameter named sender of type Object and a parameter named e of type EventArgs.

Your code should resemble the following code.


... Public Interface IEventEnabledMeasuringDevice

L11-2

Lab 11: Decoupling Methods and Handling Events

Inherits IMeasuringDevice Event NewMeasurementTaken(ByVal sender As Object, ByVal e As EventArgs) End Interface ...

4.

Build the application to enable Microsoft IntelliSense to reflect your changes. On the Build menu, click Build Solution, and then correct any errors.

Task 3: Add the NewMeasurementTaken event to the MeasureDataDevice class.


1. Review the Task List. a. b. 2. 3. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

Locate the TODO: - Modify the class definition to implement the extended interface task, and then double-click this task. This task is located in the MeasureDataDevice class file. Remove the TODO: - Modify the class definition to implement the extended interface. comment, and then modify the class definition to implement the IEventEnabledMeasuringDevice interface instead of the IMeasuringDevice interface. At the top of the code file, in the class definition, replace IMeasuringDevice with IEventEnabledMeasuringDevice.

Your code should resemble the following code.


... Public MustInherit Class MeasureDataDevice IEventEnabledMeasuringDevice, IDisposable ...

4. 5.

In the Task List, locate the TODO: - Add the NewMeasurementTaken event task, and then doubleclick this task. This task is located at the end of the MeasureDataDevice class. Remove the TODO: - Add the NewMeasurementTaken event comment, and then declare an event named NewMeasurementTaken by using the same signature as the interface.

Your code should resemble the following code.


... ' Class implementation of the NewMeasurementTaken event. Public Event NewMeasurementTaken(ByVal sender As Object, ByVal e As EventArgs) Implements IEventEnabledMeasuringDevice.NewMeasurementTaken ' TODO: - Add an OnMeasurementTaken method. ...

6.

Below the event, remove the TODO: - Add an OnMeasurementTaken method comment, and then add a protected overridable method named OnNewMeasurementTaken. The method should accept no parameters and have no return type. The MeasureDataDevice class will use this method to raise the NewMeasurementTaken event.

Your code should resemble the following code.


... ' Method to raise the NewMeasurementTaken event.

Lab 11: Decoupling Methods and Handling Events

L11-3

Protected Overridable Sub OnNewMeasurementTaken(ByVal sender As Object, ByVal e As EventArgs) End Sub End Class ...

7.

In the OnNewMeasurementTaken method, add code to raise the event.

Your code should resemble the following code.


... Protected Overridable Sub OnNewMeasurementTaken(ByVal sender As Object, ByVal e As EventArgs) RaiseEvent NewMeasurementTaken(Me, Nothing) End Sub ...

Task 4: Add a BackgroundWorker member to the MeasureDataDevice class.


1. 2. In the Task List, locate the TODO: - Declare a BackgroundWorker to generate data task, and then double-click this task. This task is located near the top of the MeasureDataDevice class. Remove the TODO: - Declare a BackgroundWorker to generate data comment, and then add a private BackgroundWorker member named dataCollector to the class.

Your code should resemble the following code.


... ' BackgroundWorker member to generate measurements. Private dataCollector As BackgroundWorker ''' <summary> ...

Task 5: Add the GetMeasurements method to the MeasureDataDevice class.


The GetMeasurements method will initialize the dataCollector BackgroundWorker member to poll for new measurements and raise the NewMeasurementTaken event each time it detects a new measurement. 1. 2. In the Task List, locate the TODO: - Implement the GetMeasurements method task, and then double-click this task. Remove the TODO: - Implement the GetMeasurements method comment, and then add a new private method named GetMeasurements to the class. This method should take no parameters and not return a value.

Your code should resemble the following code.


... ' Add a GetMeasurements method to configure and start the ' BackgroundWorker. Private Sub GetMeasurements() End Sub ...

3.

In the GetMeasurements method, add code to perform the following actions. a. b. Instantiate the dataCollector BackgroundWorker member. Specify that the dataCollector BackgroundWorker member supports cancellation.

L11-4

Lab 11: Decoupling Methods and Handling Events

c.

Specify that the dataCollector BackgroundWorker member reports progress while running.

Hint: Set the WorkerSupportsCancellation and WorkerReportsProgress properties. Your code should resemble the following code.
... Private Sub GetMeasurements() dataCollector = New BackgroundWorker() dataCollector.WorkerSupportsCancellation = True dataCollector.WorkerReportsProgress = True End Sub ...

4.

Add the following code to instantiate a DoWorkEventHandler delegate that refers to a method called dataCollector_DoWork. Attach the delegate to the DoWork event property of the dataCollector member. The dataCollector object will call the dataCollector_DoWork method when the DoWork event is raised.
dataCollector.WorkerReportsProgress = True

...

AddHandler dataCollector.DoWork, AddressOf dataCollector_DoWork End Sub ...

5.

Position the mouse pointer over the squiggly blue line under the dataCollector_DoWork method name, click the arrow to open the drop-down list, and then click Generate method stub for dataCollector_DoWork in MeasuringDevice.MeasureDataDevice. Using the same technique as in step 4, instantiate a ProgressChangedEventHandler delegate that refers to a method called dataCollector_ProgressChanged. Attach this delegate to the ProgressChanged event property of the dataCollector member. The dataCollector object will call the dataCollector_ProgressChanged method when the ProgressChanged event is raised.

6.

Your code should resemble the following code.


... AddHandler dataCollector.DoWork, AddressOf dataCollector_DoWork

AddHandler dataCollector.ProgressChanged, AddressOf dataCollector_ProgressChanged End Sub ...

7.

Position the mouse pointer over the squiggly blue line under the dataCollector_ProgressChanged method name, click the arrow to open the drop-down list, and then click Generate method stub for dataCollector_ProgressChanged in MeasuringDevice.MeasureDataDevice. Add code to start the dataCollector BackgroundWorker object running asynchronously.

8.

Your code should resemble the following code.


... AddHandler dataCollector.ProgressChanged, dataCollector_ProgressChanged)

dataCollector.RunWorkerAsync() End Sub ...

Lab 11: Decoupling Methods and Handling Events

L11-5

Task 6: Implement the dataCollector_DoWork method.


1. At the end of the MeasureDataDevice class, locate the generated dataCollector_DoWork method stub.

This method was generated during the previous task. It runs on a background thread, and its purpose is to collect and store measurement data. 2. In the dataCollector_DoWork method, remove the statement that raises the NotImplementedException exception and add code to perform the following actions. a. b. c. Redimension the dataDeviceDataCaptured array to hold 10 items. Define an integer i with an initial value of zero. You will use this variable to track the current position in the dataDeviceDataCaptured array. Add a While loop that runs until the dataCollector.CancellationPending property is False.

Your code should resemble the following code.


... Private Sub dataCollector_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) ReDim dataDeviceDataCaptured(9) Dim i As Integer = 0 While Not dataCollector.CancellationPending End While End Sub ...

3.

In the While loop, add code to perform the following actions. a. Invoke the controller.TakeMeasurement method, and store the result in the dataCaptured array at the position that the integer i indicates. The TakeMeasurement method of the controller object blocks until a new measurement is available. Update the dataDeviceMostRecentMeasure field to contain the value in the dataCaptured array at the position that the integer i indicates. If the value of the disposedValue variable is True, terminate the While loop. This step ensures that the measurement collection stops when the MeasureDataDevice object is destroyed.

b. c.

Your code should resemble the following code.


... While Not dataCollector.CancellationPending dataDeviceDataCaptured(i) = dataDeviceController.TakeMeasurement() dataDeviceMostRecentMeasure = dataDeviceDataCaptured(i) If disposedValue Then Exit While End If End While ...

4.

Add code to the While loop after the statements that you added in the previous step to perform the following actions. a. Check whether the loggingFileWriter property is null.

L11-6

Lab 11: Decoupling Methods and Handling Events

b.

If the loggingFileWriter property is not null, call the loggingFileWriter.WriteLine method, passing a string parameter of the format "Measurement - mostRecentMeasure" where mostRecentMeasure is the value of the dataDeviceMostRecentMeasure variable.

Note: The loggingFileWriter property is a simple StreamWriter object that writes to a text file. This property is initialized in the StartCollecting method. You can use the WriteLine method to write to a StreamWriter object. Your code should resemble the following code.
... Exit While End If

If loggingFileWriter IsNot Nothing Then loggingFileWriter.WriteLine("Measurement - {0}", dataDeviceMostRecentMeasure.ToString()) End If End While ...

5.

Add a line of code to the end of the While loop to invoke the dataCollector.ReportProgress method, passing zero as the parameter.

The ReportProgress method raises the ReportProgress event and is used to return the percentage completion of the tasks assigned to the BackgroundWorker object. You can use the ReportProgress event to update progress bars or time estimates in the user interface (UI). In this case, because the task will run indefinitely until canceled, you will use the ReportProgress event as a mechanism to prompt the UI to refresh the display with the new measurement. Your code should resemble the following code.
... loggingFileWriter.WriteLine("Measurement - {0}", dataDeviceMostRecentMeasure.ToString()) End If dataCollector.ReportProgress(0) End While ...

6.

Add code to the end of the While loop to perform the following actions. a. b. c. Increment the integer i. If the value of the integer is greater than nine, reset i to zero. You are using the integer i as a pointer to the next position to write to in the dataDeviceDataCaptured array. This array has space for 10 measurements. When element 9 is filled, the device will start to overwrite data beginning at element 0.

Your code should resemble the following code.


... dataCollector.ReportProgress(0) i += 1 If i > 9 Then i = 0

Lab 11: Decoupling Methods and Handling Events

L11-7

End If End While ...

Task 7: Implement the dataCollector_ProgressChanged method.


1. Locate the dataCollector_ProgressChanged method. This method was generated during an earlier task. It runs when the ProgressChanged event is raised. In this exercise, this event occurs when the dataCollector_DoWork method takes and stores a new measurement. 2. In the event handler, delete the exception code and then invoke the OnNewMeasurementTaken method.

The OnNewMeasurementTaken method raises the NewMeasurementTaken event that you defined earlier. You will modify the UI to subscribe to this event so that when it is raised, the UI can update the displayed information. Your code should resemble the following code.
... Private Sub dataCollector_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) OnNewMeasurementTaken(Me, Nothing) End Sub ...

Task 8: Call the GetMeasurements method to start collecting measurements.


1. 2. In the Task List, locate the TODO: - Call the GetMeasurements method task, and then double-click this task. This task is located in the StartCollecting method. Remove the TODO: - Call the GetMeasurements method comment, and add a line of code to invoke the GetMeasurements method.

Your code should resemble the following code.


... Else loggingFileWriter.WriteLine("Log file status checked - Already open") loggingFileWriter.WriteLine("Collecting Started") End If GetMeasurements() End Sub ...

Task 9: Call the CancelAsync method to stop collecting measurements.


1. 2. In the Task List, locate the TODO: - Cancel the data collector task, and then double-click this task. This task is located in the StopCollecting method. Remove the TODO: - Cancel the data collector comment and add code to perform the following actions. a. b. Check that the dataCollector member is not null. If the dataCollector member is not null, call the CancelAsync method to stop the work performed by the dataCollector BackgroundWorker object.

L11-8

Lab 11: Decoupling Methods and Handling Events

Your code should resemble the following code.


... ' Stop the data collection BackgroundWorker. If Not dataCollector Is Nothing Then dataCollector.CancelAsync() End If End Sub ...

Task 10: Dispose of the BackgroundWorker object when the MeasureDataDevice object
is destroyed.
1. 2. In the Task List, locate the TODO: - Dispose of the data collector task, and then double-click this task. This task is located in the Dispose method of the MeasureDataDevice class. Remove the TODO: - Dispose of the data collector comment and add code to perform the following actions. a. b. Check that the dataCollector member is not null. If the dataCollector member is not null, call the Dispose method to dispose of the dataCollector instance.

Your code should resemble the following code.


... End If

' Dispose of the dataCollector BackgroundWorker object. If Not dataCollector Is Nothing Then dataCollector.Dispose() End If End Sub ...

Task 11: Update the UI to handle measurement events.


1. 2. In the Task List, locate the TODO: - Hook up the event handler to the event task, and then double-click this task. This task is located in the code behind for the MainWindow.xaml window. In the StartCollectingButton_Click method, remove the TODO: - Hook up the event handler to the event comment, and add code to connect the newMeasurementTaken delegate to the NewMeasurementTaken event of the device object. The device object is an instance of the MeasureMassDevice class, which inherits from the MeasureDataDevice abstract class.

Hint: To connect a delegate to an event, use the AddHandler statement and AddressOf operator on the event. Your code should resemble the following code.
... AddHandler device.NewMeasurementTaken, AddressOf device_NewMeasurementTaken LoggingFileNameTextBox.Text = device.GetLoggingFile() ...

Lab 11: Decoupling Methods and Handling Events

L11-9

Task 12: Implement the device_NewMeasurementTaken event-handling method.


1. 2. In the Task List, locate the TODO: - Add the device_NewMeasurementTaken event handler method to update the UI with the new measurement task, and then double-click this task. Remove the TODO: - Add the device_NewMeasurementTaken event handler method to update the UI with the new measurement comment, and add a private event-handler method named device_NewMeasurementTaken. The method should not return a value, but should take the following parameters. An Object object named sender. An EventArgs object named e.

Your code should resemble the following code.


... Private Sub device_NewMeasurementTaken(ByVal sender As Object, ByVal e As EventArgs) End Sub Private Sub UpdateButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) ...

3.

In the device_NewMeasurementTaken method, add code to check that the device member is not null. If the device member is not null, perform the following tasks. a. Update the Text property of the MostRecentMeasureTextBox control with the value of the device.MostRecentMeasure property.

Hint: Use the ToString method to convert the value that the device.MostRecentMeasure property returns from an integer to a string. b. c. d. e. Update the Text property of the MetricValueTextBox control with the value that the device.MetricValue method returns. Update the Text property of the ImperialValueTextBox control with the value that the device.ImperialValue method returns. Reset the RawDataValuesListBox.ItemsSource property to Nothing. Set the RawDataValuesListBox.ItemsSource property to the value that the device.GetRawData method returns.

Note: The final two steps are both necessary to ensure that the data-binding mechanism that the Raw Data box uses on the WPF window updates the display correctly. Your code should resemble the following code.
... Private Sub device_NewMeasurementTaken(ByVal sender As Object, ByVal e As EventArgs) If Not device Is Nothing Then MostRecentMeasureTextBox.Text = device.MostRecentMeasure.ToString() MetricValueTextBox.Text = device.MetricValue().ToString() ImperialValueTextBox.Text = device.ImperialValue().ToString() RawDataValuesListBox.ItemsSource = Nothing RawDataValuesListBox.ItemsSource = device.GetRawData() End If End Sub

L11-10

Lab 11: Decoupling Methods and Handling Events

...

Task 13: Disconnect the event handler.


1. In the Task List, locate the TODO: - Disconnect the event handler task, and then double-click this task. This task is located in the StopCollectingButton_Click method, which runs when the user clicks the Stop Collecting button. Remove the TODO: - Disconnect the event handler comment, and add code to disconnect the newMeasurementTaken delegate from the device.NewMeasurementTaken event.

2.

Hint: To disconnect a delegate from an event, use the RemoveHandler statement and AddressOf operator on the event. Your code should resemble the following code.
... device.StopCollecting()

RemoveHandler device.NewMeasurementTaken, AddressOf device_NewMeasurementTaken End If ...

Task 14: Test the solution.


1. Build the project and correct any errors. 2. On the Build menu, click Build Solution.

Start the application. On the Debug menu, click Start Debugging.

3.

Click Start Collecting, and verify that measurement values begin to appear in the Raw Data box.

The MeasureMassDevice object used by the application takes metric measurements and stores them before raising the NewMeasurementTaken event. The event calls code that updates the UI with the latest information. Continue to watch the Raw Data list box to see the buffer fill with data and then begin to overwrite earlier values. 4. 5. 6. 7. Click Stop Collecting, and verify that the UI no longer updates. Click Start Collecting again. Verify that the Raw Data list box is cleared and that new measurement data is captured and displayed. Click Stop Collecting. Close the application, and then return to Visual Studio.

Exercise 2: Using Lambda Expressions to Specify Code


Task 1: Open the Events solution.
1. Open the Events solution in the D:\Labfiles\Lab11\Ex2\Starter folder. a. b. 2. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab11\Ex2 \Starter folder, click Events.sln, and then click Open.

Import the code snippets from the D:\Labfiles\Lab11\Snippets folder.

Lab 11: Decoupling Methods and Handling Events

L11-11

a. b. c. d.

In Visual Studio, on the Tools menu, click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language list, select Visual Basic, and then click Add. In the Code Snippets Directory dialog box, browse to the D:\Labfiles \Lab11\Snippets folder, and then click Select Folder. In the Code Snippets Manager dialog box, click OK.

Note: The Events solution in the Ex2 folder is functionally the same as the code that you completed in Exercise 1; however, it includes an updated Task List to enable you to complete this exercise.

Task 2: Define a new EventArgs class to support heartbeat events.


1. In the MeasuringDevice project, add a new code file named HeartBeatEventArgs.vb. a. b. c. 2. In Solution Explorer, right-click the MeasuringDevice project, point to Add, and then click New Item. In the Add New Item - MeasuringDevice dialog box, in the template list click Code File. In the Name box, type HeartBeatEventArgs and then click Add.

In the HeartBeatEventArgs.vb code file, make the HeartBeatEventArgs class extend the EventArgs class.

Note: A custom event arguments class can contain any number of properties; these properties store information when the event is raised, enabling an event handler to receive event-specific information when the event is handled. Your code should resemble the following code.
Public Class HeartBeatEventArgs Inherits EventArgs End Class

3.

In the HeartBeatEventArgs class, add a DateTime property named TimeStamp. Make the property read-only by making the setter method private. You can either type this code manually, or you can use the Mod11TimeStampProperty code snippet.

Your code should resemble the following code.


Public Class HeartBeatEventArgs Inherits EventArgs Private heartBeatTimeStamp As DateTime Public Property TimeStamp As DateTime Get Return heartBeatTimeStamp End Get Private Set(ByVal value As DateTime) heartBeatTimeStamp = value End Set End Property End Class

To use the code snippet, type Mod11TimeStampProperty and then press Tab.

L11-12

Lab 11: Decoupling Methods and Handling Events

4.

Add a constructor to the HeartBeatEventArgs class. The constructor should accept no arguments and initialize the TimeStamp property to the date and time when the class is constructed. The constructor should also extend the base class constructor.

Your code should resemble the following code.


Public Class HeartBeatEventArgs Inherits EventArgs ... Public Sub New() MyBase.New() Me.TimeStamp = DateTime.Now End Sub End Class

Task 3: Declare a new delegate type.


1. At the bottom of the HeartBeatEventArgs class, declare a Public Delegate type named HeartBeatEventHandler. The delegate should refer to a method that does not return a value, but that has the following parameters. a. b. An object parameter named sender. A HeartBeatEventArgs parameter named args.

Your code should resemble the following code.


... ' Delegate defining the HeartBeat event signature. Public Delegate Sub HeartBeatEventHandler(ByVal sender As Object, ByVal args As HeartBeatEventArgs) ...

Task 4: Update the IEventEnabledMeasuringDevice interface.


1. In the Task List, locate the TODO: - Define the new event in the interface task and then doubleclick this task. This task is located in the IEventEnabledMeasuringDevice interface. a. b. c. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments. Double-click the TODO: - Define the new event in the interface task.

Remove this comment and add an event called HeartBeat to the interface. The event should specify that subscribers use the HeartBeatEventHandler delegate type to specify the method to run when the event is raised.

Your code should resemble the following code.


... ' Event that fires every heartbeat. Event HeartBeat As HeartBeatEventHandler ' TODO: - Define the HeartBeatInterval property in the interface. ...

3.

Position the mouse over the squiggly blue line under the HeartBeatEventHandler name, click the arrow to open the drop-down list, and then click Import 'MeasuringDevice.HeartBeatEventArgs'.

Lab 11: Decoupling Methods and Handling Events

L11-13

4.

Remove the TODO: - Define the HeartBeatInterval property in the interface comment, and then add a read-only integer property called HeartBeatInterval to the interface.

Your code should resemble the following code.


... ' Read-only heartbeat interval - set in constructor. ReadOnly Property HeartBeatInterval As Integer End Interface

Task 5: Add the HeartBeat event and HeartBeatInterval property to the


MeasureDataDevice class.
1. 2. In the Task List, locate and double-click the TODO: - Add the HeartBeatInterval property task. This task is located in the MeasureDataDevice class. Remove the TODO: - Add the HeartBeatInterval property comment, and add a protected integer member named heartBeatIntervalTime.

Your code should resemble the following code.


... ' Heartbeat interval in milliseconds. Protected heartBeatIntervalTime As Integer ' TODO: - Add the HeartBeat event. ...

3.

Add code to implement the public integer property HeartBeatInterval that the IEventEnabledMeasuringDevice interface defines. The property should return the value of the heartBeatInterval member when the Get accessor method is called. The property should have a Private Set accessor method to enable the constructor to set the property. You can either type this code manually, or you can use the Mod11HeartBeatIntervalProperty code snippet.

Your code should resemble the following code.


... Protected heartBeatIntervalTime As Integer Public ReadOnly Property HeartBeatInterval As Integer Implements IEventEnabledMeasuringDevice.HeartBeatInterval Get Return heartBeatIntervalTime End Get End property ' TODO: - Add the HeartBeat event. ...

4.

To use the code snippet, type Mod11TimeStampProperty and then press Tab.

Remove the TODO: - Add the HeartBeat event comment, and add the HeartBeat event that the IEventEnabledMeasuringDevice interface defines.

Your code should resemble the following code.


... ' Event that fires every heartbeat Public Event HeartBeat As HeartBeatEventHandler Implements IEventEnabledMeasuringDevice.HeartBeat ' TODO: - Add the OnHeartBeat method to fire the event. ...

L11-14

Lab 11: Decoupling Methods and Handling Events

5.

Position the mouse pointer over the squiggly blue line under the HeartBeatEventHandler name, click the arrow to open the drop-down list, and then click Import 'MeasuringDevice.HeartBeatEventArgs'. Remove the TODO: - Add the OnHeartBeat method to fire the event comment, and add a protected overridable method named OnHeartBeat that takes no parameters.

6.

Your code should resemble the following code.


... ' Overridable method to fire the OnHeartBeat event. Protected Overridable Sub OnHeartBeat() End Sub ' TODO: - Declare the BackgroundWorker to generate the heartbeat. ...

7.

In the OnHeartBeat method, add code to raise the HeartBeat event, passing the current object and a new instance of the HeartBeatEventArgs object as parameters.

Your code should resemble the following code.


... Protected Overridable Sub OnHeartBeat() RaiseEvent HeartBeat(Me, New HeartBeatEventArgs()) End Sub ...

Task 6: Use a BackgroundWorker object to generate the heartbeat.


1. Remove the TODO: - Declare the BackgroundWorker to generate the heartbeat comment, and then define a private BackgroundWorker object named heartBeatTimer.

Your code should resemble the following code.


... ' Background worker object to host the heartbeat thread. Private heartBeatTimer As BackgroundWorker ' TODO: - Create a method to configure the background Worker by using ' Lambda Expressions. ...

2.

Remove the TODO: - Create a method to configure the BackgroundWorker using Lambda Expressions comment, and declare a private method named StartHeartBeat that accepts no parameters and does not return a value.

Your code should resemble the following code.


... ' Start the BackgroundWorker that fires the heartbeat. Private Sub StartHeartBeat() End Sub

...

3.

In the StartHeartBeat method, add code to perform the following actions. a. b. Instantiate the heartBeatTimer BackgroundWorker object. Configure the heartBeatTimer object to support cancellation.

Lab 11: Decoupling Methods and Handling Events

L11-15

c.

Configure the heartBeatTimer object to support progress notification.

Your code should resemble the following code.


... Private Sub StartHeartBeat() heartBeatTimer = New BackgroundWorker() heartBeatTimer.WorkerSupportsCancellation = True heartBeatTimer.WorkerReportsProgress = True End Sub ...

4.

Add a handler for the heartBeatTimer DoWork event by using a lambda expression to define the actions to be performed. The lambda expression should take two parameters (use the names o and args). In the Lambda expression body, add a While loop that continually iterates and contains code to perform the following actions. You can either type this code manually, or you can use the Mod11DoWorkHandler code snippet. a. b. c. Use the shared Thread.Sleep method to put the current thread to sleep for the length of time that the HeartBeatInterval property indicates. Check the value of the disposedValue property. If the value is True, terminate the loop. Call the heartBeatTimer.ReportProgress method, passing zero as the parameter.

Note: Use the AddHandler statement to specify that the method will handle the DoWork event. Your code should resemble the following code.
... heartBeatTimer.WorkerReportsProgress = True AddHandler heartBeatTimer.DoWork, Sub(o, args) While True Thread.Sleep(HeartBeatInterval) If disposedValue Then Exit While End If heartBeatTimer.ReportProgress(0) End While End Sub

...

5.

To use the code snippet, type Mod11DoWorkHandler and then press Tab.

Add a handler for the heartBeatTimer.ReportProgress event by using another lambda expression to create the method body. In the lambda expression body, add code to call the OnHeartBeat method, which raises the HeartBeat event.

Your code should resemble the following code.


... heartBeatTimer.ReportProgress(0) End While End Sub

AddHandler heartBeatTimer.ProgressChanged, Sub(o, args) OnHeartBeat()

L11-16

Lab 11: Decoupling Methods and Handling Events

...

End Sub

6.

At the end of the StartHeartBeat method, add a line of code to start the heartBeatTimer BackgroundWorker object running asynchronously.

Your code should resemble the following code.


... AddHandler heartBeatTimer.ProgressChanged, Sub(o, args) OnHeartBeat() End Sub heartBeatTimer.RunWorkerAsync() End Sub ...

Task 7: Call the StartHeartBeat method when the MeasureDataDevice object starts
running.
1. 2. In the Task List, locate the TODO: - Call StartHeartBeat from StartCollecting method task, and then double-click this task. This task is located in the StartCollecting method. Remove this comment, and add a line of code to invoke the StartHeartBeat method.

Your code should resemble the following code.


... loggingFileWriter.WriteLine("Collecting Started") End If StartHeartBeat() GetMeasurements() ...

Task 8: Dispose of the heartBeatTimer BackgroundWorker object when the


MeasureDataDevice object is destroyed.
1. 2. In the Task List, locate the TODO: - dispose of the heartBeatTimer BackgroundWorker task, and then double-click this task. This task is located in the Dispose method. Remove the comment and add code to check that the heartBeatTimer BackgroundWorker object is not null. If the heartBeatTimer object is not null, call the Dispose method of the BackgroundWorker object.

Your code should resemble the following code.


... If Not dataCollector Is Nothing Then dataCollector.Dispose() End If

If Not heartBeatTimer Is Nothing Then heartBeatTimer.Dispose() End If End Sub ...

Lab 11: Decoupling Methods and Handling Events

L11-17

You have now updated the MeasureDataDevice abstract class to implement event handlers by using lambda expressions. To enable the application to benefit from these changes, you must modify the MeasureMassDevice class, which extends the MeasureDataDevice class.

Task 9: Update the constructor for the MeasureMassDevice class.


1. Open the MeasureMassDevice class file. 2. In Solution Explorer, in the MeasuringDevice project, double-click MeasureMassDevice.vb.

At the start of the class, modify the signature of the constructor to take an additional Integer value named heartBeatInterval.

Your code should resemble the following code.


... Public Sub New(ByVal deviceUnits As Units, ByVal logFileName As String, ByVal heartBeatInterval As Integer) dataDeviceUnitsToUse = deviceUnits dataDeviceMeasurementType = DeviceType.MASS dataDeviceLoggingFileName = logFileName End Sub ...

3.

Modify the body of the constructor to store the value of the HeartBeatInterval member in the heartBeatInterval member.

Your code should resemble the following code.


... dataDeviceLoggingFileName = logFileName heartBeatIntervalTime = heartBeatInterval End Sub ...

4.

Below the existing constructor, remove the TODO: Add a chained constructor that calls the previous constructor comment, and add a second constructor that accepts the following parameters. a. b. A Units instance named deviceUnits. A String instance named logFileName.

Your code should resemble the following code.


... Public Sub New(ByVal deviceUnits As Units, ByVal logFileName As String) End Sub ...

5.

Modify the new constructor to call the existing constructor. Pass a value of 1000 as the heartBeatInterval parameter value.

Your code should resemble the following code.


... Public Sub New(ByVal deviceUnits As Units, ByVal logFileName As String) Me.New(deviceUnits, logFileName, 1000) End Sub ...

L11-18

Lab 11: Decoupling Methods and Handling Events

Task 10: Handle the HeartBeat event in the UI.


1. In the Task List, locate the TODO: - Use a lambda expression to handle the HeartBeat event in the UI task, and then double-click the task. This task is located in the StartCollectingButton_Click method in the code behind the MainWindow window in the Monitor project. Remove the comment and add a lambda expression to handle the device.HeartBeat event. The lambda expression should take two parameters (name them o and args). In the body of the lambda expression, add code to update the HeartBeatTimeStampLabel control with the text "HeartBeat Timestamp: timestamp" where timestamp is the value of the args.TimeStamp property. You can either type this code manually, or you can use the Mod11HeartBeatHandler code snippet.

2.

Hint: Set the Content property of a label to modify the text that the label displays. Your code should resemble the following code.
... AddHandler device.HeartBeat, Sub(o, args) HeartBeatTimeStampLabel.Content = String.Format("HeartBeat Timestamp: {0}", args.TimeStamp) End Sub ...

To use the code snippet, type Mod11HeartBeatHandler and then press TAB.

Task 11: Test the solution.


1. Build the project and correct any errors. 2. On the Build menu, click Build Solution.

Start the application. On the Debug menu, click Start Debugging.

3. 4.

Click Start Collecting, and verify that values begin to appear as before. Also, note that the HeartBeat Timestamp value now updates once per second. Click Stop Collecting, and verify that the RawData list box no longer updates. Note that the timestamp continues to update, because your code does not terminate the timestamp heartbeat when you stop collecting. Click Dispose Object and verify that the timestamp no longer updates. Close the application and then return to Visual Studio. Close Visual Studio. In Visual Studio, on the File menu, click Exit.

5. 6. 7.

Lab A: Using Collections

L12A-1

Module 12: Using Collections and Building Generic Types

Lab A: Using Collections


Exercise 1: Optimizing a Method by Caching Data
Task 1: Open the Collections solution.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Open the Collections solution in the D:\Labfiles\Lab12\LabA\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab12\LabA \Ex1\Starter folder, click Collections.sln, and then click Open.

Task 2: Modify the Gauss class to implement the memoization pattern.


1. In the TestHarness project, display the MainWindow.xaml window. In Solution Explorer, expand the TestHarness project, and then double-click MainWindow.xaml.

The MainWindow window implements a simple test harness to enable you to test the method that you will use to perform Gaussian elimination. This is a Windows Presentation Foundation (WPF) application that enables a user to enter the coefficients for four simultaneous equations that consist of four variables (w, x, y, and z). It then uses Gaussian elimination to find a solution for these equations. The results are displayed in the lower part of the screen. 2. Review the Task List. a. b. 3. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

In the Task List, locate the TODO: - Add a Shared Hashtable task, and then double-click this task.

This task is located in the GaussianElimination project, in the Gauss class. 4. At the top of the Gauss.vb file, at the end of the list of Imports statements, add a statement to bring the System.Text namespace into scope.

Your code should resemble the following code.


... Imports System.Threading Imports System.Text ...

5.

Remove the comment, and then add code to define a shared Hashtable object named results.

Your code should resemble the following code.


Public Class Gauss Private Shared results As Hashtable ...

L12A-2

Lab A: Using Collections

End Class

6.

At the beginning of the SolveGaussian method, before the statements that create a deep copy of the parameters, add code to ensure that the results Hashtable object is initialized. Create a new instance of this object, if it is currently Nothing.

Your code should resemble the following code.


... Public Shared Function SolveGaussian(ByVal coefficients(,) As Double, ByVal rhs() As Double) As Double() If results Is Nothing Then results = New Hashtable() End If ... End Function ...

7.

Add code to generate a hash key that is based on the method parameters, by performing the following tasks. a. b. c. d. Define a new StringBuilder object named hashString. Iterate through the coefficients array and append each value in the array to the hashString StringBuilder object. Iterate through the rhs array and append each value in the array to the hashString StringBuilder object. Define a new string object named hashValue, and initialize it to the value that the hashString.ToString method returns.

Hint: This procedure generates a hash key by simply concatenating the values that are passed into the method. You can use more advanced hashing algorithms to generate better hashes. The System.Security.Cryptography namespace includes many classes that you can use to implement hashing. Your code should resemble the following code.
... Public Shared Function SolveGaussian(ByVal coefficients(,) As Double, ByVal rhs() As Double) As Double() ... Dim hashString As New StringBuilder() For Each coefficient As Double In coefficients hashString.Append(coefficient) Next For Each value As Double In rhs hashString.Append(value) Next Dim hashValue As String = hashString.ToString() ... End Sub ...

Lab A: Using Collections

L12A-3

8.

Add code to check whether the results object already contains a key that has the value in the hashValue string. If it does, return the value that is stored in the Hashtable collection class that corresponds to the hashValue key. If the results object does not contain the hashValue key, the method should use the existing logic in the method to perform the calculation.

Hint: A Hashtable object stores and returns values as objects. You must cast the value that is returned from a Hashtable object to the appropriate type before you work with it. In this case, cast the returned value to an array of Double values. Your code should resemble the following code.
... Public Shared Function SolveGaussian(ByVal coefficients(,) As Double, ByVal rhs() As Double) As Double() ... Dim hashValue As String = hashString.ToString() If results.Contains(hashValue) Then Return CType(results(hashValue), double()) Else ' Make a deep copy of the parameters ... Return rhsCopy End If End Sub ...

9.

In the Task List, locate the TODO: - Store the result of the calculation in the Hashtable task, and then double-click this task.

This task is located near the end of the SolveGaussian method. 10. Remove the comment, and then add code to the method to store the rhsCopy array in the Hashtable object, specifying the hashValue object as the key. Your code should resemble the following code.
... Public Shared Function SolveGaussian(ByVal coefficients(,) As Double, ByVal rhs() As Double) As Double() ... Else ... System.Threading.Thread.Sleep(5000) results.Add(hashValue, rhsCopy) Return rhsCopy End If End Sub ...

Task 3: Test the solution.


1. Build the solution and correct any errors. 2. On the Build menu, click Build Solution. Correct any errors.

Run the application. Press Ctrl+F5.

L12A-4

Lab A: Using Collections

3. w 2 -3 -2 3

In the MainWindow window, enter the following equations, and then click Solve. x 1 -1 1 -1 y -1 2 -2 2 z 1 1 0 -2 Result 8 -11 -3 -5

Observe that the operation takes approximately five seconds to complete. 4. Verify that the following results are displayed. 5. w -2 w=4 x = 17 y = 11 z=6

Modify the third equation to match the following equation, and then click Solve again. x 1 y -2 z 3 Result -3

Observe that this operation also takes approximately five seconds to complete. 6. Verify that the following results are displayed. 7. w = 2 x = 25 y=7 z = 6

Undo the change to the third equation so that all the equations match those in Step 3, and then click Solve. Observe that this time, the operation takes less time to complete because it uses the solution that was generated earlier. Close the application, and then close Visual Studio. In Visual Studio, on the File menu, click Exit.

8.

Lab B: Building Generic Types

L12B-1

Module 12: Using Collections and Building Generic Types

Lab B: Building Generic Types


Exercise 1: Defining a Generic Interface
Task 1: Open the GenericTypes solution.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Import the code snippets from the D:\Labfiles\Lab12\LabB\Snippets folder. a. b. c. d. e. In Visual Studio, on the Tools menu, click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language drop-down list box, select Visual Basic. Click Add. In the Code Snippets Directory dialog box, browse to the D:\Labfiles \Lab12\LabB\Snippets folder, and then click Select Folder. In the Code Snippets Manager dialog box, click OK.

3.

Open the GenericTypes solution in the D:\Labfiles\Lab12\LabB\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab12\LabB \Ex1\Starter folder, click GenericTypes.sln, and then click Open.

Task 2: Define the generic IBinaryTree interface.


1. Review the Task List. a. b. 2. 3. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

In the Task List, locate the TODO: Define the IBinaryTree interface task, and then double-click this task. This task is located in the IBinaryTree.vb file. Define a new generic public interface named IBinaryTree. This interface should take a single type parameter named TItem. Specify that the type parameter must implement the IComparable interface.

Your code should resemble the following code.


Public Interface IBinaryTree(Of TItem As IComparable) End Interface

4.

In the IBinaryTree interface, define the following public methods. a. b. c. An Add method, which takes a TItem object named newItem as a parameter and does not return a value A Remove method, which takes a TItem object named itemToRemove as a parameter and does not return a value A WalkTree method, which takes no parameters and does not return a value

Your code should resemble the following code.

L12B-2

Lab B: Building Generic Types

Public Interface IBinaryTree(Of TItem As IComparable) Sub Add(ByVal newItem As TItem) Sub Remove(ByVal itemToRemove As TItem) Sub WalkTree() End Interface

5.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Exercise 2: Implementing a Generic Interface


Task 1: Open the GenericTypes solution.
Open the GenericTypes solution in the D:\Labfiles\Lab12\LabB\Ex2\Starter folder. This solution is a modified version of the solution from Exercise 1. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab12\LabB \Ex2\Starter folder, click GenericTypes.sln, and then click Open.

Task 2: Create the Tree class.


1. In the BinaryTree project, add a new class named Tree. a. b. 2. In Solution Explorer, right-click the BinaryTree project, point to Add, and then click Class. In the Add New Item - BinaryTree dialog box, in the Name box, type Tree and then click Add.

Modify the Tree class definition. This class should be a public generic class that takes a single type parameter named TItem and implements the IBinaryTree interface. The TItem type parameter must implement the generic IComparable interface.

Your code should resemble the following code.


Public Class Tree(Of TItem As IComparable) Implements IBinaryTree(Of TItem) End Class

3.

Add the following automatic properties to the Tree class. a. b. c. A TItem property named NodeData A generic Tree(Of TItem) property named LeftTree A generic Tree(Of TItem) property named RightTree

Your code should resemble the following code.


Public Class Tree(Of TItem As IComparable) Implements IBinaryTree(Of TItem) Public Property NodeData As TItem Public Property LeftTree As Tree(Of TItem) Public Property RightTree As Tree(Of TItem) End Class

Lab B: Building Generic Types

L12B-3

4.

Add a public constructor to the Tree class. The constructor should take a single TItem parameter named nodeValue. The constructor should initialize the NodeData member by using the nodeValue parameter, and then set the LeftTree and RightTree members to Nothing.

Your code should resemble the following code.


Public Class Tree(Of TItem As IComparable) Implements IBinaryTree(Of TItem) ... Public Sub New(ByVal nodeValue As TItem) Me.NodeData = nodeValue Me.LeftTree = Nothing Me.RightTree = Nothing End Sub End Class

5.

After the constructor, define a method named Add. This method should take a TItem object as a parameter, but not return a value.

Your code should resemble the following code.


Public Class Tree(Of TItem As IComparable) Implements IBinaryTree(Of TItem) ... Public Sub New(ByVal nodeValue As TItem) Me.NodeData = nodeValue Me.LeftTree = Nothing Me.RightTree = Nothing End Sub Public Sub Add(ByVal newItem As TItem) Implements IBinaryTree(Of TItem).Add End Sub End Class

6.

In the Add method, add code to insert the newItem object into the tree in the appropriate place by performing the following tasks. You can type this code manually, or use the Mod12Add code snippet. a. Compare the value of the newItem object with the value of the NodeData property. Both items implement the IComparable interface, so use the CompareTo method of the NodeData property. The CompareTo method returns zero if both items have the same value, a positive value if the value of the NodeData property is greater than the value of the newItem object, and a negative value if the value of the NodeData property is less than the value of the newItem object. If the value of the newItem object is less than the value of the NodeData property, perform the following actions to insert a newItem object into the left subtree. If the LeftTree property is Nothing, initialize it and pass the newItem object to the constructor. If the LeftTree property is not Nothing, recursively call the Add method of the LeftTree property and pass the newItem object as the parameter. If the value of the newItem object is greater than or equal to the value of the NodeData property, perform the following actions to insert the newItem object into the right subtree. If the RightTree property is Nothing, initialize it and pass the value of the newItem object to the constructor. If the RightTree property is not Nothing, recursively call the Add method of the RightTree property and pass the newItem object as the parameter.

b. c. d. e. f. g.

Your code should resemble the following code.

L12B-4

Lab B: Building Generic Types

Public Sub Add(ByVal newItem As TItem) Implements IBinaryTree(Of TItem).Add Dim currentNodeValue As TItem = Me.NodeData ' Check if the item should be inserted in the left tree. If currentNodeValue.CompareTo(newItem) > 0 Then ' Is the left tree null? If Me.LeftTree Is Nothing Then Me.LeftTree = New Tree(Of TItem)(newItem) Else ' Call the Add method recursively. Me.LeftTree.Add(newItem) End If Else ' Insert in the right tree. ' Is the right tree null? If Me.RightTree Is Nothing Then Me.RightTree = New Tree(Of TItem)(newItem) Else ' Call the Add method recursively. Me.RightTree.Add(newItem) End If End If End Sub

7.

To use the code snippet, type Mod12Add, and then press Tab.

After the Add method, add another public method named WalkTree that does not take any parameters and does not return a value.

Your code should resemble the following code.


Public Class Tree(Of TItem As IComparable) Implements IBinaryTree(Of TItem) ... Public Sub Add(ByVal newItem As TItem) ... End Sub Public Sub WalkTree()Implements IBinaryTree(Of TItem).WalkTree End Sub End Class

8.

In the WalkTree method, add code that traverses each node in the tree in order and displays the value that each node holds by performing the following tasks. You can type this code manually, or use the Mod12WalkTree code snippet. a. b. c. If the value of the LeftTree property is not Nothing, recursively call the WalkTree method on the LeftTree property. Display the value of the NodeData property to the console by using a Console.WriteLine statement. If the value of the RightTree property is not Nothing, recursively call the WalkTree method on the RightTree property.

Your code should resemble the following code.


Public Sub WalkTree() Implements IBinaryTree(Of TItem).WalkTree ' Recursive descent of the left tree. If Not Me.LeftTree Is Nothing Then Me.LeftTree.WalkTree() End If Console.WriteLine(Me.NodeData.ToString()) ' Recursive descent of the right tree.

Lab B: Building Generic Types

L12B-5

If Not Me.RightTree Is Nothing Then Me.RightTree.WalkTree() End If End Sub

9.

To use the code snippet, type Mod12WalkTree, and then press Tab.

After the WalkTree method, add the Remove method to delete a value from the tree, as the following code example shows. It is not necessary for you to fully understand how this method works, so you can either type this code manually, or use the Mod12Remove code snippet.

Public Sub Remove(ByVal itemToRemove As TItem) Implements IBinaryTree(Of TItem).Remove ' Cannot remove null. If itemToRemove Is Nothing Then Return ' Check if the item could be in the left tree. If Me.NodeData.CompareTo(itemToRemove) > 0 AndAlso Not Me.LeftTree Is Nothing Then ' Check the left tree. ' Check 2 levels down the tree - cannot remove ' 'this', only the LeftTree or RightTree properties. If Me.LeftTree.NodeData.CompareTo(itemToRemove) = 0 Then ' The LeftTree property has no children - set the ' LeftTree property to null. If Me.LeftTree.LeftTree Is Nothing AndAlso Me.LeftTree.RightTree Is Nothing Me.LeftTree = Nothing Else ' Remove LeftTree. RemoveNodeWithChildren(Me.LeftTree) End If

Then

' Keep looking - call the Remove method recursively. Me.LeftTree.Remove(itemToRemove) End If End If ' Check if the item could be in the right tree.? If Me.NodeData.CompareTo(itemToRemove) < 0 AndAlso Not Me.RightTree Is Nothing Then ' Check the right tree. ' Check 2 levels down the tree - cannot remove ' 'this', only the LeftTree or RightTree properties. If Me.RightTree.NodeData.CompareTo(itemToRemove) = 0 Then ' The RightTree property has no children set the ' RightTree property to null. If Me.RightTree.LeftTree Is Nothing AndAlso Me.RightTree.RightTree Is Nothing Me.RightTree = Nothing Else ' Remove the RightTree. RemoveNodeWithChildren(Me.RightTree) End If

Else

Then

' Keep looking - call the Remove method recursively. Me.RightTree.Remove(itemToRemove) End If End If ' This will only apply at the root node. If Me.NodeData.CompareTo(itemToRemove) = 0 Then ' No children - do nothing, a tree must have at least ' one node. If Me.LeftTree Is Nothing AndAlso Me.RightTree Is Nothing Then Return Else ' The root node has children. RemoveNodeWithChildren(Me)

Else

L12B-6

Lab B: Building Generic Types

End If End If End Sub

To use the code snippet, type Mod12Remove, and then press Tab.

10. After the Remove method, add the RemoveNodeWithChildren method to remove a node that contains children from the tree, as the following code example shows. This method is named by the Remove method. Again, it is not necessary for you to understand how this code works, so you can either type this code manually, or use the Mod12RemoveNodeWithChildren code snippet.
Private Sub RemoveNodeWithChildren(ByVal node As Tree(Of TItem)) ' Check whether the node has children. If node.LeftTree Is Nothing AndAlso node.RightTree Is Nothing Then Throw New ArgumentException("Node has no children") End If ' The tree node has only one child - replace the ' tree node with its child node. If node.LeftTree Is Nothing Xor node.RightTree Is Nothing Then If node.LeftTree Is Nothing Then node.CopyNodeToThis(node.RightTree) Else node.CopyNodeToThis(node.LeftTree) End If Else ' The tree node has two children - replace the tree node's value ' with its "in order successor" node value and then remove the ' in order successor node. ' Find the in order successor the leftmost descendant of ' its RightTree node. Dim successor As Tree(Of TItem) = GetLeftMostDescendant(node.RightTree) ' Copy the node value from the in order successor. node.NodeData = successor.NodeData ' Remove the in order successor node. If node.RightTree.RightTree Is Nothing AndAlso node.RightTree.LeftTree Is Nothing node.RightTree = Nothing ' The successor node had no ' children.

Then

node.RightTree.Remove(successor.NodeData) End If End If End Sub

Else

To use the code snippet, type Mod12RemoveNodeWithChildren, and then press Tab.

11. After the RemoveNodeWithChildren method, add the CopyNodeToThis method, as the following code example shows. The RemoveNodeWithChildren method calls this method to copy another node's property values into the current node. You can either type this code manually, or use the Mod12CopyNodeToThis code snippet.
Private Sub CopyNodeToThis(ByVal node As Tree(Of TItem)) Me.NodeData = node.NodeData Me.LeftTree = node.LeftTree Me.RightTree = node.RightTree End Sub

To use the code snippet, type Mod12CopyNodeToThis, and then press Tab.

Lab B: Building Generic Types

L12B-7

12. After the CopyNodeToThis method, add the GetLeftMostDescendant method, as the following code example shows. The RemoveNodeWithChildren method also calls this method to retrieve the leftmost descendant of a tree node. You can either type this code manually, or use the Mod12GetLeftMostDescendant code snippet.
Private Function GetLeftMostDescendant(ByVal node As Tree(Of TItem)) As Tree(Of TItem) While Not node.LeftTree Is Nothing node = node.LeftTree End While Return node End Function

To use the code snippet, type Mod12GetLeftMostDescendant, and then press Tab.

13. Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Exercise 3: Implementing a Test Harness for the BinaryTree Project


Task 1: Open the GenericTypes solution.
Open the GenericTypes solution in the D:\Labfiles\Lab12\LabB\Ex3\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab12\LabB \Ex3\Starter folder, click GenericTypes.sln, and then click Open.

Task 2: Import the TestHarness project.


1. Import the TestHarness project in the D:\Labfiles\Lab12\LabB\Ex3\Starter \TestHarness folder into the GenericTypes solution. a. b. 2. In Visual Studio, in Solution Explorer, right-click Solution 'GenericTypes' (1 Project), point to Add, and then click Existing Project. In the Add Existing Project dialog box, browse to the D:\Labfiles\Lab12 \LabB\Ex3\Starter\TestHarness folder, click TestHarness.vbproj, and then click Open.

Set the TestHarness project as the startup project. In Solution Explorer, right-click TestHarness, and then click Set as Startup Project.

Task 3: Complete the test harness.


1. Open the Module1.vb file. 2. In Solution Explorer, in the TestHarness project, double-click Module1.vb.

At the top of the Module1.vb file, add a statement to bring the BinaryTree namespace into scope.

Your code should resemble the following code example.


Imports BinaryTree Module Module1 ...

L12B-8

Lab B: Building Generic Types

3.

In the Main method, add code to instantiate a new IBinaryTree object named tree, using Integer as the type parameter. Pass the value 5 to the constructor. This code creates a new binary tree of integers and adds an initial node that contains the value 5.

Your code should resemble the following code.


... Sub Main() Dim tree As IBinaryTree(Of Integer) = New Tree(Of Integer)(5) End Sub ...

4.

Add code to the Main method to add the following values to the tree, in the following order, 1, 4, 7, 3, 4.

Your code should resemble the following code.


... Sub Main() Dim tree As IBinaryTree(Of Integer) = New Tree(Of Integer)(5) tree.Add(1) tree.Add(4) tree.Add(7) tree.Add(3) tree.Add(4) End Sub ...

5.

Add code to the Main method to perform the following actions. a. b. c. d. e. f. Print the message "Current Tree: " to the console, and then invoke the WalkTree method on the tree object. Print the message "Add 15" to the console, and then add the value 15 to the tree. Print the message "Current Tree: " to the console, and then invoke the WalkTree method on the tree object. Print the message "Remove 5" to the console, and then remove the value 5 from the tree. Print the message "Current Tree: " to the console, and then invoke the WalkTree method on the tree object. Pause at the end of the method until Enter is pressed.

Your code should resemble the following code.


... Sub Main() ... Console.WriteLine("Current Tree: ") tree.WalkTree() Console.WriteLine("Add 15") tree.Add(15) Console.WriteLine("Current Tree: ") tree.WalkTree() Console.WriteLine("Remove 5") tree.Remove(5) Console.WriteLine("Current Tree: ") tree.WalkTree() Console.ReadLine() End Sub ...

Lab B: Building Generic Types

L12B-9

6.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 4: Test the BinaryTree project.


1. Run the application. 2. Press Debug menu, click Start Debugging.

Verify that the output in the console window resembles the following code example. Note that the data in the binary tree is sorted and is displayed in ascending order.
Current Tree: 1 3 4 4 5 7 Add 15 Current Tree: 1 3 4 4 5 7 15 Remove 5 Current Tree: 1 3 4 4 7 15

3.

Press Enter to close the console window, and then return to Visual Studio.

Exercise 4: Implementing a Generic Method


Task 1: Open the GenericTypes solution.
Open the GenericTypes solution in the D:\Labfiles\Lab12\LabB\Ex4\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab12\LabB \Ex4\Starter folder, click GenericTypes.sln, and then click Open.

Note: The GenericTypes solution in the Ex4 folder is functionally the same as the code that you completed in Exercise 3. However, it includes an updated task list and a new test project to enable you to complete this exercise.

Task 2: Create the BuildTree method.


1. Review the Task List. a. b. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

L12B-10

Lab B: Building Generic Types

2. 3.

In the Task List, locate the TODO: - Add the BuildTree generic method task, and then double-click this task. This task is located at the end of the Tree class. Remove the TODO: - Add the BuildTree generic method comment, and then add a public shared generic method named BuildTree to the Tree class. The type parameter for the method should be named TreeItem, and the method should return a generic Tree(Of TreeItem) object. The TreeItem type parameter must represent a type that implements the generic IComparable interface.

The method should take two parameters: a TreeItem object named nodeValue and a ParamArray array of TreeItem objects named values. Your code should resemble the following code.
Public Shared Function BuildTree(Of TreeItem As IComparable)(ByVal nodeValue As TreeItem, ByVal ParamArray values() As TreeItem) As Tree(Of TreeItem) End Function

4.

In the BuildTree method, add code to construct a new Tree object by using the data that is passed in as the parameters by performing the following actions. a. b. c. Define a new Tree object named integerTree by using the TreeItem type parameter, and initialize the new Tree object by using the nodeValue parameter. Iterate through the values array, and add each value in the array to the integerTree object. Return the integerTree object at the end of the method.

Your code should resemble the following code.


Public Shared Function BuildTree(Of TreeItem As IComparable)(ByVal nodeValue As TreeItem, ByVal ParamArray values() As TreeItem) As Tree(Of TreeItem) Dim integerTree As Tree(Of TreeItem) = New Tree(Of TreeItem)(nodeValue) For Each item As TreeItem In values integerTree.Add(item) Next Return integerTree End Function

Task 3: Modify the test harness to use the BuildTree method.


1. In the Task List, locate the TODO: - Modify the test harness to use the BuildTree method task, and then double-click this task.

This task is located in the Main method of the Module1.vb class file in the TestHarness project. 2. In the Main method, remove the existing code that instantiates the tree object and adds the first five values to the tree. Replace this code with a statement that calls the BuildTree method to create a new Tree object named integerTree, based on the integer type, with the following integer values, 1, 4, 7, 3, 4, and 5.

Your code should resemble the following code.


Sub Main() Dim integerTree As IBinaryTree(Of Integer) = Tree(Of Integer).BuildTree(Of Integer)(1, {4, 7, 3, 4, 5}) Console.WriteLine("Current Tree: "); ... End Sub

Lab B: Building Generic Types

L12B-11

3. 4.

Rename current uses of the tree object to integerTree. Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

5.

Run the application. On the Debug menu, click Start Debugging.

6.

Verify that the output in the console window resembles the following code example.

Current Tree: 1 3 4 4 5 7 Add 15 Current Tree: 1 3 4 4 5 7 15 Remove 5 Current Tree: 1 3 4 4 7 15

7. 8.

Press Enter to close the console window. Close Visual Studio. In Visual Studio, on the File menu, click Exit.

L12B-12

Lab B: Building Generic Types

Lab: Building and Enumerating Custom Collection Classes

L13-1

Module 13: Building and Enumerating Custom Collection Classes

Lab: Building and Enumerating Custom Collection Classes


Exercise 1: Implementing the IList(Of TItem) Interface
Task 1: Open the CustomCollections solution.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Open the CustomCollections solution in the D:\Labfiles\Lab13\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab13\Ex1 \Starter folder, click CustomCollections.sln, and then click Open.

3.

Import the code snippets from the D:\Labfiles\Lab13\Snippets folder. a. b. c. d. e. In Visual Studio, on the Tools menu, click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language drop-down list box, select Visual Basic. Click Add. In the Code Snippets Directory dialog box, browse to the D:\Labfiles \Lab13\Snippets folder, and then click Select Folder. In the Code Snippets Manager dialog box, click OK.

Task 2: Modify the Tree class to implement the IList(Of TItem) interface.
1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

In the Task List, locate the TODO: - Implement the generic IList(Of TItem) interface task, and then double-click this task.

This task is located in the Tree class. 3. Remove the TODO: - Implement the generic IList(Of TItem) interface comment, and then modify the class definition to implement the generic IList interface. Specify the value TItem as the type parameter (this is the type parameter that the Tree class references).

Your code should resemble the following code.


Public Class Tree(Of TItem As IComparable) Implements IBinaryTree(Of TItem), IList(Of TItem) ... End Class

4.

Add method and property stubs that implement the IList interface.

L13-2

Lab: Building and Enumerating Custom Collection Classes

In the class definition, place the cursor at the end of the line of code, Implements IBinaryTree(Of TItem), IList(Of TItem), and then press Enter. Visual Studio generates method stubs for each method that is defined in the interface, and adds them to the end of the class file. You will add code to complete some of these methods, later in this exercise.

Task 3: Add support for indexing items in the Tree class.


1. 2. In the Task List, locate the TODO: - Add a member to define node position task, and then doubleclick this task. Remove the TODO: - Add a member to define node position comment, and then add code to define a private integer member named position.

Your code should resemble the following code.


Public Class Tree(Of TItem As IComparable) Implements IBinaryTree(Of TItem), IList(Of TItem) ... Public Property RightTree As Tree(Of TItem) ' Add a private integer variable position to define ' the node's position in the tree. Private position As Integer ... End Class

3.

In the class constructor, add code to initialize the position member to 1.

Note: The position member is the index for items in the tree. When you add or remove items from the tree, you will invalidate the position member of any following elements in the tree. By setting the position member to 1, you indicate to the tree that the index has become invalid. When the application attempts to use the index to perform an action, and encounters a negative value, the application can rebuild the index by invoking the IndexTree method that you will add later. Your code should resemble the following code.
Public Sub New(ByVal nodeValue As TItem) ... Me.position = -1 End Sub

4.

At the beginning of the implementation of the IBinaryTree.Add method, add code to set the position member to 1.

Your code should resemble the following code.


Public Sub Add(ByVal newItem As TItem) Implements IBinaryTree(Of TItem).Add ' If we're adding something, the position field will become ' invalid. Reset position to -1. Me.position = -1 ... End Sub

5.

In the Task List, locate the TODO: - Set the position member to -1 task, and then double-click this task.

This task is located in the Remove method.

Lab: Building and Enumerating Custom Collection Classes

L13-3

6.

Remove the TODO: - Set the position member to -1 comment, and then add code to set the position member to 1.

Your code should resemble the following code.


Public Sub Remove(ByVal itemToRemove As TItem) ... ' If we're deleting something, the position field will become ' invalid. Reset position to -1 Me.position = -1 ... End Sub

7.

In the Task List, locate the TODO: - Add methods to enable indexing the tree task, and then double-click this task.

This task is located at the end of the Tree class, in the Utility methods code region. 8. Delete the TODO: - Add methods to enable indexing the tree comment, and then add a method named IndexTree. This method should accept an integer parameter named index, and return an integer value. Add code to the method to perform the following actions. You can type this code manually, or use the Mod13IndexTreeMethod code snippet. a. If the LeftTree property is not Nothing, call the IndexTree method of the LeftTree property and assign the result to the index parameter. Pass the current value of the index parameter to the IndexTree method. Update the local position member with the value of the index parameter. Increment the index parameter. If the RightTree property is not Nothing, call the IndexTree method of the RightTree property and assign the result to the index parameter. Pass the current value of the index parameter to the IndexTree method. At the end of the method, return the value of the index parameter.

b. c. d.

e.

Your code should resemble the following code.


Public Class Tree(Of TItem As IComparable) Implements IBinaryTree(Of TItem), IList(Of TItem) ... #Region "Utility methods" ... Private Function IndexTree(ByVal index As Integer) As Integer If Not Me.LeftTree Is Nothing Then index = Me.LeftTree.IndexTree(index) End If Me.position = index index +=1 If Not Me.RightTree Is Nothing Then index = Me.RightTree.IndexTree(index) End If Return index End Function #End Region End Class

To use the code snippet, type Mod13IndexTreeMethod, and then press Tab.

L13-4

Lab: Building and Enumerating Custom Collection Classes

9.

After the IndexTree method, add a private method named GetItemAtIndex. This method should accept an integer parameter named index, and return a Tree(Of TItem) object. In the method, add code to perform the following actions. You can type this code manually, or use the Mod13GetItemAtIndexMethod code snippet. a. b. If the value of the position member is 1, call the local IndexTree method. Pass 0 as the parameter to the IndexTree method. If the value of the position member is greater than the value of the index parameter, call the GetItemAtIndex method of the LeftTree property and return the value that is generated. Pass the value of the index parameter to the GetItemAtIndex method. If the value of the position member is less than the value of the index parameter, call the GetItemAtIndex method of the RightTree property and return the value that is generated. Pass the value of the index parameter to the GetItemAtIndex method. At the end of the method, return a reference to the current object (Me).

c.

d.

Your code should resemble the following code.


Private Function GetItemAtIndex(ByVal index As Integer) As Tree(Of TItem) ' Add the index values if they're not already there If Me.position = -1 Then Me.IndexTree(0) End If If Me.position > index Then Return Me.LeftTree.GetItemAtIndex(index) End If If Me.position < index Then Return Me.RightTree.GetItemAtIndex(index) End If Return Me End Function

To use the code snippet, type Mod13GetItemAtIndexMethod, and then press Tab.

10. After the GetItemAtIndex method, add a private method named GetCount. This method should accept an integer parameter named accumulator, and return an integer value. Add code to the method to perform the following actions. You can type this code manually, or use the Mod13GetCountMethod code snippet. a. If the LeftTree property is not Nothing, call the GetCount method of the LeftTree property and store the result in the accumulator variable. Pass the current value of the accumulator variable to the GetCount method. Increment the value in the accumulator parameter. If the RightTree property is not Nothing, call the GetCount method of the RightTree property and store the result in the accumulator parameter. Pass the current value of the accumulator parameter to the GetCount method. At the end of the method, return the value of the accumulator variable.

b. c.

d.

Your code should resemble the following code.


Private Function GetCount(ByVal accumulator As Integer) As Integer If Not Me.LeftTree Is Nothing Then accumulator = LeftTree.GetCount(accumulator) End If

Lab: Building and Enumerating Custom Collection Classes

L13-5

Accumulator += 1 If Not Me.RightTree Is Nothing Then accumulator = RightTree.GetCount(accumulator) End If Return accumulator End Function

To use the code snippet, type Mod13GetCountMethod, and then press Tab.

Task 4: Implement the IList(Of T) interface methods and properties.


1. Locate the IndexOf method. This method accepts a TItem object named item, and returns an integer value.

This method should iterate through the tree and return a value that indicates the index of the TItem object in the tree. 2. Replace the code in the IndexOf method with code to perform the following actions. You can type this code manually, or use the Mod13IndexOf code snippet. a. b. c. d. If the item parameter is Nothing, return the value 1. If the value of the position member is 1, call the IndexTree method and pass the value 0 as a parameter to the IndexTree method. Compare the value of the item parameter to the local NodeData property value. If the value of the item parameter is less than the value in the NodeData property, and if the LeftTree property is Nothing, return 1. Otherwise, return the result of a recursive call to the LeftTree.IndexOf method, passing the item value to the IndexOf method. If the value of the item parameter is greater than the value in the NodeData property, and if the RightTree property is Nothing, return 1. Otherwise, return the result of a recursive call to the RightTree.IndexOf method, passing the item value to the IndexOf method.

e.

Hint: Use the CompareTo method to compare the value in the item parameter and the value in the NodeData property. f. At the end of the method, return the value of the local position member.

Your code should resemble the following example.


Public Function IndexOf(ByVal item As TItem) As Integer Implements System.Collections.Generic.IList(Of TItem).IndexOf If item Is Nothing Then Return -1 ' Add the index values if they're not already there If Me.position = -1 Then Me.IndexTree(0) End If ' Find the item - searching the tree for a matching Node. If item.CompareTo(Me.NodeData) < 0 Then If Me.LeftTree Is Nothing Then Return -1 End If Return Me.LeftTree.IndexOf(item) End If

L13-6

Lab: Building and Enumerating Custom Collection Classes

If item.CompareTo(Me.NodeData) > 0 Then If Me.RightTree Is Nothing Then Return -1 End If Return Me.RightTree.IndexOf(item) End If Return Me.position End Function

3.

To use the code snippet, type Mod13IndexOf, and then press Tab.

Locate the indexer or default property.

The Item property should return the TItem object at the index specified by the index parameter. 4. Replace the code in the Get procedure with code to perform the following actions. a. If the value of the index parameter is less than zero, or greater than the value of the Count property, throw an ArgumentOutOfRangeException exception with the following parameters: b. A string value, "index". The index parameter value. A string value, "Indexer out of range".

At the end of the Get procedure, call the GetItemAtIndex method. Pass the value of the index variable to the GetItemAtIndex method. Return the value of the NodeData property from the item that is retrieved by calling the GetItemAtIndex method.

Your code should resemble the following code.


Default Public Property Item(ByVal index As Integer) As TItem Implements System.Collections.Generic.IList(Of TItem).Item Get If index < 0 OrElse index >= Count Then Throw New ArgumentOutOfRangeException("index", index, "Indexer out of range") End If Return GetItemAtIndex(index).NodeData End Get Set End Set End Property

5.

Locate the Clear method. This method accepts no parameters, and does not return a value.

This method should clear the contents of the tree and return it to a default state. 6. Replace the code in the Clear method with code to perform the following actions. a. b. c. Set the LeftTree property to Nothing. Set the RightTree property to Nothing. Set the NodeData property to the default value for a TItem object, by assigning Nothing.

Your code should resemble the following code.


Public Sub Clear() Implements System.Collections.Generic.ICollection(Of TItem).Clear LeftTree = Nothing RightTree = Nothing

Lab: Building and Enumerating Custom Collection Classes

L13-7

NodeData = Nothing End Sub

7.

Locate the Contains method. This method accepts a TItem parameter, item, and returns a Boolean value.

This method should iterate through the tree and return a Boolean value that indicates whether a node that matches the value of the item parameter exists in the tree. 8. Replace the code in the Contains method with code to perform the following actions. You can type this code manually, or use the Mod13Contains code snippet. a. b. If the value of the NodeData property is the same as the value of the item parameter, return True. If the value of the NodeData property is greater than the value of the item parameter, and if the LeftTree property is not Nothing, return the result of a recursive call to the LeftTree.Contains method, passing the item parameter to the Contains method. If the value of the NodeData property is less than the value of the item parameter, and if the RightTree property is not Nothing, return the result of a recursive call to the RightTree.Contains method, passing the item parameter to the Contains method. At the end of the method, return False.

c.

d.

Your code should resemble the following code.


Public Function Contains(ByVal item As TItem) As Boolean Implements System.Collections.Generic.ICollection(Of TItem).Contains If NodeData.CompareTo(item) = 0 Then Return True End If If NodeData.CompareTo(item) > 0 Then If Not Me.LeftTree Is Nothing Then Return Me.LeftTree.Contains(item) End If Else If Not Me.RightTree Is Nothing Then Return Me.RightTree.Contains(item) End If End If Return False End Function

9.

To use the code snippet, type Mod13Contains, and then press Tab.

Locate the Count property.

This property is read-only, and should return an integer that represents the total number of items in the tree. 10. Replace the code in the Get procedure with code to invoke the GetCount method, by passing 0 to the method call. Return the value that the GetCount method calculates. Your code should resemble the following code.
Public ReadOnly Property Count As Integer Implements System.Collections.Generic.ICollection(Of TItem).Count Get Return Me.GetCount(0)

L13-8

Lab: Building and Enumerating Custom Collection Classes

End Get End Property

11. Locate the IsReadOnly property. This property should return a Boolean value that signifies whether the tree is read-only. 12. Replace the code in the Get procedure with a statement that returns the Boolean value False. Your code should resemble the following code.
Public ReadOnly Property IsReadOnly As Boolean Implements System.Collections.Generic.ICollection(Of TItem).IsReadOnly Get Return False End Get End Property

13. Locate the Remove1 method and rename it as RemoveItem. This method accepts a TItem parameter named item, and returns a Boolean value. This method should check whether a node with a value that matches the item parameter exists in the tree, and if so, remove the item from the tree. If an item is removed, the method should return True; otherwise, the method should return False.

Note: This version of the Remove method was named Remove1, because otherwise it would clash with the Remove method that implements the method of the same name for the IBinaryTree interface. 14. In the RemoveItem method, replace the existing code with statements that perform the following actions. a. b. If the tree contains a node that matches the value in the item parameter, call the local RemoveItem method, and then return True. At the end of the method, return False.

Your code should resemble the following code.


Public Function RemoveItem(ByVal item As TItem) As Boolean Implements System.Collections.Generic.ICollection(Of TItem).Remove If Me.Contains(item) Then Me.RemoveItem(item) Return True End If Return False End Function

15. Build the solution and correct any errors. On the Build menu, click Build Solution.

Task 5: Use the BinaryTreeTestHarness application to test the solution.


In the BinaryTreeTestHarness project, open the Module1.vb file and examine the Main method. The BinaryTreeTestHarness project contains code that you will use to test the completed BinaryTree class. You will continue to extend the BinaryTree class in the following exercises, so the BinaryTree

Lab: Building and Enumerating Custom Collection Classes

L13-9

class is not currently complete. For this reason, this exercise does not use some methods in the test harness. The Main method contains method calls to each of the test methods that you are about to examine. In Solution Explorer, in the BinaryTreeTestHarness project, double-click Module1.vb.

Examine the TestIntegerTree method.

The TestIntegerTree method tests the Remove and Contains methods, and the indexer functionality of the BinaryTree class. First, the method invokes the CreateATreeOfIntegers method to build a sample tree that contains 10 values. Then, the method invokes the WalkTree method, which prints each node value to the console in numerical order.

Note: The CreateATreeOfIntegers method creates a Tree object that contains the values 10, 5, 11, 5, 12, 15, 0, 14, 8, and 10 in the order that the method adds them. The method then invokes the Count method and prints the result to the console. The method casts the tree to an ICollection object, and then calls the Remove method to remove the value 11 from the tree. The method again prints the result of the Count method to the console to prove that an item has been removed.

Note: The BinaryTree method contains two Remove methods, and in this case, the test method should invoke the interface-defined ICollection.Remove method. To enable the test method to do this, it must cast the Tree object to an ICollection object. The method then tests the Contains method by invoking the Contains method with the value 11 (which has just been removed) and then 12 (which is known to exist in the list). Finally, the method tests the tree indexer by first retrieving the index of the value 5 in the tree and printing the index to the console, and then using the same index to retrieve the value 5 from that position in the tree. Examine the TestDeleteRootNodeInteger method.

The TestDeleteRootNodeInteger method tests the functionality of the Remove method when it attempts to remove the tree root node. When the root node value is removed from the tree, the next available node should be copied into its place to enable the tree to continue to function. In this test, the root node has the value 10. There is a second node with the value 10, so the Remove method must be invoked twice to remove both values. The method first invokes the CreateATreeOfIntegers method to build a sample tree, and then prints the tree to the console by invoking the WalkTree method. The method then casts the Tree object to an ICollection object, and then invokes the Remove method twice to remove both values of 10. Finally, the method again invokes the WalkTree method to verify that the tree still functions correctly. Examine the TestStringTree method.

This method uses similar logic to the TestIntegerTree method to test the Count, Remove, Contains, and indexer method functionality. This method uses a BinaryTree object that contains the string values "k203", "h624", "p936", "h624", "a279", "z837", "e762", "r483", "d776", and "k203". In this test, the Remove method is tested by using the "p936" string value, and the indexer is tested by using the "h624" string value. Examine the TestDeleteRootNodeString method.

L13-10

Lab: Building and Enumerating Custom Collection Classes

This method uses similar logic to the TestDeleteRootNodeInteger method to test the Remove method functionality, using the same string-based tree as the TestStringTree method. In this test, the "k203" string value is removed twice to test root node removal. Examine the TestTestResultTree method.

This method uses similar logic to the TestIntegerTree and TestStringTree methods to test the Count, Remove, Contains, and indexer method functionality, but it uses a BinaryTree object based on the TestResult type.

Note: The TestResult class implements the IComparable interface, and uses the Deflection property to compare instances of the TestResult object. Therefore, items in this tree are indexed by their Deflection property value. In this case, the Remove method is tested with the TestResult object that has a Deflection value of 226. The indexer is tested with the TestResult object that has a Deflection value of 114. Examine the TestDeleteRootNodeTestResult method.

This method uses similar logic to the TestDeleteRootNodeInteger and TestDeleteRootNodeString methods to test the Remove method functionality, using the same TestResult-based tree as the TestTestResultTree method. In this test, the TestResult object that has a Deflection value of 190 is removed twice to test root node removal. Run the BinaryTreeTestHarness application. Press Ctrl+F5.

Verify that the output in the console window resembles the following code example.
TestIntegerTree() WalkTree() -12 -8 0 5 5 10 10 11 14 15 Count: 10 Remove(11) Count: 9 Contains(11): False Contains(-12): True IndexOf(5): 3 tree(3): 5

Note that:

Lab: Building and Enumerating Custom Collection Classes

L13-11

a. b. c. d. e.

The console shows the output of the TestIntegerTree method. The tree is displayed in numerical order by the WalkTree method. Initially, the list contains 10 items, and then after the Remove method is called, the tree contains nine items. The Remove method removes the value 11, so the result of the Contains method is False. Note also that the Contains method verifies the presence of the value 12. The IndexOf method reports that the value 5 is in position 3 in the list. This is confirmed by retrieving the value in position 3, which is shown to be 5.

Press Enter, and then verify that the output in the console window resembles the following code example.
TestDeleteRootNodeInteger() Before -12 -8 0 5 5 10 10 11 14 15 Remove 5 twice After -12 -8 0 10 10 11 14 15

Note that the tree shows two instances of the value 5 in the first list. Then, after those values are removed, the list no longer contains them. Also note that after removing the root node value, the tree retains the remaining values and continues to function as expected. Press Enter, and then verify that the output in the console window matches the following output.
TestStringTree() WalkTree() a279 d776 e762 h624 h624 k203 k203 p936 r483 z837 Count: 10

L13-12

Lab: Building and Enumerating Custom Collection Classes

Remove("p936") Count: 9 Contains("p936"): False Contains("a279"): True IndexOf("h624"): 3 stringTree(3): h624

This is the same test as the one you performed in step 9, but it is performed by using string data. Items in the list are displayed in alphabetical order. Press Enter, and then verify that the output in the console window matches the following output.
TestDeleteRootNodeString() Before a279 d776 e762 h624 h624 k203 k203 p936 r483 z837 Remove k203 twice After a279 d776 e762 h624 h624 p936 r483 z837

Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestTestResultTree() WalkTree() Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Count: 10 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

Lab: Building and Enumerating Custom Collection Classes

L13-13

Remove(def266) Count: 9 Contains(def266): False Contains(def0): True IndexOf(def114): 3 Tree(3): Deflection: 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010

This test is the same as the one you performed in steps 9 and 11, but this test is based on TestResult objects. Items are displayed in numerical order based on the value of the Deflection property. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestDeleteRootNodeTestResults() Before Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

Remove def190 twice After Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

Press Enter to return to Visual Studio.

Exercise 2: Implementing an Enumerator by Writing Code


Task 1: Open the CustomCollections solution.
Open the CustomCollections solution in the D:\Labfiles\Lab13\Ex2\Starter folder.

Note: The CustomCollections solution in the Ex2 folder is functionally the same as the code that you completed in Exercise 1. However, it includes an updated Task List and an updated test harness to enable you to complete this exercise. a. In Visual Studio, on the File menu, click Open Project.

L13-14

Lab: Building and Enumerating Custom Collection Classes

b.

In the Open Project dialog box, browse to the D:\Labfiles\Lab13\Ex2 \Starter folder, click CustomCollections.sln, and then click Open.

Task 2: Create the TreeEnumerator class.


In the BinaryTree project, add a new class named TreeEnumerator. This class should implement the IEnumerator interface, and should take a type parameter, TItem, where the TItem type implements the IComparable interface. a. b. c. In Solution Explorer, right-click the BinaryTree project, point to Add, and then click Class. In the Add New Item - BinaryTree dialog box, in the Name box, type TreeEnumerator, and then click Add. In the TreeEnumerator.vb file, modify the TreeEnumerator class definition to make the class generic, based on a type parameter called TItem. Specify that the class implements the IEnumerator generic interface, and that the TItem type parameter implements the IComparable generic interface.

Your code should resemble the following code.


Public Class TreeEnumerator(Of TItem As IComparable) Implements IEnumerator(Of TItem) End Class

Task 3: Add class-level variables and a constructor.


1. In the TreeEnumerator class, add the following members. A Tree(Of TItem) object named currentData, initialized to Nothing. This member will store the initial Tree object data that is passed to the class when it is constructed, and will be used to populate the internal queue with data. The data is also stored to enable the internal queue to reset. A TItem object named currentItem, initialized to a default TItem object, by assigning Nothing. This member will store the last item that is removed from the queue. A private Queue(Of TItem) object named enumData, initialized to Nothing. This member holds an internal queue of items that the enumerator will iterate over. You will populate this queue with the items in the Tree object.

Your code should resemble the following code.


Public Class TreeEnumerator(Of TItem As IComparable) Implements IEnumerator(Of TItem) Private currentData As Tree(Of TItem) = Nothing Private currentItem As TItem = Nothing Private enumData As Queue(Of TItem) = Nothing End Class

2.

Add a constructor. The constructor should accept a Tree(Of TItem) parameter named data, and should initialize the currentData member with the value of this parameter.

Your code should resemble the following code.

Lab: Building and Enumerating Custom Collection Classes

L13-15

Public Class TreeEnumerator(Of TItem As IComparable) Implements IEnumerator(Of TItem) ... Public Sub New(ByVal data As Tree(Of TItem)) Me.currentData = data End Sub End Class

Task 4: Add a method to populate the queue.


Below the constructor, add a new private method named Populate. The method should accept a Queue(Of TItem) parameter named enumQueue, and a Tree(Of TItem) parameter named populatedTree. It should not return a value. Add code to the method to perform the following actions. a. If the LeftTree property of the populatedTree parameter is not Nothing, recursively call the Populate method, passing the enumQueue parameter and the populatedTree.LeftTree property as parameters to the method. Add the populatedTree.NodeData property value of the populatedTree parameter to the enumQueue queue. If the RightTree property of the populatedTree parameter is not Nothing, recursively call the Populate method, passing the enumQueue parameter and the populatedTree.RightTree property as parameters to the method.

b. c.

This code walks the tree and fills the queue with each item that is found, in order. Your code should resemble the following code.
Private Sub Populate(ByVal enumQueue As Queue(Of TItem), ByVal populatedTree As Tree(Of TItem)) If Not populatedTree.LeftTree Is Nothing Then Populate(enumQueue, populatedTree.LeftTree) End If enumQueue.Enqueue(populatedTree.NodeData) If Not populatedTree.RightTree Is Nothing Then Populate(enumQueue, populatedTree.RightTree) End If End Sub

Task 5: Implement the IEnumerator(Of T) and IEnumerator methods.


1. In the class definition, place the cursor at the end of the line of code, Implements IEnumerator(Of TItem), and then press Enter.

Visual Studio will generate stubs for the methods and properties that the IEnumerator(Of), IEnumerator, and IDisposable interfaces expose. 2. Locate the Current property.

This property should return the last TItem object that was removed from the queue. 3. In the Get procedure of the Current property, replace the existing code with code to perform the following actions. a. b. If the enumData member is Nothing, throw a new InvalidOperationException exception with the message "Use MoveNext before calling Current". Return the value of the currentItem member.

L13-16

Lab: Building and Enumerating Custom Collection Classes

Your code should resemble the following code.


Public ReadOnly Property Current As TItem Implements System.Collections.Generic.IEnumerator(Of TItem).Current Get If Me.enumData Is Nothing Then Throw New InvalidOperationException("Use MoveNext before calling Current") End If Return Me.currentItem End Get End Property

4.

Locate the Current1 property.

This property should return the value of the Current property. 5. In the Get procedure of the Current1 property, add the following code, Return Me.Current.

Your code should resemble the following code.


Public ReadOnly Property Current1 As Object Implements System.Collections.IEnumerator.Current Get Return Me.Current End Get End Property

6.

Locate the MoveNext method. The method accepts no parameters and returns a Boolean value.

The MoveNext method should ensure that the internal queue is initialized, retrieve the next item from the internal queue, and then store it in the currentItem property. If the operation succeeds, the method returns True; otherwise, it returns False. 7. In the MoveNext method, replace the existing code with code to perform the following actions. a. If the enumData object is Nothing, create a new queue object, and then invoke the Populate method, passing the new Queue object and the currentData member as parameters to the method call. If the enumData object contains any values, retrieve the first item in the queue, store it in the currentItem member, and then return the Boolean value True. At the end of the method, return the Boolean value False.

b. c.

Your code should resemble the following code.


Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext If Me.enumData Is Nothing Then Me.enumData = new Queue(Of TItem)() Populate(Me.enumData, Me.currentData) End If If Me.enumData.Count > 0 Then Me.currentItem = Me.enumData.Dequeue() Return True End If Return False End Function

8.

Locate the Reset method. This method accepts no parameters, and does not return a value.

Lab: Building and Enumerating Custom Collection Classes

L13-17

This method should reset the enumerator to its initial state. You do this by re-populating the internal queue with the data from the Tree object. 9. In the Reset method, replace the existing code with code that invokes the Populate method, passing the enumData and currentData members as parameters to the method.

Your code should resemble the following code.


Public Sub Reset() Implements System.Collections.IEnumerator.Reset Populate(Me.enumData, Me.currentData) End Sub

10. Build the solution and correct any errors. On the Build menu, click Build Solution.

Task 6: Implement the IDisposable interface.


1. In the TreeEnumerator class, locate the Dispose method. This method accepts no parameters and does not return a value.

The method should dispose of the class, relinquishing any resources that may not be reclaimed if they are not disposed of explicitly, such as file streams and database connections.

Note: The Queue object does not implement the IDisposable interface, so you will use the Dispose method of the TreeEnumerator class to clear the queue of any data. 2. In the Dispose method, replace the existing code with code that clears the enumQueue queue object.

Hint: Use the Clear method of the Queue class to empty a Queue object. Your code should resemble the following code.
Protected Overridable Sub Dispose(ByVal disposing As Boolean) If Not Me.disposedValue Then If disposing Then Me.enumData.Clear() End If ... End Sub

3.

Build the solution and correct any errors. On the Build menu, click Build Solution.

Task 7: Modify the Tree class to return a TreeEnumerator object.


1. In the Task List, locate the TODO: - Update the Tree class to return the TreeEnumerator class task, and then double-click this task.

This task is located in the Tree class. a. b. c. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments. Double-click the TODO: - Update the Tree class to return the TreeEnumerator class task.

L13-18

Lab: Building and Enumerating Custom Collection Classes

2.

Remove the comment. In the GetEnumerator method, add code that creates and initializes a new TreeEnumerator object. Specify the TItem type as the type parameter, and pass the current object as the parameter to the TreeEnumerator constructor. Return the TreeEnumerator object that is created.

Your code should resemble the following code.


Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of TItem) Implements System.Collections.Generic.IEnumerable(Of TItem).GetEnumerator Return New TreeEnumerator(Of TItem)(Me) End Function

3.

Build the solution and correct any errors. On the Build menu, click Build Solution.

Task 8: Use the BinaryTreeTestHarness application to test the solution.


1. In the BinaryTreeTestHarness project, open the Module1.vb file. This version of the BinaryTreeTestHarness project contains the same code and performs the same tests as in Exercise 1. However, it has been updated to test the enumerator functionality that you just added. 2. In Solution Explorer, in the BinaryTreeTestHarness project, double-click Module1.vb.

Examine the TestIteratorsIntegers method.

This method tests the iterator functionality that you just implemented, by using the same integer tree as in Exercise 1. The method builds the tree by invoking the CreateATreeOfIntegers method, and then uses a For Each statement to iterate through the list and print each value to the console. The method then attempts to iterate through the tree in reverse order, and print each item to the console.

Note: You will add the functionality to enable reverse iteration of the tree in the next exercise. It is expected that attempting to reverse the tree will throw a NotImplementedException exception. The TestIteratorsIntegers method will catch this exception when it occurs, and print a message to the console. 3. Examine the TestIteratorsStrings method.

This method uses similar logic to the TestIteratorsIntegers method to test the iterator functionality of the BinaryTree object, but it uses the same string-based tree as the one you used in Exercise 1. The method uses the CreateATreeOfStrings method to build the tree, iterates through the tree, and then prints all items to the console. This method also attempts to display the data in the tree in reverse order, and will encounter a NotImplementedException exception (you will implement this feature in the next exercise). 4. Examine the TestIteratorsTestResults method.

This method uses similar logic to the TestIteratorsIntegers and TestIteratorsStrings methods to test the iterator functionality of the BinaryTree object. It uses a TestResult-based tree by invoking the CreateATreeOfTestResults method as in Exercise 1. 5. Run the BinaryTreeTestHarness application. Press Ctrl+F5. 6. Verify that the output in the console window matches the following code example.

TestIntegerTree()

Lab: Building and Enumerating Custom Collection Classes

L13-19

WalkTree() -12 -8 0 5 5 10 10 11 14 15 Count: 10 Remove(11) Count: 9 Contains(11): False Contains(-12): True IndexOf(5): 3 tree(3): 5

This output matches the TestIntegerTree method output from Exercise 1, and confirms that you have not compromised existing functionality by adding the iterator functionality. 7. Press Enter, and then verify that the output in the console window matches the following output.

TestDeleteRootNodeInteger() Before -12 -8 0 5 5 10 10 11 14 15 Remove 5 twice After -12 -8 0 10 10 11 14 15

This output matches the TestDeleteRootNodeInteger method output from Exercise 1, and again confirms that existing functionality works as expected. 8. Press Enter, and then verify that the output in the console window matches the following output.

L13-20

Lab: Building and Enumerating Custom Collection Classes

TestIteratorsIntegers() In ascending order -12 -8 0 5 5 10 10 11 14 15 In descending order Not Implemented. You will implement this functionality in Exercise 3

Note that the items in the list are displayed in numerical order, and note that the Reverse method displays a message that indicates that the Reverse functionality is not yet implemented. 9. Press Enter, and then verify that the output in the console window matches the following output.

TestStringTree() WalkTree() a279 d776 e762 h624 h624 k203 k203 p936 r483 z837 Count: 10 Remove("p936") Count: 9 Contains("p936"): False Contains("a279"): True IndexOf("h624"): 3 Tree(3): h624

This output matches the TestStringTree method output from Exercise 1. 10. Press Enter, and then verify that the output in the console window matches the following output.
TestDeleteRootNodeString() Before a279 d776 e762 h624 h624 k203

Lab: Building and Enumerating Custom Collection Classes

L13-21

k203 p936 r483 z837 Remove k203 twice After a279 d776 e762 h624 h624 p936 r483 z837

This output matches the TestDeleteRootNodeString method output from Exercise 1. 11. Press Enter, and then verify that the output in the console window matches the following output.
TestIteratorsStrings() In ascending order a279 d776 e762 h624 h624 k203 k203 p936 r483 z837 In descending order Not Implemented. You will implement this functionality in Exercise 3

Note that this represents the same test as you performed in step 8. It uses string data to verify the iterator functionality, and all items are displayed in alphabetical order. 12. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestTestResultTree() WalkTree() Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Count: 10 Remove(def266) Count: 9 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

L13-22

Lab: Building and Enumerating Custom Collection Classes

Contains(def266): False Contains(def0): True IndexOf(def114): 3 Tree(3): Deflection: 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010

This output matches the TestTestResultTree method output from Exercise 1. 13. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestDeleteRootNodeTestResults() Before Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

Remove def190 twice After Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

This output matches the TestDeleteRootNodeTestResults method output from Exercise 1. 14. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestIteratorsTestResults() In ascending order Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 Deflection: 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 Deflection: 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 Deflection: 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 Deflection: 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 Deflection: 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 Deflection: 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 Deflection: 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 Deflection: 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 Deflection: 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010 In descending order Not Implemented. You will implement this functionality in Exercise 3

Lab: Building and Enumerating Custom Collection Classes

L13-23

Note that this represents the same test as you performed in steps 8 and 11. It uses TestResult object data to verify the iterator functionality, and all items are displayed in numerical order based on the value of the Deflection property. 15. Press Enter to return to Visual Studio.

Exercise 3: Implementing an Enumerator by Using an Iterator


Task 1: Open the CustomCollections solution.
Open the CustomCollections solution in the D:\Labfiles\Lab13\Ex3\Starter folder.

Note: The CustomCollections solution in the Ex3 folder is functionally the same as the code that you completed in Exercise 2. However, it includes an updated Task List and an updated test harness to enable you to complete this exercise. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab13\Ex3 \Starter folder, click CustomCollections.sln, and then click Open.

Task 2: Add an enumerator to return an enumerator that iterates through data in reverse
order.
1. Review the Task List. a. b. 2. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

In the Task List, locate the TODO: - Add a method to return the list in reverse order task, and then double-click this task.

This task is located at the end of the Tree class. 3. Remove the task comment, and then add a new public method named Reverse. The method should accept no parameters, and return an IEnumerable collection based on the TItem type parameter.

Your code should resemble the following code.


Public Function Reverse() As IEnumerable(Of TItem) End Function

4.

Add code to the method to perform the following actions. You can type this code manually, or use the Mod13Reverse code snippet. Create and initialize an instance named tempQueue of type Queue(Of TItem). If the RightTree property is not Nothing, iterate through the items that are returned by calling the Reverse method of the RightTree property, and then add each item that is found to tempQueue. Return the value in the NodeData property to tempQueue. If the LeftTree property is not Nothing, iterate through the items that are returned by calling the Reverse method of the LeftTree property, and then add each item that is found to tempQueue. Return the tempQueue object.

L13-24

Lab: Building and Enumerating Custom Collection Classes

Note: You can use the Enqueue method of the Queue class to add an item. Your code should resemble the following code.
Public Function Reverse() As IEnumerable(Of TItem) Dim tempQueue As New Queue(Of TItem) If Not Me.RightTree Is Nothing Then For Each itm In Me.RightTree.Reverse tempQueue.Enqueue(itm) Next End If tempQueue.Enqueue(Me.NodeData) If Not Me.LeftTree Is Nothing Then For Each itm In Me.LeftTree.Reverse tempQueue.Enqueue(itm) Next End If Return tempQueue End Function

5.

To use the code snippet, type Mod13Reverse, and then press Tab.

Build the solution and correct any errors. On the Build menu, click Build Solution.

Task 3: Use the BinaryTreeTestHarness application to test the solution.


1. In the BinaryTreeTestHarness project, open the Module1.vb file. This version of the BinaryTreeTestHarness project contains the same code and performs the same tests as in Exercise 2. Now that you have implemented the Reverse method in the BinaryTree object, the test application should not encounter the NotImplementedException exception in the TestIteratorsIntegers, TestIteratorsStrings, and TestIteratorsTestResults methods. 2. Run the BinaryTreeTestHarness application. 3. Press Ctrl+F5.

Verify that the output in the console window matches the following code example.

TestIntegerTree() WalkTree() -12 -8 0 5 5 10 10 11 14 15 Count: 10 Remove(11)

Lab: Building and Enumerating Custom Collection Classes

L13-25

Count: 9 Contains(11): False Contains(-12): True IndexOf(5): 3 tree(3): 5

This output matches the TestIntegerTree method output from Exercises 1 and 2, and confirms that you have not compromised existing functionality by adding the reverse iterator functionality. 4. Press Enter, and then verify that the output in the console window matches the following output.

TestDeleteRootNodeInteger() Before -12 -8 0 5 5 10 10 11 14 15 Remove 5 twice After -12 -8 0 10 10 11 14 15

This output matches the TestDeleteRootNodeInteger method output from Exercises 1 and 2, and again confirms that the existing functionality works as expected. 5. Press Enter, and then verify that the output in the console window matches the following output.

TestIteratorsIntegers() In ascending order -12 -8 0 5 5 10 10 11 14 15 In descending order

L13-26

Lab: Building and Enumerating Custom Collection Classes

15 14 11 10 10 5 5 0 -8 -12

This output is similar to the TestIteratorsIntegers method in Exercise 2, but the Reverse method is now implemented, so the tree is also displayed in descending numerical order. 6. Press Enter, and then verify that the output in the console window matches the following output.

TestStringTree() WalkTree() a279 d776 e762 h624 h624 k203 k203 p936 r483 z837 Count: 10 Remove("p936") Count: 9 Contains("p936"): False Contains("a279"): True IndexOf("h624"): 3 Tree(3): h624

This output matches the TestStringTree method output from Exercises 1 and 2. 7. Press Enter, and then verify that the output in the console window matches the following output.

TestDeleteRootNodeString() Before a279 d776 e762 h624 h624 k203 k203 p936 r483 z837 Remove k203 twice

Lab: Building and Enumerating Custom Collection Classes

L13-27

After a279 d776 e762 h624 h624 p936 r483 z837

This output matches the TestDeleteRootNodeString method output from Exercises 1 and 2. 8. Press Enter, and then verify that the output in the console window matches the following output.

TestIteratorsStrings() In ascending order a279 d776 e762 h624 h624 k203 k203 p936 r483 z837 In descending order z837 r483 p936 k203 k203 h624 h624 e762 d776 a279

This test uses string data to verify the iterator functionality, and all items are displayed in alphabetical order, and then reverse alphabetical order. 9. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.

TestTestResultTree() WalkTree() Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Count: 10 Remove(def266) 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

L13-28

Lab: Building and Enumerating Custom Collection Classes

Count: 9 Contains(def266): False Contains(def0): True IndexOf(def114): 3 Tree(3): Deflection: 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010

This output matches the TestTestResultTree method output from Exercises 1 and 2. 10. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestDeleteRootNodeTestResults() Before Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

Remove def190 twice After Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

This output matches the TestDeleteRootNodeTestResults method output from Exercises 1 and 2. 11. Press Enter, and then verify that the output in the console window matches the following output. The date will be different, as it will match todays date.
TestIteratorsTestResults() In ascending order Deflection: 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010 Deflection: 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 Deflection: 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 Deflection: 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 Deflection: 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 Deflection: 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 Deflection: 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 Deflection: 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 Deflection: 304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 Deflection: 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010 In descending order Deflection: 342, AppliedStress: 100, Temperature: 200, Date: 3/18/2010

Lab: Building and Enumerating Custom Collection Classes

L13-29

Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection: Deflection:

304, AppliedStress: 90, Temperature: 200, Date: 3/18/2010 266, AppliedStress: 80, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 70, Temperature: 200, Date: 3/18/2010 190, AppliedStress: 60, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 50, Temperature: 200, Date: 3/18/2010 114, AppliedStress: 40, Temperature: 200, Date: 3/18/2010 76, AppliedStress: 30, Temperature: 200, Date: 3/18/2010 38, AppliedStress: 20, Temperature: 200, Date: 3/18/2010 0, AppliedStress: 10, Temperature: 200, Date: 3/18/2010

This test uses TestResult object data to verify iterator functionality. Therefore, all items are displayed in numerical order, based on the value of the Deflection property. Then, the list is reversed to display data in descending numerical order, based on the value of the Deflection property. 12. Press Enter to return to Visual Studio. 13. Close Visual Studio. In Visual Studio, on the File menu, click Exit.

L13-30

Lab: Building and Enumerating Custom Collection Classes

Lab: Using LINQ to Query Data

L14-1

Module 14: Using LINQ to Query Data

Lab: Using LINQ to Query Data


Exercise 1: Using the LINQ Query Operators
Task 1: Open the starter solution.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Import the code snippets from the D:\Labfiles\Lab14\Snippets folder. a. b. c. d. e. In Visual Studio, on the Tools menu, click Code Snippets Manager. In the Code Snippets Manager dialog box, in the Language list, click Visual Basic. Click Add. In the Code Snippets Directory dialog box, move to the D:\Labfiles \Lab14\Snippets folder, and then click Select Folder. In the Code Snippets Manager dialog box, click OK.

3.

Open the StressDataAnalyzer solution in the D:\Labfiles\Lab14\Ex1\Starter folder. a. b. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, move to the D:\Labfiles\Lab14\Ex1 \Starter folder, click StressDataAnalyzer.sln, and then click Open.

4.

Examine the user interface (UI) for the StressDataAnalyzer application. Note the following features of the application. The stress test data is generated by a stress test device. The data is stored in a binary data file, and this application reads the data from this file when the application starts to run. The application holds the data in memory by using a Tree object. The UI contains two main areas. The upper area enables the user to specify criteria to match stress data. The lower area displays the data. The stress test data criteria are. i. ii. The date that the test was performed. The temperature at which the test was performed.

iii. The stress that was applied during the test. iv. The deflection that resulted from applying the stress. Each criterion is specified as a range by using the slider controls. After selecting the criteria to match, the user clicks Display to generate a Language-Integrated Query (LINQ) query that fetches the matching data from the Tree object in memory and shows the results. a. b. In Solution Explorer, expand the StressDataAnalyzer project. Double-click the DataAnalyzer.xaml file.

L14-2

Lab: Using LINQ to Query Data

Task 2: Declare variables to specify the stress data file name and the Tree object.
1. Review the Task List. a. b. 2. 3. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

In the Task List, locate the TODO: - Declare filename and tree variables task, and then doubleclick this task. This task is located in the DataAnalyzer.xaml.vb class. Delete the TODO: - Declare filename and tree variables comment, and then add code to declare the following variables. a. b. A private constant String object named stressDataFilename. Initialize the object with the string "D:\Labfiles\Lab14\StressData.dat". This is the name of the data file that holds the stress data. A private Tree object named stressData that is based on the TestResult type. This Tree object will hold the data that is read from the stress data file. Initialize this object to null.

The TestResult type is a structure that contains the following four fields, corresponding to the data for each stress test record. TestDate. This is a DateTime field that contains the date on which the stress test was performed. Temperature. This is a Short field that contains the temperature, in Kelvin, at which the test was performed. AppliedStress. This is another Short field that specifies the stress, in kiloNewtons (kN), that was applied during the test. Deflection. This is another Short field that specifies the deflection of the girder, in millimeters (mm), when the stress was applied.

The TestResult type implements the IComparable interface. The comparison of test data is based on the value of the Deflection field. Your code should resemble the following code.
Public Class DataAnalyzer ' Declare a string variable to hold the name of the file ' that contains the stress test data. Private Const stressDataFilename As String = "D:\Labfiles\Lab14\StressData.dat" ' Declare a Tree variable to hold the loaded data. Private stressData As Tree(Of TestResult) = Nothing Private Sub Window_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded ... End Class

Task 3: Add a method to read the test data.


1. 2. In the Task List, locate the TODO: - Add a method to read the contents of the StressData file task, and then double-click this task. Delete the TODO: - Add a method to read the contents of the StressData file comment, and then add the method in the following code example, which is named ReadTestData. This method reads the stress data from the file and populates the Tree object. It is not necessary for you to fully

Lab: Using LINQ to Query Data

L14-3

understand how this method works, so you can either type this code manually, or you can use the Mod14ReadTestData code snippet.
Private Sub ReadTestData() Try ' Open a stream over the file that holds the test data. using readStream As FileStream = File.Open(stressDataFilename, FileMode.Open) ' The data is serialized as TestResult instances. ' Use a BinaryFormatter object to read the stream and ' deserialize the data. Dim formatter As New BinaryFormatter() Dim initialNode As TestResult = CType(formatter.Deserialize(readStream), TestResult) ' Create the binary tree and use the first item retrieved ' as the root node. (Note: The tree will likely be ' unbalanced, because it is probable that most nodes will ' have a value that is greater than or equal to the value in ' this root node - this is because of the way in which the ' test results are generated and the fact that the TestResult ' class uses the deflection as the discriminator when it ' compares instances.) stressData = New Tree(Of TestResult)(initialNode) ' Read the TestResult instances from the rest of the file ' and add them into the binary tree. While readStream.Position < readStream.Length Dim data As TestResult = CType(formatter.Deserialize(readStream), TestResult) stressData.Insert(data) End While End Using Catch ex As Exception MessageBox.Show(ex.Message) End Try End Sub

To use the code snippet, type Mod14ReadTestData, and then press the Tab key twice.

Task 4: Read the test data by using a BackgroundWorker object.


1. In the Window_Loaded method, add code to perform the following tasks. a. b. Create a BackgroundWorker object named workerThread. Configure the workerThread object; the object should not report progress or support cancellation.

Your code should resemble the following code.


Private Sub Window_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded ' Read the test data and populate the binary tree. ' Use a BackgroundWorker object to avoid tying up the UI. Dim workerThread As New BackgroundWorker() workerThread.WorkerReportsProgress = False workerThread.WorkerSupportsCancellation = False End Sub

2.

In the Window_Loaded method, add an event handler for the workerThread.DoWork event, by using a lambda expression. When the event is raised, the event handler should invoke the ReadTestData method.

L14-4

Lab: Using LINQ to Query Data

Your code should resemble the following code.


Private Sub Window_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded ... AddHandler workerThread.DoWork, Sub(o, args) Me.ReadTestData() End Sub End Sub

3.

Add an event handler for the workerThread.RunWorkerComplete event, by using a lambda expression. When the event is raised, the event handler should perform the following tasks. a. b. Enable the DisplayResultsButton control. Display the message 'Ready' in the StatusMessageItem StatusBarItem in the status bar at the lowermost part of the Windows Presentation Foundation (WPF) window.

Hint: Set the Content property of a status bar item to display a message in that item. Your code should resemble the following code.
Private Sub Window_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded ... AddHandler workerThread.RunWorkerCompleted, Sub(o, args) Me.DisplayResultsButton.IsEnabled = True Me.StatusMessageItem.Content = "Ready" End Sub End Sub

4.

At the end of the Window_Loaded method, add code to perform the following tasks. a. b. Start the workerThread BackgroundWorker object running asynchronously. Display the message "Reading Test Data" in the StatusMessageItem item in the status bar at the lowermost part of the WPF window.

Your code should resemble the following code.


Private Sub Window_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded ... workerThread.RunWorkerAsync() Me.StatusMessageItem.Content = "Reading test data ..." End Sub

Task 5: Define the LINQ query.


1. 2. In the Task List, locate the TODO: - Define the LINQ query task, and then double-click this task. This task is located in the CreateQuery method. Replace the existing code in the method with code that defines an IEnumerable(Of TestResult) object called query. Initialize the query variable with a LINQ query that retrieves all the TestResult objects in the stressData tree that meet the following criteria. The query should order returned values by the TestDate property. The query should evaluate each object by using the following

Lab: Using LINQ to Query Data

L14-5

criteria. You can either type this code manually, or you can use the Mod14GetTestResultObjects code snippet. a. b. c. d. e. f. g. h. The value of the TestDate property is greater than or equal to the dateStart parameter value. The value of the TestDate property is less than or equal to the dateEnd parameter value. The value of the Temperature property is greater than or equal to the temperatureStart parameter value. The value of the Temperature property is less than or equal to the temperatureEnd parameter value. The value of the AppliedStress property is greater than or equal to the appliedStressStart parameter value. The value of the AppliedStress property is less than or equal to the appliedStressEnd parameter value. The value of the Deflection property is greater than or equal to the deflectionStart parameter value. The value of the Deflection property is less than or equal to the deflectionEnd parameter value.

Your code should resemble the following code.


Private Function CreateQuery(ByVal dateStart As DateTime, ByVal dateEnd As DateTime, ByVal temperatureStart As Short, ByVal temperatureEnd As Short, ByVal appliedStressStart As Short, ByVal appliedStressEnd As Short, ByVal deflectionStart As Short, ByVal deflectionEnd As Short) As IEnumerable(Of TestResult) Dim query As IEnumerable(Of TestResult) = From result In stressData Where result.TestDate >= dateStart AndAlso result.TestDate <= dateEnd AndAlso result.Temperature >= temperatureStart AndAlso result.Temperature <= temperatureEnd AndAlso result.AppliedStress >= appliedStressStart AndAlso result.AppliedStress <= appliedStressEnd AndAlso result.Deflection >= deflectionStart AndAlso result.Deflection <= deflectionEnd Order By result.TestDate Select result End Function

3.

To use the code snippet, type Mod14GetTestResultObjects, and then press the Tab key twice.

At the end of the method, return the query object.

Your code should resemble the following code.


Private Function CreateQuery(ByVal dateStart As DateTime, ByVal dateEnd As DateTime, ByVal temperatureStart As Short, ByVal temperatureEnd As Short, ByVal appliedStressStart As Short, ByVal appliedStressEnd As Short, ByVal deflectionStart As Short, ByVal deflectionEnd As Short) As IEnumerable(Of TestResult) ... Select result Return query End Function

4.

Build the solution and correct any errors.

L14-6

Lab: Using LINQ to Query Data

On the Build menu, click Build Solution. Correct any errors.

Task 6: Run the query.


1. In the Task List, locate the TODO: - Execute the LINQ query task, and then double-click this task. This task is located in the FormatResults method. This method takes an enumerable collection of TestResult objects as a parameter and generates a string that contains a formatted list of TestResult objects. The parameter is the item that the CreateQuery method returns. Iterating through this list runs the LINQ query. Delete the TODO: - Execute the LINQ query comment, and then add code to the FormatResults method to perform the following task. For each item that the query returns, format and append the details of each item to the builder StringBuilder object. Each item should be formatted to display the following properties in a double-tab delimited format. i. ii. iii. iv. TestDate Temperature AppliedStress Deflection

2.

Your code should resemble the following code.


Private Function FormatResults(ByVal query As IEnumerable(Of TestResult)) As String ... builder.Append("Test Date" & vbTab & vbTab & "Temperature" & vbTab & "Applied Stress" & vbTab & "Deflection" & vbNewLine) ' Iterate through the results and format each item found. For Each itm in query builder.Append(String.Format("{0:d}" & vbTab & vbTab & "{1}" & vbTab & vbTab & "{2}" & vbTab & vbTab & "{3}" & vbNewLine, itm.TestDate, itm.Temperature, itm.AppliedStress, itm.Deflection)) Next ' Return the string that is constructed by using the ' StringBuilder object. Return builder.ToString() End Function

3.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 7: Run the query by using a BackgroundWorker object.


1. In the Task List, locate the TODO: - Add a BackgroundWorker DoWork event handler to invoke the query operation task, and then double-click this task. This task is located in the DisplayResultsButton_Click method. This method calls the CreateQuery method to generate the LINQ query that matches the criteria that the user specifies, and it then runs the query to generate and format the results by using a BackgroundWorker object called workerThread. Delete the TODO: - Add a BackgroundWorker DoWork event handler to invoke the query operation comment, and then define an event handler for the workerThread.DoWork event, by using a lambda expression. Add code to the event handler to invoke the FormatResults method, passing the query object as the parameter to the method. Store the value that the method returns in the Result parameter of the DoWork event handler.

2.

Lab: Using LINQ to Query Data

L14-7

Your code should resemble the following code.


Private Sub DisplayResultsButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Try ... workerThread.WorkerSupportsCancellation = False ' Return the formatted string as the result of the background ' operation. AddHandler workerThread.DoWork, Sub(o, args) args.Result = FormatResults(query) End Sub ... End Try ... End Sub

3.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 8: Display the results.


1. Below the event handler for the DoWork event, add an event handler for the workerThread.RunWorkerComplete event, by using a lambda expression. Add code to the event handler to perform the following tasks. a. b. c. Update the ResultsTextBox.Text property with the value of the Result parameter of the RunWorkerComplete event handler. Enable the DisplayResultsButton button. Update the StatusMessageItem status bar item to "Ready".

Your code should resemble the following code.


Private Sub DisplayResultsButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Try ... AddHandler workerThread.DoWork, Sub(o, args) args.Result = FormatResults(query) End Sub ' When the BackgroundWorker object has completed reading ' the test data, display the results, set the status bar ' to "Ready", and enable the DisplayResultsButton button. AddHandler workerThread.RunWorkerCompleted, Sub(o, args) Me.ResultsTextBox.Text = CType(args.Result, String) Me.DisplayResultsButton.IsEnabled = True Me.StatusMessageItem.Content = "Ready" End Sub ... End Try ... End Sub

2.

Build the solution and correct any errors. On the Build menu, click Build Solution. Correct any errors.

L14-8

Lab: Using LINQ to Query Data

Task 9: Test the solution.


1. Run the application. If you receive an error message stating The process cannot access the file 'D:\Labfiles\Lab14\StressData.dat' because it is being used by another process", click OK. 2. 3. Press Ctrl+F5.

Click Display, and make a note of the Time (ms) value that is displayed next to the Display button. Click Display two more times. The times for these operations will probably be lower than the time that the initial query took because the various internal data structures have already been initialized. Make a note of these times.

Note: The time that is displayed is the time that is required to fetch the data by using the LINQ query, but not the time that is taken to format and display this data. This is why the "Fetching results" message appears for several seconds after the data has been retrieved. 4. 5. When the query is complete, examine the contents of the box in the lower part of the window. The search should return 40,641 values. Use the DatePicker and slider controls to modify the search criteria to the values in the following table, and then click Display again. Value From 02/01/2009 To 02/28/2009 From 250 to 450

Criteria Test Date Temperature 6.

When the query is complete, examine the contents of the box in the lower part of the window. The search should return 1,676 values. Note the time that it took to complete the searchthe time should be less than the times that you recorded in Step 3. Keep a note of these values for comparison in Exercise 2. Close the Stress Data Analyzer window, and then return to Visual Studio.

7.

Currently, any search through the data uses all four criteriadate, temperature, applied stress, and deflectionregardless of the values that are specified in the UI. If the user does not change the default values for any criteria, the LINQ query that the application generates still contains criteria for each field. This is rather inefficient. However, you can construct dynamic LINQ queries to enable you to generate a custom query that is based only on the criteria that are specified at run time. You will implement this functionality in the next exercise.

Exercise 2: Building Dynamic LINQ Queries


Task 1: Open the StressDataAnalyzer solution.
1. Open the StressDataAnalyzer solution in the D:\Labfiles\Lab14\Ex2\Starter folder. a. b. 2. In Visual Studio, on the File menu, click Open Project. In the Open Project dialog box, move to the D:\Labfiles\Lab14\Ex2 \Starter folder, click StressDataAnalyzer.sln, and then click Open.

Review the Task List. a. b. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

Lab: Using LINQ to Query Data

L14-9

3.

Examine the modified UI for the StressDataAnalyzer application. Note the following features of the application. The UI is an extended version of that used in Exercise 1. The user can specify which criteria to apply by using check boxes. Any criteria that are not selected are not included in the LINQ query. The user can change the order in which the data is displayed by selecting the appropriate option button in the Order By section of the window. The user can limit the number of items that a query returns by selecting the Limit check box and by using the slider control to specify the number of items. a. b. In Solution Explorer, expand the StressDataAnalyzer project. Double-click the DataAnalyzer.xaml file.

Task 2: Dynamically build a lambda expression for the query criteria.


1. In the Task List, locate the TODO: - Complete the BuildLambdaExpressionForQueryCriteria method task, and then double-click this task. This task is located in the BuildLambdaExpressionForQueryCriteria method.

The BuildLambdaExpressionForQueryCriteria method dynamically constructs a lambda expression from the values that are passed in as parameters. There are 12 parameters, which are divided into four groups. The dateRangeSpecified parameter is a Boolean value that indicates whether the user has selected the date criteria in the window, and the startDate and endDate parameters contain the start date and end date values that the user specified. If the dateRangeSpecified parameter is False, the date is not included in the criteria for matching stress data. The same logic applies to the remaining parameters. The value that the BuildLambdaExpressionForQueryCriteria method returns is an Expression object. The Expression type represents a strongly typed lambda expression as a data structure in the form of an expression tree. The type parameter is a delegate that indicates the form of the lambda expression. In the BuildLambdaExpressionForQueryCriteria method, the lambda expression takes a TestResult object and returns a Boolean value that indicates whether this object should be included in the results that are generated by running the lambda expression. The existing code in this method creates a reference to an Expression object named lambda. You will add code to populate this object with an expression tree that represents a lambda expression that matches the query criteria that the 12 parameters specify. If the user does not specify any query criteria, this method returns a null value. Note: The Expression type is located in the System.Linq.Expressions namespace. The application creates an alias for this namespace called Expressions. You cannot refer to the Expression type without the qualifying namespace in a WPF application because the WPF assemblies also contain a type called Expression. 2. Delete the TODO: - Complete the BuildLambdaExpressionForQueryCriteria method comment, and then add code to perform the following tasks. a. Create a Type reference for the TestResult type named testResultType.

Hint: Creating a type reference in this way enables you to repeatedly refer to an object type without repeatedly calling the GetType method. The GetType method is a relatively costly method compared to retrieving an object reference.

L14-10

Lab: Using LINQ to Query Data

b.

Create an Expressions.ParameterExpression object named itemBeingQueried by using the Expressions.Expression.Parameter shared method. Specify the testResultType type reference as the type of the parameter, and use the string "item" as the name of the parameter.

Hint: The string that is passed as the second parameter to the method call defines how your lambda expression will refer to the object that is being queried. In this example, one part of the resultant expression will resemble "item.TestDate >= startDate". Your code should resemble the following code.
Private Function BuildLambdaExpressionForQueryCriteria(...) ... If dateRangeSpecified OrElse temperatureRangeSpecified OrElse appliedStressRangeSpecified OrElse deflectionRangeSpecified Then ' Create the expression that defines the parameter for the ' lambda expression. ' The type is TestResult, and the lambda expression refers to ' it with the name "item". Dim testResultType As Type = GetType(TestResult) Dim itemBeingQueried As Expressions.ParameterExpression = Expressions.Expression.Parameter(testResultType, "item") ... End If ... End Function

3.

Add code to the method to create the following Expressions.BinaryExpression objects; each object should have an initial value of Nothing. dateCondition temperatureCondition appliedStressCondition deflectionCondition

You will populate these expression objects with query criteria that match the parameters that are passed in to the method. You will then combine these expression objects together to form the complete lambda expression tree. Your code should resemble the following code.
If dateRangeSpecified OrElse temperatureRangeSpecified OrElse appliedStressRangeSpecified OrElse deflectionRangeSpecified Then ... ' Create expressions for each of the possible conditions. Dim dateCondition As Expressions.BinaryExpression = Nothing Dim temperatureCondition As Expressions.BinaryExpression = Nothing Dim appliedStressCondition As Expressions.BinaryExpression = Nothing Dim deflectionCondition As Expressions.BinaryExpression = Nothing ... End If ...

4.

Add code to the method to invoke the BuildDateExpressionBody method, and store the result in the dateCondition object. Pass the following values as parameters to the method call. dateRangeSpecified startDate

Lab: Using LINQ to Query Data

L14-11

endDate testResultType itemBeingQueried

Note: The BuildDateExpressionBody method returns a BinaryExpression object that checks the stress test data against the startDate and endDate values. You will update the BuildDateExpressionBody method in the following task. Your code should resemble the following code.
If dateRangeSpecified OrElse temperatureRangeSpecified OrElse appliedStressRangeSpecified OrElse deflectionRangeSpecified Then ... ' Build Boolean expressions for each of the possible criteria ' that the user specifies. ' These method calls may return null if the user did not ' specify criteria for a property. dateCondition = BuildDateExpressionBody( dateRangeSpecified, startDate, endDate, testResultType, itemBeingQueried) ...

5.

Add code to the method to invoke the BuildNumericExpressionBody method, and store the result in the temperatureCondition object. Pass the following values as parameters to the method call. temperatureRangeSpecified fromTemperature toTemperature testResultType A string that contains the value "Temperature" itemBeingQueried

Note: The BuildNumericExpressionBody method also returns a BinaryExpression object that will form part of the dynamic LINQ query. In this case, the data that this part of the query checks will contain numeric data rather than a DateTime value, and the name of the field that is being checked is Temperature. You will update the BuildNumericExpressionBody method later in the lab. Your code should resemble the following code.
dateCondition = BuildDateExpressionBody( dateRangeSpecified, startDate, endDate, testResultType, itemBeingQueried) temperatureCondition = BuildNumericExpressionBody( temperatureRangeSpecified, fromTemperature, toTemperature, testResultType, "Temperature", itemBeingQueried)

6.

Add code to the method to invoke the BuildNumericExpressionBody method, and store the result in the appliedStressCondition object. Pass the following values as parameters to the method call.

L14-12

Lab: Using LINQ to Query Data

appliedStressRangeSpecified fromStressRange toStressRange testResultType A string that contains the value "AppliedStress" itemBeingQueried

Your code should resemble the following code.


temperatureCondition = BuildNumericExpressionBody( temperatureRangeSpecified, fromTemperature, toTemperature, testResultType, "Temperature", itemBeingQueried) appliedStressCondition = BuildNumericExpressionBody( appliedStressRangeSpecified, fromStressRange, toStressRange, testResultType, "AppliedStress", itemBeingQueried)

7.

Add code to the method to invoke the BuildNumericExpressionBody method, and store the result in the deflectionCondition object. Pass the following values as parameters to the method call. deflectionRangeSpecified fromDeflection toDeflection testResultType A string that contains the value "Deflection" itemBeingQueried

Your code should resemble the following code.


appliedStressCondition = BuildNumericExpressionBody( appliedStressRangeSpecified, fromStressRange, toStressRange, testResultType, "AppliedStress", itemBeingQueried) deflectionCondition = BuildNumericExpressionBody( deflectionRangeSpecified, fromDeflection, toDeflection, testResultType, "Deflection", itemBeingQueried)

8.

Add code to the method to invoke the BuildLambdaExpressionBody method, and store the result in a new Expressions.Expression object named body. Pass the dateCondition, temperatureCondition, appliedStressCondition, and deflectionCondition objects as parameters to the method.

Note. The BuildLambdaExpressionBody method takes the four expression objects, each of which evaluates a single property in a TestResult object, and combines them into a complete lambda expression that evaluates all the properties that the user specifies criteria for. You will complete the BuildLambdaExpressionBody method later in the lab. Your code should resemble the following code.
If dateRangeSpecified OrElse temperatureRangeSpecified OrElse appliedStressRangeSpecified OrElse deflectionRangeSpecified Then ...

Lab: Using LINQ to Query Data

L14-13

' Combine the Boolean expressions together into a single body. Dim body As Expressions.Expression = BuildLambdaExpressionBody( dateCondition, temperatureCondition, appliedStressCondition, deflectionCondition) ... End If

9.

Add code to the method to invoke the Expression.Lambda generic method, and store the response in the lambda object. The Expression.Lambda method should construct a lambda expression from the body of the lambda expressions in the body Expression object and the itemBeingQueried ParameterExpression object. Specify the delegate type Func(Of TestResult, Boolean) as the type parameter of the method.

Hint: The shared Expression.Lambda method constructs an expression tree that represents a completed lambda expression, including the data that is being queried by the expression. Your code should resemble the following code.
If dateRangeSpecified OrElse temperatureRangeSpecified OrElse appliedStressRangeSpecified OrElse deflectionRangeSpecified Then ... ' Build the lambda expression by using the parameter and the ' body expressions. lambda = Expressions.Expression.Lambda(Of Func(Of TestResult, Boolean))(body, itemBeingQueried) End If

10. Build the project and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 3: Dynamically build the date expression tree.


1. In the Task List, locate the TODO: - Complete the BuildDateExpressionBody method task, and then double-click this task. This task is located in the BuildDateExpressionBody method.

The existing code in this method defines a BinaryExpression object named dateCondition. This object will be used to return the expression tree that evaluates date values. The method then checks that the dateRangeSpecified parameter is True. You will add code to this conditional statement to build an expression tree that is equivalent to the condition in the following code example.
item.TestDate >= startDate AndAlso item.TestDate <= endDate

If the user did not specify any date criteria, this method returns a null expression tree. 2. Delete the TODO: - Complete the BuildDateExpressionBody method comment, and then add code to create a new MemberInfo object named testDateProperty. Call the GetProperty method to generate code that retrieves the TestDate property from the testResultType type parameter. Pass the string "TestDate" as the parameter to the GetProperty method.

Your code should resemble the following code.


If dateRangeSpecified Then ' Generate the expression: ' ' item.TestDate >= startDate ' Dim testDateProperty As MemberInfo =

L14-14

Lab: Using LINQ to Query Data

testResultType.GetProperty("TestDate") ... End If

3.

Add code to the method to create a MemberExpression object named testDateMember. Populate the object with the value that is returned by calling the Expression.MakeMemberAccess method, passing the itemBeingQueried parameter and the testDateProperty value as parameters to the method.

Note: A MemberExpression object is an expression that represents access to a property of the object that is being queried. In this case, the object represents the item.TestDate property. Your code should resemble the following code.
If dateRangeSpecified Then ... Dim testDateProperty As MemberInfo = testResultType.GetProperty("TestDate") Dim testDateMember As Expressions.MemberExpression = Expressions.Expression.MakeMemberAccess( itemBeingQueried, testDateProperty) ... End If

4.

Add code to create an Expressions.ConstantExpression object named lowerDate, and populate the object with the result of calling the Expression.Expressions.Constant method. Pass the startDate parameter as a parameter to the method call.

Note: A ConstantExpression object is an expression that represents the results of evaluating a constant value. In this case, the object represents the value in the startDate variable. Your code should resemble the following code.
If dateRangeSpecified Then ... Dim testDateMember As Expressions.MemberExpression = Expressions.Expression.MakeMemberAccess( itemBeingQueried, testDateProperty) Dim lowerDate As Expressions.ConstantExpression = Expressions.Expression.Constant(startDate) ... End If

5.

Add code to create an Expressions.BinaryExpression object named lowerDateCondition, and populate the object with the result of calling the Expressions.Expression.GreaterThanOrEqual method. Pass the testDateMember and lowerDate objects as parameters to the method call.

Note: The GreaterThanOrEqual method generates a binary expression that combines the testDateMember object (representing the "Me.startDate" portion of the expression) and the lowerDate object (representing the "startDate" portion of the expression) to generate a tree for the expression "Me.startDate >= startDate".

Lab: Using LINQ to Query Data

L14-15

Your code should resemble the following code.


If dateRangeSpecified Then ... Dim lowerDate As Expressions.ConstantExpression = Expressions.Expression.Constant(startDate) Dim lowerDateCondition As Expressions.BinaryExpression = Expressions.Expression.GreaterThanOrEqual( testDateMember, lowerDate) ... End If

6.

Using the same principles that you saw in Steps 4 and 5, add code to perform the following tasks. a. b. Create a ConstantExpression object named upperDate by passing the endDate parameter as a parameter to the method call. Create a BinaryExpression object named upperDateCondition by invoking the Expression.LessThanOrEqual method. Pass the testDateMember and upperDate objects as parameters to the method call.

Note: This code should build the second part of the date evaluation expression, which represents "endDate <= testDateMember". Your code should resemble the following code.
If dateRangeSpecified Then ... Dim lowerDateCondition As Expressions.BinaryExpression = Expressions.Expression.GreaterThanOrEqual( testDateMember, lowerDate) ' Generate the expression: ' ' item.Testdate <= endDate ' Dim upperDate As Expressions.ConstantExpression = Expressions.Expression.Constant(endDate) Dim upperDateCondition As Expressions.BinaryExpression = Expressions.Expression.LessThanOrEqual( testDateMember, upperDate) ... End If

7.

Add code to combine the expressions in the lowerDate and upperDate ExpressionTree objects into a single Boolean expression tree that returns True if both conditions are True, or False otherwise; call the Expressions.Expression.AndAlso shared method to combine the expressions together, and store the result in the dateCondition object.

Note: The Expressions.Expression.AndAlso method combines the two discrete expressions that you just created, "item.TestDate >= startDate" and "Item.TestDate <= endDate" into a BinaryExpression object that represents the expression "item.TestDate >= startDate AndAlso Item.TestDate <= endDate". Your code should resemble the following code.
If dateRangeSpecified Then ...

L14-16

Lab: Using LINQ to Query Data

' Combine the expressions with the AndAlso operator. dateCondition = Expressions.Expression.AndAlso( lowerDateCondition, upperDateCondition) End If

8.

Build the project and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 4: Dynamically build numeric expression trees.


1. In the Task List, locate the TODO: - Complete the BuildNumericExpressionBody method task, and then double-click this task. This task is located in the BuildNumericExpressionBody method.

The existing code in this method defines a BinaryExpression object named booleanCondition. This object will be used to return the expression tree that evaluates conditions based on Short integer values. You will add code to this conditional statement to build an expression tree that is equivalent to the expression in the following code example, where propertyName represents the value of the propertyName parameter.
item.PropertyName >= lowerRange AndAlso item.PropertyName <= upperRange

2.

Delete the TODO: - Complete the BuildNumericExpressionBody method comment, and then add code to generate the first half of the expression by performing the following tasks. a. Create a new MemberInfo object named testProperty. Call the GetProperty method to generate code that retrieves the property that the propertyName parameter specifies from the testResultType type parameter. Create a MemberExpression object named testMember that represents access to the property that the testProperty object specifies; call the shared Expression.MakeMemberAccess method, passing the itemBeingQueried parameter and the testProperty object as parameters. Create a ConstantExpression object named lowerValue by invoking the Expression.Constant method. Pass the lowerRange parameter as a parameter to the method call. Create a BinaryExpression object named lowerValueCondition, which combines the testMember and lowerValue expression objects into a GreaterThanOrEqual binary expression.

b.

c. d.

Hint: Your code should build the first half of the target expression, which represents "item.PropertyName >= lowerRange", where PropertyName represents the value of the propertyName parameter. Your code should use similar syntax to that used to generate the expression in Task 3 Your code should resemble the following code.
Private Function BuildNumericExpressionBody(ByVal rangeSpecified As Boolean, ByVal lowerRange As Short, ByVal upperRange As Short, ByVal testResultType As Type, ByVal propertyName As String, ByVal itemBeingQueried As Expressions.ParameterExpression) As Expressions.BinaryExpression ... If rangeSpecified Then ' Generate the expression: ' ' item.<Property> >= lowerRange ' Dim testProperty As MemberInfo = testResultType.GetProperty(propertyName) Dim testMember As Expressions.MemberExpression =

Lab: Using LINQ to Query Data

L14-17

Expressions.Expression.MakeMemberAccess( itemBeingQueried, testProperty) Dim lowerValue As Expressions.ConstantExpression = Expressions.Expression.Constant(lowerRange) Dim lowerValueCondition As Expressions.BinaryExpression = Expressions.Expression.GreaterThanOrEqual( testMember, lowerValue) ... End If ... End Function

3.

Add code to generate the second half of the target expression by performing the following tasks. a. Create a ConstantExpression object named upperValue by invoking the shared Expression.Constant method. Pass the upperRange parameter as a parameter to the method call. Create a BinaryExpression object named upperValueCondition, which combines the testMember and upperValue expression objects into a LessThanOrEqual binary expression.

b.

Hint: Your code should build the second half of the target expression, which represents "item.PropertyName <= upperRange", where PropertyName represents the value of the propertyName parameter. Your code should again use similar syntax to that used to generate the expression in Task 3. Your code should resemble the following code.
If rangeSpecified Then ... ' Generate the expression: ' ' item.<Property> <= upperRange ' Dim upperValue As Expressions.ConstantExpression = Expressions.Expression.Constant(upperRange) Dim upperValueCondition As Expressions.BinaryExpression = Expressions.Expression.LessThanOrEqual( testMember, upperValue) ... End If

4.

At the end of the method, add code to set the booleanCondition object to an expression that combines the lowerValueCondition and upperValueCondition expressions by using the shared Expressions.Expression.AndAlso method.

Your code should resemble the following code.


If rangeSpecified Then ... ' Combine the expressions with AndAlso booleanCondition = Expressions.Expression.AndAlso( lowerValueCondition, upperValueCondition)

L14-18

Lab: Using LINQ to Query Data

End If

5.

Build the project and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 5: Combine the expression trees.


1. In the Task List, locate the TODO: - Complete the BuildLambdaExpressionBody method task, and then double-click this task. This task is located in the BuildLambdaExpressionBody method.

This method takes four parameters that define the expression trees for each of the possible criteria that the user can enter. If any criteria are missing, the corresponding expression tree is null. The purpose of this method is to combine these expression trees together into an overall expression tree that includes all the criteria that the user enters. The existing code in this method creates an Expression object named body. This object will contain the combined binary expressions that form the body of the LINQ query. If the user did not specify any criteria, the body object is assigned an expression tree that contains the Boolean constant True. This expression tree causes all items to be retrieved. 2. Delete the TODO: - Complete the BuildLambdaExpressionBody method comment, and then add code to check whether the dateCondition parameter is null. If not, set the body object to the dateCondition expression tree.

Your code should resemble the following code.


Private Function BuildLambdaExpressionBody(...) As Expressions.Expression ... Dim body As Expressions.Expression = Nothing If Not dateCondition Is Nothing Then body = dateCondition End If ... End Function

3.

Add code to check whether the temperatureCondition parameter is null, and if not, perform the following tasks. a. b. If the body object is null, set the body object to the temperatureCondition expression tree. If the body object is not null, combine the expression tree in the body object with the expression tree in the temperatureCondition parameter by using the shared Expressions.Expression.AndAlso method. Assign the result to the body object.

Your code should resemble the following code.


Private Function BuildLambdaExpressionBody(...) As Expressions.Expression ... ' Add the temperatureCondition expression. If Not temperatureCondition Is Nothing Then If body Is Nothing Then body = temperatureCondition Else body = Expressions.Expression.AndAlso( body, temperatureCondition) End if End If ... End Function

Lab: Using LINQ to Query Data

L14-19

4.

Repeat the logic in Step 3 and add the expression tree that the appliedStressCondition parameter defines to the body expression tree.

Your code should resemble the following code.


Private Function BuildLambdaExpressionBody(...) As Expressions.Expression ... ' Repeat the same logic for the remaining condition expressions. If Not appliedStressCondition Is Nothing Then if body Is Nothing Then body = appliedStressCondition Else body = Expressions.Expression.AndAlso( body, appliedStressCondition) End If End If ... End Function

5.

Repeat the logic in Step 3 and add the expression tree that the deflectionCondition parameter defines to the body expression tree.

Your code should resemble the following code.


Private Function BuildLambdaExpressionBody(...) As Expressions.Expression ... ' Repeat the same logic for the remaining condition expressions. ... If Not deflectionCondition Is Nothing Then If body Is Nothing Then body = deflectionCondition Else body = Expressions.Expression.AndAlso( body, deflectionCondition) End if End If ... End Function

6.

Build the project and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 6: Build a lambda expression for the OrderBy statement.


1. In the Task List, locate the TODO: - Create the type reference and ParameterExpression in the BuildLambdaExpressionForOrderBy method task, and then double-click this task. This task is located in the BuildLambdaExpressionForOrderBy method.

The purpose of this method is to construct an expression that specifies the order in which the data should be retrieved. The parameter to this method is a value from the OrderByKey enumeration. This enumeration is defined as part of the application and contains the following values: ByDate, ByTemperature, ByAppliedStress, ByDeflection, and None. The following code example shows the form of the lambda expression that this method generates.
item => item.Property

L14-20

Lab: Using LINQ to Query Data

2.

In this example, Property references the property from the TestResult type that corresponds to the parameter that is passed into the method. If the user does not specify a sort key, this method returns a null value. Delete the TODO: - Create the type reference and ParameterExpression in the BuildLambdaExpressionForOrderBy method comment, and then add code to the method to create the ParameterExpression object that defines the parameter for the lambda expression by performing the following tasks.

3.

Note: You will need to create a Type reference to the TestResult object, and the lambda expression should refer to the object item. a. b. Create a Type reference named testResultType by using the GetType operator and passing a TestResult object as a parameter. Create a ParameterExpression object named itemBeingQueried by using the shared Expressions.Expression.Parameter method. Specify the testResultType object and a string that contains the text "item" as parameters to the method.

Your code should resemble the following code.


Private Function BuildLambdaExpressionForOrderBy(ByVal obKey As OrderByKey) As Expressions.Expression(Of Func(Of TestResult, ValueType)) ... If obKey <> OrderByKey.None Then ' Create the expression that defines the parameter for the ' lambda expression. ' The type is TestResult, and the lambda expression refers to ' it with the name "item". Dim testResultType As Type = GetType(TestResult) Dim itemBeingQueried As Expressions.ParameterExpression = Expressions.Expression.Parameter(testResultType, "item")

... End If ... End Function

4.

In the BuildLambdaExpressionForOrderBy method, replace the TODO: - Create a MemberExpression and MemberInfo object comment with code to perform the following tasks. a. b. Create a MemberExpression object named sortKey, and initialize this object to null. Create a MemberInfo object named propty, and initialize this object to null.

Your code should resemble the following code.


If obKey <> OrderByKey.None Then ... ' Create the expression that will define the sort key that ' the lambda expression returns. ' This expression will reference one of the properties in the ' TestResult structure depending on the key that the user ' specifies. Dim sortKey As Expressions.MemberExpression = Nothing Dim propty As MemberInfo = Nothing ...

Lab: Using LINQ to Query Data

L14-21

End If

5.

Replace the TODO: - Evaluate the obKey parameter to determine the property to sort by comment with code to evaluate the obKey parameter. Use the GetProperty method of the testResultType variable to generate code that retrieves the corresponding property value from the item that is specified as the parameter to the lambda expression. Store the result in the property variable. The following table lists the name of each property to use, depending on the value of the obKey parameter. You can either type this code manually, or you can use the Mod14SelectCaseobKey code snippet. testResultType property to use "TestDate" "Temperature" "AppliedStress" "Deflection"

obKey value ByDate ByTemperature ByAppliedStress ByDeflection

Note: Near the beginning of the BuildLambdaExpressionForOrderBy method, a conditional statement prevents the method from performing this code if the obKey parameter has the value OrderByKey.None; therefore, you do not need to check for this value. Your code should resemble the following code.
If obKey <> OrderByKey.None Then ... Dim propty As MemberInfo = Nothing Select Case obKey Case OrderByKey.ByDate ' If the user selected the date column, set the propty ' object to TestDate. propty = testResultType.GetProperty("TestDate") Case OrderByKey.ByTemperature ' If the user selected the temperature column, set the ' propty object to Temperature. propty = testResultType.GetProperty("Temperature") Case OrderByKey.ByAppliedStress ' If the user selected the applied stress column, set the ' propty object to AppliedStress. propty = testResultType.GetProperty("AppliedStress") Case OrderByKey.ByDeflection ' If the user selected the deflection column, set the ' propty object to Deflection. propty = testResultType.GetProperty("Deflection") End Select

... End If

6.

To use the code snippet, type Mod14SelectCaseobKey, and then press the Tab key twice.

Replace the TODO: - Construct the expression that specifies the OrderBy field comment with code that retrieves the value that the property variable specifies from the item that the itemBeingQueried variable specifies. To do this, call the shared Expressions.Expression.MakeMemberAccess method, and pass the itemBeingQueried expression tree and the property object as parameters to this method.

L14-22

Lab: Using LINQ to Query Data

Your code should resemble the following code.


If obKey <> OrderByKey.None Then ... ' Construct an expression that specifies the value in the field ' that the property object references in the TestResult object. sortKey = Expressions.Expression.MakeMemberAccess( itemBeingQueried, propty) ... End If

7.

Replace the TODO: - Create a UnaryExpression object to convert the sortKey object to a ValueType comment with code to create a new UnaryExpression object named convert by invoking the shared Expressions.Expression.Convert method. Pass the sortKey object and the type of the ValueType type as parameters to the method call. This step is necessary because the possible sort keys are all value types, and they must be converted to ValueType objects for the ordering to function correctly.

Your code should resemble the following code.


If obKey <> OrderByKey.None Then ... ' Cast the sortKey object to a ValueType object (ValueType is the ' ancestor of all value types, including DateTime and Short). Dim convert As Expressions.UnaryExpression = Expressions.Expression.Convert(sortKey, GetType(ValueType)) ... End If

8.

Replace the TODO: - Create the OrderBy lambda expression comment with code to combine the converted unary expression that contains the sort key and the itemBeingQueried variable into a lambda expression by using the shared Expression.Lambda generic method. Specify the type Func(Of TestResult, ValueType) as the type parameter to the Lambda method; the resulting lambda expression takes a TestResult object as the parameter and returns a ValueType object.

Your code should resemble the following code.


If obKey <> OrderByKey.None Then ... ' Build the lambda expression by using the parameter and the ' expression that contains the sort key. lambda = Expressions.Expression.Lambda(Of Func(Of TestResult, ValueType))(convert, itemBeingQueried) End If

9.

Build the project and correct any errors. On the Build menu, click Build Solution. Correct any errors.

Task 7: Examine the CreateQuery method.


In the Task List, locate the TODO: - Examine the CreateQuery method task, and then double-click this task. This task is located in the CreateQuery method. This method is the starting point for the lambda expression generation. The method accepts parameters that indicate which query criteria the lambda expression should include and the upper and lower ranges for each of these criteria. The method first calls the BuildLambdaExpressionForQueryCriteria method to construct a lambda expression that incorporates the query criteria. It then calls the BuildLambdaExpressionForOrderBy method to construct the lambda expression that defines the sort order for retrieving the data. Note

Lab: Using LINQ to Query Data

L14-23

that, at this point, it is possible that either of these expressions may still be null if the user either did not specify any criteria or did not specify a sort key. After the method creates the expression objects, it creates an IEnumerable generic collection named query that is based on the TestResult type, and it initializes the object with the data in the stressData parameter. If the lambda expression that specifies the query criteria is not null, the method filters the data in the IEnumerable collection by invoking the Where LINQ extension method on the collection. The parameter to the Where method is the lambda expression that contains the query criteria. Note that the Compile method of an Expression(Of TDelegate) object converts the expression tree into a compiled lambda expression that the common language runtime (CLR) can execute. If the lambda expression that defines the sort order is not null, this method then applies this lambda expression to the IEnumerable collection by using the OrderBy LINQ extension method. As before, the Compile method converts the expression tree that defines the sort key into code that can be executed by using the CLR. If the user specifies that the query should return a limited number of rows, the Take LINQ extension method is applied to the IEnumerable collection with the limit that the user specifies. Finally, the IEnumerable collection is returned to the caller. Note that this method does not run the LINQ query. This action occurs in the DisplayResultsButton_Click method, when the code calls the Count method of the IEnumerable collection.

Task 8: Test the solution.


1. Run the application. 2. Press Ctrl+F5.

In the Stress Data Analyzer window, click Display to display all results with no query criteria, sort key, or limit to the number of items that are returned. Note the time that it takes to run the query.

Note: This test is different from the test that you performed at the end of the first exercise. In the original application, the LINQ query used a lambda expression that contained criteria for all properties, whereas this test does not use any criteria. Therefore, the operation should be faster. 3. Select the Test Date and Temperature check boxes, modify the search criteria to the values in the following table, and then click Display again. Value From 02/01/2009 To 02/28/2009 From 250 to 450

Criteria Test Date Temperature

4.

When the query is complete, examine the contents of the box in the lower part of the window. The search should return 1,676 values, as in the test in Exercise 1. However, the time it takes to run the query should again be less than the time that you recorded in Exercise 1. Clear the Test Date and Temperature check boxes, and then select the Limit? check box. Set the limit value to 2,000, and then click Display.

5.

Note that when the number of rows is reduced, the time it takes to run the query is substantially reduced.

L14-24

Lab: Using LINQ to Query Data

6.

In the Order By section, select Temperature, and then click Display again.

Note that the expression takes substantially longer to run when a sort key is included in the expression. 7. 8. Close the Stress Data Analyzer window, and then return to Visual Studio. Close Visual Studio. In Visual Studio, on the File menu, click Exit.

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

L15-1

Module 15: Integrating Visual Basic Code with Dynamic Languages and COM Components

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components
Exercise 1: Integrating Code Written by Using a Dynamic Language into a Visual Basic Application
Task 1: Examine the Python and Ruby code.
1. Open Microsoft Visual Studio 2010. 2. Click Start, point to All Programs, click Microsoft Visual Studio 2010, and then click Microsoft Visual Studio 2010.

Using Notepad, open the Shuffler.py file in the D:\Labfiles\Lab15\Python folder. a. b. c. d. Using Windows Explorer, browse to the D:\Labfiles\Lab15\Python folder. Right-click Shuffler.py, and then click Open. In the Windows dialog box, click Select a program from a list of installed programs, and then click OK. In the Open with dialog box, click Notepad, and then click OK.

3.

In Notepad, examine the Python code. The Shuffler.py file contains a Python class named Shuffler that provides a method named Shuffle. The Shuffle method takes a parameter named data that contains a collection of items. The Shuffle method implements the Fisher-Yates-Durstenfeld algorithm to randomly shuffle the items in the data collection. The Python class also exposes a function named CreateShuffler that creates a new instance of the Shuffler class. You will use this method from Microsoft Visual Basic to create a Shuffler object.

4. 5.

Close Notepad. Using Notepad, open the Trapezoid.rb file in the D:\Labfiles\Lab15\Ruby folder. a. b. c. d. Using Windows Explorer, browse to the D:\Labfiles\Lab15\Ruby folder. Right-click Trapezoid.rb, and then click Open. In the Windows dialog box, click Select a program from a list of installed programs, and then click OK. In the Open with dialog box, click Notepad, and then click OK.

6.

In Notepad, examine the Ruby code. The Trapezoid.rb file contains a Ruby class named Trapezoid that models simple trapezoids. The constructor expects the angle of the lower-left vertex, the length of the base, the length of the top, and the height of the trapezoid. The lengths of the remaining sides and angles are calculated.

Note: The Trapezoid class models a subset of possible trapezoids. The length of the base must be greater than the length of the top, and the specified vertex must be an acute angle.

L15-2

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

The lengths of the sides, the angles of each vertex, and the height are exposed as properties. The to_s method returns a string representation of the trapezoid.
Note: The to_s method is the Ruby equivalent of the ToString method in Microsoft .NET Framework. The Ruby binder in the dynamic language runtime (DLR) automatically translates a call to the ToString method on a Ruby object to a call to the to_s method. The area method calculates the area of the trapezoid. The Ruby file also provides a function named CreateTrapezoid that creates a new instance of the Trapezoid class. 7. Close Notepad.

Task 2: Open the starter project.


1. Open the DynamicLanguageInterop solution in the D:\Labfiles\Lab15\Starter folder. a. b. c. 2. On the File menu, click Open Project. In the Open Project dialog box, browse to the D:\Labfiles\Lab15\Starter folder. Click DynamicLanguageInterop.sln, and then click Open.

To the TestDynamicLanguageInterop project, add a reference to the DynamicLanguageInterop project. a. b. c. In Solution Explorer, right-click TestDynamicLanguageInterop, and then click Add Reference. In the Add Reference dialog box, click Projects. Select DynamicLanguageInterop, and then click OK.

Task 3: Create a Python object and call Python methods.


1. Examine the InteropTestWindow.xaml file. In Solution Explorer, expand the DynamicLanguageInterop project, and then double-click the InteropTestWindow.xaml file.

This window contains two tabs, labeled Python Test and Ruby Test. The Python Test tab enables you to type values into the Data box and specify whether this is text or numeric data. When you click Shuffle, the data will be packaged into an array and passed to the Shuffle method of a Python Shuffler object. The shuffled data will be displayed in the Shuffled Data box. The functionality to create the Python object and call the Shuffle method has not yet been implemented; you will do this in this task. 2. To the DynamicLanguageInterop project, add references to the assemblies listed in the following table. The DLR uses these assemblies to provide access to the IronPython runtime. Path C:\Program Files (x86)\ IronPython 2.6 for .NET 4.0\ IronPython.dll

Assembly IronPython

IronPython.Modules C:\Program Files (x86)\ IronPython 2.6 for .NET 4.0\

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

L15-3

Assembly

Path IronPython.Modules.dll

Microsoft.Dynamic

C:\Program Files (x86)\ IronPython 2.6 for .NET 4.0\ Microsoft.Dynamic.dll C:\Program Files (x86)\ IronPython 2.6 for .NET 4.0\ Microsoft.Scripting.dll

Microsoft.Scripting

d. e. f. g. 3.

In Solution Explorer, right-click DynamicLanguageInterop, and then click Add Reference. In the Add Reference dialog box, click Browse. Browse to the C:\Program Files (x86)\IronPython 2.6 for .NET 4.0 folder. Select IronPython.dll, IronPython.Modules.dll, Microsoft.Dynamic.dll, and Microsoft.Scripting.dll, and then click OK.

Review the Task List. a. b. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

4.

In the Task List, locate the TODO: Add Namespaces containing IronPython and IronRuby runtime support and interop types task, and then double-click this task. This task is located near the top of the InteropTestWindow.xaml.vb file. This is the code behind the InteropTestWindow window. After the comment, add Imports statements to bring the IronPython.Hosting and Microsoft.Scripting.Hosting namespaces into scope. Your code should resemble the following code example.
' TODO: Add Namespaces containing IronPython and IronRuby runtime support and interop types Imports IronPython.Hosting Imports Microsoft.Scripting.Hosting ...

5.

6.

In the InteropTestWindow class, examine the string constants near the start of the class. In particular, note the pythonLibPath and pythonCode strings. The pythonLibPath constant specifies the folder where the Python libraries are installed. The Shuffler class makes use of a Python library named random that is located in this folder. The pythonCode constant specifies the name and location of the Python script that contains the Shuffler class.

7.

In the Task List, locate the TODO: Create an instance of the Python runtime, and add a reference to the folder holding the "random" module task, and then double-click this task. This task is located in the ShuffleData method. The ShuffleButton_Click method calls the ShuffleData method when the user clicks the Shuffle Data button. The ShuffleButton_Click method gathers the user input from the form and parses it into an array of objects. It then passes this array to the ShuffleData method. The purpose of the

L15-4

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

ShuffleData method is to create a Python Shuffler Python object and then call the Shuffle method by using the array as a parameter. When the ShuffleData method finishes, the ShuffleButton_Click method displays the shuffled data in the Windows Presentation Foundation (WPF) window. 8. After the TODO comment, add code that performs the following tasks: a. b. Create a ScriptEngine object named pythonEngine by using the shared CreateEngine method of the Python class. Obtain a reference to the search paths that the Python runtime uses; call the GetSearchPaths method of the pythonEngine object and store the result in an ICollection(Of String) collection object named paths. Add the path that is specified in the pythonLibPath string to the paths collection. Set the search paths that the pythonEngine object uses to the paths collection; use the SetSearchPaths method.

c. d.

Your code should resemble the following code example.


Private Sub ShuffleData(ByVal data() as Object) ' TODO: Create an instance of the Python runtime, and add a reference to the folder holding the "random" module ' The Python script references this module Dim pythonEngine As ScriptEngine = Python.CreateEngine() Dim paths As ICollection(Of String) = pythonEngine.GetSearchPaths() paths.Add(pythonLibPath) pythonEngine.SetSearchPaths(paths) ... End Sub

9.

After the comment TODO: Run the script and create an instance of the Shuffler class by using the CreateShuffler method in the script, add code that performs the following tasks: a. Create an object named pythonScript. Initialize this object with the value that is returned by calling the ExecuteFile method of the pythonEngine object. Specify the pythonCode constant as the parameter to this method. This statement causes the Python runtime to load the Shuffler.py script. The pythonScript object contains a reference to this script that you can use to invoke functions and access classes that are defined in this script. b. Create another object named pythonShuffler. Call the CreateShuffler method of the pythonScript object and store the result in the pythonShuffler object. This statement invokes the CreateShuffler function in the Python script. This function creates an instance of the Shuffler class and returns it. The pythonShuffler object then holds a reference to this object.

Note: The pythonScript variable is of type Object, so Microsoft IntelliSense does not display the CreateShuffler method. Your code should resemble the following code example.
Private Sub ShuffleData(ByVal data() as Object) ... ' TODO: Run the script and create an instance of the Shuffler class by using the CreateShuffler method in the script Dim pythonScript As Object = pythonEngine.ExecuteFile(pythonCode) Dim pythonShuffler As Object = pythonScript.CreateShuffler()

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

L15-5

... End Sub

10. After the comment TODO: Shuffle the data, add code that calls the Shuffle method of the pythonShuffler object. Pass the data array as the parameter to the Shuffle method. This statement runs the Shuffle method in the Python object. The DLR marshals the data array into a Python collection and then invokes the Shuffle method. When the method completes, the DLR unmarshals the shuffled collection back into the data array. Your code should resemble the following code example.
Private Sub ShuffleData(ByVal data() as Object) ... ' TODO: Shuffle the data. pythonShuffler.Shuffle(data) End Sub

11. Build the application and correct any errors. On the Build menu, click Build DynamicLanguageInterop.

Task 4: Test the Python code.


1. Run the application. 2. 3. 4. 5. 6. Press Ctrl+F5.

In the Dynamic Language Interop Tests window, on the Python Test tab, in the Data box, type some random words that are separated by spaces. Click the Text option button, and then click Shuffle. Verify that the shuffled version of the data appears in the Shuffled Data box. Click Shuffle again. The data should be shuffled again and appear in a different sequence. Replace the text in the Data box with integer values, click Integer, and then click Shuffle. Verify that the numeric data is shuffled. Close the Dynamic Language Interop Tests window, and then return to Visual Studio.

Task 5: Create a Ruby object and call Ruby methods.


1. Examine the Ruby Test tab in the InteropTestWindow.xaml file. The Ruby Test tab enables you to specify the dimensions of a trapezoid (the angle of the first vertex, the length of the base, the length of the top, and the height) by using a series of slider controls. When you click the Visualize button, the application will create an instance of the Ruby Trapezoid class and display a graphical representation in the canvas in the lower part of the window. The dimensions and area of the trapezoid will be displayed in the text block that is to the right. The functionality to create the Ruby object and calculate its area and dimensions has not yet been implemented; you will do this in this task. a. b. 2. In Solution Explorer, double-click the InteropTestWindow.xaml file. Click the Ruby Test tab.

To the DynamicLanguageInterop project, add references to the assemblies listed in the following table. The DLR uses these assemblies to provide access to the IronRuby runtime. Path

Assembly

L15-6

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

Assembly IronRuby IronRuby.Libraries

Path C:\Program Files (x86)\IronRuby 1.0v4\ bin\IronRuby.dll C:\Program Files (x86)\IronRuby 1.0v4\ bin\IronRuby.Libraries.dll

a. b. c. d. 3.

In Solution Explorer, right-click DynamicLanguageInterop, and then click Add Reference. In the Add Reference dialog box, click Browse. Browse to the C:\Program Files (x86)\IronRuby 1.0v4\bin\ folder. Select IronRuby.dll and IronRuby.Libraries.dll, and then click OK.

Review the Task List. a. b. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

4. 5.

In the Task List, locate the TODO: Add Namespaces containing IronPython and IronRuby runtime support and interop types task, and then double-click this task. Add an Imports statement to bring the IronRuby namespace into scope. Your code should resemble the following code example.
Imports IronPython.Hosting Imports Microsoft.Scripting.Hosting Imports IronRuby ...

6.

In the InteropTestWindow class, examine the rubyCode string constant near the start of the class. The rubyCode constant specifies the name and location of the Ruby script that contains the Trapezoid class.

7.

In the Task List, locate the TODO: Retrieve the values specified by the user. These values are used to create the trapezoid task, and then double-click this task. This task is located in the VisualizeButton_Click method. This method is named when the user clicks the Visualize button, after the user has specified the data for the trapezoid. After the TODO comment, add code that performs the following tasks. a. Create an integer variable named vertexAInDegrees. Initialize this variable with the value of the vertexA slider control.

8.

Hint: Use the Value property of a slider control to read the value. This value is returned as a Double value, so use a cast to convert it to an integer. This cast is safe because the slider controls are configured to return integer values in a small range, so no data will be lost. b. c. Create an integer variable named lengthSideAB. Initialize this variable with the value of the sideAB slider control. Create an integer variable named lengthSideCD. Initialize this variable with the value of the sideCD slider control.

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

L15-7

d.

Create an integer variable named heightOfTrapezoid. Initialize this variable with the value of the height slider control.

Your code should resemble the following code example.


Private Sub VisualizeButton_Click(ByVal sender As object , ByVal e As RoutedEventArgs) Try ' TODO: Retrieve the values specified by the user. These values are used to create the trapezoid. Dim vertexAInDegrees As Integer = CType(VertexASlider.Value, Integer) Dim lengthSideAB As Integer = CType(SideABSlider.Value, Integer) Dim lengthSideCD As Integer = CType(SideCDSlider.Value, Integer) Dim heightOfTrapezoid As Integer = CType(HeightSlider.Value, Integer) ... End Try ... End Sub

9.

After the comment TODO: Call the CreateTrapezoid method and build a trapezoid object, add a statement that creates an Object variable named trapezoid and initializes it with the value that the CreateTrapezoid method returns. Pass the variables vertexAInDegrees, lengthSideAB, lengthSideCD, and heightOfTrapezoid as arguments to the CreateTrapezoid method. You will implement the CreateTrapezoid method in a later step. This method will create an instance of the Ruby Trapezoid class by using the specified data and return it. Your code should resemble the following code example.
Private Sub VisualizeButton_Click(ByVal sender As object , ByVal e As RoutedEventArgs) Try ... ' TODO: Call the CreateTrapezoid method and build a trapezoid object. Dim trapezoid As Object = CreateTrapezoid(vertexAInDegrees, lengthSideAB, lengthSideCD, heightOfTrapezoid) ... ... End Sub

10. After the comment TODO: Display the lengths of each side, the internal angles, and the area of the trapezoid, add a statement that calls the DisplayStatistics method. Pass the trapezoid object and the TrapezoidStatisticsTextBlock control as parameters to this method. You will implement the DisplayStatistics method in a later step. This method will call the to_s and area methods of the Ruby Trapezoid class and display the results in the trapezoidStatistics text block on the right of the Ruby Test tab in the WPF window. Your code should resemble the following code example.
Private Sub VisualizeButton_Click(ByVal sender As object , ByVal e As RoutedEventArgs) Try ... ' TODO: Display the lengths of each side, the internal angles, and the area of the trapezoid. DisplayStatistics(trapezoid, Me.TrapezoidStatisticsTextBlock) ... End Try ...

L15-8

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

End Sub

11. After the comment TODO: Display a graphical representation of the trapezoid, add a statement that calls the RenderTrapezoid method. Pass the trapezoid object and the trapezoidCanvas canvas control as parameters to this method. The RenderTrapezoid method is already complete. This method queries the properties of the Ruby Trapezoid object and uses them to draw a representation of the trapezoid on the canvas in the lower part of the window. Your code should resemble the following code example.
Private Sub VisualizeButton_Click(ByVal sender As object , ByVal e As RoutedEventArgs) Try ... ' TODO: Display a graphical representation of the trapezoid. RenderTrapezoid(trapezoid, Me.TrapezoidCanvas) End Try ... End Sub

12. In the Task List, locate the TODO: Create an instance of the Ruby runtime task, and then doubleclick this task. This task is located in the CreateTrapezoid method. 13. At the start of this method, remove the statement that throws the NotImplementedException exception. After the comment, add a statement that creates a ScriptRuntime object named rubyRuntime. Initialize the rubyRuntime variable with the value that the shared CreateRuntime method of the Ruby class returns. Your code should resemble the following code example.
Private Function CreateTrapezoid(ByVal vertexAInDegrees As Integer, ByVal lengthSideAB As Integer, ByVal lengthSideCD As Integer, ByVal heightOfTrapezoid As Integer) As Object ' TODO: Create an instance of the Ruby runtime. Dim rubyRuntime As ScriptRuntime = Ruby.CreateRuntime() ... End Function

14. After the comment TODO: Run the Ruby script that defines the Trapezoid class, add a statement that creates a variable of type Object named rubyScript. Initialize the rubyScript variable with the value that the UseFile method of the rubyRuntime object returns. Pass the rubyCode constant as the parameter to the UseFile method. This statement causes the Ruby runtime to load the Trapezoid.rb script. The rubyScript object contains a reference to this script that you can use to invoke functions and access classes that are defined in this script. Your code should resemble the following code example.
Private Function CreateTrapezoid(ByVal vertexAInDegrees As Integer, ByVal lengthSideAB As Integer, ByVal lengthSideCD As Integer, ByVal heightOfTrapezoid As Integer) As Object ... ' TODO: Run the Ruby script that defines the Trapezoid class. Dim rubyScript As Object = rubyRuntime.UseFile(rubyCode) ...

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

L15-9

End Function

15. After the comment TODO: Call the CreateTrapezoid method in the Ruby script to create a trapezoid object, add a statement that creates a variable of type Object named rubyTrapezoid. Initialize the rubyTrapezoid variable with the value that the CreateTrapezoid method of the rubyScript object returns. Pass the vertexAInDegrees, lengthSideAB, lengthSideCD, and heightOfTrapezoid variables as parameters to the CreateTrapezoid method. This statement invokes the CreateTrapezoid function in the Ruby script. The DLR marshals the arguments that are specified and passes them as parameters to the CreateTrapezoid function. This function creates an instance of the Trapezoid class and returns it. The rubyTrapezoid object then holds a reference to this object.
Note: The rubyScript variable is of type Object, so IntelliSense does not display the CreateTrapezoid method. Your code should resemble the following code example.
Private Function CreateTrapezoid(ByVal vertexAInDegrees As Integer, ByVal lengthSideAB As Integer, ByVal lengthSideCD As Integer, ByVal heightOfTrapezoid As Integer) As Object ... ' TODO: Call the CreateTrapezoid method in the Ruby script to create a trapezoid object. Dim rubyTrapezoid As Object = rubyScript.CreateTrapezoid( vertexAInDegrees, lengthSideAB, lengthSideCD, heightOfTrapezoid) ... End Function

16. After the comment TODO: Return the trapezoid object, add a statement that returns the value in the rubyTrapezoid variable. Your code should resemble the following code example.
Private Function CreateTrapezoid(ByVal vertexAInDegrees As Integer, ByVal lengthSideAB As Integer, ByVal lengthSideCD As Integer, ByVal heightOfTrapezoid As Integer) As Object ... ' TODO: Return the trapezoid object. Return rubyTrapezoid End Function

17. In the Task List, locate the TODO: Use a StringBuilder object to construct a string holding the details of the trapezoid task, and then double-click this task. This task is located in the DisplayStatistics method. 18. After the comment, add a statement that creates a new StringBuilder object named builder. Your code should resemble the following code example.
Private Sub DisplayStatistics(ByVal trapezoid As Object, ByVal trapezoidStatistics As TextBlock) ' TODO: Use a StringBuilder object to construct a string holding the details of the trapezoid. Dim builder As New StringBuilder() ... End Sub

L15-10

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

19. After the comment TODO: Call the to_s method of the trapezoid object to return the details of the trapezoid as a string, add a statement that calls the ToString method of the trapezoid variable and appends the result to the end of the builder object. The DLR automatically converts the ToString method call into a call to the to_s method in the Ruby object. The to_s method constructs a Ruby string, which is unmarshaled into a .NET Framework string. Your code should resemble the following code example.
Private Sub DisplayStatistics(ByVal trapezoid As Object, ByVal trapezoidStatistics As TextBlock) ... ' TODO: Call the to_s method of the trapezoid object to return the details of the trapezoid as a string. ' Note: The ToString method invokes to_s. builder.Append(trapezoid.ToString()) ... End Sub

20. After the comment TODO: Calculate the area of the trapezoid object by using the area method of the trapezoid class, add code that calls the area method of the trapezoid variable, converts the result into a string, and appends this string to the end of the builder object. Your code should resemble the following code example.
Private Sub DisplayStatistics(ByVal trapezoid As Object, ByVal trapezoidStatistics As TextBlock) ... ' TODO: Calculate the area of the trapezoid object by using the area method of the trapezoid class ' and append it to the string holding the details of the trapezoid builder.Append(String.Format(vbNewLine & "Area:" & vbTab & vbTab & "{0}", trapezoid.area().ToString())) ... End Sub

21. After the comment TODO: Display the details of the trapezoid in the TextBlock control, add a statement that sets the Text property of the trapezoidStatistics control to the string that is constructed by the builder object. Your code should resemble the following code example.
Private Sub DisplayStatistics(ByVal trapezoid As Object, ByVal trapezoidStatistics As TextBlock) ... ' TODO: Display the details of the trapezoid in the TextBlock control trapezoidStatistics.Text = builder.ToString() End Sub

22. Build the application and correct any errors. On the Build menu, click Build DynamicLanguageInterop.

Task 6: Test the Ruby code.


1. Run the application. 2. Press Ctrtl+F5.

In the Dynamic Language Interop Tests window, click the Ruby Test tab.

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

L15-11

3.

Set the Vertex A slider to 75, set the Length of Base slider to 200, set the Length of Top slider to 100, set the Height slider to 150, and then click Visualize. Verify that a representation of the trapezoid is displayed in the canvas in the lower half of the window and the statistics for the trapezoid appear in the text block that is to the right. The area of the trapezoid should be 22,500.

4.

Experiment with different values for the slider controls, and then click Visualize. If you specify values that are outside the range for the set of trapezoids that the Trapezoid class can model, a message box should be displayed to indicate the problem. This error message is raised by the constructor in the Trapezoid class. The DLR catches the error and converts it into a .NET Framework Exception object. The VisualizeButton_Click method caches this exception and displays the error in a message box. Close the Dynamic Language Interop Tests window, and then return to Visual Studio.

5.

Exercise 2: Using a COM Component from a Visual Basic Application


Task 1: Examine the data files.
1. Using Windows Explorer, browse to the D:\Labfiles\Lab15 folder, and then verify that this folder contains the following three text files: 2. 298K.txt 318K.txt 338K.txt

Using Notepad, open the 298K.txt file. In Windows Explorer, right-click the 298K.txt file, and then click Open.

This file contains results from the deflection tests for steel girders that were subjected to various pressures at a temperature of 298 Kelvin. The number on a line by itself at the top of the file is the temperature at which the tests were performed (298). The remaining lines contain pairs of numbers; the numbers in each pair are separated by a comma. These numbers are the pressure applied, which is measured in kiloNewtons (kN), and the deflection of the girder, which is measured in millimeters. 3. 4. Close Notepad. Using Notepad, open the 318K.txt file. This file is in the same format as the 298K.txt file. It contains the results of deflection tests that were performed at a temperature of 318 Kelvin. Notice that the final few lines do not contain any deflection data because the test was halted at a force of 1,000 kN. 5. 6. Close Notepad. Using Notepad, open the 338K.txt file. This file is similar to the other two. It contains the results of deflection tests that were performed at a temperature of 338 Kelvin. The test was halted at a force of 800 kN. 7. Close Notepad.

Task 2: Open the starter project and examine the StressData type.
1. Using Visual Studio, open the GenerateGraph solution in the D:\Labfiles \Lab15\Starter folder. a. On the File menu, click Open Project.

L15-12

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

b. c. 2.

In the Open Project dialog box, browse to the D:\Labfiles\Lab15\Starter folder. Click GenerateGraph.sln, and then click Open.

Open the StressData.vb file. In Solution Explorer, double-click StressData.vb.

The StressData type acts as a container for the stress data for a given temperature. It contains the following public properties. Temperature. This is a short value that records the temperature of the test. Data. This is a Dictionary collection that holds the data. The stress value is used as the key into the dictionary, and the item data is the deflection.

The StressData class also overrides the ToString method, which returns a formatted string that lists the stress test data that is stored in the object.

Task 3: Examine the GraphWindow test harness.


1. Open the GraphWindow.xaml file. In Solution Explorer, double-click GraphWindow.xaml.

This window provides a simple test harness for reading the data from the data files and invoking Microsoft Office Excel to generate a graph by using this data. When users click Get Data, they are prompted for the data file to load. The file is read into a new StressData object, and the contents of the file are displayed in the TreeView control that occupies the main part of the window. A user can click Get Data multiple times and load multiple files; they will all be read in and displayed. The StressData objects are stored in a List collection that is held in a private field in the GraphWindow class and is named graphData. This code has already been written for you. When a user clicks Graph, the data in the graphData collection will be used to generate an Office Excel graph. The information in each StressData object will be transferred to an Office Excel worksheet, and a line graph will then be generated to show the stress data for each temperature. A user can quickly examine this graph and spot any trends in the failure of girders. 2. Open the GraphWindow.xaml.vb code file. 3. In Solution Explorer, expand GraphWindow.xaml, and then double-click GraphWindow.xaml.vb.

Locate the populateFromFile method. This method uses a StreamReader object to read and parse the stress data from a file that is specified as a parameter, and it populates a StressData object that is also specified as a parameter. This method is complete.

4.

Locate the displayData method. This method takes a populated StressData object and displays the items in this object in the TreeView control in the window. This method is also complete.

5.

Locate the GetDataButton_Click method. This method runs when the user clicks the Get Data button. It uses an OpenFileDialog object to prompt the user for the name of a data file and then passes the file name together with a new

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

L15-13

StressData object to the populateFromFile method. It then adds the populated StressData object to the graphData collection before it calls the displayData method to add the data to the TreeView control in the window. This method is complete. 6. Locate the GenerateGraphButton_Click method. This method runs when the user clicks the Generate button. It prompts the user for the name of an Office Excel workbook to create. It will then create this new workbook and copy the data in the graphData collection into a worksheet in this workbook before it generates a graph. This method is not complete. You will add the missing functionality and complete the transferDataToExcelSheet and generateExcelChart helper methods that this code will use.

Task 4: Copy data to an Office Excel worksheet.


1. Add a reference to the Microsoft Excel 14.0 Object Library to the GenerateGraph project. This is the COM object library that implements the Office Excel object model. a. b. c. 2. In Solution Explorer, right-click GenerateGraph, and then click Add Reference. In the Add Reference dialog box, click the COM tab. In the list of COM components, scroll down and click Microsoft Excel 14.0 Object Library, and then click OK.

Review the Task List. a. b. If the Task List is not already visible, on the View menu, point to Other Windows, and then click Task List. If the Task List is displaying User Tasks, in the drop-down list box, click Comments.

3. 4.

In the Task List, locate the TODO: Add the Microsoft.Office.Interop.Excel namespace task, and then double-click this task. This task is located near the top of the GraphWindow.xaml.vb file. Bring the Microsoft.Office.Interop.Excel namespace into scope, and give it an alias of Excel. This alias helps you to distinguish items in this namespace and avoid name clashes without having to specify the full namespace in ambiguous object references. Your code should resemble the following code example.
' TODO: Add the Microsoft.Office.Interop.Excel namespace. Imports Excel = Microsoft.Office.Interop.Excel

5.

Locate the transferDataToExcelSheet method. The GenerateGraphButton_Click method will call this method. It takes three parameters: An Excel.Worksheet object named excelWS. This object is a reference to the Office Excel worksheet that you will copy the data to. An Excel.Range object named dataRange. This is an output parameter. You will use this object to indicate the area of the worksheet that contains the data after it has been copied. A List(Of StressData) object named excelData. This is a collection of StressData objects that contain the data that you will copy to the Office Excel worksheet.

This method returns True if it successfully copies the data to the Office Excel worksheet, and False if an exception occurs. 6. In the transferDataToExcelSheet method, after the comment TODO: Copy the data for the applied stresses to the first column in the worksheet, add code that performs the following tasks:

L15-14

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

a. b. c.

Declare an integer variable named rowNum and initialize it to 1. Declare an integer variable named colNum and initialize it to 1. Set the value of the cell at location rowNum, colNum in the excelWS worksheet object to the text "Applied Stress".

Hint: You can use the Cells property to read and write a cell in an Excel worksheet object. This property acts like a two-dimensional array. d. Use a For Each loop to iterate through the keys in the first StressData object in the excelData collection.

Hint: Remember that the StressData object contains a Dictionary property named Data, and the key values in this dictionary are the applied stresses for the test (100, 200, 300, up to 1,500 kN). You can use the Keys property of a Dictionary object to obtain a collection of keys that you can iterate through. e. In the body of the For Each loop, increment the rowNum variable, and store the value of each key found in the cell at location rowNum, colNum in the excelWS worksheet object.

Your code should resemble the following code example.


Private Function transferDataToExcelSheet(ByVal excelWS As Excel.Worksheet, ByRef dataRange As Excel.Range, ByVal excelData As List(Of StressData)) As Boolean Try ' TODO: Copy the data for the applied stresses to the first column in the worksheet. ' This should be a list of values: 100, 200, 300, ..., 1500 ' Each set of data in the list in the graphData object uses the same set of stresses. Dim rowNum As Integer = 1 Dim colNum As Integer = 1 excelWS.Cells(rowNum, colNum) = "Applied Stress" For Each appliedStress As Short In excelData(0).Data.Keys rowNum += 1 excelWS.Cells(rowNum, colNum) = appliedStress Next ... End Try End Function

7.

Locate the comment TODO: Give each column a header that specifies the temperature. This comment is located in a For Each loop that iterates over each item in the excelData collection. These items are StressData objects, and each StressData object contains the data for the tests for a given temperature. When complete, the code in this For Each loop will copy the data for each StressData object to a new column in the excelWS worksheet object, and each column will have a header that specifies the temperature.

8.

After the comment, add code that performs the following tasks. a. b. Increment the colNum variable so that it refers to the next column in the worksheet. Set the rowNum variable to 1.

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

L15-15

c.

Retrieve the temperature from the deflectionData StressData object, format it as a string with the letter "K" appended to the end (for Kelvin), and store this string in the cell at location rowNum, colNum in the excelWS worksheet object.

Your code should resemble the following code example.


For Each deflectionData As StressData In excelData ' TODO: Give each column a header that specifies the temperature. colNum += 1 rowNum = 1 excelWS.Cells(rowNum, colNum) = String.Format("{0}K", deflectionData.Temperature) ... Next

9.

Locate the comment TODO: Only copy the deflection value if it is not null. This comment is located in a nested For Each loop that iterates over each value in a StressData object. Remember that not all stresses have a deflection value. Where this occurs, the data in the StressData object is null. The If statement detects whether the current deflection value is null.

10. After the comment, in the body of the If statement, add code that performs the following tasks: a. b. Increment the rowNum variable so that it refers to the next row in the worksheet. Copy the value of the deflection variable (that contains the deflection data) into the cell at location rowNum, colNum in the excelWS worksheet object.

Your code should resemble the following code example.


' Copy the deflection data to this column in the worksheet For Each deflection As Short? In deflectionData.Data.Values ' TODO: Only copy the deflection value if it is not null If Not deflection Is Nothing Then rowNum += 1 excelWS.Cells(rowNum, colNum) = deflection End If Next

11. Locate the comment TODO: Specify the range of cells in the spreadsheet containing the data in the dataRange variable. This comment is located after all the For Each loops have completed and all the data has been copied to the worksheet. 12. After the comment, add a statement that populates the dataRange variable with information about the set of cells that have been filled.
Hint: You can determine the boundaries of the filled area of an Office Excel worksheet by querying the UsedRange property. This property returns an Excel.Range object. Your code should resemble the following code example.
... ' TODO: Specify the range of cells in the spreadsheet containing the data in the dataRange variable. dataRange = excelWS.UsedRange ...

13. Build the solution and correct any errors.

L15-16

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

On the Build menu, click Build Solution.

Task 5: Generate an Office Excel graph.


1. Locate the generateExcelChart method. The GenerateGraphButton_Click method will call this method after the data has been copied to the Office Excel worksheet. It takes three parameters: 2. A String object named fileName. When the graph has been created, the method will save the Office Excel workbook to a file by using this file name. An Excel.Workbook object named excelWB. This is a reference to the Office Excel workbook containing the Office Excel worksheet that contains the data to use for the graph. An Excel.Range object named dataRange. This range specifies the location in the Office Excel worksheet that contains the data to use for the graph.

In the generateExcelChart method, after the comment TODO: Generate a line graph based on the data in the dataRange range, add code that performs the following tasks: a. Add a new chart object to the Office Excel workbook, and store a reference to this chart object in an Excel.Chart variable named excelChart.

Hint: You can create a new chart by using the Add method of the Charts property of an Office Excel workbook object. The Add method takes no parameters and returns a reference to the chart object. b. Call the ChartWizard method of the chart object to generate the chart. The following table lists the parameters that you should specify. Value "Applied Stress (kN) versus Deflection (mm)" dataRange Excel.XlChartType.xlLine Excel.XlRowCol.xlColumns 1 1 "Deflection" "Applied Stress"

Parameter name Title Source Gallery PlotBy CategoryLabels SeriesLabels ValueTitle CategoryTitle

Your code should resemble the following code example.


Private Shared Sub generateExcelChart(ByVal fileName As String, ByVal excelWB As Excel.Workbook, ByVal dataRange As Excel.Range) ' TODO: Generate a line graph based on the data in the dataRange range. Dim excelChart As Excel.Chart = excelWB.Charts.Add() excelChart.ChartWizard( Title:="Applied Stress (kN) versus Deflection (mm)", Source:=dataRange, Gallery:=Excel.XlChartType.xlLine, PlotBy:=Excel.XlRowCol.xlColumns, CategoryLabels:=1,

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

L15-17

End Sub

SeriesLabels:=1, ValueTitle:="Deflection", CategoryTitle:="Applied Stress") ...

3.

After the comment TODO: Save the Excel workbook, add a statement that saves the Office Excel workbook by using the value in the fileName parameter.

Hint: Use the SaveAs method of the Office Excel Workbook object to save a workbook. This method takes a parameter named Filename that specifies the name of the file to use. Your code should resemble the following code example.
Private Shared Sub generateExcelChart(ByVal fileName As String, ByVal excelWB As Excel.Workbook, ByVal dataRange As Excel.Range) ... ' TODO: Save the Excel workbook excelWB.SaveAs(Filename:=fileName) End Sub

4.

Build the solution and correct any errors. On the Build menu, click Build Solution.

Task 6: Complete the test harness.


1. 2. Return to the GenerateGraphButton_Click method. After the comment TODO: If the user specifies a valid file name, start Excel and create a new workbook and worksheet to hold the data, add code to perform the following tasks: a. b. c. d. e. Create a new Excel.Application object named excelApp. Make the application visible on the user's desktop by setting the Visible property of the excelApp object to True. (By default, Office Excel will run in the background.) Set the AlertBeforeOverwriting property of the excelApp object to False. This ensures that the SaveAs method always saves the workbook. Set the DisplayAlerts property of the excelApp object to False. Create a new workbook, and store a reference to this workbook in an Excel.Workbook variable named excelWB.

Hint: You can create a new workbook by using the Add method of the Workbooks property of an Excel.Application object. This method takes no parameters and returns a reference to the new workbook. f. Create a variable named excelWS of type Excel.Worksheet and set it as the active worksheet in the new workbook.

Hint: You can obtain a reference to the active worksheet in an Office Excel workbook by using the ActiveSheet property. You code should resemble the following code example.
If saveDialog.ShowDialog().Value Then

L15-18

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

' TODO: If the user specifies a valid file name, start Excel ' and create a new workbook and worksheet to hold the data excelApp = New Excel.Application() excelApp.Visible = True excelApp.AlertBeforeOverwriting = False excelApp.DisplayAlerts = False Dim excelWB As Excel.Workbook = excelApp.Workbooks.Add() Dim excelWS As Excel.Worksheet = excelWB.ActiveSheet ... End If

3.

After the comment TODO: Copy the data from the graphData variable to the new worksheet and generate a graph, add code to perform the following tasks: a. b. Create an Excel.Range object named dataRange and initialize it to null. Call the transferDataToExcelSheet method, and pass the excelWS object the dataRange object and the graphData variable as parameters. Note that the dataRange object should be an output parameter. If the value that the transferDataToExcelSheet method returns is True, call the generateExcelChart method. Pass the FileName property of the saveDialog object, the excelWB object, and the dataRange object as parameters.

c.

Your code should resemble the following code example.


If saveDialog.ShowDialog().Value Then ... ' TODO: Copy the data from the graphData variable to the new Worksheet and generate a graph ' The dataRange variable specifies the cells on the worksheet that hold the data Dim dataRange As Excel.Range = Nothing If transferDataToExcelSheet(excelWS, dataRange, Me.graphData) Then generateExcelChart(saveDialog.FileName, excelWB, dataRange) End If End If

4.

At the end of the GenerateGraphButton_Click method, in the finally block, after the comment TODO: Close Excel and release any resources, add code to check whether the excelApp variable is null; if it is not, close the Office Excel application.

Hint: Use the Quit method of an Excel.Application object to close Office Excel. Your code should resemble the following code example.
Finally ' TODO: Close Excel and release any resources If Not excelApp Is Nothing Then excelApp.Quit() End If End Try

5.

Build the solution and correct any errors. On the Build menu, click Build Solution.

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

L15-19

Task 7: Test the application.


1. Start the application in Debug mode. 2. 3. 4. 5. On the Debug menu, click Start Debugging.

In the Graphing Data window, click Get Data. In the Graph Data dialog box, click the 298K.txt file, and then click Open. In the Graphing Data window, in the tree view, expand the Temperature: 298K node. Verify that the data has been correctly loaded. Repeat steps 2, 3, and 4 and load the data in the 318K.txt and 338K.txt files. Verify that the tree view lists the data from all three files.

Note: The displayData method displays the value 1 for any missing deflection data. 6. 7. Click Graph. In the Graph Data dialog box, accept the default file name, StressData.xlsx, for the name of the Office Excel workbook to be generated, and then click Save. You will see Office Excel start to run and your data copied across to a new worksheet. You will also briefly see the graph that is generated before the workbook is saved and Office Excel closes. 8. 9. Using Windows Explorer, browse to the D:\Labfiles\Lab15 folder. Verify that this folder contains the Office Excel workbook StressData.xlsx. Double-click the StressData.xlsx file to start Office Excel and open the workbook. The workbook should contain a chart that displays the stress test results by using the data and settings that you specified. 10. In Office Excel, click the Sheet1 tab. This is the worksheet that your code generated. The first column contains the applied stress values, and the remaining three columns contain the deflections recorded at each of the three temperatures. 11. Close Office Excel. 12. Close the Graphing Data window. 13. Close Visual Studio. On the File menu, click Exit.

L15-20

Lab: Integrating Visual Basic Code with Dynamic Languages and COM Components

Potrebbero piacerti anche