破解 C# 2.0 迭代器






4.47/5 (8投票s)
2005年4月5日
23分钟阅读

75482

501
一项可在 .NET 1.1 上启用 C# 2.0 迭代器的破解。
引言
本文的重点是一项为 .NET Framework 1.1 版本实现 C# 2.0 迭代器而开发的破解。这仅仅是对编译器功能着迷以及纯粹好奇它如何在 Framework 1.1 版本上运行的结果。该破解基于 C# 规范第 22 章中描述的迭代器实现。
然而,在此之前,读者应该非常清楚,该破解不具有任何实用价值。当然,不期望或不建议任何人实际将其用于任何用途,更不用说生产代码,这并非因为它无法完成任务或存在主要的性能问题,而是因为将代码,尤其是生产代码,暴露给一个修改 PE 文件但尚未经过充分测试的工具是不明智的,就像这个工具一样,更何况这个工具仅仅是一个破解!此外,C# 在下一个主要版本 2.0 的编译器中直接提供了此功能,所以为什么还要费心呢。读者已被警告!本文旨在通过展示 C# 2.0 迭代器如何与 .NET Framework 1.1 版本一起使用来娱乐读者。它并非旨在为读者提供任何可用于生产目的的东西。
首先,对于那些不熟悉该功能的读者,将对 C# 2.0 迭代器进行一个非常简短而粗略的介绍。本文假设读者非常熟悉迭代器设计模式以及它如何在 .NET 中实现。具体来说,您应该完全理解迭代器类型接口 IEnumerator 以及迭代器工厂类型接口 IEnumerable,以及 foreach
此介绍之后简要概述了该破解用于在目标为 .NET Framework 1.1 版本的 PE 文件中实现迭代器所采取的步骤,这些文件可以使用 .NET 1.1 平台 SDK 附带的 ildasm.exe 工具反汇编为 IL 代码。但是请注意,在使用非 Visual Studio .NET 2003 附带的 C# 和 VB 编译器生成的 PE 文件时,应格外小心。
C# 2.0 迭代器
去阅读 C# 规范第 22 章!开玩笑的,尽管强烈推荐。此外,此 C# 2.0 迭代器介绍仅提供该功能和实现的主要特性。它省略了许多其他非常重要的事实,例如,那些处理异常处理的事实。此外,读者不应惊讶地发现,此迭代器介绍只是简单地复述了 C# 规范第 22 章中的特定内容。最后,尽管如此,读者最好阅读并理解 C# 规范第 22 章,然后跳过本节。
C# 2.0 迭代器只是一个编译器功能,极大地促进了迭代器设计模式的实现。具体来说,该语言获得了通过普通函数定义迭代器类型的能力。这些迭代器函数包含迭代逻辑,并在编译时封装在嵌套的 IEnumerable/IEnumerator 类型中。在运行时,这些嵌套类型的实例用于执行相应迭代器函数表达的迭代逻辑。迭代器函数绝不会直接在其定义的类型中执行。相反,它在编译器创建并嵌套在包含迭代器函数的类型中的 IEnumerator 类型的 MoveNext 方法中运行。
迭代器函数是满足以下主要标准的任何函数,尽管也存在其他标准并在 C# 规范第 22 章中完整记录
- 该函数的返回类型为 IEnumerator或IEnumerable。因此,该函数可以与foreach
- 在函数代码块中,单词(而不是关键字)yieldreturn
- 在函数代码块中,单词(而不是关键字)yieldbreak
一个生成 1 到 5 的值的迭代器函数示例如下
System.Collections.IEnumerable yieldOneToFive()
{
    yield return 1;
    yield return 2;
    yield return 3;
    yield return 4;
    yield return 5;
}
迭代器函数的代码块可以称为 yield
- 在包含迭代器函数的类型中创建嵌套的 IEnumerable/IEnumerator类型。此嵌套迭代器类型通过其IEnumerator.MoveNext方法执行迭代器函数。
- 用返回嵌套迭代器类型实例的代码替换迭代器函数体。迭代器函数从不以原始形式实际执行,而是始终返回一个包含原始代码的类实例。
编译器只是完成了程序员在以传统方式实现迭代器设计模式时需要做的工作。此外,迭代器函数还可以用作协程。话虽如此,C# 2.0 迭代器不仅简化了迭代器设计模式的实现,还简化了从使用协程中受益的其他设计模式的实现(Wesner Moise, .NET Undocumented - 迭代器,不仅仅用于迭代)。幸运的是,对于学习经验来说,注意迭代器函数的行为将使那些了解协程的人清楚这一点。其行为可以描述如下,尽管这肯定不是一个详尽的描述;有关详细信息,请参阅 C# 规范第 22 章
- 每当遇到 yieldreturnyieldreturnMoveNext方法中的点,这些点将迭代器的Current属性的值设置为yieldreturnMoveNext将返回true
- 迭代器函数首次调用时,执行从代码块的开头开始。随后的调用中,执行从遇到的最后一个 yieldreturnyieldIEnumerator类型来克服这些障碍,这些类型充当状态机,每个状态导致在嵌套迭代器的MoveNext方法中执行不同的代码分支。此外,这些嵌套类型具有与其封装的迭代器函数的局部变量一一对应的字段,从而给人一种局部状态在方法调用之间持续存在的错觉。然而,在程序员看来,迭代器函数的行为就像协程,无论它们如何实现。
- 每当遇到 yieldbreakreturnMoveNext返回值false
综上所述,上述示例中所示的迭代器函数将导致一个类似但不完全相同的嵌套迭代器类型
class _IEnumerable : System.Collections.IEnumerable, System.Collections.IEnumerator
{
bool System.Collections.IEnumerator.MoveNext()
{
    switch(_state)
    {
        case 0: break;
        case 1: goto state_1;
        case 2: goto state_2;
        case 3: goto state_3;
        case 4: goto state_4;
        default: return false;
    }
    _current = 1;
    _state++;
    return true;
    state_1:
        _current = 2;
        _state++;
        return true;
    state_2:
        _current = 3;
        _state++;
        return true;
    state_3:
        _current = 4;
        _state++;
        return true;
    state_4:
        _current = 5;
        _state++;
        return true;
}
//...the rest of the nested class definition
}
此外,迭代器函数本身将被重写以返回上述嵌套迭代器类型的实例。也许像这样:
System.Collections.IEnumerable yieldOneToFive()
{
    return new _IEnumerable();
}
C# 2.0 迭代器是一个极大地简化迭代器设计模式实现的特性。它消除了显式创建实现 IEnumerator 接口的类型的需要。此外,鉴于它们的行为类似于协程,迭代器函数可以用于实现与迭代无关的其他设计模式,有时解决在其中生成的值无关紧要的问题,重要的是能够让方法相互协作,即相互通信并且在这样做时不会丢失局部状态。
至此,关于 C# 2.0 迭代器的简短、几乎不存在的介绍就结束了。再次强调,强烈建议读者理解 C# 规范第 22 章。接下来将描述一个破解,它使您能够在 .NET Framework 1.1 版本中看到此功能的实际应用。
IteratorsHack
要在 Framework 1.1 版本中看到 C# 2.0 迭代器在实际运行,必须采取以下步骤,至少在处理本文描述的破解时如此
- 添加对名为 Iterators的程序集的引用,该程序集公开两个类型:Iterators.IteratorFunctionAttribute和Iterators.Yield。本文随附的源代码提供了这样的程序集。
- 创建返回类型为 IEnumerable的函数。尽管 C# 规范规定迭代器函数可以具有IEnumerable或IEnumerator的返回类型,但此破解要求迭代器函数具有IEnumerable的返回类型,它只是IEnumerator的一个工厂。
- 使用 IteratorFunctionAttribute装饰上述步骤 2 中定义的每个迭代器函数,此属性由上述步骤 1 中引用的程序集公开。
- 在上述步骤 2 中定义的每个迭代器函数中,在需要向调用者生成值时,调用静态方法 Yield.Return,该值是提供给该方法的参数。实际上,C# 2.0 的yieldreturnYield类型中。
- 在每个迭代器函数中,使用带有 nullreturnreturnyieldbreak
- 编译定义了迭代器函数的程序集。
- 运行 IteratorsHack程序集,为其两个命令行参数提供参数。第一个参数包含要处理的源 PE 文件(即上述步骤 6 的结果)的路径。第二个参数包含目标 PE 文件(即破解创建的 PE 文件)的路径,该文件将迭代器函数封装在嵌套的迭代器类型中,这在半完整程度上符合 C# 规范第 22 章的迭代器实现部分。
- 运行上述步骤 7 中破解创建的 PE 文件,并祈祷它能正常工作!
一个破解将处理的迭代器函数示例
[Iterators.IteratorFunction]
System.Collections.IEnumerable yieldOneToFive()
{
    Iterators.Yield.Return(1);
    Iterators.Yield.Return(2);
    Iterators.Yield.Return(3);
    Iterators.Yield.Return(4);
    Iterators.Yield.Return(5);
    return null;
}
在高层次上,该破解执行以下步骤:
- 通过运行 ildasm.exe 工具将源 PE 文件反汇编为 IL 文本文件。
- 将上述步骤 1 中生成的 IL 文本文件的内容加载到 StringCollection中。此后,此StringCollection将被称为指令流。
- 检查上述步骤 2 中加载的指令流,并记录所有迭代器函数。
- 处理指令流的头部和定义部分,添加必要的嵌套 IEnumerable/IEnumerator类型,这些类型封装了上述步骤 3 中捕获的迭代器函数。
- 创建一个包含上述步骤 4 结果的 IL 文本文件。
- 使用 ilasm.exe 工具将上述步骤 5 中创建的 IL 文本文件汇编成 PE 文件。
- 通过 peverify.exe 工具运行上述步骤 6 中创建的 PE 文件,以确保 PE 文件符合类型安全标准,并且没有会导致无效堆栈状态的代码。如果 peverify 工具确定 PE 文件存在问题,则该破解将抛出异常。
请注意,此破解绝非词法分析器,而只是一种粗糙的解析例程,它完全依赖于原生的 string
接下来简要描述该破解执行的部分(但不是全部)处理。具体来说,简要介绍了在将迭代器函数转换为相应的嵌套 IEnumerator 类型过程中对迭代器函数进行的一些 IL 修改,主要关注从迭代器函数到封装该函数的嵌套迭代器的 MoveNext 方法的转换。鉴于此讨论面向 IL,读者最好熟悉 IL 堆栈。
迭代器函数中的实例成员访问
如果迭代器函数是实例方法,则指令流会更新,以便每个参数为零的 ldargldarg.0ldfldldarg.0ldarg.0IEnumerator 类型的 MoveNext 方法中执行,而不是在定义迭代器函数的类中执行。这个内部类将反过来拥有一个指向外部类实例的字段。添加到指令流中的 ldfld 操作将把这个指针推送到堆栈上,以便对外部类型的成员访问不中断,因为后续指令将对这个后者指针进行操作。例如,以下是迭代器函数中定义的访问实例成员的代码
//this is IL code running within a non static Iterator function
//defined within type Namespace1.Class1 
//push instance (this) pointer onto stack 
ldarg.0 
//push field _i onto stack 
ldfld int32 Namespace1.Class1::_i
迭代器函数被封装在嵌套类型中后,上述 IL 代码将类似于
//this is IL code running within the MoveNext method 
//of type Namespace1.Class1/Enumerable1 which is nested 
//within type Namespace1.Class1 
//and represents an Iterator function 
//push instance pointer onto stack 
//that is, pointer to the nested type instance 
//Namespace1.Class1/Enumerable1 
ldarg.0 
//push field _this onto stack 
//which is a pointer to the outer type instance 
//Namespace1.Class1 
ldfld class Namespace1.Class1/Enumerable1::_this 
//push field _i onto stack 
//which is a field of the outer type 
//Namespace1.Class1 
ldfld int32 Namespace1.Class1::_i
执行此处理的实际代码是
private void processMemberAccess(StringCollection sc, 
                    IteratorMethodInfo imi, string cls)
{
    if(imi.IsStatic)
        return;
    int n = -1;
    while(++n < sc.Count)
    {
        string s = sc[n].Trim();
        int ndx = s.IndexOf(": ");
        if(ndx != -1)
            s = s.Substring(ndx + 1).Trim();
        if(s.StartsWith("ldarg.0"))
        sc.Insert(++n, "ldfld class " + cls + " " + cls + "/" + 
                  imi.ShortEnumerableName + "::" + THIS_FIELD);
    }
}
迭代器函数的局部变量和参数的读/写访问
所有读/写局部变量和参数值的指令都将进行处理。所有局部变量和参数最终将在表示迭代器函数的嵌套类型中提升为字段状态。因此,指令流会更新,以便读取局部变量和参数值的指令会转换为读取相应字段值的指令。具体来说,我们要处理的指令是
- ldloc
- ldloca
- ldarg
- ldarga
基本上,每当遇到这些指令之一时,都会根据提供给相关指令的参数定位相应的字段。然后,该指令会替换为 ldfldldfldaldarg.0
//this is IL code running within a non static Iterator function 
//defined within type Namespace1.Class1 
//push onto stack the value of local variable v 
ldloc v 
//push onto stack the value of the argument supplied to parameter p 
//which is the first parameter in the method’s parameter list 
ldarg 1
迭代器函数被封装在嵌套类型中后,上述 IL 代码将类似于
//this is IL code running within the MoveNext method 
//of type Namespace1.Class1/Enumerable1 which is nested 
//within type Namespace1.Class1 
//and represents an Iterator function 
//push onto stack the value of field _v 
//which corresponds to local variable v 
//first though push the instance pointer 
ldarg.0 
ldfld int32 Namespace1.Class1/Enumerable1::_v 
//push onto stack the value of field _p 
//which corresponds to parameter p 
//first though push the instance pointer 
ldarg.0 
ldfld int32 Namespace1.Class1/Enumerable1::_p
执行此处理的实际代码是
private void processLocalRead(StringCollection sc, IteratorMethodInfo imi)
{
    int n = -1;
    while(++n < sc.Count)
    {
        string s = sc[n].Trim();
        int ndx = s.IndexOf(": ");
        string label = string.Empty;
        if(ndx != -1)
        {
            label = s.Substring(0, ndx + 1);
            s = s.Substring(ndx + 1).Trim();
        }
        FieldInfo fi = null;
        bool loadAddress = false;
        if(Regex.IsMatch(s, @"(?:^ldloc(?:\.| ))"))
            fi = getFieldInfo(s, imi, false);
        else if(s.StartsWith("ldloca"))
        {
            fi = getFieldInfo(s, imi, false);
            loadAddress = true;
        }
        else if(Regex.IsMatch(s, @"(?:^ldarg(?:\.| ))") && 
               (imi.IsStatic || s.IndexOf("ldarg.0") == -1))
            fi = getFieldInfo(s, imi, true);
        else if(s.StartsWith("ldarga"))
        {
            fi = getFieldInfo(s, imi, true);
            loadAddress = true;
        }
        if(fi != null)
        {
            sc.Insert(n++, label + " ldarg.0");
            sc[n] = sc[n].Replace(s, "ldfld" + 
              (loadAddress ? "a" : string.Empty) + " " + fi.Type + " " 
              + imi.EnumerableName + "::" + fi.Name).Replace(label, 
              string.Empty).Trim();
        }
    }
}
一旦局部变量和参数的读取指令被处理,所有写入局部变量和参数的指令也都会被处理,尽管后者的过程不像前者那样直接。有必要确保对局部变量和参数进行操作的写入指令反映在嵌套类型的相应字段中。换句话说,如果局部变量或参数的值在迭代器函数中设置,那么当迭代器函数被封装时,与此局部变量或参数对应的嵌套类型的字段也需要设置其值。具体来说,我们要处理的写入指令是
- stloc
- starg
与读取局部变量或参数的情况一样,每当遇到这些指令之一时,都会根据提供给相关指令的参数定位相应的字段。然而,这就是差异所在。读取指令总是被替换;然而,写入指令从不被替换,而是后面跟着额外的指令,这些指令通过 stfld
//this is IL code running within a non static Iterator function 
//defined within type Namespace1.Class1 
//store in local variable v the value that is on top of the stack 
stloc v 
//store in parameter p the value that is on top of the stack 
//parameter p is the first parameter of the method’s parameter list 
starg 1
迭代器函数被封装在嵌套类型中后,上述 IL 代码将类似于
//this is IL code running within the MoveNext method 
//of type Namespace1.Class1/Enumerable1 which is nested 
//within type Namespace1.Class1 
//and represents an Iterator function 
//store in local variable v the value that is on top of the stack 
//notice that this instruction has not been replaced 
stloc v 
//now store in field _v that value of variable v 
ldarg.0 
ldloc v 
stfld int32 Namespace1.Class1/Enumerable1::_v 
//store in parameter p the value that is on top of the stack 
//parameter p is the first parameter of the method’s parameter list 
//notice that this instruction has not been replaced 
//HOWEVER, since the MoveNext method does not have a parameter list 
//all parameters of the Iterator function will become local variables 
//of the MoveNext method, in addition to the local variables of the 
//Iterator function, more on this to come 
stloc p 
//now store in field _p the value of variable p 
//which in the Iterator function was actually parameter p 
//more on this to come 
ldarg.0 
ldloc p 
stfld int32 Namespace1.Class1/Enumerable1::_p
那么这里发生了什么?为什么写入局部变量和参数的指令不像读取局部变量或参数的指令那样简单地被替换?为什么迭代器函数的局部变量也必须在封装迭代器函数的嵌套类型的 MoveNext 方法中可用,尽管此嵌套类型将具有与这些局部变量对应的字段?为什么迭代器函数的参数必须转换为嵌套迭代器的 MoveNext 方法的局部变量,尽管此迭代器将具有与这些参数对应的字段?为什么会有这些低效率?
所有这些问题都有一个简单的答案,那就是,到目前为止所描述的无非是一个破解,因此,它选择了解决一个问题的简单方法,而这个问题否则将需要一个更复杂的解决方案。正如前面提到的,实例成员访问,无论是读取还是写入,都需要事先将实例指针推送到堆栈上。字段读取操作期望堆栈上的最顶层值是实例指针,并且它们只期望看到这个。另一方面,字段写入指令 stfldstfld
所以问题在于将 ldarg.0stfldldarg.0stfldstfldaddstfldldarg.0addaddstfld
要完全用 stfldMoveNext 方法的局部变量存在。局部变量写入指令保持不变,参数写入指令替换为局部变量写入指令,最后,已赋值的局部变量立即赋值给其相应的字段。
执行此处理的实际代码是
private void processLocalWrite(StringCollection sc, IteratorMethodInfo imi)
{
    int n = -1;
    while(++n < sc.Count)
    {
        string s = sc[n].Trim();
        int ndx = s.IndexOf(": ");
        if(ndx != -1)
            s = s.Substring(ndx + 1).Trim();
        FieldInfo fi = null;
        bool starg = false;
        if(s.StartsWith("stloc"))
            fi = getFieldInfo(s, imi, false);
        else if(s.StartsWith("starg"))
        {
            fi = getFieldInfo(s, imi, true);
            starg = true;
        }
        if(fi == null)
            continue;
        string local = fi.LocalName;
        if(starg)
            sc[n] = sc[n].Replace(s, "stloc " + local);
        sc.Insert(++n, "ldarg.0");
        sc.Insert(++n, "ldloc " + local);
        sc.Insert(++n, "stfld " + fi.Type + " " + 
                  imi.EnumerableName + "::" + fi.Name);
    }
}
从迭代器函数生成值
现在,核心问题已解决,即指令流根据所有 yieldreturnyieldreturnYield.Return 方法时,提供的参数将通过 IEnumerator.Current 属性返回给调用者。具体来说:
- 调用 Yield.Return方法的callstloc
- 此局部变量的值被赋给迭代器的 _current字段,此字段当然保存迭代器Current属性的值。
- true- MoveNext方法结果的局部变量中。
- 迭代器的 _state字段的值加一,以便下次调用MoveNext时,它可以查询此字段以确定在代码块中的哪个位置恢复执行。
- 执行无条件地转移到标记为 _EXIT_ITERATOR_BLOCK的指令,该指令只是开始退出MoveNext方法的过程。
- 添加了一个虚拟指令,并将其标记为上述步骤 4 中达到的状态的执行目标。因此,下次调用 MoveNext时,它将从该指令开始执行。
- 必要的指令已添加到 MoveNext代码块的开头,以便考虑到上述步骤 4 中达到的状态并分支到该状态,下次调用MoveNext时。换句话说,状态机相应地更新。
为了说明
//this is IL code running within a non static Iterator function 
//defined within type Namespace1.Class1 
//yield the value 1 to the caller 
ldc.i4.1 
box 
call void [Iterators]Iterators.Yield::Return(object) 
//yield the value 2 to the caller 
ldc.i4.1 
box 
call void [Iterators]Iterators.Yield::Return(object)
迭代器函数被封装在嵌套类型中后,上述 IL 代码将类似于
//this is IL code running within the MoveNext method 
//of type Namespace1.Class1/Enumerable1 which is nested 
//within type Namespace1.Class1 
//and represents an Iterator function 
//branch to state 1 if necessary 
//that is, the state of the Iterator 
//after the first yield is encountered 
ldc.i4 1 
ldarg.0 
ldfld int32 Namespace1.Class1/Enumerable1::_state 
beq _STATE_1 
//otherwise, execution begins here 
//yield the value 1 to the caller 
//store the value in the local variable current 
ldc.i4.1 
box 
stloc current 
//set the _current field to the 
//value of the local variable current 
ldarg.0 
ldloc current 
stfld object Namespace1.Class1/Enumerable1::_current 
//set the local variable result to true 
//this variable holds the value returned by MoveNext 
ldc.i4.1 
stloc result 
//set the _state field to 1 
//so that next time MoveNext is invoked, 
//execution will begin at the instruction labeled STATE_1 
ldarg.0 
ldc.i4 1 
stfld int32 Namespace1.Class1/Enumerable1::_state 
//exit MoveNext 
br _EXIT_ITERATOR_BLOCK 
//execution will begin here when the _state field equals 1 
STATE_1: nop 
//yield the value 2 to the caller in the same manner 
//that the value 1 was yielded, always updating the state machine
执行此处理的实际代码(需要重构)是
private int processMethodYields(StringCollection sc, int index, 
          ref int tryBlockIndex, IteratorMethodInfo imi,
          ref int state, int instrIndex)
{
    bool tryBlock = sc[index == 0 ? 0 : index - 1].Trim().StartsWith(".try");
    bool finallyBlock = 
         sc[index == 0 ? 0 : index - 1].Trim().StartsWith("finally");
    bool validYldBlock = index == 0 || tryBlock;
    string tryBlockLabel = string.Empty;
    int tryInstrIndex = 0;
    if(tryBlock)
    {
        tryBlockLabel = TRY_BLOCK_LABEL + (tryBlockIndex++).ToString();
        sc.Insert(++index, tryBlockLabel + ": nop");
        tryInstrIndex = index + 1;
    }
    else if(finallyBlock)
    {
        int i = index;
        while(!sc[++i].EndsWith(" endfinally"));
        string endFinallyLabel = sc[i].Substring(0, sc[i].IndexOf(":")).Trim();
        sc.Insert(++index, "ldc.i4.1");
        sc.Insert(++index, "ldloc " + imi.MoveNextResultLocal);
        sc.Insert(++index, "beq " + endFinallyLabel);
    }
    while(true)
    {
        string s = sc[++index].Trim();
        if(s == "{")
            index = processMethodYields(sc, index, 
                    ref tryBlockIndex, imi, ref state, instrIndex);
        else if(s.StartsWith("}"))
            return index;
        else if(validYldBlock && Regex.IsMatch(s, 
             @"(?:call +void +\[Iterators\]Iterators\.Yield::Return\(object\))"))
        {
            sc[index] = s.Substring(0, s.IndexOf("call ")) + 
                                    "stloc " + imi.CurrentLocal;
            sc.Insert(++index, "ldarg.0");
            sc.Insert(++index, "ldloc " + imi.CurrentLocal);
            sc.Insert(++index, "stfld object " + 
                      imi.EnumerableName + "::" + CURRENT_FIELD);
            sc.Insert(++index, "ldc.i4.1");
            sc.Insert(++index, "stloc " + imi.MoveNextResultLocal);
            sc.Insert(++index, "ldarg.0");
            sc.Insert(++index, "ldc.i4 " + (++state).ToString());
            sc.Insert(++index, "stfld int32 " + 
                      imi.EnumerableName + "::" + STATE_FIELD);
            sc.Insert(++index, (tryBlock ? "leave" : "br") + 
                                " " + EXIT_ITERATOR_BLOCK_LABEL);
            string stateLabel = STATE_LABEL + state.ToString();
            sc.Insert(++index, stateLabel + ": nop");
            sc.Insert(instrIndex++, "ldc.i4 " + (state).ToString());
            index++;
            tryInstrIndex++;
            sc.Insert(instrIndex++, "ldarg.0");
            index++;
            tryInstrIndex++;
            sc.Insert(instrIndex++, "ldfld int32 " + 
                      imi.EnumerableName + "::" + STATE_FIELD);
            index++;
            tryInstrIndex++;
            sc.Insert(instrIndex++, 
                      "beq " + (tryBlock ? tryBlockLabel : stateLabel));
            index++;
            tryInstrIndex++;
            if(!tryBlock)
                continue;
            sc.Insert(tryInstrIndex++, "ldc.i4 " + (state).ToString());
            index++;
            sc.Insert(tryInstrIndex++, "ldarg.0");
            index++;
            sc.Insert(tryInstrIndex++, "ldfld int32 " + 
                      imi.EnumerableName + "::" + STATE_FIELD);
            index++;
            sc.Insert(tryInstrIndex++, "beq " + stateLabel);
            index++;
        }
    }
}
终止迭代器函数的执行
指令流已更新,所有 retreturn
- 弹出堆栈顶部的数值。由于迭代器函数返回 IEnumerable类型的值,因此在任何遇到retnullIEnumerable类型。但是,由于迭代器函数最终会被转换为封装迭代器函数的嵌套迭代器的MoveNext方法,而且MoveNext方法返回Boolean类型的值,因此堆栈上的任何值都会被弹出,因为从返回Boolean的函数返回IEnumerable类型的值是不合法的。
- 将 falseMoveNext方法结果的局部变量中。回想一下,除了yieldreturnMoveNext方法将返回false
- 值 -1 存储在迭代器的 _state字段中,以便后续对MoveNext方法的任何调用除了立即退出外不会执行任何操作。
- 无条件地分支到标记为 _EXIT_ITERATOR_BLOCK的指令,该指令只是开始退出MoveNext方法的过程。
为了说明
//this is IL code running within a non static Iterator function 
//defined within type Namespace1.Class1 
//exit the method 
//we know that either null 
//or a value of type IEnumerable 
//is the only value on the stack 
ret
迭代器函数被封装在嵌套类型中后,上述 IL 代码将类似于
//this is IL code running within the MoveNext method 
//of type Namespace1.Class1/Enumerable1 which is nested 
//within type Namespace1.Class1 
//and represents an Iterator function 
//pop off whatever value is on the stack, 
//either null or a value of type IEnumerable 
pop 
//store the value false in the local variable 
//that holds the result of the MoveNext method 
ldc.i4.0 
stloc result 
//store the value -1 in the _state field 
ldarg.0 
ldc.i4 -1 
stfld int32 Namespace1.Class1/Enumerable1::_state 
//unconditionally branch to the instruction 
//labeled _EXIT_ITERATOR_BLOCK 
br _EXIT_ITERATOR_BLOCK
执行此处理的实际代码是
private void processMethodReturns(StringCollection sc, IteratorMethodInfo imi)
{
    int n = -1;
    string result = imi.MoveNextResultLocal;
    while(++n < sc.Count)
    {
        string s = sc[n].Trim();
        int ndx = s.IndexOf(": ");
        string label = string.Empty;
        if(ndx != -1)
        {
            label = s.Substring(0, ndx + 1);
            s = s.Substring(ndx + 1).Trim();
        }
        if(s != "ret")
            continue;
        sc.Insert(n++, (label == string.Empty ? label : label + " ") + "pop");
        sc.Insert(n++, "ldc.i4.0");
        sc.Insert(n++, "stloc " + result);
        sc.Insert(n++, "ldarg.0");
        sc.Insert(n++, "ldc.i4 -1");
        sc.Insert(n++, "stfld int32 " + imi.EnumerableName + "::" + STATE_FIELD);
        sc[n] = "br " + EXIT_ITERATOR_BLOCK_LABEL;
    }
}
至此,我们结束了对该破解执行的一些 IL 破解的讨论。
演示
本文的源代码提供了相同演示的两个版本,一个用 C# 编写,另一个用 VB 编写。该演示是一个简单的 Windows 应用程序,通过使用所选目录的内容填充 TreeView 控件来演示递归迭代器的使用。请注意您选择的目录的深度;没有取消按钮!此外,不言而喻,该演示是微不足道的,并且只有在它是经过 IteratorsHack 进程运行的 PE 文件时才能工作。为方便起见,每个版本的演示都在相关演示项目的根文件夹中直接放置了这样的 PE 文件。
C# 版本的递归迭代器函数是
[IteratorFunction]
private IEnumerable getDirectories(DirectoryInfo dir)
{
    foreach(DirectoryInfo dir1 in dir.GetDirectories())
    {
        Yield.Return(dir1);
        foreach(DirectoryInfo dir2 in getDirectories(dir1))
        {
            Yield.Return(dir2);
        }
    }
    return null;
}
VB 版本的递归迭代器函数是
<IteratorFunction()> _
Private Function getDirectories(ByVal dir As DirectoryInfo) As IEnumerable
    For Each dir1 As DirectoryInfo In dir.GetDirectories()
        Yield.Return(dir1)
        For Each dir2 As DirectoryInfo In getDirectories(dir1)
            Yield.Return(dir2)
        Next
    Next
End Function
最终结论
C# 2.0 迭代器是一个强大的编程功能,它简化了迭代器设计模式的实现,尽管迭代器还有许多其他用途,这些用途与迭代本身无关。IteratorsHack 是一个次优的破解,它使得在 Framework 1.1 版本中看到迭代器功能成为可能。这之所以可能,仅仅是因为迭代器是 C# 2.0 编译器的一个功能,而不是 .NET 2.0 Framework (CLR/BCL) 的功能,不像泛型那样。不幸的是,VB 的下一个版本,它针对 .NET Framework 2.0 版本,将不具备迭代器编译器功能,只有 C# 会有。确实,VB 程序员将不得不等待,尽管 VB 比 C# 任何时候都强大 Int32.MaxValue 倍,这是一个普遍、毋庸置疑且显而易见的事实!我真诚地道歉,但我只是忍不住要发表这个廉价的评论。
感谢您花时间阅读这篇文章,希望您喜欢它,就像我喜欢写它一样。如果存在任何不准确之处(因为我不是任何领域的专家,这是完全有可能的),请告知。各位兄弟们,再见,下次再见,当然,如果上帝愿意的话!
