New information has been added to this article since publication.
Refer to the Editor's Update below.


Managing the Lifetime of Remote .NET Objects with Leasing and Sponsorship

Juval Lowy

Code download available at:LeaseManager.exe(166 KB)

This article assumes you're familiar with .NET Remoting and C#

Level of Difficulty123


Leasing and sponsorship is the solution for managing the lifecycle of a remote object in .NET. Each object has a lease that prevents the local garbage collector from destroying it, and most distributed applications rely upon leasing. There are several ways in which objects and clients can extend the lease, including dedicated sponsor objects. In this article, the author explains leasing, shows how to configure it, and how it relates to the various remoting activation models. He then discusses design guidelines and options, along with their impact on throughput and performance. Additionally, he introduces a helper class used to automate the management of lease sponsors.


Lease Properties
Configuring and Renewing Leases
Leasing Specifics

The Microsoft® .NET Framework manages the lifecycle of objects using garbage collection. It keeps track of memory allocation and objects accessed by all the clients in an AppDomain. When an object becomes unreachable by its clients, it will eventually be collected by the garbage collector. If the objects are in the same AppDomain, garbage collection functions quite well. In fact, even in the case of a client in one AppDomain accessing an object in a different AppDomain (but in the same process), garbage collection still works because in the same process all AppDomains share the same managed heap. In the case of a remote object accessed across processes and machines, the strategy breaks—the object may not have local clients at all. If garbage collection were to take place, the collector would not find a reference to the object and would deem it garbage, even though there may be remote clients on other machines (or even in a separate process on the same machine) that want to use the object.

This article focuses on leasing and sponsorship, which is a core piece of the .NET Remoting architecture designed to address this problem. I'll describe the leasing mechanism and interfaces, and show you its effect on the programming models, both on the client and server side.

The idea behind leasing is simple: each server object accessed by remote clients is associated with a lease object, so named because it literally gives the server object a lease on life. When a client creates a remote server object, the .NET Remoting Framework creates a lease object and associates it with the server object. A special entity in .NET Remoting called the lease manager keeps track of the server objects and their lease objects. Each lease object has an initial lease time. The clock starts ticking as soon as the first reference to the server object is marshaled across the AppDomain boundary, and the lease time is decremented as time goes by. As long as the lease has not expired, the common language runtime (CLR) considers the server object to be in use by its clients. The lease manager keeps a reference on the server object, which prevents it from being collected when a garbage collection is triggered. When the lease expires, the CLR assumes that the server object has no remote clients. It then disconnects the server object from the remoting infrastructure. The server object becomes a candidate for garbage collection and is eventually destroyed.

After the object is disconnected, any attempt by a client to access it will result in an exception of type RemotingException, letting the client know the object was disconnected. This may appear strange at first because the object may still be alive. The .NET Framework behaves this way because otherwise the client's interaction with the remote object will be nondeterministic. In other words, if .NET were to allow remote clients to access objects when their lease is up, it might work some of the time, but would fail in cases when garbage collection has already occurred.

Figure 1 Leasing Architecture

Figure 1** Leasing Architecture **

That said, it would be incorrect to disconnect the object every time a lease expires. What if there are still some remote clients who would like to keep the server object alive? The smart thing to do is to contact such clients and ask them if they want to extend or sponsor the lease. Clients who want to be contacted when the lease expires need to provide a special sponsor object. The .NET Framework will contact the sponsor object so it can extend the lease or refuse to do so. A server object can have many sponsors associated with its lease. The lease manager keeps a list of all the sponsors. When a lease expires, the lease manager starts traversing the sponsor list looking for one that's willing to extend the lease. If such a sponsor is found, the lease manager extends the lease. Otherwise, the lease manager disconnects the server object. This architecture is shown in Figure 1.

Lease Properties

Every lease has a number of properties associated with it that control the manner in which the lease manager interacts with the remote object's lease. The .NET Remoting Framework assigns global default values to these properties, but you can instruct it to use other values. You can even override some of the default lease properties for individual objects. One property, the expired time, reflects the time that has expired since the beginning of the lease. Another property, the initial lease time, is initially set to five minutes, but you can override this default with your own value. The .NET Remoting Framework could have simply disconnected the object when the expired time is equal to the initial lease time, but what should it do if the clients continue to call the object? This would clearly indicate that the object is needed. If the lease is about to expire, the .NET Remoting Framework will automatically extend the lease on every call by the value set in the RenewOnCallTime property. The default call time renewal is two minutes. The value of the current lease time (the time the object has to live unless the lease is extended) is a product of the lease time (the initial lease time for the first time) and renew on call time, according to the formula shown in the following line of code:

