[pMixins]

A lightweight open source Mixin and AOP framework for C#


In the real world, inheritance tends to be an antipattern

Kas Thomas Big Think

How [pMixins] Works

[pMixins] is a Visual Studio plug-in that scans a solution for partial classes decorated with pMixin attributes. By marking your class partial, [pMixins] can create a code-behind file and add additional members to your class. This is the same mechanism asp.net uses to create data members for server-side controls. In fact, this is how [pMixins] gets its name: partial mixins!

Design-Time Weaving

The process of combining the code from several classes together in AOP is called aspect weaving. This most commonly occurs at either compile-time, by invoking a custom compiler (this is the method PostSharp uses) or at run-time by emitting specialized proxy objects (this is how Castle Dynamic Proxy works).

Since [pMixins] uses code-behind files which are generated as soon as the save button is clicked in Visual Studio, I have come to call this Design-Time Weaving. There are several benefits with this approach because with Design Time Weaving, the aspect weaving code is generated immediately. This means that Visual Studio will give you Intellisense support. Code analysis tools like ReSharper fully understand the code. And the Compiler gives you full compile-time error checking i.e. every line of code that could be executes exists before compilation.

Design Time Weaving also works great for teams, for example: If your colleagues don't want to use [pMixins], that's not a problem. Because the code-behind files are already generated and checked into your version control system of choice so they don't need [pMixins] installed to build or run the solution. And this is the same for your build server, test server, etc.

Is there a down side? Like any engineering decision there are always trade offs. [pMixins] does have a small NuGet dependency, but there is an additional Visual Studio plugin that will auto-add it to your projects: pMixins - Item Template. Also, as [pMixins] doesn't change the original class file, [pMixins] syntax can be more verbose when compared to other frameworks, but in practice, this is rarely an issue.

Code Behind Walk Through

So let's take a look at the code-behind file generated by [pMixins]. Down below is a simplified version of the code behind generated for the Introduction class from the Introduction page. This is a lot to digest in one piece, but we'll walk through it step by step futher below.

public partial class Introduction
{
   /// <summary>
   /// Container for all Generated Mixin Wrapper Class Definitions
   /// </summary>
   private sealed class __pMixinAutoGenerated
   {
      /// <summary>
      /// Container for Hello World Specific Wrappers
      /// </summary>
      public sealed class HelloWorldMixin
      {
         /// <summary>
         /// Promote Protected Members to Public.  pMixins will enforce
         /// access restrictions.
         /// </summary>
         public abstract class HelloWorldProtectedMembersWrapper : HelloWorld
         {
         }
         /// <summary>
         /// Satisfies any abstract member requirements by proxying to 
         /// Target.
         /// </summary>
         public class HelloWorldAbstractWrapper : HelloWorldProtectedMembersWrapper
         {
            private readonly Introduction _target;
            public HelloWorldAbstractWrapper(Introduction target)
            {
               _target = target;
            }
         }
         public sealed class HelloWorldMasterWrapper : CopaceticSoftware.pMixins.Infrastructure.MasterWrapperBase
         {
            public readonly HelloWorld _mixinInstance;
            public HelloWorldMasterWrapper(Introduction _target)
            {
               _mixinInstance = base.TryActivateMixin<HelloWorldAbstractWrapper>(_target);
               base.Initialize(_target, _mixinInstance, new List<CopaceticSoftware.pMixins.Interceptors.IMixinInterceptor>());
            }
            internal string SayHello()
            {
               var methodName = "SayHello";
               var parameters = new List<CopaceticSoftware.pMixins.Interceptors.Parameter>();
               Func<string> invocationDelegate = () => _mixinInstance.SayHello();
               //Wire into AOP Infrastructure
               return base.ExecuteMethod(methodName, parameters, invocationDelegate);
            }
         }
      }
   }
   /// <summary>
   /// Container for Mixin instances
   /// </summary>
   private sealed class __Mixins
   {
      //Instance of Hello World 
      public readonly __pMixinAutoGenerated.HelloWorldMixin.HelloWorldMasterWrapper _HelloWorld;
      public __Mixins(Introduction host)
      {
         var activator = CopaceticSoftware.pMixins.Infrastructure.MixinActivatorFactory.GetCurrentActivator();
         _HelloWorld = activator.CreateInstance<__pMixinAutoGenerated.HelloWorldMixin.HelloWorldMasterWrapper>(host);
      }
   }
   private __Mixins ___mixinsBacking;
   private __Mixins __mixins {
      get {
         return ___mixinsBacking ??(___mixinsBacking = new __Mixins(this));
      }
   }
   /// <summary>
   /// Mixed in from HelloWorld.SayHello()
   /// </summary>
   public string SayHello()
   {
      return __mixins._HelloWorld.SayHello();
   }
   public static implicit operator HelloWorld(Introduction target) {
      return target.__mixins._HelloWorld._mixinInstance;
   }
}
Mixin Wrapper Class Definitions

First up is the __pMixinAutoGenerated class. This contains child class definitions for each Mixin. In this case, we only have one, HelloWorldMixin, which in turn contains several wrapper classes.

[pMixins] uses three levels of wrapper classes in order to ensure all Mixin members are correctly added to the Target and are weaved into the AOP infrastructure:

 

Protected Wrapper

This wrapper inherits from the Mixin promotes all protected members to public so that the Master Wrapper can call them. [pMixins] enforces member access, so no other class can accidentally invoke these members.

If the Mixin is sealed, this wrapper is not generated.

Abstract Wrapper

This wrapper inherits from the Protected Member Wrapper and is responsible for satisfying all abstract requirements imposed by the Mixin.

If the Mixin is abstract, a Requirements interface is generated that matches the signature of the abstract members. The Target is forced to implement this interface which allows the Abstract Wrapper to implement the abstract members by proxying them to the Target.

If the Mixin is sealed, this wrapper is not generated.

Master Wrapper

This wrapper is responsible for initializing the previous wrappers, which effectively generates an instance of the Mixin (_mixinInstance) and for proxying member calls through the [pMixins] AOP infrastructure.

Both goals are accomplished by inheriting from CopaceticSoftware.pMixins.Infrastructure.MasterWrapperBase which provides methods to proxy Mixin initialization (Initialize) and member invocation (ExecuteMethod, ExecutePropertyGet, etc) through the AOP infrastructure to any registered Interceptors (Aspects).

Of special interest is the line base.TryActivateMixin<helloworldabstractwrapper>(_target) which transfers responsibility for creating an instance of the Mixin to the MixinActivatorFactory. This is an extensibility point where a custom (ie Dependency Injector) Activator could be plugged in.

The __Mixins Class

Next is the __Mixins class, which is responsible for creating and housing instances of Master Wrappers. It again requests an Activator from MixinActivatorFactory.GetCurrentActivator() and uses that to create instances of the Master Wrappers.

A private Property (and backing field) are also created to house an instance of a __Mixin object. The field is lazily initialized so Mixin objects are not created until they are needed. Note: The thread synchronization code [pMixins] uses has been removed from this example for the sake of readability.

 

Member Proxying

After that we get to the SayHello method declared at the Introduction scope. This proxies call through the __mixins Property to the instance of the HelloWorldMasterWrapper.

 

Conversion Operators

Finally we end with a conversion operator which allows an instance of Introduction to be passed as a HelloWorld.
Note:This unfortunately doesn't allow Introduction to pass an is or as check. Instead, [pMixins] provides extension methods to replace the default functionality via the AsIsHelper.
See this Stackoverflow Question for more details / discussion on this.