BUY THIS BOOK
Add to Cart

Print Book $44.95


Add to Cart

Print+PDF $58.44

Add to Cart

PDF $35.99

Safari Books Online

What is this?

Add to UK Cart

Print Book £31.95

What is this?

Looking to Reprint or License this content?


Programming .NET Components
Programming .NET Components, Second Edition Design and Build .NET Applications Using Component-Oriented Programming By Juval Löwy
July 2005
Pages: 644

Cover | Table of Contents


Table of Contents

Chapter 1: Introducing Component-Oriented Programming
Over the last decade, component-oriented programming has established itself as the predominant software development methodology. The software industry is moving away from giant, monolithic, hard-to-maintain code bases. Practitioners have discovered that by breaking down a system into binary components, they can attain much greater reusability, extensibility, and maintainability. These benefits can, in turn, lead to faster time to market, more robust and highly scalable applications, and lower development and long-term maintenance costs. Consequently, it's no coincidence that component-oriented programming has caught on in a big way.
Several component technologies, such as DCOM, CORBA, and JavaBeans™ give programmers the means to implement component-oriented applications. However, each technology has its drawbacks; for example, DCOM is too difficult to master, and the Java™ Virtual Machine (JVM) doesn't support interoperation with other languages.
.NET is the latest entrant to the field, and as you will see later in this chapter and in the rest of this book, it addresses the requirements of component-oriented programming in a way that is both unique and vastly easier to use. These improvements are of little surprise, because the .NET architects were able to learn from both the mistakes and successes of previous technologies.
In this chapter, I'll define the basic terms of component-oriented programming and summarize its core principles and their corresponding benefits. These principles apply throughout the book, and I'll refer to them in later chapters when describing the motivations for particular .NET design patterns.
Component-oriented programming is different from object-oriented programming, although the two methodologies do have things in common. You could say that component-oriented programming sprouted from the well of object-oriented programming methodologies. Therefore, this chapter also contrasts component-oriented programming and object-oriented programming, while briefly discussing .NET as a component technology.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Basic Terminology
The term component is probably one of the most overloaded and therefore most confusing terms in modern software engineering, and the .NET documentation has its fair share of inconsistency in its handling of this concept. The confusion arises in deciding where to draw the line between a class that implements some logic, the physical entity that contains it (typically a dynamic link library, or DLL), and the associated logic used to deploy and use it, including type information, security policy, and versioning information (called an assembly in .NET). In this book, a component is a .NET class. For example, this is a .NET component:
    public class MyClass
    {
       public string GetMessage()
       {
          return "Hello";
       }
    }
Chapter 2 discusses DLLs and assemblies and explains the rationale behind physical and logical packaging. It also discusses why it is that every .NET class, unlike traditional object-oriented classes, is a binary component.
A component is responsible for exposing business logic to clients. A client is any entity that uses the component, although typically, clients are simply other classes. The client's code can be packaged in the same physical unit as the component, in the same logical unit but in a separate physical unit, or in separate physical and logical units altogether. The client code should not have to make any assumptions about such details. An object is an instance of a component, a definition that is similar to the classic object-oriented definition of an object as an instance of a class. The object is also sometimes referred to as the server, because the relationship between client and object is often called the client/server model. In this model, the client creates an object and accesses its functionality via a publicly available entry point, traditionally a public method but preferably an interface, as illustrated by Figure 1-1. Note that in the figure an object is an instance of a component; the "lollipop" denotes an interface.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Component-Oriented Versus Object-Oriented Programming
If every .NET class is a component, and if classes and components share so many qualities, then what is the difference between traditional object-oriented programming and component-oriented programming? In a nutshell, object-oriented programming focuses on the relationships between classes that are combined into one large binary executable, while component-oriented programming focuses on interchangeable code modules that work independently and don't require you to be familiar with their inner workings to use them.
The fundamental difference between the two methodologies is the way in which they view the final application. In the traditional object-oriented world, even though you may factor the business logic into many fine-grained classes, once those classes are compiled, the result is monolithic binary code. All the classes share the same physical deployment unit (typically an EXE), process, address space, security privileges, and so on. If multiple developers work on the same code base, they have to share source files. In such an application, a change made to one class can trigger a massive re-linking of the entire application and necessitate retesting and redeployment of all the other classes.
On the other hand, a component-oriented application comprises a collection of interacting binary application modules —that is, its components and the calls that bind them (see Figure 1-2).
Figure 1-2: A component-oriented application
A particular binary component may not do much on its own. Some may be general- purpose components, such as communication wrappers or file-access components. Others may be highly specialized and developed specifically for the application. An application implements and executes its required business logic by gluing together the functionality offered by the individual components. Component-enabling technologies such as COM, J2EE, CORBA, and .NET provide the "plumbing" or infrastructure needed to connect binary components in a seamless manner, and the main distinction between these technologies is the ease with which they allow you to connect those components.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Principles of Component-Oriented Programming
Component-oriented programming requires both systems that support the approach and programmers that adhere to its discipline and its core principles. However, it's often hard to tell the difference between a true principle and a mere feature of the component technology being used. As the supporting technologies become more powerful, no doubt software engineering will extend its understanding of what constitutes component-oriented programming and embrace new ideas, and the core principles will continue to evolve. The most important principles of component-oriented programming include:
  • Separation of interface and implementation
  • Binary compatibility
  • Language independence
  • Location transparency
  • Concurrency management
  • Version control
  • Component-based security
The following subsections discuss these seven important principles. As discussed in the next section, .NET enables complying with all of the core principles, but it does not necessarily enforces these principles.
The fundamental principle of component-oriented programming is that the basic unit in an application is a binary-compatible interface. The interface provides an abstract service definition between a client and the object. This principle contrasts with the object-oriented view of the world, which places the object, rather than its interface, at the center. An interface is a logical grouping of method definitions that acts as the contract between the client and the service provider. Each provider is free to provide its own interpretation of the interface—that is, its own implementation. The interface is implemented by a black-box binary component that completely encapsulates its interior. This principle is known as
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
.NET Adherence to Component Principles
One challenge facing the software industry today is the skill gap between what developers should know and what they do know. Even if you have formal training in computer science, you may lack effective component-oriented design skills, which are primarily acquired through experience. Today's aggressive deadlines, tight budgets, and a continuing shortage of developers precludes, for many, the opportunity to attend formal training sessions or to receive effective on-the-job training. Nowhere is the skill gap more apparent than among developers at companies who attempt to adhere to component development principles. In contrast, object-oriented concepts are easier to understand and apply, partly because they have been around much longer (and hence a larger number of developers are familiar with them) and partly because of the added degree of complexity involved with component development as compared to development of monolithic applications.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Developing .NET Components
A component technology is more than just a set of rules and guidelines for how to build components. A successful component technology must provide a development environment and tools that will allow you to rapidly develop components. .NET offers a superb development environment and semantics that are the product of years of observing the way developers use COM and the hurdles they face. .NET 2.0 also includes innovative solutions to problems faced by developers using previous versions. All .NET programming languages are component-oriented in their very nature, and the primary development environment (Visual Studio 2005) provides views, wizards, and tools that are oriented toward developing components. .NET shields you from the underlying raw operating services and instead provides operating-system-like services (such as filesystem access or threading) in a component-oriented manner. The services are factored into various components in a logical and consistent fashion, resulting in a uniform programming model. You will see numerous examples of these services throughout this book. The following subsections detail key factors that enable .NET to significantly simplify component development.
When you develop .NET components, there is no need to master a hard-to-learn component development framework such as the Active Template Library (ATL), which was used to develop COM components in C++. .NET takes care of all the underlying plumbing. In addition, to help you develop your business logic faster, .NET provides you with thousands of classes and types (from message boxes to security permissions), accessible through a common library available to all .NET languages. The base classes are easy to learn and apply, and you can use them as they are or derive from them to extend and specialize their behavior. You will see examples of how to use these base classes throughout this book.
When developing components, you can use attributes to declare their special runtime and other needs, rather than coding them. This is analogous to the way COM developers declare the threading model attribute of their components. .NET offers numerous attributes, allowing you to focus on the domain problem at hand. You can also define your own attributes or extend existing ones. This book makes extensive use of .NET attributes and declarative programming . Appendix C discusses reflection and custom attributes.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: .NET Component-Oriented Programming Essentials
Regardless of what you use .NET components for, you need to be familiar with the essentials of .NET as a component technology and deployment platform. This chapter introduces basic concepts such as the assembly, metadata, and the Common Language Runtime (CLR). You will see how to compose client and class library assemblies and how to consume a binary component in one assembly by a client in another. The chapter then discusses how .NET achieves binary compatibility, demonstrating how .NET supports this important component-oriented principle presented in the previous chapter. Although I use C# to demonstrate the key points in this chapter and elsewhere in this book, the discussion (unless explicitly stated otherwise) is always from a language-agnostic perspective. The information is applicable to every .NET language, so the focus is on the concept, not the syntax. If you are already familiar with .NET essentials both in .NET 1.1 and 2.0, feel free to skip this chapter and move on to Chapter 3.
The .NET CLR provides a common context within which all .NET components execute, regardless of the language in which they are written. The CLR manages every runtime aspect of the code, providing it with memory management, a secure environment to run in, and access to the underlying operating system services. Because the CLR manages these aspects of the code's behavior, code that targets the CLR is called managed code . The CLR provides adequate language interoperability, allowing a high degree of component interaction during development and at runtime. This is possible because the CLR is based on a strict type system to which all .NET languages must adhere—all constructs (such as classes, interfaces, structures, and primitive types) in every .NET language must compile to CLR-compatible types. This gain in language interoperability, however, comes at the expense of the ability to use languages and compilers that Windows and COM developers have been using for years. The problem is that pre-existing compilers produce code that doesn't target the CLR, doesn't comply with the CLR type system, and therefore can't be managed by the CLR.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Language Independence: The CLR
The .NET CLR provides a common context within which all .NET components execute, regardless of the language in which they are written. The CLR manages every runtime aspect of the code, providing it with memory management, a secure environment to run in, and access to the underlying operating system services. Because the CLR manages these aspects of the code's behavior, code that targets the CLR is called managed code . The CLR provides adequate language interoperability, allowing a high degree of component interaction during development and at runtime. This is possible because the CLR is based on a strict type system to which all .NET languages must adhere—all constructs (such as classes, interfaces, structures, and primitive types) in every .NET language must compile to CLR-compatible types. This gain in language interoperability, however, comes at the expense of the ability to use languages and compilers that Windows and COM developers have been using for years. The problem is that pre-existing compilers produce code that doesn't target the CLR, doesn't comply with the CLR type system, and therefore can't be managed by the CLR.
To program .NET components, you must use one of the .NET language compilers available with the .NET Framework and Visual Studio. The first release of Visual Studio (version 1.0, called Visual Studio .NET 2002) shipped with three new CLR-compliant languages: C#, Visual Basic .NET, and Managed C++. The second version (version 1.1, called Visual Studio .NET 2003) contained J# (Java for .NET). The third release of Visual Studio (version 2.0, called Visual Studio 2005) provided extensive language extensions, such as generics, supported by both C# 2005 and Visual Basic 2005. Third-party compiler vendors are also targeting the CLR, with more than 20 additional languages, from COBOL to Eiffel.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Packaging and Deployment: Assemblies
.NET assemblies were developed to try to improve the ways previous technologies packaged and deployed components. To make the most of .NET assemblies, it's best to first understand the rationale behind them. Understanding the "why" will make the "how" a lot easier.
Microsoft's first two attempts at component technologies (first, raw DLLs exporting functions, and then later, COM components) used raw executable files for storing binary code. In COM, component developers compiled their source code, usually into a DLL or sometimes into an EXE, and then installed these executables on the customer's machine. Higher-level abstractions or logical attributes shared by all the DLLs had to be managed manually by both the component vendor and the client-side administrator. For example, even though all DLLs in a component-oriented application should be installed and uninstalled as one logical operation, developers had to either write installation programs to repeat the same registration code for every DLL used, or copy them one by one. Most companies didn't invest enough time in developing robust installation programs and procedures, and this in turn resulted in orphaned DLLs bloating the clients' machines after uninstallation. Even worse, after a new version was installed, the application might still try to use the older versions of the DLLs.
Another attribute that should have applied logically to all DLLs that were part of the same application was a version number. Imagine a particular vendor providing a set of interacting components in two DLLs, both labeled version 1.0. When a new version (1.1) of these components became available, the vendor had to manually update the version number of both DLLs to 1.1. A change to the version number of one DLL didn't trigger an automatic change in the other DLL, even though logically both were part of the same deployment unit.
A third logical attribute typically associated with a set of DLLs from the same vendor was their security credentials—what the DLLs were allowed to access, what the DLLs were allowed to do share with other applications, and so on. The client application administrator needed to manage the way he trusted these components and had to repeat this process for all the DLLs, even though they shared the same security origin. Client-side developers and system administrators used clumsy tools such as DCOMCFG to manage these attributes in a fragile and error-prone manner.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Binary Compatibility
As explained in Chapter 1, one of the core principles of component-oriented programming is binary compatibility between client and server. Binary compatibility enables binary components because it enforces both sides to abide by a binary contract (typically, an interface). As long as newer versions of the server abide by the original contract between the two, the client isn't affected by changes made to the server. COM was the first component technology to offer true binary compatibility free of DLL Hell (described in the previous chapter), and many developers have come to equate COM's implementation of binary compatibility (and the resulting restrictions, such as immutable COM interfaces) with the principle itself. The .NET approach to binary compatibility is different from that of COM, though, and so are the implications of the programming model. To understand these implications and why they differ from COM's, this section first briefly describes COM's binary-compatibility implementation and then discusses .NET's way of supporting binary compatibility.
COM provides binary compatibility by using interface pointers and virtual tables. In COM, the client interacts with the object indirectly via an interface pointer. The interface pointer actually points to another pointer called the virtual table pointer. The virtual table pointer points to a table of function pointers. Each slot in the table points to the location where the corresponding interface's method code resides (see Figure 2-9).
Figure 2-9: COM binary compatibility
When the client uses the interface pointer to call a method (such as the second method of the interface), all the compiler builds into the client's code is an instruction to jump to the address pointed to by the second entry in the virtual table. In effect, this address is given as an offset from the table's starting point. At runtime, the loader patches this jump command to the actual address, because it already knows the table memory location. All the COM client records in its code is the offset from the virtual table start address. At runtime, any server that provides the same in-memory table layout (in which the table entries point to methods with exactly the same signatures) is considered binary-compatible. This is, by the way, exactly the definition of implementing a COM interface. This model yields the famous COM prime directive:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Interface-Based Programming
As explained in Chapter 1, separation of interface from implementation is a core principle of component-oriented programming. When you separate interface from implementation, the client is coded against an abstraction of a service (the interface), not a particular implementation of it (the object). As a result, changing an implementation detail on the server side (or even switching to a different service provider altogether) doesn't affect the client. This chapter starts by presenting .NET interfaces and describing what options are available to .NET developers when it comes to enforcing the separation of interface from implementation. It then addresses a set of practical issues involving the definition and use of interfaces, such as how to implement multiple interfaces and how to combine interfaces and class hierarchies. After a detailed look at generic interfaces, the chapter ends with a discussion of interface design and factoring guidelines.
In both C# and Visual Basic 2005, the reserved word interface defines a CLR reference type that can't have any implementation, can't be instantiated, and has only public members. Saying that an interface can't have implementation means that it's as if all the interface's methods and properties were abstract. Saying it can't be instantiated means the same as if the interface were an abstract class (or MustInherit in Visual Basic 2005). For example, this interface definition:
    public interface IMyInterface
    {
       void Method1();
       void Method2();
       void Method3();
    }