current lease time = MAX(lease time - expired time,renew on call time)

The renew on call time will have an effect only if it is greater than the lease time minus the expired time. In that case, the expired time property is reset, and the lease time is set to the renew on call time. The result is that even if an object is very busy, its lease time does not grow in proportion to the amount of traffic it has. Even if the object has a temporary spike in its load, its lease will expire after some quiet time.

There are two properties pertaining to the lease manager itself. The first, the lease manager's poll time, governs the rate at which the lease manager polls the leases. The default poll time is 10 seconds. The other property, SponsorshipTimeout, controls how long the lease manager should wait for a reply from a sponsor that might reside on a remote machine (the default is two minutes). This property is important for handling network failures or sponsor machine unavailability. If the lease manager tries to reach a sponsor and it does not reply within the specified sponsorship timeout, the lease manager removes that sponsor from the list associated with the lease.

If you do not like the default values of the various lease and lease manager properties, you can provide your own values either programmatically or administratively using the application configuration file. To configure global defaults programmatically, use the static properties of the LifetimeServices class defined in the System.Runtime.Remoting.Lifetime namespace:

public sealed class LifetimeServices { public static TimeSpan LeaseManagerPollTime { get; set; } public static TimeSpan LeaseTime { get; set; } public static TimeSpan RenewOnCallTime { get; set; } public static TimeSpan SponsorshipTimeout { get; set; } }

You typically use these properties in the Main method of your host (the application assembly hosting the remote objects) as shown in the following code snippet:

static void Main() { LifetimeServices.LeaseTime = TimeSpan.FromMinutes(10); LifetimeServices.RenewOnCallTime = TimeSpan.FromMinutes(15); /* Register types or load configuration file */ }

Note that you must set the global leasing defaults before you register types for remote access (programmatically or using the configuration file) because immediately after registration the host may start servicing remote calls, and these calls will not be using the new defaults. You can also provide these new values in the host config file using the <lifetime> element, as shown in Figure 2.

Figure 2 New Global Defaults

<configuration> <system.runtime.remoting> <application> <lifetime leaseTime = "10M" sponsorshipTimeOut = "1M" renewOnCallTime = "15M" LeaseManagePollTime = "8s" /> </application> </system.runtime.remoting> </configuration>

Configuring and Renewing Leases

Every lease object implements the ILease interface, defined in the System.Runtime.Remoting.Lifetime namespace (see Figure 3). ILease allows you to control and configure the lease properties for an individual object and manage sponsors for that lease. Both the object and its clients can obtain the ILease interface. An individual lease can be in one of a number of states, most important of which are initial, active, or expired. You can obtain the state of the lease by accessing the CurrentState read-only property of the ILease interface. CurrentState is of the enum type LeaseState:

public enum LeaseState { Active, Expired, Initial, Null, Renewing }

Figure 3 ILease Interface

public interface ILease { TimeSpan CurrentLeaseTime {get;} LeaseState CurrentState {get;} TimeSpan InitialLeaseTime {get;set;} TimeSpan RenewOnCallTime {get;set;} TimeSpan SponsorshipTimeout {get;set;} void Register(ISponsor obj); void Register(ISponsor obj,TimeSpan renewalTime); TimeSpan Renew(TimeSpan renewalTime); void Unregister(ISponsor obj); }

A remote object can provide its own values to the lease's properties, giving the object control over its own lifetime. To accomplish this, override the InitializeLifetimeService method of MarshalByRefObject and return a lease object. InitializeLifetimeService is called by the .NET Remoting Framework immediately after the remote object's constructor, but before a reference to the object is marshaled back to the client. InitializeLifetimeService is never called if the object is created by a local client. Although you can return from InitializeLifetimeService any object that implements the ILease interface, in practice you need to obtain the lease already associated with your object and modify its properties. You do that by calling your base class's InitializeLifetimeService and modifying the lease properties, as shown in Figure 4. You can only set the lease properties if the lease is in the LeaseState.Initial state, as shown in the figure.

Figure 4 Providing New Lease Properties for an Object

public class MyServer : MarshalByRefObject { public override object InitializeLifetimeService() { ILease lease = (ILease)base.InitializeLifetimeService(); Debug.Assert(lease.CurrentState == LeaseState.Initial); //Set lease properties lease.InitialLeaseTime = TimeSpan.FromMinutes(30); lease.RenewOnCallTime = TimeSpan.FromMinutes(10); lease.SponsorshipTimeout = TimeSpan.FromMinutes(2); return lease; } }

Both the object and its clients can extend the lease explicitly by obtaining the object's lease and calling the ILease.Renew method to provide a new lease time. Renewing a lease explicitly will affect the current lease time according to this formula:

current lease time = MAX(lease time - expired time, renewal time)

This means that the renewal time will have an effect only if its time is greater than the lease time minus the expired time. In that case, the expired time is reset, and the lease time becomes the renewal time. Consequently, if different clients all try to renew a lease explicitly, the lease will not grow to the value of their combined renewal sum to make sure the object only remains connected when clients require it. Both the client and the object obtain the lease associated with the object using the static method GetLifetimeService of the RemotingServices class:

public static object GetLifetimeService(MarshalByRefObject obj);

You can only renew a lease if it is in the LeaseState.Active state. For example, here is how a client would renew a lease:

MyClass obj; obj = new MyClass(); ILease lease = (ILease)RemotingServices.GetLifetimeService(obj); Debug.Assert(lease.CurrentState == LeaseState.Active); lease.Renew(TimeSpan.FromMinutes(30));

If the object wants to renew its own lease, it can simply call GetLifetimeService, providing itself as the parameter:

public class MyServer : MarshalByRefObject { public void SomeMethod() { ILease lease = (ILease)RemotingServices.GetLifetimeService(this); Debug.Assert(lease.CurrentState == LeaseState.Active); lease.Renew(TimeSpan.FromMinutes(30)); ••• } }


As mentioned already, a sponsor is a third party that the .NET Remoting Framework consults when a lease expires, giving that party an opportunity to renew the lease. The sponsor must implement the ISponsor interface, defined as:

public interface ISponsor { TimeSpan Renewal(ILease lease); }

The lease manager calls ISponsor's single method, Renewal, when the lease expires, asking for new lease time. To add a sponsor to a lease object, simply obtain the lease object using GetLifetimeService, then call the ILease Register method, as shown in Figure 5. The client can detach a particular sponsor from the lease by calling the ILease Unregister method.

Figure 5 Implementing and Registering a Sponsor

public class MySponsor : MarshalByRefObject,ISponsor { public TimeSpan Renewal(ILease lease) { Debug.Assert(lease.CurrentState == LeaseState.Active); //Renew lease by 5 minutes return TimeSpan.FromMinutes(5); } } ISponsor sponsor = new MySponsor(); MyClass obj = new MyClass(); //Register the sponsor ILease lease = (ILease)RemotingServices.GetLifetimeService(obj); lease.Register(sponsor);

If the sponsor does not want to renew the lease, it can return TimeSpan.Zero:

public TimeSpan Renewal(ILease lease) { Debug.Assert(lease.CurrentState == LeaseState.Active); //Refuse to renew lease: return TimeSpan.Zero; }

However, it would probably make better sense to unregister the sponsor instead.

Because the sponsor is called across an AppDomain boundary, the sponsor must be a remotable object, meaning it must be marshaled either by value or by reference. If you derive the sponsor from MarshalByRefObject, the sponsor will reside on the client's side, and it can base its decision of the renewal time on client-side events or properties that it is monitoring. That raises an interesting question: if the lease keeps the remote server object alive, and if the sponsor keeps the lease alive, who keeps the sponsor alive? The answer is that somebody on the client side must keep a reference on the sponsor, typically as a class member variable. Doing so also allows the client to unregister the sponsor when the client shuts down. The client can also unregister the sponsor in its implementation of IDisposable.Dispose. Unregistering a sponsor improves overall performance because the lease manager will not spend time trying to reach a sponsor that is not available.

If you only mark the sponsor as serializable, when you register the sponsor it will be marshaled by value to the host's side, where the sponsor will reside. This will eliminate the marshaling overhead of contacting the sponsor, but it will also disconnect it from the client. A marshaled-by-value sponsor can only base its decision on information available on the host's side.

When the sponsor is a marshaled-by-reference object, the client application must register a port with its channels to allow the remote lease manager to call back to the sponsor. In general, the client does not care which port is used. The client can instruct the .NET Remoting Framework to automatically select an available port by registering port number 0. The channel, port number, and sponsor object location are captured when the reference to the sponsor object is marshaled to the remote host.

Because the sponsor is provided to the host in the form of a remote callback object, the host must set its channels-type filtering level to full. Note that sponsoring will not work with the default-type filtering level of low. This is a tightened security requirement that was introduced in the .NET Framework 1.1. The rationale is that callback objects (such as a sponsor) leave the host inherently vulnerable to attacks that lure it to perform malicious operations unknowingly by calling back to a malicious remote object. Setting the type filtering to full does not address the problem. What it does is make it a conscious decision to allow callbacks if you do not perceive the threat to be real. You can set the filtering level both programmatically and administratively in the configuration files, on both the host and the client side. You need to set the type-filtering level explicitly for each channel and for each formatter used with that channel. For example, the required entries in the host configuration file when using the TCP channel on port 8005 are shown in the following code:

<channel ref="tcp" port="8005"> <serverProviders> <formatter ref="soap" typeFilterLevel="Full"/> <formatter ref="binary" typeFilterLevel="Full"/> </serverProviders> </channel>

These are the matching entries in the client configuration file:

<channel ref="tcp" port="0"> <serverProviders> <formatter ref="soap" typeFilterLevel="Full"/> <formatter ref="binary" typeFilterLevel="Full"/> </serverProviders> </channel>

Note the use of port 0 on the client side to automatically select any available port for the callback to the sponsor.

Leasing Specifics

A single lease can have multiple sponsors, and multiple clients can all share the same sponsor. In addition, a single sponsor can be registered with multiple remote leases. Typically, a client application will have one sponsor for all of its remote objects. As a result, a remote object will typically have as many sponsors to its lease as it has distinct clients. When the client application shuts down, it will unregister the sponsor from all the remote leases it sponsors.

Server-activated single-call objects don't need leasing because such objects are disconnected immediately after each call. This is not the case for both client-activated objects and server-activated singleton objects.

The singleton design pattern semantics mandate that the singleton object lives forever once it is created. This can't happen if its default lease time is five minutes. If no client accesses a singleton object for more than five minutes after it is created, the .NET Remoting Framework will deactivate the singleton. Future calls from the clients will be silently routed to a new singleton object in violation of the pattern's semantics. Fortunately, infinite lease time is supported. When you design a singleton object, override InitializeLifetimeService and return a null object as the new lease, indicating that this lease never expires:

public class MySingleton : MarshalByRefObject { public override object InitializeLifetimeService() { return null; } }

Returning an infinite lease relieves the pain of managing global lease defaults or dealing with sponsors. In fact, you can have any object activation mode use an infinite lease, but it makes sense only in the case of a singleton.

The case of the client-activated object is the one affected most by the leasing mechanism. When using a client-activated model, every client that creates a new object gets a dedicated server object. That object's lifetime is governed by a lease, and the object will be disconnected from the clients once its lease has expired. The only safe way to manage client-activated objects is to use sponsors. All other options, such as global lease properties or configuring individual objects' leases, are speculative at best. Ultimately, only the client knows when it no longer needs the object. The sponsor should set the renewal to a value that balances network traffic and load on the lease manager, but which is also granular enough to manage the resources that the object may hold.

If the sponsor provides too short a renewal period, it will cause too many queries from the lease manager, resulting in unnecessary traffic. If the sponsored lease is too long, the lease manager will still keep the remote object alive longer than the client needs it. Every case is unique, and when throughput and performance are a concern, you will have to investigate and profile the system with various sponsorship renewal times.

I can offer the following rule of thumb: the sponsorship time should be the same as the initial lease time. The reason is that if the initial lease time is appropriate for your case, does not generate too much traffic, and is not too coarse, then it is probably going to be adequate for the sponsors on subsequent lease extension requests. Figure 6 shows a client using a client-activated object whose lifetime is controlled by a sponsor. In fact, you can even use the code in Figure 6 as a template or a starting point for your remote client-activated objects.

Figure 6 Sponsoring a Client-activated Object

////// RemoteServerHost.exe.config : the host configuration file //////// <?xml version="1.0"?> <configuration> <system.runtime.remoting> <application> <service> <activated type="RemoteServer.MyCAO,ServerAssembly"/> </service> <channels> <channel ref="tcp" port="8005"> <serverProviders> <formatter ref="soap" typeFilterLevel="Full"/> <formatter ref="binary" typeFilterLevel="Full"/> </serverProviders> </channel> </channels> </application> </system.runtime.remoting> </configuration> //////// RemoteServerHost.exe : the host application assembly ////////// public class ServerHostDialog : Form { public ServerHostDialog() { InitializeComponent(); } void InitializeComponent() {...} static void Main() { RemotingConfiguration.Configure("RemoteServerHost.exe.config"); Application.Run(new ServerHostDialog()); } } ////////////// ServerAssembly class library //////////////////////////// namespace RemoteServer { public class MyCAO : MarshalByRefObject { public void Count() { m_Counter++; string appName = AppDomain.CurrentDomain.FriendlyName; MessageBox.Show("Counter value is " + m_Counter.ToString(),appName); } int m_Counter = 0; } } ///////// Client.exe.config: the client configuration file ////////////// <?xml version="1.0"?> <configuration> <system.runtime.remoting> <application> <client url="tcp://localhost:8005"> <activated type="RemoteServer.MyCAO,ServerAssembly"/> </client> <channels> <channel ref="tcp" port="0"> <serverProviders> <formatter ref="soap" typeFilterLevel="Full"/> <formatter ref="binary" typeFilterLevel="Full"/> </serverProviders> </channel> </channels> </application> </system.runtime.remoting> </configuration> //////////////////// Client EXE assembly //////////////////////////////// using System.Runtime.Remoting.Lifetime; public class MySponsor : MarshalByRefObject,ISponsor { public TimeSpan Renewal(ILease lease) { Debug.Assert(lease.CurrentState == LeaseState.Active); return lease.InitialLeaseTime; } } using RemoteServer; public class ClientForm : Form { Button m_CallButton; ISponsor m_Sponsor; MyCAO m_Obj; public ClientForm() { InitializeComponent(); m_Sponsor = new MySponsor(); m_Obj = new MyCAO(); //Register the sponsor ILease lease = (ILease) RemotingServices.GetLifetimeService(m_Obj); lease.Register(m_Sponsor); } void InitializeComponent() {...} static void Main() { RemotingConfiguration.Configure("Client.exe.config"); Application.Run(new ClientForm()); } //Called when the button is clicked void OnCall(object sender,EventArgs e) { m_Obj.Count(); } //Called when the form is closed void OnClosed(object sender,EventArgs e) { //Unegister the sponsor ILease lease = (ILease) RemotingServices.GetLifetimeService(m_Obj); lease.Unregister(m_Sponsor); } }

The source code accompanying this article contains Figure 6 in the Leasing solution (see the link at the top of this article). The solution has three projects: the RemoteServerHost application assembly, the ServerAssembly class library, and the Client application assembly. The host is a do-nothing Windows® Forms application implemented by adding a single line in Main that loads the configuration file. The host configuration file registers the type MyCAO as a client-activated object and sets type filtering on the TCP channel to full to allow calling back into the sponsor. The ServerAssembly class library contains the MyCAO definition, so that both the client and the host could access its metadata. The class MyCAO provides a single method called Count, which displays the value of a counter. The Client assembly contains the MySponsor class because there is no need to put the sponsor in a class library. All the host needs is the metadata for the ISponsor interface, which is part of the .NET Framework class library already.

The MySponsor implementation of Renewal simply extends the lease by the initial lease time. The client configuration file designates the MyCAO type as a remote client-activated object, and it registers port 0 with the channel as well as setting type filtering to full to allow calling the sponsor. The client's class (ClientForm) has as member variables both the remote client-activated object and the sponsor. In its constructor, the client creates new instances of the server and the sponsor and registers the sponsor with the lease of the client-activated object. The client is a Windows Forms dialog with a single button. When clicked, the button calls the client-activated object. When the user closes the dialog, the client unregisters the sponsors in the OnClose event handler.

[Editor's Update - 6/9/2005: The code in Figure 7 has been updated.]

Figure 7 SponsorshipManager Helper Class

public class SponsorshipManager : MarshalByRefObject,ISponsor { IList m_LeaseList; public SponsorshipManager() { m_LeaseList = new ArrayList(); } ~SponsorshipManager() { UnregisterAll(); } public void OnExit(object sender,EventArgs e) { UnregisterAll(); } TimeSpan ISponsor.Renewal(ILease lease) { Debug.Assert(lease.CurrentState == LeaseState.Active); return lease.InitialLeaseTime; } public void Register(MarshalByRefObject obj) { ILease lease = (ILease) RemotingServices.GetLifetimeService(obj); Debug.Assert(lease.CurrentState == LeaseState.Active); lease.Register(this); lock(this) { m_LeaseList.Add(lease); } } public void Unregister(MarshalByRefObject obj) { ILease lease = (ILease) RemotingServices.GetLifetimeService(obj); Debug.Assert(lease.CurrentState == LeaseState.Active); lease.Unregister(this); lock(this) { m_LeaseList.Remove(lease); } } public void UnregisterAll() { lock(this) { while(m_LeaseList.Count > 0) { ILease lease = (ILease)m_LeaseList[m_LeaseList.Count-1]; lease.Unregister(this); m_LeaseList.RemoveAt(m_LeaseList.Count-1); } } } }

SponsorshipManager implements ISponsor by returning the initial lease time of the provided lease. In addition, it maintains a collection of all the leases it sponsors in a member variable called m_LeaseList. SponsorshipManager provides the Register method:

public void Register(MarshalByRefObject obj);

Register accepts a remote object, extracts the lease from it, registers SponsorshipManager as the sponsor, and adds the lease to the internal list. The Unregister method is defined as:

public void Unregister(MarshalByRefObject obj);

This method removes the lease associated with the specified object from the list and unregisters SponsorshipManager as a sponsor. SponsorshipManager also provides the UnregisterAll method:

public void UnregisterAll();

This unregisters SponsorshipManager from all of the lease objects in the list. Note that the list access is done in a thread-safe manner by locking the SponsorshipManager on every access. Thread safety is required because multiple clients on multiple threads could use the same SponsorshipManager object to manage their remote leases. However, the clients don't have to bother calling Unregister or UnregisterAll because SponsorshipManager provides an event-handling method called OnExit:

public void OnExit(object sender,EventArgs e);

OnExit calls UnregisterAll as a response to the client application shutting down. Figure 8 shows how to use SponsorshipManager within a Windows Forms client. The client in Figure 8 maintains a remote object of type MyCAO and a SponsorshipManager object as member variables. The client registers the remote object with SponsorshipManager and hooks up the application's OnExit event with the SponsorshipManager OnExit event-handling method. SponsorshipManager does the rest. It registers itself as the sponsor and unregisters all the remote leases it sponsors automatically when the client shuts down.

Figure 8 Using SponsorshipManager

public class ClientForm : Form { SponsorshipManager m_SponsorshipManager; MyCAO m_Obj; //The remote CAO public ClientForm() { InitializeComponent(); m_SponsorshipManager = new SponsorshipManager(); m_Obj = new MyCAO(); m_SponsorshipManager.Register(m_Obj); Application.ApplicationExit += new EventHandler(m_SponsorshipManager.OnExit); } void InitializeComponent() {...} static void Main() { RemotingConfiguration.Configure("Client.exe.config"); Application.Run(new ClientForm()); } void OnCall(object sender,EventArgs e) { m_Obj.Count(); } }


In general, you cannot develop a distributed .NET Framework-based application without taking advantage of leasing and sponsorship. The mechanism is an integral part of your application architecture and choosing the appropriate leasing and sponsorship properties will have a significant impact on the performance, scalability, and throughput of your application—not to mention its behavior. I recommend that you follow a few rules when it comes to the leasing of your remote objects and clients. First of all, do not provide a lease for single-call objects. Second, provide a null lease for singleton objects, and finally, return the initial lease time for client-activated objects, or better yet, use the SponsorshipManager class. You may still have to profile your application's load and calling patterns before you decide the exact initial lease time, but helper classes such as SponsorshipManager can encapsulate the interaction with the leasing mechanism.

For related articles see:
.NET Remoting: Design and Develop Seamless Distributed Applications for the Common Language Runtime
Contexts in .NET: Decouple Components by Injecting Custom Services into Your Object's Interception Chain

For background information see:
Programming .NET Components by Juval Lowy (O'Reilly, 2003)
IDesign: .NET Design and Business Solutions

Juval Lowy is a software architect providing consultation and training on .NET Framework design and migration. He is also the Microsoft Regional Director for the Silicon Valley. This article contains excerpts from his latest book, Programming .NET Components (O'Reilly, 2003). Contact Juval at