Remoting Basics

Remoting begins with the class or classes you want to remote. A conventional class can be used only by clients running in the same application domain. A remotable class can be used by clients in other application domains, which can mean other application domains in the client’s process, application domains in other processes, or application domains on other machines. How hard is it to write a remotable class? Not hard at all. All you have to do is derive from System.MarshalByRefObject.

The following class can be instantiated only in a client’s own application domain:

public?class?LocalClass
{
??...
}

The following class can be instantiated in a client’s application domain or in a remote application domain:

public?class?RemotableClass?:?MarshalByRefObject
{
??...
}

When a client creates a remote instance of RemotableClass, the .NET Framework creates a proxy in the client’s application domain, as shown in Figure 15-1. The proxy looks and feels like the real object. Calls received by the proxy, however, are transmitted to the remote object through a channel connecting the two application domains. We say that an object served by a proxy has been marshaled by reference because the object isn’t copied to the client’s application domain; the client merely holds a reference to the object. That reference is the proxy.

Figure 15-1
Marshal-by-reference remoting architecture.

The second step in remoting an object is to have a server process register the remotable class so that it can be activated from another application domain. Depending on how the object is to be activated, the server registers the remotable class by calling one of two static System.Runtime.Remoting.RemotingConfiguration methods: RegisterActivatedServiceType or RegisterWellKnownServiceType. The following statement uses RegisterWellKnownServiceType to register RemotableClass for remote activation:

RemotingConfiguration.RegisterWellKnownServiceType?(
????typeof?(RemotableClass),???????//?Remotable?class
??? "RemoteObject",????????????????//?URI?of?remotable?class
????WellKnownObjectMode.SingleCall?//?Activation?mode
);

The first parameter identifies the remotable class. The second specifies the uniform resource identifier (URI) that the client will use to activate the object—that is, the URI that the client will use to tell the server to activate an instance of RemotableClass. The third and final parameter specifies the activation mode. The two possible choices are WellKnownObjectMode.SingleCall, which creates a new instance of RemotableClass for each and every call placed by a client, and WellKnownObjectMode.Singleton, which creates one instance of RemotableClass to process all calls from all clients.

To make RemotableClass available to remote clients, the server process must also create and register a channel. The channel provides a conduit for communication between an object and a remote client. The .NET Framework class library includes two channels for use on the server side: System.Runtime.Remoting.Channels.Tcp.TcpServerChannel, which accepts TCP connections from remote clients, and System.Runtime.Remoting.Channels.Http.HttpServerChannel, which accepts HTTP connections. The following statements create a TcpServerChannel that listens on port 1234 and register it with the .NET Framework:

TcpServerChannel?channel?=?new?TcpServerChannel?(1234);
ChannelServices.RegisterChannel?(channel);

These statements register an HttpServerChannel that listens on port 1234:

HttpServerChannel?channel?=?new?HttpServerChannel?(1234);
ChannelServices.RegisterChannel?(channel);

TcpServerChannel is the more efficient of the two because TCP is a leaner protocol than HTTP. HttpServerChannel, however, is the channel of choice for applications that use Internet Information Services (IIS) as a remote activation agent—a topic that’s discussed later in this chapter.

A client application that wants to create a remote instance of RemotableClass has to do some registration of its own. First it must register a client channel. The .NET Framework provides two client channel types: TcpClientChannel and HttpClientChannel. The former lets a client talk to a server that’s listening with a TcpServerChannel; the la渎雁银詫颠E渎雁银詫颠cate with a server listening with an HttpServerChannel. Second, if the client wants to use the new operator to instantiate the remote object, it must register the remote class in the local application domain. RemotingConfiguration.RegisterWellKnownClientType registers a class on the client that’s registered with RemotingConfiguration.RegisterWellKnownServiceType on the server. The following statements create and register a client-side TCP channel. They also register RemotableClass as a valid type in the client’s application domain:

TcpClientChannel?channel?=?new?TcpClientChannel?();
ChannelServices.RegisterChannel?(channel);

