C#: ¿El objeto instanciado por reflexión arroja un error de transmisión?

Estoy trabajando en una aplicación de C# que se supone que debe examinar otros ejecutables de C# y afirmar, a través de pruebas unitarias, ciertas propiedades sobre las interfaces que exponen. (Para el contexto, esta es una aplicación de calificación para un curso de CS; la tarea es analizar rangos numéricos de un archivo de texto y devolverlos de acuerdo con una interfaz fija).

Hasta ahora, he logrado:

  • Cargue el ejecutable como una variable assembly, utilizando Assembly.LoadFrom(string)
  • Obtenga el tipo de interfaz en cuestión, usando assembly.GetType(string)
  • Encuentre un tipo de implementación para la interfaz, nuevamente con assembly.getType(string)
  • Crea una instancia del tipo de implementación en un dynamic objeto, usando type.GetConstructor(Type[]) y constructor.Invoke(Object[])

En este punto, tengo un dynamic objeto loader que sé implementa la interfaz que estoy probando. Quiero llamar a uno de los métodos de interfaz en obj, así que ejecuto:

dynamic rangeSet = loader.GetRangeSetFromFile (inputFile); // inputFile is a string

Esto arroja un InvalidCastException con el siguiente trazo:

System.InvalidCastException : Cannot cast from source type to destination type.
at SwapAssignment3.Implementations.RangeLoaderAdapter.GetRangeSetFromFile (string) <IL 0x0001e, 0x00066>
at (wrapper dynamic-method) object.CallSite.Target (System.Runtime.CompilerServices.Closure,System.Runtime.CompilerServices.CallSite,object,string) <IL 0x00036, 0x0007b>
at System.Dynamic.UpdateDelegates.UpdateAndExecute2<object, string, object> (System.Runtime.CompilerServices.CallSite,object,string) <0x003cf>
at AssignmentTests.R3Test.TestLoadingViaInterface () [0x00054] in /Users/tim/Dropbox/Courses/CSSE375-TA/AssignmentTests/AssignmentTests/R3Test.cs:82
at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&) <0x00003>
at System.Reflection.MonoMethod.Invoke (object,System.Reflection.BindingFlags,System.Reflection.Binder,object[],System.Globalization.CultureInfo) <IL 0x000db, 0x00147>

¿Por qué se lanzaría este error? Ejecutar el ejecutable en cuestión funciona bien por sí solo; este error solo aparece en el contexto de mi aplicación de prueba. el cuerpo del GetRangeSetFromFile método es el siguiente:

public IRangeSet GetRangeSetFromFile(string filePath)
{
    var newRange = new RangeStorage();
    _fileProcessor.ProcessFile(filePath);
    newRange.StoreElements((List<IRange>) _fileProcessor.GetStorage());
    return newRange;
}

Tengo buenas razones para creer (a partir de la salida del programa, entre otras cosas) que el error de conversión se genera desde la tercera línea, con el List<IRange> emitir; sin embargo, dado que el seguimiento proporciona ubicaciones de IL, no estoy 100 % seguro de esto, y no sé por qué ese lanzamiento fallaría en primer lugar, ya que funciona bien si el programa se ejecuta solo (fuera de mi ensayador).

Mi pregunta principal es: ¿Por qué se genera este error de conversión y cómo puedo evitarlo?

Edit: a pedido, el código de prueba equivale a lo siguiente:

Type interfaceType = assembly.GetType("IRangeLoader");
List<Type> implementingTypes = new List<Type> (assembly.GetTypes ())
                                        .FindAll ((Type t) => t.IsClass)
                                        .FindAll ((Type t) => (new List<Type> (t.GetInterfaces ())).Contains (interfaceType));
Type implementingType = implementingTypes[0];
ConstructorInfo ctor = implementationType.GetConstructor (new Type[] {});
dynamic loader = ctor.Invoke (new Object[] {});
dynamic rangeSet = loader.GetRangeSetFromFile ("sample range.txt");

preguntado el 03 de mayo de 12 a las 18:05

es la dinamica loader tipo considerado como tipo X, o a partir de la interfaz Y? Podría marcar la diferencia si el método de interfaz se implementa explícitamente. -

Algo para probar: extraiga una línea separada var storage = _fileProcessor.GetStorage(), y luego en un depurador mire storage.GetType() para ver si es lo que espera. -

¿Es SwapAssignment3.Implementations.RangeLoaderAdapter algo que está creando? Parece que está jugando con los códigos MSIL/Emit en alguna parte y pueden estar equivocados. -

@Tejs: solo intento acceder a los métodos definidos en la interfaz en loader. Para ese propósito, estoy pensando en ello como Y. -

@JasonMalinowski: De hecho, es lo que esperaba. -

2 Respuestas

Ok, el siguiente código pareció funcionar al invocar el método en otro ensamblado:

Assembly testAssembly = Assembly.LoadFile(<path>);

var interfaceType = testAssembly.GetTypes().Where(x => x.Name == "ISampleInterface").FirstOrDefault();

if(interfaceType != null)
{
    var implementingType = testAssembly.GetTypes().Where(typ => type.GetInterfaces().Any(iface => iface == interfaceType)).FirstOrDefault();

    if(implementingType != null)
    {
        dynamic obj = Activator.CreateInstance(implementingType);

        dynamic result = obj.SampleInterfaceMethod();

        Console.WriteLine(result);
    }
}

Intenta usar algo de esto. Pude llamar a ese objeto y luego recuperar el resultado de ese método.

contestado el 03 de mayo de 12 a las 19:05

¿El método SampleInterfaceMethod hacer algun casting? Siento que ese es el error con el que me estoy topando. Cambiar mi propio código para usar Activator.CreateInstance en lugar de conseguir el ConstructorInfo sigue arrojando el mismo error. - Tim

El mío simplemente devolvió una cadena "HelloWorld" de un object SampleInterfaceMethod() contrato de interfaz. Entonces, en ese sentido, solo estaba boxeando el tipo. ¿Cuál es el tipo de retorno de _fileProcessor.GetStorage()? - Tejas

Es de tipo IRangeSet, que es otra interfaz que se garantiza que está en el ensamblado cargado pero que mi proyecto de prueba no conocía originalmente. - Tim

¿Cómo es IRangeSet y List<IRange> incluso en la misma jerarquía de tipos entonces? - Tejas

Esto es lo que sucede cuando confía en que los estudiantes presenten soluciones que funcionen. El probador está detectando correctamente un error porque el estudiante hace algunos lanzamientos ilegales :) ¡Gracias por toda su ayuda! - Tim

Creo que esta es una gran idea, pero realmente parece que te estás inclinando demasiado a favor de los estudiantes aquí.

Podría proporcionar un conjunto (por ejemplo, CSxxxx.Interfaces.dll) que requiere que sus estudiantes hagan referencia e implementen. Cargar las implementaciones es bastante simple desde allí.

También podría proporcionar un conjunto de pruebas que probablemente arrojarían una C más o menos si todas pasaran. Entonces su suite probaría todo. Si fuera un poco tortuoso y pudiera contar con algunos de sus estudiantes para extender las pruebas dadas, entonces podría ejecutar las pruebas de StudentX contra todas las demás implementaciones y otorgar puntos de bonificación por romper otras implementaciones.

contestado el 03 de mayo de 12 a las 21:05

Si bien tenemos un conjunto común de interfaces a las que deben hacer referencia, estamos (lamentablemente) desarrollando el probador después del hecho; todos los envíos ya están y no tenemos nada compartido para usar. ¡Sin embargo, definitivamente transmitiré este consejo a los alumnos del próximo año! - Tim

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.