Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Code generation at runtime

0.00/5 (No votes)
28 Aug 2013 1  
The .NET platform contains no extensions which would allow for the use of advanced aspect oriented programming. But it does contain mechanisms which makes it possible to build components which provide similar functionality. We will find out how to achieve this in this article.

This article is written by Łukasz Kwieciński and was originally published in the February 2005 issue of the Software 2.0 magazine. You can find more articles at the SDJ website.

Introduction

Classical object-oriented programming does not guarantee the avoidance of problems related to code duplication and insufficient modularization. The issues of creating operation logs, data validation, or transaction support, could be solved effectively by applying an Aspect-Oriented Software Development (AOD) approach. However, no aspect extensions were built-in the .NET platform that yet provides other mechanisms, which enable construction of easy-to-use components solving the described problems.

Let's take a look at an example from Listing 1. It presents an application of a proxy design pattern delegating all messages to an object hidden behind it. This pattern, in a simple and elegant way, makes it possible to control object behavior. In the example, it is used to manage transactionality of the methods of the wrappedObject object. The proxy and the underlying (implicit) classes implement a common interface (a proxy interface) - in this case, the ITransactionalObject. In any proxy class, an implementation of interface methods reduces to examining of an implicit object state and method parameters, executing special operations, and then passing control to an implicit object (potentially after its creation). Writing proxy classes, we quickly become convinced about profits in the form of a clear design and losses expressed as the necessity of implementing an object interface twice - once in an underlying class (hidden behind proxy), and then in a proxy class. Taking a closer look at particular proxy classes, we will affirm that, often their only task is the creation of a special environment for the call of one method of a target object (example in Listing 1).

Listing 1. Example of a proxy class managing the transaction of the call of an implicit object method

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;
   }
}

The example in Listing 1 shows how manual maintenance of a proxy class for an evolving interface sometimes becomes time-consuming. This action will tire a programmer quickly – every time, one should apply the same pattern of work. The code is extensive, duplicated, and not very readable. The attempt of coding that template in a clear object-oriented language with a preservation of strong typing will end in a fiasco. There is a need for creating a service which will produce a proxy class for our interface. The service would have to read a definition of an interface and generate appropriate code implementing the services of a proxy object.

Look in yourself, that is reflection

The revision of class methods at run time offers classes from the System.Reflection namespace. Because of these, the processing of objects of an unknown type is possible. This technique is called reflection. For example, this mechanism was used in data binding in System.Windows.Forms. Because of reflection, examining metadata of types such as fields, properties, methods and attributes, reading and setting fields, properties and calling object methods is possible. Also, reflection enables the creation of objects of unknown types at compilation time. In Listing 2, we can see the method which outputs the names of all public methods of the provided object, and calls all its non-parameter methods.

Listing 2. Using reflection to process an object of an unknown type at compilation time

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);
      }
   }
}

Some interesting features of reflection are the access to internal and private class members, including access from code, which have not got such permissions at compilation time. For example, it is possible to create an object by calling a private constructor.

Reflection has a fairly essential disadvantage – it is not effective. The examples of the bottlenecks of the examined features are as follows:

  • operating on the most general interface of objects (System.Object), i.e. a large number of costly type casting and type checking.
  • searching for classes and their members according to names.
  • expressing a list of method parameters in the form of object tables.

In many cases, a performance loss can be minimalised, effectively applying an appropriate design (e.g., buffering objects found according to a name).

The example from Listing 1 represents a large group of proxy classes. By generalizing this, we get a template of classes in which an implementation of every method of an interface will take the form as per Listing 3. To generate proxy classes dynamically, one should use reflection in order to specify at least a return value type and parameters of every method of an interface.

Listing 3. The template of a method of a dynamic proxy class

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 – high-level assembler

