LoginSignup
13
11

More than 5 years have passed since last update.

C#でAOPのサンプル実装

Last updated at Posted at 2016-07-15

1.AOPの実装

C#でAOPを実装したいけどライブラリのような使い勝手の良さそうな
Nugetパッケージ等が見当たらなかったのでサンプルコードを実装してみました。

2.使用方法

void Main関数の処理を見て貰えれば分かるかと思います

3.作成動機

色々ログを取りたいけど一々ログを手動で出力する実装を記述するのが面倒だった為

4.使用技術

ReflectionとExpressionsを使用しています。
Reflectionだけだとコンストラクタを探す時と実行する時に
アセンブリ情報に毎回アクセスを行いパフォーマンスを悪くしてしまうので、
Expressionsを使用して初回時にコンストラクタのFunctionを作成し
キャッシュすることによってパフォーマンスを下げないようにしています。

5.サンプルコード

IgnorLoggingMethodAttribute.cs
/// <summary>
/// ロギングしないメソッドに付与する属性
/// </summary>
/// <memo>
/// メソッドにしか適用出来ません
/// </memo>
[System.AttributeUsage(System.AttributeTargets.Method)]
public class IgnorLoggingMethodAttribute : Attribute
{
    /// <summary>
    /// メソッドが実行される前にするロギングするか判断するフラグ
    /// </summary>
    public bool IgnorLoggingBeforeMethod
    {
        get;
        set;
    }

    /// <summary>
    /// メソッドが実行された後にするロギングするか判断するフラグ
    /// </summary>
    public bool IgnorLoggingAffterMethod
    {
        get;
        set;
    }

    /// <summary>
    /// コンストラクタ(ロギングフラグを設定する)
    /// </summary>
    /// <param name="isBefore">trueの場合、メソッドが実行される前のロギングをしない</param>
    /// <param name="isAffter">trueの場合、メソッドが実行された後のロギングをしない</param>
    public IgnorLoggingMethodAttribute(bool isBefore, bool isAffter)
        : base()
    {
        this.IgnorLoggingBeforeMethod = isBefore;
        this.IgnorLoggingAffterMethod = isAffter;
    }
}
ConstructorFunc.cs
/// <summary>
/// コンストラクタの処理をキャッシュするクラス
/// </summary>
internal static class ConstructorFunc
{
    /// <summary>
    /// コンストラクタのFuncが入っています
    /// </summary>
    public static ConcurrentDictionary<string, Func<object[], object>> ConstractorList = new ConcurrentDictionary<string, Func<object[], object>>();
}
MethodBaseProxy.cs
System.Linq.Expressions
System.Reflection
System.Runtime.Remoting.Messaging
System.Runtime.Remoting.Proxies

/// <summary>
/// ロギングクラス
/// アスペクト指向言語よりに実装しました
/// </summary>
/// <typeparam name="T">クラス内の全メソッドでロギングしたいクラス名</typeparam>
/// <memo>
/// Tに入るクラスは必ずMarshalByRefObjectクラスを継承して下さい
/// </memo>
public class MethodBaseProxy<T> : RealProxy
{
    protected virtual string keyPrefix
    {
        get
        {
            return "FuncKey";
        }
    }

    protected readonly T realObject;

