Pregunta

Para una clase determinada, me gustaría tener una funcionalidad de seguimiento, es decir.Me gustaría registrar cada llamada al método (firma del método y valores de parámetros reales) y cada salida del método (solo la firma del método).

¿Cómo logro esto suponiendo que:

  • No quiero usar las bibliotecas AOP de terceros para C#,
  • No quiero agregar código duplicado a todos los métodos que quiero rastrear,
  • No quiero cambiar la API pública de la clase; los usuarios de la clase deberían poder llamar a todos los métodos exactamente de la misma manera.

Para hacer la pregunta más concreta, supongamos que hay 3 clases:

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

¿Cómo invoco? Registrador.LogStart y Registrador.LogEnd por cada llamada a Método 1 y Método 2 sin modificar el Llamador.Llamar método y sin agregar las llamadas explícitamente a Método.trazado1 y Trazado.Método2?

Editar:¿Cuál sería la solución si me permitieran cambiar ligeramente el método de llamada?

¿Fue útil?

Solución

C# no es un lenguaje orientado a AOP.Tiene algunas características de AOP y puedes emular otras, pero hacer AOP con C# es complicado.

Busqué formas de hacer exactamente lo que querías hacer y no encontré una manera fácil de hacerlo.

Según tengo entendido, esto es lo que quieres hacer:

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

y para hacerlo tienes dos opciones principales

  1. Herede su clase de MarshalByRefObject o ContextBoundObject y defina un atributo que herede de IMessageSink. Este artículo tiene un buen ejemplo.Sin embargo, debes considerar que al usar un MarshalByRefObject el rendimiento disminuirá muchísimo, y lo digo en serio, estoy hablando de una pérdida de rendimiento 10 veces mayor, así que piénsalo detenidamente antes de intentarlo.

  2. La otra opción es inyectar código directamente.En tiempo de ejecución, lo que significa que tendrás que usar la reflexión para "leer" cada clase, obtener sus atributos e inyectar la llamada apropiada (y de hecho, creo que no podrías usar el método Reflection.Emit como creo que lo haría Reflection.Emit). No le permite insertar código nuevo dentro de un método ya existente).En el momento del diseño, esto significará crear una extensión para el compilador CLR que, sinceramente, no tengo idea de cómo se hace.

La última opción es utilizar un marco de la COI.Tal vez no sea la solución perfecta, ya que la mayoría de los marcos de IoC funcionan definiendo puntos de entrada que permiten conectar los métodos pero, dependiendo de lo que desee lograr, podría ser una aproximación justa.

Otros consejos

La forma más sencilla de lograrlo es probablemente utilizar PostSharp.Inyecta código dentro de sus métodos en función de los atributos que le aplica.Te permite hacer exactamente lo que quieres.

Otra opción es utilizar el API de creación de perfiles inyectar código dentro del método, pero eso es realmente complicado.

Si escribe una clase (llámela Tracing) que implemente la interfaz IDisposable, podría envolver todos los cuerpos de los métodos en un

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

En la clase Tracing, puede manejar la lógica de los seguimientos en el método constructor/Dispose, respectivamente, en la clase Tracing para realizar un seguimiento de la entrada y salida de los métodos.Tal 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 ...]
            }
        }
    }

Podrías lograrlo con Interceptación característica de un contenedor DI como Castillo de Windsor.De hecho, es posible configurar el contenedor de tal manera que todas las clases que tengan un método decorado con un atributo específico sean interceptadas.

Con respecto al punto 3, OP solicitó una solución sin marco AOP.Supuse en la siguiente respuesta que lo que se debía evitar eran Aspect, JointPoint, PointCut, etc.De acuerdo a Documentación de interceptación de CastleWindsor, ninguno de ellos es necesario para lograr lo que se pide.

Configurar el registro genérico de un Interceptor, en función de la presencia de un 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;
    }
}

Agregue el IContributeComponentModelConstruction creado al contenedor

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

Y puedes hacer lo que quieras en el propio interceptor.

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

Agregue el atributo de registro a su método para iniciar sesión

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

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

Tenga en cuenta que se requerirá cierto manejo del atributo si solo es necesario interceptar algún método de una clase.De forma predeterminada, se interceptarán todos los métodos públicos.

Echa un vistazo a esto: algo bastante pesado.http://msdn.microsoft.com/en-us/magazine/cc164165.aspx

Essential .net: don box tenía un capítulo sobre lo que necesita llamado Intercepción.Saqué algo de esto aquí (Perdón por los colores de fuente, tenía un tema oscuro en ese entonces...)http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html