Having learnt from reflection about metadata of a proxy interface, we can start the generation of an appropriate proxy class. In the System.Reflection.Emit assembly, there were collected classes designed for creating types and generating code for methods during program execution. Using them looks like building an XML document by the manipulation of its Document Object Model (DOM). In this model, the assemblies form a root. They consist of types composed of members (fields, properties, and methods). One of the method properties is the code, which we can modify. This code is independent of the language in which the assembly was written: C#, VB.NET, or others in accordance with the Common Language Specification (CLS). The .NET assemblies are collections of Intermediate Language (IL) code compiled to a machine language before running on a target platform. Wanting to generate code at run time, one should just use this language.

The ILGenerator class provides a number of methods designed for generating IL instructions. All accessible instructions in IL language are collected in the OpCodes class. It is a collection of simple operations well known to people who have had contact with an assembler. IL is a low-level language, but it disregards hardware architecture entirely. At the last moment, it is compiled into the machine code of the computer on which it was started, therefore it leaves many details of the implementation to a JIT (just-in-time) compiler responsible for this transformation. Hence, in the IL language, we find features unusual for an assembler, e.g., exception handling, composed data types, and control of types. Developing a program in IL language can seem difficult, but a programmer inexperienced in this field will learn quickly using IL on the basis of two practices described later.

Appropriate design, that is, avoidance of IL coding

An object of a call method controller (controller, Listing 3) will be defined at a proxy object's creation, enabling an implementation of any proxy. For example, in Listing 1, it would start with the controller opening the transaction in the OnMethodCalling method, approving it in the OnMethodCallSuccess method, and withdrawing it in the OnMethodCallFailure method. Note that the controller will start out in C# language or in any other language from the CLS family. However, its services will be weaved dynamically into a proxy class. The mechanism of the generator of the indispensable IL code, we will close in the DynamicProxies component. The programmer who uses it does not need to know that the component operates in such a low-level layer. It will suffice that they understand the operation of the controller and can construct the appropriate implementation.

The proxy class has to implement the interface with the type passed to the factory method:

object DynamicProxies.Create(object wrappedObject, Type proxyInterface, 
IMethodExecutionController methodExecutionController);

To simplify the presented example, we will assume that the implicit wrappedObject object has to exist at the moment of creating its proxy. The DynamicProxies.Create factory method will execute the following functions:

  • it will examine the proxyInterface type metadata in order to collect information about all methods defined in it.
  • it will create a proxy class implementing the proxyInterface interface, generating it in IL.
  • it will implement all methods of the proxyInterface interface in accordance with the template from Listing 3.
  • it will create the object of the generated class, passing to it the implicit wrappedObject object and the methodExecutionController controller.

Implementation of IL code – generalizing examples

An inexperienced programmer in the IL language has little chance to quickly create the code required for a dynamic proxy class without the analysis of a specific example of such a class. Fortunately, it is easy to get an example by doing the following: coding in the C# language, compiling it, and then disassembling it into the IL language. We will analyze the code from Listing 4.

Listing 4. Example of a proxy class in accordance with a design of a generator of dynamic proxy classes

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;
   }
}

The .NET Framework SDK provides the appropriate tool for disassembly (ildasm.exe). Having started the disassembler, we open the assembly in which we compiled the example proxy class from Listing 4 (see Figure 1). The tree on the left-hand side groups all types defined in the assembly (two indispensable interfaces and the underlying proxy class). In the near window, we can see the preview of the chosen method's code.

Figure 1. Disassembly of the proxy class from Listing 4

The IL code of the SampleProxy.SampleMethod method consists of instructions described by labels (e.g. IL_0006). Some of the instructions are grouped in try-catch blocks. The main processing element in the IL language is the evaluation stack, on which results of all operations are being placed. The input data for operations are invariable values being, at present, on the stack. Before being called, the parameters should be pushed onto the stack in order from left to right. However, they are not taken from the stack in the body of the called method. Access to them is realized by operations from the ldarg family.

