ようこそ。睡眠不足なプログラマのチラ裏です。

コンストラクタのインターセプトとアドバイス、それはアクセプト指向的な何か。

まずは、アスペクト指向プログラミング(AOP)的な用語の簡単な説明とか。

アドバイスとは

アスペクト指向で言うところのアドバイスとは、「どのような処理を行うか」のことを指す。
コード(ある処理)に対してインターセプトを行い、そこで「何」を行うのか、それがアドバイス。
ロギング機能の例でいうと「どのようなログをどのような方法で出すか」がそれにあたる。
例えば、イベントログに吐き出すのか、データベースに登録するのか等々の処理がアドバイスにあたる。


ジョインポイントとは

アスペクト指向で言うところのジョインポイントとは、
コード(ある処理)のどこでアドバイスを行うかを示す具体的な場所のことをいう。
ロギング機能の例でいえば「どこでログを出すか」にあたる。
例えば、メソッドの呼び出し前と呼び出し後にログを出力するのであれば、
その「呼び出し前」と「呼出し後」、つまりメソッド呼び出しの前後がジョインポイントとなる。


ポイントカットとは

ジョインポイントの集まりのことをポイントカットという。ただし、単に集合を指すのではなく、
ポイントカットはジョインポイントの集合を条件で表したものである。
ロギング機能の例で言うなら、これも広い意味では「どこでログを出すか」にあたるが、
どのクラスのどのメソッドに対してロギング機能を適用するか等の、
ジョインポイントを適用する対象と条件をあらわす単位がポイントカット。


ウィーブとは

コード(ある処理)に対して、アスペクトを合体させること。
コード(ある処理)の部分部分に対してアスペクトが挿入されていくので、ウィーブ(織り込み)と呼ばれる。


アスペクト指向プログラミング(AOP)的な用語のまとめ
ジョインポイントはアスペクトをウィーブする「コード上のポイント」であり、
1つ以上のジョインポイントをウィーブする適用条件をまとめた定義がポイントカットである。
アドバイスは実際にウィーブするアスペクトの処理定義であり、ポイントカットに対してアドバイスを定義しておくことで、
そのポイントカットで定義されている各ジョインポイントに対してアドバイスの処理が実行されることになる。
短く言えば、あるポイントカットの各ジョイントポイントに対してアドバイスをウィーブすることがAOP


ContextBoundObjectの派生クラスとProxyAttributeの派生クラスを用いたコンストラクタインターセプト

以下に示すコードをアスペクト指向プログラミング(AOP)的な用語を用いて簡単に説明すると、
ProxyAttributeの派生クラスであるLoggingProxyAttributeとRealProxyの派生クラスである
LoggingProxyがポイントカットの役割を果たし、そのLoggingProxy内にジョイントポイントとアドバイスが含まれ、
そこでウィーブしている。実際には、ContextBoundObjectの派生クラスTestのコンストラクタインターセプトし、
LoggingProxyというプロキシを介することで、アスペクトしている。

using System;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Services;
using System.Runtime.Remoting.Contexts;

namespace ConsoleApplication1
{
    public class Program
    {
        /// <summary>
        /// メイン
        /// </summary>
        /// <param name="args">コマンドライン引数</param>
        private static void Main(string[] args)
        {
            Test t = new Test();
            t.Nantekottai();
            t.Owata();
            Console.Read();
        }
    }

    /// <summary>
    /// テスト用クラス
    /// ログ出力透過プロキシ属性を付加
    /// (ContextBoundObjectの派生クラスである必要がある)
    /// </summary>
    [LoggingProxy()]
    public class Test : ContextBoundObject
    {
        /// <summary>
        /// ログ出力属性を付加していないメソッド
        /// </summary>
        public void Nantekottai()
        {
            Console.WriteLine("/(^o^)\");
        }

        /// <summary>
        /// ログ出力属性を付加したメソッド
        /// </summary>
        [Logging()]
        public void Owata()
        {
            Console.WriteLine("\(^o^)/");
        }
    }

    /// <summary>
    /// ログ出力透過プロキシ
    /// </summary>
    public class LoggingProxy : RealProxy
    {
        /// <summary>
        /// プロキシ対象を保持
        /// </summary>
        readonly MarshalByRefObject target;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="target">プロキシ対象</param>
        /// <param name="t"></param>
        public LoggingProxy(MarshalByRefObject target, Type t): base(t)
        {
            this.target = target;
        }

        /// <summary>
        /// 現在のインスタンスが表すリモート オブジェクトで呼び出します。
        /// </summary>
        /// <param name="msg">メソッドの呼び出しに関する情報の IDictionary を格納している IMessage</param>
        /// <returns>呼び出されたメソッドが返すメッセージで、
        /// out パラメータまたは ref パラメータのどちらかと戻り値を格納しているメッセージ。
        /// </returns>
        public override IMessage Invoke(IMessage msg)
        {
            IMethodCallMessage call = (IMethodCallMessage)msg;
            IConstructionCallMessage ctor = call as IConstructionCallMessage;

            //コンストラクタの場合の処理
            if (ctor != null)
            {
                RealProxy defaultProxy = RemotingServices.GetRealProxy(target);
                defaultProxy.InitializeServerObject(ctor);
                
                //透過プロキシを取得
                MarshalByRefObject tp = (MarshalByRefObject)this.GetTransparentProxy();
                return EnterpriseServicesHelper.CreateConstructionReturnMessage(ctor, tp);
            }

            //コンストラクタではない場合の処理

            //メソッドのメタデータを取得
            IMethodCallMessage req = msg as IMethodCallMessage;
            MethodInfo methodInfo = ((MethodInfo)req.MethodBase);

            bool log = IsLogging(methodInfo);
            if (log) { Console.WriteLine(methodInfo.Name + "メソッド呼び出し前"); }
            IMessage response = RemotingServices.ExecuteMessage(target, call);
            if (log) { Console.WriteLine(methodInfo.Name + "メソッド呼び出し後"); }

            return response;
        }

        /// <summary>
        /// メソッドのメタデータに対して、ログ出力属性の有無を取得します。
        /// </summary>
        /// <param name="methodInfo">メソッドのメタデータ</param>
        /// <returns>true有:、false:無</returns>
        private static bool IsLogging(MethodInfo methodInfo){
            // メソッドに指定されている属性を取得する
            object[] attrs = methodInfo.GetCustomAttributes(typeof(Attribute), true);
            foreach (Attribute attr in attrs)
            {
                if (attr as LoggingAttribute != null){return true;}
            }
            return false;
        }
    }

    /// <summary>
    /// ログ出力透過プロキシを付加する属性
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
    public class LoggingProxyAttribute : ProxyAttribute
    {
        /// <summary>
        /// ログ出力を付加した透過プロキシを取得します。
        /// </summary>
        /// <param name="serverType">インスタンスを作成するオブジェクト型</param>
        /// <returns>ログ出力を付加した透過プロキシ</returns>
        public override MarshalByRefObject CreateInstance(Type serverType)
        {
            MarshalByRefObject target = base.CreateInstance(serverType);
            RealProxy rp = new LoggingProxy(target, serverType);
            return (MarshalByRefObject)rp.GetTransparentProxy();
        }
    }
    
    /// <summary>
    /// ログ出力を利用するメソッドに付加する属性です。
    /// </summary>
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false ,Inherited = false)]
    public class LoggingAttribute : Attribute{}
}


実行結果

/(^o^)\
Owataメソッド呼び出し前
\(^o^)/
Owataメソッド呼び出し後


てーか、用語使うのもほどほどにって感じ(^ω^;)
まぁ、用語は用語で大事なので覚えておいて損はない。一応覚えましょう。