Avatar billede beef12 Nybegynder
18. maj 2005 - 23:53 Der er 34 kommentarer og
1 løsning

Dynamisk metodekald

Hej C# eksperter,

Jeg sidder og roder med Reflections og har en spøjs problemstilling. Jeg arbejder med Attributes og Reflection og prøver at gennemløbe alle mine attibutes og derefter kalde en bestemt metode som jeg fisker ud af min attribute

Jeg vil gerne kalde en metode dynamisk, således:

object myMethod = methodFor.Name;
forTest.myMethod(1000);

MethodFor.Name er noget jeg henter frem vha. Reflection, og indeholder navnet på metoden.

Som jeg selv lidt havde gættet på forhånd så virker det her ikke. Er der en måde hvor jeg kan kalde en metode dynamisk?
Avatar billede beef12 Nybegynder
18. maj 2005 - 23:54 #1
methodFor.Name kunne for eksempel være MoveNext eller AddTime...

Håber det er til at forstå.
Avatar billede arne_v Ekspert
18. maj 2005 - 23:57 #2
eksempel:

using System;

public class DinKlasse
{
    public void DinMetode()
    {
        Console.WriteLine("Det virker");
    }
    public void DinAndenMetode(int iv, string sv)
    {
        Console.WriteLine(iv + " " + sv);
    }
}

og

using System;
using System.Reflection;

public class TestKlasse
{
    public static void Main(string[] args)
    {
        Object o = Assembly.Load("DinAssembly").CreateInstance("DinKlasse");
        o.GetType().InvokeMember("DinMetode", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, o, null);
        Object[] margs = { 123, "ABC" };
        o.GetType().InvokeMember("DinAndenMetode", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, o, margs);
    }
}
Avatar billede beef12 Nybegynder
19. maj 2005 - 00:00 #3
hvad er Assemly.Load for noget? Hvordan finder jeg ud af hvad "DinAssembly" er?
Avatar billede arne_v Ekspert
19. maj 2005 - 00:06 #4
i det eksempel loades klassen dynamisk fra en DLL fil

hvis du står med et Object i hånden så kan du springe den linie over
Avatar billede beef12 Nybegynder
19. maj 2005 - 00:11 #5
Ok...
Men jeg er ikke sikker om vi er på samme spor. Lad mig prøve at formulere det således:

Kan jeg f.eks. gøre noget så det her virker:

Object myMethod = "MoveNext";
myMethod(1000);

Hvor myMethod skal forstås af compileren som: MoveNext(1000);
Avatar billede beef12 Nybegynder
19. maj 2005 - 00:12 #6
Det er så ikke præcist det jeg vil, men hvis du kan svare på dette, så har jeg de informationer jeg har brug for at komme videre...
Avatar billede arne_v Ekspert
19. maj 2005 - 00:14 #7
det er præcis det mit eksempel viser
Avatar billede arne_v Ekspert
19. maj 2005 - 00:15 #8
du har et objekt o og en string med et metode navn "MoveNext" og et argument 1000

og det kalder du med

Object[] args = { 1000 };
o.GetType().InvokeMember("MoveNext", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, o, args);
Avatar billede beef12 Nybegynder
19. maj 2005 - 00:17 #9
gør den? jeg google'de lige efter "Calling methods dynamically" og fandt følgende stump kode:

  Assembly asm = Assembly.LoadFrom("somewhere.dll");
    Type type = asm.GetType("SomeClass");
    MethodInfo meth = type.GetMethod("SomeMethod");
    Object instance = Activator.CreateInstance(type);
    meth.Invoke(instance,<args>);

Er det ikke rigeligt? Jeg forstår nemlig ikke hvad f.eks. Object[] margs = { 123, "ABC" }; skal eller hvad "DinAndenMetode" er osv.
Avatar billede beef12 Nybegynder
19. maj 2005 - 00:22 #10
Aha... det sidste du skrev virkede nogenlunde... Får en MissingMethodException, da den ikke forstår fra hvilken klasse den stammer fra.
Avatar billede arne_v Ekspert
19. maj 2005 - 00:23 #11
den stykke kode gør jo næsten det samme

Load er erstattet af LoadFrom
CreateInstance er flyttet ned i en seperat linie
InvokeMember er erstattet af GetMethod og Invoke
Avatar billede arne_v Ekspert
19. maj 2005 - 00:24 #12
den leder jo via reflection i objektet o efter den metode - hvis den er der
så bør den finde den

