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

时间:2020-03-05 18:42:45  来源:igfitidea点击:

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

假设以下条件,我如何完成此操作:

  • 我不想将任何第三方AOP库用于C#,
  • 我不想将重复的代码添加到我要跟踪的所有方法中,
  • 我不想更改类的公共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);
 }

如何在不修改Caller.Call方法且未将调用显式添加到Traced.Method1和Traced.Method2的情况下,对Method1和Method2的每次调用调用Logger.LogStart和Logger.LogEnd?

编辑:如果允许我稍微更改Call方法,那将是解决方案?

解决方案

回答

  • 编写自己的AOP库。
  • 使用反射在实例上生成日志记录代理(不确定是否可以在不更改现有代码的某些部分的情况下进行操作)。
  • 重写程序集并注入日志记录代码(基本上与1相同)。
  • 托管CLR并在此级别添加日志记录(我认为这是最难实现的解决方案,尽管不确定在CLR中是否具有必需的钩子)。

回答

我不知道解决方案,但是我的方法如下。

用自定义属性装饰类(或者其方法)。在程序中的其他位置,让初始化函数反映所有类型,读取用属性装饰的方法,并将一些IL代码注入该方法中。实际上,用存根调用LogStart,实际方法然后是LogEnd的存根替换该方法可能更实际。另外,我不知道我们是否可以使用反射更改方法,因此替换整个类型可能更实用。

回答

如果编写实现IDisposable接口的称为Tracing的类,则可以将所有方法主体包装在

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

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

回答

看一看这个很重的东西。
http://msdn.microsoft.com/zh-CN/magazine/cc164165.aspx

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

回答

不支持AOP的语言。它具有一些AOP功能,我们可以效仿其他功能,但是使用Cis会使AOP痛苦不堪。

我一直在寻找可以完全按照自己的意愿去做的方法,但是却发现没有简单的方法可以做到。

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

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

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

  • 从MarshalByRefObject或者ContextBoundObject继承类,并定义一个从IMessageSink继承的属性。本文有一个很好的例子。尽管如此,我们仍必须考虑使用MarshalByRefObject会导致性能下降,而我的意思是,我说的是性能下降了10倍,因此在尝试之前应仔细考虑。
  • 另一种选择是直接注入代码。在运行时中,这意味着我们必须使用反射来"读取"每个类,获取其属性并注入适当的调用(为此,我认为我们不能像我认为Reflection.Emit那样使用Reflection.Emit方法不允许我们在现有方法中插入新代码)。在设计时,这将意味着创建CLR编译器的扩展,老实说,我不知道它是如何完成的。

最后的选择是使用IoC框架。也许这不是完美的解决方案,因为大多数IoC框架通过定义允许方法挂接的入口点来工作,但是根据我们要实现的目标,这可能是一个合理的替代。

回答

最简单的方法可能是使用PostSharp。它根据我们应用于属性的方法将代码注入到方法中。它使我们可以完全按照自己的意愿去做。

另一个选择是使用分析API将代码注入方法中,但这确实很困难。

回答

我们可能会使用GOF装饰器模式,并"装饰"所有需要跟踪的类。

这可能仅在IOC容器中才真正可行(但如前所述,如果我们打算沿IOC路径前进,则可能需要考虑方法拦截)。

回答

我们需要向Ayende寻求有关其工作方式的答案:
http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx