Für eine bestimmte Klasse möchte ich eine Ablaufverfolgungsfunktion haben, dh ich möchte jeden Methodenaufruf (Methodensignatur und tatsächliche Parameterwerte) und jeden Methodenexit (nur die Methodensignatur) protokollieren.
Wie erreiche ich dies unter der Annahme, dass:
- Ich möchte keine AOP-Bibliotheken von Drittanbietern für C # verwenden.
- Ich möchte nicht allen Methoden, die ich verfolgen möchte, doppelten Code hinzufügen.
- Ich möchte die öffentliche API der Klasse nicht ändern - Benutzer der Klasse sollten in der Lage sein, alle Methoden auf genau dieselbe Weise aufzurufen.
Um die Frage konkreter zu machen, nehmen wir an, dass es drei Klassen gibt:
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);
}
Wie rufe ich Logger.LogStart und Logger.LogEnd für jeden Aufruf von Methode1 und Methode2 auf, ohne die Caller.Call- Methode zu ändern und ohne die Aufrufe explizit zu Traced.Method1 und Traced.Method2 hinzuzufügen ?
Bearbeiten: Was wäre die Lösung, wenn ich die Call-Methode leicht ändern darf?
c#
reflection
aop
Geselle
quelle
quelle
Antworten:
C # ist keine AOP-orientierte Sprache. Es hat einige AOP-Funktionen und Sie können einige andere emulieren, aber AOP mit C # zu erstellen ist schmerzhaft.
Ich habe nach Wegen gesucht, genau das zu tun, was Sie wollten, und ich habe keinen einfachen Weg gefunden, dies zu tun.
So wie ich es verstehe, möchten Sie Folgendes tun:
und dazu haben Sie zwei Hauptoptionen
Erben Sie Ihre Klasse von MarshalByRefObject oder ContextBoundObject und definieren Sie ein Attribut, das von IMessageSink erbt. Dieser Artikel hat ein gutes Beispiel. Sie müssen jedoch berücksichtigen, dass bei Verwendung eines MarshalByRefObject die Leistung höllisch sinken wird, und ich meine es so, ich spreche von einem 10-fachen Leistungsverlust. Überlegen Sie also sorgfältig, bevor Sie dies versuchen.
Die andere Möglichkeit besteht darin, Code direkt einzufügen. Zur Laufzeit bedeutet dies, dass Sie Reflection verwenden müssen, um jede Klasse zu "lesen", ihre Attribute abzurufen und den entsprechenden Aufruf einzufügen (und ich denke, Sie können die Reflection.Emit-Methode nicht so verwenden, wie Reflection.Emit es meiner Meinung nach nicht tun würde Sie können keinen neuen Code in eine bereits vorhandene Methode einfügen. Zur Entwurfszeit bedeutet dies, eine Erweiterung des CLR-Compilers zu erstellen, von der ich ehrlich gesagt keine Ahnung habe, wie es gemacht wird.
Die letzte Option ist die Verwendung eines IoC-Frameworks . Vielleicht ist es nicht die perfekte Lösung, da die meisten IoC-Frameworks Einstiegspunkte definieren, mit denen Methoden verknüpft werden können. Je nachdem, was Sie erreichen möchten, kann dies jedoch eine faire Annäherung sein.
quelle
Reflection.Emit
. Dies ist der von Spring.NET gewählte Ansatz . Dies würde jedoch virtuelle Methoden erfordernTraced
und ist für die Verwendung ohne einen IOC-Container nicht wirklich geeignet. Daher verstehe ich, warum diese Option nicht in Ihrer Liste enthalten ist.Der einfachste Weg, dies zu erreichen, ist wahrscheinlich die Verwendung von PostSharp . Es fügt Code in Ihre Methoden ein, basierend auf den Attributen, die Sie darauf anwenden. Damit können Sie genau das tun, was Sie wollen.
Eine andere Möglichkeit besteht darin, die Profiling-API zu verwenden, um Code in die Methode einzufügen, aber das ist wirklich Hardcore.
quelle
Wenn Sie eine Klasse schreiben - nennen Sie sie Tracing -, die die IDisposable-Schnittstelle implementiert, können Sie alle Methodenkörper in a einschließen
In der Tracing-Klasse können Sie die Logik der Traces in der Konstruktor- / Dispose-Methode bzw. in der Tracing-Klasse behandeln, um den Ein- und Ausgang der Methoden zu verfolgen. So dass:
quelle
Sie können dies mit der Interception- Funktion eines DI-Containers wie Castle Windsor erreichen . In der Tat ist es möglich, den Container so zu konfigurieren, dass alle Klassen, deren Methode durch ein bestimmtes Attribut gekennzeichnet ist, abgefangen werden.
In Bezug auf Punkt 3 bat OP um eine Lösung ohne AOP-Framework. In der folgenden Antwort ging ich davon aus, dass Aspect, JointPoint, PointCut usw. vermieden werden sollten. Laut Interception-Dokumentation von CastleWindsor ist keines davon erforderlich, um das zu erreichen, was gefragt wird.
Konfigurieren Sie die generische Registrierung eines Interceptors basierend auf dem Vorhandensein eines Attributs:
Fügen Sie die erstellte IContributeComponentModelConstruction zum Container hinzu
Und Sie können im Abfangjäger selbst tun, was Sie wollen
Fügen Sie das Protokollierungsattribut Ihrer zu protokollierenden Methode hinzu
Beachten Sie, dass eine gewisse Behandlung des Attributs erforderlich ist, wenn nur eine Methode einer Klasse abgefangen werden muss. Standardmäßig werden alle öffentlichen Methoden abgefangen.
quelle
Wenn Sie Ihre Methoden ohne Einschränkung verfolgen möchten (keine Code-Anpassung, kein AOP-Framework, kein doppelter Code), lassen Sie mich Ihnen sagen, Sie brauchen etwas Magie ...
Im Ernst, ich habe es beschlossen, ein AOP-Framework zu implementieren, das zur Laufzeit funktioniert.
Sie finden hier: NConcern .NET AOP Framework
Ich habe beschlossen, dieses AOP-Framework zu erstellen, um auf diese Art von Anforderungen zu reagieren. Es ist eine einfache Bibliothek, die sehr leicht ist. Sie können ein Beispiel für einen Logger auf der Startseite sehen.
Wenn Sie keine Assembly eines Drittanbieters verwenden möchten, können Sie die Codequelle (Open Source) durchsuchen und beide Dateien Aspect.Directory.cs und Aspect.Directory.Entry.cs nach Ihren Wünschen anpassen. Mit diesen Klassen können Sie Ihre Methoden zur Laufzeit ersetzen. Ich möchte Sie nur bitten, die Lizenz zu respektieren.
Ich hoffe, Sie finden, was Sie brauchen, oder überzeugen Sie, endlich ein AOP-Framework zu verwenden.
quelle
Schauen Sie sich das an - Ziemlich schweres Zeug. Http://msdn.microsoft.com/en-us/magazine/cc164165.aspx
Essential .net - don box hatte ein Kapitel über das, was Sie brauchen, namens Interception. Ich habe hier einiges davon abgekratzt (Entschuldigung für die Schriftfarben - ich hatte damals ein dunkles Thema ...) http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html
quelle
Ich habe einen anderen Weg gefunden, der vielleicht einfacher ist ...
Deklarieren Sie eine Methode InvokeMethod
Ich definiere dann meine Methoden so
Jetzt kann ich die Prüfung zur Laufzeit ohne die Abhängigkeitsinjektion durchführen lassen ...
Keine Fallstricke vor Ort :)
Hoffentlich stimmen Sie zu, dass dies weniger Gewicht hat als ein AOP-Framework oder von MarshalByRefObject abgeleitet ist oder Remoting- oder Proxy-Klassen verwendet.
quelle
Zuerst müssen Sie Ihre Klasse ändern, um eine Schnittstelle zu implementieren (anstatt das MarshalByRefObject zu implementieren).
Als nächstes benötigen Sie ein generisches Wrapper-Objekt, das auf RealProxy basiert, um eine beliebige Schnittstelle zu dekorieren und das Abfangen eines Aufrufs des dekorierten Objekts zu ermöglichen.
Jetzt können wir Aufrufe von Methode1 und Methode2 von ITraced abfangen
quelle
Sie können Open Source Framework CInject auf CodePlex verwenden. Sie können minimalen Code schreiben, um einen Injector zu erstellen und ihn dazu zu bringen, jeden Code mit CInject schnell abzufangen. Da dies Open Source ist, können Sie dies auch erweitern.
Sie können auch die in diesem Artikel beschriebenen Schritte zum Abfangen von Methodenaufrufen mit IL ausführen und mithilfe von Reflection.Emit-Klassen in C # einen eigenen Interceptor erstellen.
quelle
Ich kenne keine Lösung, aber mein Ansatz wäre wie folgt.
Dekorieren Sie die Klasse (oder ihre Methoden) mit einem benutzerdefinierten Attribut. Lassen Sie an einer anderen Stelle im Programm eine Initialisierungsfunktion alle Typen widerspiegeln, lesen Sie die mit den Attributen dekorierten Methoden und fügen Sie IL-Code in die Methode ein. Es könnte tatsächlich praktischer sein , die Methode durch einen Stub zu ersetzen
LogStart
, der die eigentliche Methode aufruft und dannLogEnd
. Außerdem weiß ich nicht, ob Sie Methoden mithilfe von Reflektion ändern können, sodass es möglicherweise praktischer ist, den gesamten Typ zu ersetzen.quelle
Sie können möglicherweise das GOF-Dekorationsmuster verwenden und alle Klassen, die nachverfolgt werden müssen, "dekorieren".
Es ist wahrscheinlich nur mit einem IOC-Container wirklich praktisch (aber als Hinweis darauf sollten Sie das Abfangen von Methoden in Betracht ziehen, wenn Sie den IOC-Pfad entlang gehen).
quelle
Sie müssen Ayende nerven, um eine Antwort darauf zu erhalten, wie er es getan hat: http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx
quelle
AOP ist ein Muss für die Implementierung von sauberem Code. Wenn Sie jedoch einen Block in C # umgeben möchten, sind generische Methoden relativ einfach zu verwenden. (mit intelligentem Sinn und stark typisiertem Code) Natürlich kann es KEINE Alternative für AOP sein.
Obwohl PostSHarp kleine Buggy-Probleme hat (ich bin nicht sicher, ob ich es in der Produktion verwenden soll), ist es ein gutes Zeug.
Generische Wrapper-Klasse,
Die Verwendung könnte so sein (natürlich mit intelligentem Sinn)
quelle
quelle
Das Beste, was Sie vor C # 6 mit 'nameof' tun können, ist die Verwendung von langsamen StackTrace- und linq-Ausdrücken.
ZB für eine solche Methode
Eine solche Zeile wird möglicherweise in Ihrer Protokolldatei erstellt
Hier ist die Implementierung:
quelle