Introduced in .NET Framework v4.5 (as a C# 5.0 compiler
feature), the CallerMemberNameAttribute allows you to determine the method or
property name of the caller of a method.
This comes in very handy if you’re adding logging code, or in
the case of with WPF you’re using models based on the INotifyPropertyChanged
interface, where you need to fire the PropertyChanged event passing along the
name of the property that’s changed.
Prior to the availability of CallerMemberName, you had a
couple of choices when calling the PropertyChanged event:
- Pass a hard-coded string to identify the property name.
This has clear drawbacks – what if the property name changes/identifying code dependent on the property?
- Parsing an expression tree that represents a concrete class
property
Handy, because you’re referring to a class's property
There are plenty of examples available showing how to parse
an expression tree (see below), but be very careful, parsing an expression tree
is very, very slow compared to using
the CallerMemberName or passing in a string to identify the property.
Results
If your application
can target v4.5 then, in terms of performance, it’s strongly recommended that
you replace any expression tree parsing with the CallerMemberNameAttribute.
The chart below shows how slow repeatedly parsing an
Expression in order to determine the bound property, can be, when compared to CallerMemberNameAttribute
or a string value:
In order to determine these numbers, I created a suite of tests,
that all performed the same task of firing a property change notification
change for a collection of 100 objects (each object having two properties
changed 200 times).
The statistical 95th percentile number of
milliseconds for each test was calculated (rather than using a less accurate average) and plotted above.
Implementation
|
No. Of Objects
|
Properties to Change
|
No. of Times Each Property Changed
|
Time Taken Milliseconds
|
Expression
Parsing
|
100
|
2
|
200
|
238
|
CallerMemberNameAttribute
|
100
|
2
|
200
|
4
|
Hard
Coded String
|
100
|
2
|
200
|
4
|
Clearly, if you only have a few objects to track or the
number of property change is equally low then this will not be such a problem,
but as the volumes increase you might notice a difference – and this is before
you factor in the time it takes for your observer action to complete. In my case I was simply incrementing a counter
to keep the action work to a minimum.
Code Snippets
Before delving into the test code, I’ll summarise a few basic
snippets for the three standard INotifyPropertyChanged implementations.
1. CallerMemberName
Using the CallerMemberName attribute to notify a change in
the Customer’s Name (in addition you’d usually check that the old property
value is different to the new incoming value before notifying the change)
public class Customer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
}
2. Hard Coded Property Name Strings
Contrast this with the older style of passing along a string
to identify the property.
It’s simple, it works, but you have no easy way of checking
if the name of bound property changes or the impact of changing that name or
how it’s really used – a potential maintenance nightmare.
public class Customer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propName));
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
}
3. Expression Parsing
You get the benefits of compile-time checking and it’s easy to
determine property usage in your code…but it can be very slow.
public class Customer : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string _name; public string Name { get { return _name; } set { _name = value; OnPropertyChanged(() => Name); } } protected void OnPropertyChanged<T>(Expression<Func<T>> expression) { var handler = PropertyChanged; if (handler != null) { var propName = GetPropertyNameFromExpression(expression); handler(this, new PropertyChangedEventArgs(propName)); } } private static string GetPropertyNameFromExpression<T>(Expression<Func<T>> exp) { var memberExpression = exp.Body as MemberExpression ?? ((UnaryExpression)exp.Body).Operand as MemberExpression; if (memberExpression == null) throw new ArgumentException( String.Format("[{0}] is not a member expression", exp.Body)); return memberExpression.Member.Name; } }
Test Cases
In order to run the tests, I prefer to use the NUnit
framework to create the tests and then run them using Resharper (in a handy single
click).
Using the Package Manager console, you’ll need to run Install-Package
NUnit to get NUnit downloaded from NuGet and referenced in your
project.
I have a general purpose base class that I often use when
creating timed test code, TimedActionBase.cs.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using NUnit.Framework;
[TestFixture]
public abstract class TimedActionBase
{
protected abstract void TimedAction();
…
There is an abstract void method, TimedAction(), which contains the code that I want to measure and two
public methods that NUnit can see:
[Test]
public void SinglePassRunner()
{
TimedActionRunner();
}
[Test]
public void MultiPassRunner()
{
const int NoOfPasses = 180;
for (var i = 0; i < NoOfPasses; i ++)
{
TimedActionRunner();
}
}
TimedActionRunner is the main controlling method,
responsible for timing the method under test and generating the output
statistics via - CalculateStatistics()
private void TimedActionRunner()
{
const int DefaultTestCycles = 100;
// Prerun TimedAction method in case there's any JIT overhead
TimedAction();
_durations.Clear();
for (var cycle = 1; cycle <= DefaultTestCycles; cycle++)
{
_timer.Start();
TimedAction();
_timer.Stop();
var duration = _timer.Elapsed;
_durations.Add(duration);
_timer.Reset();
}
CalculateStatistics();
}
In each of the performance test, I’m testing a specific
implementation of a Car class (CarBase)
public abstract class CarBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected CarBase(int id)
{
Id = id;
}
public int Id { get; private set; }
public abstract string Name { get; set; }
public abstract int Speed { get; set; }
public abstract int Mileage { get; set; }
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The only difference between each of the tests is how OnPropertyChanged is called (it
needs a string property in all cases).
To create the actual tests I extend the TimedActionBase class
further with a NotifyTestBase class.
This serves as the core for each notification test.
public abstract class NotifyTestBase : TimedActionBase
{
static class TestParams
{
public const int NumberOfCars = 100;
public const int NumberOfLaps = 200;
public const int MileageIncrement = 2;
public const int SpeedIncrement = 3;
}
private readonly IEnumerable<CarBase> _cars;
private int _propertyChangedCount;
protected NotifyTestBase()
{
_cars = Enumerable
.Range(1, TestParams.NumberOfCars)
.Select(i =>
{
var car = CreateCar(i);
car.PropertyChanged += (sender, args) => _propertyChangedCount ++;
return car;
})
.ToList();
}
protected override void TimedAction()
{
for (var i = 0; i < TestParams.NumberOfLaps; i++)
{
foreach (var car in _cars)
{
car.Mileage += TestParams.MileageIncrement;
car.Speed += TestParams.SpeedIncrement;
}
}
}
protected abstract CarBase CreateCar(int id);
}
You’ll see that NotifyTestBase overrides the base class’s TimedAction()
method. It ensures that for each test, the same number of objects are modified
and the same number of times between each CarBase implementation by overriding
the factory method CarBase CreateCar().
All that’s required for each scenario is to create a
concrete implementation of CarBase and have a new instance of that type
returned in the test by overriding CreateCar(int
i).
Taking CallerMemberName as the first example, I’ll extend a
CarBase with a new class NotifyByCallerMemberNameCar:
public class NotifyByCallerMemberNameCar : CarBase
{
public NotifyByCallerMemberNameCar(int id) : base(id)
{}
private string _name;
public override string Name
{
get { return _name; }
set
{
SetAndRaiseIfChanged(ref _name, value);
}
}
private int _speed;
public override int Speed
{
get { return _speed; }
set
{
SetAndRaiseIfChanged(ref _speed, value);
}
}
private int _mileage;
public override int Mileage
{
get { return _mileage; }
set
{
SetAndRaiseIfChanged(ref _mileage, value);
}
}
private void SetAndRaiseIfChanged<T>(ref T backingField, T newValue, [CallerMemberName]string propName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, newValue))
{
return;
}
backingField = newValue;
OnPropertyChanged(propName);
}
}
You can see that each of the property sets calls SetAndRaiseIfChanged
– no need to pass in the property name as the propName string is decorated with the [CallerMemberName] attribute.
In order to test this I need to create test class that
extends NotifyTestBase, with a method that overrides CreateCar to return the concrete NotifyByCallerMemberNameCar class:
public class NotifyByCallerMemberNameTest : NotifyTestBase { protected override CarBase CreateCar(int i) { return new NotifyByCallerMemberNameCar(i); } }
The NotifyByExpressionTest looks similar, we return a NotifyByExpressionCar
instance, with each property set making use of an Expression:
public class NotifyByExpressionTest : NotifyTestBase
{
protected override CarBase CreateCar(int i)
{
return new NotifyByExpressionCar(i);
}
}
public class NotifyByExpressionCar : CarBase
{
public NotifyByExpressionCar(int id) : base(id)
{}
private string _name;
public override string Name
{
get { return _name; }
set
{
SetAndRaiseIfChanged(ref _name, value, () => Name);
}
}
private int _speed;
public override int Speed
{
get { return _speed; }
set
{
SetAndRaiseIfChanged(ref _speed, value, () => Speed);
}
}
private int _mileage;
public override int Mileage
{
get { return _mileage; }
set
{
SetAndRaiseIfChanged(ref _mileage, value, () => Mileage);
}
}
protected void SetAndRaiseIfChanged<T>(ref T backingField, T newValue,
Expression<Func<T>> expression)
{
if (EqualityComparer<T>.Default.Equals(backingField, newValue))
{
return;
}
var propName = GetPropertyNameFromExpression(expression);
backingField = newValue;
OnPropertyChanged(propName);
}
private static string GetPropertyNameFromExpression<T>(
Expression<Func<T>> expression)
{
var memberExpression = expression.Body as MemberExpression
?? ((UnaryExpression)expression.Body).Operand as MemberExpression;
if (memberExpression == null)
throw new ArgumentException(String.Format("[{0}] is not a member expression", expression.Body));
return memberExpression.Member.Name;
}
}
and finally NotifyByStringTest - for the hard-coded string property
public class NotifyByStringTest : NotifyTestBase
{
protected override CarBase CreateCar(int i)
{
return new NotifyByStringCar(i);
}
}
public class NotifyByStringCar : CarBase
{
public NotifyByStringCar(int id) : base(id)
{}
private string _name;
public override string Name
{
get { return _name; }
set
{
SetAndRaiseIfChanged(ref _name, value, "Name");
}
}
private int _speed;
public override int Speed
{
get { return _speed; }
set
{
SetAndRaiseIfChanged(ref _speed, value, "Speed");
}
}
private int _mileage;
public override int Mileage
{
get { return _mileage; }
set
{
SetAndRaiseIfChanged(ref _mileage, value, "Mileage");
}
}
protected void SetAndRaiseIfChanged<T>(ref T backingField, T newValue, string propName)
{
if (EqualityComparer<T>.Default.Equals(backingField, newValue))
{
return;
}
backingField = newValue;
OnPropertyChanged(propName);
}
}
You can download the full test solution from here.
Further Information
I was interested in finding out how the use of CallerMemberNameAttribute
affects my assemblies. It turns out CallerMemberNameAttribute
is part of the System.Runtime.CompilerServices namespace:
The System.Runtime.CompilerServices namespace provides functionality
for compiler writers who use managed code to specify attributes in metadata
that affect the run-time behavior of the common language runtime. This namespace is primarily for compiler
writers
Normally you use Reflection to get hold of types decorated
with an attribute at runtime, but the C# 5.0 compiler looks for this attribute
when compiling your code.
I used ILDASM, Microsoft’s MSIL dissembler, to examine assembly
that was created. It’s interesting to note
that the property sets created using CallerMemberName AND a string constant are
identical:
No comments:
Post a Comment