2010-12-12 41 views
5

To pytanie wymaga nieco wstępu.Zachowanie delegata CIL ze sprzeczną "statycznością" metody docelowej

Pracuję nad projektem bezpieczeństwa, który przeanalizuje złożenia CIL i odrzuci te, które wykonują pewne określone "złe" rzeczy, jednocześnie umożliwiając aplikacji hostującej dostarczanie "bramek" dla niektórych metod, aby umożliwić filtrowanie niektórych połączeń. (Jest to niewielki podzbiór funkcjonalności projektu, ale jest to część, o którą będę tutaj pytać.)

Projekt przeszukuje wszystkie instrukcje w każdej metodzie w zespole i szuka połączenia, callcirt, ldftn, ldvirtftn i newobj opcodes, ponieważ są to jedyne opkody, które mogą ostatecznie spowodować wywołanie metody. Te rozkazy ldftn są wykorzystywane przy konstruowaniu delegatów, tak:

ldarg.1 
ldftn instance bool string::EndsWith(string) 
newobj instance void class [System.Core]System.Func`2<string, bool>::'.ctor'(object, native int) 

Pod koniec tej sekwencji Func<string, bool> znajduje się na szczycie stosu.

Załóżmy, że chcę przechwycić wszystkie połączenia z numerem String.EndsWith(String). W przypadku wywołania i callvirt, mogę po prostu zastąpić wywołanie instancji statycznym wywołaniem sygnatury Boolean(String,String) - pierwszym argumentem będzie instancja napisu, na której metoda była pierwotnie wywoływana. Na poziomie CIL zachowanie będzie jednoznaczne i dobrze zdefiniowane, ponieważ w ten sposób wywoływane są metody statyczne.

Ale czy na pewno? Próbowałem po prostu zastępując operandu instrukcji ldftn z tej samej metody statyczne stosowane w celu zastąpienia CALL/callvirt za argument:

ldarg.1 
ldftn bool class Prototype.Program::EndsWithGate(string, string) 
newobj instance void class [System.Core]System.Func`2<string, bool>::'.ctor'(object, native int) 

byłem w pełni spodziewając to się nie powiedzie, ponieważ pełnomocnik otrzymuje obiekt docelowy (not null) podczas wręczania statycznego wskaźnika metody. Ku mojemu zaskoczeniu działa to zarówno w środowisku wykonawczym Microsoft .NET, jak i Mono. Rozumiem, że obiekt docelowy/ten parametr jest tylko pierwszym parametrem metody i jest ukryty dla metod instancji. (Projekt opiera się na tej wiedzy.) Ale fakt, że delegaci faktycznie działają w tych okolicznościach, jest dla mnie nieco zagadkowy.

Moje pytanie brzmi: czy to zdefiniowane i udokumentowane zachowanie? Czy delegaci, jeśli zostaną przywołani, zawsze przesuwają swój cel na stos, jeśli nie jest on zerowy? Czy byłoby lepiej skonstruować klasę zamknięcia, która uchwyci cel i "poprawnie" wywoła metodę statyczną, mimo że będzie to o wiele bardziej skomplikowane i denerwujące?

+2

Bez tego zachowania nie można stworzyć delegata z metod rozszerzenie tak jak od metody instancji. – CodesInChaos

Odpowiedz

5

ECMA 335 spec część 2 14.6.2 akapit ma na ten temat: Konwencja powołanie T i D są dokładnie takie same, ignorując rozróżnienie metod statycznych i instancji. (tj. ten parametr, jeśli występuje, nie jest traktowany specjalnie).

Co to brzmi dla mnie tak do metod statycznych będą przebywać w dwóch odmianach:

  • bez tego, w którym to przypadku NULL powinny być przekazywane
  • z dodatkowym pierwszego parametru, zakładając zapałki typu co zostało wprowadzone do połączenia z newobj.
1

Cóż, nie sądziłem, że będę odpowiadając na moje pierwsze pytanie sobie ...

kolega #mono (Ck) poinformowała mnie o odpowiednim zachowaniu Delegate.CreateDelegate: (nacisk kopalni)

Jeśli podano firstArgument, jest przekazywana do metody za każdym razem, gdy wywoływany jest uczestnik; firstArgument mówi się, że jest związany z delegatem, a delegat jest uważany za zamknięty w stosunku do pierwszego argumentu. Jeśli metoda jest statyczna (udostępniana w języku Visual Basic), lista argumentów podana podczas wywoływania delegata obejmuje wszystkie parametry oprócz pierwszego; jeśli metoda jest metodą instancji, to firstArgument jest przekazywany do parametru ukrytej instancji (reprezentowanego przez to w C# lub przeze mnie w Visual Basic).

Logiczne wydaje mi się wywnioskować z tej dokumentacji, że ten (ab) wykorzystanie ldftn podczas budowy delegata, w połączeniu z nie-zerowej tarczy, jest faktycznie dobrze określone zachowanie.

3

Warto podkreślić, że to nie jest nadużyciem. Jest to technika znana jako "delegowanie curry". To pochodzi z bardziej ogólną techniką zwaną „Zmiękczanie” w językach programowania funkcjonalnych, gdzie funkcja z N + 1 argumentów jest przekształcany funkcji z N arguments.The C# odpowiednik będzie wyglądać następująco:

Func<T2, R> CurryFirst<T1, T2, R>(
    Func<T1, T2, R> f, 
    T1 arg 
) 
{ 
    return (x) => f(arg, x); 
} 

Func<T1, R> CurrySecond<T1, T2, R>(
    Func<T1, T2, R> f, 
    T2 arg 
) 
{ 
    return (x) => f(x, arg); 
} 

CLR zapewnia specjalne wsparcie dla „curry” przypadek pierwszy, głównie ze względu na poziomie kodu maszyną, curry statyczna metoda wywołanie wygląda prawie dokładnie tak samo jak metody instancji inwokacji (parametr this jest przekazywana jako niejawny pierwszy argument).

To czyni delegata wykonawczych currying dość skuteczny. Został pierwotnie zaimplementowany wraz z DynamicMethod do obsługi Iron Python. Jest również używany do innych celów, takich jak umożliwienie delegatom odwołania się do metod rozszerzeń w sposób przejrzysty.

+0

Czy ta funkcja została zaimplementowana tylko razem z DLR? –

+0

wierzę, że został wprowadzony w celu wspierania Pythona żelaza, który poprzedza DLR przez co najmniej 2 lata lub więcej. –

Powiązane problemy