Frage

Bei einer gegebenen Klasse, ich möchte tracing-Funktionalität, d.h.Ich möchte melden Sie jedem Aufruf der Methode (Signatur der Methode und der tatsächlichen Parameterwerte) und jede Methode exit (nur die Signatur der Methode).

Wie kann ich dies erreichen, vorausgesetzt, dass:

  • Ich will nicht von jedem 3rd-party - AOP-Bibliotheken für C#,
  • Ich will nicht hinzufügen, um den doppelten code zu allen Methoden, die ich verfolgen wollen,
  • Ich will nicht zu ändern, die öffentliche API der Klasse - Benutzer der Klasse sollten in der Lage zu rufen Sie all die Methoden, die in genau der gleichen Weise.

Um die Frage konkreter nehmen wir an, es gibt 3 Klassen:

 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 Method1 und Method2 ohne änderung der Anrufer.Call Methode und ohne die Anrufe ausdrücklich zu Verfolgt.Method1 und Verfolgt.Method2?

Edit:Was wäre die Lösung, wenn ich erlaubt bin zu leicht ändern die Call-Methode?

War es hilfreich?

Lösung

C# ist nicht eine AOP-Sprache.Es hat einige AOP-Funktionen und Sie können emulieren ein paar andere, aber machen AOP mit C# ist schmerzhaft.

Ich schaute nach Möglichkeiten, genau das zu tun, was Sie tun wollte, und fand ich keine einfache Möglichkeit, es zu tun.

Wie ich es verstehe, das ist, was Sie tun möchten:

[Log()]
public void Method1(String name, Int32 value);

und um das zu tun, Sie haben zwei Hauptoptionen

  1. Erben, die Klasse von MarshalByRefObject oder ContextBoundObject und definieren Sie ein Attribut, das erbt von IMessageSink. In diesem Artikel hat ein gutes Beispiel.Sie haben zu prüfen, nichtsdestotrotz, dass die Verwendung von MarshalByRefObject die Leistung wird nach unten gehen wie die Hölle, und ich meine es, ich spreche von 10x Leistung verloren, so denken Sie sorgfältig, bevor Sie versuchen, dass.

  2. Die andere Möglichkeit ist, code zu injizieren direkt.In der Laufzeit, das heißt, Sie werden haben zu verwenden die Reflexion zu "Lesen" jede Klasse, seine Attribute und injizieren Sie den entsprechenden Anruf (und ich denke, dass Sie nicht nutzen konnten die Reflexion.Emittieren-Methode, wie ich denke, Reflexion.Emittieren würde nicht zulassen, dass Sie zu legen Sie neue code in eine bereits bestehende Methode).Zur design-Zeit bedeutet dies die Schaffung einer Erweiterung der CLR-compiler, die ich habe ehrlich gesagt keine Ahnung, wie es gemacht wird.

Die Letzte option ist die Verwendung eines IoC-framework.Vielleicht ist es nicht die perfekte Lösung, da die meisten IoC-frameworks arbeiten durch die Einstiegspunkte definieren, die es erlauben, Methoden gestoßen zu werden, aber je nachdem, was Sie wollen, zu erreichen, das wäre ein fairer aproximation.

Andere Tipps

Der einfachste Weg das zu erreichen, ist wahrscheinlich zu verwenden PostSharp.Es injiziert code in Ihre Methoden, basierend auf den Parametern, die Sie darauf anwenden.Es ermöglicht Ihnen, genau das zu tun, was Sie wollen.

Eine weitere option ist die Verwendung der profiling API einschleusen von code innerhalb der Methode, aber das ist wirklich hardcore.

Wenn Sie eine Klasse schreiben - nennen wir es-Tracing -, die die IDisposable-Schnittstelle implementiert, Sie könnte wrap-Methode alle Organe in einem

Using( Tracing tracing = new Tracing() ){ ... method body ...}

Im Tracing-Klasse könnten Sie den Griff der Logik der Spuren im Konstruktor/Dispose-Methode, beziehungsweise, in der Ablaufverfolgung Klasse zu halten track der Eingabe und verlassen der Methoden.So dass:

    public class Traced 
    {
        public void Method1(String name, Int32 value) {
            using(Tracing tracer = new Tracing()) 
            {
                [... method body ...]
            }
        }

        public void Method2(Object object) { 
            using(Tracing tracer = new Tracing())
            {
                [... method body ...]
            }
        }
    }

Sie könnten es erreichen, mit Interception Funktion einer DI-container wie Schloss Windsor.In der Tat ist es möglich, konfigurieren Sie den container in einer solchen Weise, dass alle Klassen, die haben ein Verfahren eingerichtet, die von einem bestimmten Attribut würden Sie abgefangen werden.

Zu Punkt #3, OP fragte nach einer Lösung ohne AOP-framework.Ich nahm in der folgenden Antwort, dass das, was vermieden werden sollte, waren Aspekt, JointPoint, PointCut, etc.Nach Abfangen-Dokumentation von CastleWindsor, keiner von denen, die erforderlich sind, um zu erreichen, was gefragt ist.

Konfigurieren von generischen Registrierung einen Interceptor, basierend auf dem Vorhandensein eines Attributs:

public class RequireInterception : IContributeComponentModelConstruction
{
    public void ProcessModel(IKernel kernel, ComponentModel model)
    {
        if (HasAMethodDecoratedByLoggingAttribute(model.Implementation))
        {
            model.Interceptors.Add(new InterceptorReference(typeof(ConsoleLoggingInterceptor)));
            model.Interceptors.Add(new InterceptorReference(typeof(NLogInterceptor)));
        }
    }

    private bool HasAMethodDecoratedByLoggingAttribute(Type implementation)
    {
        foreach (var memberInfo in implementation.GetMembers())
        {
            var attribute = memberInfo.GetCustomAttributes(typeof(LogAttribute)).FirstOrDefault() as LogAttribute;
            if (attribute != null)
            {
                return true;
            }
        }

        return false;
    }
}

Fügen Sie die erstellte IContributeComponentModelConstruction zu container

container.Kernel.ComponentModelBuilder.AddContributor(new RequireInterception());

Und Sie können tun, was Sie wollen, in der interceptor selbst

public class ConsoleLoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.Writeline("Log before executing");
        invocation.Proceed();
        Console.Writeline("Log after executing");
    }
}

Fügen Sie die logging-Attribut, um Ihre Methode zu melden

 public class Traced 
 {
     [Log]
     public void Method1(String name, Int32 value) { }

     [Log]
     public void Method2(Object object) { }
 }

Beachten Sie, dass einige der Handhabung des Attributs benötigt werden, wenn nur eine Methode einer Klasse muss abgefangen werden.Standardmäßig werden alle öffentlichen Methoden abgefangen werden.

Werfen Sie einen Blick auf diese Ziemlich hartes Zeug..http://msdn.microsoft.com/en-us/magazine/cc164165.aspx

Unerlässlich .net - don box hatte ein Kapitel auf, was Sie brauchen, genannt Abfangen.Ich kratzte einige es hier (Sorry über die schriftart, die Farben - ich hatte ein dunkles Thema zurück, dann...) http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html

Ich fand einen anderen Weg, was kann einfacher sein...

Deklarieren Sie eine Methode InvokeMethod

[WebMethod]
    public object InvokeMethod(string methodName, Dictionary<string, object> methodArguments)
    {
        try
        {
            string lowerMethodName = '_' + methodName.ToLowerInvariant();
            List<object> tempParams = new List<object>();
            foreach (MethodInfo methodInfo in serviceMethods.Where(methodInfo => methodInfo.Name.ToLowerInvariant() == lowerMethodName))
            {
                ParameterInfo[] parameters = methodInfo.GetParameters();
                if (parameters.Length != methodArguments.Count()) continue;
                else foreach (ParameterInfo parameter in parameters)
                    {
                        object argument = null;
                        if (methodArguments.TryGetValue(parameter.Name, out argument))
                        {
                            if (parameter.ParameterType.IsValueType)
                            {
                                System.ComponentModel.TypeConverter tc = System.ComponentModel.TypeDescriptor.GetConverter(parameter.ParameterType);
                                argument = tc.ConvertFrom(argument);

                            }
                            tempParams.Insert(parameter.Position, argument);

                        }
                        else goto ContinueLoop;
                    }

                foreach (object attribute in methodInfo.GetCustomAttributes(true))
                {
                    if (attribute is YourAttributeClass)
                    {
                        RequiresPermissionAttribute attrib = attribute as YourAttributeClass;
                        YourAttributeClass.YourMethod();//Mine throws an ex
                    }
                }

                return methodInfo.Invoke(this, tempParams.ToArray());
            ContinueLoop:
                continue;
            }
            return null;
        }
        catch
        {
            throw;
        }
    }

