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!