RemotingConfiguration.RegisterWellKnownClientType?(
????typeof?(RemotableClass),????????????//?Remotable?class
??? "tcp://localhost:1234/RemoteObject" //?URL?of?remotable?class
);

The second parameter passed to RegisterWellKnownClientType specifies the URL where the remote class is located. The protocol (tcp in this example) must match the protocol of the channels registered in the application domains. The machine identifier—localhost—identifies the server that exports RemotableClass and thus identifies the machine on which the object will be created. You can replace localhost with a machine name or an IP address. The port number following the machine identifier must match the port number that the server is listening on—that is, the port number that the server registered with TcpServerChannel. Finally, after the machine identifier and port number comes the object URI, which must match the URI that the server passed to RegisterWellKnownServiceType.

Once both the client and server have performed the requisite registrations, all that remains is for the client to instantiate RemotableClass with the new operator:

RemotableClass?rc?=?new?RemotableClass?();

This action creates a proxy in the client’s application domain and returns a RemotableClass reference that physically refers to the proxy but logically refers to the remote object. Let’s prove that this works by testing it out in a real application.

Your First Remoting Application

The application in Figure 15-2 demonstrates the basic steps involved in activating an object in a remote application domain. It is centered on a remotable class named Clock. Clock has one public method—GetCurrentTime—that retrieves the current wall-clock time and returns it as a string. The application contains three constituent parts:

TimeServer and TimeClient are console applications. When executed, TimeClient displays the current time. It gets the time by instantiating a Clock object and calling Clock.GetCurrentTime.

ClockServer.cs
using?System;

public?class?Clock?:?MarshalByRefObject
{
????public?string?GetCurrentTime?()
????{
????????return?DateTime.Now.ToLongTimeString?();
????}
}
Figure 15-2
A simple remoting application.
TimeServer.cs
using?System;
using?System.Runtime.Remoting;
using?System.Runtime.Remoting.Channels;
using?System.Runtime.Remoting.Channels.Tcp;

class?MyApp
{
????static?void?Main?()
????{
????????TcpServerChannel?channel?=?new?TcpServerChannel?(1234);
????????ChannelServices.RegisterChannel?(channel);

????????RemotingConfiguration.RegisterWellKnownServiceType
????????????(typeof?(Clock), "Clock",?WellKnownObjectMode.SingleCall);

????????Console.WriteLine?("Press?Enter?to?terminate...");
????????Console.ReadLine?();
????}
}
TimeClient.cs
using?System;
using?System.Runtime.Remoting;
using?System.Runtime.Remoting.Channels;
using?System.Runtime.Remoting.Channels.Tcp;

class?MyApp
{
????static?void?Main?()
????{
????????TcpClientChannel?channel?=?new?TcpClientChannel?();
????????ChannelServices.RegisterChannel?(channel);

????????RemotingConfiguration.RegisterWellKnownClientType
????????????(typeof?(Clock), "tcp://localhost:1234/Clock");

????????Clock?clock?=?new?Clock?();
????????Console.WriteLine?(clock.GetCurrentTime?());
????}
}

Here’s a script for building and testing the application:

  1. Use the commands below to build ClockServer.dll, TimeServer.exe, and TimeClient.exe, or simply copy them from the CD that comes with this book:

    csc?/t:library?clockserver.cs
    csc?/r:clockserver.dll?timeserver.cs
    csc?/r:clockserver.dll?timeclient.cs
  2. Open a console window and start TimeServer. Do not press the Enter key to terminate the application.

  3. Open a second console window and run TimeClient. The current time should be displayed in the window.

  4. Terminate TimeServer by switching back to the first console window and pressing the Enter key.

TimeClient runs in one process, and TimeServer runs in another. Because Clock is instantiated in the application domain in which it is registered, calls from TimeClient cross process boundaries to TimeServer.

