Is [Callermembername] Slow Compared to Alternatives When Implementing Inotifypropertychanged

Is [CallerMemberName] slow compared to alternatives when implementing INotifyPropertyChanged?

No, the use of [CallerMemberName] is not slower than the upper basic implementation.

This is because, according to this MSDN page,

Caller Info values are emitted as literals into the Intermediate
Language (IL) at compile time

We can check that with any IL disassembler (like ILSpy) : the code for the "SET" operation of the property is compiled exactly the same way :
Decompiled property with CallerMemberName

So no use of Reflection here.

(sample compiled with VS2013)

CallerMemberName in an extension (INotifyPropertyChanged)

Simply, it isn't possible. You need 3 pieces of information:

  • the event-handler instance (this.PropertyChanged, on the left)
  • the event-name (propertyName, supplied by the compiler)
  • the sender (sender)

sender cannot be inferred from any of the other information, and there is no compiler option to provide it. However, frankly, I would simply use an instance method instead:

protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if(handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}

Then the caller simply issues:

OnPropertyChanged(); // job done

You could of course have OnPropertyChanged call your static method, but that seems unnecessary.

In some ways it feels like we should be able to just pass in the INotifyPropertyChanged instance to use for both sender and to access the PropertyChanged, but of course we can't get the actual delegate from an event declaration.

Implementing INotifyPropertyChanged - does a better way exist?

Without using something like postsharp, the minimal version I use uses something like:

public class Data : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}

// props
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
}

Each property is then just something like:

private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}

which isn't huge; it can also be used as a base-class if you want. The bool return from SetField tells you if it was a no-op, in case you want to apply other logic.


or even easier with C# 5:

protected bool SetField<T>(ref T field, T value,
[CallerMemberName] string propertyName = null)
{...}

which can be called like this:

set { SetField(ref name, value); }

with which the compiler will add the "Name" automatically.


C# 6.0 makes the implementation easier:

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

...and now with C#7:

protected void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}

private string name;
public string Name
{
get => name;
set => SetField(ref name, value);
}

And, with C# 8 and Nullable reference types, it would look like this:

public event PropertyChangedEventHandler? PropertyChanged;

protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}

private string name;
public string Name
{
get => name;
set => SetField(ref name, value);
}

MVVM CallerMemberName and magic strings

The simple answer is, that you can't do that. As the name CallerMemberName indicates, it will contain the name of the caller.

For cases where you want to raise PropertyChanged for another than the current one, you will have to use one of the "old" ways.

In IDataErrorInfo you also have to use one of those ways, there is no alternative.

When implementing INotifyPropertyChanged, do navigation properties need to implement it too?

As you understand, you implement INotifyPropertyChanged (INPC) in order for the UI to update when a property on the model changes. So in your case, if you have something that is data binding to the Album property, it must implement INPC if there is a chance that it might change. Instead of using regular collection, you have a class called ObservableCollection that already implements INPC for you so you don't have to.

INotifyChangedProperty dynamic implementation

You can use the [CallerMemberName] if you are using .NET Framework 4.5

so your code will be :

using System.Runtime.CompilerServices;

class BetterClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// Check the attribute in the following line :
private void FirePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}

private int sampleIntField;

public int SampleIntProperty
{
get { return sampleIntField; }
set
{
if (value != sampleIntField)
{
sampleIntField = value;
// no "magic string" in the following line :
FirePropertyChanged();
}
}
}
}

as described in the question INotifyPropertyChanged : is [CallerMemberName] slow compared to alternatives?

Pattern for implementing INotifyPropertyChanged?

Eric Lippert explains this in details in this blog article: Events and races.

Basically, the idea is to avoid a race condition in case another thread unsubscribes the last handler for this event after you check PropertyChanged != null, but before you actually invoke PropertyChanged. If you make a local copy of the handler, this cannot happen (but you might end up calling a handler that's just been unsubscribed)

Is there any benefit of using the nameof operator instead of the CallerMemberNameAttribute to notify property changes in .NET 4.5.3?

About efficiency: using a string directly, CallerMemberNameAttribute, nameof are all exactly the same since the string is injected by the compiler at compile-time. There's no reflection involved.

We can see that using TryRoslyn that produces this for CallerMemberNameAttribute:

public string TestValue
{
get { return this.testValue; }
set { this.testValue = value; this.NotifyPropertyChanged("TestValue"); }
}
protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

And this for nameof:

public string TestValue
{
get { return this.testValue; }
set { this.testValue = value; this.NotifyPropertyChanged("TestValue"); }
}
protected virtual void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

Since at runtime all options are simply a string there's no problem with the WPF context.

About convenience: CallerMemberNameAttribute requires you to have an optional parameter while nameof doesn't but nameof requires you to specify the property while CallerMemberNameAttribute doesn't.

I predict that nameof would turn out to be so popular that it would be much simpler to use it instead.

Implementing INotifyPropertyChanged or similar custom event in base class

Proxies or AOP really are your only options for doing this automagically, so you will need to either find the time to investigate or do it the good old-fashioned way.



Related Topics



Leave a reply



Submit