Status Code 303 - See Other

サーバサイド、iOS・アンドロイドアプリ、インフラレベルの話まで幅広くやってます。情報の誤りや指摘・意見などは自由にどうぞ。

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;
		}
	}
}

PostSharpとの違い

OnExitの挙動が違う

PostSharpでは、AOP適用後のOnExitメソッドはfinally句にあったが、Fodyでは、try句の最後にあるため、以下のような違いになる。

  • PostSharp: AOP適用メソッドで例外が発生しようがOnExitメソッドが動く
  • Fody: AOP適用メソッドで例外が発生した場合はOnExitは実行されない