71

There is a property, it's named ImageFullPath1

public string ImageFullPath1 {get; set; }

I'm going fire an event whenever its value changed. I am aware of changing INotifyPropertyChanged, but I want to do it with events.

6 Answers 6

174

The INotifyPropertyChanged interface is implemented with events. The interface has just one member, PropertyChanged, which is an event that consumers can subscribe to.

The version that Richard posted is not safe. Here is how to safely implement this interface:

public class MyClass : INotifyPropertyChanged
{
    private string imageFullPath;

    protected void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
            handler(this, e);
    }

    protected void OnPropertyChanged(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    public string ImageFullPath
    {
        get { return imageFullPath; }
        set
        {
            if (value != imageFullPath)
            {
                imageFullPath = value;
                OnPropertyChanged("ImageFullPath");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Note that this does the following things:

  • Abstracts the property-change notification methods so you can easily apply this to other properties;

  • Makes a copy of the PropertyChanged delegate before attempting to invoke it (failing to do this will create a race condition).

  • Correctly implements the INotifyPropertyChanged interface.

If you want to additionally create a notification for a specific property being changed, you can add the following code:

protected void OnImageFullPathChanged(EventArgs e)
{
    EventHandler handler = ImageFullPathChanged;
    if (handler != null)
        handler(this, e);
}

public event EventHandler ImageFullPathChanged;

Then add the line OnImageFullPathChanged(EventArgs.Empty) after the line OnPropertyChanged("ImageFullPath").

Since we have .Net 4.5 there exists the CallerMemberAttribute, which allows to get rid of the hard-coded string for the property name in the source code:

    protected void OnPropertyChanged(
        [System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    public string ImageFullPath
    {
        get { return imageFullPath; }
        set
        {
            if (value != imageFullPath)
            {
                imageFullPath = value;
                OnPropertyChanged();
            }
        }
    }
10
  • 29
    +1 for being the only person in this thread so far to get the null check on the event correct. Commented Feb 11, 2010 at 18:55
  • @Aaronaught: How to wire up the event-method with event?could you please explain.. I mean, where do I need to write the implementation for the event? Commented Oct 26, 2011 at 18:33
  • Why use the local var "handler", why not just if (ImageFullPathChanged != null) ImageFullPathChanged(this, e);
    – dlchambers
    Commented Jul 14, 2015 at 14:03
  • 1
    since the compiler will process lvalues before rvalues ImageFullPathChanged?.Invoke... will always negate the race condition, i.e. ImageFullPathChanged will never be invoked if it is null. Its just syntactic sugar which Roslyn would then process on build. would need to check IL output to verify but pretty certain.
    – Lou Watson
    Commented Oct 11, 2016 at 1:06
  • 5
    I would replace the parameter from call of OnPropertyChanged("ImageFullPath"); with nameof(ImageFullPath). This way you will have compile time checking of the name of the property so in case you will ever change it, you will receive an error message if you forget to replace it in the method call too. Commented Mar 2, 2018 at 13:08
38

I use largely the same patterns as Aaronaught, but if you have a lot of properties it could be nice to use a little generic method magic to make your code a little more DRY

public class TheClass : INotifyPropertyChanged {
    private int _property1;
    private string _property2;
    private double _property3;

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) {
        PropertyChangedEventHandler handler = PropertyChanged;
        if(handler != null) {
            handler(this, e);
        }
    }

    protected void SetPropertyField<T>(string propertyName, ref T field, T newValue) {
        if(!EqualityComparer<T>.Default.Equals(field, newValue)) {
            field = newValue;
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
    }

    public int Property1 {
        get { return _property1; }
        set { SetPropertyField("Property1", ref _property1, value); }
    }
    public string Property2 {
        get { return _property2; }
        set { SetPropertyField("Property2", ref _property2, value); }
    }
    public double Property3 {
        get { return _property3; }
        set { SetPropertyField("Property3", ref _property3, value); }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

Usually I also make the OnPropertyChanged method virtual to allow sub-classes to override it to catch property changes.

3
  • 12
    Now with .NET 4.5 you can even get the property name for free using the CallerMemberNameAttribute msdn.microsoft.com/en-us/library/….
    – fsimonazzi
    Commented Oct 25, 2012 at 17:40
  • This is working great. Thanks for the example. +1 for the CallerMemberName
    – curob
    Commented Aug 31, 2016 at 16:43
  • 1
    Time went since last edit and some things changed. Maybe better way to invoke event delegate: PropertyChanged?.Invoke(this, e);
    – Tzwenni
    Commented Sep 2, 2020 at 9:24
9

Raising an event when a property changes is precisely what INotifyPropertyChanged does. There's one required member to implement INotifyPropertyChanged and that is the PropertyChanged event. Anything you implemented yourself would probably be identical to that implementation, so there's no advantage to not using it.

1
  • 2
    +1 for truth. Even if you want to implement a separate XChangedEvent for every property, you're already doing the work, so go ahead and implement INotifyPropertyChanged too. The future (like WPF) will thank you, because that's what the future expects you to do.
    – Greg D
    Commented Feb 11, 2010 at 18:48
5
public event EventHandler ImageFullPath1Changed;

public string ImageFullPath1
{
    get
    {
        // insert getter logic
    }
    set
    {
        // insert setter logic       

        // EDIT -- this example is not thread safe -- do not use in production code
        if (ImageFullPath1Changed != null && value != _backingField)
            ImageFullPath1Changed(this, new EventArgs(/*whatever*/);
    }
}                        

That said, I completely agree with Ryan. This scenario is precisely why INotifyPropertyChanged exists.

2
  • 3
    This event invocation has a race condition. The value of ImageFullPath1Changed can change to null between the check and the subsequent invocation. Do not invoke events like this!
    – Aaronaught
    Commented Feb 11, 2010 at 18:52
  • 2
    Your check for null of the ImageFullPath1Changed event is not safe. Given that events can be subscribed/unsubscribed to/from asynchronously from outside of your class, it could become null after your null check and cause a NullReferenceException. Instead, you should take a local copy before checking for null. See Aaronaught's answer. Commented Feb 11, 2010 at 18:54
4

If you change your property to use a backing field (instead of an automatic property), you can do the following:

public event EventHandler ImageFullPath1Changed;
private string _imageFullPath1 = string.Empty;

public string ImageFullPath1 
{
  get
  {
    return imageFullPath1 ;
  }
  set
  {
    if (_imageFullPath1 != value)
    { 
      _imageFullPath1 = value;

      EventHandler handler = ImageFullPathChanged;
      if (handler != null)
        handler(this, e);
    }
  }
}
5
  • Your check for null of the ImageFullPath1Changed event is not safe. Given that events can be subscribed/unsubscribed to/from asynchronously from outside of your class, it could become null after your null check and cause a NullReferenceException. Instead, you should take a local copy before checking for null. See Aaronaught's answer Commented Feb 11, 2010 at 18:54
  • 1
    @Simon P Stevens - thanks for the info. Updated answer to reflect.
    – Oded
    Commented Feb 11, 2010 at 19:12
  • @Oded I tried using your approach, but for the above code handler(this, e), e does not exist in current context Am I mssing something?
    – Abhijeet
    Commented Nov 10, 2012 at 5:59
  • 1
    @autrevo - The e is simply an example. You need to pass in an instance of EventArgs. If you don't have any to pass, you can use EventArgs.Empty.
    – Oded
    Commented Nov 10, 2012 at 8:09
  • @Simon P Stevens I prerfer using. public event EventHandler ImageFullPath1Changed = delegate {}; Then avoid having to check for null....
    – madrang
    Commented Mar 20, 2014 at 5:47
0

There is already have good answers but some people are still confused

  • EventArgs and who they can use it
  • And some of them about how to pass custom parameters
class Program
    {
        static void Main(string[] args)
        {
            Location loc = new Location();

            loc.LocationChanged += (obj, chngLoc) =>
            {
                Console.WriteLine("Your LocId Is");
                Console.WriteLine(chngLoc.LocId);
                Console.WriteLine(chngLoc.LocCode);
                Console.WriteLine(chngLoc.LocName);
                Console.ReadLine();
            };

            Console.WriteLine("Default Location Is");
            Console.WriteLine(loc.LocId);

            Console.WriteLine("Change Location");
            loc.LocId = Console.ReadLine();
        }
    }

    public class Location
    {

        private string _locId = "Default Location";
        public string LocId
        {
            get
            {
                return _locId;
            }
            set
            {

                _locId = value;
                if (LocationChanged != null && value != LocId)
                {
                    B1Events b1 = new B1Events();
                    b1.LocCode = "Changed LocCode";
                    b1.LocId = value;
                    b1.LocName = "Changed LocName";
                    LocationChanged(this, b1);
                }
                
            }
        }
         public event EventHandler<B1Events> LocationChanged;
    }

    public class B1Events : EventArgs
    {
        public string LocId { get; set; }
        public string LocCode{ get; set; }
        public string LocName { get; set; }
    }



 

Not the answer you're looking for? Browse other questions tagged or ask your own question.