読者です 読者をやめる 読者になる 読者になる

替え玉バリカタでお願いします

お仕事と、お仕事そうでお仕事じゃない、少しお仕事な備忘など。

PostSharpでExceptionをハンドリングする

C# PostSharp

後先考えずにアスペクトを適用することはできるんだけど、アスペクトを多段にしようとしてハマったのでメモ。
あるメソッドAでExceptionのtry〜catchをアスペクト化し、さらにメソッドAの処理を続けたい場合は、メソッドAで呼んでいるメソッドに対してそのアスペクトを適用しなければなりません。
以下のコードで動作確認して納得。PostSharpはAspectを付与したメソッドに対してウィービングするので、それはそうかと納得。基本がなってませんでした。。

エントリ

/// <summary>
/// エントリーポイント
/// </summary>
class Program
{
    static void Main(string[] args)
    {
        TestClass t = new TestClass();

        //t.doExceptionTest1();
        //t.doExceptionTest2();
        t.doExceptionTest3();
    }
}

/// <summary>
/// ExceptionのMessagesを受け取るためのインタフェース
/// </summary>
interface IMessages
{
    List<String> messages { get; set; }
}

アスペクト(2種類)

#region アスペクトその1

/// <summary>
/// Exception用のアスペクト
/// </summary>
[Serializable]
public class ExceptionAspect : OnExceptionAspect
{
    public Type ExceptionType { get; set; }

    public FlowBehavior Behavior { get; set; }

    public override void OnException(MethodExecutionArgs args)
    {
        var testClass = args.Instance as TestClass;

        string msg = string.Format("{0}: Error running {1}. {2}{3}{4}", DateTime.Now, args.Method.Name, args.Exception.Message, Environment.NewLine, args.Exception.StackTrace);

        System.Diagnostics.Debug.WriteLine("OnException==========");
        Debug.WriteLine(msg);
        System.Diagnostics.Debug.WriteLine("OnException==========");

        if (testClass.messages == null) { testClass.messages = new List<string>(); }

        testClass.messages.Add(msg);

        args.FlowBehavior = FlowBehavior.Continue;
    }

    public override Type GetExceptionType(System.Reflection.MethodBase targetMethod)
    {
        return ExceptionType;
    }
}

#endregion アスペクトその1



#region アスペクトその2

/// <summary>
/// Exception用のアスペクト
/// </summary>
[Serializable]
public class AnotherExceptionAspect : OnExceptionAspect
{
    public Type ExceptionType { get; set; }

    public FlowBehavior Behavior { get; set; }

    public override void OnException(MethodExecutionArgs args)
    {
        var testClass = args.Instance as TestClass;

        string msg = string.Format("{0}: Error running {1}. {2}{3}{4}", DateTime.Now, args.Method.Name, args.Exception.Message, Environment.NewLine, args.Exception.StackTrace);

        System.Diagnostics.Debug.WriteLine("OnException==========");
        Debug.WriteLine(msg);
        System.Diagnostics.Debug.WriteLine("OnException==========");

        if (testClass.messages == null) { testClass.messages = new List<string>(); }

        testClass.messages.Add(msg);

        args.FlowBehavior = FlowBehavior.Continue;
    }

    public override Type GetExceptionType(System.Reflection.MethodBase targetMethod)
    {
        return ExceptionType;
    }
}

#endregion アスペクトその2

多段なしタイプの実装例

#region 多段なしタイプの実装例

/// <summary>
/// Exceptionを2発投げる
/// </summary>
public void doExceptionTest1()
{
    ThrowSampleExecption();

    ThrowSampleExecption();

    //ThrowDummyException(); //AspectではCatchされない

    System.Diagnostics.Debug.WriteLine("doExceptionTest1==========");
    foreach (var msg in this.messages)
    {
        System.Diagnostics.Debug.WriteLine(msg);
    }
    System.Diagnostics.Debug.WriteLine("doExceptionTest1==========");
}

[ExceptionAspect(ExceptionType = typeof(ApplicationException), Behavior = FlowBehavior.Continue)]
private void ThrowSampleExecption()
{
    throw new ApplicationException("Sample Exception");
}

private void ThrowDummyException()
{
    throw new NotImplementedException();
}

#endregion 多段なしタイプの実装例

多段ありタイプの実装例

#region 多段ありタイプの実装例

/// <summary>
/// Exceptionを2発投げる
/// </summary>
public void doExceptionTest2()
{
    ThrowSampleExceptionOnChildren();

    //ThrowDummyException(); //AspectではCatchされない

    System.Diagnostics.Debug.WriteLine("doExceptionTest2==========");
    foreach (var msg in this.messages)
    {
        System.Diagnostics.Debug.WriteLine(msg);
    }
    System.Diagnostics.Debug.WriteLine("doExceptionTest2==========");
}

//ここにアスペクトを適用してもダメ。子メソッドのFlowBehavior.Continueは無視される。
private void ThrowSampleExceptionOnChildren()
{
    this.ChildMethod1(); //このメソッドにアスペクトを適用していれば、ChildMethod2も実行される

    this.ChildMethod2();
}

//ここにアスペクトを適用するのが正しい
[ExceptionAspect(ExceptionType = typeof(ApplicationException), Behavior = FlowBehavior.Continue)]
private void ChildMethod1()
{
    throw new ApplicationException("ChildMethod1 Exception");

    // FlowBehavior.Continueにしても、ここは実行されない
    System.Diagnostics.Debug.WriteLine("ChildMethod1 continue...");
}

//ここにアスペクトを適用するのが正しい
[ExceptionAspect(ExceptionType = typeof(ApplicationException), Behavior = FlowBehavior.Continue)]
private void ChildMethod2()
{
    throw new ApplicationException("ChildMethod1 Exception");

    // FlowBehavior.Continueにしても、ここは実行されない
    System.Diagnostics.Debug.WriteLine("ChildMethod2 continue...");
}

#endregion 多段ありタイプの実装例

入れ子タイプの実装例

#region 入れ子タイプの実装例

/// <summary>
/// Exceptionを3発投げる
/// </summary>
public void doExceptionTest3()
{
    ThrowAnotherSampleExceptionOnChildren();

    //ThrowDummyException(); //AspectではCatchされない

    System.Diagnostics.Debug.WriteLine("doExceptionTest2==========");
    foreach (var msg in this.messages)
    {
        System.Diagnostics.Debug.WriteLine(msg);
    }
    System.Diagnostics.Debug.WriteLine("doExceptionTest2==========");
}

private void ThrowAnotherSampleExceptionOnChildren()
{
    this.AnotherChildMethod1();

    this.AnotherChildMethod1(false);

    this.AnotherChildMethod2();
}

//ここにアスペクトを適用するのが正しい
[ExceptionAspect(ExceptionType = typeof(ApplicationException), Behavior = FlowBehavior.Continue)]
private void AnotherChildMethod1(bool processApplicationException = true)
{
    if (processApplicationException)
    {
        //processApplicationException = true の場合はthrowされたらこのメソッドを抜ける
        throw new ApplicationException("ChildMethod1 Exception");
    }

    //
    //processApplicationException = false の場合はこれより下のコードも実行される
    //

    this.AnotherMethod();

    System.Diagnostics.Debug.WriteLine("AnotherChildMethod1 continue...");
}

//ここにアスペクトを適用するのが正しい
[ExceptionAspect(ExceptionType = typeof(ApplicationException), Behavior = FlowBehavior.Continue)]
private void AnotherChildMethod2()
{
    throw new ApplicationException("AnotherChildMethod2 Exception");

    // FlowBehavior.Continueにしても、ここは実行されない
    System.Diagnostics.Debug.WriteLine("ChildMethod2 continue...");
}

//ここにアスペクトを適用するのが正しい
[AnotherExceptionAspect(ExceptionType = typeof(NotImplementedException), Behavior = FlowBehavior.Continue)]
private void AnotherMethod()
{
    throw new NotImplementedException("つくってまへん");
}

#endregion 入れ子タイプの実装例

サンプルはこちら。
github

2013/08/15 後日談
よ〜く考えてみると、多段にしたい場合があっても、リファクタして多段→単発に置き換えることもできそう。
もう少し考えおけばよかったな。。

広告を非表示にする