c# - una - no se puede cargar el archivo o ensamblado restsharp
Cómo reproducir InvalidCastException cuando se vincula a un ensamblado en el contexto LoadFrom (1)
En .NET CLR Notes de Suzanne Cook, ella habla sobre los peligros del contexto "LoadFrom". Específicamente,
- Si un ensamblaje de contexto de carga intenta cargar este ensamblado por el nombre para mostrar, no se encontrará por defecto (por ejemplo, cuando mscorlib.dll deserializa este ensamblaje)
- Peor aún, un ensamblado con la misma identidad pero en una ruta diferente podría encontrarse en la ruta de exploración, lo que ocasionaría una InvalidCastException, MissingMethodException o un comportamiento de método inesperado más adelante.
¿Cómo se reproduce este comportamiento con la deserialización, pero sin cargar explícitamente dos versiones diferentes del ensamblaje?
Creé una aplicación de consola, A.exe, que carga indirectamente (a través de `Assembly.LoadFrom) y llama (mediante reflexión) el código de una biblioteca de clases, B.dll.
- A.exe no tiene (necesariamente) una referencia a B.dll, pero B.dll debe existir en el mismo directorio que A.exe
- Se debe colocar una copia de B.dll en otro directorio (aquí he usado el subdirectorio llamado LoadFrom), esta es la ubicación en la que usaremos
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);
}
}
}
}
Salida
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()
Esto es lo que está sucediendo:
- Cuando se realiza la llamada a
formatter.Deserialize(ms)
, utiliza la información almacenada en MemoryStream para determinar qué tipo de objeto necesita crear (y qué ensamblaje necesita para crear ese objeto). - Encuentra que necesita B.dll e intenta cargarlo (desde el contexto predeterminado "Cargar").
- El B.dll cargado actualmente no se encuentra (porque se cargó en el contexto "LoadFrom").
- Por lo tanto, se intenta encontrar B.dll en las ubicaciones habituales, se encuentra en el directorio ApplicationBase y se carga.
- Todos los tipos en este B.dll se consideran tipos diferentes a los de los otros B.dll. Por lo tanto, el molde en la expresión
(T)formatter.Deserialize(ms)
falla.
Notas adicionales:
- Si el B.dll no hubiera existido en algún lugar donde A.exe pudiera encontrarlo usando
Assembly.Load
, entonces en lugar de unaInvalidCastException
, habría unaSerializationException
con el mensaje Imposible encontrar el ensamblado ''B, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null ''. - El mismo problema ocurre incluso con ensamblajes firmados, pero lo que es más alarmante con los ensamblados firmados es que puede cargar una versión diferente del ensamblado firmado. Es decir, si B.dll en el contexto "LoadFrom" es 1.0.0.0, pero el B.dll encontrado en el directorio principal es 2.0.0.0, el código de serialización todavía cargará la versión incorrecta B.dll para hacer la deserialización.
- El código de
DeepClone
que he mostrado parece ser una de las formas más populares de hacer un clon profundo en un objeto. Ver: objetos de clonación profunda en C # .
Por lo tanto, desde cualquier código que se haya cargado en el contexto "LoadFrom", no se puede utilizar la deserialización con éxito (sin saltar a través de aros adicionales para permitir que el ensamblaje se cargue correctamente en el contexto "Cargar" predeterminado).