DEV Community

Matt Eland
Matt Eland

Posted on • Originally published at killalldefects.com on

Caller Member Info, C#, and You

Four "Hello my name is" nametags with the words "CalculateAnswer", "ExportGridContents", "FetchExternalData", and "HideBody"

A number of years ago .NET added a small feature called Caller Member Info. This isn’t the biggest feature out there, and you’re not going to use this frequently unless you’re doing a lot of XAML development, but it’s good to have in your tool belt.

Caller Member Info attributes allow you to simplify your code by providing more information to individual methods without having to pass in explicit values.

In this short article I’ll give you an overview of the caller member info attributes, how they work, and the problems they can solve.

The Problem

Let’s say you have code that does some standard checks:

There’s some obvious code duplication here. We can clean it up by extracting a method:

Okay, cool. We now have logic in one method, but having to pass in the method name is a little irritating.

This is where CallerMemberNameAttribute comes into play. Let’s take a look at an improved version of the code and then discuss how it works:

Note here that the code is barely changed. We added [CallerMemberName] to the methodName parameter on CheckInput and also made methodName optional by giving it a default value of an empty string.

The optional parameter allows the code to compile, and then .NET inspects the CallerMemberName attribute at runtime and injects IL code to switch from the default value to that of the name of the method that just called the executing method.

For example, in DoBar(), CheckInput is called and the methodName parameter is not specified. Instead of taking the default value of "", the CallerMemberName attribute sets the value of methodName to DoBar, the caller.

How is this useful?

Admittedly it’s an interesting parlor trick for developer parties (I don’t know for sure, I don’t get invites to those), but how is this actually useful?

For one, logging methods can frequently benefit from knowing who called them.

For another, if you do work with model view view model frameworks (MVVM) such as XAML that rely on things like INotifyPropertyChanged, you frequently need to state the name of the property that changed. This allows our code to be very simple:

We also copy and paste code more than we’d like to admit – even small pieces of code. Why? Because we hate boilerplate and logging, validation, and property changed notifications are all boiler-plate things.

If we copy and paste and forget to change the name, mistype it, or we rename a method or property down the road without changing the string value, these all can lead to bugs and confusion. Admittedly, the nameof keyword can help with the renaming scenario, but the point remains: Code you don’t have to write won’t break.

This is a point I’m quite fond of making, but it’s true. If you don’t have to code something, you won’t make a mistake doing it.

Other Caller Member Info Attributes

Okay, so we talked at length at how CallerMemberName gets us the name of the method or property that invoked the method.

Beyond that, we also have the CallerLineNumberAttribute and CallerFilePathAttribute. Both work similarly to CallerMemberNameAttribute(though CallerLineNumber works with an integer) and will provide orienting information on the source code as it existed at compile time.

Realistically you won’t use these nearly as much as CallerMemberName. The only reason I’ve needed either of these two attributes has been around error logging code.


If you’d like to read more, check out Microsoft’s documentation on Caller Member Info.

If you do use any of these attributes for something I haven’t mentioned, I’d love to hear about it. Please send me a note in the comments or on twitter.

The post Caller Member Info, C#, and You appeared first on Kill All Defects.

Top comments (5)

Collapse
 
johnn6tsm profile image
JohnN6TSM

I used callerfilepathattribute to create a slick unit Test database. I store data for a unit test method in a file next to the source file. I have a test method that writes its argument to the database if shift and alt are down, otherwise it checks the string against the database. Makes it easy to veeify complicated strings have not changed.

Collapse
 
integerman profile image
Matt Eland

This sounds similar to how Snapper works for identifying snapshot files for a unit test.

Collapse
 
seangwright profile image
Sean G. Wright

I've used this in the past for some internal infrastructure pieces... but the one thing I don't like about this attribute is that it requires you to add a parameter to a method that the caller should not use.

It's like I create an API and expose something that I don't want the caller to use.

public class Logger
{
    public void Log(string message, string doNotUseThisParameter);
}

This breaks several SOLID principles and is downright confusing for those not familiar with how the [CallerMemberName] attribute works.

So, I normally don't expose any infrastructure that uses it and I try to keep its use very limited.

That said, it can be useful, and I'm sure there are devs out there that will discover it through this post.

Thanks!

Collapse
 
luturol profile image
Rafael Ahrons

Loved the article!!

Didn't know about this feature until now. When should I use it instead of overload or just passing an nullable parameter?

Collapse
 
integerman profile image
Matt Eland

My argument is that you should always use CallerMemberName in cases where you want to pass a member name as it prevents mistakes.

Really, the only scenario I could use it and don't is like this:

public void LogSomething(string message, [CallerMemberName] caller = "")
  => LogSomething(message, LogPriority.Normal, caller);

public void LogSomething(string message, LogPriority priority, [CallerMemberName] caller = "") {
  // Do some logging
}

In this example, if I didn't pass in caller to the other method via the overload, caller would default to LogSomething.

So, really, that's the only case where I would explicitly set caller - when I need to pass data around and don't want CallerMemberName to overwrite the value.