Postsharp – aspekty metod (OnMethodBoundaryAspect)

W ostatnim wpisie przedstawiłem aspekt do obsługi wyjątków. Dzisiaj trochę więcej o samych aspektach dla metod. Oprócz wyświetlenia  nazwy metody czy przekazanych parametrów można wyciągnąć trochę więcej informacji. Dla przypomnienia, parametry można wyświetlać następująco:

public override void OnEntry(MethodExecutionArgs args) 
{ 
    var argValues = new StringBuilder(); 
    
    foreach (var argument in args.Arguments) 
    { 
        argValues.Append(argument.ToString()).Append(","); 
    } 
 
    Console.WriteLine("The {0} method was entered with the parameter values: {1}", 
    args.Method.Name, argValues.ToString()); 
} 

Możliwe jest również czytanie lub modyfikowanie wartości zwracanej. Przykład:

public class MyAspect : OnMethodBoundaryAspect
{
   public override void OnExit(MethodExecutionArgs args)
   {
       base.OnExit(args);
       Console.WriteLine("Orginalna wartosc:{0}",args.ReturnValue);
       args.ReturnValue = "Test";
   } 
}    

internal class Program
{
   private static void Main()
   {
       Console.WriteLine(GetText());
   }          
   [MyAspect]
   private static string GetText()
   {
       return "Witaj swiecie.";
   }
}

Właściwość ReturnValue jest do odczytu i zapisu więc można zmienić to co funkcja początkowo zwróciła.

Można również mieć dostęp do klasy, do której aspekt został doczepiony tzn.:

public class MyAspect : OnMethodBoundaryAspect
{
   public override void OnEntry(MethodExecutionArgs args)
   {
       base.OnEntry(args);
       Console.WriteLine(args.Instance.ToString());
   }
}

internal class Wrapper
{
   [MyAspect]
   public string GetText()
   {
       return "Witaj swiecie.";
   }
}

internal class Program
{
   private static void Main()
   {
       Wrapper wrapper=new Wrapper();
       Console.WriteLine(wrapper.GetText());
   }                
}

W powyższym przykładzie, Instance zawiera referencję na obiekt klasy Wrapper.

Z kolei właściwość Method zawiera informację o metodzie w formie MethodBase:

public override void OnEntry(MethodExecutionArgs args)
{
    base.OnEntry(args);
    Console.WriteLine(args.Method.Name);
    Console.WriteLine(args.Method.IsVirtual);
}

Ciekawą właściwością jest FlowBehaviour. FlowBehaviour przyjmuje następujące wartości: Default, Continue, RethrowException, Return, ThrowException. Poniższy kod wyświetli tylko jedną wiadomość ponieważ w aspekcie ustawiamy FlowBehaviour na Return co spowoduje po prostu wykonanie return przed logiką metody:

[Serializable]
public class MyAspect : OnMethodBoundaryAspect
{
   public override void OnEntry(MethodExecutionArgs args)
   {
       if ((bool)args.Arguments[0])
           args.FlowBehavior = FlowBehavior.Return;
   }
}
internal class Program
{
   private static void Main()
   {
       DoSomething(true);
       DoSomething(false);
   }
   [MyAspect]
   public static void DoSomething(bool flag)
   {
       Console.WriteLine("Witaj swiecie:{0}",flag);
   }
}

RethrowException z kolei jest przydatny, gdy przeciążamy OnException:

public class MyAspect : OnMethodBoundaryAspect
{
   public override void OnException(MethodExecutionArgs args)
   {
       Console.WriteLine("Zlapano wyjatek");
       args.FlowBehavior = FlowBehavior.RethrowException;
   }
}
internal class Program
{
   private static void Main()
   {
       DoSomething(true);
       DoSomething(false);
   }
   [MyAspect]
   public static void DoSomething(bool flag)
   {
       throw new NotImplementedException();
   }
}

RethrowException jak sama nazwa mówi, wyrzuci ponownie ten sam wyjątek. Najpierw on zostanie złamany w celu wywołania OnException na aspekcie a potem przez RethrowException zostanie on ponownie wyrzucony.

Kolejną wartością jest ThrowException, który wyrzuci nowy wyjątek zdefiniowany w aspekcie:

[Serializable]
public class MyAspect : OnMethodBoundaryAspect
{
   public override void OnException(MethodExecutionArgs args)
   {
       Console.WriteLine("Zlapano wyjatek");
       args.Exception=new Exception("Custom exception");
       args.FlowBehavior = FlowBehavior.ThrowException;
   }
}
internal class Program
{
   private static void Main()
   {
       DoSomething(true);
       DoSomething(false);
   }
   [MyAspect]
   public static void DoSomething(bool flag)
   {
       throw new NotImplementedException();
   }
}

Jeśli nie ustawilibyśmy właściwości Exception wtedy FlowBehaviour.ThrowException i FlowBehaviour.RethrowException miałby takie same znaczenie.

Z kolei Continue zdławi wyjątek i należy tego w większości sytuacjach unikać jeśli chodzi o metodę OnException:

[Serializable]
public class MyAspect : OnMethodBoundaryAspect
{
   public override void OnException(MethodExecutionArgs args)
   {
       Console.WriteLine("Zlapano wyjatek");
       args.FlowBehavior = FlowBehavior.Continue;
   }
}

Domyślną wartością jest FlowBehaviour.Default co oznacza, że dla OnException będzie to RethrowException a dla OnExit\OnEntry Continue.

Ostatnią właściwością jaką chciałem omówić jest MethodExceuctionTag. Jeśli zachodzi potrzeba dzielenia stanu pomiędzy poradami (OnEntry, OnExit)  możemy właśnie z tego skorzystać. Na przykład aspekt sprawdzający jak długo metoda była wykonywana można zaimplementować następująco:

[Serializable]
public class PerformanceAspect : OnMethodBoundaryAspect
{
   public override void OnEntry(MethodExecutionArgs args)
   {
       args.MethodExecutionTag = Stopwatch.StartNew();
   }

   public override void OnExit(MethodExecutionArgs args)
   {
       var sw = (Stopwatch)args.MethodExecutionTag;
       sw.Stop();

       Console.WriteLine("{0}:{1}", args.Method.Name,sw.ElapsedMilliseconds);
   }
} 

Wyłącznie w powyższy sposób należy przekazywać dane pomiędzy zdarzeniami – nie można polegać na prywatnych polach jak to byśmy zrobili w klasycznej klasie!

Leave a Reply

Your email address will not be published.