is almost equivalent to this class definition:
    public abstract class MyInterface
    {
       public abstract void Method1();
       public abstract void Method2();
       public abstract void Method3();
    }
In traditional object-oriented programming, you typically use an abstract class to define a service abstraction. The abstract class serves to define a set of signatures that multiple classes will implement after deriving from the abstract class. When different service providers share a common base class, they all become polymorphic with that service abstraction, and the client can potentially switch between providers with minimum changes. There are a few important differences between an abstract class and an interface:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Separating Interface from Implementation
In both C# and Visual Basic 2005, the reserved word interface defines a CLR reference type that can't have any implementation, can't be instantiated, and has only public members. Saying that an interface can't have implementation means that it's as if all the interface's methods and properties were abstract. Saying it can't be instantiated means the same as if the interface were an abstract class (or MustInherit in Visual Basic 2005). For example, this interface definition:
    public interface IMyInterface
    {
       void Method1();
       void Method2();
       void Method3();
    }
is almost equivalent to this class definition:
    public abstract class MyInterface
    {
       public abstract void Method1();
       public abstract void Method2();
       public abstract void Method3();
    }
In traditional object-oriented programming, you typically use an abstract class to define a service abstraction. The abstract class serves to define a set of signatures that multiple classes will implement after deriving from the abstract class. When different service providers share a common base class, they all become polymorphic with that service abstraction, and the client can potentially switch between providers with minimum changes. There are a few important differences between an abstract class and an interface:
  • An abstract class can still have implementation: it can have member variables or non-abstract methods or properties. An interface can't have implementation or member variables.
  • A .NET class can derive from only one base class, even if that base class is abstract. However, a .NET class can implement as many interfaces as required.
  • An abstract class can derive from any other class or from one or more interfaces. An interface can derive only from other interfaces.
  • An abstract class can have nonpublic (protected or private) methods and properties, even if they are all abstract. In an interface, by definition, all members are public.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Working with Interfaces
