Riflessione: come invocare il metodo con parametri


197

Sto cercando di invocare un metodo tramite la riflessione con parametri e ottengo:

l'oggetto non corrisponde al tipo di destinazione

Se invoco un metodo senza parametri, funziona benissimo. Basato sul seguente codice se chiamo il metodo Test("TestNoParameters"), funziona benissimo. Tuttavia, se chiamo Test("Run"), ottengo un'eccezione. C'è qualcosa che non va nel mio codice?

Il mio scopo iniziale era quello di passare una serie di oggetti, ad esempio public void Run(object[] options)ma questo non funzionava e ho provato qualcosa di più semplice, ad esempio una stringa senza successo.

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

4
la riga corretta sarebbe object [] parameterArray = new object [] {new object [] {"Hello"}};
Nick Kovalsky,

Risposte:


236

Cambia "methodInfo" in "classInstance", proprio come nella chiamata con l'array di parametri null.

  result = methodInfo.Invoke(classInstance, parametersArray);

Funziona, tranne quando si lavora con un'istanza di un assembly remoto. Il problema era che si riversava lo stesso errore che non è molto utile. Ho trascorso diverse ore a cercare di risolverlo e ho pubblicato una nuova soluzione generale sia per il mio caso che per quella fornita qui. Nel caso in cui qualcuno possa averne bisogno :)
Martin Kool,

4
se i parametri sono di più tipi, come dovrebbe essere l'array? una matrice di oggetti ??
Radu Vlad,

Sì, dovrebbe essere un oggetto [] se ci sono più tipi di argomenti
Martin Johansson,

29

Hai un bug proprio lì

result = methodInfo.Invoke(methodInfo, parametersArray);

dovrebbe essere

result = methodInfo.Invoke(classInstance, parametersArray);

24

Un errore fondamentale è qui:

result = methodInfo.Invoke(methodInfo, parametersArray); 

Stai invocando il metodo su un'istanza di MethodInfo. Devi passare un'istanza del tipo di oggetto su cui vuoi invocare.

result = methodInfo.Invoke(classInstance, parametersArray);

11

La soluzione fornita non funziona per le istanze di tipi caricate da un assembly remoto. Per fare ciò, ecco una soluzione che funziona in tutte le situazioni, che prevede una mappatura esplicita del tipo restituito tramite la chiamata CreateInstance.

È così che devo creare la mia classInstance, poiché si trovava in un assembly remoto.

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

Tuttavia, anche con la risposta fornita sopra, otterrai comunque lo stesso errore. Ecco come procedere:

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

Quindi fai come gli altri utenti menzionati qui.


5

Lo userei in questo modo, in modo più breve e non darà alcun 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);
        }

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

            }
        }

3

Ho provato a lavorare con tutte le risposte suggerite sopra, ma nulla sembra funzionare per me. Quindi sto cercando di spiegare cosa ha funzionato per me qui.

Credo che se stai chiamando un metodo come il Mainseguente o anche con un singolo parametro come nella tua domanda, devi solo cambiare il tipo di parametro da stringa objectper farlo funzionare. Ho una lezione come di seguito

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

    }
}

Quindi devi passare il parametroArray all'interno di un array di oggetti come di seguito mentre lo invochi. Il seguente metodo è ciò di cui hai bisogno per lavorare

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

Questo metodo semplifica il richiamo del metodo, può essere chiamato come segue

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

1

Sto invocando la media ponderata attraverso la riflessione. E aveva usato il metodo con più di un parametro.

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

0
string result = this.GetType().GetMethod("Print").Invoke(this, new object[]{"firstParam", 157, "third_Parammmm" } );

se non è .dll esterno (invece di this.GetType(), potresti usare typeof(YourClass)).

ps pubblicando questa risposta perché molti visitatori entrano qui per questa risposta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.