[英]How do I detect and log the parameter names and values in the method signature of a delegate?
谢谢你的期待!
我有一个扩展方法,用于在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#,如何获取有关注入/委托方法的元信息(即方法名称,原点类,参数,参数值)?
提前致谢。
在我看来,您可以改进将此日志记录横切关注点添加到应用程序的方式。
这里的主要问题是虽然您的解决方案阻止您对SomeClass.SomeMethod
(或任何被调用的方法)进行任何更改,但您仍需要对使用代码进行更改。 换句话说,你打破了开放/封闭原则 ,它告诉我们必须能够在不改变任何现有代码的情况下进行这些改变。
您可能认为我夸大了,但您的应用程序中可能已经有超过一百次调用HandleServerError
,并且调用次数将会增加。 很快你就会很快将更多的“功能装饰器”添加到系统中。 您是否考虑过进行任何授权检查,方法参数验证,检测或审计跟踪? 你必须承认做new Func<T>(() => someCall).HandleServerError()
只是感觉很乱,不是吗?
您可以通过向系统引入正确的抽象来解决所有这些问题,包括实际问题的问题。
第一步是将给定的方法参数提升为参数对象 :
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);
}
}
做这样的事情可能看起来有点傻,但它允许你快速实现这个设计,并在SomeMethodHandler
移动静态SomeClass.SomeMethod
的逻辑。
第三步是让消费者依赖于IMethodHandler<SomeMethodParameters>
接口,而不是让它们依赖于系统中的某些静态方法(在你的情况下再次是SomeClass.SomeMethod
)。 想一想取决于这种抽象的好处是什么。
一个有趣的结果是它使得对消费者进行单元测试变得更加容易。 但也许你对单元测试不感兴趣。 但是你对松耦合感兴趣。 当消费者依赖于这种抽象而不是真正的实现(特别是静态方法)时,您可以做各种疯狂的事情,例如添加日志记录等横切关注点。 一个很好的方法是用装饰器包装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
“装饰”(或“扩展”)原始方法的行为与新行为。 但是现在我们不是使用方法调用,而是使用对象。
所有这一切的LoggingMethodHandlerDecorator<T>
是,这个单一的通用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
语句`,你将回到原来的行为。
如果您想了解更多关于这种设计系统的方法: 从这里开始 。
您可以简单地访问其Method
和Target
属性,因为它基本上是任何其他委托。
只需使用func.Method
和func.Target
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.