Implementing INotifyPropertyChanged - does a better way exist?
Solution 1
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);
}
Solution 2
As of .Net 4.5 there is finally an easy way to do this.
.Net 4.5 introduces a new Caller Information Attributes.
private void OnPropertyChanged<T>([CallerMemberName]string caller = null) {
// make sure only to call this if the value actually changes
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(caller));
}
}
It's probably a good idea to add a comparer to the function as well.
EqualityComparer<T>.Default.Equals
Also see Caller Information (C# and Visual Basic)
Solution 3
I really like Marc's solution, but I think it can be slightly improved to avoid using a "magic string" (which doesn't support refactoring). Instead of using the property name as a string, it's easy to make it a lambda expression :
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, () => Name); }
}
Just add the following methods to Marc's code, it will do the trick :
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
if (selectorExpression == null)
throw new ArgumentNullException("selectorExpression");
MemberExpression body = selectorExpression.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
OnPropertyChanged(body.Member.Name);
}
protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(selectorExpression);
return true;
}
BTW, this was inspired by this blog post.
Solution 4
There's also Fody which has a AddINotifyPropertyChangedInterface add-in, which lets you write this:
[AddINotifyPropertyChangedInterface]
public class Person
{
public string GivenNames { get; set; }
public string FamilyName { get; set; }
}
...and at compile time injects property changed notifications.
Solution 5
I think people should pay a little more attention to performance; it really does impact the UI when there are a lot of objects to be bound (think of a grid with 10,000+ rows), or if the object's value changes frequently (real-time monitoring app).
I took various implementation found here and elsewhere and did a comparison; check it out perfomance comparison of INotifyPropertyChanged implementations.
Here is a peek at the result
P.K
All code is poetry. But, not every coder is poet. I am far off from being a poet and this keeps me on toes and makes me constantly improve and learn new things.
Updated on July 08, 2022Comments
-
P.K almost 2 years
Microsoft should have implemented something snappy for
INotifyPropertyChanged
, like in the automatic properties, just specify{get; set; notify;}
I think it makes a lot of sense to do it. Or are there any complications to do it?Can we ourselves implement something like 'notify' in our properties. Is there a graceful solution for implementing
INotifyPropertyChanged
in your class or the only way to do it is by raising thePropertyChanged
event in each property.If not can we write something to auto-generate the piece of code to raise
PropertyChanged
event? -
Thomas Levesque almost 15 yearsNice trick Marc ! I suggested an improvement to use a lambda expression instead of the property name, see my answer
-
Anton Tykhyy almost 15 yearsDevXpress Xpo does it this way.
-
Marc Gravell almost 15 years@Thomas - the lambda is all well and good, but it adds a lot of overhead for something that is actually very simple. A handy trick, but I'm not sure it is always practical.
-
Thomas Levesque almost 15 years@Marc - Yes, it can probably degrade performance... However I really like the fact that it's checked at compile time, and is correctly refactored by the "Rename" command
-
scoopr over 14 yearsIn order to stay away from magic strings, you can also use the code from this blog post: blog.m.jedynak.pl/2009/02/static-typed-propety-names.html
-
AlSki over 12 yearsThere's at least one framework using this method, ReactiveUI.
-
T. Fabre over 12 yearsCode snippets are nice when you write the code, but can become a pain in maintenance.
-
abatishchev over 11 yearsBrilliant! But why is it generic?
-
Daniel Little over 11 years@abatishchev I guess it doesn't have to be, I was just playing with the idea of having the function set the property as well. I'll see if I can update my answer provide the full solution. The extra examples do a good job that in the meantime.
-
J. Lennon over 11 yearsIt was introduced by C # 5.0. It has nothing to do with .net 4.5, but this is a great solution!
-
Daniel Little over 11 years@J. Lennon .net 4.5 still has something to do with it, after all the attribute comes from somewhere msdn.microsoft.com/en-au/library/…
-
J. Lennon over 11 years@Lavinski change your application to eg .NET 3.5 and see what will work (in vs2012)
-
J. Lennon over 11 years@Lavinski sorry, I was wrong. CallerMemberName was introduced by other implementations for older versions of .NET (async bridge and microsoft.bcl)
-
Bob about 11 years@Lavinski It is possible to use this while targeting older framework versions, with a minor tweak: stackoverflow.com/a/13382257/1030702
-
Cocowalla almost 11 yearsA great approach, but it does have some shortcomings: awkwardcoder.blogspot.co.uk/2012/07/…
-
Marc Gravell almost 11 years@PKKG that means nobody has subscribed to the event
-
Pedro77 over 10 yearsWhat about: private void RaisePropertyChanged( [CallerMemberName] string caller = "") { if ( PropertyChanged != null ) { PropertyChanged( this, new PropertyChangedEventArgs( caller ) ); } }
-
Gusdor over 10 years@ThomasLevesque The refactor ability supersedes the performance hit imo. Never pre-optimise where sacrificing maintainability is concerned.
-
Marc Gravell over 10 years@Gusdor fortunately, with C#5 there is no need to compromise - you can get the best of both via (as Pedro77 notes)
[CallerMemberName]
-
Gusdor over 10 years@MarcGravell I wish I could know this joy. I'm still supporting windows XP and have to use .Net 4 :*(
-
Marc Gravell over 10 years@Gusdor the language and framework are separate; you can use the C# 5 compiler, target .NET 4, and just add the missing attribute yourself - it will work fine. It just has to have the correct name and be in the correct namespace. It does not need to be in a specific assembly.
-
itsho over 10 yearsIn case reflection is not an Issue in , one can use
MethodBase.GetCurrentMethod().Name.Substring(4);
instead of writing the Property name every time. the 4 is to trim the"set_"
string. I know, it is ugly... -
Triynko over 10 yearsA free tool called Fody seems to do the same thing, functioning as a generic compile-time code injector. It's downloadable in Nuget, as are its PropertyChanged and PropertyChanging plugin packages.
-
JYL over 10 years-1 : there is no performance overhead : CallerMemberName are changed into literal values at compile time. Just try and decompile your app.
-
Bionic over 10 yearshere is the according question and answer: stackoverflow.com/questions/22580623/…
-
Peijen about 10 years@JYL, you are correct that CallerMemberName did not add a large overhead. I must have implemented something wrong last time I tried it. I will update the blog and answer to reflect the benchmark for CallerMemberName and Fody implementation later.
-
Chris Fong about 10 years@Gusdor [CallerMemberName] is available for .Net 4.0 using the Microsoft.Bcl nuget package! nuget.org/packages/Microsoft.Bcl
-
Dan Bechard about 10 yearsCode snippets do not solve the problem at all. The issue is magic strings not being renamed when refactoring property names.
-
nawfal almost 10 yearsThis is pretty cool, I like it more than expression approach. On the downside, should be slower.
-
Jay Carlton over 9 yearsIs it possible to write an intermediate parent class that exposes the PropertyChagned event and implements SetField<>? It seems like a code smell to have this method defined in every class that implements INotifyPropertyChanged. Likewise, can we not squeeze out the "notifying public property" idiom somehow?
-
Marc Gravell over 9 years@Jay there are ways of doing it with static methods so that you only need to declare it once, sure
-
Jay Carlton over 9 yearsI just noticed
BindableBase
does what I was looking for. Thanks, @Marc. -
MCattle over 9 yearsThis is a nice solution, but the only downside is that there's a small performance hit involving boxing / unboxing.
-
TiMoch over 9 years@MCattle if we are down to measuring the impact of boxing/unboxing, then we may as well remove the usage of the dictionary and provide Get/Set signatures that take a ref backing field. We may as well provide the means for caching PropertyChangedEventArgs objects
-
Stéphane Gourichon over 9 yearsIs that fast ? Isn't access to stack frame bound to some permission requirement ? Is that robust in a context of using async/await ?
-
Miquel over 9 yearsI would suggest to use
protected T Get<T>(T defaultValue, [CallerMemberName] string name = null)
and also checkif (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name)))
in Set (to raise & save when first set to default value) -
Bruno Brant over 9 yearsVery late, this meant going through reflection, which meant a performance hit. It could be acceptable, but setting a property isn't a place where I'd like my application to spend to many cycles.
-
stakx - no longer contributing over 9 years+1 for a great solution! I wish I had discovered this answer sooner. Incidentally, quite a few related concerns can be solved much easier with a dictionary-based
INotifyPropertyChanged
than if each property had its own backing field: for example, creating & restoring object snapshots (thinkIEditableObject
). -
TiMoch over 9 years@Miquel adding support for custom default values can be useful for sure, however you should be careful to only raise the changed event when the value actually changed. Setting a property to the same value it had should not raise events. I must admit in most cases it's harmless, howeverI've been bit quite a few times with properties being set thousands of time to the same value with events destroying UI responsiveness.
-
TiMoch over 9 years@stakx I have a few applications that build on this to support the memento pattern for undo/redo or to enable the unit of work pattern in applications where nhibernate isn't usable
-
Nathaniel Elkins over 9 years@BrunoBrant Are you sure there is a performance hit? According to the blog post the reflection happens during compile time rather than runtime (i.e. static reflection).
-
Bruno Brant about 9 years@StéphaneGourichon No, it isn't. Accessing the stack frame means a considerable performance hit on most cases.
-
Bruno Brant about 9 years@NathanielElkins you are correct. I was remembering that I actually had a performance hit when using expressions, but the reason for that is that I needed to compile the expression afterwards. One more tip: this fails if the users points to a property on a sublevel, ie, "foo.bar.baz". You can expand the algorithm to cover that.
-
ASA about 9 yearsI believe your whole OnPropertyChanged<T> is obsolete with the nameof operator of C# 6, making this monster a bit sleeker.
-
Thomas Levesque about 9 years@Traubenfuchs, actually, C#5's CallerMemberName attribute makes it even simpler, since you don't need to pass anything at all ...
-
Dib about 9 yearsIs there a neat way to use this when you just have public getters and are using private setters with a ChangeMyProperty(string value) type of method call to follow a more DDD pattern? I assume [CallerMemberName] will report incorrectly with this approach?
-
Andrew Savinykh about 9 yearsWhy do you need
TDeclaration
type parameter onPropertyImplementation
? Surely you can find appropriate type to call (not callvirt) the getter/setter from with onlyTImplementation
? -
Kelqualyn almost 9 yearsTImplementation works in most of cases. Exceptions are: 1. Properties redefined with "new" C# keyvord. 2. Properties of explicit interface implementation.
-
Fandi Susanto over 8 yearsWhat black magic is this? Can somebody point to some reference or explanation? What is
[CallerMemberName]
andPropertyChanged?.Invoke
? -
Marc Gravell over 8 years@FandiSusanto
[CallerMemberName]
is a parameter attribute that the C# 5 compiler recognizes, and automatically injects the name of the calling method/member (as a constant string, so interned, etc); The?.
syntax is C# 6 fornull
propagation; if the object on the left is non-null
, the method will be invoked; if the object is null,it won't. If there was a return value implied, it would assume the valuenull
if the object wasnull
. -
evilkos over 8 yearsI really like this particular solution: short notation, no dynamic proxy stuff, no IL-meddling, etc. Although, you can make it shorter by removing the need to specify T every time for Get by making Get return dynamic. I know, this impacts runtime performance, but now the code for getters and setters can finally be always the same and in one line, praise the Lord! P.S. you should take additional care inside your Get method (one time when you write the base class) when returning default values for valuetypes as dynamic. Be sure to always return correct default values (it can be done)
-
Panagiotis Kanavos over 8 years@MarcGravell perhaps you could clean up the code for C# 6, so this can be avoided? I now regret answering instead of voting to close
-
IAbstract about 8 yearsI'm interested to know which version of Castle you are using. I am using 3.3.0 and the CreateClassProxy method does not have those parameters:
type
,interfaces to apply
,interceptors
. -
IAbstract about 8 yearsNevermind, I was using the generic
CreateClassProxy<T>
method. Much different ...hmmm, wondering why so limited with the generic method. :( -
BrainSlugs83 about 8 yearsDownvoted. This is not automatic. -- Even the recommended way in C# 5 is lame. C# 6 adds a whole slew of built-in ways to do cool things with properties -- but not this, and it's a shame.
-
Marc Gravell about 8 years@BrainSlugs83 which bit is "not automatic"? There's actually something very interesting being discussed in C# 7 which may do what you want, but right now, without using additional tools (like postsharp), this is about the simplest you'll get.
-
Stanislav about 8 yearsI think an even better variation in C# 6 would be to call PropertyChanged directly from the setter of an attribute:
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CallingAttribute)));
-
Ofir about 8 yearsYes there is, you can see it at codereview.stackexchange.com/questions/13823/…
-
Ashoat almost 8 yearsI think this is exactly what OP was looking for when they asked "Can we ourselves implement something like 'notify' in our properties. Is there a graceful solution for implementing INotifyPropertyChanged in your class"
-
jmbpiano almost 8 yearsRe: the C#6 example- It's occasionally useful to pass
null
as the argument to thePropertyChanged
event (to indicate that all properties on the object have changed). Thus, it might still be valuable to utilize a separateSetField
method just so thatOnPropertyChanged
can be explicitly called withnull
for this purpose. -
evilkos over 7 years@MarcGravell I'd suggest an even shorter way. Yet another answer won't be read and it's in part had been suggested below, but, to summarize, I declare any property as follows:
public string MyProp { get { return Get(); } set { Set(value); } }
. The benefit is it's shorter (a one-liner if you wish) and it can be blindly copied without changing to make any property bindable. No backing fields also. The downside - having a base class. The trick is to make Get returndynamic
and Set takeobject
. And no, this does not slow down your app in any significant way. But it does speed up the dev. -
Adam Goodwin over 7 yearsWhat's the reasoning for your
SetField
method usingEqualityComparer<T>.Default.Equals(field, value)
rather than justfield == value
? Is this just to better support the case where typeT
has been implemented poorly? -
Marc Gravell over 7 years@AdamGoodwin generics do not work with operators like
==
-
Adam Goodwin over 7 yearsOops, I knew it would be something obvious – I should have just tried it.
-
Guge over 7 yearsLink is broken.
-
NSGaga-mostly-inactive over 7 yearsThis is the only graceful solution really, and it does work flawlessly as @CADbloke said. And I was skeptic about the weaver as well, but I checked/rechecked the IL code behind and it's perfect, it's simple, does all you need and none else. It also hooks and calls whatever method name you have designated in the base class for it, whether NotifyOnProp..., OnNotify... doesn't matter, so works well with any base class that you might have and that implements INotify...
-
Austin Rhymer almost 7 yearsIf you have a grid of 10,000+ in the UI then you should probably be combining approaches to handle performance, like paging where you only show 10, 50, 100, 250 hits per page...
-
CAD bloke almost 7 yearsYou can easily double-check what the weaver is doing, have a look at the build output window, it lists all the PropertyChanged things it has weaved. Using the VScolorOutput extension with the regex pattern
"Fody/.*?:",LogCustom2,True
highlights it in the "Custom 2" color. I made it bright pink so it's easy to find. Just Fody everything, it's the neatest way to do anything that has lots of repetitive typing. -
juFo almost 7 yearsany disadvantages of using this?
-
bh_earth0 over 6 yearsis there a way to not write that boilerplate in every new class i create ??
-
brakeroo over 6 yearsFurthermore in C# 6.0 you can use expression body notation:
protected void OnPropertyChanged( [CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
-
ToolmakerSteve over 6 years
ObservableCollection
eliminates the need to implementOnPropertyChanged
, for simple situations. See How to: Create and Bind to an ObservableCollection, and ObservableCollection. -
Andrey Klochkov about 6 yearsAnd for C#7 we can skip the nameof(Name) param to make it a little shorter.
-
Marc Gravell about 6 years@AndreyKlochkov you're right - fixed; looks like that segment was added at a later date, but: all fixed
-
JDR almost 6 yearsOut of interest: why
SetField
and notSetProperty
? -
Marc Gravell almost 6 years@JDR because it takes, and assigns to, a reference to a field - not a reference to a property (there is no such thing as a reference to a property, unless you count
ref return
which is new) -
fstam over 5 yearsI've recently started to incorporate bellow check into the SetField method. I often copy and paste the code into a class and forget to inherit INotifyPropertyChanged. This gives me a clear reminder.
if (this != null && !(this is INotifyPropertyChanged)) { throw new Exception("Forgot to inherit INotifyPropertyChanged"); }
-
Mark Redman over 5 yearsshould the c# & code be: set => SetField(ref name, value, "name");
-
Marc Gravell over 5 years@MarkRedman no; the whole point of
[CallerMemberName]
is that it provides the caller name automatically -
Mark Redman over 5 years@MarcGravell, ok, makes sense, was sure I am am C#7, but indicated a missing parameter...
-
Marc Gravell over 5 years@MarkRedman that's odd - just to be sure, I did test it here before I replied, and it compiled fine
-
JAD over 5 yearsThis could probably benefit from C#8's upcoming default interface implementations. That way a lot of the boilerplate could possibly be moved into the interface declaration.
-
Rhyous over 5 yearsThis is pretty cool. However, in MVVM, the pattern is usually, in my experience, that the ViewModel wraps a model. So there is no backing field. Instead we need to set model.Name to a value. We can't pass model.Name in as a ref.
-
bytecode77 about 5 yearsNote that inlining may hide the
get_Foo
method in Release mode. -
Larry almost 5 years@mahmoudnezarsarhan no, it's not, I remember there was a slight change in the way it has to be configured, but Fody PropertyChanged is still alive and active.
-
AFract over 4 yearsVery interesting approach to prevent the burden of backing fields, thanks.
-
Bilal about 4 yearsAustin Rhymer , if you have larg data + 50 use data virtualisation no need to load all data it will load only the data that is visible on the current scolling displayed area !
-
t0b4cc0 about 4 yearsI found this absolutely great until I noticed that I do not have the new value on the event PropertyChanged. the field = value somehow does not happen / the field has not been updated in the class where i implemented this (and assigned an event where i want to work with the new value)
-
Marc Gravell about 4 years@t0b4cc0 that probably means that your
IEquatable<T>
implementation isn't right - see where we short-circuit if the two values are equal, atif (EqualityComparer<T>.Default.Equals(field, value))
- this usesIEquatable<T>
, or regularEquals(object)
if that isn't available -
t0b4cc0 about 4 years@MarcGravell thank you for the quick reply! I do not implement IEquatable myself so it defaults. I should clarify the issue: The two values are not equal. The line
field = value;
does happen but I can not retrieve that new value in the class I implemented the interface AND the function I put onto the event handler. Just after thefield = value;
theOnPropertyChanged
will be called. I assign an event to that (in the class where the field lives) but the Field will not have the new value there. If this is not intended behavior I will start a new topic. -
Marc Gravell about 4 years@t0b4cc0 I'd probably need to see it in context as a repro to understand exactly what your scenario is
-
t0b4cc0 about 4 years@MarcGravell It works perfect in another other class. I moved my code from on property changed between the brakets in the
if(SetField(...,..)){//my property changed code}
because ofc a test version should be here yesterday :) I will probably start anew topic if this becomes relevant for me again -
GisMofx almost 4 yearsI've been looking for this. Thanks for sharing your implementation. Are you still using this method?
-
Dan almost 4 years@GisMofx I haven't really done much WPF development recently but in my last WPF project, I did use this method, along with some of the others in NotifyPropertyChange from my repo that I linked
-
Damien over 3 yearsIt does seem to have been removed from Fody
-
Bill Menees about 3 yearsC#9 allows the
new PropertyChangedEventArgs(propertyName)
expression to be shortened to justnew(propertyName)
. -
Examath over 2 yearsI agree @Damien. As of version 3.4.0 this property is deprecated. using the
AddINotifyPropertyChangedInterfaceAttribute
, as recommended by documentation, works for me.