Reflexão: Como invocar o método com parâmetros

197

Estou tentando invocar um método via reflexão com parâmetros e recebo:

objeto não corresponde ao tipo de destino

Se eu invocar um método sem parâmetros, ele funcionará bem. Com base no código a seguir, se eu chamar o método Test("TestNoParameters"), ele funciona bem. No entanto, se eu ligar Test("Run"), recebo uma exceção. Há algo de errado com o meu código?

Meu objetivo inicial era passar uma matriz de objetos, por exemplo, public void Run(object[] options)mas isso não funcionou e eu tentei algo mais simples, por exemplo, string sem sucesso.

// 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
fonte
4
a linha correta seria objeto [] parametersArray = novo objeto [] {novo objeto [] {"Olá"}};
Nick Kovalsky

Respostas:

236

Altere "methodInfo" para "classInstance", assim como na chamada com a matriz de parâmetros nulos.

  result = methodInfo.Invoke(classInstance, parametersArray);
womp
fonte
Isso funciona, exceto ao trabalhar com uma instância de um assembly remoto. O problema era que ele derrama o mesmo erro, o que não ajuda muito. Passei várias horas tentando consertá-lo e publiquei uma nova solução geral para o meu caso e o fornecido aqui. No caso de alguém pode precisar dele :)
Martin Kool
4
se os parâmetros são de vários tipos, como deve ser a matriz? uma matriz de objetos?
Radu Vlad
Sim, deve ser um objeto [] se houver vários tipos de argumentos
Martin Johansson
29

Você tem um bug aí

result = methodInfo.Invoke(methodInfo, parametersArray);

deveria ser

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

Um erro fundamental está aqui:

result = methodInfo.Invoke(methodInfo, parametersArray); 

Você está chamando o método em uma instância de MethodInfo. Você precisa passar em uma instância do tipo de objeto que deseja chamar.

result = methodInfo.Invoke(classInstance, parametersArray);
Jason
fonte
11

A solução fornecida não funciona para instâncias de tipos carregados de um assembly remoto. Para fazer isso, aqui está uma solução que funciona em todas as situações, que envolve um mapeamento explícito de tipo do tipo retornado pela chamada CreateInstance.

É assim que preciso criar meu classInstance, pois ele estava localizado em um assembly remoto.

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

No entanto, mesmo com a resposta fornecida acima, você ainda receberá o mesmo erro. Aqui está como proceder:

// 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(); 

Em seguida, faça como os outros usuários mencionados aqui.

Martin Kool
fonte
5

Eu usaria assim, é bem mais curto e não dará problemas

        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.
fonte
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
fonte
3

Tentei trabalhar com todas as respostas sugeridas acima, mas nada parece funcionar para mim. Então, eu estou tentando explicar o que funcionou para mim aqui.

Acredito que se você está chamando algum método como o Mainabaixo ou mesmo com um único parâmetro, como na sua pergunta, basta alterar o tipo de parâmetro de stringpara objectpara que isso funcione. Eu tenho uma aula como abaixo

//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]);
           }
        }

    }
}

Então você deve passar o parameterArray dentro de uma matriz de objetos como abaixo, enquanto o chama. O método a seguir é o que você precisa para trabalhar

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 } );
        }
    }
}

Esse método facilita a chamada do método, pode ser chamado da seguinte maneira

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

Estou invocando a média ponderada através da reflexão. E tinha usado o método com mais de um 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
fonte
0
string result = this.GetType().GetMethod("Print").Invoke(this, new object[]{"firstParam", 157, "third_Parammmm" } );

se não for .dll externo (em vez de this.GetType(), você pode usar typeof(YourClass)).

ps postando esta resposta porque muitos visitantes entram aqui para esta resposta.

T.Todua
fonte