Ich möchte meiner C # -Anwendung Protokollierung oder Ablaufverfolgung hinzufügen, aber ich möchte nicht, dass der Aufwand für die Formatierung der Zeichenfolge oder das Berechnen von Werten protokolliert wird, wenn die Protokoll Ausführlichkeitsstufe so niedrig eingestellt ist, dass die Nachricht nicht protokolliert wird / p>
In C ++ können Sie den Präprozessor verwenden, um Makros zu definieren, die verhindern, dass Code überhaupt so ausgeführt wird:
%Vor%Verwendet wie folgt:
%Vor%Wie machst du das in C #?
Ich habe die Microsoft-Dokumentation gelesen, in der die Funktionen zum Verfolgen und Debuggen erläutert werden hier , und sie behaupten, dass die Verwendung von #undef DEBUG und #undef TRACE den gesamten Tracing- und Debugging-Code aus der erzeugten ausführbaren Datei entfernt, aber entfernt sie wirklich den gesamten Aufruf? Bedeutung, wenn ich
schreibe %Vor%wird es meine teure Funktion nicht aufrufen, wenn ich TRACE nicht definiere? Oder macht den Anruf, dann entscheidet, dass es nichts verfolgen wird?
Wie auch immer, selbst wenn es es entfernt, ist dies dem C ++ - Makro unterlegen, weil ich diesen großen hässlichen Aufruf nicht wie meinen einfachen VLOG () - Aufruf in C ++ aussehen lassen kann und immer noch Parameter auswerte, kann ich? Ich kann auch nicht den Overhead vermeiden, indem ich die Ausführlichkeit niedriger zur Laufzeit definiere, wie ich es in C ++ kann, oder?
Zwei dieser Antworten (Andrew Arnotts und Brians) haben einen Teil meiner Frage beantwortet. Das ConditionalAttribute, das auf die Trace- und Debug-Klassenmethoden angewendet wird, bewirkt, dass alle Aufrufe der Methoden entfernt werden, wenn TRACE oder DEBUG # undefiniert sind, einschließlich der teuren Parameterauswertung. Danke!
Für den zweiten Teil, ob Sie alle Aufrufe zur Laufzeit, nicht zur Kompilierzeit vollständig entfernen können, fand ich die Antwort im log4net fac . Wenn Sie zum Zeitpunkt des Starts eine Eigenschaft readonly festlegen, kompiliert die Laufzeitumgebung alle Aufrufe, die den Test nicht bestehen. Das lässt Sie nicht nach dem Start ändern, aber das ist in Ordnung, es ist besser, als sie zur Kompilierzeit zu entfernen.
Um eine Ihrer Fragen zu beantworten, werden alle Methodenaufrufe, die ausgewertet werden müssen, um Trace.WriteLine (oder seine Geschwister / Cousins) aufzurufen, nicht aufgerufen, wenn Trace.WriteLine kompiliert wird. Gehen Sie also vor und stellen Sie Ihre teuren Methodenaufrufe direkt als Parameter für den Trace-Aufruf ein, der zur Kompilierungszeit entfernt wird, wenn Sie das TRACE-Symbol nicht definieren.
Nun zu Ihrer anderen Frage bezüglich der Änderung Ihrer Ausführlichkeit zur Laufzeit. Der Trick dabei ist, dass Trace.WriteLine und ähnliche Methoden 'params object [] args' für ihre String-Formatierungsargumente verwenden. Nur wenn die Zeichenkette tatsächlich ausgegeben wird (wenn Ausführlichkeit ausreichend hoch eingestellt ist), ruft die Methode ToString für diese Objekte auf, um eine Zeichenkette daraus zu erhalten. Ein Trick, den ich oft spiele, besteht darin, Objekte und nicht vollständig zusammengesetzte Strings an diese Methoden zu übergeben und die String-Erstellung im ToString des Objekts zu belassen, an das ich mich begebe. und es gibt Ihnen die Freiheit, Ausführlichkeit zu ändern, ohne Ihre App neu zu kompilieren.
Eine Lösung, die für mich funktioniert hat, ist ein Singleton Klasse. Es kann Ihre Protokollierungsfunktionen verfügbar machen und Sie können sein Verhalten effizient steuern. Rufen wir die Klasse 'AppLogger' an. Sie ist ein Beispiel
%Vor%Beachten Sie, dass das Singleton-Zeug im obigen Beispiel weggelassen wurde. Es gibt viele gute Beispiele aus den Röhren. NOw das Interessante ist, wie Multi-Threading zu unterstützen. Ich habe es so gemacht: (abgekürzt für die Kürze, hahahaha)
%Vor%Auf diese Weise kann jeder Thread protokollieren und die Nachrichten werden auf dem ursprünglichen Erstellungs-Thread behandelt. Oder Sie könnten einen speziellen Thread nur für die Verarbeitung der Protokollausgabe erstellen.
ConditionalAttribute ist dein bester Freund. Der Anruf wird vollständig entfernt (so als wären Anruf-Sites # wenn), wenn #define nicht festgelegt ist.
EDIT: jemand hat dies in einen Kommentar (Danke!), aber erwähnenswert in der Hauptantwort Körper:
Alle Methoden der Trace-Klasse sind mit Conditional ("TRACE") versehen. Habe das nur mit einem Reflektor gesehen.
Was bedeutet Trace.Blah (... teuer ...) verschwindet vollständig, wenn TRACE nicht definiert ist.
Alle Informationen über Conditional (Trace) sind gut - aber ich nehme an, Ihre eigentliche Frage ist, dass Sie die Trace-Aufrufe in Ihrem Produktionscode belassen, aber (normalerweise) zur Laufzeit deaktivieren möchten, außer Sie haben ein Problem / p>
Wenn Sie TraceSource verwenden (was ich meiner Meinung nach tun sollte, anstatt Trace direkt aufzurufen, da es Ihnen zur Laufzeit eine detailliertere Kontrolle über die Ablaufverfolgung auf Komponentenebene gibt), können Sie Folgendes tun:
%Vor%Dies setzt voraus, dass Sie in der Lage sind, die Tracing-Parameter in einer anderen Funktion zu isolieren (d. h. sie hängen meistens von den Mitgliedern der aktuellen Klasse ab und nicht von teuren Operationen mit den Parametern der Funktion, in der sich dieser Code befindet).
Der Vorteil dieses Ansatzes besteht darin, dass der JITer funktional nach Bedarf kompiliert, wenn das "if" zu false führt, wird die Funktion nicht nur nicht aufgerufen - es wird auch nicht sein JITed. Der Nachteil ist (a) Sie haben das Wissen über die Trace-Ebene zwischen diesem Aufruf und der Funktion OutputExpensiveTraceInformation getrennt (wenn Sie zB den TraceEventType dort beispielsweise zu TraceEventType.Information ändern, wird es nicht funktionieren, weil Sie es nie tun werden) rufe es auch auf, wenn die TraceSource in diesem Beispiel nicht für die Verfolgung der Verbose-Ebene aktiviert ist) und (b) es mehr Code zum Schreiben gibt.
Dies ist ein Fall, in dem es scheint, dass ein C-ähnlicher Präprozessor helfen würde (da es z. B. sicherstellen könnte, dass der Parameter für ShouldTrace und den eventuellen TraceEvent-Aufruf gleich ist), aber ich verstehe, warum C # enthält das nicht.
Andrews Vorschlag, teure Operationen in den .ToString-Methoden der Objekte, die Sie an TraceEvent übergeben, zu isolieren, ist auch eine gute; In diesem Fall könnten Sie beispielsweise ein Objekt entwickeln, das nur für Trace verwendet wird, an das Sie die Objekte übergeben, für die Sie eine teure String-Repräsentation erstellen möchten, und diesen Code in der ToString-Methode des Trace-Objekts isolieren Parameterliste für den TraceEvent-Aufruf (was dazu führt, dass er ausgeführt wird, auch wenn TraceLevel zur Laufzeit nicht aktiviert ist).
Hoffe, das hilft.
Für Ihren Kommentar
"weil ich diesen großen hässlichen Aufruf nicht wie meinen einfachen VLOG () - Aufruf in C ++ aussehen lassen kann - Sie könnten eine using-Anweisung als Beispiel hinzufügen.
Wie ich weiß, werden Zeilen, die Trace enthalten, entfernt, wenn Sie das Trace-Symbol aufheben.
Ich bin mir nicht sicher, aber Sie können die Antwort selbst herausfinden.
Machen Sie eine wirklich teure Funktion (wie Thread.Sleep(10000)
) und starten Sie den Anruf. Wenn es sehr lange dauert, ruft es deine Funktion trotzdem auf.
(Sie können den Trace.WriteLineIf()
-Aufruf mit #if TRACE
und #endif
umbrechen und erneut für einen Basisvergleich testen.)
Er ruft den teuren Anruf auf, weil er möglicherweise gewünschte Nebenwirkungen hat.
Sie können Ihre teure Methode mit einem [Conditional ("TRACE")] oder [Conditional ("DEBUG")] -Attribut dekorieren. Die Methode wird nicht in die endgültige ausführbare Datei kompiliert, wenn die DEBUG- oder TRACE-Konstante nicht definiert ist, und auch keine Aufrufe zum Ausführen der teuren Methode.