简体   繁体   English

如何在委托的方法签名中检测并记录参数名称和值?

[英]How do I detect and log the parameter names and values in the method signature of a delegate?

Thanks for looking! 谢谢你的期待!

Background 背景

I have an extension method that is used to wrap a given method in a try/catch and I am adding code for logging any caught exceptions: 我有一个扩展方法,用于在try/catch包装给定的方法,我正在添加用于记录任何捕获的异常的代码:

 public static T HandleServerError<T>(this Func<T> func)
        {
            T result = default(T);
            try
            {
                result = func();
            }
            catch (Exception ex)
            {
                //******************************
                //Code for logging will go here.
                //******************************

                ErrorHandlers.ThrowServerErrorException(ex);
            }

            return result;
        }

Here is how the method is called: 以下是调用该方法的方法:

var result = new Func<SomeClass.SomeType>(() => SomeClass.SomeMethod(id, name, color, quantity)).HandleServerError();
return result;

As you can see, whatever method I am calling is injected into the extension method and executed inside the try/catch. 正如您所看到的,无论我调用什么方法都注入到扩展方法中并在try / catch中执行。

We will be using NLog or ELMAH for logging, but that is largely irrelevant to this question. 我们将使用NLog或ELMAH进行日志记录,但这与此问题基本无关。

Problem 问题

If something goes wrong, I need to log as much information about the delegated method as possible since things like "Object reference not set to an instance of an object" is not in itself helpful. 如果出现问题,我需要尽可能多地记录有关委托方法的信息,因为“对象引用未设置为对象实例”之类的内容本身并不有用。

I would like to log the class and name of the method being called as well as the parameters in the method signature along with their values. 我想记录被调用方法的类和名称,以及方法签名中的参数及其值。 If possible, I would even like to log which line failed, and finally the actual stack trace. 如果可能的话,我甚至想记录哪一行失败,最后是实际的堆栈跟踪。

I am guessing that I need to use reflection for this and maybe catch the binding flags somehow as the injected method executes but I am not entirely sure if that is the best approach or if it is even feasible. 我猜我需要使用反射,并且可能在注入的方法执行时以某种方式捕获绑定标志,但我不完全确定这是否是最好的方法或者它是否可行。

Question

Using C#, how do I get the meta information (ie method name, class of origin, parameters, parameter values) about an injected/delegated method? 使用C#,如何获取有关注入/委托方法的元信息(即方法名称,原点类,参数,参数值)?

Thanks in advance. 提前致谢。

It seems to me that there is a possibility for you to improve the way you are adding this logging cross-cutting concern to your application. 在我看来,您可以改进将此日志记录横切关注点添加到应用程序的方式。

The main issue here is that although your solution prevents you from making any changes to SomeClass.SomeMethod (or any called method), you still need to make changes to the consuming code. 这里的主要问题是虽然您的解决方案阻止您对SomeClass.SomeMethod (或任何被调用的方法)进行任何更改,但您仍需要对使用代码进行更改。 In other words you are breaking the Open/closed principle , which tells us that it must be possible to make these kinds of changes without changing any existing code. 换句话说,你打破了开放/封闭原则 ,它告诉我们必须能够在不改变任何现有代码的情况下进行这些改变。

You might think I'm exaggerating, but you probably already have over a hundred calls to HandleServerError in your application, and the number of calls will only be growing. 您可能认为我夸大了,但您的应用程序中可能已经有超过一百次调用HandleServerError ,并且调用次数将会增加。 And you'll soon add even more of those 'functional decorators' to the system pretty soon. 很快你就会很快将更多的“功能装饰器”添加到系统中。 Did you ever think about doing any authorization checks, method argument validation, instrumentation, or audit trailing? 您是否考虑过进行任何授权检查,方法参数验证,检测或审计跟踪? And you must admit that doing new Func<T>(() => someCall).HandleServerError() just feels messy, doesn't it? 你必须承认做new Func<T>(() => someCall).HandleServerError()只是感觉很乱,不是吗?

You can resolve all these problems, including the problem of your actual question, by introducing the right abstraction to the system. 您可以通过向系统引入正确的抽象来解决所有这些问题,包括实际问题的问题。

First step is to promote the given method arguments into a Parameter Object : 第一步是将给定的方法参数提升为参数对象

public SomeMethodParameters
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Color Color { get; set; }
    public decimal Quantity { get; set; }

    public decimal Result { get; set; }
}

Instead of passing all the individual arguments into a method, we can pass them all together as one single object. 我们可以将它们作为一个单独的对象一起传递,而不是将所有单个参数传递给方法。 What's the use of that, you may say? 你可能会说,有什么用? Read on. 继续阅读。