    /***************************
    * 透過プロキシのインタフェースを指定
    ***************************/
    public MethodBaseProxy(params object[] objs)
        : base(typeof(T))
    {
        var templateType = typeof(T);
        var paramTypes = objs.Select(obj =>
        {
            var type = obj.GetType();
            return string.Format("{0}.{1}", type.Namespace, type.Name);
        }).ToArray();

        var funcKey = string.Format("{0}-{1}.{2}-{3}", this.keyPrefix, templateType.Namespace, templateType.Name, string.Join("-", paramTypes));

        Func<object[], object> funcConstractor;
        //ConstructorFunc.ConstractorList.Clear();
        ConstructorFunc.ConstractorList.TryGetValue(funcKey, out funcConstractor);
        if (null == funcConstractor)
        {
            /*****************************
            * 実行するコンストラクタ情報を取得する
            *****************************/
            Func<System.Type, object[], ConstructorInfo> findConstructor = (type, paramInfos) =>
            {

                var targetConstructor = type.GetConstructor(paramInfos.Select(i => i.GetType()).ToArray());
                if (null != targetConstructor)
                {
                    return targetConstructor;
                }

                /********************************************
                * デフォルト引数のあるコンストラクタを検索
                ********************************************/
                var constructors = type.GetConstructors();
                var constructorInfos = constructors.Select(constructorInfo =>
                {
                    var paramsInfos = constructorInfo.GetParameters();
                    var needNum = paramsInfos.Count() - paramInfos.Count();
                    return new {
                        isDefaultValue = paramsInfos.Any(),
                        defaultValues = paramsInfos.Select(paramsInfo => paramsInfo.DefaultValue).Reverse().Take(needNum).Reverse().ToArray()
                    };
                }).Where(constructorInfo => constructorInfo.isDefaultValue).ToArray();

                foreach (var constructorInfo in constructorInfos)
                {
                    var paramInfo = paramInfos != null ? paramInfos.Concat(constructorInfo.defaultValues) : constructorInfo.defaultValues;
                    var a = paramInfo.Select(i => i.GetType()).ToArray();
                    targetConstructor = type.GetConstructor(paramInfo.Select(i => i.GetType()).ToArray());
                    if (null != targetConstructor)
                    {
                        break;
                    }
                }

                return targetConstructor;
            };

            /********************
            * デフォルト引数を取得する
            ********************/
            var constructor = findConstructor(typeof(T), objs);
            var parameters = constructor.GetParameters();
            var needParamNum = parameters.Count() - objs.Count();
            if (needParamNum != 0)
            {
                var defaultParams = parameters.Where(x => x.HasDefaultValue).Reverse().Select(x => x.DefaultValue).Take(needParamNum).Reverse();
                objs = objs.Concat(defaultParams).ToArray();
            }

            /***********************
            * コンストラクタを式木にして対応
            ***********************/
            Expression<Func<object[], object>> constractorEx = (paramObjs) => findConstructor(typeof(T), paramObjs).Invoke(paramObjs);
            funcConstractor = constractorEx.Compile();
            ConstructorFunc.ConstractorList[funcKey] = funcConstractor;
        }

        realObject = (T)funcConstractor(objs);
    }

    /// <summary>
    /// Tクラスのインスタンスを取得する
    /// </summary>
    /// <returns></returns>
    public T GetInstance()
    {
        return (T)this.GetTransparentProxy();
    }

