如何在LoadFrom上下文中绑定到程序集时重现InvalidCastException

在Suzanne Cook的.NET CLR笔记中,她谈到了“LoadFrom”环境的危险性。 特别,

  • 如果Load上下文程序集尝试按显示名称加载此程序集,则默认情况下将无法找到它(例如,当mscorlib.dll反序列化此程序集时)
  • 更糟糕的是,可以在探测路径上找到具有相同标识但位于不同路径的程序集,从而导致InvalidCastException,MissingMethodException或稍后出现意外的方法行为。

如何使用反序列化重现此行为,但是没有显式加载两个不同版本的程序集?

我创建了一个控制台应用程序A.exe,它间接加载(通过`Assembly.LoadFrom)并从类库B.dll调用(通过reflection)代码。

  • A.exe不一定(必然)引用B.dll,但B.dll应该与A.exe存在于同一目录中
  • 应将B.dll的副本放入另一个目录(这里我使用了名为LoadFrom的子目录),这是我们将使用Assembly.LoadFrom的位置。

A.EXE

 class Program { static void Main(string[] args) { // I have a post build step that copies the B.dll to this sub directory. // but the B.dll also lives in the main directory alongside the exe: // mkdir LoadFrom // copy B.dll LoadFrom // var loadFromAssembly = Assembly.LoadFrom(@".\LoadFrom\B.dll"); var mySerializableType = loadFromAssembly.GetType("B.MySerializable"); object mySerializableObject = Activator.CreateInstance(mySerializableType); var copyMeBySerializationMethodInfo = mySerializableType.GetMethod("CopyMeBySerialization"); try { copyMeBySerializationMethodInfo.Invoke(mySerializableObject, null); } catch (TargetInvocationException tie) { Console.WriteLine(tie.InnerException.ToString()); } Console.ReadKey(); } } 

B.DLL

 namespace B { [Serializable] public class MySerializable { public MySerializable CopyMeBySerialization() { return DeepClone(this); } private static T DeepClone(T obj) { using (var ms = new MemoryStream()) { var formatter = new BinaryFormatter(); formatter.Serialize(ms, obj); ms.Position = 0; return (T)formatter.Deserialize(ms); } } } } 

产量

 System.InvalidCastException: [A]B.MySerializable cannot be cast to [B]B.MySerializable. Type A originates from 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'c:\Dev\bin\Debug\B.dll'. Type B originates from 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadFrom' at location 'c:\Dev\bin\Debug\LoadFrom\B.dll'. at B.MySerializable.DeepClone[T](T obj) at B.MySerializable.CopyMeBySerialization() 

这是正在发生的事情:

  • 当调用formatter.Deserialize(ms) ,它使用存储在MemoryStream中的信息来确定它需要创建什么类型的对象(以及创建该对象所需的程序集)。
  • 它发现它需要B.dll并尝试加载它(从默认的“加载”上下文)。
  • 找不到当前加载的B.dll(因为它是在“LoadFrom”上下文中加载的)。
  • 因此,尝试在通常的位置找到B.dll – 它可以在ApplicationBase目录中找到并加载。
  • 此B.dll中的所有类型都被视为与其他B.dll中的类型不同。 因此,表达式(T)formatter.Deserialize(ms)强制转换失败。

补充说明:

  • 如果B.dll不存在于A.exe可以使用Assembly.Load找到它的地方,那么会有一个SerializationException ,而消息无法找到程序集’B,Version = 1.0.0.0,Culture =中性,PublicKeyToken = null’。
  • 即使使用签名程序集也会出现同样的问题,但对于签名程序集更令人担忧的是它可以加载已签名程序集的不同版本 。 也就是说,如果“LoadFrom”上下文中的B.dll是1.0.0.0,但是在主目录中找到的B.dll是2.0.0.0,则序列化代码仍会加载错误的版本B.dll来进行反序列化。
  • 我展示的DeepClone代码似乎是在对象上进行深度克隆的更流行的方法之一。 请参阅: C#中的深度克隆对象 。

因此,从加载到“LoadFrom”上下文中的任何代码,您都无法成功使用反序列化(无需跳过额外的循环以允许程序集在默认的“加载”上下文中成功加载)。