C# Tip: An In-Depth Look at CallerMemberName (and some Compile-Time trivia)
Let’s dive deep into the CallerMemberName attribute and explore its usage from multiple angles. We’ll see various methods of invoking it, shedding light on how it is defined at compile time.
Table of Contents
Just a second! π«·
If you are here, it means that you are a software developer. So, you know that storage, networking, and domain management have a cost .
If you want to support this blog, please ensure that you have disabled the adblocker for this site. I configured Google AdSense to show as few ADS as possible - I don't want to bother you with lots of ads, but I still need to add some to pay for the resources for my site.
Thank you for your understanding.
- Davide
Method names change. And, if you are using method names in some places specifying them manually, you’ll spend a lot of time updating them.
Luckily for us, in C#, we can use an attribute named CallerMemberName
.
This attribute can be applied to a parameter in the method signature so that its runtime value is the caller method’s name.
public void SayMyName([CallerMemberName] string? methodName = null) =>
Console.WriteLine($"The method name is {methodName ?? "NULL"}!");
It’s important to note that the parameter must be a nullable string: this way if the caller sets its value, the actual value is set. Otherwise, the name of the caller method is used. Well, if the caller method has a name! π
Getting the caller method’s name via direct execution
The easiest example is the direct call:
private void DirectCall()
{
Console.WriteLine("Direct call:");
SayMyName();
}
Here, the method prints:
Direct call:
The method name is DirectCall!
In fact, we are not specifying the value of the methodName
parameter in the SayMyName
method, so it defaults to the caller’s name: DirectCall
.
CallerMemberName when using explicit parameter name
As we already said, we can specify the value:
private void DirectCallWithOverriddenName()
{
Console.WriteLine("Direct call with overridden name:");
SayMyName("Walter White");
}
Prints:
Direct call with overridden name:
The method name is Walter White!
It’s important to note that the compiler sets the methodName
parameter only if it is not otherwise specified.
This means that if you call SayMyName(null)
, the value will be null
- because you explicitly declared the value.
private void DirectCallWithNullName()
{
Console.WriteLine("Direct call with null name:");
SayMyName(null);
}
The printed text is then:
Direct call with null name:
The method name is NULL!
CallerMemberName when the method is called via an Action
Let’s see what happens when calling it via an Action:
public void CallViaAction()
{
Console.WriteLine("Calling via Action:");
Action<int> action = (_) => SayMyName();
var singleElement = new List<int> { 1 };
singleElement.ForEach(s => action(s));
}
This method prints this text:
Calling via Action:
The method name is CallViaAction!
Now, things get interesting: the CallerMemberName
attribute recognizes the method’s name that contains the overall expression, not just the actual caller.
We can see that, syntactically, the caller is the ForEach
method (which is a method of the List<T>
class). But, in the final result, the ForEach
method is ignored, as the method is actually called by the CallViaAction
method.
This can be verified by accessing the compiler-generated code, for example by using Sharplab.
At compile time, since no value is passed to the SayMyName
method, it gets autopopulated with the parent method name. Then, the ForEach
method calls SayMyName
, but the methodName
is already defined at compiled time.
Lambda executions and the CallerMemberName attribute
The same behaviour occurs when using lambdas:
private void CallViaLambda()
{
Console.WriteLine("Calling via lambda expression:");
void lambdaCall() => SayMyName();
lambdaCall();
}
The final result prints out the name of the caller method.
Calling via lambda expression:
The method name is CallViaLambda!
Again, the magic happens at compile time:
The lambda is compiled into this form:
[CompilerGenerated]
private void <CallViaLambda>g__lambdaCall|0_0()
{
SayMyName("CallViaLambda");
}
Making the parent method name available.
CallerMemberName when invoked from a Dynamic type
What if we try to execute the SayMyName
method by accessing the root class (in this case, CallerMemberNameTests
) as a dynamic
type?
private void CallViaDynamicInvocation()
{
Console.WriteLine("Calling via dynamic invocation:");
dynamic dynamicInstance = new CallerMemberNameTests(null);
dynamicInstance.SayMyName();
}
Oddly enough, the attribute does not work as could have expected, but it prints NULL:
Calling via dynamic invocation:
The method name is NULL!
This happens because, at compile time, there is no reference to the caller method.
private void CallViaDynamicInvocation()
{
Console.WriteLine("Calling via dynamic invocation:");
object arg = new C();
if (<>o__0.<>p__0 == null)
{
Type typeFromHandle = typeof(C);
CSharpArgumentInfo[] array = new CSharpArgumentInfo[1];
array[0] = CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null);
<>o__0.<>p__0 = CallSite<Action<CallSite, object>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "SayMyName", null, typeFromHandle, array));
}
<>o__0.<>p__0.Target(<>o__0.<>p__0, arg);
}
I have to admit that I don’t understand why this happens: if you want, drop a comment to explain to us what is going on, I’d love to learn more about it! π©
Event handlers can get the method name
Then, we have custom events.
We define events in one place, but they are executed indirectly.
private void CallViaEventHandler()
{
Console.WriteLine("Calling via events:");
var eventSource = new MyEventClass();
eventSource.MyEvent += (sender, e) => SayMyName();
eventSource.TriggerEvent();
}
public class MyEventClass
{
public event EventHandler MyEvent;
public void TriggerEvent() =>
// Raises an event which in our case calls SayMyName via subscribing lambda method
MyEvent?.Invoke(this, EventArgs.Empty);
}
So, what will the result be? “Who” is the caller of this method?
Calling via events:
The method name is CallViaEventHandler!
Again, it all boils down to how the method is generated at compile time: even if the actual execution is performed “asynchronously” - I know, it’s not the most obvious word for this case - at compile time the method is declared by the CallViaEventHandler
method.
CallerMemberName from the Class constructor
Lastly, what happens when we call it from the constructor?
public CallerMemberNameTests(IOutput output) : base(output)
{
Console.WriteLine("Calling from the constructor");
SayMyName();
}
We can consider constructors to be a special kind of method, but what’s in their names? What can we find?
Calling from the constructor
The method name is .ctor!
Yes, the actual method name is .ctor
! Regardless of the class name, the constructor is considered to be a method with that specific internal name.
Wrapping up
In this article, we started from a “simple” topic but learned a few things about how code is compiled and the differences between runtime and compile time.
As always, things are not as easy as they appear!
This article first appeared on Code4IT π§
I hope you enjoyed this article! Let’s keep in touch on LinkedIn, Twitter or BlueSky! π€π€
Happy coding!
π§