MEFでAOP

過去に似たようなことをやった気がしますが、今回はMefContribを使った一応、正式っぽい方法で。

using System;
using System.Linq;
using System.Reflection;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Primitives;
using System.ComponentModel.Composition.Hosting;
using MefContrib;
using MefContrib.Hosting.Interception;
using MefContrib.Hosting.Interception.Castle;
using MefContrib.Hosting.Interception.Configuration;
using Castle.DynamicProxy;

namespace MefInterceptorSample
{
	public class MyInterceptor : IInterceptor
	{
		public void Intercept(IInvocation invocation)
		{
			Console.WriteLine("Call {0}.", invocation.Method.Name);
			invocation.Proceed();
			Console.WriteLine("Called {0}.", invocation.Method.Name);
		}
	}
	
	public interface ICalc
	{
		int Add(int x, int y);
		int Sub(int x, int y);
	}
	
	[Export(typeof(ICalc))]
	public class CalcImpl : ICalc
	{
		public int Add (int x, int y)
		{
			Console.WriteLine("In Add.");
			return x + y;
		}
		
		public int Sub (int x, int y)
		{
			Console.WriteLine("In Sub.");
			return x - y;
		}
	}
	
	[Export]
	public class MainClass
	{
		[Import]
		public ICalc Calc {private get;set;}
		
		public void Run()
		{
			Console.WriteLine(Calc.Add(1, 2));
			Console.WriteLine(Calc.Sub(1, 2));
		}
		
		public static void Main (string[] args)
		{
			// 割り込み対象となるクラス用カタログ
			// 割り込めないクラスがカタログに含まれていると例外が発生するのでカタログを分ける(恐らくバグ)
			var targetCatalog = new TypeCatalog(typeof(CalcImpl));
			// Castle DynamicProxyを使って割り込む
			var interceptor = new DynamicProxyInterceptor(new MyInterceptor());
			// 第2引数は割込条件。ここでは無条件に割り込ませるため、常にtrue。
			var criteria = new PredicateInterceptionCriteria(interceptor, _=>true);
			// 割り込み用カタログ。 
			var config = new InterceptionConfiguration().AddInterceptionCriteria(criteria);
			var interceptCatalog = new InterceptingCatalog(targetCatalog, config);
			
			// 割り込み対象外のクラス用カタログ。
			var otherCatalog = new TypeCatalog(typeof(MainClass));
			
			var container = new CompositionContainer(new AggregateCatalog(interceptCatalog, otherCatalog));
			var main = container.GetExportedValue<MainClass>();
			main.Run();
		}
	}
}
/* 結果
Call Add.
In Add.
Called Add.
3
Call Sub.
In Sub.
Called Sub.
-1
*/

InterceptingCatalogを使った割り込みですが、これが最終版ではなく、アイディア募集中みたいなことをどこかのフォーラムで見た気がします。

あと、コメントにも書きましたが、Castle.DynamicProxyを使っている場合、interface継承もしくは、仮想メソッドでないと割り込みできないのですが、割り込み対象が存在しない場合、例外が発生してしまいます。恐らくバグだとは思いますが、取りあえずカタログを分けることで回避可能です。

ネットで見かけたサンプルコードでは、ExportMetadata属性を使って割り込み対象の制御をおこなっていましたが、カスタム属性だとソースコードが必要な上、再コンパイルが必要です。AOP自体多用するものではありませんが、想定される使用状況を考えると、これはイマイチな気がしますね。