使用 C# 編譯器解譯而來的屬性判斷呼叫端資訊

使用資訊屬性可取得方法呼叫者的資訊。 您可以取得原始程式碼的檔案路徑、原始程式碼中的行號,以及呼叫端的成員名稱。 若要取得成員呼叫端資訊,請使用套用至選擇性參數的屬性。 每個選擇性參數都會指定預設值。 下表列出 System.Runtime.CompilerServices 命名空間中定義的 Caller Info 屬性:

屬性 描述 類型
CallerFilePathAttribute 包含呼叫端的原始程式檔完整路徑。 完整路徑是編譯時的路徑。 String
CallerLineNumberAttribute 原始程式檔中呼叫方法的行號。 Integer
CallerMemberNameAttribute 呼叫端的方法名稱或屬性名稱。 String
CallerArgumentExpressionAttribute 引數運算式的字串表示。 String

此資訊可協助您進行追蹤和偵錯,以及協助您建立診斷工具。 下列範例示範如何使用呼叫端資訊屬性。 每次呼叫 TraceMessage 方法時,會將引數的呼叫端資訊插入選擇性參數。

public void DoProcessing()
{
    TraceMessage("Something happened.");
}

public void TraceMessage(string message,
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string sourceFilePath = "",
        [CallerLineNumber] int sourceLineNumber = 0)
{
    Trace.WriteLine("message: " + message);
    Trace.WriteLine("member name: " + memberName);
    Trace.WriteLine("source file path: " + sourceFilePath);
    Trace.WriteLine("source line number: " + sourceLineNumber);
}

// Sample Output:
//  message: Something happened.
//  member name: DoProcessing
//  source file path: c:\Visual Studio Projects\CallerInfoCS\CallerInfoCS\Form1.cs
//  source line number: 31

您必須為每個選擇性參數指定明確的預設值。 您無法對未指定成選擇性參數的參數套用呼叫端資訊屬性。 呼叫端資訊屬性不會將參數指定為選擇性。 而是會影響省略引數時傳入的預設值。 呼叫端資訊的值會在編譯時,以常值的形態發出給中繼語言 (IL)。 結果不受模糊化所影響,與 StackTrace 屬性中例外狀況的結果不同。 您可以明確提供選擇性引數來控制呼叫端資訊,或是隱藏呼叫端資訊。

成員名稱

您可以使用 CallerMemberName 屬性避免指定成員名稱做為所呼叫方法的 String 引數。 利用這個技巧就可以避免發生 [重新命名重構] 未變更 String 值這個問題。 這項優點對於下列工作特別有用:

  • 使用追蹤和診斷常式。
  • 當繫結資料時,實作 INotifyPropertyChanged 介面。 此介面允許物件的屬性通知繫結控制項屬性已變更。 控制項可以顯示更新的資訊。 沒有 CallerMemberName 屬性 (Attribute),您就必須指定屬性 (Property) 名稱做為常值。

下圖顯示當您使用 CallerMemberName 屬性時,傳回的成員名稱。

呼叫會發生在 成員名稱結果
方法、屬性或事件 產生呼叫的方法、屬性或事件的名稱。
建構函式 字串 ".ctor"
靜態建構函式 字串 ".cctor"
完成項 字串 "Finalize"
使用者定義的運算子或轉換 產生的成員名稱,例如 "op_Addition"。
屬性建構函式 套用屬性 (attribute) 的方法或屬性 (property) 名稱。 如果屬性為成員內的任何項目 (例如參數、傳回值或泛型類型參數),這個結果會是與該項目相關聯的成員名稱。
無包含的成員 (例如,組件層級或套用至類型的屬性)。 選擇性參數的預設值。

引數運算式內

當您想要以引數形式傳遞運算式時,可使用 System.Runtime.CompilerServices.CallerArgumentExpressionAttribute。 診斷程式庫可能會想要為傳遞給引數的 運算式提供更多詳細資料。 除了參數名稱及提供觸發診斷的運算式之外,開發人員還有觸發診斷之條件的詳細資料。 這些額外資訊有利於修正運算式。

下列範例示範如何在引數無效時,提供引數的詳細資訊:

public static void ValidateArgument(string parameterName, bool condition, [CallerArgumentExpression("condition")] string? message=null)
{
    if (!condition)
    {
        throw new ArgumentException($"Argument failed validation: <{message}>", parameterName);
    }
}

叫用方法如下列範例所示:

public void Operation(Action func)
{
    Utilities.ValidateArgument(nameof(func), func is not null);
    func();
}

condition 使用的運算式,由編譯器插入 message 引數中。 當開發人員使用 null 引數呼叫 Operation 時,下列訊息會儲存在 ArgumentException 中:

Argument failed validation: <func is not null>

此屬性可讓您撰寫診斷公用程式來提供更多詳細資料。 開發人員可以更快了解需要變更的事項。 您也可以使用 CallerArgumentExpressionAttribute,決定要使用哪個運算式做為擴充方法的接收端。 下列方法會定期取樣序列。 若序列的元素數低於頻率,會回報錯誤:

public static IEnumerable<T> Sample<T>(this IEnumerable<T> sequence, int frequency, 
    [CallerArgumentExpression(nameof(sequence))] string? message = null)
{
    if (sequence.Count() < frequency)
        throw new ArgumentException($"Expression doesn't have enough elements: {message}", nameof(sequence));
    int i = 0;
    foreach (T item in sequence)
    {
        if (i++ % frequency == 0)
            yield return item;
    }
}

上一個範例在 sequence 參數中使用 nameof 運算子。 這是 C# 11 的功能。 在 C# 11 之前,必須以字串格式鍵入參數的名稱。 您可以依照下列方式呼叫此方法:

sample = Enumerable.Range(0, 10).Sample(100);

上述範例會擲回 ArgumentException,其訊息文字如下所示:

Expression doesn't have enough elements: Enumerable.Range(0, 10) (Parameter 'sequence')

另請參閱