Question

Pour une classe donnée, j'aimerais avoir une fonctionnalité de traçage, c'est-à-direJe voudrais enregistrer chaque appel de méthode (signature de méthode et valeurs réelles des paramètres) et chaque sortie de méthode (juste la signature de méthode).

Comment puis-je y parvenir en supposant que :

  • Je ne veux pas utiliser de bibliothèques AOP tiers pour C #,
  • Je ne veux pas ajouter de code en double à toutes les méthodes que je souhaite tracer,
  • Je ne veux pas changer l'API publique de la classe - les utilisateurs de la classe devraient pouvoir appeler toutes les méthodes exactement de la même manière.

Pour rendre la question plus concrète, supposons qu'il existe 3 classes :

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

Comment puis-je invoquer Enregistreur.LogStart et Enregistreur.LogEnd pour chaque appel à Méthode 1 et Méthode 2 sans modifier le Appelant.Appel méthode et sans ajouter explicitement les appels à Traced.Method1 et Traced.Method2?

Modifier:Quelle serait la solution si je suis autorisé à modifier légèrement la méthode Call ?

Était-ce utile?

La solution

C# n'est pas un langage orienté AOP.Il possède certaines fonctionnalités AOP et vous pouvez en émuler d’autres, mais créer AOP avec C# est pénible.

J’ai cherché des moyens de faire exactement ce que vous vouliez faire et je n’ai trouvé aucun moyen facile de le faire.

Si je comprends bien, voici ce que vous voulez faire :

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

et pour ce faire, vous avez deux options principales

  1. Héritez de votre classe de MarshalByRefObject ou ContextBoundObject et définissez un attribut qui hérite de IMessageSink. Cet article a un bon exemple.Vous devez néanmoins considérer qu'en utilisant un MarshalByRefObject, les performances diminueront comme un enfer, et je le pense vraiment, je parle d'une performance 10x perdue, alors réfléchissez bien avant d'essayer cela.

  2. L'autre option consiste à injecter du code directement.Au moment de l'exécution, ce qui signifie que vous devrez utiliser la réflexion pour "lire" chaque classe, obtenir ses attributs et injecter l'appel approprié (et d'ailleurs, je pense que vous ne pouvez pas utiliser la méthode Reflection.Emit comme je pense que Reflection.Emit le ferait ne vous permet pas d'insérer un nouveau code dans une méthode déjà existante).Au moment de la conception, cela signifiera créer une extension du compilateur CLR dont je n'ai honnêtement aucune idée de la façon dont cela est fait.

La dernière option consiste à utiliser un Cadre IoC.Ce n'est peut-être pas la solution parfaite, car la plupart des frameworks IoC fonctionnent en définissant des points d'entrée qui permettent d'accrocher des méthodes, mais, en fonction de ce que vous souhaitez réaliser, cela peut être une approximation juste.

Autres conseils

Le moyen le plus simple d'y parvenir est probablement d'utiliser PostSharp.Il injecte du code dans vos méthodes en fonction des attributs que vous lui appliquez.Cela vous permet de faire exactement ce que vous voulez.

Une autre option consiste à utiliser le API de profilage pour injecter du code à l'intérieur de la méthode, mais c'est vraiment hardcore.

Si vous écrivez une classe - appelez-la Tracing - qui implémente l'interface IDisposable, vous pouvez envelopper tous les corps de méthode dans un

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

Dans la classe Tracing, vous pouvez gérer la logique des traces dans la méthode constructeur/Dispose, respectivement, dans la classe Tracing pour garder une trace de l'entrée et de la sortie des méthodes.Tel que:

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

Vous pourriez y parvenir avec Interception fonctionnalité d'un conteneur DI tel que Château de Windsor.En effet, il est possible de configurer le conteneur de telle sorte que toutes les classes possédant une méthode décorée par un attribut spécifique soient interceptées.

Concernant le point n°3, OP a demandé une solution sans framework AOP.J'ai supposé dans la réponse suivante que ce qui devait être évité était Aspect, JointPoint, PointCut, etc.Selon Documentation d'interception de CastleWindsor, aucun de ceux-ci n’est requis pour accomplir ce qui est demandé.

Configurez l'enregistrement générique d'un intercepteur, en fonction de la présence d'un attribut :

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

Ajouter le IContributeComponentModelConstruction créé au conteneur

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

Et vous pouvez faire ce que vous voulez dans l'intercepteur lui-même

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

Ajoutez l'attribut de journalisation à votre méthode pour vous connecter

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

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

Notez qu'une certaine gestion de l'attribut sera nécessaire si seule une méthode d'une classe doit être interceptée.Par défaut, toutes les méthodes publiques seront interceptées.

Jetez un oeil à ceci - Des trucs assez lourds..http://msdn.microsoft.com/en-us/magazine/cc164165.aspx

Essential .net - don box contenait un chapitre sur ce dont vous avez besoin appelé Interception.J'en ai gratté une partie ici (Désolé pour les couleurs de police - j'avais un thème sombre à l'époque...)http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html

