Reflexión: cómo invocar el método con parámetros

197

Estoy tratando de invocar un método mediante reflexión con parámetros y obtengo:

el objeto no coincide con el tipo de destino

Si invoco un método sin parámetros, funciona bien. Basado en el siguiente código si llamo al método Test("TestNoParameters"), funciona bien. Sin embargo, si llamo Test("Run"), recibo una excepción. ¿Hay algún problema con mi código?

Mi propósito inicial era pasar una serie de objetos, por ejemplo, public void Run(object[] options)pero esto no funcionó y probé algo más simple, por ejemplo, una cadena sin éxito.

// Assembly1.dll
namespace TestAssembly
{
    public class Main
    {
        public void Run(string parameters)
        { 
            // Do something... 
        }
        public void TestNoParameters()
        {
            // Do something... 
        }
    }
}

// Executing Assembly.exe
public class TestReflection
{
    public void Test(string methodName)
    {
        Assembly assembly = Assembly.LoadFile("...Assembly1.dll");
        Type type = assembly.GetType("TestAssembly.Main");

        if (type != null)
        {
            MethodInfo methodInfo = type.GetMethod(methodName);

            if (methodInfo != null)
            {
                object result = null;
                ParameterInfo[] parameters = methodInfo.GetParameters();
                object classInstance = Activator.CreateInstance(type, null);

                if (parameters.Length == 0)
                {
                    // This works fine
                    result = methodInfo.Invoke(classInstance, null);
                }
                else
                {
                    object[] parametersArray = new object[] { "Hello" };

                    // The invoke does NOT work;
                    // it throws "Object does not match target type"             
                    result = methodInfo.Invoke(methodInfo, parametersArray);
                }
            }
        }
    }
}
Ioannis
fuente
44
la línea correcta sería object [] parameterArray = new object [] {new object [] {"Hello"}};
Nick Kovalsky

Respuestas:

236

Cambie "methodInfo" a "classInstance", al igual que en la llamada con la matriz de parámetros nulos.

  result = methodInfo.Invoke(classInstance, parametersArray);
womp
fuente
Esto funciona, excepto cuando se trabaja con una instancia de un ensamblaje remoto. El problema fue que derrama el mismo error que no es muy útil. Pasé varias horas tratando de solucionarlo, y publiqué una nueva solución general tanto para mi caso como para el que se proporciona aquí. En caso de que alguien pueda necesitarlo :)
Martin Kool
44
Si los parámetros son de varios tipos, ¿cómo debería ser la matriz? una serie de objetos ??
Radu Vlad
Sí, debería ser un objeto [] si hay varios tipos de argumentos
Martin Johansson
29

Tienes un error ahí mismo

result = methodInfo.Invoke(methodInfo, parametersArray);

debería ser

result = methodInfo.Invoke(classInstance, parametersArray);
Oleg I.
fuente
24

Un error fundamental está aquí:

result = methodInfo.Invoke(methodInfo, parametersArray); 

Estás invocando el método en una instancia de MethodInfo. Debe pasar una instancia del tipo de objeto sobre el que desea invocar.

result = methodInfo.Invoke(classInstance, parametersArray);
jason
fuente
11

La solución proporcionada no funciona para instancias de tipos cargados desde un ensamblaje remoto. Para hacer eso, aquí hay una solución que funciona en todas las situaciones, que implica una reasignación de tipo explícito del tipo devuelto a través de la llamada CreateInstance.

Así es como necesito crear mi classInstance, ya que se encuentra en un ensamblado remoto.

// sample of my CreateInstance call with an explicit assembly reference
object classInstance = Activator.CreateInstance(assemblyName, type.FullName); 

Sin embargo, incluso con la respuesta proporcionada anteriormente, aún obtendría el mismo error. Aquí es cómo hacerlo:

// first, create a handle instead of the actual object
ObjectHandle classInstanceHandle = Activator.CreateInstance(assemblyName, type.FullName);
// unwrap the real slim-shady
object classInstance = classInstanceHandle.Unwrap(); 
// re-map the type to that of the object we retrieved
type = classInstace.GetType(); 

Luego haz lo que los otros usuarios mencionados aquí.

Martin Kool
fuente
5

Lo usaría así, es mucho más corto y no dará ningún problema

        dynamic result = null;
        if (methodInfo != null)
        {
            ParameterInfo[] parameters = methodInfo.GetParameters();
            object classInstance = Activator.CreateInstance(type, null);
            result = methodInfo.Invoke(classInstance, parameters.Length == 0 ? null : parametersArray);
        }
