Remoting fun
I found an interesting little eddy in the remoting world. I'm sure I'm far from the first to find it but it's a pretty neato little place.
As you may know there are two ways in which you can control how an object gets remoted in .NET. By deriving from MarshalByRefObject you tell the runtime that you want a proxy to be created and if you mark it as [Serializable] you're telling the runtime to just send a clone. Most of the time we think about this in terms of what happens when we connect to a server object from a client, but this setting also matters a great deal any time you go to pass a parameter to a remote object. If you pass a MarshalByRefObject then any calls the server makes on the passed object come back to the client. This is often considered "icky". Serializable (and not MBRO) objects are just sent along as values and any calls the server makes to the object happen locally.
So here's the situation I found myself in. I have a system that passes around a shared property bag, hidden behind an interface like so:
public interface IIntBag { int this[string id] { get; set; } }
[Serializable] public class IntBagImpl : IIntBag { Hashtable h = new Hashtable();
public int this[string id] { get { return (int)h[id]; } set { h[id] = value; } } }
Pretty pedestrian stuff - nothing that interesting so far. A typical class that manipulates it might look like this:
public class BagManipulator : MarshalByRefObject { public void AddItemToBag(IIntBag bag) { Console.WriteLine("bag[\"1"] = 1;"); bag["3"] = bag["1"] + bag["2"]; } }
Now - what happens when I call AddItemToBag? In the case where the caller and the callee are in the same appdomain nothing very interesting happens at all - the server modifies the bag directly and the client sees the results. Things get interesting when I want to call AddItemToBag remotely. If I mark the underlying object as serializable then the whole schmeer gets sent up to the server and the calls into the bag all happens locally. On one hand this is good - the server can play with the bag all it wants and no network traffic is incurred. Notice though, that any changes the server makes to the bag are NOT propagated back to the client. I could derive IntBagImpl from MarshalByRefObject but then every time the server called the bag it would be calling back across the network into the client's AppDomain. This would be just a wee bit SLOW.
What I really want for this class is to serialize it both directions - I want the object to be serialized up to the server so the server can play with it, but when AddItemToBag returns I want the modified bag to come back down to the client. I was all geared up to start exploring custom marshaling when I tried one simple thing - I changed AddItemToBag to take a ref IIntBag instead of just a plain IIntBag:
public void AddItemToBag(ref IIntBag bag) { Console.WriteLine("bag[\"1"] = 1;"); bag["3"] = bag["1"] + bag["2"]; }
This turned out to be exactly the combination I wanted - the hashtable is serialized in both directions! Reasonably obvious I suppose but I wasn't convinced it was going to work this way. I figured "ref Int" wouldn't send results back down unless I literally allocated a new hashtable and assigned it to bag. This is too cool.
Interestingly the C# compiler demonstrates a little quirk (maybe it demonstrates my own quirkiness) when I try to call the function. If I say this:
IntBagImpl i = new IntBagImpl(); Server.Add(ref i);
the compiler complains:
error CS1503: Argument '1': cannot convert from 'ref IntBagImpl' to 'ref IIntBag'
Well, duh. Just for fun I tried this:
IntBagImpl i = new IntBagImpl(); Server.Add(ref (i as IIntBag));
Sick stuff to be sure, but hey - it might work :) Alas, the compiler slaps me down with this:
error CS1510: A ref or out argument must be an lvalue
Changing the type of i to IIntBag of course fixed it, but apparently the compiler doesn't like taking a ref on a temporary object :). Interesting. I wonder what the Mono compiler does here...
11:57:26 PM
|