Refleksja: Jak wywołać metodę z parametrami

197

Próbuję wywołać metodę poprzez odbicie z parametrami i otrzymuję:

obiekt nie pasuje do typu docelowego

Jeśli wywołam metodę bez parametrów, działa dobrze. Na podstawie następującego kodu, jeśli wywołam metodę Test("TestNoParameters"), działa dobrze. Jednak jeśli zadzwonię Test("Run"), otrzymam wyjątek. Czy coś jest nie tak z moim kodem?

Moim początkowym celem było przekazanie tablicy obiektów, public void Run(object[] options)ale to nie zadziałało i próbowałem czegoś prostszego, np. Łańcucha bez powodzenia.

// 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
źródło
4
poprawna linia to object [] parametersArray = new object [] {new object [] {"Hello"}};
Nick Kovalsky

Odpowiedzi:

236

Zmień „methodInfo” na „classInstance”, tak jak w wywołaniu z tablicą parametrów zerowych.

  result = methodInfo.Invoke(classInstance, parametersArray);
womp
źródło
Działa to, z wyjątkiem pracy z wystąpieniem zespołu zdalnego. Problem polegał na tym, że wylewa się ten sam błąd, co nie jest zbyt pomocne. Spędziłem kilka godzin, próbując to naprawić, i opublikowałem nowe ogólne rozwiązanie zarówno w mojej sprawie, jak i tutaj. Na wypadek, gdyby ktoś tego potrzebował :)
Martin Kool,
4
jeśli parametry są wielu typów, jaka powinna być tablica? tablica obiektów?
Radu Vlad,
Tak, powinien to być obiekt [], jeśli w argumentach występuje wiele typów
Martin Johansson,
29

Masz tam błąd

result = methodInfo.Invoke(methodInfo, parametersArray);

powinno być

result = methodInfo.Invoke(classInstance, parametersArray);
Oleg I.
źródło
24

Podstawowy błąd jest tutaj:

result = methodInfo.Invoke(methodInfo, parametersArray); 

Wywołujesz metodę na instancji MethodInfo. Musisz przekazać instancję typu obiektu, który chcesz wywołać.

result = methodInfo.Invoke(classInstance, parametersArray);
Jason
źródło
11

Dostarczone rozwiązanie nie działa w przypadku instancji typów ładowanych ze zdalnego zestawu. Aby to zrobić, oto rozwiązanie, które działa we wszystkich sytuacjach, co obejmuje jawne ponowne mapowanie typu zwróconego przez wywołanie CreateInstance.

W ten sposób muszę utworzyć moją classInstance, ponieważ została ona umieszczona w zdalnym zestawie.

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

Jednak nawet przy powyższej odpowiedzi nadal pojawiałby się ten sam błąd. Oto jak to zrobić:

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

Następnie wykonaj czynności, o których wspominali inni użytkownicy.

Martin Kool
źródło
5

Użyłbym go w ten sposób, jest o wiele krótszy i nie spowoduje żadnych problemów

        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.
źródło
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
źródło
3

Próbowałem pracować ze wszystkimi sugerowanymi odpowiedziami powyżej, ale wydaje mi się, że nic nie działa. Staram się więc wyjaśnić, co mi tu zadziałało.

Uważam, że jeśli wywołujesz jakąś metodę, jak Mainponiżej, lub nawet z jednym parametrem, jak w swoim pytaniu, wystarczy zmienić typ parametru z stringna, objectaby to zadziałało. Mam klasę jak poniżej

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

    }
}

Następnie musisz przekazać parametrArray wewnątrz tablicy obiektów, jak poniżej, podczas jej wywoływania. Poniższa metoda jest tym, czego potrzebujesz do pracy

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

Ta metoda ułatwia wywołanie metody, można ją wywołać w następujący sposób

ExecuteWithReflection("Hello");
ExecuteWithReflection("Run","Vinod");
ExecuteWithReflection("TestNoParameters");
ExecuteWithReflection("Execute",new object[]{"Vinod","Srivastav"});
Vinod Srivastav
źródło
1

Przywołuję średnią ważoną poprzez odbicie. I zastosował metodę z więcej niż jednym parametrem.

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
źródło
0
string result = this.GetType().GetMethod("Print").Invoke(this, new object[]{"firstParam", 157, "third_Parammmm" } );

jeśli nie jest to zewnętrzny plik .dll (zamiast tego this.GetType()możesz użyć typeof(YourClass)).

ps publikuje tę odpowiedź, ponieważ wielu odwiedzających wchodzi tutaj w celu uzyskania tej odpowiedzi.

T.Todua
źródło