C#のAOPライブラリ(Fody)
概要
前回記事(C#のAOPライブラリ(PostSharp) - Status Code 303 - See Other)
上記とは別のライブラリ(無料版)を使ってAOPを試した内容。
GitHub - Fody/Fody: Extensible tool for weaving .net assemblies
使い方
サンプルクラス(PostSharpとほぼ同様)
全体処理は以下のようにする。
using System; namespace FodySample { class Program { static void Main(string[] args) { Console.WriteLine("Start Main"); new Sample().Execute(); Console.WriteLine("End Main"); Console.Read(); } } }
今回は以下のSampleクラスのメソッドに対して、メソッド開始・終了にログを出力できるようにする。
using System; namespace FodySample { class Sample { public void Execute() { Console.WriteLine("Hello {0}!", GetVal()); } public string GetVal() { return "Fody"; } } }
上記プログラムは、以下の出力になる。
Start Main Hello Fody! End Main
Fodyインストール
NuGetから以下のパッケージをインストールする。
- Fody (ver 1.29.4)
- MethodDecorator.Fody (ver 0.9.0.6)
Fodyの安定版の最新は2.1.0(2017/6/25現在)だが、このバージョンはビルド時に以下のエラーが出て動かない。
Fody: The weaver assembly 'MethodDecorator.Fody, Version=0.9.0.4, Culture=neutral, PublicKeyToken=null' references an out of date version of Mono.Cecil.dll (cecilReference.Version). At least version 0.10 is expected. The weaver needs to add a NuGet reference to FodyCecil version 2.0.
検索したら何かしらエラーが出てきた。MonoCecilのバージョン違いが原因みたいだがどうすりゃ直るのか分からなかった。
Please fix this error · Issue #53 · Fody/MethodDecorator · GitHub
念のため、Fodyパッケージの参照するMono.Cecil.dllのバージョンを確認したが、0.10.0.0-beta6だったので問題なさそう。MethodDecorator側の問題?
・・こちらに関しては解決したら追記します。
今回は、1.29.4にダウングレードして動作を確認してます。
MethodDecorator.FodyはFodyに機能を組み込むアドインとして実装されているらしい。
そのため、Fodyにどのアドインを組み込むかを FodyWeavers.xml に定義しなければいけない。
<?xml version="1.0" encoding="utf-8" ?> <Weavers> <MethodDecorator /> </Weavers>
AOP適用定義
AOP適用属性を定義。適用メソッドのログ出力。
using System; using System.Reflection; [module: FodySample.Logger] namespace FodySample { [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Assembly | AttributeTargets.Module)] public class Logger : Attribute { private object instance; private MethodBase method; private object[] args; public virtual void Init(object instance, MethodBase method, object[] args) { this.instance = instance; this.method = method; this.args = args; } public void OnEntry() { Console.WriteLine("OnEntry {0}", method.Name); } public void OnExit() { Console.WriteLine("OnExit {0}", method.Name); } public void OnException(Exception exception) { } } }
適用メソッドに属性付加
using System; namespace FodySample { class Sample { [Logger] public void Execute() { Console.WriteLine("Hello {0}!", GetVal()); } [Logger] public string GetVal() { return "Fody"; } } }
すると、AOP適用したプログラムは以下の出力になる。
Start Main OnEntry Execute OnEntry GetVal OnExit GetVal Hello Fody! OnExit Execute End Main
参考までに
MethodDecorator.Fodyの場合、コンパイルした後は以下のようになるらしい。
なお、以下はInterceptorAttributeをAOP適用属性を作った場合。
public void Method(int value) { InterceptorAttribute attribute = (InterceptorAttribute) Activator.CreateInstance(typeof(InterceptorAttribute)); // in c# __methodref and __typeref don't exist, but you can create such IL MethodBase method = MethodBase.GetMethodFromHandle(__methodref (Sample.Method),__typeref (Sample)); object[] args = new object[1] { (object) value }; attribute.Init((object)this, method, args); attribute.OnEntry(); try { Debug.WriteLine("Your Code"); attribute.OnExit(); } catch (Exception exception) { attribute.OnException(exception); throw; } }
コンソールアプリ内のSampleクラスをILSpyで逆コンパイルした結果は以下のようになった。
using System; using System.Reflection; namespace FodySample { internal class Sample { [Logger] public void Execute() { MethodBase methodFromHandle = MethodBase.GetMethodFromHandle(methodof(Sample.Execute()).MethodHandle, typeof(Sample).TypeHandle); Logger logger = (Logger)Activator.CreateInstance(typeof(Logger)); object[] args = new object[0]; logger.Init(this, methodFromHandle, args); logger.OnEntry(); try { Console.WriteLine("Hello {0}!", this.GetVal()); logger.OnExit(); } catch (Exception exception) { logger.OnException(exception); throw; } } [Logger] public string GetVal() { MethodBase methodFromHandle = MethodBase.GetMethodFromHandle(methodof(Sample.GetVal()).MethodHandle, typeof(Sample).TypeHandle); Logger logger = (Logger)Activator.CreateInstance(typeof(Logger)); object[] args = new object[0]; logger.Init(this, methodFromHandle, args); logger.OnEntry(); string result; try { string text = "Fody"; result = text; logger.OnExit(); } catch (Exception exception) { logger.OnException(exception); throw; } return result; } } }