对于集合的遍历,使用foreach是非常方便的,但是Emit动态生成foreach的代码就要复杂很多。它涉及到以下几个方面:
(1)IEnumerable<> 是所有可枚举类型的基础接口。
(2)IEnumerator<>,通过IEnumerable<> 接口的GetEnumerator方法可以获取枚举器IEnumerator<>,而对集合元素的遍历正是由IEnumerator<>的MoveNext方法完成的。
(3)遍历完成以后,需要调用IEnumerator<>的Dispose方法释放它。
(4)为了IEnumerator<>被正常释放,还需要使用try....finally块包含相应的代码。
下面我们来举个例子,比如对于如下的C#代码:
{
voidAdd(inta,intb);
}
publicclassCompute:ICompute
{
privateICollection<ICompute>computers;
publicvoidAdd(inta,intb)
{
foreach(IComputecomincomputers)
{
com.Add(a,b);
}
}
}
Compute类的Add方法使用了foreach进行遍历操作,我们可以将Add方法的等价形式写出来:
{
IEnumerator<ICompute>enumerator=this.computers.GetEnumerator();
try
{
while(enumerator.MoveNext())
{
enumerator.Current.Add(num1,num2);
}
}
finally
{
if(enumerator!=null)
{
enumerator.Dispose();
}
}
}
那么为了Emit类似的代码,需要生成如下的IL:
{
.maxstack3
.localsinit(
[0]classTheTest.IComputecom,
[1]class[mscorlib]System.Collections.Generic.IEnumerator`1<classTheTest.ICompute>CS$5$0000,
[2]boolCS$4$0001)
L_0000:nop
L_0001:nop
L_0002:ldarg.0
L_0003:ldfldclassclass[mscorlib]System.Collections.Generic.ICollection`1<classTheTest.ICompute>TheTest.Compute::computers
L_000d:callvirtinstanceclass[mscorlib]System.Collections.Generic.IEnumerator`1<!0>[mscorlib]System.Collections.Generic.IEnumerable`1<classTheTest.ICompute>::GetEnumerator()
L_0012:stloc.1
L_0013:br.sL_0027
L_0015:ldloc.1
L_0016:callvirtinstance!0[mscorlib]System.Collections.Generic.IEnumerator`1<classTheTest.ICompute>::get_Current()
L_001b:stloc.0
L_001c:nop
L_001d:ldloc.0
L_001e:ldarg.1
L_001f:ldarg.2
L_0020:callvirtinstancevoidTheTest.ICompute::Add(int32,int32)
L_0025:nop
L_0026:nop
L_0027:ldloc.1
L_0028:callvirtinstancebool[mscorlib]System.Collections.IEnumerator::MoveNext()
L_002d:stloc.2
L_002e:ldloc.2
L_002f:brtrue.sL_0015
L_0031:leave.sL_0043
L_0033:ldloc.1
L_0034:ldnull
L_0035:ceq
L_0037:stloc.2
L_0038:ldloc.2
L_0039:brtrue.sL_0042
L_003b:ldloc.1
L_003c:callvirtinstancevoid[mscorlib]System.IDisposable::Dispose()
L_0041:nop
L_0042:endfinally
L_0043:nop
L_0044:ret
.tryL_0013toL_0033finallyhandlerL_0033toL_0043
}
请注意红色代码部分,这与我们上面的描述是一致的。其它的IL代码的Emit相对简单,这里我们只提一下如何Emit最后这句try...finally块:
对于try...catch...finnally块的标准Emit过程时这样的:
methodGen.BeginExceptionBlock();
//......
//开始catch块
methodGen.BeginCatchBlock(typeof(Exception));
//......
//开始finally块
methodGen.BeginFinallyBlock();
//......
//结束try/Catch/finally块
methodGen.EndExceptionBlock();
如果只有try...catch...,则
//开始try块
methodGen.BeginExceptionBlock();
//......
//开始catch块
methodGen.BeginCatchBlock(typeof(Exception));
//......
//结束try/Catch块
methodGen.EndExceptionBlock();
如果只有try...finnally...,则
//开始try块
methodGen.BeginExceptionBlock();
//......
//开始finally块
methodGen.BeginFinallyBlock();
//......
//结束try/finally块
methodGen.EndExceptionBlock();