Jaki jest najlepszy sposób na wywołanie metody ogólnej, gdy parametr type nie jest znany w czasie kompilacji, a zamiast tego jest uzyskiwany dynamicznie w czasie wykonywania?
Rozważ następujący przykładowy kod - w jaki Example()
sposób najbardziej zwięzły sposób wywołać GenericMethod<T>()
za pomocą Type
przechowywanej w myType
zmiennej zmiennej?
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>()
{
//...
}
}
c#
.net
generics
reflection
Bevan
źródło
źródło
BindingFlags.Instance
nie tylkoBindingFlags.NonPublic
metody prywatnej / wewnętrznej.Odpowiedzi:
Musisz użyć refleksji, aby rozpocząć metodę, a następnie „skonstruować” ją, podając argumenty typu z MakeGenericMethod :
W przypadku metody statycznej przekaż
null
jako pierwszy argument doInvoke
. To nie ma nic wspólnego z metodami ogólnymi - to tylko normalne odbicie.Jak wspomniano, wiele z nich jest prostszych niż w C # 4
dynamic
- jeśli oczywiście można użyć wnioskowania typu. Nie pomaga w przypadkach, w których wnioskowanie typu nie jest dostępne, takich jak dokładny przykład w pytaniu.źródło
GetMethod()
domyślnie uwzględniane są tylko metody instancji publicznych, więc możesz potrzebowaćBindingFlags.Static
i / lubBindingFlags.NonPublic
.BindingFlags.NonPublic | BindingFlags.Instance
(i opcjonalnieBindingFlags.Static
).dynamic
nie pomaga, ponieważ typ wnioskowanie nie jest dostępna. (Nie ma żadnych argumentów, za pomocą których kompilator może określić argument typu).Tylko dodatek do oryginalnej odpowiedzi. Chociaż to zadziała:
Jest to również trochę niebezpieczne, ponieważ tracisz kontrolę czasu kompilacji
GenericMethod
. Jeśli później dokonasz refaktoryzacji i zmienisz nazwęGenericMethod
, ten kod nie zauważy tego i zawiedzie w czasie wykonywania. Ponadto, jeśli nastąpi jakiekolwiek przetwarzanie końcowe zestawu (na przykład zaciemnianie lub usuwanie nieużywanych metod / klas), ten kod może również ulec uszkodzeniu.Tak więc, jeśli znasz metodę, z którą się łączysz w czasie kompilacji, i nie jest to nazywane miliony razy, więc narzut nie ma znaczenia, zmieniłbym ten kod na:
Chociaż nie jest to zbyt ładne, masz
GenericMethod
tutaj odniesienie do czasu kompilacji , a jeśli przefakturujesz, usuniesz lub zrobisz cokolwiek zGenericMethod
tym, ten kod będzie działał lub przynajmniej ulegnie awarii w czasie kompilacji (jeśli na przykład usunieszGenericMethod
).Innym sposobem na zrobienie tego samego byłoby utworzenie nowej klasy opakowania i utworzenie jej przez
Activator
. Nie wiem, czy jest lepszy sposób.źródło
GenMethod.Method.GetGenericMethodDefinition()
zamiastthis.GetType().GetMethod(GenMethod.Method.Name)
. Jest nieco czystszy i prawdopodobnie bezpieczniejszy.nameof(GenericMethod)
Wywołanie metody ogólnej z parametrem typu znanym tylko w czasie wykonywania można znacznie uprościć, stosując
dynamic
typ zamiast interfejsu API odbicia.Aby użyć tej techniki, typ musi być znany z rzeczywistego obiektu (nie tylko instancji
Type
klasy). W przeciwnym razie musisz utworzyć obiekt tego typu lub użyć standardowego rozwiązania interfejsu API odbicia . Możesz utworzyć obiekt za pomocą metody Activator.CreateInstance .Jeśli chcesz wywołać metodę ogólną, która w „normalnym” użyciu miałaby wywnioskowany typ, po prostu przychodzi do rzutowania obiektu nieznanego typu na
dynamic
. Oto przykład:A oto wynik tego programu:
Process
jest ogólną metodą instancji, która zapisuje prawdziwy typ przekazywanego argumentu (za pomocąGetType()
metody) i typ parametru ogólnego (za pomocątypeof
operatora).Rzucając argument
dynamic
typu na typ, odroczyliśmy podawanie parametru type do czasu działania. GdyProcess
metoda jest wywoływana zdynamic
argumentem, kompilator nie dba o typ tego argumentu. Kompilator generuje kod, który w czasie wykonywania sprawdza rzeczywiste typy przekazywanych argumentów (za pomocą odbicia) i wybiera najlepszą metodę do wywołania. Tutaj jest tylko jedna ogólna metoda, więc jest wywoływana z odpowiednim parametrem typu.W tym przykładzie dane wyjściowe są takie same, jakbyś napisał:
Wersja z typem dynamicznym jest zdecydowanie krótsza i łatwiejsza do napisania. Nie należy również martwić się wydajnością wywołania tej funkcji wiele razy. Następne wywołanie z argumentami tego samego typu powinno być szybsze dzięki mechanizmowi buforowania w DLR. Oczywiście możesz napisać kod, który buforuje wywoływanych delegatów, ale używając
dynamic
typu otrzymujesz to zachowanie za darmo.Jeśli ogólna metoda, którą chcesz wywołać, nie ma argumentu sparametryzowanego (więc nie można wywnioskować jej parametru typu), możesz zawrzeć wywołanie metody ogólnej w metodzie pomocniczej, jak w poniższym przykładzie:
Zwiększone bezpieczeństwo typu
To, co jest naprawdę świetne w używaniu
dynamic
obiektu jako zamiennika do używania refleksyjnego interfejsu API, polega na tym, że tracisz tylko sprawdzanie czasu kompilacji tego konkretnego typu, którego nie znasz przed uruchomieniem. Inne argumenty i nazwa metody są jak zwykle analizowane statycznie przez kompilator. Jeśli usuniesz lub dodasz więcej argumentów, zmienisz ich typy lub zmienisz nazwę metody, pojawi się błąd kompilacji. Nie stanie się tak, jeśli podasz nazwę metody jako ciąg znaków,Type.GetMethod
a argumenty jako tablicę obiektówMethodInfo.Invoke
.Poniżej znajduje się prosty przykład ilustrujący, w jaki sposób niektóre błędy mogą zostać wykryte w czasie kompilacji (kod komentarza), a inne w czasie wykonywania. Pokazuje także, w jaki sposób DLR próbuje rozwiązać metodę, którą należy wywołać.
Tutaj ponownie wykonujemy jakąś metodę, rzutując argument na
dynamic
typ. Tylko weryfikacja typu pierwszego argumentu jest odraczana do środowiska wykonawczego. Otrzymasz błąd kompilatora, jeśli nazwa metody, którą wywołujesz, nie istnieje lub jeśli inne argumenty są niepoprawne (zła liczba argumentów lub niewłaściwe typy).Gdy przekazujesz
dynamic
argument do metody, to wywołanie jest ostatnio powiązane . Metoda rozwiązywania przeciążenia ma miejsce w czasie wykonywania i próbuje wybrać najlepsze przeciążenie. Jeśli więc wywołujeszProcessItem
metodę z obiektemBarItem
typu, wówczas faktycznie wywołasz metodę inną niż ogólna, ponieważ lepiej pasuje do tego typu. Jednak podczas przekazywania argumentuAlpha
typu pojawi się błąd czasu wykonania, ponieważ nie ma metody, która mogłaby obsłużyć ten obiekt (metoda ogólna ma ograniczenie,where T : IItem
aAlpha
klasa nie implementuje tego interfejsu). Ale o to chodzi. Kompilator nie ma informacji, że to wywołanie jest prawidłowe. Jako programista wiesz o tym i powinieneś upewnić się, że ten kod działa bez błędów.Typ zwrotu gotcha
Kiedy dzwoni non-void metody z parametrem dynamicznym typu, jego typ zwracany będzie prawdopodobnie być
dynamic
zbyt . Więc jeśli zmienisz poprzedni przykład na ten kod:wówczas typ obiektu wynikowego to
dynamic
. Wynika to z faktu, że kompilator nie zawsze wie, która metoda zostanie wywołana. Jeśli znasz typ zwracanego wywołania funkcji, powinieneś niejawnie przekonwertować go na wymagany typ, aby reszta kodu została wpisana statycznie:Otrzymasz błąd czasu wykonania, jeśli typ nie pasuje.
W rzeczywistości, jeśli spróbujesz uzyskać wartość wyniku w poprzednim przykładzie, otrzymasz błąd czasu wykonania w drugiej iteracji pętli. Jest tak, ponieważ próbowałeś zapisać wartość zwracaną funkcji void.
źródło
ProcessItem
metoda ogólna ma ogólne ograniczenie i akceptuje tylko obiekt, który implementujeIItem
interfejs. Kiedy zadzwoniszProcessItem(new Aplha(), "test" , 1);
lubProcessItem((object)(new Aplha()), "test" , 1);
dostaniesz błąd kompilatora, ale podczas przesyłania dodynamic
Ciebie odłożysz tę kontrolę do środowiska wykonawczego.W wersji C # 4.0 odbicie nie jest konieczne, ponieważ DLR może je wywoływać przy użyciu typów środowiska wykonawczego. Ponieważ korzystanie z biblioteki DLR jest rodzajem dynamicznego bólu (zamiast kodu generującego kompilator C # dla Ciebie), struktura Open Source Dynamitey (.net standard 1.5) zapewnia łatwy buforowany dostęp w czasie wykonywania do tych samych wywołań, które generowałby kompilator dla Was.
źródło
Uzupełnienie odpowiedzi Adriana Gallero :
Wywołanie metody ogólnej z informacji o typie obejmuje trzy kroki.
TLDR: Wywołanie znanej metody ogólnej z obiektem typu można osiągnąć poprzez:
gdzie
GenericMethod<object>
jest nazwą metody do wywołania i dowolnym typem, który spełnia ogólne ograniczenia.(Action) pasuje do sygnatury metody, która ma zostać wywołana, tj. (
Func<string,string,int>
LubAction<bool>
)Krok 1 to uzyskanie MethodInfo dla ogólnej definicji metody
Metoda 1: Użyj GetMethod () lub GetMethods () z odpowiednimi typami lub flagami wiążącymi.
Metoda 2: Utwórz delegata, pobierz obiekt MethodInfo, a następnie wywołaj GetGenericMethodDefinition
Wewnątrz klasy zawierającej metody:
Spoza klasy, która zawiera metody:
W języku C # nazwa metody, tj. „ToString” lub „GenericMethod” faktycznie odnosi się do grupy metod, które mogą zawierać jedną lub więcej metod. Dopóki nie podasz typów parametrów metody, nie wiadomo, do której metody się odwołujesz.
((Action)GenericMethod<object>)
odnosi się do delegata dla określonej metody.((Func<string, int>)GenericMethod<object>)
odnosi się do innego przeciążenia GenericMethodMetoda 3: Utwórz wyrażenie lambda zawierające wyrażenie wywołania metody, pobierz obiekt MethodInfo, a następnie GetGenericMethodDefinition
To rozkłada się na
Utwórz wyrażenie lambda, w którym ciało jest wywołaniem wybranej metody.
Wyodrębnij treść i rzutuj do MethodCallExpression
Uzyskaj ogólną definicję metody z tej metody
Krok 2 wywołuje MakeGenericMethod, aby utworzyć ogólną metodę z odpowiednimi typami.
Krok 3 polega na wywołaniu metody z odpowiednimi argumentami.
źródło
Nikt nie podał „ klasycznego rozwiązania refleksji ”, więc oto kompletny przykład kodu:
Powyższa
DynamicDictionaryFactory
klasa ma metodęCreateDynamicGenericInstance(Type keyType, Type valueType)
i tworzy i zwraca instancję IDictionary, której typy kluczy i wartości są dokładnie określone w wywołaniu
keyType
ivalueType
.Oto kompletny przykład, jak wywołać tę metodę, aby utworzyć instancję i użyć
Dictionary<String, int>
:Po uruchomieniu powyższej aplikacji konsoli otrzymujemy poprawny, oczekiwany wynik:
źródło
To moje 2 centy na podstawie odpowiedzi Graxa , ale z dwiema parametrami wymaganymi dla ogólnej metody.
Załóżmy, że twoja metoda jest zdefiniowana w następujący sposób w klasie Helpers:
W moim przypadku typ U jest zawsze obserwowalnym zbiorem przechowującym obiekt typu T.
Ponieważ moje typy są predefiniowane, najpierw tworzę „obojętne” obiekty, które reprezentują obserwowalną kolekcję (U) i obiekt w niej przechowywany (T) i które zostaną użyte poniżej, aby uzyskać ich typ podczas wywoływania Make
Następnie wywołaj GetMethod, aby znaleźć swoją funkcję ogólną:
Jak dotąd powyższe wywołanie jest prawie identyczne z tym, co wyjaśniono powyżej, ale z niewielką różnicą, gdy trzeba przekazać do niego wiele parametrów.
Musisz przekazać tablicę Type [] do funkcji MakeGenericMethod, która zawiera typy obiektów „obojętnych”, które zostały utworzone powyżej:
Gdy to zrobisz, musisz wywołać metodę Invoke, jak wspomniano powyżej.
I jesteś skończony. Działa urok!
AKTUALIZACJA:
Jak podkreślono @Bevan, nie muszę tworzyć tablicy podczas wywoływania funkcji MakeGenericMethod, ponieważ przyjmuje ona parametry, i nie muszę tworzyć obiektu, aby uzyskać typy, ponieważ mogę po prostu przekazać typy bezpośrednio do tej funkcji. W moim przypadku, ponieważ mam typy predefiniowane w innej klasie, po prostu zmieniłem kod na:
myClassInfo zawiera 2 właściwości typu,
Type
które ustawiam w czasie wykonywania na podstawie wartości wyliczenia przekazywanej do konstruktora i zapewni mi odpowiednie typy, których następnie używam w MakeGenericMethod.Jeszcze raz dziękuję za wyróżnienie tego @Bevan.
źródło
MakeGenericMethod()
mieć słowo kluczowe params, aby nie trzeba było tworzyć tablicy; nie trzeba też tworzyć instancji, aby uzyskać typy -methodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject))
wystarczyłoby.Zainspirowany odpowiedzią Enigmativity - załóżmy, że masz dwie (lub więcej) klasy
i chcesz wywołać metodę za
Foo<T>
pomocąBar
iSquare
, który jest zadeklarowany jakoNastępnie możesz zaimplementować metodę rozszerzenia, taką jak:
Dzięki temu możesz po prostu wywołać
Foo
:który działa dla każdej klasy. W takim przypadku wyświetli:
źródło