Remoting Object Model Overview

In this section we will explore the Remoting Object model in greater detail. We will look at some of the key concepts behind object remoting before jumping into the architecture itself. Hopefully this will provide more contextual information so you can gain a better perspective of the previous examples.

Remoting Object Model Concepts

.NET Remoting relies on some fundamental concepts surrounding what objects can and can't do over the network. Some objects can be serialized and passed-by-value back to the client, while others cannot. We will begin our exploration of the .NET Remoting Object model by defining the different levels of object remoting.

Agile Objects

Agile objects are accessed from any context and are always called directly. These types of objects can be invoked through direct references. Agile objects do not need to be called by proxy and have no constraints on what they can do. Most "regular" objects are considered agile.

Not Marshaled Type Category

Objects that do not derive from System.MarshalByRefObject or System.ContextBoundObject and do not have a [serializable] custom attribute are considered not marshaled. In other words, the object cannot be referenced remotely, nor can it be serialized into a form that can be stored or transmitted. This implies that an object of this type cannot be accessed from outside the application domain either by value or by reference.

Objects of this type are usually designed for use inside the application in which they are defined. By definition these types of objects are always accessed directly because all references to them are actual instances. Invoking methods on these objects do not suffer performance penalties because of argument and return result marshaling.

Marshal By Value Object Category

The only difference between these objects and those belonging to the Not Marshaled category is that MarshalByValue (MBV) objects can be serialized. This allows them to be copied directly into the address space of another application. In this way these objects are remotable, although no proxies are ever involved in their use. Because they must live within the application domain, all references to them must be actual instances. Therefore, their method performance is optimized.

MBV objects are easily identifiable by a serialization indicator in the class definition. Such classes usually take [serializable] attributes or else provide their own serialization methods by implementing the ISerializable interface. For example, the following class definition illustrates an MBV object.

[serializable]
public class MyMarshalByValueClass : ISerializable
{
  // ...
  private MyMarshalByValueClass(
                  SerializationInfo info, 
                  StreamingContext context)
  {
    //. . .
  }

  // The ISerializable Implementation
  public virtual void GetObjectData(
                        SerializationInfo info, 
                        StreamingContext context)
  {
    //. . .
  }
} 

Marshal By Reference Object Category

MarshalByReference (MBR) objects make up the class of objects we just created in the Factorial and ShoppingCart examples. MBR objects all inherit, either directly or indirectly, from the MarshalByRefObject class.

All objects of this type have a distributed identity or some URI by which they can be located over a network. Instances of MarshalByRefObject classes must remain in the application domain where they are defined. Although references to these objects might exist in other application domains (or other machines), the actual instances must stay put.

MBR objects are particularly appropriate for circumstances in which objects that depend on a local resource need to be referenced remotely. Objects that access server-side resources, such as databases and file systems, and cannot be serialized are likely candidates for MBR remoting.

Whenever MBR objects are passed out of the Home application domain, either as a parameter to a method or a method's return result, the runtime takes care of marshaling a stub reference into the Target application domain. The runtime manages MBR reference semantics in all three of the following scenarios.

All out-of-process references to an MBR object must go through a layer of abstraction when performing operations on that object. This is so that the .NET Remoting framework can step in and insert a proxy class that handles marshaling the operation over a channel and back again.

All MBR object fields must be accessed through public accessors. The ShoppingCart example had two public properties, firstName and lastName, which appeared to be accessed directly by the ShoppingCart client. (For example, the statement cart2.lastName = "Dough"; appears to alter the lastName property directly.) This is, in fact, not true. What happened in this case was that the assignment operators for the firstName and lastName properties were overloaded to call the appropriate accessors in the ShoppingCart class. The accessors are implicitly associated with the properties via the following lines of code in ShoppingCart.cs

    public String FirstName
    {
      get
      {
        Console.WriteLine("First Name: {0}", firstName);
        return firstName;
      }
      set
      {
        lock(this)
        {
          firstName = value;
          Console.WriteLine("First Name: {0}", firstName);
        }
      }
    }
    public String LastName
    {
      get
      {
        Console.WriteLine("Last Name: {0}", LastName);
        return lastName;
      }
      set
      {
        lock(this)
        {
          lastName = value;
          Console.WriteLine("Last Name: {0}", LastName);
        }
      }
    }

Because all method calls on MBR objects must happen though indirection, MBR operations carry with them a performance penalty. The severity of this penalty depends on the degree of separation between the two objects, the amount of marshaling logic that is required, and the speed of the network link between them.

