2010年1月4日月曜日

PostSharp興味深い

PostSharp - Bringing AOP to .NET

AOPフレームワークと言われるんだって。

ボスの大嫌いなRemotingのProxy(.NET Framework 2.0 コア機能解説 ~ 第 1 回 .NET リモーティング ~)でのフックや(A basic proxy for intercepting method calls (Part –1) - Mehfuz's WebLog)、Invokerを必ず呼び出すASP.NET MVCでのFilterAttribute達のどっちかが一般的なのかと思ってたけど、世の中スゴイ事思いつく人たちがいるモンで、ビルド後にILを書き換えてフックポイントを追加してしまえばいいじゃないかという発想で作られてます。まさにEmitにを超える黒魔術。

ドキュメントに書かれてる実装パターン。

int MyMethod(object arg0, int arg1)
{
  OnEntry();
  try
  {
    // Original method body. 

    OnSuccess();
    return returnValue;
  }
  catch ( Exception e )
  {
    OnException();
  }
  finally
  {
    OnExit();
  }
}

Reflectorなんかでビルド後のアセンブリを見ると上記のパターンにそったものが出力されてるのが確認出来ます。

postsharp1 

確かに~、確かに~。

ドキュメントを見つつ、どんなことが出来るのかチラッとコード書いてみたんです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PostSharp.Laos;

namespace TestPostSharp
{
  class Program
  {
    [ThirdAspect]
    private static int _intFldValue;

    static void Main(string[] args)
    {
      Console.WriteLine("Start program.");

      PrintMessage("Hello, World! - first");
      PrintMessage("Hello, World! - second");
      PrintMessage("Hello, World! - third");
      
      var res = Invocation(100,"まじか!",DateTime.Now);
      Console.WriteLine("res = {0}",res);

      _intFldValue = 100;
      Console.WriteLine("Get field {0}", _intFldValue);

      Console.WriteLine("End program.");
      Console.ReadKey();
    }

    [FirstAspect]
    static void PrintMessage(string message)
    {
      Console.WriteLine(message);
    }

    [SecondAspect]
    static int Invocation(int p1, string p2, DateTime dateTime)
    {
      Console.WriteLine("何かか実行されるようだ");
      Console.WriteLine("{0} - {1} - {2}",p1,p2,dateTime);

      return p1;
    }
  }

  [Serializable]
  public class FirstAspectAttribute:OnMethodBoundaryAspect
  {
    public override void OnEntry(MethodExecutionEventArgs eventArgs)
    {
      Console.WriteLine(eventArgs.Method.Name + " - OnEntry");
      base.OnEntry(eventArgs);
    }

    public override void OnExit(MethodExecutionEventArgs eventArgs)
    {
      Console.WriteLine(eventArgs.Method.Name + " - OnExit");
      base.OnExit(eventArgs);
    }

    public override void OnSuccess(MethodExecutionEventArgs eventArgs)
    {
      Console.WriteLine(eventArgs.Method.Name + " - OnSuccess");
      base.OnSuccess(eventArgs);
    }

    public override void OnException(MethodExecutionEventArgs eventArgs)
    {
      Console.WriteLine(eventArgs.Method.Name + " - OnException");
      base.OnException(eventArgs);
    }

    public override void RuntimeInitialize(System.Reflection.MethodBase method)
    {
      Console.WriteLine(method.Name + " - RuntimeInitialize");
      base.RuntimeInitialize(method);
    }

    public override void CompileTimeInitialize(System.Reflection.MethodBase method)
    {
      Console.WriteLine(method.Name + " - CompileTimeInitialize");
      base.CompileTimeInitialize(method);
    }

    public override bool CompileTimeValidate(System.Reflection.MethodBase method)
    {
      Console.WriteLine(method.Name + " - CompileTimeValidate");
      return base.CompileTimeValidate(method);
    }
  }

  [Serializable]
  public class SecondAspect : OnMethodInvocationAspect
  {
    public override void OnInvocation(MethodInvocationEventArgs eventArgs)
    {
      Console.WriteLine("Calling {0}", eventArgs.Method.Name);

      var args = eventArgs.GetArgumentArray();
      if (args[0].GetType() == typeof(int))
        args[0] = (int) args[0] * 2;

      eventArgs.Proceed(args);
      var res = eventArgs.ReturnValue;
      eventArgs.ReturnValue = (int) res + 100;
    }
  }

  [Serializable]
  public class ThirdAspect : OnFieldAccessAspect
  {
    public override void OnGetValue(FieldAccessEventArgs eventArgs)
    {
      Console.WriteLine("Getter {0} = {1}", eventArgs.FieldInfo.Name, eventArgs.StoredFieldValue);
      
      base.OnGetValue(eventArgs);
    }

    public override void OnSetValue(FieldAccessEventArgs eventArgs)
    {
      Console.WriteLine("Setter {0} = {1} -> {2}", eventArgs.FieldInfo.Name, eventArgs.StoredFieldValue, eventArgs.ExposedFieldValue);
      var value = eventArgs.ExposedFieldValue;
      eventArgs.ExposedFieldValue = (int) value*2;
      
      base.OnSetValue(eventArgs);
    }
  }
}

postsharp2

最初から用意されてる便利クラス。

  • OnExceptionAspect
  • OnMethodBoundaryAspect
  • OnMethodInvocationAspect
  • OnFieldAccessAspect

それぞれ、だいたい名前の通り。引数や戻り値を書き換えたりも出来るよ!IOn~のインターフェースも用意されてるから、用意されてるのが気に入らないなら最初から実装してしまうのも可能。OnExceptionAspectについてはその他のクラスのOnExceptionでも取れるから単体で使う場面はそんなに無かったりするのかな~。どうなんでしょう。

属性クラスとしてAspectクラスを実装して、クラスやメソッド、フィールドに指定してビルドするのがオーソドックスな使い方だと思うけど、更に[assembly:自作Aspectクラス(~)]でBCLにゴッソリ指定出来たりするのが恐ろしい。

めっぽう気になってしかたがないのがCompositionAspect。これってDIなんですかね?

フック出来るタイミングや、シリアライズのカスタマイズやら、プラグイン(ビルド後の処理時にだと思われるけどよく分かってない)、msbuild実行されないASP.NETの対応なんかもあって、イロイロ遊べそうな感じデス!