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 astring.[CallerFilePath]: Substitutes the full file path of the source file containing the caller as astring.[CallerLineNumber]: Substitutes the source code line number where the call is made as anint.
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.,
""or0). - 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.