Событие NotifyPropertyChanged, в котором аргументы событий содержат старое значение
Есть ли интерфейс, совместимый с 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.
Ответы
Ответ 1
Как указано в ответах, мне пришлось реализовать свое собственное решение. В интересах других, я представил его здесь:
Расширенное событие 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
Если программист хотел создать событие, в котором заставляет уведомляющие свойства включать старое значение и новое значение, им нужно реализовать только следующий интерфейс:
// 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;
}
Производительность
Если производительность является проблемой, см. этот вопрос: Внедрение NotifyPropertyChanged без магических строк.
Таким образом, накладные расходы минимальны. Добавление старого значения и переход к расширенному событию происходит примерно на 15% -ное замедление, все еще позволяющее получать порядка миллиона уведомлений о свойствах в секунду, а переход на лямбда-выражения - это 5-кратное замедление, позволяющее примерно сто тысяч уведомлений о свойствах за второй. Эти цифры далеки от того, чтобы создать узкое место в любом приложении, основанном на пользовательском интерфейсе.
Ответ 2
Похоже, вы хотите использовать INotifyPropertyChanging
в сочетании с INotifyPropertyChanged
. Документация Msdn http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanging.aspx
Ответ 3
Если вы хотите только старое значение, вы можете вызвать событие перед изменением значения свойства. Но это было бы отходом от того, как это событие обычно используется, поэтому я бы создал специальный интерфейс и поддерживал его.
Ответ 4
Нет, вам нужно создать свой собственный с нуля.
Я делал то же самое в своем исследовательском проекте "Гранит", но я пришел к выводу, что это не стоит того. Слишком много свойств, с которыми я работаю, вычисляются, и необходимость запускать их дважды просто для того, чтобы поднять событие было слишком дорогостоящим.
Ответ 5
Принятый ответ великолепен, но я изо всех сил пытался понять, как 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'.