2015-03-09 6 views
5

сгенерировать следующий код:Почему ILGenerator вставки Оставить инструкцию в заявлении Foreach

public override void Map(IEnumerable enumerable1) 
{ 
    List<int> list = new List<int>(); 
    foreach (object obj2 in enumerable1) 
    { 
    } 
} 

через Emit

Вот полный код:

MethodBuilder mapMethod = typeBuilder.DefineMethod("Map", MethodAttributes.Public | MethodAttributes.Virtual, typeof(void), new[] { typeof(IEnumerable) }); 

ILGenerator il = mapMethod.GetILGenerator(); 
LocalBuilder result = il.DeclareLocal(typeof(List<int>)); //0 
LocalBuilder item = il.DeclareLocal(typeof(object)); //1 
LocalBuilder enumeartor = il.DeclareLocal(typeof(IEnumerator)); //2 
LocalBuilder dispose = il.DeclareLocal(typeof(IDisposable)); //3 

Label labelWhile = il.DefineLabel(); 
Label labelReturn = il.DefineLabel(); 
Label labelMoveNext = il.DefineLabel(); 
Label labelEndFinally = il.DefineLabel(); 

//Create result List 
ConstructorInfo constructorInfo = (typeof(List<int>).GetConstructor(Type.EmptyTypes)); 
il.Emit(OpCodes.Newobj, constructorInfo); 
il.Emit(OpCodes.Stloc_0, result); 

il.Emit(OpCodes.Ldarg_1); 
il.EmitCall(OpCodes.Callvirt, typeof(IEnumerable).GetMethod("GetEnumerator"), Type.EmptyTypes); 
il.Emit(OpCodes.Stloc_2, enumeartor); 

il.BeginExceptionBlock(); 
il.Emit(OpCodes.Br_S, labelMoveNext); 
il.MarkLabel(labelWhile); 

il.Emit(OpCodes.Ldloc_2); 
il.EmitCall(OpCodes.Callvirt, typeof(IEnumerator).GetProperty("Current").GetGetMethod(), Type.EmptyTypes); 
il.Emit(OpCodes.Stloc_1, item); 
il.Emit(OpCodes.Ldloc_1); 

il.MarkLabel(labelMoveNext); 
il.Emit(OpCodes.Ldloc_2); 
il.EmitCall(OpCodes.Callvirt, typeof(IEnumerator).GetMethod("MoveNext"), Type.EmptyTypes); 
il.Emit(OpCodes.Brtrue_S, labelWhile); 

THE ISSUE IS HERE, I don't insert Leave instruction, but it there 
// il.Emit(OpCodes.Leave_S, labelReturn); 

il.BeginFinallyBlock(); 

il.Emit(OpCodes.Ldloc_2); 
il.Emit(OpCodes.Isinst, typeof(IDisposable)); 
il.Emit(OpCodes.Stloc_3, dispose); 
il.Emit(OpCodes.Ldloc_3); 
il.Emit(OpCodes.Brfalse_S, labelEndFinally); 

il.Emit(OpCodes.Ldloc_3); 
il.EmitCall(OpCodes.Callvirt, typeof(IDisposable).GetMethod("Dispose"), Type.EmptyTypes); 

il.MarkLabel(labelEndFinally); 
il.EndExceptionBlock(); 

il.MarkLabel(labelReturn); 
il.Emit(OpCodes.Ret); 

Вот результат IL (см IL_001f):

.method public virtual instance void Map(class [mscorlib]System.Collections.IEnumerable A_1) cil managed 
{ 
    // Code size  54 (0x36) 
    .maxstack 5 
    .locals init (class [mscorlib]System.Collections.Generic.List`1<int32> V_0, 
      object V_1, 
      class [mscorlib]System.Collections.IEnumerator V_2, 
      class [mscorlib]System.IDisposable V_3) 
    IL_0000: newobj  instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor() 
    IL_0005: stloc.0 
    IL_0006: ldarg.1 
    IL_0007: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Collections.IEnumerable::GetEnumerator() 
    IL_000c: stloc.2 
    .try 
    { 
    IL_000d: br.s  IL_0017 
    IL_000f: ldloc.2 
    IL_0010: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current() 
    IL_0015: stloc.1 
    IL_0016: ldloc.1 
    IL_0017: ldloc.2 
    IL_0018: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() 
    IL_001d: brtrue.s IL_000f 

    THE ISSUE IS HERE 
    IL_001f: leave  IL_0035 
    } // end .try 
    finally 
    { 
    IL_0024: ldloc.2 
    IL_0025: isinst  [mscorlib]System.IDisposable 
    IL_002a: stloc.3 
    IL_002b: ldloc.3 
    IL_002c: brfalse.s IL_0034 
    IL_002e: ldloc.3 
    IL_002f: callvirt instance void [mscorlib]System.IDisposable::Dispose() 
    IL_0034: endfinally 
    } // end handler 
    IL_0035: ret 
} // end of method ForeachType::Map 

Не могли бы вы объяснить, почему вышло leave инструкция?

+1

Я считаю, что это требование IL для завершения любого блока 'try {}'. –

+0

Возможно, но тот же ручной код имеет инструкцию 'leave.s'. Если я вставляю 'leave.s', есть две инструкции: – GSerjo

+1

Я не уверен, что я точно понимаю, что вы здесь говорите, но' leave' и 'leave.s' - это одна и та же команда, за исключением разрешенного диапазона цели метка. –

ответ

3

Hans Passant благодарит за совет. После code объясняет, что происходит.

public virtual void BeginFinallyBlock() 
{ 
    if (m_currExcStackCount==0) { 
     throw new NotSupportedException(Environment.GetResourceString("Argument_NotInExceptionBlock")); 
    } 
    __ExceptionInfo current = m_currExcStack[m_currExcStackCount-1]; 
    int   state = current.GetCurrentState(); 
    Label  endLabel = current.GetEndLabel(); 
    int   catchEndAddr = 0; 
    if (state != __ExceptionInfo.State_Try) 
    { 
     // generate leave for any preceeding catch clause 
     this.Emit(OpCodes.Leave, endLabel); 
     catchEndAddr = m_length; 
    } 

    MarkLabel(endLabel); 


    Label finallyEndLabel = this.DefineLabel(); 
    current.SetFinallyEndLabel(finallyEndLabel); 

    // generate leave for try clause 
    this.Emit(OpCodes.Leave, finallyEndLabel); HERE'S THE ANSWER 
    if (catchEndAddr == 0) 
     catchEndAddr = m_length; 
    current.MarkFinallyAddr(m_length, catchEndAddr); 
} 
+0

Хм, не могли бы вы объяснить, что для меня код правильный, но комментарий не является :) – GSerjo

+1

Это на самом деле низкий блок кода в этот метод, который прокомментировал '// генерировать отпуск для try clause', который добавляет оставление, которое вы искали, - заметил Бен, - отсутствие предложения' catch' в вашем коде означает, что этот блок комментирует '// генерирует отпуск для любое предшествующее уловку catch »не имеет значения. –

+0

Упс, да, ты прав. Благодаря! – GSerjo