Pergunta

Para uma determinada classe, gostaria de ter funcionalidade de rastreamento, ou seja,Gostaria de registrar todas as chamadas de método (assinatura do método e valores reais dos parâmetros) e todas as saídas do método (apenas a assinatura do método).

Como faço isso assumindo que:

  • Não quero usar nenhuma biblioteca de AOP de terceiros para C#,
  • Não quero adicionar código duplicado a todos os métodos que desejo rastrear,
  • Não quero alterar a API pública da classe - os usuários da classe devem poder chamar todos os métodos exatamente da mesma maneira.

Para tornar a questão mais concreta, vamos supor que existam 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);
 }

Como faço para invocar Logger.LogStart e Logger.LogEnd para cada chamada para Método 1 e Método 2 sem modificar o Chamador.Chamada método e sem adicionar as chamadas explicitamente ao Rastreado.Method1 e Rastreado.Method2?

Editar:Qual seria a solução se eu pudesse alterar ligeiramente o método Call?

Foi útil?

Solução

C# não é uma linguagem orientada a AOP.Ele possui alguns recursos de AOP e você pode emular outros, mas criar AOP com C# é doloroso.

Procurei maneiras de fazer exatamente o que você queria e não encontrei nenhuma maneira fácil de fazer isso.

Pelo que entendi, é isso que você quer fazer:

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

e para fazer isso você tem duas opções principais

  1. Herde sua classe de MarshalByRefObject ou ContextBoundObject e defina um atributo que herda de IMessageSink. Este artigo tem um bom exemplo.Você deve considerar, no entanto, que usando um MarshalByRefObject o desempenho cairá como o inferno, e estou falando sério, estou falando de uma perda de desempenho de 10x, então pense com cuidado antes de tentar isso.

  2. A outra opção é injetar código diretamente.Em tempo de execução, o que significa que você terá que usar reflexão para "ler" cada classe, obter seus atributos e injetar a chamada apropriada (e nesse caso, acho que você não poderia usar o método Reflection.Emit como acho que Reflection.Emit faria não permite inserir novo código dentro de um método já existente).Em tempo de design, isso significará criar uma extensão para o compilador CLR que, honestamente, não tenho ideia de como isso é feito.

A opção final é usar um Estrutura COI.Talvez não seja a solução perfeita, já que a maioria dos frameworks IoC funcionam definindo pontos de entrada que permitem conectar métodos, mas, dependendo do que você deseja alcançar, essa pode ser uma aproximação justa.

Outras dicas

A maneira mais simples de conseguir isso é provavelmente usar PostSharp.Ele injeta código dentro de seus métodos com base nos atributos que você aplica a ele.Ele permite que você faça exatamente o que deseja.

Outra opção é usar o API de criação de perfil injetar código dentro do método, mas isso é realmente difícil.

Se você escrever uma classe - chame-a de Tracing - que implemente a interface IDisposable, você poderá agrupar todos os corpos dos métodos em um

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

Na classe Tracing você poderia manipular a lógica dos rastreamentos no método construtor/Dispose, respectivamente, na classe Tracing para acompanhar a entrada e saída dos métodos.De tal modo 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 ...]
            }
        }
    }

Você poderia conseguir isso com Interceptação recurso de um contêiner DI, como Castelo Windsor.Na verdade, é possível configurar o container de forma que todas as classes que possuam um método decorado por um atributo específico sejam interceptadas.

Em relação ao ponto 3, o OP solicitou uma solução sem estrutura AOP.Presumi na resposta a seguir que o que deveria ser evitado eram Aspect, JointPoint, PointCut, etc.De acordo com Documentação de interceptação do CastleWindsor, nenhum deles é necessário para realizar o que é solicitado.

Configure o cadastro genérico de um Interceptor, baseado na presença de um atributo:

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

Adicione o IContributeComponentModelConstruction criado ao contêiner

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

E você pode fazer o que quiser no próprio interceptor

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

Adicione o atributo logging ao seu método para registrar

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

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

Observe que será necessária alguma manipulação do atributo se apenas algum método de uma classe precisar ser interceptado.Por padrão, todos os métodos públicos serão interceptados.

Dê uma olhada nisso - Coisas bem pesadas.http://msdn.microsoft.com/en-us/magazine/cc164165.aspx

Essential .net - don box tinha um capítulo sobre o que você precisa chamado Interceptação.Raspei um pouco aqui (desculpe pelas cores da fonte - eu tinha um tema sombrio naquela época...)http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html

Eu encontrei uma maneira diferente que pode ser mais fácil ...

Declarar um método 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;
        }
    }

Eu então defino meus métodos assim

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

Agora posso fazer a verificação em tempo de execução sem a injeção de dependência ...

Sem pegadinhas no site :)

Esperamos que você concorde que isso tem menos peso do que um Framework AOP ou derivado de MarshalByRefObject ou usando classes de comunicação remota ou proxy.

Primeiro você precisa modificar sua classe para implementar uma interface (em vez de implementar MarshalByRefObject).

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

Em seguida, você precisa de um objeto wrapper genérico baseado em RealProxy para decorar qualquer interface e permitir a interceptação de qualquer chamada ao objeto decorado.

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

Agora estamos prontos para interceptar chamadas para Método1 e Método2 do ITraced

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

Se você deseja rastrear seus métodos sem limitação (sem adaptação de código, sem estrutura AOP, sem código duplicado), deixe-me dizer, você precisa de um pouco de mágica...

Sério, resolvi implementar um Framework AOP funcionando em tempo de execução.

Você pode encontrar aqui: Estrutura AOP NConcern .NET

Decidi criar este Framework AOP para dar resposta a este tipo de necessidades.é uma biblioteca simples e muito leve.Você pode ver um exemplo de logger na página inicial.

Se não quiser usar um assembly de terceiros, você pode navegar no código-fonte (código aberto) e copiar os dois arquivos Aspecto.Diretório.cs e Aspecto.Directory.Entry.cs para adaptar conforme seus desejos.Essas classes permitem substituir seus métodos em tempo de execução.Eu apenas pediria que você respeitasse a licença.

Espero que você encontre o que precisa ou que o convença a finalmente usar um Framework AOP.

Você pode usar uma estrutura de código aberto CInjetar no CodePlex.Você pode escrever código mínimo para criar um injetor e fazer com que ele intercepte qualquer código rapidamente com CInject.Além disso, como se trata de código aberto, você também pode estendê-lo.

Ou você pode seguir as etapas mencionadas neste artigo em Interceptando chamadas de método usando IL e crie seu próprio interceptor usando classes Reflection.Emit em C#.

Não conheço uma solução, mas minha abordagem seria a seguinte.

Decore a classe (ou seus métodos) com um atributo personalizado.Em algum outro lugar do programa, deixe uma função de inicialização refletir todos os tipos, ler os métodos decorados com os atributos e injetar algum código IL no método.Na verdade, pode ser mais prático substituir o método por um stub que chama LogStart, o método real e então LogEnd.Além disso, não sei se você pode alterar os métodos usando reflexão, por isso pode ser mais prático substituir o tipo inteiro.

Você poderia usar o padrão GOF Decorator e 'decorar' todas as classes que precisam de rastreamento.

Provavelmente só é realmente prático com um contêiner IOC (mas, como mencionado anteriormente, você pode querer considerar a interceptação de método se quiser seguir o caminho do IOC).

você precisa incomodar Ayende para obter uma resposta sobre como ele fez isso:http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx

AOP é essencial para implementação de código limpo; no entanto, se você deseja cercar um bloco em C#, os métodos genéricos têm uso relativamente mais fácil.(com sentido inteligente e código fortemente digitado) Certamente, NÃO pode ser uma alternativa para AOP.

Embora PostSharp tenho alguns problemas de bugs (não me sinto confiante para usar na produção), é uma coisa boa.

Classe de wrapper genérica,

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

o uso poderia ser assim (com sentido inteligente, é claro)

var exception = Wrapper.LogOnError(() =>
{
  MessageBox.Show("test");
  throw new Exception("test");
}, "Hata");
  1. Escreva sua própria biblioteca AOP.
  2. Use a reflexão para gerar um proxy de registro em suas instâncias (não tenho certeza se você pode fazer isso sem alterar alguma parte do seu código existente).
  3. Reescreva o assembly e injete seu código de log (basicamente igual a 1).
  4. Hospede o CLR e adicione log neste nível (acho que esta é a solução mais difícil de implementar, mas não tenho certeza se você tem os ganchos necessários no CLR).

O melhor que você pode fazer antes do C # 6 com 'nameof' lançado é usar StackTrace lento e expressões linq.

Por exemplo.para tal método

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

        //do your stuff
    }

Essa linha pode ser produzida em seu arquivo de log

Method 'MyMethod' parameters age: 20 name: Mike

Aqui está a implementação:

    //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
    }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top