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

时间:2014-04-23 21:51:04

标签: c# serialization .net-assembly

Suzanne Cook's .NET CLR Notes中,她谈到了“LoadFrom”背景的危险。具体地,

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

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

1 个答案:

答案 0 :(得分:4)

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

  • 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>(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并尝试加载它(来自默认的&#34;加载&#34;上下文)。
  • 找不到当前加载的B.dll(因为它已加载到&#34; LoadFrom&#34;上下文中)。
  • 因此,尝试在通常的位置找到B.dll - 它可以在ApplicationBase目录中找到并加载。
  • 此B.dll中的所有类型都被认为是来自其他B.dll的类型。因此,表达式(T)formatter.Deserialize(ms)中的强制转换失败。

附加说明:

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

因此,从任何加载到&#34; LoadFrom&#34;上下文,你不能成功使用反序列化(没有跳过额外的箍以允许程序集成功加载默认的&#34;加载&#34;上下文)。