J'ai trouvé une autre manière qui peut être plus simple...

Déclarer une méthode 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;
        }
    }

Je définis ensuite mes méthodes ainsi

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

Maintenant, je peux avoir la vérification au moment de l'exécution sans l'injection de dépendances...

Aucun piège sur le site :)

J'espère que vous conviendrez que cela pèse moins qu'un framework AOP ou dérive de MarshalByRefObject ou utilise des classes distantes ou proxy.

Vous devez d’abord modifier votre classe pour implémenter une interface (plutôt que d’implémenter MarshalByRefObject).

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

Ensuite, vous avez besoin d'un objet wrapper générique basé sur RealProxy pour décorer n'importe quelle interface afin de permettre d'intercepter tout appel à l'objet décoré.

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

Nous sommes maintenant prêts à intercepter les appels vers Method1 et Method2 d'ITraced

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

Si vous souhaitez tracer vos méthodes sans limitation (pas d'adaptation de code, pas de Framework AOP, pas de code en double), laissez-moi vous dire qu'il vous faut un peu de magie...

Sérieusement, je l'ai résolu pour implémenter un framework AOP fonctionnant au moment de l'exécution.

Vous pouvez trouver ici : Cadre NConcer .NET AOP

J'ai décidé de créer ce Framework AOP pour répondre à ce genre de besoins.c'est une bibliothèque simple très légère.Vous pouvez voir un exemple d'enregistreur sur la page d'accueil.

Si vous ne souhaitez pas utiliser un assembly tiers, vous pouvez parcourir la source du code (open source) et copier les deux fichiers Aspect.Directory.cs et Aspect.Directory.Entry.cs à adapter selon vos souhaits.Ces classes permettent de remplacer vos méthodes au moment de l'exécution.Je vous demanderais juste de respecter la licence.

J'espère que vous trouverez ce dont vous avez besoin ou que vous serez convaincu d'utiliser enfin un Framework AOP.

Vous pouvez utiliser un framework open source CInjecter sur CodePlex.Vous pouvez écrire un minimum de code pour créer un injecteur et lui faire intercepter rapidement n'importe quel code avec CInject.De plus, comme il s'agit d'Open Source, vous pouvez également l'étendre.

Ou vous pouvez suivre les étapes mentionnées dans cet article sur Interception des appels de méthode à l'aide d'IL et créez votre propre intercepteur à l'aide des classes Reflection.Emit en C#.

Je ne connais pas de solution mais mon approche serait la suivante.

Décorez la classe (ou ses méthodes) avec un attribut personnalisé.Quelque part ailleurs dans le programme, laissez une fonction d'initialisation refléter tous les types, lisez les méthodes décorées avec les attributs et injectez du code IL dans la méthode.Il serait peut-être effectivement plus pratique de remplacer la méthode par un stub qui appelle LogStart, la méthode réelle et ensuite LogEnd.De plus, je ne sais pas si vous pouvez modifier les méthodes en utilisant la réflexion, il pourrait donc être plus pratique de remplacer le type entier.

Vous pouvez potentiellement utiliser le modèle GOF Decorator et « décorer » toutes les classes nécessitant un traçage.

Ce n'est probablement vraiment pratique qu'avec un conteneur IOC (mais comme indiqué plus tôt, vous voudrez peut-être envisager l'interception de méthode si vous envisagez d'emprunter le chemin IOC).

vous devez interroger Ayende pour savoir comment il l'a fait :http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx

AOP est indispensable pour l'implémentation de code propre, mais si vous souhaitez entourer un bloc en C#, les méthodes génériques sont relativement plus faciles à utiliser.(avec intelli sense et code fortement typé) Certes, cela ne peut PAS être une alternative à l'AOP.

Bien que PostSHarp J'ai des petits problèmes de buggy (je ne me sens pas en confiance pour l'utiliser en production), c'est une bonne chose.

Classe wrapper générique,

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'utilisation pourrait être comme ça (avec le sens intelli bien sûr)

var exception = Wrapper.LogOnError(() =>
{
  MessageBox.Show("test");
  throw new Exception("test");
}, "Hata");
  1. Écrivez votre propre bibliothèque AOP.
  2. Utilisez la réflexion pour générer un proxy de journalisation sur vos instances (je ne sais pas si vous pouvez le faire sans modifier une partie de votre code existant).
  3. Réécrivez l'assembly et injectez votre code de journalisation (essentiellement le même que 1).
  4. Hébergez le CLR et ajoutez la journalisation à ce niveau (je pense que c'est la solution la plus difficile à mettre en œuvre, mais je ne sais pas si vous disposez des hooks requis dans le CLR).

Le mieux que vous puissiez faire avant la sortie de C# 6 avec 'nameof' est d'utiliser des expressions StackTrace et Linq lentes.

Par exemple.pour une telle méthode

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

        //do your stuff
    }

Une telle ligne peut être produite dans votre fichier journal

Method 'MyMethod' parameters age: 20 name: Mike

Voici la mise en œuvre :

    //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
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top