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

时间:2012-11-09 15:07:12

标签: c# reflection dependency-injection

感谢您的期待!

背景

我有一个扩展方法,用于在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;
        }

以下是调用该方法的方法:

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

正如您所看到的,我调用的任何方法都被注入到扩展方法中并在try / catch中执行。

我们将使用NLog或ELMAH进行日志记录,但这与此问题无关。

问题

如果出现问题,我需要尽可能多地记录有关委托方法的信息,因为“对象引用未设置为对象实例”之类的内容本身并没有帮助。

我想记录被调用方法的类和名称,以及方法签名中的参数及其值。如果可能的话,我甚至想记录哪一行失败,最后是实际的堆栈跟踪。

我猜我需要使用反射,并且可能会在注入的方法执行时以某种方式捕获绑定标志,但我不完全确定这是否是最好的方法,或者它是否可行。

问题

使用C#,如何获取有关注入/委托方法的元信息(即方法名称,原点类,参数,参数值)?

提前致谢。

2 个答案:

答案 0 :(得分:4)

在我看来,您可以改进将此日志记录横切关注点添加到应用程序中的方式。

这里的主要问题是,尽管您的解决方案阻止您对SomeClass.SomeMethod(或任何被调用的方法)进行任何更改,但您仍需要更改使用代码。换句话说,你打破了Open/closed principle,它告诉我们必须能够在不改变任何现有代码的情况下进行这些更改。

您可能认为我夸大了,但您的应用程序中可能已经有超过一百个HandleServerError来电,而且通话次数也会越来越多。而且您很快就会添加更多这些功能性的装饰者&#39;很快到系统。您是否考虑过进行任何授权检查,方法参数验证,检测或审计跟踪?你必须承认做new Func<T>(() => someCall).HandleServerError()只是感觉很乱,不是吗?

您可以通过向系统引入正确的抽象来解决所有这些问题,包括实际问题的问题。

第一步是将给定的方法参数提升为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; }
}

我们可以将它们作为一个单独的对象一起传递,而不是将所有单个参数传递给方法。你可能会说,使用它的是什么?请继续阅读。

第二步是引入一个通用接口来隐藏SomeClass.SomeMethod(或实际上任何方法)背后的实际逻辑:

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

对于系统中的每个(业务)操作,您可以编写IMethodHandler<TParameter>实现。在您的情况下,您可以简单地创建一个包含对SomeClass.SomeMethod的调用的实现,如下所示:

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

执行这样的操作可能看起来有些愚蠢,但它允许您快速实现此设计,并在SomeClass.SomeMethod内部移动静态SomeMethodHandler的逻辑。

第三步是让消费者依赖IMethodHandler<SomeMethodParameters>接口,而不是让他们依赖于系统中的某些静态方法(在你的情况下再次是SomeClass.SomeMethod)。想一想取决于这种抽象的好处是什么。

这样做的一个有趣结果是,它使得对消费者进行单元测试变得更加容易。但也许你对单元测试不感兴趣。但是你对松耦合感兴趣。当消费者依赖于这种抽象而不是真正的实现(特别是静态方法)时,您可以做各种疯狂的事情,例如添加日志记录等横切关注点。 一个很好的方法是使用decorator包装IMethodHandler<T>实现。这是一个用例的装饰器:

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

了解此装饰器的Handle方法如何包含原始HandleServerError<T>方法的代码?事实上它与你正在做的事情并没有太大的不同,因为HandleServerError&#39;装饰&#39; (或&#39;扩展&#39;)具有新行为的原始方法的行为。但现在不是使用方法调用,而是使用对象。

关于这一切的好处是,这个单一的通用LoggingMethodHandlerDecorator<T>可以包含在每个IMethodHandler<T>实现中,并且可以被每个消费者使用。通过这种方式,我们可以添加横切关注点,例如日志记录等,而无需消费者和方法来了解这一点。这是开放/封闭原则。

但是还有其他一些非常好的事情。您最初的问题是如何获取有关方法名称和参数的信息。好吧,所有这些信息现在都很容易获得,因为我们已经将所有参数包装在一个对象中,而不是调用包含在Func委托中的一些自定义方法。我们可以像这样实现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)));

这将TParameter对象的名称序列化为XML格式的值。或者您当然可以使用.NET的XmlSerializer将对象序列化为XML或使用您需要的任何其他序列化。所有信息如果在元数据中可用,这是非常好的。当您为参数对象提供一个好的和唯一的名称时,它允许您立即在日志文件中识别它。连同实际参数和一些上下文信息(例如日期时间,当前用户等),您将获得修复错误所需的所有信息。

LoggingMethodHandlerDecorator<T>与原始HandleServerError<T>之间存在一个区别,那就是最后一个throw语句。您的实现实现了某种ON ERROR RESUME NEXT,这可能不是最好的事情。方法失败时,继续(并返回默认值)实际上是否安全?根据我的经验,它通常不是,并且在此时继续,可能会使编写消费类的开发人员认为一切都按预期工作,或者甚至可能使应用程序的用户认为一切都按预期工作(例如,他的变化被保存了,而实际上他们并没有。通常对此无能为力,并且在catch语句中包装所有内容只会使情况变得更糟,尽管我可以想象您想要记录此信息。不要被用户要求所迷惑,例如“应用程序必须始终工作”或“我们不希望看到任何错误页面”。通过抑制所有错误来实现这些要求将无济于事,也无法解决根本原因。但是,如果你真的需要捕捉并继续,只需删除throw语句,你就会恢复原来的行为。

如果您想详细了解这种设计系统的方式:start here

答案 1 :(得分:2)

您只需访问其MethodTarget属性,因为它基本上是任何其他代表。

只需使用func.Methodfunc.Target