为什么 ILGenerator 在 Foreach 语句中插入 Leave 指令

Why ILGenerator inserts Leave instruction into Foreach statement

我生成以下代码:

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指令?

Hans Passant thanks for the advice. Following 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); 
}