Ich habe dann definieren Sie meinen Methoden, wie Sie so

[WebMethod]
    public void BroadcastMessage(string Message)
    {
        //MessageBus.GetInstance().SendAll("<span class='system'>Web Service Broadcast: <b>" + Message + "</b></span>");
        //return;
        InvokeMethod("BroadcastMessage", new Dictionary<string, object>() { {"Message", Message} });
    }

    [RequiresPermission("editUser")]
    void _BroadcastMessage(string Message)
    {
        MessageBus.GetInstance().SendAll("<span class='system'>Web Service Broadcast: <b>" + Message + "</b></span>");
        return;
    }

Jetzt kann ich die Prüfung zur Laufzeit ohne die dependency injection,...

Keine Einschränkungen in der Website :)

Ich hoffe, Sie werden mir Zustimmen, dass dies weniger Gewicht, dann ein AOP Framework oder die Ableitung von MarshalByRefObject abgeleitet oder über remoting oder proxy-Klassen.

Erste, müssen Sie ändern Ihre Klasse eine Schnittstelle implementieren (und nicht als Umsetzung der MarshalByRefObject).

interface ITraced {
    void Method1();
    void Method2()
}
class Traced: ITraced { .... }

Als Nächstes müssen Sie eine generische wrapper-Objekt basierend auf RealProxy zu dekorieren jede Schnittstelle zu ermöglichen abfangen eines Anrufs auf das dekorierte Objekt.

class MethodLogInterceptor: RealProxy
{
     public MethodLogInterceptor(Type interfaceType, object decorated) 
         : base(interfaceType)
     {
          _decorated = decorated;
     }

    public override IMessage Invoke(IMessage msg)
    {
        var methodCall = msg as IMethodCallMessage;
        var methodInfo = methodCall.MethodBase;
        Console.WriteLine("Precall " + methodInfo.Name);
        var result = methodInfo.Invoke(_decorated, methodCall.InArgs);
        Console.WriteLine("Postcall " + methodInfo.Name);

        return new ReturnMessage(result, null, 0,
            methodCall.LogicalCallContext, methodCall);
    }
}

Jetzt sind wir bereit zum abfangen von aufrufen zu Method1 und Method2 von ITraced

 public class Caller 
 {
     public static void Call() 
     {
         ITraced traced = (ITraced)new MethodLogInterceptor(typeof(ITraced), new Traced()).GetTransparentProxy();
         traced.Method1();
         traced.Method2(); 
     }
 }

Wenn Sie verfolgen möchten, nachdem Sie Ihre Methoden-ohne Beschränkung (keine code-Anpassung, keine AOP-Framework, keine doppelten code), lassen Sie mich Ihnen sagen, müssen Sie einige Magische...

Im ernst, ich entschloss mich, es zu implementieren, ein AOP-Framework arbeitet bei Laufzeit.

Sie finden hier : NConcern .NET AOP Framework

Ich habe beschlossen, diesen AOP-Framework zu geben, eine Antwort auf diese Art von Bedürfnissen.es ist eine einfache Bibliothek, die sehr leicht ist.Sie können sehen, ein Beispiel der logger in home page.

Wenn Sie nicht wollen, zu verwenden eine 3rd-party-Montage, können Sie die Quellcodes (open source) und kopieren die beiden Dateien Aspekt.Verzeichnis.cs und Aspekt.Verzeichnis.Eintrag.cs angepasst wie Ihre Wünsche.Thesen Klassen erlauben Sie zu ersetzen Ihre Methoden zur Laufzeit.Ich würde nur bitten, Sie zu respektieren die Lizenz.

Ich hoffe, Sie finden, was Sie brauchen, oder Sie zu überzeugen, um letztendlich ein AOP-Framework.

Sie können open-source-framework CInject auf CodePlex.Sie können schreiben, minimal-code zum erstellen eines Injektors und bekommen es abzufangen code schnell mit CInject.Plus, da diese Open Source ist, können Sie verlängern diese als gut.

Oder Sie können Folgen die Schritte erwähnt, die sich auf diesen Artikel auf Abfangen von Methodenaufrufen mit IL und erstellen Sie Ihren eigenen interceptor mit Reflexion.Emittieren-Klassen in C#.

Ich weiß nicht, eine Lösung, aber mein Ansatz wäre wie folgt.

