Dosyć długo szukałem w jaki sposób pobrać nazwy parametrów metody wraz z ich wartościami w sposób uniwersalny.
Ułatwiłoby to logowanie wszystkiego, co zachodzi w aplikacji. Wywołując metodę:
public void CountPrice(int bar, string baz)
{
}
chciałbym otrzymać w logach wpis:
Method’s Foo arguments >> bar: 1, baz: zzz
Najpierw spróbowałem za pomocą metody GetParameters klasy MethodInfo. Zwrócone obiekty typu ParameterInfo zawierają jednak tylko informacje o typach parametrów – nie mają wartości tych parametrów w czasie wykonywania kodu.
Moim celem było stworzenie metody, którą mógłbym użyć do pobrania sformatowanej informacji o parametrach metody – na przykład w celu zalogowania ich. Załóżmy dla prostoty testowania, że nasza metoda Foo będzie zwracała sformatowany napis z jej parametrami i ich wartościami.
Pierwsza wersja:
public class TestClass
{
public static string Foo(int bar, string baz)
{
return LogingHelpers.GetMethodArguments();
}
}
public class LoggingHelpers
{
public static string GetMethodArguments()
{
//TODO tutaj magia :-)
return "";
}
}
Niestety – z taką składnią nie udało mi się. Z kontekstu LoggingHelpers.GetMethodArguments nie da się (lub nadal nie potrafię :-) ) dostać się do parametrów metody wywołującej. Działająca składnia jest nieco inna i odrobinę mniej wygodna:
public class TestClass
{
public static string Foo(int bar, string baz)
{
return GetMethodArguments(() => bar, () => baz);
}
public static string NoParameters()
{
return GetMethodArguments();
}
}
public class LoggingHelpers
{
public static string GetMethodArguments(params Func<object>[] expr)
{
//TODO tutaj magia, ujawnione już za chwilę!
return "";
}
}
Ale najpierw test z wykorzystaniem NUnit.
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
[TestFixture]
public class LoggingTests
{
[Test]
public void TwoParametersCorrectlyFormatted
{
string parameterNamesAndValues = TestClass.Foo(1, "zzz");
Assert.That(parameterNamesAndValues, Is.EqualTo("Method's Foo arguments >> bar: 1, baz: zzz");
}
[Test]
public void NoParametersCorrectlyFormatted
{
string noParameters = TestClass.NoParameters();
Assert.That(noParameters, Is.EqualTo("Method's Foo arguments >> No arguments.");
}
}
Odpalamy testy, który oczywiście nie przechodzą.
Żeby testy zadziałały, nasza klasa LoggingHelpers wraz z metodą GetMethodParameters może wyglądać tak:
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
public class LoggingHelpers
{
public static string GetMethodParameters(params Func<object>[] expr)
{
// Pobieramy nazwę metody.
string methodname = new StackFrame(1).GetMethod().Name;
// wstępne formatowanie wynikowego napisu:
string logmessage = "Method's " + methodname + " arguments >> ";
// Tablica wyrażeń lambda jest pusta
if (expr.Length == 0)
{
logmessage += "No arguments.";
}
else
{
try
{
// dla każdego Expression
foreach (var par in expr)
{
// Zaawansowane sztuczki. Za pomocą MSIL znajdujemy informacje o Expression
byte[] il = par.Method.GetMethodBody().GetILAsByteArray();
// bajty 2-6 oznaczają uchwyt do naszych parametrów
int fieldHandle = BitConverter.ToInt32(il, 2);
// pobieramy FieldInfo tego parametru
FieldInfo field = par.Target.GetType().Module.ResolveField(fieldHandle);
// Formatujemy ostatecznie napis dla tego parametru.
// Nazwę parametru uzyskujemy z FieldInfo, natomiast wartość wykonując przekazane Expression.
logmessage += string.Format("{0}: {1}. ", field.Name, par() ?? "@null");
}
}
catch
{
// Ignorujemy błędy - nic nie jest gorsze od doskonale działającej logiki aplikacji, która wywala się na narzędziach do logowania :-)
// Tego typu try-catch to zła praktyka w aplikacji! W tym miejscu jest to stosowne, ale przestrzegam przed nadużywaniem "zjadania" wyjątków.
logmessage += " FAILED reading arguments.";
}
}
}
}
Klasa do wykorzystania w aplikacji.
W kolejnej notce pokażę, jak to spiąć z log4net.
Tags: .net, C#, logging, reflection
0
komentarzy