When most people hear the term “remoting,” they think of calls going from machine to machine. With some minor tweaking, the application in Figure 15-2 can be modified to work across machines. To demonstrate, pick two computers on your network, one to act as the server (machine A), and the other to act as the client (machine B). Then do the following:

  1. Copy TimeServer.exe and ClockServer.dll to a directory on machine A.

  2. Replace localhost in the URL that TimeClient passes to RemotingConfiguration.RegisterWellKnownClientType with machine A’s name or IP address.

  3. Rebuild TimeClient.exe.

  4. Copy the modified version of TimeClient.exe to a directory on machine B. Copy ClockServer.dll to the same directory.

  5. Open a console window on machine A and start TimeServer.

  6. Open a console window on machine B and run TimeClient. To prove that the time displayed on machine B came from machine A, temporarily change the clock on machine A and verify that TimeClient displays the modified time.

  7. Terminate TimeServer on machine A.

Were you surprised by step 4—surprised that you had to copy ClockServer.dll to the client machine? To create a proxy for a remote Clock object, the .NET Framework has to have metadata describing the Clock class. It gets that metadata from the DLL. If you don’t copy the DLL to the client machine, the framework can’t activate a remote Clock object because it can’t create the proxy. You can prove it by temporarily deleting ClockServer.dll from machine B and running TimeClient again. This time, the .NET Framework throws an exception because it has no metadata describing Clock.

Programmatic vs. Declarative Configuration

TimeServer and TimeClient use information embedded in their source code to register channels and remotable classes. The drawback to performing registrations this way is that if any of the registration data changes (as it did when you modified TimeClient to point to a remote server), you have to modify the source code and rebuild the executables.

That’s why the .NET Framework supports an alternate form of registration that is declarative rather than programmatic. Declarative registration takes information from CONFIG files and is enacted by calling the static RemotingConfiguration.Configure method.

Figure 15-3 contains modified versions of TimeServer and TimeClient that demonstrate how declarative configuration works. Functionally, this application is identical to the one in Figure 15-2. Internally, it’s different. Rather than call RegisterChannel, RegisterWellKnownServiceType, and RegisterWellKnown-ClientType, the revised source code files call RemotingConfiguration.Configure and pass in the names of configuration files. Now modifying the client to activate a Clock object on another machine is a simple matter of using Notepad or the text editor of your choice to edit TimeClient.exe.config. No source code changes are required.

ClockServer.cs
using?System;

public?class?Clock?:?MarshalByRefObject
{
????public?string?GetCurrentTime?()
????{
????????return?DateTime.Now.ToLongTimeString?();
????}
}
Figure 15-3
A remoting application that uses declarative configuration.
TimeServer.cs
using?System;
using?System.Runtime.Remoting;

class?MyApp
{
????static?void?Main?()
????{
????????RemotingConfiguration.Configure?("TimeServer.exe.config");
????????Console.WriteLine?("Press?Enter?to?terminate...");
????????Console.ReadLine?();
????}
}
TimeServer.exe.config
<configuration>
??<system.runtime.remoting>
????<application>
??????<service>
????????<wellknown?mode="SingleCall" type="Clock,?ClockServer"
??????????objectUri="Clock" />
??????</service>
??????<channels>
????????<channel?ref="tcp?server" port="1234" />
??????</channels>
????</application>
??</system.runtime.remoting>
</configuration>
TimeClient.cs
using?System;
using?System.Runtime.Remoting;

class?MyApp
{
????static?void?Main?()
????{
????????RemotingConfiguration.Configure?("TimeClient.exe.config");
????????Clock?clock?=?new?Clock?();
????????Console.WriteLine?(clock.GetCurrentTime?());
????}
}
TimeClient.exe.config
<configuration>
??<system.runtime.remoting>
????<application>
??????<client>
????????<wellknown?type="Clock,?ClockServer"
??????????url="tcp://localhost:1234/Clock" />
??????</client>
??????<channels>
????????<channel?ref="tcp?client" />
??????</channels>
????</application>
??</system.runtime.remoting>
</configuration>

Declarative registration has its drawbacks, too. Registration information embedded in an EXE isn’t easily changed by an end user. Registration information encoded in a text file, however, can be modified by anyone savvy enough to run Notepad. It can also be deleted altogether. Choosing between declarative and programmatic registration means choosing which quality is more important to you: convenience or robustness. The right choice depends on the scenario and is ultimately left up to you.