Look at the method's code from Listing 5. The first operation is ldarg.0, i.e. loading onto the stack the method argument value with the index 0. The zero argument of every method of the object (instance) is a reference to the object for which the method has been called, that is the implicit this parameter well-known from the C# language. The reference is loaded onto the stack to become the argument of the ldfld operation. As a result of this operation, the value of the this reference is taken from the stack, however, the controller field value will be located in the this object (the value taken from the stack). From the IL_0006 address, we observe exactly the same scenario with the reference to the wrappedObject field. The callvirt instruction under the IL_000c address explains the goal of this processing: here, on the stack, there are arguments of the OnMethodCalling(object) controller method call – the second one, because first there is the reference to the object which is the target of the call (it is an explicit passing of the this zero argument, in this case, the value of the controller field). The callvirt instruction calls the method having a virtual parameter. It corresponds to the first line of the code in the SampleMethod method from Listing 4.

Listing 5. The IL code of the SampleMethod method from Listing 4

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

Comparing Listings 4 and 5, one can easily guess the meanings of individual operations. The value returned by the method is pushed onto the stack before the ret instruction call (return from the method). The stloc.0 (IL_001e) instruction pulls from the stack the value returned by the SampleMethod method of the implicit object (the reference taken from the wrappedObject field) and loads it into the local variable with the index zero (every local variable has its index). Operations generated by a compiler on local variables are somewhat more complex than their equivalents in C#. While it is known that the variable with index zero (set by IL_001e) is the result variable from Listing 4, it is difficult to identify the variable with the index 2 (set by IL_0031). The situation will become clear if we know a restriction which IL imposes on the ret instruction: it can not be (like return from C#) in the try block. Leaving the try block can be realised only by using the special leave (IL_0032) jump statement. Hence, the value of the result variable (index 0) will be passed to the temporary variable (index 2) because of the following operations:

//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.

The result variable value assigned under the index 2 is loaded onto the stack just before leaving the (IL_0049) method, to become the value being returned by this method. The throw operation results in throwing an exception on the stack (IL_0048). The local exception (index 1) variable is being placed in the IL_0034 line (at the entry of the catch block, there is an exception caught on the stack).

We have discussed here one of the more complex examples of IL code (the most complicated is just exception handling). Also, it is a very representative example. It can be noted that few instructions are needed, several simple rules are applied, and we are able to reproduce C# code fragments in the IL language.

Generating and running IL code

Before applying an induction from an example and generating IL instructions in bodies of methods, we have to create metadata describing the proxy class and its methods. The .NET Framework enables defining types at run-time in assemblies created only at the moment of execution (they are called dynamic assemblies). Generating the content of the assembly has to occur within one application domain. One can not add new classes or methods to the earlier compiled assembly. This limitation also applies to dynamic assemblies saved on disk. Creating a dynamic assembly is realized using the AssemblyBuilder class, from which we obtain objects by means of the AppDomain.DefineDynamicAssembly method. We place types in the specific module of the dynamic assembly, which we create by means of the AssemblyBuilder.DefineDynamicModule method. Only the ModuleBuilder class returned by this method allows the definition of a new type by means of the ModuleBuilder.DefineType method:

TypeBuilder proxyBuilder = moduleBuilder.DefineType(proxyName,
attributes, typeof(object), new Type[] {proxyInterface});

In this way, we define a class with a required name and attributes (public, class, ...), inherited from the System.Object type and implementing the proxyInterface type interface. By means of the obtained TypeBuilder object, we will be adding fields and methods of a proxy class. According to conditions agreed earlier, we will place all methods generating the IL code in the DynamicProxies service-class. We will begin by defining fields of a controller and an implicit object, as well as a constructor of the proxy object (Listing 6, compare with Listing 4).

Listing 6. Generating fields and constructor of a dynamic proxy class

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);
}

Let's turn our attention to the explicit call of the constructor of the superclass (DefineConstructor, Listing 6). The omission of this code is possible; though in relation to the parent, which is System.Object, it is not the best move. If we were creating a proxy object as a subclass of our class, then omission of a call of a base class constructor would result in null values of inherited fields, including fields initialized in a field declaration! The C# compiler does not allow such a possibility, but the IL compiler is not so restrictive.

It's time to generate the heart of a proxy class – controlled delegations to methods of an implicit object. We begin with an analysis of an interface of a proxy class and generation of the metadata of the methods (their signatures), taking Listing 7 as an example. The methods returned by the Type.GetMethods() include property access methods (get, set) defined in a given type. However, only implementing access methods is not enough. It is necessary to duplicate definitions of all interface properties in a proxy class. It is similar when overloading methods of access from a superclass – definitions of appropriate properties have to be duplicated in all subclasses of a class, which defines them. This means that metadata describing properties are not inherited as syntax and semantics, as the C# language would suggest. The easiest way to observe repetitions of a property definition in subclasses is to disassemble an appropriate example.

Listing 7. Generation of signatures of methods controlled by a proxy class

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);
   }
}

Referring to the example from Listing 5, we built the DefineDelegation method by generating it in the IL language (Listing 8). Its task is to define (in a proxy class) a method with a signature which is identical to a signature of an appropriate method of a proxy interface. It is necessary to bind the method to the method implemented by it from the interface, by means of the TypeBuilder.DefineMethodOverride call (because of such mapping, implicit implementations of interface methods operate in .NET). Let's turn our attention to the return value. The appropriate value will be on the stack only if the method of the interface returns some value. Otherwise, we do not declare the result local variable designed for its storage, and do not place it on the stack before leaving the method.

The EmitDelegatedCallToWrappedMethod method generates the code responsible for passing values of parameters of the call to the method of the implicit object. The number of parameters is arbitrary, because of the use of the iteration and the OpCodes.Ldarg_S instruction loading on the stack an actual argument with an index passed as a parameter. We applied a similar approach in the EmitControllerMethodCall method capable of passing a value of any number of local variables to the controller method (in our case only the OnMethodCallFailure controller method accepts the additional parameter in the form of the exception object). The rethrow instruction situated at the end of the catch block is interesting. This instruction continues throwing the exception caught in this block (the stack of calls and other exception data are saved). It has no equivalent in the C# language. The necessity of keeping track of the current state of the stack is the only difficulty when creating IL code. The attempt to pull a value from the stack, which was pushed onto the stack before a call from the current method, always results in throwing the InvalidProgramException exception. The service generating dynamic proxy objects is almost ready for use. Yet, it is necessary to confirm the type building completion by the TypeBuilder.CreateType() method call and create its instance by means of the System.Activator service:

object proxy = Activator.CreateInstance(proxyClass, 
   new object[] { wrappedObject, methodExecutionController });

Listing 8. Methods generating the body of the controlled proxy class delegation

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);
}

Summary

The dynamic generation of code at runtime is not the easiest thing to implement. Fortunately, there are no other disadvantages with this method, and the benefits can be considerable. The classes generated dynamically are immediately compiled into native code, and the performance of such code is as good as the performance of the code compiled, for example, in C# language. The example of dynamic proxy classes shows how effectively one can get rid of frustrating code duplication and a sad obligation of keeping proxy classes. It shows how a component generated by using IL code (by means of a controller class written in C#) can be universal. Perspectives are even more encouraging. The use of reflection allows examining .NET attributes, by which we mark code fragments. The IL generator is able to read and pass these attributes to the controller, which in turn can take various actions depending upon the state of attributes. This means that by applying some attributes to a method, we declare how its execution will be changed. We improve our code by means of declarative elements, the operation which we do not define by ourselves. This is characteristic for aspect-oriented programming (AOP). Although the standard .NET platform doesn't have aspect extensions, the use of the IL generator and reflection makes it possible to build them in, defined by us to some extent. This constitutes a new quality in creating business applications.

Bibliography

Reflection:

IL language and IL code generation:

Design patterns:

  • Gamma E., Design Patterns. Elements of re-useable software, Addison-Wesley.
  • Aspect-oriented programming.
  • Michael A. Blackstock, Aspect Weaving with C# and .NET.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here