Context Bound Object Category

ContextBound objects are similar to MBR objects. The major difference is that ContextBound objects have a predefined context, or state of parameters, that must be defined for them to operate. All calling and passing semantics for ContextBound objects are the same as those for MBR objects.

ContextBound classes are marked by the ContextAttribute. This attribute defines the properties that a ContextBound object requires to operate correctly. These properties can define synchronization rules, transactions, just-in-time activation, security, or any other contexts that the object might be expecting. Context-specific data is stored into a shared memory mechanism managed by the .NET. The runtime allocates a multi-slot data store array to each process upon its creation. These slots house the contextual information unique to a thread or context.

ContextBound classes are opposite Agile classes in terms of contextual behavior. ContextBound classes are guaranteed to operate only in the context in which they were created. Conversely, Agile classes are guaranteed to operate in the context of the calling program.

Table 8-6 through Table 8-9 show the public members of the context objects. 

Table 8-6 Public Static (Shared) Properties
Property Description

DefaultContext

Gets the default context for the current application domain.

Table 8-7 Public Static (Shared) Methods
Method Description

AllocateDataSlot

Allocates an un-named data slot.

AllocateNamedDataSlot

Allocates a named data slot.

FreeNamedDataSlot

Frees a named data slot.

GetData

Retrieves the value from the specified slot in the current context.

GetNamedDataSlot

Looks up a named data slot.

RegisterDynamicProperty

Registers a dynamic property implementing IContributeDynamicSink with the remoting service.

SetData

Sets the data in the specified slot on the current context.

UnregisterDynamicProperty

Unregisters a dynamic property implementing IcontributeDynamicSink.

Table 8-8 Public Instance Properties
Property Description

ContextID

Gets the context ID for the current context.

ContextProperties

Gets the specified context property.

Table 8-9 Public Instance Methods
Method Description

DoCallBack

Executes code in another context.

Equals

Overloaded. Determines whether two object instances are equal.

Freeze

Freezes the context.

GetHashCode

Serves as a hash function for a particular type, suitable for use in hashing algorithms and data structures like a hash table.

GetProperty

Returns a specific context property by name.

GetType

Gets the type of the current instance.

SetProperty

Sets a specific property by name.

ToString

Returns a string representation of the context.

Remoting Object Model Architecture

The Remoting Object Model Architecture consists of interfaces, context-bound classes, application domain-bound classes, unbound classes, and value types. Let's look at how all this lays out. Figure 8-3 contains a high-level diagram of the inheritance hierarchy for all run-time classes.

Figure 8-3 A Logical class inheritance hierarchy.

Once a class is marked as MarshalByValue, AppDomainBound, or ContextBound, all subclasses of that class are similarly restricted. In other words, a class that inherits from a MarshalByValue class cannot also be marked as MarshalByReference.

Instance Fields and Accessors

The behavior of instance fields, or properties, varies across the class hierarchy. Depending on the type of object, instance properties can either be manipulated directly or through well-defined accessors. If the class is MarshalByValue, its fields can be directly accessed. No proxies are required because all the object's state resides in the local application domain.

Properties can also be directly manipulated if they belong to "this" or any of the object's parents. This is because the instance of the object in question must be operating in the application domain of its parent. If the object is MBV, its parent must also be MBV and, therefore, exists in the same application domain. Conversely, if the object is MBR, the actual instance of the object must run in the application domain where the object was born (even though references to the object might reside elsewhere). Here again the parent object must also reside in the same application domain so its properties can be accessed directly in memory.

Finally the runtime checks to see if the object is a proxy. If so, its fields are viewed and changed through accessors defined in the proxy class. Field values can safely be referred to via direct memory.

Statics

Static fields and methods are always agile. They are never remoted, and access to them is always done via direct memory. Furthermore, static fields are not subject to contextual rules placed on Context objects.

Passing Objects as Parameters

Agile objects can be passed and received to and from other application domains either by value or by reference. ContextBound objects, or objects that depend on the context in which they were born, are always passed by reference.

Overriding Methods on Object

The virtual methods defined on the class Object raise an interesting dilemma for the .NET Remoting framework. In the case of PassByReference remote objects, both the remote object and the local stub derive from the class Object. Therefore, these virtual functions are doubly defined. For instance, if ToString( ) is invoked on an object stub, should the .NET call the stub's ToString( ) method or the ToString( ) method associated with the remote object?

Generally speaking, these methods are only called remotely if the MBR object has explicitly overridden them. To be exact, here is what happens in the case of each of Object's virtual methods: