AppDomains and Remoting Life-Time Service
Sometimes we forget that .NET AppDomains are really separate light-weight processes inside a single .NET process. We enjoy all the benefits of a light-weight process: fault isolation, assembly "unloading", custom security policies, and many other things.
But we often forget that AppDomains are separate from their host and from each other, and that under the covers they use good old .NET Remoting to communicate. With that in mind, perhaps you can answer the following question:
I'm creating an object in a separate AppDomain and storing a reference to it in a dictionary. At some point, when I'm trying to access the object I'm getting an exception as if the object has been collected - even though I have a reference to it. What's going on?
Well, if you remember, when you create a remote object (including an object in a separate AppDomain), it can either be a marshal-by-value or a marshal-by-ref object. If it's serializable, it's automatically treated as marshal-by-value, in which case you have a copy of the object in the "host" domain and there are no lifetime issues to consider. But if the object derives from MarshalByRefObject, then it's deemed marshal-by-ref, meaning that you actually have a remote proxy to the original object, which lives in a separate AppDomain.
As such, the remote object's lifetime is no longer tied to the standard .NET GC rules. It is tied to the remoting lease and sponsor mechanism. This means that unless we explicitly opt-in and manage the object's lifetime, it's possible for it to appear collected and disappear even though we still have a reference to its proxy. If this happens, the exception will resemble the following:
System.Runtime.Remoting.RemotingException: Object '/76e7cd41_2cd2_4e89_9c03_fae752ec4d59
/zb_uualy_cm6kwizjlentfdl_3.rem' has been disconnected or does not exist at the server.
(Note that the object URI is automatically generated when you use methods such as AppDomain.CreateInstanceAndUnwrap to create an object within the other domain.)
If we desire to achieve singleton semantics for the remote object, it's simplest to ensure that it never dies. This can be done by overriding the InitializeLifetimeService method on your MarshalByRefObject-derived class and returning null instead of a valid ILease implementation.