هل من الممكن تحميل نوع القيمة بشكل غير مباشر على المكدس

StackOverflow https://stackoverflow.com/questions/76274

  •  09-06-2019
  •  | 
  •  

سؤال

في Microsoft IL، لاستدعاء أسلوب على نوع قيمة، فإنك تحتاج إلى مرجع غير مباشر.لنفترض أن لدينا ILGenerator اسمه "il" وأن لدينا حاليًا Nullable أعلى المكدس، إذا أردنا التحقق مما إذا كان له قيمة، فيمكننا إصدار ما يلي:

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);

ومع ذلك، سيكون من الجيد تخطي حفظه كمتغير محلي، واستدعاء الطريقة ببساطة على عنوان المتغير الموجود بالفعل على المكدس، مثل:

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

تبدو مجموعة التعليمات ldind واعدة (خاصة ldind_ref) ولكن لا يمكنني العثور على وثائق كافية لمعرفة ما إذا كان هذا سيؤدي إلى ملاكمة القيمة، وهو ما أظن أنه قد يحدث.

لقد ألقيت نظرة على مخرجات برنامج التحويل البرمجي C#، ولكنه يستخدم متغيرات محلية لتحقيق ذلك، مما يجعلني أعتقد أن الطريقة الأولى قد تكون الطريقة الوحيدة.هل لدى أي شخص أي أفكار أفضل؟

**** يحرر:ملاحظات إضافية ****

محاولة استدعاء الأسلوب مباشرةً، كما هو الحال في البرنامج التالي مع الأسطر التي تم التعليق عليها، لن تنجح (سيكون الخطأ هو "قد تؤدي العملية إلى زعزعة استقرار وقت التشغيل").قم بإلغاء التعليق على الأسطر وسترى أنه يعمل كما هو متوقع، ويعيد "صحيح".

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));

لذلك لا يمكنك ببساطة استدعاء الطريقة بالقيمة الموجودة في المكدس لأنها نوع قيمة (على الرغم من أنه يمكنك ذلك إذا كان نوعًا مرجعيًا).

ما أود تحقيقه (أو معرفة ما إذا كان ذلك ممكنًا) هو استبدال الأسطر الثلاثة التي تم التعليق عليها، مع الحفاظ على عمل البرنامج، دون استخدام محلي مؤقت.

هل كانت مفيدة؟

المحلول

إذا كان المتغير موجودًا بالفعل على المكدس، فيمكنك المضي قدمًا وإصدار استدعاء الأسلوب فقط.

يبدو أن المنشئ لا يدفع المتغير إلى المكدس في نموذج مكتوب.بعد البحث في IL قليلاً، يبدو أن هناك طريقتين لاستخدام المتغير بعد إنشائه.

يمكنك تحميل المتغير الذي سيخزن المرجع في حزمة التقييم قبل استدعاء المنشئ، ثم تحميل هذا المتغير مرة أخرى بعد استدعاء المنشئ كما يلي:

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));

الخيار الآخر هو القيام بذلك بالطريقة التي أظهرتها.السبب الوحيد الذي يمكنني رؤيته لهذا هو أن أساليب ctor تُرجع فارغة، لذا فهي لا تضع قيمتها على المكدس مثل الطرق الأخرى.يبدو من الغريب أنه يمكنك استدعاء Setloc إذا لم يكن الكائن الجديد موجودًا في المكدس.

نصائح أخرى

بعد النظر في الخيارات بمزيد من الاهتمام، أعتقد أنك على حق في افتراض أنه لا يمكن القيام بذلك.إذا قمت بفحص سلوك المكدس لتعليمات MSIL، يمكنك أن ترى أنه لا توجد عملية تترك معاملها (معاملاتها) على المكدس.نظرًا لأن هذا سيكون متطلبًا لعملية "الحصول على عنوان إدخال المكدس"، فأنا واثق تمامًا من عدم وجوده.

هذا يتركك إما مع dup+box أو stloc+ldloca.وكما أشرت، فمن المرجح أن يكون الأخير أكثر كفاءة.

@جريج:العديد من التعليمات تترك نتيجة على المكدس، ولكن لا توجد تعليمات تترك أيًا منها المعاملات على المكدس، والذي سيكون مطلوبًا لتعليمة "الحصول على عنوان عنصر المكدس".

اكتشفتها!ومن حسن الحظ أنني كنت أقرأ عن unbox كود التشغيل ولاحظت أنه يدفع عنوان من القيمة. unbox.any يدفع القيمة الفعلية.لذلك، من أجل استدعاء أسلوب على نوع قيمة دون الحاجة إلى تخزينه في متغير محلي ثم تحميل عنوانه، يمكنك ببساطة box تليها unbox.باستخدام المثال الأخير الخاص بك:

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));

الجانب السلبي لهذا هو أن الملاكمة تسبب تخصيص الذاكرة للكائن المعبأ، لذا فهي أبطأ قليلاً من استخدام المتغيرات المحلية (التي سيتم تخصيصها بالفعل).ولكنه يوفر عليك من الاضطرار إلى تحديد جميع المتغيرات المحلية التي تحتاجها وإعلانها والإشارة إليها.

لقد كتبت للتو فصلًا دراسيًا يفعل ما يطلبه OP ...إليك رمز IL الذي ينتجه برنامج التحويل البرمجي C#:

  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
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top