AppDomains are like scaled down processes that allow us to have the isolation we need for reliability and security without the greater processor and memory overhead that complete Win32 processes entail. Many AppDomains can be run inside of one actual process, and communication between AppDomains within a process is, as you might expect, faster than inter-process communication, but there is still some performance impact due to the need to traverse Remoting boundaries.
To understand, relatively, the overhead involved in remote communication, let's consider two people. First, imagine that they are in the same office building, in two different departments. The departments could be likened to AppDomains in one process. To send a message or package between themselves, they use interoffice mail. There's fairly little time or cost involved to do this.
Now imagine those same two people are located across the country from one another and they want to send the same message or package. Now they'd have to pay for postage and schedule a pickup or drop it off at a post office to send it, which clearly involves more time and cost, increased overhead. This would be relatively similar to communicating between actual Win32 processes.
Finally, consider that one person lives in the U.S.A., and the other lives in the U.K. To send the same message or package would dramatically increase time and, to a lesser extent, cost, and is akin to communicating over a network between two physical machines.
If these two people were in the same room, communication would be much faster and cheaper. No packaging or envelopes would be necessary to communicate-they could simply talk to each other and hand each other whatever they needed to. This would resemble in-AppDomain communication and is clearly the most efficient means of communicating between two objects.
So far, the analogy speaks mainly to performance. In our case, with the .NET Service Manager, the more important feature of Remoting is the ability to instantiate an object in another AppDomain and to send it key messages, such as Start and Stop. We do this by using an object that inherits from the MarshalByRef class. Inheriting from this allows our objects to be created and live in one AppDomain and be controlled from another AppDomain because just a reference (address) is sent across AppDomain boundaries in a package (proxy); hence the name marshal (escort, if you will) by reference.
By default, objects are marshaled across Remoting boundaries by value, meaning a complete copy of the data (values) will be serialized and sent. Actually, for that to work with Remoting properly (as the term is normally applied), you have to add the SerializableAttribute to your class. In saying "by default," it would probably be better to say that in most cases, it is the preferred and recommended way to deal with remote data for reasons that we won't cover here. In our case, however, we must use MarshalByRef, and since we are dealing with intra-process, cross-AppDomain communication, the typical negative impacts of MarshalByRef are negligible.
So we use Remoting to create an instance of a type in a different AppDomain. In the code, this type is called RemoteServiceHandler. The reason that we have to use this approach is that we must avoid loading any type information into the main AppDomain because if we do that, the entire assembly will be loaded into the main AppDomain and we won't be able to unload it dynamically. One of our goals in this application is to be able to load and unload our managed services (assemblies) dynamically to allow for hot updates to be applied. The only way to do that, which I have found, is to create a separate AppDomain for each unique managed service assembly and use Remoting to create and control the managed service's IService implementation. (If you are unfamiliar with the IService or managed service terminology, please refer to Part 1.)
By loading up individual AppDomains, we can use the AppDomain.Unload method to completely unload the assemblies from memory and, if applicable, load a new, updated copy without having to restart the main .NET Service Manager process. Again, we use the separate AppDomain to isolate the managed service's assembly information to make this possible. If we loaded the type directly in the main AppDomain, we could not unload it to be updated without restarting the main AppDomain and, consequently, the entire process because, as I said, if you load any type info from an assembly, it will load the assembly into that AppDomain. See Figure 1 for an illustration of the isolation we have.
Figure 1 – AppDomain Remoting