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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25803/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-02 10:44:07  来源:igfitidea点击:

How do I intercept a method call in C#?

提问by Journeyman

For a given class I would like to have tracing functionality i.e. I would like to log every method call (method signature and actual parameter values) and every method exit (just the method signature).

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

How do I accomplish this assuming that:

假设我如何做到这一点:

  • I don't want to use any 3rd party AOP libraries for C#,
  • I don't want to add duplicate code to all the methods that I want to trace,
  • I don't want to change the public API of the class - users of the class should be able to call all the methods in exactly the same way.
  • 我不想为 C# 使用任何 3rd 方 AOP 库,
  • 我不想在我想跟踪的所有方法中添加重复的代码,
  • 我不想更改类的公共 API - 类的用户应该能够以完全相同的方式调用所有方法。

To make the question more concrete let's assume there are 3 classes:

为了使问题更具体,让我们假设有 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);
 }

How do I invoke Logger.LogStartand Logger.LogEndfor every call to Method1and Method2without modifying the Caller.Callmethod and without adding the calls explicitly to Traced.Method1and Traced.Method2?

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

Edit: What would be the solution if I'm allowed to slightly change the Call method?

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

采纳答案by Jorge Córdoba

C# is not an AOP oriented language. It has some AOP features and you can emulate some others but making AOP with C# is painful.

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

I looked up for ways to do exactly what you wanted to do and I found no easy way to do it.

我寻找方法来做你想做的事情,但我没有找到简单的方法。

As I understand it, this is what you want to do:

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

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

and in order to do that you have two main options

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

  1. Inherit your class from MarshalByRefObject or ContextBoundObject and define an attribute which inherits from IMessageSink. This articlehas a good example. You have to consider nontheless that using a MarshalByRefObject the performance will go down like hell, and I mean it, I'm talking about a 10x performance lost so think carefully before trying that.

  2. The other option is to inject code directly. In runtime, meaning you'll have to use reflection to "read" every class, get its attributes and inject the appropiate call (and for that matter I think you couldn't use the Reflection.Emit method as I think Reflection.Emit wouldn't allow you to insert new code inside an already existing method). At design time this will mean creating an extension to the CLR compiler which I have honestly no idea on how it's done.

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

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

The final option is using an IoC framework. Maybe it's not the perfect solution as most IoC frameworks works by defining entry points which allow methods to be hooked but, depending on what you want to achive, that might be a fair aproximation.

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

回答by kokos

  1. Write your own AOP library.
  2. Use reflection to generate a logging proxy over your instances (not sure if you can do it without changing some part of your existing code).
  3. Rewrite the assembly and inject your logging code (basically the same as 1).
  4. Host the CLR and add logging at this level (i think this is the hardest solution to implement, not sure if you have the required hooks in the CLR though).
  1. 编写自己的 AOP 库。
  2. 使用反射在您的实例上生成日志代理(不确定您是否可以在不更改现有代码的某些部分的情况下执行此操作)。
  3. 重写程序集并注入日志代码(与 1 基本相同)。
  4. 托管 CLR 并在此级别添加日志记录(我认为这是最难实现的解决方案,但不确定您是否在 CLR 中具有所需的钩子)。

回答by Konrad Rudolph

I don't know a solution but my approach would be as follows.

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

Decorate the class (or its methods) with a custom attribute. Somewhere else in the program, let an initialization function reflect all types, read the methods decorated with the attributes and inject some IL code into the method. It might actually be more practical to replacethe method by a stub that calls LogStart, the actual method and then LogEnd. Additionally, I don't know if you can change methods using reflection so it might be more practical to replace the whole type.

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

回答by Steen

If you write a class - call it Tracing - that implements the IDisposable interface, you could wrap all method bodies in a

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

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

In the Tracing class you could the handle the logic of the traces in the constructor/Dispose method, respectively, in the Tracing class to keep track of the entering and exiting of the methods. Such that:

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

回答by Gishu

Take a look at this - Pretty heavy stuff.. http://msdn.microsoft.com/en-us/magazine/cc164165.aspx

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

Essential .net - don box had a chapter on what you need called Interception. I scraped some of it here (Sorry about the font colors - I had a dark theme back then...) http://madcoderspeak.blogspot.com/2005/09/essential-interception-using-contexts.html

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

回答by Antoine Aubry

The simplest way to achieve that is probably to use PostSharp. It injects code inside your methods based on the attributes that you apply to it. It allows you to do exactly what you want.

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

Another option is to use the profiling APIto inject code inside the method, but that is really hardcore.

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

回答by Stacy A

You could potentially use the GOF Decorator Pattern, and 'decorate' all classes that need tracing.

您可以潜在地使用 GOF 装饰器模式,并“装饰”所有需要跟踪的类。

It's probably only really practical with an IOC container (but as pointer out earlier you may want to consider method interception if you're going to go down the IOC path).

它可能只适用于 IOC 容器(但作为早先指出的指针,如果您要沿着 IOC 路径走下去,您可能需要考虑方法拦截)。

回答by Liao

you need to bug Ayende for an answer on how he did it: http://ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx

你需要给 Ayende 找一个关于他是如何做到的答案:http: //ayende.com/Blog/archive/2009/11/19/can-you-hack-this-out.aspx

回答by Jay

I have found a different way which may be easier...

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

Declare a Method InvokeMethod

声明一个方法 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;
        }
    }

I then define my methods like so

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

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

Now I can have the check at run time without the dependency injection...

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

No gotchas in site :)

网站上没有问题:)

Hopefully you will agree that this is less weight then a AOP Framework or deriving from MarshalByRefObject or using remoting or proxy classes.

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

回答by Nitro

AOP is a must for clean code implementing, however if you want to surround a block in C#, generic methods have relatively easier usage. (with intelli sense and strongly typed code) Certainly, it can NOT be an alternative for AOP.

AOP 是实现干净代码的必要条件,但是如果你想在 C# 中包围一个块,泛型方法相对更容易使用。(具有智能感知和强类型代码)当然,它不能替代 AOP。

Although PostSHarphave little buggy issues (i do not feel confident for using at production), it is a good stuff.

尽管PostSHArp几乎没有问题(我对在生产中使用没有信心),但它是个好东西。

Generic wrapper class,

通用包装类,

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

usage could be like this (with intelli sense of course)

用法可能是这样的(当然有智能感)

var exception = Wrapper.LogOnError(() =>
{
  MessageBox.Show("test");
  throw new Exception("test");
}, "Hata");