程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Emit學習-基礎篇-使用循環

Emit學習-基礎篇-使用循環

編輯:關於.NET

今天我們來研究下用IL書寫For循環和Foreach循環,在書中一直看到說使用 Foreach循環比普通的For循環來的好,這次正好趁機來看看他們的IL代碼有何不同 .按照慣例,我們先給出要實現的類的C#代碼,如下:

class Iterator

{

    public int ForMethod(int[] ints)

    {

        int sum = 0;

        for (int i = 0; i < ints.Length; i++)

        {

            sum += ints[i];

        }

        return sum;

    }


    public int ForeachMethod(int[] ints)

    {

        int sum = 0;

        foreach (int i in ints)

        {

            sum += i;

        }

        return sum;

    }

}

然後,我們先來實現其中的ForMethod,相信大家已經等不及了,那麼我先給出實 現的IL代碼,然後針對其中的關鍵部分進行講解,代碼如下:

MakeForMethod

/// <summary>

/// 生成For循環

/// </summary>

/// <param name="typeBuilder"></param>

static void MakeForMethod(TypeBuilder typeBuilder)

{

    //定義一個傳入參數為Int32[],返回值為Int32的方法

    MethodBuilder methodBuilder = typeBuilder.DefineMethod

("ForMethod", MethodAttributes.Public | MethodAttributes.Static, 

typeof(Int32), new Type[] { typeof(Int32[]) });

    ILGenerator methodIL = methodBuilder.GetILGenerator();


    //用來保存求和結果的局部變量

    LocalBuilder sum = methodIL.DeclareLocal(typeof(Int32));

    //循環中使用的局部變量

    LocalBuilder i = methodIL.DeclareLocal(typeof(Int32));


    Label compareLabel = methodIL.DefineLabel();

    Label enterLoopLabel = methodIL.DefineLabel();


    //int sum = 0;

    methodIL.Emit(OpCodes.Ldc_I4_0);

    methodIL.Emit(OpCodes.Stloc_0);

    //int i = 0

    methodIL.Emit(OpCodes.Ldc_I4_0);

    methodIL.Emit(OpCodes.Stloc_1);

    methodIL.Emit(OpCodes.Br, compareLabel);


    //定義一個標簽,表示從下面開始進入循環體

    methodIL.MarkLabel(enterLoopLabel);

    //sum += ints[i];

    //其中Ldelem_I4用來加載一個數組中的Int32類型的元素

    methodIL.Emit(OpCodes.Ldloc_0);

    methodIL.Emit(OpCodes.Ldarg_0);

    methodIL.Emit(OpCodes.Ldloc_1);

    methodIL.Emit(OpCodes.Ldelem_I4);

    methodIL.Emit(OpCodes.Add);

    methodIL.Emit(OpCodes.Stloc_0);


    //i++

    methodIL.Emit(OpCodes.Ldloc_1);

    methodIL.Emit(OpCodes.Ldc_I4_1);

    methodIL.Emit(OpCodes.Add);

    methodIL.Emit(OpCodes.Stloc_1);


    //定義一個標簽,表示從下面開始進入循環的比較

    methodIL.MarkLabel(compareLabel);

    //i < ints.Length

    methodIL.Emit(OpCodes.Ldloc_1);

    methodIL.Emit(OpCodes.Ldarg_0);

    methodIL.Emit(OpCodes.Ldlen);

    methodIL.Emit(OpCodes.Conv_I4);

    methodIL.Emit(OpCodes.Clt);

    methodIL.Emit(OpCodes.Brtrue_S, enterLoopLabel);


    //return sum;

    methodIL.Emit(OpCodes.Ldloc_0);

    methodIL.Emit(OpCodes.Ret);

}

為了在測試時方便點,這裡我為方法加上了Static標簽,要注意的是,非靜態方 法的第一參數是this指針,而Static方法的第一個參數既是傳入的第一個參數,所 以在使用時要注意.要寫一個循環,和我們之前的代碼有所不同的兩個地方就是取 數組裡的元素和取數組的長度.

其中,取數組裡的元素使用如下的指令流程:

l  首先,加載數組指針到堆棧: methodIL.Emit(OpCodes.Ldarg_0);

l  然後,加載現在要加載的元素在數組中的索引:methodIL.Emit (OpCodes.Ldloc_1);

l  最後,獲得數組中的元素:methodIL.Emit(OpCodes.Ldelem_I4);這裡的 Ldelem_I4表示加載的數組元素為Int32類型。