    /// <summary>
    /// プロキシがメッセージを受け取った時に呼ばれるメソッド。
    /// ここで色々な処理を噛ませることができる。
    /// </summary>
    /// <param name="msg"></param>
    /// <returns></returns>
    public override IMessage Invoke(IMessage msg)
    {
        IMethodMessage method = msg as IMethodMessage;

        Console.WriteLine(string.Format("--- Start {0} ---", method.MethodName));
        try
        {
            MethodInfo methodInfo = (MethodInfo)method.MethodBase;

            /**************************************
            * プロパティのGetメソッド,Setメソッドでない事を確認
            **************************************/
            var isProperty = methodInfo.DeclaringType.GetProperties().Any(x =>
            {
                return (x.GetGetMethod() == methodInfo || x.GetSetMethod() == methodInfo);
            });

            /**************************
            * 対象のメソッドが監視対象か判定
            **************************/
            var attribute = methodInfo.GetCustomAttribute(typeof(IgnorLoggingMethodAttribute));
            IgnorLoggingMethodAttribute customAttribute = null;
            if (null != attribute)
            {
                customAttribute = (IgnorLoggingMethodAttribute)attribute;
            }

            if (false == isProperty)
            {
                if (customAttribute != null)
                {
                    if (false == customAttribute.IgnorLoggingBeforeMethod)
                    {
                        BeforeExecuteMethod(method);
                    }
                }
                else
                {
                    BeforeExecuteMethod(method);
                }
            }

            var args = method.Args;

            /********************
            * Tオブジェクトのメソッド実行
            ********************/
            var result = methodInfo.Invoke(realObject, args);

            if (false == isProperty)
            {
                if (customAttribute != null)
                {
                    if (false == customAttribute.IgnorLoggingAffterMethod)
                    {
                        AfterExecuteMethod(method, args, result);
                    }
                }
                else
                {
                    AfterExecuteMethod(method, args, result);
                }
            }

            return new ReturnMessage(
                result, args, args.Length, method.LogicalCallContext, (IMethodCallMessage)msg);
        }
        catch (TargetInvocationException ex)
        {
            /****************************************************
             * メソッド内部で例外が発生すると
             * TargetInvocationExceptionが送出される。
             * Echo内部で発生した例外は ex.InnerException で取得
             ****************************************************/
            return new ReturnMessage(ex.InnerException, (IMethodCallMessage)msg);
        }
        catch (Exception ex)
        {
            /**************************************************
             * TargetInvocationExceptionでキャッチ出来なかった
             * Exceptionを全てキャッチする
             **************************************************/
            return new ReturnMessage(ex.InnerException, (IMethodCallMessage)msg);
        }
        finally
        {
            Console.WriteLine(string.Format("--- End {0} ---", method.MethodName));
        }
    }

    /// <summary>
    /// 呼び出しメソッドが実行前に実行されるメソッド
    /// </summary>
    /// <param name="method"></param>
    protected virtual void BeforeExecuteMethod(IMethodMessage method)
    {
        var methodInfo = (MethodInfo)method.MethodBase;
        Console.WriteLine(string.Format("--- Logging Start MethodName={0} RequestParamater={1} ---", method.MethodName, JsonConvert.SerializeObject(method.Args)));
    }

    /// <summary>
    /// 呼び出しメソッドの実行後に実行されるメソッド
    /// </summary>
    /// <param name="method"></param>
    /// <param name="resultObject"></param>
    protected virtual void AfterExecuteMethod(IMethodMessage method, object[] args, object resultObject)
    {
        Console.WriteLine(string.Format("--- Logging End MethodName={0} RequestParamater={1} ResultData={2} ---", method.MethodName, JsonConvert.SerializeObject(args), JsonConvert.SerializeObject(resultObject)));
    }
}
sample.cs
System.Linq.Expressions
System.Reflection
System.Runtime.Remoting.Messaging
System.Runtime.Remoting.Proxies

public class TestProxy : MarshalByRefObject
{
    private int TestA {get;set;}

    private int TestB {get;set;}

    public TestProxy(int a = 1)
    {
        TestA = a;
        TestB = 9;
    }

    public TestProxy(int a, int b = 1, int c = 1, string asd = "")
    {
        TestA = a;
        TestB = b + c;
    }

    public void GetOutPut()
    {
        Console.WriteLine(string.Format(@"GetOutPut(int {0},{1})", TestA, TestB));
    }
}

/* 使用方法はこちら */
void Main()
{
    var testProxy = new MethodBaseProxy<TestProxy>(1, 1).GetInstance();
    try
    {
        testProxy.GetOutPut();
    }
    catch (Exception ex)
    {
        ex.Dump();
    }
}

// Define other methods and classes here
結果
--- Start GetOutPut ---
--- Logging Start MethodName=GetOutPut RequestParamater=[] ---
GetOutPut(int 1,2)
--- Logging End MethodName=GetOutPut RequestParamater=[] ResultData=null ---
--- End GetOutPut ---
13
11
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
11