Kaip naudoti refleksiją, kad paskambintumėte bendru metodu?

Koks yra geriausias būdas kreiptis į bendrąjį metodą, kai tipo parametras nežinomas kompiliavimo metu, bet yra gaunamas dinamiškai vykdymo metu?

Apsvarstykite tokį kodo pavyzdį - Example() metodo viduje, kuris yra GenericMethod<T>() būdas skambinti „ GenericMethod<T>() naudojant„ myType kintamajame saugomą myType ?

 public class Sample { public void Example(string typeName) { Type myType = FindType(typeName); // What goes here to call GenericMethod<T>()? GenericMethod<myType>(); // This doesn't work // What changes to call StaticMethod<T>()? Sample.StaticMethod<myType>(); // This also doesn't work } public void GenericMethod<T>() { // ... } public static void StaticMethod<T>() { //... } } 
868
24 окт. paprašė Bevan 24 spalio. 2008-10-24 08:17 '08 8:17 am. 2008-10-24 08:17
@ 8 atsakymai

Jei norite pradėti naudoti metodą, turite naudoti refleksiją, tada „kurti“, pateikdami argumentus, pvz., „ MakeGenericMethod“ :

 MethodInfo method = typeof(Sample).GetMethod("GenericMethod"); MethodInfo generic = method.MakeGenericMethod(myType); generic.Invoke(this, null); 

Dėl statinio metodo null kaip pirmąjį argumentą, kuriuo vadovaujamasi. Tai neturi nieko bendro su visuotiniais metodais - tai tik normalus atspindys.

Kaip jau minėta, daugelis jų yra paprastesni naudojant „C # 4“ naudodami dynamic - jei galite naudoti tipo išvadą, žinoma. Tai nepadeda tais atvejais, kai tipo išvada nėra, pavyzdžiui, konkretus pavyzdys.

948
24 окт. Atsakyti į Jon Skeet spalio 24 d 2008-10-24 09:13 '08 at 9:13 2008-10-24 09:13

Tiesiog pridėkite prie pradinio atsakymo. Nors jis veiks:

 MethodInfo method = typeof(Sample).GetMethod("GenericMethod"); MethodInfo generic = method.MakeGenericMethod(myType); generic.Invoke(this, null); 

Taip pat yra šiek tiek pavojinga, kad prarandate kompiliavimo laiko patikrą. Jei vėliau GenericMethod ir pervadinsite GenericMethod , šis kodas nepastebės ir neveiks paleisties metu. Be to, jei yra po apdorojimo surinkimo (pvz., Nepanaudotų metodų / klasių trikdymas ar ištrynimas), šis kodas taip pat gali sulūžti.

Taigi, jei žinote, kokiu būdu susieti kompiliavimo laiką, ir tai nėra vadinama milijoną kartų, todėl pridėtinės vertės nėra svarbu, pakeisiu šį kodą:

border=0
 Action<> GenMethod = GenericMethod<int>; //change int by any base type //accepted by GenericMethod MethodInfo method = this.GetType().GetMethod(GenMethod.Method.Name); MethodInfo generic = method.MakeGenericMethod(myType); generic.Invoke(this, null); 

Tol, kol nesate labai gražus, čia turite GenericMethod kompiliavimo laiko GenericMethod , o jei reorganizavote, ištrinate ar darote viską su „ GenericMethod , šis kodas ir toliau veiks arba bent pertraukos metu (jei, pavyzdžiui, , ištrinate „ GenericMethod ).

Kitas būdas tai padaryti yra sukurti naują pakavimo klasę ir sukurti ją per „ Activator . Nežinau, ar yra geresnis būdas.

142
27 февр. Atsakymą pateikė Adrian Gallero , vasario 27 d. 2011-02-27 19:11 '11, 19:11, 2011-02-27 19:11

Bendrojo metodo skambinimas tik tipiniu parametru, žinomu tik vykdymo metu, gali būti labai supaprastintas naudojant dynamic o ne refleksijos API.

Norėdami naudoti šį metodą, tipas turi būti žinomas iš faktinio objekto (ir ne tik Type klasės atvejui). Priešingu atveju turite sukurti tokio tipo objektą arba naudoti standartinį atspindžio aprašą API. Objektą galite sukurti naudodami dinaminį metodą . Štai pavyzdys:

 class Alpha { } class Beta { } class Service { public void Process<T>(T item) { Console.WriteLine("item.GetType(): " + item.GetType() + "\ttypeof(T): " + typeof(T)); } } class Program { static void Main(string[] args) { var a = new Alpha(); var b = new Beta(); var service = new Service(); service.Process(a); // Same as "service.Process<Alpha>(a)" service.Process(b); // Same as "service.Process<Beta>(b)" var objects = new object[] { a, b }; foreach (var o in objects) { service.Process(o); // Same as "service.Process<object>(o)" } foreach (var o in objects) { dynamic dynObj = o; service.Process(dynObj); // Or write "service.Process((dynamic)o)" } } } 

Ir čia yra šios programos rezultatai:

 item.GetType(): Alpha typeof(T): Alpha item.GetType(): Beta typeof(T): Beta item.GetType(): Alpha typeof(T): System.Object item.GetType(): Beta typeof(T): System.Object item.GetType(): Alpha typeof(T): Alpha item.GetType(): Beta typeof(T): Beta 

Process yra bendrojo pavyzdžio metodas, kuris užfiksuoja faktinį argumento tipą (naudojant „ GetType() metodą) ir bendrojo parametro tipą (naudojant tipo operatorių).

Atmetę objekto argumentą dynamic tipui, atidedame tipo parametro teikimą iki vykdymo. Kai Process metodas vadinamas dynamic argumentu, kompiliatorius nerūpi argumento tipo. Kompiliatorius generuoja kodą, kuris vykdymo metu patikrina faktinius perduotų argumentų tipus (naudojant atspindį) ir pasirenka geriausią būdą skambinti. Čia yra tik vienas bendras metodas, todėl jis vadinamas teisingo tipo parametru.

Šiame pavyzdyje išvestis yra tokia pati, kaip rašydami:

 foreach (var o in objects) { MethodInfo method = typeof(Service).GetMethod("Process"); MethodInfo generic = method.MakeGenericMethod(o.GetType()); generic.Invoke(service, new object[] { o }); } 

Dinaminė tipo versija tikrai trumpesnė ir lengviau rašoma. Jūs taip pat neturite nerimauti dėl šios funkcijos skambinimo kelis kartus. Kitas kvietimas su tuo pačiu tipo argumentais turėtų būti greitesnis dėl DLR talpyklos mechanizmo. Žinoma, galite rašyti kodą, kuriame naudojami talpykloje esantys delegatai, tačiau naudojant dynamic tipą, jūs gaunate šį elgesį nemokamai.

Jei bendrasis metodas, kurį norite skambinti, neturi parametruojamo tipo argumento (todėl jo tipo parametro negalima daryti išvados), bendrąjį metodą galite įvesti į pagalbinį metodą, kaip nurodyta sekančiame pavyzdyje:

 class Program { static void Main(string[] args) { object obj = new Alpha(); Helper((dynamic)obj); } public static void Helper<T>(T obj) { GenericMethod<T>(); } public static void GenericMethod<T>() { Console.WriteLine("GenericMethod<" + typeof(T) + ">"); } } 

Didesnis tipo saugumas

Tai, kas iš tikrųjų mus išskiria nuo dynamic objekto naudojimo kaip atspindžio API pakeitimo, yra ta, kad prarasite tik tokio tipo kompiliavimo laiko patikrinimą, kurio nežinote prieš vykdant. Kiti argumentai ir metodo pavadinimas yra statiškai analizuojami kompiliatoriaus, kaip įprasta. Jei pašalinsite ar pridėsite daugiau argumentų, pakeisite jų tipus arba pervadinsite metodo pavadinimą, gausite kompiliavimo laiko klaidą. Tai neįvyksta, jei metodo pavadinimą nurodote kaip eilutę Type.GetMethod ir argumentus kaip objektų masyvą MethodInfo.Invoke .

Žemiau pateikiamas paprastas pavyzdys, iliustruojantis, kaip kai kurios klaidos gali būti sugautos kompiliavimo metu (komentuotas kodas) ir kiti vykdymo metu. Jis taip pat rodo, kaip DLR bando nuspręsti, kokį metodą reikia skambinti.

 interface IItem { } class FooItem : IItem { } class BarItem : IItem { } class Alpha { } class Program { static void Main(string[] args) { var objects = new object[] { new FooItem(), new BarItem(), new Alpha() }; for (int i = 0; i < objects.Length; i++) { ProcessItem((dynamic)objects[i], "test" + i, i); //ProcesItm((dynamic)objects[i], "test" + i, i); //compiler error: The name 'ProcesItm' does not //exist in the current context //ProcessItem((dynamic)objects[i], "test" + i); //error: No overload for method 'ProcessItem' takes 2 arguments } } static string ProcessItem<T>(T item, string text, int number) where T : IItem { Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}", typeof(T), text, number); return "OK"; } static void ProcessItem(BarItem item, string text, int number) { Console.WriteLine("ProcessItem with Bar, " + text + ", " + number); } } 

Čia mes vėl vykdome tam tikrą metodą, atmetant argumentą į tipo dynamic . Į vykdymo laiką perkeliamas tik pirmojo tipo argumentų patikrinimas. Gausite kompiliatoriaus klaidą, jei nenurodomas jūsų skambinančio metodo pavadinimas arba kiti argumentai yra neteisingi (neteisingas argumentų skaičius arba netinkami tipai).

Kai perduodate dynamic argumentą metodui, šis skambutis neseniai buvo susietas . Leisti metodo perkrovą vyksta vykdymo metu ir bandoma pasirinkti geriausią perkrovą. Todėl, jei skambinate „ ProcessItem metodu „ BarItem tipo BarItem , tada iš tikrųjų vadinate netradicinį metodą, nes jis geriau tinka šiam tipui. Tačiau, kai jūs perduodate Alpha tipo argumentą, gausite paleisties klaidą, nes nėra jokio metodo, galinčio tvarkyti šį objektą (bendras metodas turi, where T : IItem apribojimas where T : IItem ir Alpha klasė neįgyvendina šios sąsajos). Bet viskas. Kompiliatorius neturi informacijos, kad šis skambutis galioja. Kaip programuotojas, jūs žinote, ir jūs turite įsitikinti, kad šis kodas veikia be klaidų.

Grąžinimo tipas getcha

Kai skambinate į tuščiąjį metodą su dinaminio tipo parametru, jo grąžinimo tipas taip pat gali būti dynamic . Todėl, jei pakeitėte ankstesnį pavyzdį į šį kodą:

 var result = ProcessItem((dynamic)testObjects[i], "test" + i, i); 

tada rezultato objekto tipas bus dynamic . Taip yra dėl to, kad kompiliatorius ne visada žino, kuris metodas bus vadinamas. Jei žinote grąžinto funkcijos skambučio tipą, turite netiesiogiai konvertuoti jį į reikiamą tipą, kad likęs kodo dalis būtų įrašytas statiškai:

 string result = ProcessItem((dynamic)testObjects[i], "test" + i, i); 

Jei tipas neatitiks, gausite vykdymo klaidą.

Tiesą sakant, jei bandysite gauti rezultato vertę ankstesniame pavyzdyje, antrąjame ciklo iteracijoje gausite vykdymo klaidą. Taip yra dėl to, kad bandėte išsaugoti tuščiosios funkcijos grąžinimo vertę.

108
16 марта '14 в 22:21 2014-03-16 22:21 atsakymą pateikė Mariusz Pawelski kovo 14 d. 14 d. 10:21 2014-03-16 22:21

Naudojant „C # 4.0“, refleksija nereikalinga, nes DLR gali jį skambinti naudodamas vykdymo tipus. Kadangi DLR bibliotekos naudojimas yra dinamiškai skausmingas (vietoj to, kad jums būtų sukurtas C # kompiliatoriaus generavimo kodas), „ Dynamitey“ atvirojo kodo aplinka (.net standartas 1.5) suteikia jums paprastą talpyklinę prieigą prie tų pačių skambučių, kuriuos sukurs kompiliatorius jums

 var name = InvokeMemberName.Create; Dynamic.InvokeMemberAction(this, name("GenericMethod", new[]{myType})); var staticContext = InvokeContext.CreateStatic; Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType})); 
11
05 июля '11 в 16:40 2011-07-05 16:40 atsakymą pateikė jbtule liepos 05 '11, 16:40 2011-07-05 16:40

Tai yra tas pats klausimas, kurį paklausiau kitą savaitę: apmąstymai ir paplitę tipai

Tada pažiūrėjau, kaip mano dienoraštyje skambinti bendru perkrovimu: http://www.aaron-powell.com/reflection-and-generics

10
05 дек. atsakymą pateikė Aaron Powell 05 dec. 2017-12-05 06:18 '17 at 6:18 2017-12-05 06:18

Pridėti prie atsakymo Adriana Gallero :

Bendrojo metodo skambinimas iš tipo informacijos apima tris veiksmus.

TL; DR: skambutis gerai žinomam įprastam metodui su tipo objektu gali būti atliekamas naudojant:

 ((Action)GenericMethod<object>) .Method .GetGenericMethodDefinition() .MakeGenericMethod(typeof(string)) .Invoke(this, null); 

kur GenericMethod<object> yra skambinimo metodo pavadinimas ir bet koks tipas, atitinkantis bendruosius apribojimus.

(Veiksmas) atitinka metodo, kuris bus vadinamas, parašu ( Func<string,string,int> arba Action<bool> )

1 žingsnis gauna MethodInfo, kad nustatytų bendrą metodą

1 metodas: naudokite „GetMethod“ () arba „GetMethods“ () su atitinkamais tipų ar vėliavų tipais.

 MethodInfo method = typeof(Sample).GetMethod("GenericMethod"); 

2 metodas: sukurkite delegatą, gaukite MethodInfo objektą ir paskambinkite „GetGenericMethodDefinition“

Iš klasės viduje pateikiami metodai:

 MethodInfo method = ((Action)GenericMethod<object>) .Method .GetGenericMethodDefinition(); MethodInfo method = ((Action)StaticMethod<object>) .Method .GetGenericMethodDefinition(); 

Už klasės ribų, kuriuose nurodyti metodai:

 MethodInfo method = ((Action)(new Sample()) .GenericMethod<object>) .Method .GetGenericMethodDefinition(); MethodInfo method = ((Action)Sample.StaticMethod<object>) .Method .GetGenericMethodDefinition(); 

C #, metodo pavadinimas, ty „ToString“ arba „GenericMethod“, iš tikrųjų reiškia metodų grupę, kurioje gali būti vienas ar keli metodai. Jei nenurodysite metodų parametrų tipų, nežinoma, kokį metodą nurodote.

((Action)GenericMethod<object>) nurodo tam tikro metodo delegatą. ((Func<string, int>)GenericMethod<object>) reiškia kitą GenericMethod perkrovą

3 metodas: sukurkite lambda išraišką, kurioje yra metodo skambučio išraiška, gaukite MethodInfo objektą, o tada GetGenericMethodDefinition

 MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)( (Sample v) => v.GenericMethod<object>() )).Body).Method.GetGenericMethodDefinition(); 

Ji įsilaužta

Sukurkite lambda išraišką, kur kūnas yra jūsų pageidaujamas metodas.

 Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>(); 

Ištraukite kūną ir pridėkite į MethodCallExpression

 MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body; 

Iš metodo naudokite bendrą metodą

 MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition(); 

2 veiksme „MakeGenericMethod“ metodas sukuriamas sukuriant bendrąjį metodą su atitinkamais tipais.

 MethodInfo generic = method.MakeGenericMethod(myType); 

3 veiksme metodas vadinamas atitinkamais argumentais.

 generic.Invoke(this, null); 
6
10 янв. Atsakymą pateikė Grax Jan 10 2015-01-10 01:20 '15 at 1:20 2015-01-10 01:20

Niekas nepateikė „klasikinio atspindžio“, todėl čia yra visas kodo pavyzdys:

 using System; using System.Collections; using System.Collections.Generic; namespace DictionaryRuntime { public class DynamicDictionaryFactory { /// <summary> /// Factory to create dynamically a generic Dictionary. /// </summary> public IDictionary CreateDynamicGenericInstance(Type keyType, Type valueType) { //Creating the Dictionary. Type typeDict = typeof(Dictionary<,>); //Creating KeyValue Type for Dictionary. Type[] typeArgs = { keyType, valueType }; //Passing the Type and create Dictionary Type. Type genericType = typeDict.MakeGenericType(typeArgs); //Creating Instance for Dictionary<K,T>. IDictionary d = Activator.CreateInstance(genericType) as IDictionary; return d; } } } 

Pirmiau minėta klasė „ DynamicDictionaryFactory turi metodą

CreateDynamicGenericInstance(Type keyType, Type valueType)

ir jis sukuria ir grąžina ID dolgų egzempliorių, kurio raktai ir vertės tipai yra nurodyti raktų tipų ir vertės valueType .

Čia pateikiamas išsamus pavyzdys, kaip paskambinti šiuo metodu kuriant pavyzdį ir naudoti Dictionary<String, int> :

 using System; using System.Collections.Generic; namespace DynamicDictionary { class Test { static void Main(string[] args) { var factory = new DictionaryRuntime.DynamicDictionaryFactory(); var dict = factory.CreateDynamicGenericInstance(typeof(String), typeof(int)); var typedDict = dict as Dictionary<String, int>; if (typedDict != null) { Console.WriteLine("Dictionary<String, int>"); typedDict.Add("One", 1); typedDict.Add("Two", 2); typedDict.Add("Three", 3); foreach(var kvp in typedDict) { Console.WriteLine("\"" + kvp.Key + "\": " + kvp.Value); } } else Console.WriteLine("null"); } } } 

Kai vykdoma pirmiau pateikta konsolės programa, gauname teisingą laukiamą rezultatą:

 Dictionary<String, int> "One": 1 "Two": 2 "Three": 3 
2
24 авг. atsakymas į Dimitre Novatchev 24 d. 2016-08-24 04:24 '16 at 4:24 am 2016-08-24 04:24

Tai yra mano 2 centai, pagrįsti Grax atsakymu , tačiau du parametrai reikalingi bendram metodui.

Tarkime, kad jūsų metodas yra apibrėžtas pagalbos klasėje:

 public class Helpers { public static U ConvertCsvDataToCollection<U, T>(string csvData) where U : ObservableCollection<T> { //transform code here } } 

Mano atveju, U tipas visada yra pastebimas T tipo saugojimo objektas.

Kadangi aš turiu iš anksto nustatytus tipus, pirmiausia sukursiu „manekeno“ objektus, kurie atspindi stebimą kolekciją (U) ir jame saugomą objektą (T), ir kurie bus naudojami toliau, kad gautumėte jų tipą, kai skambinate „Make“

 object myCollection = Activator.CreateInstance(collectionType); object myoObject = Activator.CreateInstance(objectType); 

Tada skambinkite „GetMethod“, kad surastumėte bendrą funkciją:

 MethodInfo method = typeof(Helpers). GetMethod("ConvertCsvDataToCollection"); 

Iki šiol minėtas skambutis iš esmės yra identiškas aukščiau aprašytam kvietimui, tačiau šiek tiek skiriasi, kai jums reikia perduoti keletą parametrų.

Jums reikia perduoti Type [] masyvą į funkciją MakeGenericMethod, kurioje yra „manekeno“ tipų, kurie buvo sukurti anksčiau:

 MethodInfo generic = method.MakeGenericMethod( new Type[] { myCollection.GetType(), myObject.GetType() }); 

Po to reikia skambinti į „Invoke“ metodą, kaip nurodyta pirmiau.

 generic.Invoke(null, new object[] { csvData }); 

Ir viskas yra paruošta. Veikia žavesyje!

UPDATE:

Kaip pabrėžė @Bevan, nereikia kurti masyvo, kai skambina „MakeGenericMethod“ funkcija, nes ji reikalauja parametrų, todėl nereikia sukurti objekto, kad gautumėte tipus, nes galiu tiesiog perduoti tipus tiesiogiai šiai funkcijai. Mano atveju, kadangi turiu tipų, kurie yra iš anksto nustatyti kitoje klasėje, aš tiesiog pakeitiau savo kodą į:

 object myCollection = null; MethodInfo method = typeof(Helpers). GetMethod("ConvertCsvDataToCollection"); MethodInfo generic = method.MakeGenericMethod( myClassInfo.CollectionType, myClassInfo.ObjectType ); myCollection = generic.Invoke(null, new object[] { csvData }); 

myClassInfo yra 2 Type savybės, kurias nustatiau vykdymo metu pagal skaičiavimo vertę, perduotą konstruktoriui, ir suteiks man atitinkamus tipus, kuriuos aš tada naudoju „MakeGenericMethod“.

Dar kartą dėkojame, kad pabrėžėte šį @Bevan.

0
23 окт. Thierry spalio 23 d. Atsakymas 2015-10-23 02:28 '15, 2:28 2015-10-23 02:28

Kiti klausimai apie žymės arba Užduoti klausimą