Now that you have learned the importance of using interfaces in your component-based application, it's time to examine a number of practical issues regarding working with interfaces and tying them to the rest of your application. Later in this chapter, you will also see the support Visual Studio 2005 offers component developers when it comes to adding implementation to your classes for predefined interfaces.
When you name a new interface type, you should prefix it with a capital I and capitalize the first letter of the domain term, as in IAccount, IController, ICalculator, and so on. Use the I prefix even if the domain term itself starts with an I (such as in IIDentity or IImage). .NET tries to do away with the old Windows and C++ Hungarian naming notations (that is, prefixing a variable name with its type), but the I prefix is a direct legacy from COM, and that tradition is maintained in .NET.
Interfaces are abstract types and, as such, can't be used directly. To use an interface, you need to cast into an interface reference an object that supports it. There are two types of casting—implicit and explicit—and which type you use has an impact on type safety.
Assigning a class instance to an interface variable directly is called an implicit cast, because the compiler is required to figure out which type to cast the class to:
    IMyInterface obj;
    obj = new MyClass();
    obj.Method1();
When you use implicit casts, the compiler enforces type safety. If the class MyClass doesn't implement the IMyInterface interface, the compiler refuses to generate the code and produces a compilation error. The compiler can do that because it can read the class's metadata and can tell in advance that the class doesn't derive from the interface. However, there are a number of cases where you cannot use implicit casting. In such cases, you can use explicit cast instead. Explicit casting means casting one type to another type:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Interfaces and Generics
Like classes or structures, interfaces too can be defined in terms of generic type parameters. Generic interfaces provide all the benefits of interface-based programming without compromising type safety, performance, or productivity. All of what you have seen so far with normal interfaces you can also do with generic interfaces. The main difference is that when deriving from a generic interface, you must provide a specific type parameter to use instead of the generic type parameter. For example, given this definition of the generic IList<T> interface:
    public interface IList<T>
    {
       void AddHead(T item);
       void RemoveHead(T item);
       void RemoveAll();
    }
you can implement the interface implicitly and substitute an integer for the generic type parameter:
    public class NumberList : IList<int>
    {
       public void AddHead(int item)
       {...}
       public void RemoveHead(int item)
       {...}
       public void RemoveAll()
       {...}
       //Rest of the implementation
    }
When the client uses IList<T>, it must choose an implementation of the interface with a specific type parameter:
    IList<int> list = new NumberList();
    list.AddHead(3);
Generic interfaces allow you to define an abstract service definition (the generic interface) once, yet use it on multiple components with multiple type parameters. For example, an integer-based list can implement the interface:
    public class NumberList : IList<int>
    {...}
And so can a string-based list:
    public class NameList : IList<string>
    {...}
Once a generic interface is bounded (i.e., once you've specified types for it) it is considered a distinct type. Consequently, two generic interface definitions with different generic type parameters are no longer polymorphic with each other. This means that a variable of the type IList<int> cannot be assigned to a variable or passed to a method that expects an IList<string>:
    void ProcessList(IList<strin; names)
    {...}
   
    IList<int> numbers = new NumberList();
    ProcessList(numbers);//Does not compile
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Designing and Factoring Interfaces
Syntax aside, how do you go about designing interfaces? How do you know which methods to allocate to which interface? How many members should each interface have? Answering these questions has little to do with .NET and a lot to do with abstract component-oriented analysis and design. An in-depth discussion of how to decompose a system into components and how to discover interface methods and properties is beyond the scope of this book. Nonetheless, this section offers a few pieces of advice to guide you in your interface-design effort.
An interface is a grouping of logically related methods and properties. What constitutes "logically related" is usually domain-specific. You can think of interfaces as different facets of the same entity. Once you have identified (after a requirements analysis) all the operations and properties the entity supports, you need to allocate them to interfaces. This is called interface factoring . When you factor an interface, always think in terms of reusable elements. In a component-oriented application, the basic unit of reuse is the interface. Would this particular interface factoring yield interfaces that other entities in the system can reuse? What facets of the entity can logically be factored out and used by other entities?
Suppose you wish to model a dog. The requirements are that the dog be able to bark and fetch and that it have a veterinary clinic registration number and a property for having received shots. You can define the IDog interface and have different kinds of dogs, such as Poodle and GermanShepherd, implement the IDog interface:
    public interface IDog
    {
       void  Fetch();
       void  Bark();
       long  VetClinicNumber{get;set;}
       bool  HasShots{get;set;}
    }
    public class Poo: IDog
    {...}
      
    public class GermanShepherd : IDog
    {...}
However, such a composition of the IDog interface isn't well-factored. Even though all the interface members are things a dog should support,
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Interfaces in Visual Studio 2005
Visual Studio 2005 has excellent support for implementing and refactoring interfaces. As a component developer, your classes will occasionally need to implement an interface defined by another party. Instead of copying and pasting the interface definition, or typing it in, you can use Visual Studio 2005 to generate a skeletal implementation of the interface. A skeletal implementation is a do-nothing implementation: all implanted methods or properties throw an exception of type Exception and do not contain any other code. A skeletal implementation is required to at least get the code compiled as a starting point for your implementation of an interface, and it prevents clients from consuming a half-baked implementation. To have Visual Studio 2005 generate a skeletal interface implementation, you first add the interface to the class derivation chain. When you finish typing the interface name (such as IMyInterface), Visual Studio 2005 marks a little underscore tag under the I of the interface name. If you hover over IMyInterface, Visual Studio 2005 pops up a smart tag with a tool tip, "Options to implement interface." If you click the down arrow of the smart tip you can select from two options in the menu, implementing the interface either implicitly or explicitly (see Figure 3-2).
Figure 3-2: Using Visual Studio 2005 to provide a skeletal interface implementation
Once you select an option, Visual Studio 2005 creates a skeletal implementation of the interface on your class and scopes it with a collapsible #region directive. For example, consider this interface definition:
    public interface IMyInterface
    {
       void Method1();
       int Method2(int number);
       string Method3();
    }
If you select explicit implementation, Visual Studio 2005 generates this skeletal implementation:
    public class MyClass : IMyInterface
    {
       #region IMyInterface Members
       void IMyInterface.Method1()
       {
          throw new Exception();
       }
       int IMyInterface.Method2(int number)
       {
          throw new Exception();
       }
       string IMyInterface.Method3()
       {
          throw new Exception();
       }
       #endregion
    }
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 4: Lifecycle Management
Traditionally, most defects in implementation that aren't business logic-specific can be traced back to memory management and object lifecycle issues. These defects include memory leaks, cyclic reference counts, the failure to release an object, the failure to free allocated memory, accessing already de-allocated objects, accessing not yet allocated memory or objects, and so on. Writing impeccable code is possible, but it takes years of experience, iron discipline, a mature development process, commitment to quality from management, and strict coding and development standards, such as code reviews and quality control. Most software organizations today lack most of these ingredients. To cope with this reality, .NET aims at simplifying component development to bridge the skill gap and increase the quality of the resulting code. .NET relieves developers of almost all the burden of memory allocation for objects, memory de-allocation, and object lifecycle management. This chapter describes the .NET solution for memory and object lifecycle management and its implications for the programming model, including the pitfalls and workarounds that component developers need to apply.
.NET components aren't allocated off the raw memory maintained by the underlying operating system. Instead, in each physical process that hosts .NET, the .NET runtime pre-allocates a special heap called the managed heap. This heap is used like traditional operating system heaps: to allocate memory for objects and data storage. Every time a .NET developer uses the new operator on a class:
    MyClass obj = new MyClass();
.NET allocates memory off the managed heap .
The managed heap is just a long strip of memory. .NET maintains a pointer to the next available address in the managed heap. When .NET is asked to create a new object, it allocates the required space for the object and advances the pointer, as you can see in Figure 4-1. (Figure 4-1 is adapted with permission from Figure 1 in "Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework," by Jeffrey Richter (
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The Managed Heap
.NET components aren't allocated off the raw memory maintained by the underlying operating system. Instead, in each physical process that hosts .NET, the .NET runtime pre-allocates a special heap called the managed heap. This heap is used like traditional operating system heaps: to allocate memory for objects and data storage. Every time a .NET developer uses the new operator on a class:
    MyClass obj = new MyClass();
.NET allocates memory off the managed heap .
The managed heap is just a long strip of memory. .NET maintains a pointer to the next available address in the managed heap. When .NET is asked to create a new object, it allocates the required space for the object and advances the pointer, as you can see in Figure 4-1. (Figure 4-1 is adapted with permission from Figure 1 in "Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework," by Jeffrey Richter (MSDN Magazine, November 2000.)
Figure 4-1: The managed heap
This allocation method is orders of magnitude faster than raw memory allocation. In unmanaged environments such as C++, objects are allocated off the native operating system heap. The operating system manages its memory by using a linked list of available blocks of memory. Each time the operating system has to allocate memory, it traverses that list looking for a big enough block. After a while, the memory can get fragmented, and consequently the list of available blocks gets very long. Memory fragmentation is a major source of performance problems because of the time it takes to traverse the list for allocation requests, combined with added memory page faults and disk access penalties.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Traditional Memory De-allocation Schemas
De-allocation of memory and the destruction of objects are also different in .NET, as compared with raw C++ or COM. In C++, the object destructor is called when a stack-based object goes out of scope:
    {//beginning of a C++ scope
        MyClass object;
        //use object;
    }//end of scope, C++ calls the object destructor
The object destructor is also called in C++ when the delete operator is used:
    //in C++:
    MyClass* pObject = new MyClass;
    //using pObject, then de-allocating it
    delete pObject;
COM uses reference counting, and it's up to the client to increment and decrement the counter associated with each object. Clients that share an object have to call AddRef() to increment the counter. New COM objects are created with a reference count of one. When a client is done with an object, it calls Release() to decrement the counter:
    //COM pseudo-code:
    IMyInterface* pObject = NULL;
    ::CoCreateInstance(CLSID_MyClass,IID_IMyInterface,&pObject);
    //using pObject, then releasing it
    pObject->Release();
When the reference count reaches zero, the object destroys itself:
    //COM implementation of IUnknown::Release()
    ULONG MyClass::Release()
    {
       //m_Counter is this class counter
       m_Counter--;
       if(m_Counter == 0)
       {
          delete this;
          return 0;
       }
       //Should return the counter:
       return m_Counter;
    }
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
.NET Garbage Collection
In .NET programming, exiting a scope doesn't destroy an object, and unlike COM, .NET doesn't use reference counting of objects. Instead, .NET has a sophisticated garbage-collection mechanism that detects when an object is no longer being used by clients and then destroys it. To do so, .NET must keep track of accessible paths to objects in the code. In the abstract, when the JIT compiler compiles the IL code, it updates a list of roots— top-level primordial application starting points, such as static variables and methods (Main, for example), but also internal .NET entities that should be kept alive as long as the application is running. Each root forms the topmost node in a tree-like graph. .NET keeps track of each new object it allocates off the managed heap and of the relationship between this object and its clients. Whenever an object is allocated, .NET updates its graph of objects and adds a reference in the graph to that object from the object that created it. Similarly, .NET updates the graph every time a client receives a reference to an object and when an object saves a reference to another object as a member variable. The JIT compiler also injects code to update the graphs each time the execution path enters or exits a scope.
The entity responsible for releasing unused memory is called the garbage collector . When garbage collection is triggered (usually when the managed heap is exhausted, but also when garbage collection is explicitly requested by the code), the garbage collector deems every object in the graphs as garbage. The garbage collector then recursively traverses each graph, going down from the roots, looking for reachable objects. Every time the garbage collector visits an object, it tags it as reachable. Because the graphs represent the relationships between clients and objects, when the garbage collector is done traversing the graphs, it knows which objects were reachable and which were not. Reachable objects should be kept alive. Unreachable objects are considered garbage, and therefore destroying them does no harm. This algorithm handles cyclic references between objects as well (i.e., where Object A references Object B, which references Object C, which references back to Object A). When the garbage collector reaches an object already marked as reachable, it doesn't continue to look for other objects reachable from that tagged object.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Object Finalization
.NET objects are never told when they become garbage; they are simply overwritten when the managed heap is compacted. This presents you with a problem: if the object holds expensive resources (files, connections, communication ports, data structures, synchronization handles, and so on), how can it dispose of and release these resources? To address this problem, .NET provides object finalization . If the object has specific cleanup to do, it should implement a method called Finalize(), defined as:
    protected void Finalize();
When the garbage collector decides that an object is garbage, it checks the object metadata. If the object implements the Finalize() method, the garbage collector doesn't destroy the object. Instead, the garbage collector marks the object as reachable (so it will not be overwritten by heap compaction), then moves the object from its original graph to a special queue called the finalization queue . This queue is essentially just another object graph, and the root of the queue keeps the object reachable. The garbage collector then proceeds with collecting the garbage and compacting the heap. Meanwhile, a separate thread iterates over all the objects in the finalization queue, calling the Finalize() method on each and letting the objects do their cleanup. After calling Finalize(), the garbage collector removes the object from the queue.
You can trigger garbage collection explicitly with the static method Collect() of the GC class, defined in the System namespace:
    public static class GC
    {
       public static void Collect();
       /* Other methods and members */
    }
However, I recommend avoiding explicit garbage collection of any kind. Garbage collection is an expensive operation, which involves scanning of object graphs, thread context switches, thread suspension and resumption, potential disk access, and extensive use of reflection to read object metadata. The reason to initiate garbage collection is often because you want to have certain objects'
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Deterministic Finalization
.NET tries to simplify the management of object lifecycles by relieving you of the need to explicitly de-allocate the memory occupied by their objects. However, simplifying the object lifecycle comes with potential penalties in terms of system scalability and throughput. If the object holds onto expensive resources such as files or database connections, those resources are released only when Finalize() (or the C# destructor) is called. This is done at an undetermined point in the future, usually when certain memory-exhaustion thresholds are met. In theory, releasing the expensive resources the object holds may never happen, thus severely hampering system scalability and throughput.
There are a few solutions to the problems arising from nondeterministic finalization. These solutions are called deterministic finalization, because they take place at a known, determined point in time. In all deterministic finalization techniques, the object has to be explicitly told by the client when it's no longer required. This section describes and contrasts these techniques.
In order for deterministic finalization to work, you must first implement methods on your object that allow the client to explicitly order cleanup of expensive resources the object holds. Use this pattern when the resources the object holds onto can be reallocated. If this is the case, the object should expose methods such as Open() and Close().
An object encapsulating a file is a good example. The client calls Close() on the object, allowing the object to release the file. If the client wants to access the file again, it calls Open(), without re-creating the object. The classic example of classes that implement this pattern are the database connection classes.
The main problem with using Close() is that it makes sharing the object between clients a lot more complex than COM's reference counting. The clients have to coordinate which one is responsible for calling
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 5: Versioning
As discussed in Chapter 1, a component technology must provide some sort of version-control support, which ensures that client applications have a deterministic way of always interacting with a compatible version of a server component. The component-versioning challenges you face are closely related to the component-sharing mode you choose. Private components (components that reside in a location private to the application using them) are far less exposed to versioning issues, because each application comes bundled with its own private set of compatible components—you have to explicitly intervene to cause incompatibilities. Shared components, on the other hand, can cause a lot of versioning headaches because they are stored in a globally known location and are used by multiple applications. Nonetheless, a mature component technology must allow multiple applications to share server components. A mature component technology should also allow different client applications to use different versions of the server components. Placing DLLs in global locations such as the system directory, as done in the past, proved fatal in the end, resulting in the devil's choice of either stifling innovation or suffering DLL Hell. Not surprisingly, one of the major goals set for the .NET platform was to simplify component deployment and version control. This chapter starts by presenting the principles behind .NET version control and assembly sharing, then explains how you can provide custom versioning policies for cases in which the .NET default isn't adequate. The chapter ends by describing how you should deal with versioning of .NET itself.
Every assembly has a version number. That number applies to all components (potentially across multiple modules) in the assembly. You typically specify the version number in the Visual Studio 2005 project settings, although you can also assign a version number during the link phase, using command-line utilities and switches or MSBuild.
To specify the version number using Visual Studio 2005, bring up the assembly properties, and open the Application tab. Click the Assembly Information button to display the Assembly Information dialog (see Figure 5-1).
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Assembly Version Number
Every assembly has a version number. That number applies to all components (potentially across multiple modules) in the assembly. You typically specify the version number in the Visual Studio 2005 project settings, although you can also assign a version number during the link phase, using command-line utilities and switches or MSBuild.
To specify the version number using Visual Studio 2005, bring up the assembly properties, and open the Application tab. Click the Assembly Information button to display the Assembly Information dialog (see Figure 5-1).
Figure 5-1: Specify the assembly version in the Assembly Information dialog
Next to Assembly Version are four text boxes used to specify the assembly version. The Assembly Information dialog is merely a visual editor for a set of assembly attributes. These attributes are stored in the project's AssemblyInfo.cs file. The attribute used for the assembly version is AssemblyVersion. For example, the AssemblyVersion value corresponding to the settings in is:
    [assembly: AssemblyVersion("1.2.3.4")]
The version number is recorded in the server assembly manifest. When a client developer adds a reference to the server assembly, the client assembly records in its manifest the name and the exact version of the server assembly against which it was compiled. If the client uses the class MyClass from version 1.2.3.4 of the assembly MyAssembly, the manifest of the client will record that the client requires version 1.2.3.4 of MyAssembly to operate and will contain this declaration:
    .assembly extern MyAssembly
    {
      .ver 1:2:3:4
    }
At runtime, .NET resolves the location of the requested assembly, and the client is guaranteed to get a compatible assembly. If a compatible assembly isn't found, an exception is thrown. The question is, what constitutes a compatible assembly? The rule of compatibility is straightforward: for a strongly named assembly (defined in the section "Strong Assembly Names"), a compatible assembly must have the exact same version number that the client's manifest requests. For a friendly named assembly, any assembly with the same friendly name is considered compatible.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Assembly Deployment Models
Content preview