I've created a Console application, A.exe, which indirectly loads (via `Assembly.LoadFrom) and calls (via reflection) code from a class library, B.dll.
- A.exe does not (necessarily) have a reference to B.dll, but B.dll should exist in the same directory as A.exe
- A copy of of B.dll should be placed into another directory (here I've used the subdirectory called LoadFrom), this is the location we will use
Assembly.LoadFrom
on.
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(@".LoadFromB.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);
}
}
}
}
Output
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:DevinDebugB.dll'.
Type B originates from 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
in the context 'LoadFrom' at location 'c:DevinDebugLoadFromB.dll'.
at B.MySerializable.DeepClone[T](T obj)
at B.MySerializable.CopyMeBySerialization()
Here's what is happening:
- When the call to
formatter.Deserialize(ms)
is made, it uses the information stored in the MemoryStream to determine what type of object it needs to create (and which assembly it needs in order to create that object).
- It finds that it needs B.dll and attempts to Load it (from the default "Load" context).
- The currently loaded B.dll is not found (because it was loaded in the "LoadFrom" context).
- Thus, an attempt is made to find B.dll in the usual locations--it is found in the ApplicationBase directory and is loaded.
- All types in this B.dll are considered different types that those from the other B.dll. Thus the cast in the expression
(T)formatter.Deserialize(ms)
fails.
Additional notes:
- If the B.dll had not existed somewhere where A.exe could find it using
Assembly.Load
, then instead of an InvalidCastException
, there would be a SerializationException
with the message Unable to find assembly 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
- The same problem occurs even with signed assemblies, but what is more alarming with signed assemblies is that it can load a different version of the signed assembly. That is, if B.dll in the "LoadFrom" context is 1.0.0.0, but the B.dll found in the main directory is 2.0.0.0, the serialization code will still load the wrong version B.dll to do deserialization.
- The
DeepClone
code I've shown seems to be one of the more popular ways to do a deep clone on an object. See: Deep cloning objects in C#.
So, from any code that was loaded into the "LoadFrom" context, you cannot use deserialization successfully (without jumping through additional hoops to allow the assembly to successfully load in the default "Load" context).
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…