如何在C#中拦截方法调用?

Jou*_*man 148 c# reflection aop

对于给定的类,我希望有跟踪功能,即我想记录每个方法调用(方法签名和实际参数值)和每个方法退出(只是方法签名).

我如何做到这一点假设:

  • 我不想为C#使用任何第三方AOP库,
  • 我不想将重复的代码添加到我想要跟踪的所有方法中,
  • 我不想更改类的公共API - 类的用户应该能够以完全相同的方式调用所有方法.

为了使问题更具体,让我们假设有3个类:

 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);
 }
Run Code Online (Sandbox Code Playgroud)

如何调用Logger.LogStartLogger.LogEnd每次调用方法1方法2,而无需修改Caller.Call方法,没有明确地加入调用Traced.Method1Traced.Method2

编辑:如果我允许稍微更改Call方法,会有什么解决方案?

Jor*_*oba 68

C#不是面向AOP的语言.它有一些AOP功能,你可以模仿其他一些,但用C#制作AOP是很痛苦的.

我想方设法做你想做的事情,我找不到简单的办法.

据我了解,这是你想要做的:

[Log()]
public void Method1(String name, Int32 value);
Run Code Online (Sandbox Code Playgroud)

为了做到这一点,你有两个主要选择

  1. 从MarshalByRefObject或ContextBoundObject继承您的类,并定义一个继承自IMessageSink的属性.这篇文章有一个很好的例子.你必须考虑使用MarshalByRefObject,性能会像地狱一样下降,我的意思是,我说的是性能损失了10倍,所以在尝试之前要仔细考虑.

  2. 另一种选择是直接注入代码.在运行时,意味着你将不得不使用反射"读取"每个类,获取其属性并注入适当的调用(为此我认为你不能使用Reflection.Emit方法,因为我认为Reflection.Emit不会不允许您在现有方法中插入新代码.在设计时,这将意味着创建一个CLR编译器的扩展,我真的不知道它是如何完成的.

最后一个选择是使用IoC框架.也许它不是完美的解决方案,因为大多数IoC框架通过定义允许方法挂钩的入口点来工作,但是,根据您想要实现的目标,这可能是一个公平的近似.

  • 换句话说,'ouch' (61认同)
  • 第三种方法是使用`Reflection.Emit`在运行时生成基于aop代理的继承.这是[Spring.NET选择的方法](http://www.springframework.net/doc-latest/reference/html/aop.html#aop-introduction-proxies).但是,这将需要`Traced`上的虚拟方法,并且在没有某种IOC容器的情况下不太适合使用,所以我理解为什么这个选项不在你的列表中. (3认同)
  • 我应该指出,如果您具有一流的函数,那么可以像对待其他任何变量一样对待函数,并且可以使用一个“方法挂钩”来实现他想要的功能。 (2认同)
  • 你的第二个选择基本上是"手写你需要的AOP框架的部分",这应该导致"哦等等也许我应该使用专门为解决我遇到的问题而不是下来的第三方选项-inveted,这里道路" (2认同)
  • @jorge 你能提供一些例子/链接来使用依赖注入/IoC 名气来实现这一点,比如 nInject (2认同)

Ant*_*bry 46

实现这一目标的最简单方法可能是使用PostSharp.它根据您应用于它的属性在方法中注入代码.它允许您完全按照自己的意愿行事.

另一种选择是使用分析API在方法中注入代码,但这确实是硬核.

  • 你也可以用ICorDebug注入东西,但这是超级邪恶的 (3认同)

Ste*_*een 10

如果你编写一个类 - 称之为Tracing - 实现IDisposable接口,你可以将所有方法体包装在一个

Using( Tracing tracing = new Tracing() ){ ... method body ...}
Run Code Online (Sandbox Code Playgroud)

在Tracing类中,您可以分别在Tracing类中处理构造函数/ Dispose方法中跟踪的逻辑,以跟踪方法的进入和退出.这样:

    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 ...]
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • 这与回答问题无关。 (4认同)

plo*_*g17 7

您可以使用诸如Castle Windsor之类的DI容器的拦截功能来实现它.实际上,可以以这样的方式配置容器,即每个具有由特定属性修饰的方法的类都将被截获.

关于第3点,OP要求提供没有AOP框架的解决方案.我在下面的回答中假设应该避免的是Aspect,JointPoint,PointCut等.根据CastleWindsor的拦截文档,没有一个是完成所要求的.

根据属性的存在配置拦截器的通用注册:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

将创建的IContributeComponentModelConstruction添加到容器中

container.Kernel.ComponentModelBuilder.AddContributor(new RequireInterception());
Run Code Online (Sandbox Code Playgroud)

你可以在拦截器本身做任何你想做的事情

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

将logging属性添加到要记录的方法中

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

     [Log]
     public void Method2(Object object) { }
 }
Run Code Online (Sandbox Code Playgroud)

请注意,如果只需要拦截某个类的某些方法,则需要对该属性进行一些处理.默认情况下,将截获所有公共方法.


Gis*_*shu 5

看看这个 - 相当沉重的东西.. http://msdn.microsoft.com/en-us/magazine/cc164165.aspx

Essential .net - don box 有一章介绍了您需要的内容,称为拦截。我在这里刮了一些(对不起字体颜色 - 当时我有一个黑暗的主题......) http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html


Jay*_*Jay 5

我找到了一种可能更容易的不同方法......

声明一个方法 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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

然后我像这样定义我的方法

[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;
    }
Run Code Online (Sandbox Code Playgroud)

现在我可以在没有依赖注入的情况下在运行时进行检查......

网站上没有问题:)

希望您会同意,这比 AOP 框架或从 MarshalByRefObject 派生或使用远程处理或代理类更轻。


小智 5

如果您想无限制地跟踪方法(无代码改编,无AOP框架,无重复代码),请告诉我,您需要一些魔术...

认真地说,我解决了它,以实现在运行时工作的AOP框架。

您可以在这里找到:NConcern .NET AOP框架

我决定创建此AOP框架来响应这种需求。这是一个非常轻量的简单库。您可以在主页上看到记录器的示例。

如果您不想使用第三方组装,则可以浏览代码源(开放源代码),并复制文件Aspect.Directory.csAspect.Directory.Entry.cs以适应您的需要。这些类允许在运行时替换您的方法。我只想请您尊重许可证。

我希望您能找到所需的内容或说服您最终使用AOP框架。