Dekorieren Sie die Klasse (oder Methoden) mit einem benutzerdefinierten Attribut.Irgendwo im Programm haben, lassen Sie eine Initialisierungs-Funktion spiegeln alle Typen Lesen die Methoden, geschmückt mit den Attributen und Spritzen einige IL-code in die Methode.Es könnte tatsächlich sein, mehr praktische ersetzen die Methode, durch die ein stub, der Anrufe LogStart, die tatsächliche Methode, und dann LogEnd.Außerdem, ich weiß nicht, wenn Sie ändern, können Methoden mit Reflexion so könnte es praktisch sein, zu ersetzen die ganze geben.

Sie könnten die Verwendung der GOF Dekorator-Muster und "verzieren" alle Klassen, müssen tracing.

Es ist wahrscheinlich nur wirklich praktisch ist, mit einem IOC-container (aber wie Zeiger aus früheren möchten Sie vielleicht erwägen Methode abfangen, wenn Sie unten gehen die IOC-Pfad).

Sie müssen Fehler Ayende für eine Antwort, wie er es getan hat:http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx

AOP ist ein muss für sauberen code implementieren, aber wenn Sie wollen, um einen block in C# generische Methoden relativ einfacher Nutzung.(mit intelli-sense und stark typisierte code) Sicherlich, es kann NICHT sein, eine alternative für die AOP.

Obwohl PostSHarp wenig buggy Fragen (ich fühle mich nicht zuversichtlich für die Verwendung in der Produktion), ist es eine gute Sachen.

Generische wrapper-Klasse,

public class Wrapper
{
    public static Exception TryCatch(Action actionToWrap, Action<Exception> exceptionHandler = null)
    {
        Exception retval = null;
        try
        {
            actionToWrap();
        }
        catch (Exception exception)
        {
            retval = exception;
            if (exceptionHandler != null)
            {
                exceptionHandler(retval);
            }
        }
        return retval;
    }

    public static Exception LogOnError(Action actionToWrap, string errorMessage = "", Action<Exception> afterExceptionHandled = null)
    {
        return Wrapper.TryCatch(actionToWrap, (e) =>
        {
            if (afterExceptionHandled != null)
            {
                afterExceptionHandled(e);
            }
        });
    }
}

die Nutzung könnte so sein (mit intelli-sense natürlich)

var exception = Wrapper.LogOnError(() =>
{
  MessageBox.Show("test");
  throw new Exception("test");
}, "Hata");
  1. Schreiben Sie Ihre eigene AOP-Bibliothek.
  2. Verwenden reflektion, um zu generieren Sie einen proxy-Protokollierung über Ihre Instanzen (nicht sicher, ob Sie es tun können, ohne ändern einige Teil der vorhandene code).
  3. Schreiben Sie die Montage und Spritzen Sie Ihre logging-code (im Grunde das gleiche wie 1).
  4. Host der CLR und fügen Sie die Protokollierung auf dieser Ebene (ich denke, dies ist das schwierigste Lösung zu implementieren, nicht sicher, ob Sie über die erforderlichen Haken, der in der CLR obwohl).

Das beste, was Sie tun können, bevor C# 6 'nameof' veröffentlicht hat, ist zu langsam StackTrace-und linq-Ausdrücke.

E. g.für diese Methode

    public void MyMethod(int age, string name)
    {
        log.DebugTrace(() => age, () => name);

        //do your stuff
    }

Eine solche Linie kann werden produziert in Ihrem log-Datei

Method 'MyMethod' parameters age: 20 name: Mike

Hier ist die Implementierung:

    //TODO: replace with 'nameof' in C# 6
    public static void DebugTrace(this ILog log, params Expression<Func<object>>[] args)
    {
        #if DEBUG

        var method = (new StackTrace()).GetFrame(1).GetMethod();

        var parameters = new List<string>();

        foreach(var arg in args)
        {
            MemberExpression memberExpression = null;
            if (arg.Body is MemberExpression)
                memberExpression = (MemberExpression)arg.Body;

            if (arg.Body is UnaryExpression && ((UnaryExpression)arg.Body).Operand is MemberExpression)
                memberExpression = (MemberExpression)((UnaryExpression)arg.Body).Operand;

            parameters.Add(memberExpression == null ? "NA" : memberExpression.Member.Name + ": " + arg.Compile().DynamicInvoke().ToString());
        }

        log.Debug(string.Format("Method '{0}' parameters {1}", method.Name, string.Join(" ", parameters)));

        #endif
    }
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top