Событие NotifyPropertyChanged, в котором в аргументах события содержится старое значение - PullRequest
20 голосов
/ 06 октября 2011

Существует ли интерфейс, похожий на INotifyPropertyChanged, в котором в аргументах события содержится старое значение изменяемого свойства, или мне нужно расширить этот интерфейс для его создания?

Например:

    public String ProcessDescription
    {
        get { return _ProcessDescription; }
        set
        {
            if( value != ProcessDescription )
            {
                String oldValue = _ProcessDescription;
                _ProcessDescription = value;
                InvokePropertyChanged("ProcessDescription", oldvalue);
            }
        }
    }

    InvokePropertyChanged(String PropertyName, OldValue)
    {
         this.PropertyChanged( new ExtendedPropertyChangedEventArgs(PropertyName, OldValue) );
    }

Я бы также согласился на событие, подобное PropertyChanging, которое предоставляет эту информацию, независимо от того, поддерживает ли оно e.Cancel.

Ответы [ 5 ]

36 голосов
/ 12 октября 2011

Как показывают ответы, мне пришлось реализовать собственное решение.Для удобства других я представил его здесь:

Расширенное событие PropertyChanged

Это событие было специально разработано для обратной совместимости со старыми событиями propertyChanged.Он может быть использован взаимозаменяемо с простым PropertyChangedEventArgs вызывающими.Конечно, в таких случаях ответственность за проверку того, может ли переданный PropertyChangedEventArgs быть передан в PropertyChangedExtendedEventArgs, если они хотят его использовать, лежит на обработчике события.Нет необходимости в даункинге, если все, что их интересует, это свойство PropertyName.

public class PropertyChangedExtendedEventArgs<T> : PropertyChangedEventArgs
{
    public virtual T OldValue { get; private set; }
    public virtual T NewValue { get; private set; }

    public PropertyChangedExtendedEventArgs(string propertyName, T oldValue, T newValue)
        : base(propertyName)
    {
        OldValue = oldValue;
        NewValue = newValue;
    }
}

Расширенный интерфейс PropertyChanged

If, программист хотел создать событиечто заставляет уведомляющих свойств включать старое значение и новое значение, им нужно только реализовать следующий интерфейс:

// Summary: Notifies clients that a property value is changing, but includes extended event infomation
/* The following NotifyPropertyChanged Interface is employed when you wish to enforce the inclusion of old and
 * new values. (Users must provide PropertyChangedExtendedEventArgs, PropertyChangedEventArgs are disallowed.) */
public interface INotifyPropertyChangedExtended<T>
{
    event PropertyChangedExtendedEventHandler<T> PropertyChanged;
}

public delegate void PropertyChangedExtendedEventHandler<T>(object sender, PropertyChangedExtendedEventArgs<T> e);

Пример 1

Теперь пользователь может указать более продвинутый метод NotifyPropertyChanged, который позволяет установщикам свойств передавать свое старое значение:

public String testString
{
    get { return testString; }
    set
    {
        String temp = testString;
        testValue2 = value;
        NotifyPropertyChanged("TestString", temp, value);
    }
}

Где ваш новый метод NotifyPropertyChanged выглядит следующим образом:

protected void NotifyPropertyChanged<T>(string propertyName, T oldvalue, T newvalue)
{
    OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(propertyName, oldvalue, newvalue));
}

И OnPropertyChanged - то же, что и всегда:

public virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
        handler(sender, e);
}

Пример 2

Или если вы предпочитаете использовать лямбда-выражения и делатьполностью избавившись от жестко запрограммированных строк с именами свойств, вы можете использовать следующее:

public String TestString
{
    get { return testString; }
    private set { SetNotifyingProperty(() => TestString, ref testString, value); }
}

, что поддерживается следующей магией:

protected void SetNotifyingProperty<T>(Expression<Func<T>> expression, ref T field, T value)
{
    if (field == null || !field.Equals(value))
    {
        T oldValue = field;
        field = value;
        OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(GetPropertyName(expression), oldValue, value));
    }
}
protected string GetPropertyName<T>(Expression<Func<T>> expression)
{
    MemberExpression memberExpression = (MemberExpression)expression.Body;
    return memberExpression.Member.Name;
}

Производительность

Если производительность является проблемой, см. Этот вопрос: Imдополняет NotifyPropertyChanged без волшебных строк .

Таким образом, накладные расходы минимальны.Добавление старого значения и переключение на расширенное событие приводит к замедлению примерно на 15%, при этом допускается порядка одного миллиона уведомлений о свойствах в секунду, а переключение на лямбда-выражения - это пятикратное замедление, позволяющее получать около ста тысяч уведомлений о свойствах в секунду.второй.Эти цифры далеки от того, чтобы создавать узкое место в любом приложении, управляемом пользовательским интерфейсом.

