Run-Time Weaving

Transparent Proxies, RealProxy, and ContextBoundObject

The objective is to position a proxy between the woven class and the consumer. This approach leverages the same mechanism as in remoting: the client 'sees' the remote object, but it is actually communicating with its proxy. All accesses to the aspected object are routed through the proxy class. The aspect is implemented as a transparent proxy, derived from the System.Runtime.Remoting.Proxies.RealProxy class.

There are three ways to indicate that an object should be accessed via a proxy:

  • If the aspected class is derived from ContextBoundObject, apply the System.Runtime.Remoting.Proxies.ProxyAttribute to this class to specify which proxy class should be used.
  • If the aspected class is derived from MarshalByRefObject, the method RealProxy.GetTransparentProxy() can be called to retrieve the proxy of an existing instance. However, this implies that user code cannot use constructors to get instances of aspected objects, but should instead use factory methods.
  • Alternatively, the aspected class should expose all its semantics on an interface.

These methods have the disadvantage of restricting the aspectable methods to instance methods of classes derived from ContextBoundObject or MarshalByRefObject. Moreover, the only possible join points are method boundaries, as with the previous approach.

Tools:

Load-Time Static Weaving

This approach is very similar to Compile-Time Weaving , but it takes place at runtime, just before the assembly is loaded into memory. This approach is suitable when changing aspects can require the application to be restarted. In its pure application, it does not enable true dynamic weaving. PostSharp fully supports this scenario.

A variant of this approach is to add join points to code using static weaving, but add advices to join points at runtime, i.e., without restarting the application. This can be achieved using PostSharp, but it is not available as an out-of-the-box feature. The disadvantage of this variant is that one must strike a balance between join point density (and thus flexibility) and performance. Too many join points can result in large and slow code.

Tools:

Static Weaving with Dynamic Advices

The code is modified using Compile-Time Weaving techniques (potentially done at load-time, see above) so that 'hooks' are called at certain locations. The actions of the hooks can be changed at runtime.

Tools:

JIT Emission of Classes

This approach only works with public virtual methods and interfaces. There are two methods: the first is to generate a proxy class implementing the semantics of an interface. In this case, only interface methods can be aspected. The second method is to generate a child class inherited from the aspected class; this requires the class to be unsealed and is limited to virtual methods.

The System.Reflection.Emit namespace can be used. Aside from being limited to interface or virtual methods, this approach only supports join points located at method boundaries (on entry, on exit, on exception).

Tools:

Profiler API

The Unmanaged Profiler API of the .NET Framework is primarily designed to instrument the code, typically for memory and processor consumption. The same API can be used to execute virtually any code. Given the fine granularity of the Profiler API, it is possible to define a wide range of join points. However, the downside is that the code must be executed alongside the profiler, which is not intended for use in production. This makes this approach unsuitable for commercial software.

Debugger API & Edit and Continue

The concept here is somewhat similar to the use of the Profiler API, but additionally, the edit-and-continue facility can be used to modify MSIL code at runtime. This has the potential to produce faster code than with the Profiler API. However, it shares the same disadvantages, i.e., it is not particularly suitable for commercial software.

Tools: