2008-09-16 16 views
7

W języku Microsoft IL, aby wywołać metodę typu wartości, potrzebne jest odwołanie pośrednie. Powiedzmy mamy ILGenerator nazwie „il”, a obecnie mamy pustych na wierzchu stosu, jeśli chcemy, by sprawdzić, czy to ma wartość wtedy możemy emitować następujące:Czy jest możliwe pośrednie ładowanie typu wartości na stosie

var local = il.DeclareLocal(typeof(Nullable<int>)); 
il.Emit(OpCodes.Stloc, local); 
il.Emit(OpCodes.Ldloca, local); 
var method = typeof(Nullable<int>).GetMethod("get_HasValue"); 
il.EmitCall(OpCodes.Call, method, null); 

jednak, że będzie byłoby miło, aby pominąć zapisywanie go jako zmiennej lokalnej, a po prostu wywołać metodę na adres zmiennej już na stosie, coś jak:

il.Emit(/* not sure */); 
var method = typeof(Nullable<int>).GetMethod("get_HasValue"); 
il.EmitCall(OpCodes.Call, method, null); 

rodzina ldind instrukcji wygląda obiecująco (szczególnie ldind_ref), ale nie mogę znaleźć wystarczającej dokumentacji, aby wiedzieć, czy spowodowałoby to boksowanie wartości, co podejrzewam, że może.

Miałem przyjrzeć się kompilacji C#, ale używa zmiennych lokalnych do osiągnięcia tego, co pozwala mi wierzyć, że pierwszy sposób może być jedynym sposobem. Ktoś ma lepsze pomysły?

**** Edit: Uwagi dodatkowe ****