取數組的長度使用下面這樣的指令流程:

l  首先,加載數組指針到堆棧: methodIL.Emit(OpCodes.Ldarg_0);

l  然後,加載數組的長度:methodIL.Emit(OpCodes.Ldlen);

l  最後,由於加載的數組長度為無符號整數,所以還需要轉換成Int32類型 來使用:methodIL.Emit(OpCodes.Conv_I4);

接下來,我們給出ForeachMethod的IL實現,代碼如下:

MakeForeachMethod

/// <summary>

/// 生成Foreach循環

/// </summary>

/// <param name="typeBuilder"></param>

static void MakeForeachMethod(TypeBuilder typeBuilder)

{

    //定義一個傳入參數為Int32[],返回值為Int32的方法

    MethodBuilder methodBuilder = typeBuilder.DefineMethod

("ForeachMethod", MethodAttributes.Public | MethodAttributes.Static, 

typeof(Int32), new Type[] { typeof(Int32[]) });

    ILGenerator methodIL = methodBuilder.GetILGenerator();


    //用來保存求和結果的局部變量

    LocalBuilder sum = methodIL.DeclareLocal(typeof(Int32));

    //foreach 中的 int i 

    LocalBuilder i = methodIL.DeclareLocal(typeof(Int32));

    //用來保存傳入的數組

    LocalBuilder ints = methodIL.DeclareLocal(typeof(Int32[]));

    //數組循環用臨時變量

    LocalBuilder index = methodIL.DeclareLocal(typeof(Int32));


    Label compareLabel = methodIL.DefineLabel();

    Label enterLoopLabel = methodIL.DefineLabel();


    //int sum = 0;

    methodIL.Emit(OpCodes.Ldc_I4_0);

    methodIL.Emit(OpCodes.Stloc_0);

    //ints = ints

    methodIL.Emit(OpCodes.Ldarg_0);

    methodIL.Emit(OpCodes.Stloc_2);

    //int index = 0

    methodIL.Emit(OpCodes.Ldc_I4_0);

    methodIL.Emit(OpCodes.Stloc_3);

    methodIL.Emit(OpCodes.Br, compareLabel);


    //定義一個標簽,表示從下面開始進入循環體

    methodIL.MarkLabel(enterLoopLabel);

    //其中Ldelem_I4用來加載一個數組中的Int32類型的元素

    //加載 i = ints[index]

    methodIL.Emit(OpCodes.Ldloc_2);

    methodIL.Emit(OpCodes.Ldloc_3);

    methodIL.Emit(OpCodes.Ldelem_I4);

    methodIL.Emit(OpCodes.Stloc_1);

    //sum += i;

    methodIL.Emit(OpCodes.Ldloc_0);

    methodIL.Emit(OpCodes.Ldloc_1);

    methodIL.Emit(OpCodes.Add);

    methodIL.Emit(OpCodes.Stloc_0);


    //index++

    methodIL.Emit(OpCodes.Ldloc_3);

    methodIL.Emit(OpCodes.Ldc_I4_1);

    methodIL.Emit(OpCodes.Add);

    methodIL.Emit(OpCodes.Stloc_3);


    //定義一個標簽,表示從下面開始進入循環的比較

    methodIL.MarkLabel(compareLabel);

    //index < ints.Length

    methodIL.Emit(OpCodes.Ldloc_3);

    methodIL.Emit(OpCodes.Ldloc_2);

    methodIL.Emit(OpCodes.Ldlen);

    methodIL.Emit(OpCodes.Conv_I4);

    methodIL.Emit(OpCodes.Clt);

    methodIL.Emit(OpCodes.Brtrue_S, enterLoopLabel);


    //return sum;

    methodIL.Emit(OpCodes.Ldloc_0);

    methodIL.Emit(OpCodes.Ret);

}

其中用紅色字體標出的是它和ForMethod的主要不同之處,首先,它用一個局 部變量保存了整個數組,並用它替換了所有原先直接使用數組的地方;最後,它 把sum += ints[i];的操作分解成為i = ints[index]和sum += i兩個步驟。這兩 個不同之處也是我分析了自動生成的IL代碼之後才得出的,具體為什麼還希望有 人能夠給我指點,因為都是說Foreach循環所生成的代碼是最優的,但是在這裡看 來卻比For循環多生成了一些IL語句,我想肯定是我哪裡的分析有誤,忘有人能夠 給我指出,不甚感激!

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved