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 :
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
Entity Framework - Invalid Column Name '*_Id"
Why Is Addrange Faster Than Using a Foreach Loop
How to Throttle Requests in a Web API
How am I Misusing the Null-Coalescing Operator? Is This Evaluating "Null" Correctly
Regular Expression to Allow Backslash in C#
Argumentoutofrangeexception on Initialized List
The Notorious Yet Unaswered Issue of Downloading a File When Windows Security Is Required
Efficient Way to Delete a Line from a Text File
Testinitialize VS Classinitialize
How to Determine a Session Variable Is Null or Empty in C#
System.Net.Webclient Unreasonably Slow
How to Suppress a Dialog Box Displayed by Code That I Can't Change