Executing Code in a Separate Application Domain Using C#

In certain circumstances it can be useful to execute code in a class in its own application domain. We will take a look at how to use an application domain to isolate and execute code using a generic class wrapper.

An application domain provides a layer of isolation that makes them behave much like a separate process. Special consideration is required when calling code from one application domain that exists in a separate domain. All communication between executing code that exists in different domains is done via remoting.

The following code illustrates an Isolated<T> generic class that can be used to execute code in a class in its own application domain:

public sealed class Isolated<T> : IDisposable where T : MarshalByRefObject
{
   private AppDomain _domain;
   private T _value;

   public Isolated()
   {
      _domain = AppDomain.CreateDomain("Isolated:" + Guid.NewGuid(),
         null, AppDomain.CurrentDomain.SetupInformation);

      Type type = typeof(T);

      _value = (T)_domain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
   }

   public T Value
   {
      get
      {
         return _value;
      }
   }

   public void Dispose()
   {
      if (_domain != null)
      {
         AppDomain.Unload(_domain);

         _domain = null;
      }
   }
}

Example

The following code provides a demonstration of the Isolated<T> generic class in use:

public class Work : MarshalByRefObject
{
   public void DoSomething()
   {
      //...
   }
}

class Program
{
   static void Main(string[] args)
   {
      using (Isolated<Work> isolated = new Isolated<Work>())
      {
         isolated.Value.DoSomething();
      }
   }
}

If you want to pass parameters to the class, or use existing classes that can’t be extended to use MarshalByRefObject, please read on...

The Isolated<T> generic class enforces the rule that you must inherit from the class MarshalByRefObject. If the class you want to execute does not inherit from MarshalByRefObject then you should create a new class that will act as a remote facade.

Creating a Remote Facade

If you have already built a class or set of classes that you want to use with Isolated<T>, you can create a remote facade so you don’t have to change any of them to inherit from MarshalByRefObject.

public class Existing
{
   public void DoSomething()
   {
      // code...
   }
}

public class Facade : MarshalByRefObject
{
   private Existing _existing = new Existing();

   public void DoSomething()
   {
      _existing.DoSomething();
   }
}

class Program
{
   static void Main(string[] args)
   {
      using (Isolated<Facade> isolated = new Isolated<Facade>())
      {
         isolated.Value.DoSomething();
      }
   }
}

Passing Parameters Accross Application Domain Boundaries

If you are passing classes as parameters to your remote façade you will need to make sure they are marked as serializable as they will be serialized and deserialized across the application domain boundary by .NET remoting.

[Serializable]
public class Parameters
{
   public int Value { get; set; }
}

public class Work : MarshalByRefObject
{
   public void DoSomething(Parameters parameters)
   {
      //...
   }
}

class Program
{
   static void Main(string[] args)
   {
      using (Isolated<Facade> isolated = new Isolated<Facade>())
      {
         isolated.Value.DoSomething(new Parameters { Value = 1 });
      }
   }
}

Avoiding Locking Issues

The DLL that contains the classes you are running within your Isolated<T> will be locked until the Isolated<T> is disposed or until the parent application domain or process exits. So make sure you Dispose your Isolated<T> instances when you are finished with them otherwise you may encounter locking issues, for instance if you don’t dispose Isolated<T> in tests run with NUnit, your DLL will remain locked as NUnit will remain loaded until it is closed and all associated application domains loaded inside it will remain loaded unless explicitly unloaded.