bemærk dog flagene - den leder efter public ikke-static metoder
Avatar billede beef12 Nybegynder
19. maj 2005 - 00:26 #13
Ok... mine metoder er public og ikke statiske.
Avatar billede beef12 Nybegynder
19. maj 2005 - 00:30 #14
Object o = "ListTest"; // klassen den ligger i
Object[] args = { 1000 }; // return parameter
o.GetType().InvokeMember("MoveNext", BindingFlags.DeclaredOnly |
BindingFlags.Public | BindingFlags.Instance |
BindingFlags.InvokeMethod, null, o, args);

Det her fejler stadig med MissingMethodException. Kan du se noget der er galt med det jeg laver?
Avatar billede arne_v Ekspert
19. maj 2005 - 00:33 #15
Object o = "ListTest"; // klassen den ligger i

så er o jo en String - ikke en ListTest
Avatar billede arne_v Ekspert
19. maj 2005 - 00:33 #16
Object o = Activator.CreateInstance("ListTest");

eller sådan noget
Avatar billede beef12 Nybegynder
19. maj 2005 - 00:35 #17
øh, jeg er ikke med... Du mener vel ikke: Object o = String;
Det er vel ikke en lovlig operation?
Avatar billede beef12 Nybegynder
19. maj 2005 - 00:35 #18
ah... jeg læste det forkert... nu er jeg med...
Avatar billede beef12 Nybegynder
19. maj 2005 - 00:43 #19
nå... det virkede heller ikke... den skal bruge en type som er gettype af klassen som bruger en assembly og assembly'en vil den ikke æde...

Suk...
Avatar billede burningice Nybegynder
19. maj 2005 - 07:37 #20
:)

hvordan er ListTest erklæret? Og hvor ligger den henne? I samme assembly som den klasse du vil lave din reflection med, eller et andet sted? I hvilket namespace ligger den i?
Avatar billede nielle Nybegynder
19. maj 2005 - 08:16 #21
Jeg er også nysgerrig. I starten nævnes Attributes og derefter bliver der ikke snakket mere om det. I .Net sammenhæng er der et helt specifikt begreb som hedder en "Attribute", men jeg er langt fra sikker på at det er dette som der refereres til her.

Det ville være rart lige at få de grundlæggende begreber på plads først. Så, sammen med cyberfessor, kunne vi ikke se noget mere af din konkrete kode?
Avatar billede beef12 Nybegynder
19. maj 2005 - 08:54 #22
Cyberfessor: ja, den ligger i samme assembly.

nielle: Det jeg vil er at lave en en klasse som indeholder en række metoder og anden klasse som indeholder testmetoder.
I klasse 1 har jeg f.eks:

        [Test("Performance", "medium")]
        public void Add(T t)
        {
            Array.Resize(ref tList, tList.Length + 1);
            tList[tList.Length - 1] = t;
        }

og den tilhørende testmetode i klasse2:

[TestFor("Add","Performance")]
        public TimeSpan testAdd(long iterateNum)
        {

            TimeSpan startTime = (DateTime.Now.TimeOfDay);
            StringBuilder randomString = new StringBuilder();
            Random randomNumber = new Random();
            string insertChar;
            long i = 0;
            for (i = 0; i < iterateNum; i++)
            {
                insertChar = Convert.ToString(Convert.ToInt32(26 * randomNumber.NextDouble()) + 65);
                new ItuList<string>().Add(insertChar);
            }
            TimeSpan endTime = (DateTime.Now.TimeOfDay);

            return (endTime - startTime);
        }


Så vil jeg i min 3. klasse validere deres attributes op mod hinanden:


                    Type typeFor = typeof(ListTest);
                    TestForAttribute TestForAttr;

                    // hent alle metoder
                    foreach (MethodInfo methodFor in typeFor.GetMethods())
                    {
                        // hent alle attributter
                        foreach (Attribute attrFor in methodFor.GetCustomAttributes(true))
{
  TestForAttr = attrFor as TestForAttribute;
  if (null != TestForAttr)
    {
      if (method.Name == TestForAttr.TargetMethod && TestAttr.TestType == TestForAttr.TestType)
  {
                                   
    Object[] args = { 1000 };
    Object obj = typeFor.InvokeMember(null,
    BindingFlags.DeclaredOnly |
    BindingFlags.Public |
    BindingFlags.Instance | BindingFlags.CreateInstance, null, methodFor.Name, args);
    Console.WriteLine("Type: " + obj.GetType().ToString());
                                    }
                            }
                        }
                    }