Server Activation vs. Client Activation

The .NET Framework distinguishes between two types of remotable objects: server-activated objects and client-activated objects. Server-activated objects are registered with RemotingConfiguration’s RegisterWellKnownServiceType and RegisterWellKnownClientType methods. The applications in Figures 15-2 and 15-3 rely on server-activated objects. Client-activated objects are registered with RegisterActivatedServiceType and RegisterActivatedClientType instead. Server-activated objects are called “server activated” because when the client calls new, only a proxy is created. The objects themselves aren’t created (“activated”) until a method call is placed through the proxy. In other words, the server, not the client, decides when to physically create the objects. Client-activated objects, on the other hand, are created on the server the moment the client calls new. The differences between server-activated objects and client-activated objects, however, run deeper than these simple descriptions suggest.

A practical difference between server-activated objects and client-activated objects is that the latter can be activated with nondefault constructors (constructors that accept parameters), but the former can not. Server-activated objects don’t support nondefault constructors because calls to new don’t map 1-to-1 to object instantiations; new creates a proxy but doesn’t create the corresponding object. Client-activated objects, on the other hand, can be activated with nondefault constructors because new instantiates both the proxy and the object.

A more fundamental difference between server-activated objects and client-activated objects has to do with how clients and objects are paired together. When you register a server-activated object, you specify an activation mode that determines whether a new object instance is created to service every request or one object instance is created to service all requests. The two supported activation modes are

Circumstances usually dictate which activation mode is appropriate. For example, if a remote object provides a “one-shot” service to its clients (such as the current time of day) and has no need to preserve state between method calls or to share state among its clients, then SingleCall is the right choice. A single-call object can’t easily preserve state between method calls because if one client places 10 method calls through the same proxy, 10 different object instances are created to satisfy those calls. A single-call object can’t easily share state among clients either, because if 10 different clients activate an instance of the same class and place one call each through their respective proxies, again the .NET Framework creates 10 different object instances to service the clients’ requests.

By contrast, the .NET Framework creates a singleton object one time and uses it to service all requests from all clients. If 10 clients activate a singleton object and place 10 calls each to it through their proxies, just one object instance is created. That instance receives all 100 calls placed by the clients. Because a singleton object hangs around between method calls and is shared by all active clients, it’s perfectly capable of retaining state between method calls and even of disbursing state among its clients. One client could pass some data to the object, for example, and the object could store the data in a field. Another client could then call the object and retrieve the data.

One nuance to be aware of regarding singleton objects is that the .NET Framework makes no attempts to synchronize calls placed to them. If two clients call a singleton at exactly the same time, the calls will arrive on two different threads and be processed concurrently. The implication? Unless you know beyond the shadow of a doubt that calls from clients will never overlap (you normally don’t), the object had better be thread-safe. For a refresher on thread synchronization mechanisms supported by the .NET Framework, refer to Chapter 14.

Client-activated objects offer a third option that serves as a middle ground between single-call server-activated objects and singleton server-activated objects. Each call to new placed by a client creates a new instance of a client-activated object that serves that client and that client only. Client-activated objects can preserve state from one method call to the next because they don’t get discarded following each request. They are not, however, suitable for sharing state between clients because every client that creates a client-activated object receives a brand new object instance that is unique to that client.

The combination of single-call server-activated objects, singleton server-activated objects, and client-activated objects gives you three different activation models to choose from. Which model is correct? That depends on the application and what it’s designed to do. For applications such as the ones in Figures 15-2 and 15-3, single-call server-activated objects make sense because the application doesn’t need to preserve state between method calls or share state among clients. For the distributed drawing application at the end of this chapter, however, singleton server-activated objects fit the bill nicely because the design of the application requires that all clients connect to a common object instance. For an application that doesn’t require all clients to connect to the same object but that holds per-client state on the server, a client-activated object should be used instead.

The application shown in Figure 15-4 demonstrates how client activation works and why client-activated objects are sometimes appropriate in the first place. It publishes a remotable client-activated class named Stopwatch. Stopwatch has two methods: Start and Stop. Because Stop returns the number of milliseconds elapsed since the last call to Start, a client can implement a software stopwatch by calling Start and Stop in succession.

