Dla danej klasy chciałbym mieć funkcjonalność śledzenia tj. Chciałbym logować każde wywołanie metody (podpis metody i rzeczywiste wartości parametrów) i każde wyjście metody (tylko podpis metody).
Jak to osiągnąć, zakładając, że:
- Nie chcę używać żadnych bibliotek AOP innych firm dla języka C #,
- Nie chcę dodawać zduplikowanego kodu do wszystkich metod, które chcę śledzić,
- Nie chcę zmieniać publicznego API klasy - użytkownicy klasy powinni mieć możliwość wywołania wszystkich metod w dokładnie taki sam sposób.
Aby pytanie było bardziej konkretne, przyjmijmy, że istnieją 3 klasy:
public class Caller
{
public static void Call()
{
Traced traced = new Traced();
traced.Method1();
traced.Method2();
}
}
public class Traced
{
public void Method1(String name, Int32 value) { }
public void Method2(Object object) { }
}
public class Logger
{
public static void LogStart(MethodInfo method, Object[] parameterValues);
public static void LogEnd(MethodInfo method);
}
Jak wywołać Logger.LogStart i Logger.LogEnd dla każdego wywołania Method1 i Method2 bez modyfikowania metody Caller.Call i bez jawnego dodawania wywołań do Traced.Method1 i Traced.Method2 ?
Edycja: Jakie byłoby rozwiązanie, jeśli mogę nieznacznie zmienić metodę Call?
c#
reflection
aop
Czeladnik
źródło
źródło
Odpowiedzi:
C # nie jest językiem zorientowanym na AOP. Ma kilka funkcji AOP i możesz emulować inne, ale tworzenie AOP w C # jest bolesne.
Szukałem sposobów na zrobienie dokładnie tego, co chciałeś, i nie znalazłem łatwego sposobu, aby to zrobić.
Jak rozumiem, oto, co chcesz zrobić:
Aby to zrobić, masz dwie główne opcje
Dziedzicz swoją klasę z MarshalByRefObject lub ContextBoundObject i zdefiniuj atrybut, który dziedziczy po IMessageSink. Ten artykuł zawiera dobry przykład. Musisz jednak wziąć pod uwagę, że przy użyciu MarshalByRefObject wydajność spadnie jak diabli, i mam na myśli to, mówię o 10-krotnej utracie wydajności, więc dobrze się zastanów, zanim spróbujesz.
Inną opcją jest bezpośrednie wstrzyknięcie kodu. W środowisku uruchomieniowym, co oznacza, że będziesz musiał użyć odbicia, aby "przeczytać" każdą klasę, pobrać jej atrybuty i wstrzyknąć odpowiednie wywołanie (i myślę, że nie możesz użyć metody Reflection.Emit, tak jak myślę Reflection.Emit nie nie pozwalają na wstawienie nowego kodu do już istniejącej metody). W czasie projektowania będzie to oznaczało stworzenie rozszerzenia do kompilatora CLR, którego szczerze mówiąc nie mam pojęcia, jak to się robi.
Ostatnią opcją jest użycie frameworka IoC . Może nie jest to idealne rozwiązanie, ponieważ większość frameworków IoC działa poprzez definiowanie punktów wejścia, które pozwalają na podpięcie metod, ale w zależności od tego, co chcesz osiągnąć, może to być uczciwe przybliżenie.
źródło
Reflection.Emit
. To jest podejście wybrane przez Spring.NET . WymagałobyTraced
to jednak włączonych metod wirtualnych i nie nadaje się do użycia bez jakiegoś kontenera IOC, więc rozumiem, dlaczego tej opcji nie ma na Twojej liście.Najprostszym sposobem na osiągnięcie tego jest prawdopodobnie użycie PostSharp . Wprowadza kod do twoich metod na podstawie atrybutów, które do niego stosujesz. Pozwala ci robić dokładnie to, co chcesz.
Inną opcją jest użycie interfejsu API profilowania do wstrzyknięcia kodu wewnątrz metody, ale jest to naprawdę trudne.
źródło
Jeśli napiszesz klasę - nazwij ją Tracing - która implementuje interfejs IDisposable, możesz opakować wszystkie treści metody w
W klasie Tracing można obsługiwać logikę śladów w konstruktorze / metodzie Dispose, odpowiednio w klasie Tracing, aby śledzić wprowadzanie i zamykanie metod. Takie, że:
źródło
Możesz to osiągnąć dzięki funkcji przechwytywania kontenera DI, takiego jak Castle Windsor . Rzeczywiście, możliwe jest skonfigurowanie kontenera w taki sposób, że wszystkie klasy, które mają metodę ozdobioną określonym atrybutem, zostałyby przechwycone.
W odniesieniu do punktu 3, OP poprosił o rozwiązanie bez struktury AOP. W poniższej odpowiedzi założyłem, że to, czego należy unikać, to Aspect, JointPoint, PointCut, itp. Zgodnie z dokumentacją przechwytywania z CastleWindsor , żadne z nich nie jest wymagane do wykonania tego, o co proszono.
Skonfiguruj ogólną rejestrację przechwytywacza na podstawie obecności atrybutu:
Dodaj utworzoną IContributeComponentModelConstruction do kontenera
I możesz robić, co chcesz, w samym przechwytywaczu
Dodaj atrybut rejestrowania do metody, aby rejestrować
Zauważ, że pewna obsługa atrybutu będzie wymagana, jeśli tylko jakaś metoda klasy będzie musiała zostać przechwycona. Domyślnie przechwycone zostaną wszystkie metody publiczne.
źródło
Jeśli chcesz śledzić swoje metody bez ograniczeń (bez adaptacji kodu, bez AOP Framework, bez duplikatu kodu), powiem ci, potrzebujesz trochę magii ...
Poważnie, zdecydowałem się na zaimplementowanie AOP Framework działającego w czasie wykonywania.
Możesz znaleźć tutaj: NConcern .NET AOP Framework
Postanowiłem stworzyć ten AOP Framework, aby odpowiedzieć na tego rodzaju potrzeby. jest to prosta biblioteka, bardzo lekka. Możesz zobaczyć przykład loggera na stronie głównej.
Jeśli nie chcesz używać zespołu innej firmy, możesz przeglądać kod źródłowy (open source) i skopiować oba pliki Aspect.Directory.cs i Aspect.Directory.Entry.cs, aby dostosować je do własnych potrzeb. Te klasy pozwalają na podmianę metod w czasie wykonywania. Chciałbym tylko prosić o przestrzeganie licencji.
Mam nadzieję, że znajdziesz to, czego potrzebujesz lub przekonasz się do ostatecznego wykorzystania AOP Framework.
źródło
Spójrz na to - dość ciężkie rzeczy .. http://msdn.microsoft.com/en-us/magazine/cc164165.aspx
Essential .net - don box zawierał rozdział o tym, czego potrzebujesz, o nazwie Przechwytywanie. Część z nich znalazłem tutaj (przepraszam za kolory czcionek - miałem wtedy ciemny motyw ...) http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html
źródło
Znalazłem inny sposób, który może być łatwiejszy ...
Zadeklaruj metodę InvokeMethod
Następnie definiuję swoje metody w ten sposób
Teraz mogę sprawdzić w czasie wykonywania bez wstrzykiwania zależności ...
Brak problemów na stronie :)
Miejmy nadzieję, że zgodzisz się, że jest to mniejsza waga niż AOP Framework lub wywodząca się z MarshalByRefObject lub przy użyciu klas zdalnych lub proxy.
źródło
Najpierw musisz zmodyfikować swoją klasę, aby zaimplementować interfejs (zamiast implementować MarshalByRefObject).
Następnie potrzebujesz ogólnego obiektu opakowującego opartego na RealProxy, aby udekorować dowolny interfejs, aby umożliwić przechwycenie dowolnego wywołania dekorowanego obiektu.
Teraz jesteśmy gotowi do przechwytywania wywołań metod Method1 i Method2 z ITraced
źródło
W programie CodePlex można użyć CInject Framework open source . Możesz napisać minimalny kod, aby utworzyć wtryskiwacz i szybko przechwycić dowolny kod za pomocą CInject. Dodatkowo, ponieważ jest to Open Source, możesz to również rozszerzyć.
Możesz też wykonać kroki wymienione w tym artykule na temat przechwytywania wywołań metod przy użyciu języka IL i utworzyć własny przechwytywacz przy użyciu klas Reflection.Emit w języku C #.
źródło
Nie znam rozwiązania, ale moje podejście wyglądałoby następująco.
Udekoruj klasę (lub jej metody) atrybutem niestandardowym. W innym miejscu programu pozwól funkcji inicjalizującej odzwierciedlać wszystkie typy, odczytaj metody ozdobione atrybutami i wstrzyknij kod IL do metody. W rzeczywistości może być bardziej praktyczne zastąpienie metody przez wywołanie kodu pośredniczącego
LogStart
, a następnie rzeczywistą metodęLogEnd
. Ponadto nie wiem, czy można zmienić metody za pomocą odbicia, więc może być bardziej praktyczne zastąpienie całego typu.źródło
Możesz potencjalnie użyć wzorca dekoratora GOF i „ozdobić” wszystkie klasy, które wymagają śledzenia.
Prawdopodobnie jest to naprawdę praktyczne tylko w przypadku kontenera IOC (ale jako wskazówka wcześniej możesz rozważyć przechwycenie metody, jeśli zamierzasz podążać ścieżką IOC).
źródło
musisz zgłosić błąd Ayende, aby uzyskać odpowiedź, jak to zrobił: http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx
źródło
AOP jest koniecznością do implementacji czystego kodu, jednak jeśli chcesz otoczyć blok w języku C #, metody ogólne są stosunkowo łatwiejsze w użyciu. (z intelli sense i silnie wpisanym kodem) Z pewnością NIE może być alternatywą dla AOP.
Chociaż PostSHarp ma małe problemy z błędami (nie czuję się pewnie podczas używania w produkcji), jest to dobra rzecz.
Ogólna klasa opakowania,
użycie mogłoby wyglądać tak (oczywiście z intelli sense)
źródło
źródło
Najlepsze, co możesz zrobić przed wydaniem C # 6 z wydaniem „nameof”, to użycie powolnych wyrażeń StackTrace i linq.
Np. Za taką metodę
Taka linia może pojawić się w Twoim pliku dziennika
Oto realizacja:
źródło