Second step is to introduce a generic interface to hide the actual logic of the SomeClass.SomeMethod (or in fact any method) behind: 第二步是引入一个通用接口来隐藏SomeClass.SomeMethod (或实际上任何方法)的实际逻辑:

public interface IMethodHandler<TParameter>
{
    void Handle(TParameter parameter);
}

For each (business) operation in the system, you can write an IMethodHandler<TParameter> implementation. 对于系统中的每个(业务)操作,您可以编写IMethodHandler<TParameter>实现。 In your case you could simply create an implementation that wraps the call to SomeClass.SomeMethod , like this: 在您的情况下,您可以简单地创建一个包含对SomeClass.SomeMethod的调用的实现,如下所示:

public class SomeMethodHandler 
    : IMethodHandler<SomeMethodParameters>
{
    void Handle(SomeMethodParameters parameter)
    {
        parameter.Result = SomeClass.SomeMethod(
            parameter.id,
            parameter.Name, 
            parameter.Color, 
            parameter.Quantity);
    }
}

It might look a bit silly to do things like this, but it allows you to implement this design quickly, and move the logic of the static SomeClass.SomeMethod inside of the SomeMethodHandler . 做这样的事情可能看起来有点傻,但它允许你快速实现这个设计,并在SomeMethodHandler移动静态SomeClass.SomeMethod的逻辑。

Third step is let consumers depend on a IMethodHandler<SomeMethodParameters> interface, instead of letting them depend on some static method in the system (in your case again the SomeClass.SomeMethod ). 第三步是让消费者依赖于IMethodHandler<SomeMethodParameters>接口,而不是让它们依赖于系统中的某些静态方法(在你的情况下再次是SomeClass.SomeMethod )。 Think for a moment what the benefits are of depending on such abstraction. 想一想取决于这种抽象的好处是什么。

One interesting result of this is that it makes it much easier to unit test the consumer. 一个有趣的结果是它使得对消费者进行单元测试变得更加容易。 But perhaps you're not interested in unit testing. 但也许你对单元测试不感兴趣。 But you are interested in loosely coupling. 但是你对松耦合感兴趣。 When consumers depend on such abstraction instead of a real implementation (especially static methods), you can do all kinds of crazy things, such as adding cross-cutting concerns such as logging. 当消费者依赖于这种抽象而不是真正的实现(特别是静态方法)时,您可以做各种疯狂的事情,例如添加日志记录等横切关注点。 A nice way to do this is to wrap IMethodHandler<T> implementations with a decorator . 一个很好的方法是用装饰器包装IMethodHandler<T>实现。 Here is a decorator for your use case: 这是一个用例的装饰器:

public class LoggingMethodHandlerDecorator<T> 
    : IMethodHandler<T>
{
    private readonly IMethodHandler<T> handler;

    public LoggingMethodHandlerDecorator(
        IMethodHandler<T> handler)
    {
        this.handler = handler;
    }

    public void Handle(T parameters)
    {
        try
        {
            this.handler.Handle(parameters);
        }
        catch (Exception ex)
        {
            //******************************
            //Code for logging will go here.
            //******************************

            ErrorHandlers.ThrowServerErrorException(ex);

            throw;
        }
    }
}

See how the Handle method of this decorator contains the code of your original HandleServerError<T> method? 看看这个装饰器的Handle方法如何包含原始HandleServerError<T>方法的代码? It's in fact not that much different from what you were already doing, since the HandleServerError 'decorated' (or 'extended') the behavior of the original method with new behavior. 事实上它与你正在做的事情没那么太不同,因为HandleServerError “装饰”(或“扩展”)原始方法的行为与新行为。 But instead of using method calls now, we're using objects. 但是现在我们不是使用方法调用,而是使用对象。

The nice thing about all this is, is that this single generic LoggingMethodHandlerDecorator<T> can be wrapped around every single IMethodHandler<T> implementation and can be used by every consumer. 所有这一切的LoggingMethodHandlerDecorator<T>是,这个单一的通用LoggingMethodHandlerDecorator<T>可以包装在每个IMethodHandler<T>实现中,并且可以被每个消费者使用。 This way we can add cross-cutting concerns such as logging, etc, without both the consumer and the method to know about this. 通过这种方式,我们可以添加横切关注点,例如日志记录等,而无需消费者和方法来了解这一点。 This is the Open/closed principle. 这是开放/封闭原则。

But there is something else really nice about this. 但是还有其他一些非常好的东西。 Your initial question was about how to get the information about the method name and the parameters. 您最初的问题是如何获取有关方法名称和参数的信息。 Well, all this information is easily available now, because we've wrapped all arguments in an object instead of calling some custom method wrapped inside a Func delegate. 好吧,所有这些信息现在都很容易获得,因为我们已经将所有参数包装在一个对象中,而不是调用包含在Func委托中的一些自定义方法。 We could implement the catch clause like this: 我们可以像这样实现catch子句:

string messageInfo = string.Format("<{0}>{1}</{0}>",
    parameters.GetType().Name, string.Join("",
        from property in parameters.GetType().GetProperties()
        where property.CanRead
        select string.Format("<{0}>{1}</{0}>",
            property.Name, property.GetValue(parameters, null)));

This serializes the name of the TParameter object with its values to an XML format. 这会将TParameter对象的名称及其值序列化为XML格式。 Or you can of course use .NET's XmlSerializer to serialize the object to XML or use any other serialization you need. 或者您当然可以使用.NET的XmlSerializer将对象序列化为XML或使用您需要的任何其他序列化。 All the information if available in the metadata, which is quite nice. 所有信息如果在元数据中可用,这是非常好的。 When you give the parameter object a good and unique name, it allows you to identify it in the log file right away. 当您为参数对象提供一个好的和唯一的名称时,它允许您立即在日志文件中识别它。 And together with the actual parameters and perhaps some context information (such as datetime, current user, etc) you will have all the information you need to fix a bug. 并结合实际参数和一些上下文信息(例如日期时间,当前用户等),您将获得修复错误所需的所有信息。

There is one difference between this LoggingMethodHandlerDecorator<T> and your original HandleServerError<T> , and that is the last throw statement. 这个LoggingMethodHandlerDecorator<T>和你原来的HandleServerError<T>之间有一个区别,那就是最后一个throw语句。 Your implementation implements some sort of ON ERROR RESUME NEXT which might not be the best thing to do. 您的实现实现了某种ON ERROR RESUME NEXT ,这可能不是最好的事情。 Is it actually safe to continue (and return the default value) when the method failed? 方法失败时,继续(并返回默认值)实际上是否安全? In my experience it usually isn't, and continuing at this point, might make the developer writing the consuming class think that everything works as expected, or might even make the user of the application think that everything worked out as expected (that his changes were saved for instance, while in fact they weren't). 根据我的经验,它通常不是,并且在这一点上继续,可能会使编写消费类的开发人员认为一切都按预期工作,或者甚至可能使应用程序的用户认为一切都按预期工作(他的更改)例如,保存,而实际上他们不是)。 There's usually not much you can do about this, and wrapping everything in catch statements only makes it worse, although I can imagine that you want to log this information. 通常没有什么可以做的,并且在catch语句中包装所有内容只会让它变得更糟,尽管我可以想象你想记录这些信息。 Don't be fooled by user requirements such as “the application must always work” or “we don't want to see any error pages”. 不要被用户要求所迷惑,例如“应用程序必须始终工作”或“我们不希望看到任何错误页面”。 Implementing those requirements by suppressing all errors will not help and will not fix the root cause. 通过抑制所有错误来实现这些要求将无济于事,也无法解决根本原因。 But nonetheless, if you really need to catch-and-continue, just remove the throw statement`, and you'll be back at the original behavior. 但是,如果你真的需要捕获并继续,只需删除throw语句`,你将回到原来的行为。

If you want to read more about this way of designing your system: start here . 如果您想了解更多关于这种设计系统的方法: 从这里开始

You can simply access its Method and Target properties as it's basically any other delegate. 您可以简单地访问其MethodTarget属性,因为它基本上是任何其他委托。

Just use func.Method and func.Target . 只需使用func.Methodfunc.Target

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何委派动态方法名称和动态参数的调用 - How do I delegate calling of dynamic method names and dynamic parameters 使用相同的代表签名参数和无参数方法 - Using Same Delegate Signature for Parameter and Parameterless Method 如何检测PDF文档中的签名行,然后插入签名? - How do I detect a signature line in a PDF document and then insert a signature? 如何将委托作为参数传递 - How do i pass in a delegate as a parameter 如何将事件处理程序委托转换为具有不同签名的委托 - How do I cast an event handler delegate to one with a different signature 如何使用动作委托参数方法将Win32应用程序中C ++方法的地址传递给ac#方法 - How do I pass the address of a c++ method in win32 app to a c# method with Action delegate parameter method 如何限制此HTML帮助程序方法的参数值? - How do I restrict the values of a parameter for this HTML helper method? 如何使用反射调用具有委托参数的方法? - How can I call method that has a delegate parameter using reflection? 如何从.NET中的属性类读取方法参数信息(名称和值) - How can I read method parameter information (names and values) from an attribute class in .NET 使用作为方法参数传递的参数进行委托-如何访问委托arg? - Delegate with parameter passed as a method parameter - how to access the delegate arg?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM