簡體   English   中英

如何在委托的方法簽名中檢測並記錄參數名稱和值?

[英]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語句`,你將回到原來的行為。

如果您想了解更多關於這種設計系統的方法: 從這里開始

您可以簡單地訪問其MethodTarget屬性,因為它基本上是任何其他委托。

只需使用func.Methodfunc.Target

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM