Back to Blog
C#

Using C# CallerMemberName to Avoid Hard-Coded Property Names

Learn to use the C# CallerMemberName attribute to capture caller names automatically, with practical examples for property change notifications and logging.

C#CallerMemberNameINotifyPropertyChangedAttributesLogging
Diagram showing a method call with CallerMemberName attribute automatically passing the caller's name to an optional parameter.

The CallerMemberName attribute allows a C# method to receive the name of the member that called it without requiring the caller to pass a string literal. The compiler supplies the member name at the call site, so the behavior is fixed at compile time rather than resolved through reflection. The attribute is part of the caller info attributes family and is commonly used in INotifyPropertyChanged implementations to avoid hard-coded property names. Instead of writing OnPropertyChanged("Name"), you can write OnPropertyChanged() and let the compiler fill in the caller's member name.

Why Hard-Coded Property Names Are Fragile

In a typical INotifyPropertyChanged implementation, you might see code like this:

public string Name { get => _name; set { if (_name != value) { _name = value; OnPropertyChanged("Name"); } } }

The string "Name" is a magic string. If you rename the property with a refactoring tool that does not update string literals, the notification silently breaks. The binding system receives the old name and no longer matches the property, so the UI stops updating. This is a runtime failure that is hard to trace because no exception is thrown. The CallerMemberName attribute eliminates this class of bug by making the compiler supply the correct member name.

How CallerMemberName Works

The CallerMemberName attribute is defined in System.Runtime.CompilerServices. It can be applied to an optional parameter of type string. When the caller omits that argument, the compiler substitutes the name of the calling member, such as a method, property, or event. The attribute does not use reflection at runtime; the substitution happens during compilation. The generated IL contains the literal string, so there is no runtime lookup cost.

Here is a minimal declaration:

using System.Runtime.CompilerServices; public void LogCall([CallerMemberName] string memberName = null) { Console.WriteLine($"Called from {memberName}"); }

When you call LogCall() from a property setter, the compiler passes the property name. If you call it from a method, it passes the method name. The default value is required for the optional parameter, but when the argument is omitted the compiler substitutes the calling member's name.

Practical Example: INotifyPropertyChanged Without Magic Strings

Here is a complete INotifyPropertyChanged implementation that uses CallerMemberName:

using System.ComponentModel; using System.Runtime.CompilerServices; public class ViewModel : INotifyPropertyChanged { private string _name; private int _age; public event PropertyChangedEventHandler PropertyChanged; public string Name { get => _name; set { if (_name != value) { _name = value; OnPropertyChanged(); } } } public int Age { get => _age; set { if (_age != value) { _age = value; OnPropertyChanged(); } } } protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }

Notice that the setter calls OnPropertyChanged() without an argument. The compiler fills in the name of the property that contains the call. If you later rename Name to FullName, the notification automatically uses the new name. This pattern is widely used in MVVM frameworks because it reduces boilerplate and prevents refactoring mistakes.

Using CallerMemberName for Logging and Diagnostics

Another common use is logging method entry points. Instead of writing Log("MethodA") inside each method, you can centralize the call:

public void ProcessOrder(int orderId) { Log($"Processing order {orderId}"); } private void Log(string message, [CallerMemberName] string caller = null) { Trace.WriteLine($"{caller}: {message}"); }

The caller parameter receives the method name from which Log is called. This is useful for diagnostic tracing and audit logs because it keeps the source of the log entry explicit without duplicating method names. The same approach works for exception messages and validation helpers.

Performance and Runtime Cost of CallerMemberName

Because the compiler replaces the omitted argument with a string literal, there is no reflection, no stack walking, and no lookup at runtime. The call behaves like any other call with a string argument. This makes CallerMemberName suitable for property setters that run frequently. In contrast, using StackTrace or MethodBase.GetCurrentMethod() to discover the caller is significantly slower and should be avoided for per-property-change notifications.

The attribute can be used with methods that have an optional string parameter. In async methods, the compiler substitutes the name of the async method, not the state machine. This is usually the desired behavior for logging.

Limitations and Edge Cases

CallerMemberName only works when the caller omits the argument. If the caller explicitly passes a string, that string is used instead. This is useful for overriding the default, but it also means you cannot force the compiler to always use the caller name if a developer chooses to pass a literal.

The attribute is not limited to calls from properties; it also supplies names for calls from methods, events, and constructors. However, it cannot capture a field name because fields are not callable members. In practical code, the parameter is declared as string.

Another limitation is that the attribute only captures the immediate caller. If you have a chain of methods and want the original caller, you need to pass the name manually through the chain. The attribute does not traverse the call stack.

CallerMemberName vs nameof: When to Use Which

Both CallerMemberName and nameof can produce property names, but they serve different purposes. nameof is an expression that evaluates to the name of a symbol at compile time. It is explicit and can be used anywhere a string is expected. CallerMemberName is implicit and only works in the context of an optional parameter.

CriterionCallerMemberNamenameof
UsageOmitted argument in a method callExplicit expression in code
Refactoring safetyAutomatic when property renamedTied to the source symbol
FlexibilityOnly for the immediate callerCan reference any symbol
Common useINotifyPropertyChanged, loggingArgument validation, attribute parameters

Use CallerMemberName when you want to avoid passing the caller's name repeatedly, especially in property setters. Use nameof when you need to reference a specific symbol from a different context, such as nameof(PropertyName) in a validation message or a [Display(Name = nameof(MyProperty))] attribute. Both are compile-time features; neither requires reflection or runtime lookup.

For property change notifications, CallerMemberName is the idiomatic choice because it reduces the chance of error and keeps the setter clean. For scenarios where you need to reference a property name that is not the immediate caller, nameof is more appropriate.

C# CallerMemberName: Practical Usage and Code Examples | RYUSLOG DEV