3 голосов
/ 06 октября 2011

Звучит так, как будто вы хотите использовать INotifyPropertyChanging в сочетании с INotifyPropertyChanged. MSDN Документация http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanging.aspx

1 голос
/ 14 июня 2019

Принятый ответ великолепен, но я изо всех сил пытался понять, как предполагалось реализовать PropertyChangedExtendedEventArgs<T>, в конце концов я понял, что это не так.

Ниже приведен полный рабочий пример, показывающий, как использовать PropertyChangedExtendedEventArgs<T>.

using System;
using System.ComponentModel;

namespace ConsoleApp10
{
    class Program
    {
        static void Main(string[] args)
        {
            var p = new Program();
            p.Run();
        }

        private void Run()
        {
            // Create Poco
            var poco = new MyPoco(1, "MyOldName", 150);
            // Attach property changed event
            poco.PropertyChanged += PocoOnPropertyChanged;
            // Change data
            poco.Id = 10;
            poco.Name = "NewName";
            poco.Height = 170;
        }

        /// <summary>
        /// Property changed handler
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void PocoOnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // Without casting 'e' is a standard PropertyChanged event
            if (Equals(e.PropertyName, nameof(MyPoco.Id)))
            {
                Console.WriteLine($"'{nameof(MyPoco.Id)}' has changed, but we have no other data");
            }

            // New extended property changed event of type 'string'
            if (Equals(e.PropertyName, nameof(MyPoco.Name)))
            {
                // Need to cast into type we know and are expecting
                if (e is PropertyChangedExtendedEventArgs<string> extended)
                {
                    Console.WriteLine(
                        $"'{nameof(MyPoco.Name)}' has changed, from '{extended.OldValue}' to '{extended.NewValue}'.");
                }
            }

            // New extended property changed event of type 'double'
            if (Equals(e.PropertyName, nameof(MyPoco.Height)))
            {
                // This cast will fail as the types are wrong
                if (e is PropertyChangedExtendedEventArgs<string>)
                {
                    // Should never hit here
                }
                // Cast into type we know and are expecting
                if (e is PropertyChangedExtendedEventArgs<double> extended)
                {
                    Console.WriteLine(
                        $"'{nameof(MyPoco.Height)}' has changed, from '{extended.OldValue}' to '{extended.NewValue}'.");
                }
            }
        }
    }

    /// <summary>
    /// Example POCO
    /// </summary>
    public sealed class MyPoco : NotifyBase
    {
        private int _id;
        private string _name;
        private double _height;

        public MyPoco(int id, string name, double height)
        {
            _id = id;
            _name = name;
            _height = height;
        }

        public int Id
        {
            get => _id;
            set
            {
                var old = _id;
                _id = value;
                OnPropertyChanged(old, value, nameof(Id));
            }
        }

        public string Name
        {
            get => _name;
            set
            {
                var old = _name;
                _name = value;
                OnPropertyChanged(old, value, nameof(Name));
            }
        }

        public double Height
        {
            get => _height;
            set
            {
                var old = _height;
                _height = value;
                OnPropertyChanged(old, value, nameof(Height));
            }
        }
    }

    /// <summary>
    /// Notifying base class
    /// </summary>
    public abstract class NotifyBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged<T>(T oldValue, T newValue, string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedExtendedEventArgs<T>(oldValue, newValue, propertyName));
        }
    }

    /// <summary>
    /// Extended property changed
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public sealed class PropertyChangedExtendedEventArgs<T> : PropertyChangedEventArgs
    {
        public PropertyChangedExtendedEventArgs(T oldValue, T newValue, string propertyName)
            : base(propertyName)
        {
            OldValue = oldValue;
            NewValue = newValue;
        }

        public T OldValue { get; }
        public T NewValue { get; }
    }
}

Вывод:

'Id' has changed, but we have no other data
'Name' has changed, from 'MyOldName' to 'NewName'.
'Height' has changed, from '150' to '170'.
1 голос
/ 06 октября 2011

Нет, вы должны создать свой собственный с нуля.

Раньше я делал то же самое в своем исследовательском проекте Granite, но пришел к выводу, что это не стоит затрат. Рассчитывается слишком много свойств, с которыми я работаю, и запускать их дважды, чтобы вызвать событие, было слишком дорого.

1 голос
/ 06 октября 2011

Если вы хотите использовать только старое значение, вы можете вызвать событие перед изменением значения свойства. Но это было бы отходом от того, как обычно используется это событие, поэтому я бы создал выделенный интерфейс и аргументы для него.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...