Og altså når jeg ser at attributen Testfor har Add og har en Performance test, så skal testmetoden fyres af analyseres.

Men det virker bare ikke....
Avatar billede beef12 Nybegynder
19. maj 2005 - 08:57 #23
fejlmedelelsen er faktisk nu:

MissingMethodException.
Constructor on type 'TestFrameWork.ListTest' not found.
Avatar billede burningice Nybegynder
19. maj 2005 - 09:06 #24
for det første vil jeg nok hente attributterne ud på denne måde:

object[] atts = methodFor.GetCustomAttributes(typeof(TestForAttribute), true);

if (atts.Length > 0) {
  // Der blev fundet en TestForAttribute på metoden
  // Vi skal nu hente den metode som der er angivet i attributen og kalde den

  Klasse1 k1 = new Klasse1();

  foreach (MethodInfo mi in k1.GetType().GetMethods()) {
      if (mi.Name == atts[0].TestMetode) mi.Invoke(k1, new object[] { 1000 });
  }
}
Avatar billede beef12 Nybegynder
19. maj 2005 - 09:17 #25
TargetParameterCountException

Parameter count mismatch. Kommer frem på den allersidste linie :-)
Avatar billede arne_v Ekspert
19. maj 2005 - 09:18 #26
Constructor on type 'TestFrameWork.ListTest' not found

antyder at du ikke har en no argument constructor som de CreateInstance kald kræver
Avatar billede beef12 Nybegynder
19. maj 2005 - 09:23 #27
Jeg har en no arguement constructor i min testklasse ja.
       
public ListTest()
{
}
Avatar billede beef12 Nybegynder
19. maj 2005 - 09:25 #28
jeg fik lige en advarsel fra min Visual Studio. Den siger at ikke alle klasser er sat til at køre i debug mode. Kunne det have noget med sagen at gøre?
Avatar billede beef12 Nybegynder
19. maj 2005 - 13:41 #29
Jeg har besluttet at omstrukture koden og lav det helt om...
Arne_v din kode var sådan set korrekt, smid lige et svar.
Avatar billede arne_v Ekspert
19. maj 2005 - 14:01 #30
ok
Avatar billede nielle Nybegynder
19. maj 2005 - 15:17 #31
Det ser helt klart ud til at være et meget interessant projekt du har gang i der. Men kender du NUnit som netop er et framework som er udviklet til den slags testning?

http://www.nunit.org/

Arne_v har i øvrigt skrevet en artikel om det:

http://www.eksperten.dk/artikler/607
Avatar billede beef12 Nybegynder
19. maj 2005 - 15:39 #32
Ja, det gør jeg - det baserer sig dog på JUnit. Formålet med projektet er at bruge Attributes til at bestemme hvilke metoder/klasser der skal testes og hvordan de skal testes f.eks. Performance eller Unit. Derved slipper man for at skulle pålægge testMetoderne navngivningskonventioner, f.eks testforAddPerformance og parse dem. Så i stedet for kan man tilføje en attribut [Test("Add", "Performance")] som gør det meget mere udvidelsesvenligt... tror jeg... det vil vise sig :-)
Avatar billede beef12 Nybegynder
19. maj 2005 - 15:40 #33
og tak for hjælpen arne. Har oprettet et nyt projekt i Visual Studio og nu virker det... der var et eller andet galt med min assembly.
Avatar billede nielle Nybegynder
19. maj 2005 - 21:35 #34
Hej, igen.

Udfordringen var godt nok lige vel stor til at jeg med god samvittighed kunne tage den som lidt adspredelse i arbejdet. ;^)

Nedenstående kode er baseret på det kode der postet i denne tråd, samt hvad jeg har kunnet gætte mig frem til ud fra fejlbeskeder. Jeg har også måtte lave visse tilpasninger fordi jeg ikke selv køre med 2.0 endnu.

Men, det bedste er nu at det funker. Enjoy. :^)

using System;
using System.Reflection;

using TestFrameWork;
using DetSomSkalTestes;

namespace Eksperten
{
    class Class1
    {
        [STAThread]
        static void Main(string[] args)
        {
            TestEngine Engine = new TestEngine();
            Engine.RunTests();
        }
    }
}

