One of the central aspects of implementing the MVVM pattern is the ubiquitous invocations of INotifyPropertyChanged.PropertyChanged in property setters. The simplest version that is commonly seen just passes the property name as a string and builds an EventArgs object in the shared NotifyPropertyChanged method.

public int MyProperty
{
	get { return _myProperty; }
	set
	{
		if (_myProperty == value)
			return;
		_myProperty = value;
		NotifyPropertyChanged("MyProperty");
	}
}

Over time, the verbosity of the basic pattern boilerplate has been reduced in various ways, usually with one of two goals: shorter code or stronger links between the event args and the name of the property itself. One nice technique takes advantage of expressions to create a compile time check on the argument’s match to the a valid property name.

public int MyProperty
{
	get { return _myProperty; }
	set
	{
		if (_myProperty == value)
			return;
		_myProperty = value;
		NotifyPropertyChanged(() => MyProperty);
	}
}

One of the best techniques to emerge so far came about with the release of .NET 4.5 and C# 5 which added the CallerMemberName attribute to automatically resolve the name of the calling property. This provides even easier code and a dead simple implementation in the calling method as well, but does lose out on the ability to make compiler checked calls from outside of the property itself where the property name still must be passed as a string.

public int MyProperty
{
	get { return _myProperty; }
	set
	{
		if (_myProperty == value)
			return;
		_myProperty = value;
		NotifyPropertyChanged();
	}
}

On the other end of the spectrum, pulling all of the responsibility for constructing event args up to the property requires a lot of extra code but can also allow for skipping the allocation of a new PropertyChangedEventArgs object for each call.

static readonly PropertyChangedEventArgs MyPropertyArgs = new PropertyChangedEventArgs("MyProperty");
public int MyProperty
{
	get { return _myProperty; }
	set
	{
		if (_myProperty == value)
			return;
		_myProperty = value;
		NotifyPropertyChanged(MyPropertyArgs);
	}
}

It’s great to have all of these options for structuring your code, but how do these all affect performance when lots of data is changing at once? To do some analysis I set up a ViewModel which used each of these variations and ran large timed batches of property changes to get some comparison times. I used each of the four patterns above, with an additional variation on the statically declared PropertyChangedEventArgs which takes advantage of the new nameof operator in C# 6 (VS 2015) to provide compile time checking of the property name.

  1. The expression method, while providing compile time safety for .NET 4.0 and earlier, is slower to such a degree that it probably shouldn’t be used in any sort of data-intensive UI or in any .NET 4.5+ situation.
  2. The corresponding compiler checked versions of the both of the string based approaches turn out to be a little slower but not enough that it should be noticeable in any but the most performance sensitive scenarios. The added safety of the checks (provided you’re using a new enough language version) should be worth the small cost.
  3. Avoiding repeated allocations of PropertyChangedEventArgs is definitely the fastest method, but based on CPU time probably not a significant enough improvement in most cases to justify the large amount of extra code needed for each property. It is possible that taking memory usage and garbage collection impact into account might provide a more decisive advantage but overall the memory impact of the tiny PropertyChangedEventArgs is likely to be dwarfed in most cases by that of corresponding UIElement objects which are receiving the notifications.

CallerMemberName is probably the best compromise most of the time since it gives up very little in terms of performance but makes for the cleanest code, at least until hopefully some form of automatic notifications on auto-properties can be baked into some future version of C#.

For details of the implementations of each strategy download the test code: Example Implementations