Автоматически INotifyPropertyChanged
Есть ли способ автоматически получать уведомления об изменениях свойств в классе, не записывая OnPropertyChanged в каждый сеттер? (У меня есть сотни свойств, которые я хочу знать, если они изменились).
Антон предлагает динамические прокси. Фактически я использовал библиотеку "Замок" для чего-то подобного в прошлом, и хотя она уменьшает количество кода, который мне приходилось писать, он добавил около 30 секунд к моему времени запуска программы (ymmv) - потому что это времени выполнения.
Мне интересно, есть ли время компиляции, возможно, используя атрибуты времени компиляции...
Slashene и TcKs дают предложения, которые генерируют повторяющийся код - к сожалению, не все мои свойства являются простым случаем m_Value = value - у многих из них есть собственный код в сеттерах, поэтому код куки-резака из фрагментов и xml не является действительно возможно для моего проекта.
Ответы
Ответ 1
РЕДАКТИРОВАТЬ: Автор NotifyPropertyWeaver отклонил этот инструмент в пользу более общего Fody. (A руководство по миграции для людей, перемещающихся из ткача в fody.)
Очень удобный инструмент, который я использовал для своих проектов, - это Уведомить Property Weaver Fody.
Он устанавливает себя как шаг сборки в ваших проектах, а во время компиляции вводит код, который вызывает событие PropertyChanged
.
Создание свойств повышает свойство PropertyChanged выполняется путем размещения специальных атрибутов на них:
[ImplementPropertyChanged]
public string MyProperty { get; set; }
В качестве бонуса вы также можете указать отношения для свойств, которые зависят от других свойств
[ImplementPropertyChanged]
public double Radius { get; set; }
[DependsOn("Radius")]
public double Area
{
get { return Radius * Radius * Math.PI; }
}
Ответ 2
Оператор nameof был реализован в С# 6.0 с .NET 4.6 и VS2015 в июле 2015 года. Для С# < 6,0
Мы используем код ниже (From http://www.ingebrigtsen.info/post/2008/12/11/INotifyPropertyChanged-revisited.aspx). Отлично работает:)
public static class NotificationExtensions
{
#region Delegates
/// <summary>
/// A property changed handler without the property name.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sender">The object that raised the event.</param>
public delegate void PropertyChangedHandler<TSender>(TSender sender);
#endregion
/// <summary>
/// Notifies listeners about a change.
/// </summary>
/// <param name="EventHandler">The event to raise.</param>
/// <param name="Property">The property that changed.</param>
public static void Notify(this PropertyChangedEventHandler EventHandler, Expression<Func<object>> Property)
{
// Check for null
if (EventHandler == null)
return;
// Get property name
var lambda = Property as LambdaExpression;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = lambda.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
{
memberExpression = lambda.Body as MemberExpression;
}
ConstantExpression constantExpression;
if (memberExpression.Expression is UnaryExpression)
{
var unaryExpression = memberExpression.Expression as UnaryExpression;
constantExpression = unaryExpression.Operand as ConstantExpression;
}
else
{
constantExpression = memberExpression.Expression as ConstantExpression;
}
var propertyInfo = memberExpression.Member as PropertyInfo;
// Invoke event
foreach (Delegate del in EventHandler.GetInvocationList())
{
del.DynamicInvoke(new[]
{
constantExpression.Value, new PropertyChangedEventArgs(propertyInfo.Name)
});
}
}
/// <summary>
/// Subscribe to changes in an object implementing INotifiyPropertyChanged.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="ObjectThatNotifies">The object you are interested in.</param>
/// <param name="Property">The property you are interested in.</param>
/// <param name="Handler">The delegate that will handle the event.</param>
public static void SubscribeToChange<T>(this T ObjectThatNotifies, Expression<Func<object>> Property, PropertyChangedHandler<T> Handler) where T : INotifyPropertyChanged
{
// Add a new PropertyChangedEventHandler
ObjectThatNotifies.PropertyChanged += (s, e) =>
{
// Get name of Property
var lambda = Property as LambdaExpression;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = lambda.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
{
memberExpression = lambda.Body as MemberExpression;
}
var propertyInfo = memberExpression.Member as PropertyInfo;
// Notify handler if PropertyName is the one we were interested in
if (e.PropertyName.Equals(propertyInfo.Name))
{
Handler(ObjectThatNotifies);
}
};
}
}
Используется, например, следующим образом:
public class Employee : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _firstName;
public string FirstName
{
get { return this._firstName; }
set
{
this._firstName = value;
this.PropertyChanged.Notify(()=>this.FirstName);
}
}
}
private void firstName_PropertyChanged(Employee sender)
{
Console.WriteLine(sender.FirstName);
}
employee = new Employee();
employee.SubscribeToChange(() => employee.FirstName, firstName_PropertyChanged);
Некоторые синтаксические ошибки в этом примере могут существовать. Не проверял. Но вы должны иметь концепцию, по крайней мере:)
РЕДАКТИРОВАТЬ: Теперь я вижу, что вам, возможно, понадобилось еще меньше работы, но да... материал, по крайней мере, делает его намного проще. И вы предотвратите все страшные проблемы со ссылкой на свойства, используя строки.
Ответ 3
Framework 4.5 предоставляет нам CallerMemberNameAttribute
, что делает ненужной передачу имени свойства в виде строки:
private string m_myProperty;
public string MyProperty
{
get { return m_myProperty; }
set
{
m_myProperty = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed")
{
// ... do stuff here ...
}
Подобно Svish-решению, просто замена лямбда-потрясения на скучную функциональность фреймворка ;-)
Если вы работаете на Framework 4.0 с установленным KB2468871, вы можете установить пакет совместимости Microsoft BCL через nuget, который также предоставляет этот атрибут.
Ответ 4
Вы можете использовать метод расширения в своем делете PropertyChanged и использовать его следующим образом:
public string Name
{
get { return name; }
set
{
name = value;
PropertyChanged.Raise(() => Name);
}
}
Подписка на изменение конкретного свойства:
var obj = new Employee();
var handler = obj.SubscribeToPropertyChanged(
o => o.FirstName,
o => Console.WriteLine("FirstName is now '{0}'", o.FirstName));
obj.FirstName = "abc";
// Unsubscribe when required
obj.PropertyChanged -= handler;
метод расширения способен определять имя отправителя и свойства только путем проверки дерева лямбда-выражения и без значительного воздействия на производительность:
public static class PropertyChangedExtensions
{
public static void Raise<TProperty>(
this PropertyChangedEventHandler handler, Expression<Func<TProperty>> property)
{
if (handler == null)
return;
var memberExpr = (MemberExpression)property.Body;
var propertyName = memberExpr.Member.Name;
var sender = ((ConstantExpression)memberExpr.Expression).Value;
handler.Invoke(sender, new PropertyChangedEventArgs(propertyName));
}
public static PropertyChangedEventHandler SubscribeToPropertyChanged<T, TProperty>(
this T obj, Expression<Func<T, TProperty>> property, Action<T> handler)
where T : INotifyPropertyChanged
{
if (handler == null)
return null;
var memberExpr = (MemberExpression)property.Body;
var propertyName = memberExpr.Member.Name;
PropertyChangedEventHandler subscription = (sender, eventArgs) =>
{
if (propertyName == eventArgs.PropertyName)
handler(obj);
};
obj.PropertyChanged += subscription;
return subscription;
}
}
Если событие PropertyChanged
объявлено в базовом типе, оно не будет отображаться как поле делегата в производных классах. В этом случае обходным путем является объявление защищенного поля типа PropertyChangedEventHandler
и явное использование событий add
и remove
:
public class Base : INotifyPropertyChanged
{
protected PropertyChangedEventHandler propertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add { propertyChanged += value; }
remove { propertyChanged -= value; }
}
}
public class Derived : Base
{
string name;
public string Name
{
get { return name; }
set
{
name = value;
propertyChanged.Raise(() => Name);
}
}
}
Ответ 5
INotifyPropertyChanged
безопасный тип INotifyPropertyChanged
: см. Здесь
Затем создайте свой собственный фрагмент кода:
private $Type$ _$PropertyName$;
public $Type$ $PropertyName$
{
get
{
return _$PropertyName$;
}
set
{
if(value != _$PropertyName$)
{
_$PropertyName$ = value;
OnPropertyChanged(o => o.$PropertyName$);
}
}
}
С дизайнером фрагмента кода, и вы сделали! Простой и безопасный способ создать свой INotifyPropertyChanged
.
Ответ 6
Я не знаю стандартного способа, но знаю два обхода:
1) PostSharp может сделать это для вас после компиляции. Это очень полезно, но для каждой сборки требуется некоторое время.
2) Пользовательский инструмент я Visual Studio. Вы можете объединить его с "частичным классом". Затем вы можете создать собственный инструмент для своего XML, и вы можете генерировать исходный код из xml.
Например, этот xml:
<type scope="public" type="class" name="MyClass">
<property scope="public" type="string" modifier="virtual" name="Text" notify="true" />
</type>
может быть источником для этого кода:
public partial class MyClass {
private string _text;
public virtual string Text {
get { return this._Text; }
set {
this.OnPropertyChanging( "Text" );
this._Text = value;
this.OnPropertyChanged( "Text" );
}
}
}
Ответ 7
возможно, вы захотите изучить Аспектно-ориентированное программирование в целом
Frameworks = > вы можете посмотреть linfu
Ответ 8
Вы можете посмотреть на замок или Spring.NET и реализовать функции перехватчика?
Ответ 9
Я только что нашел ActiveSharp - Automatic INotifyPropertyChanged, я еще не использовал его, но он выглядит хорошо.
Процитировать с веб-сайта...
Отправить уведомления об изменении свойств без указания имени свойства как строка.
Вместо этого напишите свойства следующим образом:
public int Foo
{
get { return _foo; }
set { SetValue(ref _foo, value); } // <-- no property name here
}
Обратите внимание, что нет необходимости включать имя свойства в виде строки. ActiveSharp надежно и правильно определяет это для себя. Он работает на основе того факта, что ваша реализация свойства передает поле поддержки (_foo) по ссылке. (ActiveSharp использует вызов "по ref" для определения того, какое поле поддержки было передано, и из поля оно идентифицирует свойство).
Ответ 10
Улучшение для вызова события в дочерних классах:
Вызывается благодаря:
this.NotifyPropertyChange(() = > PageIndex);
Добавьте это в класс NotificationExtensions:
/// <summary>
/// <para>Lève l'évènement de changement de valeur sur l'objet <paramref name="sender"/>
/// pour la propriété utilisée dans la lambda <paramref name="property"/>.</para>
/// </summary>
/// <param name="sender">L'objet portant la propriété et l'évènement.</param>
/// <param name="property">Une expression lambda utilisant la propriété subissant la modification.</param>
public static void NotifyPropertyChange(this INotifyPropertyChanged sender, Expression<Func<Object>> property)
{
if (sender == null)
return;
// Récupère le nom de la propriété utilisée dans la lambda en argument
LambdaExpression lambda = property as LambdaExpression;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
UnaryExpression unaryExpression = lambda.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
{
memberExpression = lambda.Body as MemberExpression;
}
ConstantExpression constantExpression = memberExpression.Expression as ConstantExpression;
PropertyInfo propertyInfo = memberExpression.Member as PropertyInfo;
// il faut remonter la hierarchie, car meme public, un event n est pas visible dans les enfants
FieldInfo eventField;
Type baseType = sender.GetType();
do
{
eventField = baseType.GetField(INotifyPropertyChangedEventFieldName, BindingFlags.Instance | BindingFlags.NonPublic);
baseType = baseType.BaseType;
} while (eventField == null);
// on a trouvé l'event, on peut invoquer tt les delegates liés
MulticastDelegate eventDelegate = eventField.GetValue(sender) as MulticastDelegate;
if (eventDelegate == null) return; // l'event n'est bindé à aucun delegate
foreach (Delegate handler in eventDelegate.GetInvocationList())
{
handler.Method.Invoke(handler.Target, new Object[] { sender, new PropertyChangedEventArgs(propertyInfo.Name) });
}
}
Ответ 11
Чтобы сделать реализацию быстрее, вы можете использовать фрагмент
Из http://aaron-hoffman.blogspot.it/2010/09/visual-studio-code-snippet-for-notify.html
классы ViewModel проектов, следующих за шаблоном M-V-VM, часто необходимо поднять событие PropertyChanged (чтобы помочь с реализацией интерфейса INotifyPropertyChanged) из набора свойств. Это утомительная задача, которая, надеюсь, когда-нибудь будет решена с помощью компилятора как службы...
Ядро фрагмента (для которого полный кредит принадлежит автору, кто не я) является следующим
<Code Language= "csharp ">
<![CDATA[public $type$ $property$
{
get { return _$property$; }
set
{
if (_$property$ != value)
{
_$property$ = value;
OnPropertyChanged($property$PropertyName);
}
}
}
private $type$ _$property$;
public const string $property$PropertyName = "$property$";$end$]]>
</Code>
Ответ 12
Нет единой реализации Property Changed, которая может обрабатывать каждый способ, которым люди хотят ее использовать. лучше всего создать класс-помощник, чтобы выполнить работу за вас
вот пример того, как я использую
/// <summary>
/// Helper Class that automates most of the actions required to implement INotifyPropertyChanged
/// </summary>
public static class HPropertyChanged
{
private static Dictionary<string, PropertyChangedEventArgs> argslookup = new Dictionary<string, PropertyChangedEventArgs>();
public static string ThisPropertyName([CallerMemberName]string name = "")
{
return name;
}
public static string GetPropertyName<T>(Expression<Func<T>> exp)
{
string rtn = "";
MemberExpression mex = exp.Body as MemberExpression;
if(mex!=null)
rtn = mex.Member.Name;
return rtn;
}
public static void SetValue<T>(ref T target, T newVal, object sender, PropertyChangedEventHandler handler, params string[] changed)
{
if (!target.Equals(newVal))
{
target = newVal;
PropertyChanged(sender, handler, changed);
}
}
public static void SetValue<T>(ref T target, T newVal, Action<PropertyChangedEventArgs> handler, params string[] changed)
{
if (!target.Equals(newVal))
{
target = newVal;
foreach (var item in changed)
{
handler(GetArg(item));
}
}
}
public static void PropertyChanged(object sender,PropertyChangedEventHandler handler,params string[] changed)
{
if (handler!=null)
{
foreach (var prop in changed)
{
handler(sender, GetArg(prop));
}
}
}
public static PropertyChangedEventArgs GetArg(string name)
{
if (!argslookup.ContainsKey(name)) argslookup.Add(name, new PropertyChangedEventArgs(name));
return argslookup[name];
}
}
изменить:
было высказано предположение, что я перехожу от класса-помощника к обертке значений, и с тех пор я использую этот метод, и я считаю, что он работает достаточно хорошо.
public class NotifyValue<T>
{
public static implicit operator T(NotifyValue<T> item)
{
return item.Value;
}
public NotifyValue(object parent, T value = default(T), PropertyChangingEventHandler changing = null, PropertyChangedEventHandler changed = null, params object[] dependenies)
{
_parent = parent;
_propertyChanged = changed;
_propertyChanging = changing;
if (_propertyChanged != null)
{
_propertyChangedArg =
dependenies.OfType<PropertyChangedEventArgs>()
.Union(
from d in dependenies.OfType<string>()
select new PropertyChangedEventArgs(d)
);
}
if (_propertyChanging != null)
{
_propertyChangingArg =
dependenies.OfType<PropertyChangingEventArgs>()
.Union(
from d in dependenies.OfType<string>()
select new PropertyChangingEventArgs(d)
);
}
_PostChangeActions = dependenies.OfType<Action>();
}
private T _Value;
public T Value
{
get { return _Value; }
set
{
SetValue(value);
}
}
public bool SetValue(T value)
{
if (!EqualityComparer<T>.Default.Equals(_Value, value))
{
OnPropertyChnaging();
_Value = value;
OnPropertyChnaged();
foreach (var action in _PostChangeActions)
{
action();
}
return true;
}
else
return false;
}
private void OnPropertyChnaged()
{
var handler = _propertyChanged;
if (handler != null)
{
foreach (var arg in _propertyChangedArg)
{
handler(_parent, arg);
}
}
}
private void OnPropertyChnaging()
{
var handler = _propertyChanging;
if(handler!=null)
{
foreach (var arg in _propertyChangingArg)
{
handler(_parent, arg);
}
}
}
private object _parent;
private PropertyChangedEventHandler _propertyChanged;
private PropertyChangingEventHandler _propertyChanging;
private IEnumerable<PropertyChangedEventArgs> _propertyChangedArg;
private IEnumerable<PropertyChangingEventArgs> _propertyChangingArg;
private IEnumerable<Action> _PostChangeActions;
}
пример использования
private NotifyValue<int> _val;
public const string ValueProperty = "Value";
public int Value
{
get { return _val.Value; }
set { _val.Value = value; }
}
то в конструкторе вы
_val = new NotifyValue<int>(this,0,PropertyChanged,PropertyChanging,ValueProperty );
Ответ 13
Просто используйте этот атрибут над объявлением автоматического объявления
[NotifyParentProperty(true)]
public object YourProperty { get; set; }