.NET 4.5 quietly introduced several attributes which are useful
for debugging and error reporting: CallerMemberName, CallerFilePath
and CallerLineNumber, all collectively referred to as "Caller Information". One of those,
CallerMemberName, is also very useful for MVVM apps and other apps
using INotifyPropertyChanged for change notifications.
Getting the calling function name
The CallerMemberName attribute, from the
System.Runtime.CompilerServices namespace, supplies the name of the
function which called the function with the attribute in its
parameter list. For example, if you have a function defined like
this:
private void DoSomething([CallerMemberName] string callingFunction = null)
{
if (callingFunction != null)
Debug.WriteLine("Calling function name is " + callingFunction);
else
Debug.WriteLine("Calling function not supplied.");
}
Then, you call it like this:
private void CallSomething()
{
DoSomething();
}
The callingFunction parameter will be filled with the name of
the calling function. In this case, the output indicate that the
calling function is "CallSomething". This works because the
property uses the CallerMemberName attribute and has a default
value. The default value is a required part of this pattern.
Using CallerMemberName in property change notification
XAML uses an interface and event based property change
notification pattern to alert the binding system when a non-static
property has been changed. (WPF supports binding to static
properties, and although it uses the event, it does not use the
interface as there's no class instance.) The interface used is
INotifyPropertyChanged, and regardless of how you feel about the
requirement to use this interface for change notification, it seems
it is here to stay.
The problem
One real issue with INotifyPropertyChanged is the requirement to
pass in the name of the calling property as a string. Some time
ago, I spoke with the people who originally designed this approach,
and despite me not caring much for it, I'm convinced that it was,
in fact, the correct approach to use. It provides the best
performance and flexibility compared to other approaches, and
required no changes to the language specs or the CLR.
Code using this approach, without the benefit of any MVVM
toolkits or other base classes, typically looked something like
this:
public class PuzzleLevel : INotifyPropertyChanged
{
private string _title;
public string Title
{
get { return _title; }
set
{
if (value != _title)
{
_title = value;
OnPropertyChanged("Title");
}
}
}
// ...
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
This leads to countless problems when properties are refactored
and renamed, but the string (which is not verified by the compiler)
is not changed. For example, here' I've renamed the Title property
to Name. This will compile just fine:
public class PuzzleLevel : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Title");
}
}
}
// ...
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Not only will this compile just fine, but it will run as well.
The only real indication of a problem will be the field not
updating in the UI when changed from someplace else in the code, or
another part of the UI. This can be really easy to miss in
testing.
The solution
There are multiple ways to solve this, but the newest, and
perhaps most elegant, is an approach using the CallerMemberName
attribute. This approach is used by the default Windows Store XAML
app templates in the BindableBase class:
public abstract class BindableBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
this.OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
If you were passing in strings for property names before, this
base class will simplify your code and also save you from the
difficult-to-track binding bugs you'd get if you changed property
names without updating the string in the property change
notification call. Now, your setters can be as simple as this:
public class PuzzleLevel : BindableBase
{
private string _title;
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private int _highScore;
public int HighScore
{
get { return _highScore; }
set { SetProperty(ref _highScore, value); }
}
private bool _isLocked;
public bool IsLocked
{
get { return _isLocked; }
set { SetProperty(ref _isLocked, value); }
}
}
There were other ways to do this, but they all involved more
steps and additional parameters. The use of third-party MVVM
toolkits and lambda expressions simplified that somewhat. Those
will still work and work well, but for new code, I recommend you
consider using the built-in CallerMemberName attribute
approach.
The CallerMemberName (and other Caller* functions) are
changed into literal values at compile time, so
there's no runtime reflection lookup or similar performance hit
like that encountered by other methods. This, combined
with its ease of use, makes it a no-brainer to use.
This approach also works with VB with appropriate syntax
changes.