Domanda

Per una determinata classe mi piacerebbe avere funzionalità di tracciamento, ad es.Vorrei registrare ogni chiamata al metodo (firma del metodo e valori dei parametri effettivi) e ogni uscita del metodo (solo la firma del metodo).

Come posso ottenere questo risultato presupponendo che:

  • Non voglio usare librerie AOP di terze parti per C#,
  • Non voglio aggiungere codice duplicato a tutti i metodi che voglio tracciare,
  • Non voglio modificare l'API pubblica della classe: gli utenti della classe dovrebbero essere in grado di chiamare tutti i metodi esattamente allo stesso modo.

Per rendere la domanda più concreta supponiamo che ci siano 3 classi:

 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);
 }

Come invoco Logger.LogStart E Logger.LogEnd per ogni chiamata a Metodo1 E Metodo2 senza modificare il Chiamante.Chiama metodo e senza aggiungere esplicitamente le chiamate a Metodo.Tracciato1 E Metodo.Tracciato2?

Modificare:Quale sarebbe la soluzione se mi fosse consentito modificare leggermente il metodo di chiamata?

È stato utile?

Soluzione

C# non è un linguaggio orientato all'AOP.Ha alcune funzionalità AOP e puoi emularne altre, ma realizzare AOP con C# è doloroso.

Ho cercato modi per fare esattamente quello che volevi fare e non ho trovato un modo semplice per farlo.

A quanto ho capito, questo è quello che vuoi fare:

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

e per farlo hai due opzioni principali

  1. Eredita la tua classe da MarshalByRefObject o ContextBoundObject e definisci un attributo che eredita da IMessageSink. Questo articolo ha un buon esempio.Devi considerare tuttavia che usando un MarshalByRefObject le prestazioni diminuiranno da morire, e lo dico sul serio, sto parlando di una prestazione persa di 10 volte, quindi pensaci attentamente prima di provarlo.

  2. L'altra opzione è inserire direttamente il codice.In runtime, ciò significa che dovrai usare la riflessione per "leggere" ogni classe, ottenere i suoi attributi e iniettare la chiamata appropriata (e del resto penso che non potresti usare il metodo Reflection.Emit come penso che Reflection.Emit farebbe (non consente di inserire nuovo codice all'interno di un metodo già esistente).In fase di progettazione ciò significherà creare un'estensione al compilatore CLR che onestamente non ho idea di come sia fatta.

L'ultima opzione è usare un file Quadro dell'IoC.Forse non è la soluzione perfetta poiché la maggior parte dei framework IoC funziona definendo punti di ingresso che consentono l'aggancio dei metodi ma, a seconda di ciò che si desidera ottenere, potrebbe essere una buona approssimazione.

Altri suggerimenti

Il modo più semplice per raggiungere questo obiettivo è probabilmente utilizzare PostSharp.Inserisce il codice all'interno dei tuoi metodi in base agli attributi che gli applichi.Ti permette di fare esattamente quello che vuoi.

Un'altra opzione è usare il file API di profilazione per iniettare codice all'interno del metodo, ma è davvero hardcore.

Se scrivi una classe, chiamala Tracing, che implementa l'interfaccia IDisposable, potresti racchiudere tutti i corpi del metodo in un

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

Nella classe Tracing è possibile gestire la logica delle tracce nel costruttore/metodo Dispose, rispettivamente, nella classe Tracing per tenere traccia dell'entrata e dell'uscita dai metodi.Tale che:

    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 ...]
            }
        }
    }

Potresti ottenerlo con Intercettazione caratteristica di un contenitore DI come Castello Windsor.È infatti possibile configurare il contenitore in modo tale che tutte le classi che hanno un metodo decorato da uno specifico attributo vengano intercettate.

Per quanto riguarda il punto 3, OP ha chiesto una soluzione senza quadro AOP.Nella risposta seguente ho presupposto che ciò che dovrebbe essere evitato fossero Aspect, JointPoint, PointCut, ecc.Secondo Documentazione dell'intercettazione di CastleWindsor, nessuno di questi è tenuto a realizzare ciò che viene richiesto.

Configura la registrazione generica di un Interceptor, in base alla presenza di un attributo:

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;
    }
}

Aggiungi l'oggetto IContributeComponentModelConstruction creato al contenitore

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

E puoi fare quello che vuoi nell'intercettore stesso

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

Aggiungi l'attributo logging al tuo metodo per accedere

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

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

Si noti che sarà necessaria una certa gestione dell'attributo se è necessario intercettare solo alcuni metodi di una classe.Per impostazione predefinita, tutti i metodi pubblici verranno intercettati.

Dai un'occhiata a questo: roba piuttosto pesante..http://msdn.microsoft.com/en-us/magazine/cc164165.aspx

Essential .net - don box aveva un capitolo su ciò di cui hai bisogno chiamato Intercettazione.Ne ho raschiato un po' qui (mi spiace per i colori dei caratteri, allora avevo un tema scuro...)http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html

Ho trovato un modo diverso che forse è più semplice...

Dichiarare un metodo 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;
        }
    }

Quindi definisco i miei metodi in questo modo

[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;
    }

Ora posso avere il controllo in fase di esecuzione senza l'inserimento delle dipendenze...

Nessun trucco nel sito :)

Si spera che sarete d'accordo sul fatto che questo ha meno peso di un framework AOP o derivante da MarshalByRefObject o che utilizza classi remote o proxy.

Per prima cosa devi modificare la tua classe per implementare un'interfaccia (piuttosto che implementare MarshalByRefObject).

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

Successivamente è necessario un oggetto wrapper generico basato su RealProxy per decorare qualsiasi interfaccia per consentire di intercettare qualsiasi chiamata all'oggetto decorato.

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);
    }
}

Ora siamo pronti per intercettare le chiamate al Metodo1 e Metodo2 di ITraced

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

Se vuoi seguire i tuoi metodi senza limitazioni (nessun adattamento del codice, nessun framework AOP, nessun codice duplicato), lascia che te lo dica, hai bisogno di un po' di magia...

Scherzi a parte, l'ho risolto per implementare un framework AOP funzionante in fase di runtime.

Puoi trovare qui: NConcern .NET AOP Framework

Ho deciso di creare questo AOP Framework per dare una risposta a questo tipo di esigenze.è una libreria semplice molto leggera.Puoi vedere un esempio di logger nella home page.

Se non desideri utilizzare un assembly di terze parti, puoi sfogliare il codice sorgente (open source) e copiare entrambi i file Aspetto.Directory.cs E Aspetto.Directory.Entry.cs adattato secondo i tuoi desideri.Queste classi consentono di sostituire i metodi in fase di esecuzione.Vorrei solo chiedervi di rispettare la licenza.

Spero che troverai ciò di cui hai bisogno o che ti convincerai a utilizzare finalmente un Framework AOP.

È possibile utilizzare il framework open source CInject su CodePlex.Puoi scrivere un codice minimo per creare un iniettore e fargli intercettare rapidamente qualsiasi codice con CInject.Inoltre, poiché questo è Open Source, puoi estendere anche questo.

Oppure puoi seguire i passaggi menzionati in questo articolo su Metodo di intercettazione delle chiamate tramite IL e crea il tuo interceptor utilizzando le classi Reflection.Emit in C#.

Non conosco una soluzione, ma il mio approccio sarebbe il seguente.

Decora la classe (o i suoi metodi) con un attributo personalizzato.Da qualche altra parte nel programma, lasciare che una funzione di inizializzazione rifletta tutti i tipi, legga i metodi decorati con gli attributi e inietti del codice IL nel metodo.In realtà potrebbe essere più pratico sostituire il metodo da uno stub che chiama LogStart, il metodo attuale e poi LogEnd.Inoltre, non so se puoi modificare i metodi utilizzando la riflessione, quindi potrebbe essere più pratico sostituire l'intero tipo.

Potresti potenzialmente utilizzare il modello Decoratore GOF e "decorare" tutte le classi che necessitano di tracciamento.

Probabilmente è davvero pratico solo con un contenitore IOC (ma come sottolineato in precedenza potresti prendere in considerazione l'intercettazione del metodo se hai intenzione di seguire il percorso IOC).

devi infastidire Ayende per una risposta su come ha fatto:http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx

AOP è un must per l'implementazione di codice pulito, tuttavia se si desidera racchiudere un blocco in C#, i metodi generici hanno un utilizzo relativamente più semplice.(con intelli sense e codice fortemente tipizzato) Certamente NON può essere un'alternativa ad AOP.

Sebbene PostSHarp ho piccoli problemi di bug (non mi sento sicuro di usarlo in produzione), è una buona roba.

Classe wrapper generica,

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);
            }
        });
    }
}

l'utilizzo potrebbe essere così (con Intelli Sense ovviamente)

var exception = Wrapper.LogOnError(() =>
{
  MessageBox.Show("test");
  throw new Exception("test");
}, "Hata");
  1. Scrivi la tua libreria AOP.
  2. Utilizza la riflessione per generare un proxy di registrazione sulle tue istanze (non sono sicuro di poterlo fare senza modificare parte del codice esistente).
  3. Riscrivi l'assembly e inserisci il codice di registrazione (sostanzialmente uguale a 1).
  4. Ospita il CLR e aggiungi la registrazione a questo livello (penso che questa sia la soluzione più difficile da implementare, non sono sicuro di avere gli hook richiesti nel CLR).

La cosa migliore che puoi fare prima di C# 6 con il rilascio di 'nameof' è utilizzare StackTrace lento e le espressioni linq.

Per esempio.per tale metodo

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

        //do your stuff
    }

Tale riga può essere prodotta nel file di registro

Method 'MyMethod' parameters age: 20 name: Mike

Ecco l'implementazione:

    //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
    }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top