MSIL實用指南-閉包的生成和調用
閉包(Closure)是詞法閉包(Lexical Closure)的簡稱。對閉包的具體定義有很多種說法,這些說法大體可以分為兩類:
一種說法認為閉包是符合一定條件的函數,比如參考資源中這樣定義閉包:閉包是在其詞法上下文中引用了自由變量(註1)的函數。
另一種說法認為閉包是由函數和與其相關的引用環境組合而成的實體。比如參考資源中就有這樣的的定義:在實現深約束(註2)時,需要創建一個能顯式表示引用環境的東西,並將它與相關的子程序捆綁在一起,這樣捆綁起來的整體被稱為閉包。
這兩種定義在某種意義上是對立的,一個認為閉包是函數,另一個認為閉包是函數和引用環境組成的整體。雖然有些咬文嚼字,但可以肯定第二種說法更確切。閉包只是在形式和表現上像函數,但實際上不是函數。函數是一些可執行的代碼,這些代碼在函數被定義後就確定了,不會在執行時發生變化,所以一個函數只有一個實例。閉包在運行時可以有多個實例,不同的引用環境和相同的函數組合可以產生不同的實例。所謂引用環境是指在程序執行中的某個點所有處於活躍狀態的約束所組成的集合。其中的約束是指一個變量的名字和其所代表的對象之間的聯系。那麽為什麽要把引用環境與函數組合起來呢?這主要是因為在支持嵌套作用域的語言中,有時不能簡單直接地確定函數的引用環境。這樣的語言一般具有這樣的特性:
函數是一階值(First-class value),即函數可以作為另一個函數的返回值或參數,還可以作為一個變量的值。
函數可以嵌套定義,即在一個函數內部可以定義另一個函數。
在C#裏面,變量作用域有三種,一種是屬於類的,我們常稱之為field;第二種則屬於函數的,我們通常稱之為局部變量;還有一種,其實也是屬於函數的,不過它的作用範圍更小,它只屬於函數局部的代碼片段,這種同樣稱之為局部變量。這三種變量的生命周期基本都可以用一句話來說明,每個變量都屬於它所寄存的對象,即變量隨著其寄存對象生而生和消亡。對應三種作用域我們可以這樣說,類裏面的變量是隨著類的實例化而生,同時伴隨著類對象的資源回收而消亡(當然這裏不包括非實例化的static和const對象)。而函數(或代碼片段)的變量也隨著函數(或代碼片段)調用開始而生,伴隨函數(或代碼片段)調用結束而自動由GC釋放,它內部變量生命周期滿足先進後出的特性。
比如說下面這個閉包例子:
public class ClosureTest { public static Func<int> GetClosure() { var n = 0; return () => { n++; return n; }; } public static void Main() { Func<int> fn = null; fn = GetClosure(); Console.WriteLine(fn()); Console.WriteLine(fn()); Console.WriteLine(fn()); } }
執行的結果是
1
2
3
而不是別的。
可以看出局部變量n並沒有在函數調用後被回收,而是一直存在。
我們這裏就講解怎麽實現閉包。
在最後的生成二進制文件中,局部變量n已經不是在函數內,而是轉移到了一個內部類中。
一、生成內部類
這個內部類有一下幾個成員
1.一個字段,用於存儲n的值;
2.一個最簡單的構造函數,用於被調用生成實例;
3.一個方法,用於執行lamda表達式。
生成內部類的
第1步:聲明內部類
nestedTypeBuilder = typeBuilder.DefineNestedType("ClosureNestedClass", TypeAttributes.NestedPublic | TypeAttributes.Sealed);
第2步:創建字段
字段的類型和局部變量n的類型是一樣的。
nestedFieldBuilder = nestedTypeBuilder.DefineField("NestedFeld", typeof(int), FieldAttributes.Public);
第3步:構造函數
構造函數是一個無參的構造函數。
nestedNewBuilder = nestedTypeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { });
它的方法體只需要一個最簡單ret指令就可以了。
var il = nestedNewBuilder.GetILGenerator(); il.Emit(OpCodes.Ret);
第4步:創建字段
方法是無參的,返回類型和局部變量n的類型一樣。
nestedLambdsMethod = nestedTypeBuilder.DefineMethod("Run", MethodAttributes.Public, typeof(int), new Type[] { });
方法體內聲明一個局部變量用於返回結果。
LocalBuilder retlocalBuilder = ilGenerator.DeclareLocal(typeof(int));
按照上面的示例程序給字段n++。
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldfld, nestedFieldBuilder);
ilGenerator.Emit(OpCodes.Ldc_I4_1);
ilGenerator.Emit(OpCodes.Add);
ilGenerator.Emit(OpCodes.Stfld, nestedFieldBuilder);
把字段保存到那個局部變量上
ilGenerator.Emit(OpCodes.Ldarg_0);
ilGenerator.Emit(OpCodes.Ldfld, nestedFieldBuilder);
ilGenerator.Emit(OpCodes.Stloc_0);
返回結果
ilGenerator.Emit(OpCodes.Ldloc_0);
ilGenerator.Emit(OpCodes.Ret);
第5步:完成類型
nestedTypeBuilder.CreateType();
二、生成GetClosure方法
1.聲明方法
getClosureMethod = typeBuilder.DefineMethod("GetClosure", MethodAttributes.Public | MethodAttributes.Static, typeof(Func<int>), new Type[] { });
2.聲明兩個局部變量
用於暫存
LocalBuilder localBuilder0 = ilGenerator.DeclareLocal(nestedTypeBuilder);
用於返回結果
LocalBuilder retlocalBuilder = ilGenerator.DeclareLocal(typeof(Func<int>));
3.創建一個內部類實例,保存到localBuilder0
ilGenerator.Emit(OpCodes.Newobj, nestedNewBuilder);
ilGenerator.Emit(OpCodes.Stloc_0);
4.給這個實例的字段初始化為0
這裏是對應示例程序
var n = 0;
ilGenerator.Emit(OpCodes.Ldloc_0);
ilGenerator.Emit(OpCodes.Ldc_I4_0);
ilGenerator.Emit(OpCodes.Stfld, nestedFieldBuilder);
5.生成一個Func<int>實例,並保存
這裏用Ldftn指令
ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Ldftn, nestedLambdsMethod); ilGenerator.Emit(OpCodes.Newobj, typeof(Func<int>).GetConstructors()[0]); ilGenerator.Emit(OpCodes.Stloc_1);
6.返回結果
ilGenerator.Emit(OpCodes.Ldloc_1);
ilGenerator.Emit(OpCodes.Ret);
三、生成測試方法
這個比較簡單,不是本篇重點。
static void GenerateMain() { mainMethod = typeBuilder.DefineMethod("Main", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[] { }); ILGenerator ilGenerator = mainMethod.GetILGenerator(); LocalBuilder localBuilder0 = ilGenerator.DeclareLocal(typeof(Func<int>)); ilGenerator.Emit(OpCodes.Call, getClosureMethod); ilGenerator.Emit(OpCodes.Stloc_0); ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Callvirt, typeof(Func<int>).GetMethod("Invoke", new Type[] { })); ilGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int)})); ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Callvirt, typeof(Func<int>).GetMethod("Invoke", new Type[] { })); ilGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) })); ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Callvirt, typeof(Func<int>).GetMethod("Invoke", new Type[] { })); ilGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) })); EmitReadKey(ilGenerator); ilGenerator.Emit(OpCodes.Ret); }
完整的程序如下:
using System; using System.Reflection; using System.Reflection.Emit; namespace LX1_ILDemo { class Demo29_Closure { static string binaryName = "Demo29_Closure.exe"; static string namespaceName = "LX1_ILDemo"; static string typeName = "DemoClosure"; static AssemblyBuilder assemblyBuilder; static ModuleBuilder moduleBuilder; static TypeBuilder typeBuilder; static MethodBuilder mainMethod; static MethodBuilder getClosureMethod; static TypeBuilder nestedTypeBuilder; static FieldBuilder nestedFieldBuilder; static MethodBuilder nestedLambdsMethod; static ConstructorBuilder nestedNewBuilder; public static void Generate() { InitAssembly(); typeBuilder = moduleBuilder.DefineType(namespaceName + "." + typeName, TypeAttributes.Public); Generate_Nested(); Generate_GetClosure(); GenerateMain(); assemblyBuilder.SetEntryPoint(mainMethod, PEFileKinds.ConsoleApplication); SaveAssembly(); Console.WriteLine("生成成功"); } static void Generate_Nested() { nestedTypeBuilder = typeBuilder.DefineNestedType("ClosureNestedClass", TypeAttributes.NestedPublic | TypeAttributes.Sealed); nestedFieldBuilder = nestedTypeBuilder.DefineField("NestedFeld", typeof(int), FieldAttributes.Public); nestedLambdsMethod = nestedTypeBuilder.DefineMethod("Run", MethodAttributes.Public, typeof(int), new Type[] { }); nestedNewBuilder = nestedTypeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { }); var il = nestedNewBuilder.GetILGenerator(); il.Emit(OpCodes.Ret); ILGenerator ilGenerator = nestedLambdsMethod.GetILGenerator(); LocalBuilder retlocalBuilder = ilGenerator.DeclareLocal(typeof(int)); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ldfld, nestedFieldBuilder); ilGenerator.Emit(OpCodes.Ldc_I4_1); ilGenerator.Emit(OpCodes.Add); ilGenerator.Emit(OpCodes.Stfld, nestedFieldBuilder); ilGenerator.Emit(OpCodes.Ldarg_0); ilGenerator.Emit(OpCodes.Ldfld, nestedFieldBuilder); ilGenerator.Emit(OpCodes.Stloc_0); ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Ret); nestedTypeBuilder.CreateType(); } static void Generate_GetClosure() { getClosureMethod = typeBuilder.DefineMethod("GetClosure", MethodAttributes.Public | MethodAttributes.Static, typeof(Func<int>), new Type[] { }); ILGenerator ilGenerator = getClosureMethod.GetILGenerator(); LocalBuilder localBuilder0 = ilGenerator.DeclareLocal(nestedTypeBuilder); LocalBuilder retlocalBuilder = ilGenerator.DeclareLocal(typeof(Func<int>)); ilGenerator.Emit(OpCodes.Newobj, nestedNewBuilder); ilGenerator.Emit(OpCodes.Stloc_0); ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Ldc_I4_0); ilGenerator.Emit(OpCodes.Stfld, nestedFieldBuilder); ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Ldftn, nestedLambdsMethod); ilGenerator.Emit(OpCodes.Newobj, typeof(Func<int>).GetConstructors()[0]); ilGenerator.Emit(OpCodes.Stloc_1); ilGenerator.Emit(OpCodes.Ldloc_1); ilGenerator.Emit(OpCodes.Ret); } static void GenerateMain() { mainMethod = typeBuilder.DefineMethod("Main", MethodAttributes.Public | MethodAttributes.Static, typeof(void), new Type[] { }); ILGenerator ilGenerator = mainMethod.GetILGenerator(); LocalBuilder localBuilder0 = ilGenerator.DeclareLocal(typeof(Func<int>)); ilGenerator.Emit(OpCodes.Call, getClosureMethod); ilGenerator.Emit(OpCodes.Stloc_0); ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Callvirt, typeof(Func<int>).GetMethod("Invoke", new Type[] { })); ilGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int)})); ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Callvirt, typeof(Func<int>).GetMethod("Invoke", new Type[] { })); ilGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) })); ilGenerator.Emit(OpCodes.Ldloc_0); ilGenerator.Emit(OpCodes.Callvirt, typeof(Func<int>).GetMethod("Invoke", new Type[] { })); ilGenerator.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) })); EmitReadKey(ilGenerator); ilGenerator.Emit(OpCodes.Ret); } static void EmitReadKey(ILGenerator ilGenerator) { MethodInfo readKeyMethod = typeof(Console).GetMethod("ReadKey", new Type[] { }); ilGenerator.Emit(OpCodes.Call, readKeyMethod); ilGenerator.Emit(OpCodes.Pop); } static void InitAssembly() { AssemblyName assemblyName = new AssemblyName(namespaceName); assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name, binaryName); } static void SaveAssembly() { Type t = typeBuilder.CreateType(); //完成Type,這是必須的 assemblyBuilder.Save(binaryName); } } }View Code
MSIL實用指南-閉包的生成和調用