Start records the current time in a private field named mark. Stop subtracts mark from the current time and returns the difference in milliseconds. In this example, a single-call server-activated object wouldn’t do because the call to Start would be processed by one Stopwatch instance and the call to Stop would be processed by another. A singleton server-activated object wouldn’t fare any better. If two different clients were executed concurrently and one called Start right after the other, the value written to mark by the first would be overwritten by the second. Consequently, the first client would receive a bogus count from Stop. This is a great example of an application that benefits from a client-activated object because it needs to retain state between method calls but also needs to pair each client with a unique object instance.

To run the application, compile the three source code files and run StopwatchServer. (Be sure to compile Stopwatch.cs with a /t:library switch first and include a /r:stopwatch.dll switch when compiling StopwatchServer.cs and StopwatchClient.cs.) Then, in a separate console window, run StopwatchClient, wait a couple of seconds, and press Enter to display the elapsed time.

Stopwatch.cs
using?System;

public?class?Stopwatch?:?MarshalByRefObject
{
????DateTime?mark?=?DateTime.Now;

????public?void?Start?()
????{
????????mark?=?DateTime.Now;
????}

????public?int?Stop?()
????{
????????return?(int)?((DateTime.Now?-?mark).TotalMilliseconds);
????}
}
Figure 15-4
An application that uses a client-activated object.
StopwatchServer.cs
using?System;
using?System.Runtime.Remoting;
using?System.Runtime.Remoting.Channels;
using?System.Runtime.Remoting.Channels.Tcp;

class?MyApp
{
????static?void?Main?()
????{
????????TcpServerChannel?channel?=?new?TcpServerChannel?(1234);
????????ChannelServices.RegisterChannel?(channel);

????????RemotingConfiguration.RegisterActivatedServiceType
????????????(typeof?(Stopwatch));

????????Console.WriteLine?("Press?Enter?to?terminate...");
????????Console.ReadLine?();
????}
}
StopwatchClient.cs
using?System;
using?System.Runtime.Remoting;
using?System.Runtime.Remoting.Channels;
using?System.Runtime.Remoting.Channels.Tcp;

class?MyApp
{
????static?void?Main?()
????{
????????TcpClientChannel?channel?=?new?TcpClientChannel?();
????????ChannelServices.RegisterChannel?(channel);

????????RemotingConfiguration.RegisterActivatedClientType
????????????(typeof?(Stopwatch), "tcp://localhost:1234");

????????Stopwatch?sw?=?new?Stopwatch?();
????????sw.Start?();

????????Console.WriteLine?("Press?Enter?to?show?elapsed?time...");
????????Console.ReadLine?();

????????Console.WriteLine?(sw.Stop?()?+ " millseconds");
????}
}
The Activator.GetObject and Activator.CreateInstance Methods

The new operator isn’t the only way to activate remote objects. The .NET Framework offers alternative activation mechanisms in the form of static methods named GetObject and CreateInstance. Both are members of the System.Activator class. GetObject is used to activate server-activated objects, while CreateInstance is used to activate client-activated objects.

When you use GetObject or CreateInstance to activate remote objects, you no longer have to call RegisterActivatedClientType or RegisterWellKnownClientType to register remotable classes on the server. For example, rather than activate a server-activated object this way

RemotingConfiguration.RegisterWellKnownClientType
????(typeof?(Clock), "tcp://localhost:1234/Clock");
Clock?clock?=?new?Clock?();

you can can activate the object this way:

Clock?clock?=?(Clock)?Activator.GetObject
????(typeof?(Clock), "tcp://localhost:1234/Clock");

And rather than activate a client-activated object this way

RemotingConfiguration.RegisterActivatedClientType
????(typeof?(Stopwatch), "tcp://localhost:1234");
Stopwatch?sw?=?new?Stopwatch?();

you can activate it this way:

object[]?url?=?{?new?UrlAttribute?("tcp://localhost:1234")?};
Stopwatch?sw?=?(Stopwatch)?Activator.CreateInstance
????(typeof?(Stopwatch),?null,?url);

The big question is why? Why use GetObject or CreateInstance to activate a remote object when you could use new instead? Neither GetObject nor CreateInstance is intrinsically better than new, but both offer one option that new doesn’t. GetObject and CreateInstance can be used to activate a remote type if you possess no knowledge of the type other than a URL and an interface that the type supports. For example, suppose you modify Figure 15-2’s Clock class so that it implements an interface named IClock. With GetObject, you could activate a Clock object and get back an IClock interface without referencing the Clock type in your source code:

IClock?ic?=?(IClock)?Activator.GetObject
????(typeof?(IClock), "tcp://localhost:1234/Clock");

Try the same thing with new, however, and the code won’t compile because new won’t accept an interface name:

//?Won't?compile!
RemotingConfiguration.RegisterWellKnownClientType
????(typeof?(IClock), "tcp://localhost:1234/Clock");
IClock?ic?=?new?IClock?();

CreateInstance can be used in a similar manner to activate a remote client-activated object and return an interface—again, without having intimate knowledge of the type that implements the interface.

Outside the admittedly rare circumstances in which you have metadata describing an interface that a type implements but no metadata describing the type itself, you can use new and GetObject and new and CreateInstance interchangeably.

Object Lifetimes and Lifetime Leases

How long does a remote object live once it’s activated? In DCOM, an object is destroyed (rather, the object destroys itself) when its reference count reaches 0—that is, when the last client calls Release on the last interface pointer. There is no reference counting in .NET remoting. A single-call server-activated object lives for the duration of exactly one method call. After that, it’s marked for deletion by the garbage collector. Singleton server-activated objects and client-activated objects work differently. Their lifetimes are controlled by leases that can be manipulated declaratively or programmatically. Physically, a lease is an object that implements the ILease interface defined in the System.Run-time.Remoting.Lifetime namespace.

The following ILease properties govern the lifetime of the object with which a lease is associated:

Property

Description

Get

Set

InitialLeaseTime

Length of time following activation that the object lives if it receives no method calls

RenewOnCallTime

Minimum value that CurrentLeaseTime is set to each time the object receives a call

CurrentLeaseTime

Amount of time remaining before the object is deactivated if it doesn’t receive a method call

The default lease assigned to singleton server-activated objects and client-activated objects has an InitialLeaseTime of 5 minutes, a RenewOnCallTime equal to 2 minutes, and a CurrentLeaseTime of 5 minutes. If the object receives no method calls, it’s deactivated when CurrentLeaseTime ticks down to 0—that is, after 5 minutes. However, each method call that the object receives in the last 2 minutes of its life extends the lease by setting CurrentLeaseTime to 2 minutes. This effectively means that after an initial grace period whose duration equals InitialLeaseTime, the object must be called at intervals no greater than Renew-OnCallTime or it will go away.

If the default InitialLeaseTime and RenewOnCallTime values aren’t suitable for an application you’re writing, you can change them in either of two ways. The first option is to specify lease times in a CONFIG file. Lease properties set this way affect all remote objects in the server process. The second option is to override MarshalByRefObject.InitializeLifetimeService in the remotable class. Lease properties set that way affect only instances of the overriding class and can be used to assign different lifetimes to different objects.

Here’s an example demonstrating the first option. The following CONFIG file sets InitialLeaseTime and RenewOnCallTime to 20 minutes and 10 minutes, respectively, for all objects in the process:

<configuration>
??<system.runtime.remoting>
????<application>
??????<lifetime?leaseTime="20M" renewOnCallTime="10M" />
????</application>
??</system.runtime.remoting>
</configuration>

20M means 20 minutes. Other supported suffixes include D for days, H for hours, S for seconds, and MS for milliseconds. A number without a suffix is interpreted to mean seconds. In order for the policies outlined in the CONFIG file to take effect, the server process—the one that registers objects for remote activation—must register the file with RemotingConfiguration.Configure.

The alternative to setting lease options declaratively is to set them programmatically by overriding InitializeLifetimeService. Here’s a remotable class that does just that:

using?System;
using?System.Runtime.Remoting.Lifetime;

public?class?RemotableClass?:?MarshalByRefObject
{
????public?override?object?InitializeLifetimeService?()
????{
????????//?Get?the?default?lease
????????ILease?lease?=?(ILease)?base.InitializeLifetimeService?();

????????//?Modify?it
????????if?(lease.CurrentState?==?LeaseState.Initial)??{
????????????lease.InitialLeaseTime?=?TimeSpan.FromMinutes?(20);
????????????lease.RenewOnCallTime?=?TimeSpan.FromMinutes?(10);
????????}
????????return?lease;
????}
??...
}

A lease can be modified only if it hasn’t been activated yet (that is, if the object has just been created). The if clause in this sample reads ILease.CurrentState to make absolutely sure that the lease hasn’t been activated.

A remotable class can do away with leases altogether by overriding InitializeLifetimeService and returning null:

using?System;

public?class?Foo?:?MarshalByRefObject
{
????public?override?object?InitializeLifetimeService?()
????{
????????return?null;
????}
??...
}

An object that does this won’t be deleted by the .NET Framework and will continue to run as long as the application domain, process, and machine that host it are running.

A remotable object can acquire a reference to its own lease’s ILease interface at any point during its lifetime by calling the GetLifetimeService method that it inherits from MarshalByRefObject. It can also call ILease.CurrentLeaseTime and ILease.Renew to find out how much time it has left and ask the system to extend the lease. Clients too can acquire ILease interfaces and use them to exert control over leases. The static RemotingServices.GetLifetimeService method takes a client-side object reference as input and returns an ILease interface. Here’s an example demonstrating how to manipulate a lease from the client side:

RemotableClass?rc?=?new?RemotableClass?();
ILease?lease?=?(ILease)?RemotingServices.GetLifetimeService?(rc);
TimeSpan?remaining?=?lease.CurrentLeaseTime;
if?(remaining.TotalMinutes?<?1.0)
????lease.Renew?(TimeSpan.FromMinutes?(10));

In this example, the client renews the lease if ILease.CurrentLeaseTime reveals that the lease runs out in less than a minute. The TimeSpan value passed to Renew specifies the amount of time to add to the lease. If CurrentLeaseTime is greater than the value passed to Renew, Renew has no effect. But if Current-LeaseTime is less than the value specified in Renew’s parameter list, Renew sets CurrentLeaseTime to the specified value.

ILease also has methods named Register and Unregister that register and unregister sponsors. A sponsor is an object that implements the System.Run-time.Remoting.Lifetime.ISponsor interface. When an object’s lease expires, the .NET Framework doesn’t mark the object for deletion immediately; first it checks with the application domain’s lease manager to see whether any sponsors are registered. If the answer is yes, the lease manager calls the sponsors’ ISponsor.Renewal methods to see whether any of the sponsors are willing to “sponsor” the object and extend its lease. Sponsors can be registered by clients and servers, and they provide a handy mechanism for controlling object lifetimes based on criteria that you define rather than on the passage of time and the frequency of incoming calls. The ClientSponsor class in the FCL’s System.Runtime.Remoting.Lifetime namespace provides a canned implementation of sponsors that you can use without building sponsor classes of your own.

Because sponsors don’t have to reside on the same machine as the objects they serve, the .NET Framework limits the length of time it waits for a response when it calls a sponsor’s Renewal method. The default is 2 minutes. You can modify the time-out interval by setting a lease’s ILease.SponsorshipTimeout property.

The .NET Framework’s reliance on leases to control the lifetimes of remote objects has an important implication that requires some getting used to by COM programmers. COM objects don’t disappear prematurely unless a buggy client releases one interface too many or the host process terminates. In .NET remoting, however, objects can go away without warning. What happens if a client calls an object that no longer exists? The framework throws a RemotingException. A client might respond to that exception by creating a new object instance and trying again. To the extent you can, try to avoid such errors by using leases with sufficiently high InitialLeaseTime and RenewOnCallTime values.