Using Caller Info Attributes for Diagnostics in C

In C#, the CallerMemberName, CallerFilePath, and CallerLineNumber attributes allow developers to capture diagnostic metadata about a caller method automatically at compile time. Instead of relying on expensive runtime reflection or StackTrace lookups, these attributes inject the calling member's name, source file path, and line number directly into method parameters. This article explains how these three caller information attributes work, how to implement them in a diagnostic logging utility, and why they are ideal for high-performance tracing.

Understanding the Attributes

Located in the System.Runtime.CompilerServices namespace, caller information attributes instruct the compiler to substitute default parameter values with source-code metadata at the call site:

  • [CallerMemberName]: Substitutes the method, property, or event name of the caller as a string.
  • [CallerFilePath]: Substitutes the full file path of the source file containing the caller as a string.
  • [CallerLineNumber]: Substitutes the source code line number where the call is made as an int.

Because the compiler performs this substitution during compilation, the resulting binary contains hard-coded values at each call site, eliminating any runtime performance overhead.

Implementing a Diagnostic Logger

To use these attributes, define optional parameters with default values on the diagnostic method and decorate each parameter with the corresponding attribute.

using System;
using System.IO;
using System.Runtime.CompilerServices;

public static class DiagnosticLogger
{
    public static void Log(
        string message,
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string filePath = "",
        [CallerLineNumber] int lineNumber = 0)
    {
        string fileName = Path.GetFileName(filePath);
        Console.WriteLine($"[{fileName}:{lineNumber}] {memberName} -> {message}");
    }
}

Consuming the Logger

When calling the diagnostic method, omit the decorated parameters. The compiler automatically supplies the metadata.

public class OrderService
{
    public void ProcessOrder(int orderId)
    {
        DiagnosticLogger.Log($"Starting processing for order {orderId}.");
        
        // Output format:
        // [OrderService.cs:7] ProcessOrder -> Starting processing for order 101.
    }
}

If you explicitly pass a value to any of the decorated parameters, your explicit value overrides the compiler-generated metadata.

Using CallerMemberName with INotifyPropertyChanged

Beyond general logging, [CallerMemberName] is commonly used to eliminate magic strings in data-binding scenarios:

using System.ComponentModel;
using System.Runtime.CompilerServices;

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

In property setters, calling OnPropertyChanged() without arguments automatically passes the property's name.

Key Considerations

  • Parameter Requirements: Parameters decorated with these attributes must be optional and have a default value (e.g., "" or 0).
  • Performance Advantage: Unlike System.Diagnostics.StackTrace, which unwinds the call stack at runtime, caller attributes have zero runtime lookup cost.
  • Obfuscation Impact: If code obfuscators rename members or strip source paths during build or publishing pipelines, the values captured via caller attributes will reflect the post-obfuscation states.