Próba wywołania metody bezpośrednio, jak w poniższym programie z liniami odkomentowanymi, nie działa (błąd będzie „operacja może destabilizują środowisko wykonawcze "). Odkomentuj linie, a zobaczysz, że działa zgodnie z oczekiwaniami, zwracając "True".

var m = new DynamicMethod("M", typeof(bool), Type.EmptyTypes); 
var il = m.GetILGenerator(); 
var ctor = typeof(Nullable<int>).GetConstructor(new[] { typeof(int) }); 
il.Emit(OpCodes.Ldc_I4_6); 
il.Emit(OpCodes.Newobj, ctor); 
//var local = il.DeclareLocal(typeof(Nullable<int>)); 
//il.Emit(OpCodes.Stloc, local); 
//il.Emit(OpCodes.Ldloca, local); 
var getValue = typeof(Nullable<int>).GetMethod("get_HasValue"); 
il.Emit(OpCodes.Call, getValue); 
il.Emit(OpCodes.Ret); 
Console.WriteLine(m.Invoke(null, null)); 

więc nie można po prostu wywołać metodę z wartością na stosie, ponieważ jest to typ wartości (choć może gdyby to był rodzaj odniesienia).

Co chciałbym osiągnąć (lub wiedzieć, czy jest to możliwe), to zastąpienie trzech linii, które są pokazane w komentarzach, ale należy zapewnić działanie programu, bez korzystania z tymczasowego lokalnego.

Odpowiedz

2

Jeśli zmienna jest już na stosie, możesz śmiało wyemitować wywołanie metody.

Wygląda na to, że konstruktor nie przesuwa zmiennej na stos w postaci pisanej na maszynie. Po nieco wkopaniu w IL, wydaje się, że istnieją dwa sposoby użycia zmiennej po jej skonstruowaniu.

można załadować zmienną, która będzie przechowywać odniesienia na stosie oceny przed wywołaniem konstruktora, a następnie załadować tej zmiennej ponownie po wywołaniu konstruktora tak:

DynamicMethod method = new DynamicMethod("M", typeof(bool), Type.EmptyTypes); 
ILGenerator il = method.GetILGenerator(); 
Type nullable = typeof(Nullable<int>); 
ConstructorInfo ctor = nullable.GetConstructor(new Type[] { typeof(int) }); 
MethodInfo getValue = nullable.GetProperty("HasValue").GetGetMethod(); 
LocalBuilder value = il.DeclareLocal(nullable);   

// load the variable to assign the value from the ctor to 
il.Emit(OpCodes.Ldloca_S, value); 
// load constructor args 
il.Emit(OpCodes.Ldc_I4_6); 
il.Emit(OpCodes.Call, ctor); 
il.Emit(OpCodes.Ldloca_S, value); 

il.Emit(OpCodes.Call, getValue); 
il.Emit(OpCodes.Ret); 
Console.WriteLine(method.Invoke(null, null)); 

Druga opcja to robi się sposób, który pokazałeś. Jedynym powodem, dla którego widzę, jest to, że metody ctor zwróciły nieważne, więc nie umieszczają swojej wartości na stosie, jak inne metody. Wydaje się dziwne, że możesz wywołać Setloc, jeśli nowy obiekt nie znajduje się na stosie.

+0

Tak to też działa idealnie, ale nadal wymaga miejscowy. Myślę, że to zaczyna potwierdzać moje pierwotne podejrzenie, że nie można tego zrobić bez lokalnego. myślę Stloc działa, ponieważ lokalna ma dodatkowe metadane typu są przechowywane ... ale potem znowu tak robi Zadzwoń ... dziwne. –

+0

Tak, uważam za bardzo dziwne, że nie działa tak, jak się tego spodziewałem. Jeśli dynamiczna metoda bierze pustych jako argument, wszystko co musisz zrobić to zadzwonić ldarg_0, ale jestem zakładając, że rzeczywiście trzeba utworzyć typ wartości w metodzie. –

+0

Rzeczywiście, muszę utworzyć wartości w ramach metody. Kłopot mam to, że istnieje spora liczba z nich za metodą (różnych rodzajów), co oznacza sporą liczbę mieszkańców wykorzystywanych wyłącznie do operacji przejściowych. To nie jest wielka sprawa i wszystko działa dobrze, ale wydaje się trochę nieporządna. –

1

Po uważnym przyjrzeniu się opcjom, uważam, że masz rację zakładając, że nie da się tego zrobić. Jeśli przeanalizujesz zachowanie stosu instrukcji MSIL, zobaczysz, że żaden op nie opuścił swojego argumentu (ów) na stosie. Ponieważ byłby to warunek dla "get address of stack entry" op, jestem dość pewny, że nie istnieje.

To pozostawia ci dup + box lub stloc + ldloca. Jak już zauważyłeś, ta ostatnia jest prawdopodobnie bardziej wydajna.

@greg: Wiele instrukcje opuszczenia wynik na stosie, ale żadne instrukcje opuścić żadnego z ich argumentów na stosie, które byłyby wymagane dla „Get adres stosu element” instrukcji.

+0

Instrukcja newobj pozostawia wynik na stosie - jeśli był to typ odniesienia, mógłbym natychmiast wywołać na nim metodę. Zasadniczo myślę, że masz rację, istnieje wartość struktury na stosie, ale potrzebuję adresu tej wartości, aby wywołać metodę na nim. –

+0

Wygląda na to, że zespół C# krótko rozważył pomysł opcjonalnego zezwolenia na wartości zwracane "ref", o czym jest ta dyskusja: http://blogs.msdn.com/b/ericlippert/archive/2011/06/ 23/ref-zwraca-i-ref-locals.aspx –

0

prostu napisał klasy, która robi to, co PO pyta ... Oto kod IL, że kompilator C# produkuje:

IL_0008: ldarg.0 
    IL_0009: ldarg.1 
    IL_000a: newobj  instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0) 
    IL_000f: stfld  valuetype [mscorlib]System.Nullable`1<int32> ConsoleApplication3.Temptress::_X 
    IL_0014: nop 
    IL_0015: ret 
1

I zorientowaliśmy się! Na szczęście byłem przeczytaniu o unbox opcode i zauważył, że popycha adres wartości. unbox.any przesuwa rzeczywistą wartość. Tak więc, w celu wywołania metody na typ wartości bez konieczności przechowywać ją w zmiennej lokalnej, a następnie załadować swój adres, wystarczy box następnie unbox. Korzystanie ostatni przykład:

var m = new DynamicMethod("M", typeof(bool), Type.EmptyTypes); 
var il = m.GetILGenerator(); 
var ctor = typeof(Nullable<int>).GetConstructor(new[] { typeof(int) }); 
il.Emit(OpCodes.Ldc_I4_6); 
il.Emit(OpCodes.Newobj, ctor); 
il.Emit(OpCodes.Box, typeof(Nullable<int>)); // box followed by unbox 
il.Emit(OpCodes.Unbox, typeof(Nullable<int>)); 
var getValue = typeof(Nullable<int>).GetMethod("get_HasValue"); 
il.Emit(OpCodes.Call, getValue); 
il.Emit(OpCodes.Ret); 
Console.WriteLine(m.Invoke(null, null)); 

Minusem jest to, że boks powoduje alokację pamięci dla pudełkowej obiektu, więc jest nieco wolniejszy niż przy użyciu zmiennych lokalnych (które już być przydzielone). Ale oszczędza ci to konieczności określania, deklarowania i odwoływania się do wszystkich zmiennych lokalnych, których potrzebujesz.