运行时代码生成
.NET 平台本身并没有提供允许使用高级面向切面编程的扩展。但它提供了一些机制,使得构建提供类似功能的组件成为可能。本文将介绍如何实现这一点。
本文由 Łukasz Kwieciński 撰写,最初发表于2005年2月的《Software 2.0》杂志。您可以在 SDJ 网站上找到更多文章。
引言
传统的面向对象编程并不能保证避免代码重复和模块化不足的问题。通过应用面向切面软件开发 (AOD) 方法,可以有效地解决创建操作日志、数据验证或事务支持等问题。然而,.NET 平台并未内置切面扩展,但它提供了其他机制,可以构建易于使用的组件来解决上述问题。
让我们来看一下示例 1 的代码。它展示了代理设计模式的应用,即将所有消息委托给它隐藏的对象。这种模式以一种简单优雅的方式控制对象的行为。在示例中,它用于管理 wrappedObject
对象的事务性。代理和底层(隐式)类实现一个公共接口(代理接口),在本例中是 ITransactionalObject
。在任何代理类中,接口方法的实现都简化为检查隐式对象的状态和方法参数,执行特殊操作,然后将控制权传递给隐式对象(可能在其创建后)。编写代理类时,我们很快就会认识到其带来的清晰设计的好处,以及需要实现两次对象接口的损失——一次是在底层类(隐藏在代理之后),然后是在代理类中。仔细查看特定的代理类,我们会发现,它们唯一的任务往往是为调用目标对象的一个方法创建特殊环境(示例在示例 1 中)。
示例 1. 管理隐式对象方法调用事务的代理类的示例
public class TransactionManagerProxy : ITransactionalObject
{
private ITransactionalObject wrappedObject;
void ITransactionalObject.PerformSomeProcessing(string parameter1)
{
Connection.BeginTransaction();
try
{
wrappedObject.PerformSomeProcessing(parameter1);
Connection.CommitTransaction();
}
catch (Exception e)
{
Connection.RollbackTransaction();
throw new Exception(e.Message, e);
}
}
decimal ITransactionalObject.ComputeSomething(decimal
parameter1, decimal parameter2)
{
Connection.BeginTransaction();
try
{
wrappedObject.ComputeSomething(parameter1, parameter2);
Connection.CommitTransaction();
}
catch (Exception e)
{
Connection.RollbackTransaction();
throw new Exception(e.Message, e);
}
}
internal TransactionManagerProxy(ITransactionalObject wrappedObject)
{
this.wrappedObject = wrappedObject;
}
}
示例 1 表明,手动维护一个针对不断演进接口的代理类有时会非常耗时。这种操作会很快让程序员疲惫——每次都应该遵循同样的工作模式。代码冗长、重复且可读性差。尝试使用一种清晰的面向对象语言编写这种模板,同时保持强类型,最终会以失败告终。因此,需要创建一个服务来为我们的接口生成代理类。该服务将读取接口定义并生成实现代理对象服务的相应代码。
审视自身,即反射
在运行时检查类方法的功能由 System.Reflection
命名空间中的类提供。由于这些类,可以处理未知类型的对象。这种技术称为反射。例如,数据绑定在 System.Windows.Forms
中就使用了这种机制。通过反射,可以检查类型元数据(如字段、属性、方法和特性),读写字段、属性以及调用对象方法。此外,反射还可以在编译时创建未知类型的对象。在示例 2 中,我们可以看到一个方法,它输出给定对象的所有公共方法的名称,并调用其所有无参数方法。
示例 2. 在编译时使用反射处理未知类型的对象
public void ProcessObject(object obj)
{
Type objType = obj.GetType();
foreach (MethodInfo method in objType.GetMethods())
{
Console.WriteLine(method.Name);
if (method.GetParameters().Length == 0)
{
method.Invoke(obj, null);
}
}
}
反射的一些有趣特性是对类内部和私有成员的访问,包括在编译时没有此类权限的代码的访问。例如,可以通过调用私有构造函数来创建对象。
反射有一个相当大的缺点——它效率不高。所检查功能的瓶颈示例包括:
- 操作最通用的对象接口 (
System.Object
),即大量的类型转换和类型检查。 - 根据名称搜索类及其成员。
- 以对象表的形式表达方法参数列表。
在许多情况下,可以通过有效地应用适当的设计(例如,对按名称找到的对象进行缓冲)来最大限度地减少性能损失。
示例 1 中的示例代表了大量代理类。通过泛化,我们可以得到一个类模板,其中每个接口方法的实现将按照示例 3 的形式进行。要动态生成代理类,应该使用反射来指定接口中每个方法的至少返回类型和参数。
示例 3. 动态代理类方法的模板
ReturnType Method(Type1 parameter1, Type2 parameter2, ...)
{
controller.OnMethodCalling(wrappedObject);
try
{
ReturnType result = wrappedObject.Method(parameter1, parameter2, ...);
controller.OnMethodCallSuccess(wrappedObject);
return result;
}
catch (Exception exception)
{
controller.OnMethodCallFailure(wrappedObject, exception);
throw exception;
}
}
IL – 高级汇编器
在了解了代理接口的元数据后,我们可以开始生成相应的代理类。在 System.Reflection.Emit
程序集中,收集了用于在程序执行期间创建类型和生成方法代码的类。使用它们就像通过操作文档对象模型 (DOM) 来构建 XML 文档一样。在此模型中,程序集构成根。它们由由成员(字段、属性和方法)组成的类型构成。方法属性之一是代码,我们可以对其进行修改。这段代码与编写程序集的语言无关:C#、VB.NET 或其他符合通用语言规范 (CLS) 的语言。.NET 程序集是中间语言 (IL) 代码的集合,在目标平台上运行之前会被编译成机器语言。如果想在运行时生成代码,就应该使用这种语言。
ILGenerator
类提供了一系列用于生成 IL 指令的方法。IL 语言中所有可访问的指令都收集在 OpCodes
类中。它是一组对接触过汇编器的人来说很熟悉的简单操作。IL 是一种低级语言,但它完全不考虑硬件架构。在最后一刻,它会被编译成运行它的计算机的机器代码,因此它将许多实现细节留给负责此转换的 JIT (just-in-time) 编译器。因此,在 IL 语言中,我们会发现一些汇编器不常见的特性,例如异常处理、复合数据类型和类型控制。用 IL 语言开发程序可能看起来很困难,但初学者可以通过稍后描述的两个实践快速学会使用 IL。
适当的设计,即避免 IL 编码
调用方法控制器(控制器,示例 3)的对象将在创建代理对象时定义,从而实现任何代理的实现。例如,在示例 1 中,它将从控制器在 OnMethodCalling
方法中开启事务,在 OnMethodCallSuccess
方法中批准,并在 OnMethodCallFailure
方法中撤销。请注意,控制器最初将以 C# 语言或 CLS 家族中的任何其他语言编写。但是,它的服务将被动态地编织到代理类中。我们将把生成必需的 IL 代码的机制封装在 DynamicProxies
组件中。使用它的程序员不需要知道该组件在如此低级的层面上运行。他们只需要理解控制器的操作并能够构建相应的实现就足够了。
代理类必须实现传递给工厂方法的类型接口
object DynamicProxies.Create(object wrappedObject, Type proxyInterface,
IMethodExecutionController methodExecutionController);
为了简化示例,我们将假设隐式 wrappedObject
对象在创建其代理时必须存在。DynamicProxies.Create
工厂方法将执行以下功能:
- 它将检查
proxyInterface
类型的元数据,以收集有关其中定义的所有方法的有关信息。 - 它将创建一个实现
proxyInterface
接口的代理类,并以 IL 形式生成。 - 它将根据示例 3 的模板实现
proxyInterface
接口的所有方法。 - 它将创建生成类的对象,并将隐式
wrappedObject
对象和methodExecutionController
控制器传递给它。
IL 代码实现 – 泛化示例
对于不熟悉 IL 语言的程序员来说,如果不对此类类的具体示例进行分析,就很难快速创建动态代理类所需的代码。幸运的是,通过以下方法可以轻松获得示例:用 C# 语言编写代码,编译它,然后将其反汇编为 IL 语言。我们将分析示例 4 中的代码。
示例 4. 根据动态代理类生成器设计编写的代理类示例
public class SampleProxy : ISampleProxyInterface
{
private IMethodExecutionController controller;
private ISampleProxyInterface wrappedObject;
public int SampleMethod(int x, string y)
{
controller.OnMethodCalling(wrappedObject);
try
{
int result = wrappedObject.SampleMethod(x, y);
controller.OnMethodCallSuccess(wrappedObject);
return result;
}
catch (Exception exception)
{
controller.OnMethodCallFailure(wrappedObject, exception);
throw exception;
}
}
public SampleProxy(ISampleProxyInterface wrappedObject,
IMethodExecutionController controller)
{
this.wrappedObject = wrappedObject;
this.controller = controller;
}
}
.NET Framework SDK 提供了适当的反汇编工具 (ildasm.exe)。启动反汇编器后,我们将打开编译了示例 4 中代理类的程序集(参见图 1)。左侧的树状结构对程序集中定义的所有类型进行了分组(两个必需的接口和底层代理类)。在旁边的窗口中,我们可以看到所选方法的代码预览。
图 1. 示例 4 中代理类的反汇编
SampleProxy.SampleMethod
方法的 IL 代码由带有标签(例如 IL_0006
)的指令组成。其中一些指令被分组在 try-catch
块中。IL 语言中的主要处理元素是评估堆栈,所有操作的结果都会放置在此堆栈上。操作的输入数据是当前位于堆栈上的不变值。在调用之前,参数应按从左到右的顺序推入堆栈。但是,它们不是在被调用方法的正文中从堆栈中取出的。对它们的访问是通过 ldarg 系列操作实现的。
查看示例 5 中的方法代码。第一条操作是 ldarg.0
,即加载索引为 0 的方法参数值到堆栈上。对象(实例)的每个方法的零参数是对调用该方法的对象(即 C# 语言中熟悉的隐式 this
参数)的引用。该引用被加载到堆栈上,作为 ldfld
操作的参数。此操作的结果是从堆栈中取出的 this
引用的值,但 controller
字段的值将位于 this
对象中(从堆栈中取出的值)。从 IL_0006
地址开始,我们观察到与 wrappedObject
字段引用完全相同的场景。IL_000c
地址下的 callvirt
指令解释了此处理的目标:此处,堆栈上是调用 OnMethodCalling(object)
控制器方法的参数——第二个参数,因为第一个是目标对象的引用(这是对 this
零参数的显式传递,在本例中为 controller
字段的值)。callvirt
指令调用具有虚拟参数的方法。它对应于示例 4 中 SampleMethod
方法的第一行代码。
示例 5. 示例 4 中 SampleMethod
方法的 IL 代码
IL_0000: ldarg.0
IL_0001: ldfld
class IMethodExecutionController SampleProxy::controller
IL_0006: ldarg.0
IL_0007: ldfld
class ISampleProxyInterface SampleProxy::wrappedObject
IL_000c: callvirt
instance void IMethodExecutionController::OnMethodCalling(
object)
.try
{
IL_0011: ldarg.0
IL_0012: ldfld
class ISampleProxyInterface SampleProxy::wrappedObject
IL_0017: ldarg.1
IL_0018: ldarg.2
IL_0019: callvirt
instance int32 ISampleProxyInterface::SampleMethod(
int32, string)
IL_001e: stloc.0
IL_001f: ldarg.0
IL_0020: ldfld
class IMethodExecutionController SampleProxy::controller
IL_0025: ldarg.0
IL_0026: ldfld
class ISampleProxyInterface SampleProxy::wrappedObject
IL_002b: callvirt
instance void IMethodExecutionController::OnMethodCallSuccess(
object)
IL_0030: ldloc.0
IL_0031: stloc.2
IL_0032: leave.s IL_0049
}
catch Exception
{
IL_0034: stloc.1
IL_0035: ldarg.0
IL_0036: ldfld
class IMethodExecutionController SampleProxy::controller
IL_003b: ldarg.0
IL_003c: ldfld
class ISampleProxyInterface SampleProxy::wrappedObject
IL_0041: ldloc.1
IL_0042: callvirt
instance void IMethodExecutionController::OnMethodCallFailure(
object, class Exception)
IL_0047: ldloc.1
IL_0048: throw
}
IL_0049: ldloc.2
IL_004a: ret
比较示例 4 和 5,可以轻松猜测各个操作的含义。方法的返回值在调用 ret
指令(方法返回)之前被推入堆栈。stloc.0
(IL_001e
) 指令从堆栈中取出隐式对象(从 wrappedObject
字段获取的引用)的 SampleMethod
方法返回的值,并将其加载到索引为零的本地变量中(每个本地变量都有其索引)。编译器生成的关于局部变量的操作比 C# 中的等效操作要复杂一些。虽然我们知道索引为零的变量(由 IL_001e
设置)是示例 4 中的 result
变量,但很难识别索引为 2 的变量(由 IL_0031
设置)。如果知道 IL 对 ret
指令施加的限制,情况就会变得清晰:它不能(像 C# 中的 return
一样)在 try
块中。离开 try
块只能通过特殊的 leave
(IL_0032
) 跳转语句来实现。因此,result
变量(索引 0)的值将通过以下操作传递给临时变量(索引 2):
//loading variable with the index 0 onto the stack
IL_0030: ldloc.0
IL_0031: stloc.2 //assigning value from the stack
// to the variable with the index 2
// and pulling it from the stack.
分配给索引 2 的 result
变量值在离开(IL_0049
)方法之前被加载到堆栈上,作为该方法返回的值。throw
操作引发堆栈上的异常(IL_0048
)。局部 exception
(索引 1)变量被放置在 IL_0034
行(在 catch
块的入口处,堆栈上捕获了一个异常)。
我们在这里讨论了一个相对复杂的 IL 代码示例(最复杂的是异常处理)。这也是一个非常典型的示例。可以看出,只需要少数指令,应用几个简单的规则,我们就能在 IL 语言中重现 C# 代码片段。
生成和运行 IL 代码
在应用示例的归纳法并生成方法体中的 IL 指令之前,我们必须创建描述代理类及其方法的元数据。.NET Framework 支持在仅在执行时创建的程序集中定义运行时类型(称为动态程序集)。程序集的生成内容必须在一个应用程序域内发生。无法向之前编译的程序集添加新的类或方法。此限制也适用于保存到磁盘的动态程序集。动态程序集的创建是通过 AssemblyBuilder
类实现的,我们通过 AppDomain.DefineDynamicAssembly
方法获取对象。我们将类型放置在动态程序集的特定模块中,该模块通过 AssemblyBuilder.DefineDynamicModule
方法创建。只有此方法返回的 ModuleBuilder
类允许通过 ModuleBuilder.DefineType
方法定义新类型。
TypeBuilder proxyBuilder = moduleBuilder.DefineType(proxyName,
attributes, typeof(object), new Type[] {proxyInterface});
这样,我们就定义了一个具有所需名称和属性(public
、class
等)的类,该类继承自 System.Object
类型并实现 proxyInterface
类型接口。通过获得的 TypeBuilder
对象,我们将添加代理类的字段和方法。根据之前约定的条件,我们将把生成 IL 代码的所有方法放在 DynamicProxies
服务类中。我们将从定义控制器和隐式对象的字段以及代理对象的构造函数开始(示例 6,与示例 4 对比)。
示例 6. 生成动态代理类的字段和构造函数
private void DefineFields(
TypeBuilder proxyBuilder, Type proxyInterface,
out FieldBuilder wrappedObjectField,
out FieldBuilder controllerField)
{
wrappedObjectField = proxyBuilder.DefineField(
"wrappedObject", //Name of field
proxyInterface, //Type of field
FieldAttributes.Private); //Attributes of field
controllerField = proxyBuilder.DefineField("controller",
typeof(IMethodExecutionController), FieldAttributes.Private);
}
private void DefineConstructor(
TypeBuilder proxyBuilder, Type proxyInterface,
FieldBuilder wrappedObjectField, FieldBuilder controllerField)
{
ILGenerator code = proxyBuilder.DefineConstructor(
MethodAttributes.Public, CallingConventions.Standard,
new Type[] //Parameters of constructor:
{
proxyInterface, //wrappedObject
typeof(IMethodExecutionController) //controller
})
.GetILGenerator();
//call the non-parameter constructor System.Object (base())
code.Emit(OpCodes.Ldarg_0);
code.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));
//this.wrappedObject = wrappedObject;
code.Emit(OpCodes.Ldarg_0);
code.Emit(OpCodes.Ldarg_1);
code.Emit(OpCodes.Stfld, wrappedObjectField);
//this.controller = controller;
code.Emit(OpCodes.Ldarg_0);
code.Emit(OpCodes.Ldarg_2);
code.Emit(OpCodes.Stfld, controllerField);
code.Emit(OpCodes.Ret);
}
让我们关注对超类构造函数的显式调用(DefineConstructor
,示例 6)。省略此代码是可能的;不过,相对于父类 System.Object
,这并不是一个最佳选择。如果我们创建的代理对象是我们类的子类,那么省略对基类构造函数的调用将导致继承字段(包括在字段声明中初始化的字段)的值为 null!C# 编译器不允许这种情况,但 IL 编译器对此并不那么严格。
现在该生成代理类的核心了——受控委托到隐式对象的方法。我们从分析代理类的接口开始,并生成方法的元数据(它们的签名),以示例 7 为例。Type.GetMethods()
返回的方法包括给定类型中定义的属性访问方法(get
、set
)。但是,仅实现访问方法是不够的。必须在代理类中复制所有接口属性的定义。这与重载超类中的访问方法类似——适当属性的定义必须在定义它们的类的所有子类中复制。这意味着描述属性的元数据不会像 C# 语言所暗示的那样作为语法和语义继承。观察属性定义在子类中的重复的最简单方法是反汇编相应的示例。
示例 7. 生成受代理类控制的方法的签名
private void DefineControlledDelegations(
TypeBuilder proxyBuilder, Type proxyInterface)
{
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public;
foreach (MethodInfo method in proxyInterface.GetMethods(flags))
{
DefineDelegation(
proxyBuilder, method,
wrappedObjectField, controllerField);
}
//Define properties – access methods are already implemented above
foreach (PropertyInfo property in proxyInterface.GetProperties(flags))
{
DefineProperty(proxyBuilder, property);
}
}
参照示例 5 中的示例,我们通过以 IL 语言生成 DefineDelegation
方法(示例 8)。它的任务是在代理类中定义一个签名与代理接口的相应方法签名相同的类。有必要通过 TypeBuilder.DefineMethodOverride
调用将该方法与它实现的方法绑定(因为这种映射,接口方法的隐式实现才能在 .NET 中工作)。让我们关注返回值。只有当接口方法返回某个值时,相应的值才会出现在堆栈上。否则,我们不声明用于存储它的 result
局部变量,也不在离开方法之前将其放在堆栈上。
EmitDelegatedCallToWrappedMethod
方法生成负责将调用参数值传递给隐式对象的方法的代码。参数数量是任意的,因为使用了迭代和 OpCodes.Ldarg_S
指令将实际参数加载到堆栈上,索引作为参数传递。我们在 EmitControllerMethodCall
方法中采用了类似的方法,该方法能够将任意数量的局部变量的值传递给控制器方法(在本例中,只有 OnMethodCallFailure
控制器方法接受异常对象形式的附加参数)。catch
块末尾的 rethrow
指令很有趣。此指令会继续抛出在此块中捕获的异常(调用堆栈和其他异常数据都会被保存)。它在 C# 语言中没有等效项。跟踪堆栈当前状态的必要性是在创建 IL 代码时唯一的困难。尝试从当前方法调用之前推送到堆栈上的值会始终导致 InvalidProgramException
异常。生成动态代理对象的服务几乎可以使用了。但是,有必要通过 TypeBuilder.CreateType()
方法调用来确认类型构建的完成,并通过 System.Activator
服务创建其实例。
object proxy = Activator.CreateInstance(proxyClass,
new object[] { wrappedObject, methodExecutionController });
示例 8. 生成受控代理类委托正文的方法
private void DefineDelegation(TypeBuilder proxyBuilder,
MethodInfo method, FieldBuilder wrappedObjectField,
FieldBuilder controllerField)
{
ParameterInfo[] paramInfos = method.GetParameters();
Type[] paramTypes = new Type[paramInfos.Length];
int i = 0;
foreach (ParameterInfo info in paramInfos)
{
paramTypes[i] = info.ParameterType;
i++;
}
MethodAttributes attributes =
MethodAttributes.Public |
MethodAttributes.HideBySig |
MethodAttributes.Virtual;
MethodBuilder implBuilder = proxyBuilder.DefineMethod(
method.Name, attributes,
method.ReturnType, paramTypes);
/*The built method is an
implementation of the method interface:
proxyBuilder.DefineMethodOverride(implBuilder, method); */
ILGenerator code = implBuilder.GetILGenerator();
//We generate the method body:
//Declare local variables:
LocalBuilder result = (wrappedMethod.ReturnType != typeof(void))
? code.DeclareLocal(wrappedMethod.ReturnType)
: null;
LocalBuilder exception = code.DeclareLocal(typeof(Exception));
code.BeginExceptionBlock();
//OnMethodCalling:
EmitControllerMethodCall(code, "OnMethodCalling",
wrappedObjectField, controllerField, new LocalBuilder[0]);
EmitDelegatedCallToWrappedMethod(code, wrappedMethod,
paramTypes, wrappedObjectField);
if (wrappedMethod.ReturnType != typeof(void))
{
//The appropriate return value is on the stack.
code.Emit(OpCodes.Stloc_S, result);
}
//OnMethodSuccess:
EmitControllerMethodCall(code, "OnMethodCallSuccess",
wrappedObjectField, controllerField, new LocalBuilder[0]);
code.BeginCatchBlock(typeof(Exception));
code.Emit(OpCodes.Stloc_S, exception);
//OnMethodCallFailure:
EmitControllerMethodCall(code, "OnMethodCallFailure",
wrappedObjectField, controllerField,
new LocalBuilder[] {exception});
code.Emit(OpCodes.Rethrow);
code.EndExceptionBlock();
//The method exit:
if (wrappedMethod.ReturnType != typeof(void))
{
code.Emit(OpCodes.Ldloc_S, result);
}
code.Emit(OpCodes.Ret);
}
private void EmitControllerMethodCall(ILGenerator code,
string controllerMethodName,
FieldBuilder wrappedObjectField,
FieldBuilder controllerField,
LocalBuilder[] additionalParameters)
{
//Push the reference to the controller
//object onto the stack:
code.Emit(OpCodes.Ldarg_0);
code.Emit(OpCodes.Ldfld, controllerField);
//Typical parameters:
//The reference to the implicit object:
code.Emit(OpCodes.Ldarg_0);
code.Emit(OpCodes.Ldfld, wrappedObjectField);
//The optional additional parameters of the controller:
foreach (LocalBuilder local in additionalParameters)
{
code.Emit(OpCodes.Ldloc_S, local);
}
//Call the controller method:
code.Emit(OpCodes.Callvirt,
typeof(IMethodExecutionController).GetMethod(controllerMethodName));
}
private void EmitDelegatedCallToWrappedMethod(ILGenerator code,
MethodInfo wrappedMethod, Type[] paramTypes,
FieldBuilder wrappedObjectField)
{
//Push the reference to the implicit object onto the stack
code.Emit(OpCodes.Ldarg_0);
code.Emit(OpCodes.Ldfld, wrappedObjectField);
//Push all parameters of call onto the stack
for (short j = 0; j < paramTypes.Length; j++)
{
code.Emit(OpCodes.Ldarg_S, j + 1);
}
//Call the method of the implicit object
code.Emit(OpCodes.Callvirt, wrappedMethod);
}
摘要
在运行时动态生成代码并非易事。幸运的是,这种方法没有其他缺点,而且好处可能是相当可观的。动态生成的类会立即编译成本地代码,并且此类代码的性能与用 C# 语言等编译的代码的性能一样好。动态代理类的示例显示了如何有效地摆脱令人沮丧的代码重复和必须维护代理类的痛苦。它展示了如何使用 IL 代码(通过用 C# 编写的控制器类)生成的组件可以非常通用。前景更加光明。使用反射可以检查 .NET 特性,通过这些特性我们标记代码片段。IL 生成器能够读取这些特性并将其传递给控制器,控制器反过来可以根据特性的状态采取各种行动。这意味着,通过对某个方法应用一些特性,我们声明了其执行将如何改变。我们通过声明性元素改进代码,而这些元素的操作不是我们自己定义的。这正是面向切面编程 (AOP) 的特点。尽管标准 .NET 平台没有切面扩展,但使用 IL 生成器和反射使得构建我们一定程度上定义的切面成为可能。这为创建业务应用程序带来了新的品质。
参考文献
Reflection(反射)
- Atif Aziz, 使用 .NET 元数据和反射动态将数据层绑定到存储过程和 SQL 命令, MSDN Magazine。
- System.Reflection 命名空间 - MSDN
IL 语言和 IL 代码生成
- Serge Lidin, Inside Microsoft .NET IL Assembler, Microsoft Press
- System.Reflection.Emit 命名空间 - MSDN
- MSIL 反汇编器 (Ildasm.exe) - MSDN
设计模式
- Gamma E., Design Patterns. Elements of re-useable software, Addison-Wesley。
- 面向切面编程.
- Michael A. Blackstock, Aspect Weaving with C# and .NET。