Click here to Skip to main content
15,888,803 members
Articles / Programming Languages / C#
Tip/Trick

INotifyPropertyChanged convinient implementation

Rate me:
Please Sign up or sign in to vote.
2.26/5 (4 votes)
16 Apr 2017CPOL2 min read 27.9K   53   2   28
I work with WPF not too long, but every time when I see someone code implementation of INotifyPropertyChanged I become sad. I would like to present a convinient implementation of INotifyPropertyChanged, this class could be a base class for all your databindable classes.

Introduction

First of all - the INotifyPropertyChanged is used for implementation of Observable pattern and it is used for DataBinding in WPF.

I have seen many different implemetations, but I never see any convinient one.

The convinience for me is:

  1. Thread safety - the bindable property should be updated only from thread in which control binded to viewmodel was created.
  2. Properties Names - are constants which can be used everywere.
  3. Presence of tools, helpfull for realization of notifyable properties.
  4. Possibility to have bulk notifications (when changing of property takes a while and you updating a big view model some times it is better to update all fields and then sequentially notificate view about changes)

So I will share my knowlege, let't go...

Using the code

Let suppose that we have a class:

C#
public class BadExample : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

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

Let's implement a string property MyProperty:

C#
public String MyProperty
{
    get { return _myProperty; }
    set
    {
        _myProperty = value;
        OnPropertyChanged("MyProperty");
    }
}
private String _myProperty;

Here are the problems:

  1. Thread safety
  2. Usage of constant string
  3. If you will have some difficult logic which will change _myProperty field without using property, you should call OnPropetyChanged("MyProperty") manually
  4. You have to have simple properties, you unable to have bulk notifications

Sometimes you can see realizations with using constants fields:

C#
public String MyProperty
{
    get { return _myProperty; }
    set
    {
        _myProperty = value;
        OnPropertyChanged(MyPropertyName);
    }
}
private String _myProperty;
public const String MyPropertyName = "MyProperty";

After .NET 4.5 it is possible to use CallerMemberNameAttribute and write implementation of property like that:

C#
public class BadExample : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
       
    public String MyProperty
    {
        get { return _myProperty; }
        set
        {
            _myProperty = value;
            OnPropertyChanged();
        }
    }
    private String _myProperty;
}

It introduces some convinience but do not solves any problems of advanced coding, which includes multithreading or requirements to notify from another places.

In C# 6.0 it is possible to use nameof(MyProperty) construction...

Solution

My solution is to implement a base class named Notificator which will implement some usefull functionality.

Properies realisation will be such:

C#
///<summary>
///MyProperty
///</summary>
public String MyProperty
{
    get { return _MyProperty; }
    set
    {
        ChangeProperty(
            MyProperty_PropertyName,
            () => _MyProperty == value,
            () => _MyProperty = value);
    }
}
private String _MyProperty;
public static readonly String MyProperty_PropertyName = nameof(MyProperty);

Also somebody will say that it is still unconvinient because you should write too much code.

The answer is - the things are hidden in realization of base class, mentioned as Notificator, and simplicity of code writing is hidden in a codesnippet (you can read about code snippets in the internet, about how to add and how to use).

Here is the code snippet code: Download nprop.zip

Notificator

C#
public abstract class Notificator : INotifyPropertyChanged
{
    private static volatile Dispatcher _dispatcher = Dispatcher.CurrentDispatcher;

    public static Action<Action> SafeCall = action =>
    {
        if (_dispatcher.CheckAccess())
        {
            action();
        }
        else
        {
            _dispatcher.Invoke(action);
        }
    };

    protected Notificator()
    {
        NotifySubscribers = true;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Если не установлено уведомления бросаться не будут
    /// </summary>
    public Boolean NotifySubscribers { get; set; }

    [DebuggerStepThrough]
    public void ChangeProperty(
        String property,
        Func<Boolean> areEqual,
        Action setAction,
        params String[] additionalPropertyNames)
    {
        if (areEqual()) return;

        setAction();

        if (NotifySubscribers && PropertyChanged != null)
        {
            Raise_PropertyChanged(property);
            foreach (var additionalPropertyName in additionalPropertyNames)
            {
                Raise_PropertyChanged(additionalPropertyName);
            }
        }
    }

    private void Raise_PropertyChanged(String property)
    {
        if (property == null)
        {
            Debugger.Break();
        }

        var pc = PropertyChanged;
        if (pc == null) return;
        SafeCall(() => pc(this, new PropertyChangedEventArgs(property)));
    }

    [DebuggerStepThrough]
    public void RaiseAllPropertiesChanged()
    {
        var t = GetType();
        RaiseAllPropertiesChanged(t);
    }

    [DebuggerStepThrough]
    public void RaiseAllPropertiesChanged(Type t)
    {
        var fields =
            t.GetFields(
                BindingFlags.Public |
                BindingFlags.Static |
                BindingFlags.FlattenHierarchy)
                .Where(
                f => f.Name.EndsWith("_PropertyName"));
        foreach (var fieldInfo in fields)
        {
            RaiseVirtualPropertyChanged((String)fieldInfo.GetValue(t));
        }
    }

    [DebuggerStepThrough]
    public void RaiseVirtualPropertyChanged(params String[] propertyNames)
    {
        var pc = PropertyChanged;
        if (pc == null) return;

        foreach (var additionalPropertyName in propertyNames)
        {
            SafeCall(() => pc(this, new PropertyChangedEventArgs(additionalPropertyName)));
        }
    }
}

How it is thread safe?

For purposes of thread safetey there are following things:

  1. Presence of private _dispatcher field - it is a global storage of main thread, and for this be able to work you should use Notificator class first time at the beginning of your application from main thread.
  2. SafeCall function - it just invokes any action from _dispatcher's thread.

How to turn off notifications on the object?

As you can see there is property NotifySubscribers, wich is responsible for that.

How to implement bulk notification?

You shoul set NotifySubscribers to FALSE, then change any properties you want, then turn NotifySubscribers to TRUE, and then call RaiseAllPropertiesChaged.

It is very easy to write helper function which will take an action in parameter and implement such sequence.

Was it tested?

Yes, I have a project with hundreds viewmodels, and I have no any problems with this, just convinience. So if you will find this usefull please use it and the code become cleaner...

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Saber Interactive
Russian Federation Russian Federation
My specializations:

C# (especially multithreading)
WPF (MVVM, styling)
WCF (message inspectors, configuration)
MSSQL (administartion, creation, procedures, recursive queries, bulk processing)

Comments and Discussions

 
QuestionGood intentions, but... Pin
PureNsanity24-Apr-17 7:03
professionalPureNsanity24-Apr-17 7:03 
AnswerRe: Good intentions, but... Pin
Evgeny Bestfator25-Apr-17 20:55
professionalEvgeny Bestfator25-Apr-17 20:55 
GeneralRe: Good intentions, but... Pin
PureNsanity26-Apr-17 3:34
professionalPureNsanity26-Apr-17 3:34 
GeneralRe: Good intentions, but... Pin
Evgeny Bestfator26-Apr-17 9:25
professionalEvgeny Bestfator26-Apr-17 9:25 
GeneralRe: Good intentions, but... Pin
PureNsanity26-Apr-17 11:29
professionalPureNsanity26-Apr-17 11:29 
GeneralRe: Good intentions, but... Pin
Evgeny Bestfator26-Apr-17 12:24
professionalEvgeny Bestfator26-Apr-17 12:24 
GeneralRe: Good intentions, but... Pin
PureNsanity26-Apr-17 13:54
professionalPureNsanity26-Apr-17 13:54 
GeneralRe: Good intentions, but... Pin
Evgeny Bestfator27-Apr-17 20:19
professionalEvgeny Bestfator27-Apr-17 20:19 
GeneralRe: Good intentions, but... Pin
PureNsanity28-Apr-17 1:55
professionalPureNsanity28-Apr-17 1:55 
GeneralRe: Good intentions, but... Pin
Evgeny Bestfator28-Apr-17 2:11
professionalEvgeny Bestfator28-Apr-17 2:11 
GeneralRe: Good intentions, but... Pin
PureNsanity28-Apr-17 2:42
professionalPureNsanity28-Apr-17 2:42 
GeneralRe: Good intentions, but... Pin
Evgeny Bestfator28-Apr-17 22:29
professionalEvgeny Bestfator28-Apr-17 22:29 
QuestionWrong Pin
FatCatProgrammer8-May-15 5:03
FatCatProgrammer8-May-15 5:03 
AnswerRe: Wrong Pin
Evgeny Bestfator8-May-15 6:41
professionalEvgeny Bestfator8-May-15 6:41 
GeneralRe: Wrong Pin
FatCatProgrammer11-May-15 3:19
FatCatProgrammer11-May-15 3:19 
GeneralRe: Wrong Pin
Evgeny Bestfator11-May-15 23:27
professionalEvgeny Bestfator11-May-15 23:27 
QuestionNot so good Pin
J4Nch8-May-15 5:00
J4Nch8-May-15 5:00 
AnswerRe: Not so good Pin
William E. Kempf8-May-15 5:05
William E. Kempf8-May-15 5:05 
GeneralRe: Not so good Pin
J4Nch8-May-15 5:32
J4Nch8-May-15 5:32 
AnswerRe: Not so good Pin
Evgeny Bestfator8-May-15 6:44
professionalEvgeny Bestfator8-May-15 6:44 
QuestionCriticisms Pin
William E. Kempf8-May-15 3:04
William E. Kempf8-May-15 3:04 
AnswerRe: Criticisms Pin
Evgeny Bestfator8-May-15 7:08
professionalEvgeny Bestfator8-May-15 7:08 
GeneralRe: Criticisms Pin
Pete O'Hanlon8-May-15 7:30
mvePete O'Hanlon8-May-15 7:30 
GeneralRe: Criticisms Pin
Evgeny Bestfator8-May-15 10:46
professionalEvgeny Bestfator8-May-15 10:46 
GeneralRe: Criticisms Pin
William E. Kempf8-May-15 10:04
William E. Kempf8-May-15 10:04 
Evgeny Bestfator wrote:
"Making the _dispatcher static is a bad idea. WPF doesn't limit you to a single UI thread, neither should this." - It limits, there is a requirement to have STAThread to create Window within, so you only possible to update properties of UI from only same thread.


This is wrong. We talk about a UI thread as a convenient mental model. The reality is that every window has thread affinity (they use a single thread for message dispatch). Typically we write our programs so that we use a single thread for all of our windows, thus matching the "UI thread" mental model. However, you can easily create another thread and in that thread create a window and thus have two "UI threads". There's a reason the DispatcherObject base class maintains an instance member for the Dispatcher rather than use a static instance as you've done. It's wrong.

Evgeny Bestfator wrote:
"SafeCall isn't, really, safe. Because you've used Invoke instead of BeginInvoke it's possible for this to lead to deadlock." - you can not use BeginInvoke, because in most cases your operation should be completed in sync.


I'd argue about that... especially since you're dealing with an event here. The handlers will still be invoked synchronously (on the UI thread), and due to message dispatching even multiple events being raised will occur synchronously. The only reason to ever use Invoke is when you have need to block waiting for the result of the invoked method. That's not happening here. Regardless, what I said still stands. Calling Invoke instead of BeginInvoke has the potential to cause deadlock. I'd hardly call that "Safe". Yes, you can safely call Invoke, but doing so requires you to know what's going on in both threads. You can't really determine that with an event handler.

Evgeny Bestfator wrote:
I have a simple rule to not have deadlocks: never wait started thread from main UI thread.


This rule is hardly sufficient. If you understood why this rule is *part* of how you avoid deadlocks you'd understand why I said calling Invoke isn't safe. It's possible, even likely, that calling Invoke will put you in exactly the same situation as you're trying to avoid.

Evgeny Bestfator wrote:
you have wrong scenario... you have 10 properties and during processing you change them 100 times each...


Great. You've still raised 10 events instead of 1. It's still the wrong thing to do.
William E. Kempf

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.