Nick N.
fuente
3
 Assembly assembly = Assembly.LoadFile(@"....bin\Debug\TestCases.dll");
       //get all types
        var testTypes = from t in assembly.GetTypes()
                        let attributes = t.GetCustomAttributes(typeof(NUnit.Framework.TestFixtureAttribute), true)
                        where attributes != null && attributes.Length > 0
                        orderby t.Name
                        select t;

        foreach (var type in testTypes)
        {
            //get test method in types.
            var testMethods = from m in type.GetMethods()
                              let attributes = m.GetCustomAttributes(typeof(NUnit.Framework.TestAttribute), true)
                              where attributes != null && attributes.Length > 0
                              orderby m.Name
                              select m;

            foreach (var method in testMethods)
            {
                MethodInfo methodInfo = type.GetMethod(method.Name);

                if (methodInfo != null)
                {
                    object result = null;
                    ParameterInfo[] parameters = methodInfo.GetParameters();
                    object classInstance = Activator.CreateInstance(type, null);

                    if (parameters.Length == 0)
                    {
                        // This works fine
                        result = methodInfo.Invoke(classInstance, null);
                    }
                    else
                    {
                        object[] parametersArray = new object[] { "Hello" };

                        // The invoke does NOT work;
                        // it throws "Object does not match target type"             
                        result = methodInfo.Invoke(classInstance, parametersArray);
                    }
                }

            }
        }
M Fatih Koca
fuente
3

Traté de trabajar con todas las respuestas sugeridas anteriormente, pero nada parece funcionar para mí. Así que estoy tratando de explicar lo que funcionó para mí aquí.

Creo que si está llamando a algún método como el Mainsiguiente o incluso con un solo parámetro como en su pregunta, solo tiene que cambiar el tipo de parámetro de stringa objectpara que esto funcione. Tengo una clase como abajo

//Assembly.dll
namespace TestAssembly{
    public class Main{

        public void Hello()
        { 
            var name = Console.ReadLine();
            Console.WriteLine("Hello() called");
            Console.WriteLine("Hello" + name + " at " + DateTime.Now);
        }

        public void Run(string parameters)
        { 
            Console.WriteLine("Run() called");
            Console.Write("You typed:"  + parameters);
        }

        public string TestNoParameters()
        {
            Console.WriteLine("TestNoParameters() called");
            return ("TestNoParameters() called");
        }

        public void Execute(object[] parameters)
        { 
            Console.WriteLine("Execute() called");
           Console.WriteLine("Number of parameters received: "  + parameters.Length);

           for(int i=0;i<parameters.Length;i++){
               Console.WriteLine(parameters[i]);
           }
        }

    }
}

Luego debe pasar el parámetroArray dentro de una matriz de objetos como a continuación mientras lo invoca. El siguiente método es lo que necesita para trabajar.

private void ExecuteWithReflection(string methodName,object parameterObject = null)
{
    Assembly assembly = Assembly.LoadFile("Assembly.dll");
    Type typeInstance = assembly.GetType("TestAssembly.Main");

    if (typeInstance != null)
    {
        MethodInfo methodInfo = typeInstance.GetMethod(methodName);
        ParameterInfo[] parameterInfo = methodInfo.GetParameters();
        object classInstance = Activator.CreateInstance(typeInstance, null);

        if (parameterInfo.Length == 0)
        {
            // there is no parameter we can call with 'null'
            var result = methodInfo.Invoke(classInstance, null);
        }
        else
        {
            var result = methodInfo.Invoke(classInstance,new object[] { parameterObject } );
        }
    }
}

Este método facilita la invocación del método, se puede llamar de la siguiente manera

ExecuteWithReflection("Hello");
ExecuteWithReflection("Run","Vinod");
ExecuteWithReflection("TestNoParameters");
ExecuteWithReflection("Execute",new object[]{"Vinod","Srivastav"});
Vinod Srivastav
fuente
1

Invoco el promedio ponderado a través de la reflexión. Y había utilizado el método con más de un parámetro.

Class cls = Class.forName(propFile.getProperty(formulaTyp));// reading class name from file

Object weightedobj = cls.newInstance(); // invoke empty constructor

Class<?>[] paramTypes = { String.class, BigDecimal[].class, BigDecimal[].class }; // 3 parameter having first is method name and other two are values and their weight
Method printDogMethod = weightedobj.getClass().getMethod("applyFormula", paramTypes); // created the object 
return BigDecimal.valueOf((Double) printDogMethod.invoke(weightedobj, formulaTyp, decimalnumber, weight)); calling the method
Sachin Pete
fuente
0
string result = this.GetType().GetMethod("Print").Invoke(this, new object[]{"firstParam", 157, "third_Parammmm" } );

si no es .dll externo (en lugar de this.GetType(), puede usartypeof(YourClass) ).

ps publicando esta respuesta porque muchos visitantes ingresan aquí para esta respuesta.

T.Todua
fuente