namespace DetSomSkalTestes
{
    class ItuList
    {
        [Test("Performance", "Medium")]
        public int Add(int Tal1, int Tal2)
        {
            return Tal1+Tal2;
        }
    }
}

namespace TestFrameWork
{
    class ListTest
    {
        public ListTest() {}

        [TestFor("Add", "Performance")]
        public TimeSpan testAdd(long iterateNum)
        {
            TimeSpan startTime = DateTime.Now.TimeOfDay;

            Random randomNumber = new Random();
            for (int i=0; i<iterateNum; i++)
            {
                int Res = (new ItuList()).Add(randomNumber.Next(1, 10000), randomNumber.Next(1, 10000));
            }

            TimeSpan endTime = DateTime.Now.TimeOfDay;

            return endTime - startTime;
        }
    }

    class TestAttribute : Attribute
    {
        public readonly string TestType;
        public readonly string[] Misc;

        public TestAttribute(string TestType, params string[] Misc)
        {
            this.TestType = TestType;
            this.Misc = Misc;
        }
    }

    class TestForAttribute : Attribute
    {
        public readonly string TargetMethod;
        public readonly string TestType;

        public TestForAttribute(string TargetMethod, string TestType)
        {
            this.TargetMethod = TargetMethod;
            this.TestType = TestType;
        }
    }

    class TestEngine
    {
        public void RunTests()
        {
            Type type = typeof(ItuList);
            foreach (MethodInfo method in type.GetMethods())
            {
                foreach (Attribute attr in method.GetCustomAttributes(false))
                {
                    TestAttribute TestAttr = attr as TestAttribute;
                    if (TestAttr != null)
                    {
                        Type typeFor = typeof(ListTest);
                        foreach (MethodInfo methodFor in typeFor.GetMethods())
                        {
                            foreach (Attribute attrFor in methodFor.GetCustomAttributes(false))
                            {
                                TestForAttribute TestForAttr = attrFor as TestForAttribute;
                                if (TestForAttr != null)
                                {
                                    if (TestForAttr.TargetMethod == method.Name &&
                                        TestForAttr.TestType == TestAttr.TestType)
                                    {
                                        Console.WriteLine("Method: {0}, Testtype: {1}", method.Name, TestAttr.TestType);
                                        Console.WriteLine("Testing ...");

                                        object[] args = { 100000000 };
                                        object result = methodFor.Invoke(new ListTest(), args);
                                        Console.WriteLine("Type: " + result.ToString());

                                        Console.WriteLine("Done");
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
Avatar billede nielle Nybegynder
20. maj 2005 - 09:01 #35
I NUnit ville samme konstruktion se nogenlunde sådan her ud:

using System;
using NUnit.Framework;

namespace DetSomSkalTestes
{
    class ItuList
    {
        public int Add(int Tal1, int Tal2)
        {
            return Tal1+Tal2;
        }
    }
}

namespace PocEksperten4
{
    using DetSomSkalTestes;

    [TestFixture]
    public class ListTest_PerformanceTest
    {
        [Test]
        public void ListTest_Add()
        {
            long iterateNum = 100000000;

            TimeSpan startTime = DateTime.Now.TimeOfDay;

            Random randomNumber = new Random();
            for (int i=0; i<iterateNum; i++)
            {
                int Res = (new ItuList()).Add(randomNumber.Next(1, 10000), randomNumber.Next(1, 10000));
            }

            TimeSpan endTime = DateTime.Now.TimeOfDay;

            string Performance = string.Format("Performance: {0}", endTime - startTime);
            Assert.Fail("Dette er ikke en fejl. " + Performance);
        }
    }

    [TestFixture]
    public class ListTest_FuctionalityTest
    {
        // Funktionalitets test her.
    }
}

Bortset fra at man er nødt til at bruge Assert.Fail til at meddele hvad performance var - af en eller anden grund har NUnut ikke en Assert.Pass eller en Assert.Message - så kan jeg nu ikke se at det skulle være noget specielt besværligt i at gøre det på den måde. Ja, NUnit er starter fra JUnit men det har udviklet sig videre siden da.

Bortset fra det, så er det som sagt et interessant projekt du har gang i. :^)
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
IT-kurser om Microsoft 365, sikkerhed, personlig vækst, udvikling, digital markedsføring, grafisk design, SAP og forretningsanalyse.

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester