[英]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.