He encontrado una manera diferente que puede ser más fácil...

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

Luego defino mis métodos así

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

Ahora puedo realizar la verificación en tiempo de ejecución sin la inyección de dependencia...

No hay trampas en el sitio :)

Con suerte, estará de acuerdo en que esto tiene menos peso que un marco AOP o que se deriva de MarshalByRefObject o que utiliza clases de comunicación remota o proxy.

Primero debe modificar su clase para implementar una interfaz (en lugar de implementar MarshalByRefObject).

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

A continuación, necesita un objeto contenedor genérico basado en RealProxy para decorar cualquier interfaz y permitir interceptar cualquier llamada al 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);
    }
}

Ahora estamos listos para interceptar llamadas al Método1 y al Método2 de ITraced

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

Si desea rastrear sus métodos sin limitaciones (sin adaptación de código, sin marco AOP, sin código duplicado), déjeme decirle que necesita algo de magia...

En serio, lo resolví para implementar un Framework AOP que funcione en tiempo de ejecución.

Puedes encontrar aquí: Marco NConcern .NET AOP

Decidí crear este Framework AOP para dar respuesta a este tipo de necesidades.Es una biblioteca sencilla y muy ligera.Puedes ver un ejemplo de registrador en la página de inicio.

Si no desea utilizar un ensamblado de terceros, puede explorar el código fuente (código abierto) y copiar ambos archivos. Aspecto.Directorio.cs y Aspecto.Directorio.Entrada.cs para adaptarlo a sus deseos.Estas clases permiten reemplazar sus métodos en tiempo de ejecución.Sólo les pediría que respeten la licencia.

Espero que encuentres lo que necesitas o te convenza de que finalmente uses un Framework AOP.

Puedes utilizar un marco de código abierto. Inyectar en CodePlex.Puede escribir un código mínimo para crear un inyector y lograr que intercepte cualquier código rápidamente con CInject.Además, dado que se trata de código abierto, también puedes ampliarlo.

O puede seguir los pasos mencionados en este artículo en Intercepción de llamadas a métodos usando IL y cree su propio interceptor usando las clases Reflection.Emit en C#.

No conozco una solución pero mi enfoque sería el siguiente.

Decora la clase (o sus métodos) con un atributo personalizado.En algún otro lugar del programa, deje que una función de inicialización refleje todos los tipos, lea los métodos decorados con los atributos e inyecte algo de código IL en el método.En realidad, podría ser más práctico reemplazar el método por un stub que llama LogStart, el método real y luego LogEnd.Además, no sé si se pueden cambiar los métodos mediante la reflexión, por lo que podría resultar más práctico reemplazar todo el tipo.

Potencialmente, podría utilizar el patrón decorador GOF y "decorar" todas las clases que necesiten seguimiento.

Probablemente solo sea realmente práctico con un contenedor IOC (pero, como se señaló anteriormente, es posible que desee considerar la intercepción de métodos si va a seguir el camino del IOC).

necesitas molestar a Ayende para que te responda cómo lo hizo:http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx

AOP es imprescindible para la implementación de código limpio; sin embargo, si desea rodear un bloque en C#, los métodos genéricos tienen un uso relativamente más fácil.(con sentido inteligente y código fuertemente tipado) Ciertamente, NO puede ser una alternativa para AOP.

A pesar de PublicarSharp Tengo pequeños problemas con errores (no me siento seguro de usarlo en producción), es algo bueno.

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

El uso podría ser así (con sentido inteligente, por supuesto)

var exception = Wrapper.LogOnError(() =>
{
  MessageBox.Show("test");
  throw new Exception("test");
}, "Hata");
  1. Escriba su propia biblioteca AOP.
  2. Utilice la reflexión para generar un proxy de registro sobre sus instancias (no estoy seguro de poder hacerlo sin cambiar alguna parte de su código existente).
  3. Vuelva a escribir el ensamblaje e inyecte su código de registro (básicamente el mismo que 1).
  4. Aloje el CLR y agregue el registro en este nivel (creo que esta es la solución más difícil de implementar, aunque no estoy seguro de tener los enlaces necesarios en el CLR).

Lo mejor que puede hacer antes de C# 6 con el lanzamiento de 'nameof' es utilizar expresiones lentas de StackTrace y linq.

P.ej.para tal método

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

        //do your stuff
    }

Esta línea puede aparecer en su archivo de registro.

Method 'MyMethod' parameters age: 20 name: Mike

Aquí está la implementación:

    //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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top