现在的位置: 首页 > 综合 > 正文

【Emit基础】如何发射foreach代码?

2013年10月05日 ⁄ 综合 ⁄ 共 3516字 ⁄ 字号 评论关闭

  对于集合的遍历,使用foreach是非常方便的,但是Emit动态生成foreach的代码就要复杂很多。它涉及到以下几个方面:

(1)IEnumerable<> 是所有可枚举类型的基础接口。

(2)IEnumerator<>,通过IEnumerable<> 接口的GetEnumerator方法可以获取枚举器IEnumerator<>,而对集合元素的遍历正是由IEnumerator<>的MoveNext方法完成的。

(3)遍历完成以后,需要调用IEnumerator<>的Dispose方法释放它。

(4)为了IEnumerator<>被正常释放,还需要使用try....finally块包含相应的代码。

 

下面我们来举个例子,比如对于如下的C#代码:

    public interface ICompute
    {
        
void Add(int a, int b);       
    }
    
public class Compute : ICompute
    {
        
private ICollection<ICompute> computers;

        public void Add(int a, int b)
        {
            
foreach (ICompute com in computers)
            {
                com.Add(a, b);
            }
        }
    }

 

     Compute类的Add方法使用了foreach进行遍历操作,我们可以将Add方法的等价形式写出来:

public virtual void Add(int num1, int num2)
{
    
IEnumerator<ICompute> enumerator = this.computers.GetEnumerator();
    
try
    {
        
while (enumerator.MoveNext())
        {
            enumerator.Current.Add(num1, num2);
        }
    }
    
finally
    {
        
if (enumerator != null)
        {
            enumerator.Dispose();
        }
    }
}

 

  那么为了Emit类似的代码,需要生成如下的IL:

.method public hidebysig newslot virtual final instance void Add(int32 a, int32 b) cil managed
{
    .maxstack 
3
    .locals init (
        [
0class TheTest.ICompute com,
        [
1class [mscorlib]System.Collections.Generic.IEnumerator`1<class TheTest.ICompute> CS$5$0000,
        [
2bool CS$4$0001)
    L_0000: nop 
    L_0001: nop 
    L_0002: ldarg.
0 
    L_0003: ldfld 
class  class [mscorlib]System.Collections.Generic.ICollection`1<class TheTest.ICompute> TheTest.Compute::computers
  
  L_000d: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<class TheTest.ICompute>::GetEnumerator()
    L_0012: stloc.
1 
    L_0013: br.s L_0027
    L_0015: ldloc.
1 
    L_0016: callvirt instance 
!0 [mscorlib]System.Collections.Generic.IEnumerator`1<class TheTest.ICompute>::get_Current()
    L_001b: stloc.
0 
    L_001c: nop 
    L_001d: ldloc.
0 
    L_001e: ldarg.
1 
    L_001f: ldarg.
2 
    L_0020: callvirt instance 
void TheTest.ICompute::Add(int32, int32)
    L_0025: nop 
    L_0026: nop 
    L_0027: ldloc.
1 
    L_0028: callvirt instance 
bool [mscorlib]System.Collections.IEnumerator::MoveNext()
    L_002d: stloc.
2 
    L_002e: ldloc.
2 
    L_002f: brtrue.s L_0015
    L_0031: leave.s L_0043
    L_0033: ldloc.
1 
    L_0034: ldnull 
    L_0035: ceq 
    L_0037: stloc.
2 
    L_0038: ldloc.
2 
    L_0039: brtrue.s L_0042
    L_003b: ldloc.
1 
    
L_003c: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0041: nop 
    L_0042: endfinally 
    L_0043: nop 
    L_0044: ret 
   
 .try L_0013 to L_0033 finally handler L_0033 to L_0043
}

 

   请注意红色代码部分,这与我们上面的描述是一致的。其它的IL代码的Emit相对简单,这里我们只提一下如何Emit最后这句try...finally块:

.try L_0013 to L_0033 finally handler L_0033 to L_0043

 

  对于try...catch...finnally块的标准Emit过程时这样的:

 ILGenerator methodGen = ..... ;
 
 //开始try块
 methodGen.BeginExceptionBlock();
 
//......
 
//开始catch块
 methodGen.BeginCatchBlock(typeof(Exception));
 //......
 
//开始finally块
 methodGen.BeginFinallyBlock();
 //......
 //结束try/Catch/finally块
 methodGen.EndExceptionBlock(); 

 

如果只有try...catch...,则

 ILGenerator methodGen = ..... ;

 //开始try块
 methodGen.BeginExceptionBlock();
 
//......
 
//开始catch块
 methodGen.BeginCatchBlock(typeof(Exception));
 
//......
 
//结束try/Catch块
 methodGen.EndExceptionBlock(); 

如果只有try...finnally...,则

 ILGenerator methodGen = ..... ;
 
//开始try块
 methodGen.BeginExceptionBlock();
 
//......
 
//开始finally块
 methodGen.BeginFinallyBlock();
 
//......
 
//结束try/finally块
 methodGen.EndExceptionBlock(); 

 

 

 

 

抱歉!评论已关闭.