在.NET开发中,经常会遇到需要加载不同版本的同一个程序集的情况。例如,主应用程序使用了一个版本的UnityContainer,而动态加载的程序集使用了另一个版本的UnityContainer,这将导致程序集无法加载。为了解决这个问题,可以使用多种方法。
一种方法是使用WCF服务,它托管在另一个进程中,因为不同的进程允许加载不同版本的程序集。另一种方法是使用强命名程序集,这是.NET官方解决此类问题的技术。第三种方法是使用新的AppDomain,因为不同的AppDomain可以隔离类型和程序集。
第一种解决方案对于简单案例来说可能有点过于复杂。第二种解决方案需要对所有程序集进行签名并注册到GAC中,希望避免它引入的开销。第三种解决方案是一种轻量级的方法。本文将讨论如何实现它。
在C#中,可以使用以下代码创建一个新的AppDomain:
var domain = AppDomain.CreateDomain("TestService");
var tester = (ITester)domain.CreateInstanceAndUnwrap(typeof(Tester).Assembly.FullName, typeof(Tester).FullName);
还可以指定默认的程序集搜索路径:
var setup = new AppDomainSetup { ApplicationBase = GetPath() };
var domain = AppDomain.CreateDomain("TestService", null, setup);
var tester = (ITester)domain.CreateInstanceAndUnwrap(typeof(Tester).Assembly.FullName, typeof(Tester).FullName);
为了在其他AppDomain中调用类型实例,需要让类型继承自MarshalByRefObject。.NET将自动为创建一个远程代理。因此,可以像使用普通类型一样使用它。但是,在远程域中实例化的类型依赖于客户端域的垃圾回收器。如果客户端域崩溃,远程对象将不会被释放。因此,.NET提供了一种机制来避免这种情况。
远程对象将在五分钟未使用后自动销毁。在案例中,客户端在默认AppDomain中运行,当它结束时,整个进程也将结束。因此,可以使用以下代码禁用默认的自毁行为:
public override object InitializeLifetimeService()
{
return null;
}
对于引用类型,可以继承自MarshalByRefObject。但是对于那些值类型呢?实际上,大多数值类型默认是可远程的,例如int、string、enum等。但是对于结构体类型,需要将其标记为Serializable。
[Serializable]
public struct Result
{
public string Outcome;
}
除了委托也是可序列化的。实际上,也可以将引用类型标记为Serializable。它也将支持远程操作。但是,运行时语义是不同的。使用MarshalByRefObject时,客户端代码将通过代理调用远程对象,远程对象在新的AppDomain中运行。但是,将Serializable应用于所有类型时,序列化过程将在客户端AppDomain中创建一个新实例。这意味着所有后续调用将在客户端AppDomain中执行。
为了避免将所有类型标记为Serializable,选择Marshalling方法。对于值类型,将其标记为Serializable。这可以工作,让AppDomains相互通信。在抛出异常的情况下,可能需要对异常进行特殊处理,使其可序列化。对于系统定义的异常,.NET已经处理了这个问题。对于用户定义的异常,需要处理它们:如果异常没有自定义属性,需要将它们标记为Serializable。例如,假设定义了一个名为RetryableException的异常。
[Serializable]
[ComVisible(true)]
public class RetryableException : Exception
{
public RetryableException() {}
public RetryableException(string message) : base(message) { }
public RetryableException(string message, Exception ex) : base(message, ex) { }
}
如果异常具有自定义属性,需要在序列化调用的构造函数中添加一些实现,并覆盖GetObjectData(SerializationInfo info, StreamingContext context),这是定义在ISerializable接口中的。
[Serializable]
[ComVisible(true)]
public class RetryableException : Exception
{
public Action RetryTask { get; set; }
public Exception Exception { get; set; }
public string Name { get; set; }
public RetryableException() {}
public RetryableException(string message) : base(message) { }
public RetryableException(string message, Exception ex) : base(message, ex) { }
public RetryableException(Action retry, Exception e, string name)
{
RetryTask = retry;
Exception = e;
Name = name;
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
protected RetryableException(SerializationInfo info, StreamingContext context) : base(info, context)
{
Exception = (Exception)info.GetValue("Exception", typeof(Exception));
RetryTask = (Action)info.GetValue("RetryTask", typeof(Action));
Name = info.GetString("Name");
}
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException("info");
}
info.AddValue("Exception", Exception, typeof(Exception));
info.AddValue("RetryTask", RetryTask, typeof(Action));
info.AddValue("Name", Name);
base.GetObjectData(info, context);
}
}