实现INotifyPropertyChanged – 是否存在更好的方法?

微软应该为INotifyPropertyChanged实现一些动作,就像在自动属性中一样,只需指定{get; set; notify;} {get; set; notify;} {get; set; notify;}我认为这样做很有意义。 还是有什么并发症呢?

我们是否可以在我们的房产中实施类似“通知”的内容? 有没有一个优雅的解决scheme在您的类中实现INotifyPropertyChanged或唯一的方法是通过提高每个属性中的PropertyChanged事件。

如果没有,我们可以写一些东西来自动生成一段代码来提高PropertyChanged事件?

没有使用像postsharp之类的东西,我使用的最小版本使用类似于:

 public class Data : INotifyPropertyChanged { // boiler-plate public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetField<T>(ref T field, T value, string propertyName) { if (EqualityComparer<T>.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } // props private string name; public string Name { get { return name; } set { SetField(ref name, value, "Name"); } } } 

每个属性然后就像是:

  private string name; public string Name { get { return name; } set { SetField(ref name, value, "Name"); } } 

这不是很大; 如果你愿意的话,它也可以用作基类。 SetFieldbool返回告诉你它是否是空操作,以防你想应用其他的逻辑。


或者更容易使用C#5:

 protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null) {...} 

这可以这样调用:

 set { SetField(ref name, value); } 

编译器将自动添加"Name"


C#6.0使实现更简单:

 protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } 

甚至可以变得更简单:

 private string name; public string Name { get => name; set { SetField(ref name, value, nameof(Name)); } } 

从.Net 4.5开始,终于有了一个简单的方法来做到这一点。

.Net 4.5引入了一个新的主叫信息属性。

 private void OnPropertyChanged<T>([CallerMemberName]string caller = null) { // make sure only to call this if the value actually changes var handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(caller)); } } 

向函数中添加一个比较器也是一个好主意。

 EqualityComparer<T>.Default.Equals 

更多的例子在这里和这里

另请参阅来电者信息(C#和Visual Basic)

我真的很喜欢Marc的解决scheme,但我认为可以稍微改进以避免使用“魔术串”(不支持重构)。 而不是使用属性名称作为string,很容易使它成为一个lambdaexpression式:

 private string name; public string Name { get { return name; } set { SetField(ref name, value, () => Name); } } 

只需将以下方法添加到Marc的代码中,就可以实现:

 protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression) { if (selectorExpression == null) throw new ArgumentNullException("selectorExpression"); MemberExpression body = selectorExpression.Body as MemberExpression; if (body == null) throw new ArgumentException("The body must be a member expression"); OnPropertyChanged(body.Member.Name); } protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression) { if (EqualityComparer<T>.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(selectorExpression); return true; } 

顺便说一句,这是从这个博客文章的 更新url的启发

还有一个PropertyChanged加载项的Fody ,它可以让你编写这个:

 [ImplementPropertyChanged] public class Person { public string GivenNames { get; set; } public string FamilyName { get; set; } } 

…在编译时注入属性更改的通知。

我认为人们应该多关注一下性能,当有很多对象需要绑定时(比如想象一个有10000多行的网格),或者对象的值经常变化(实时监控应用程序) 。

我采取了各种实现在这里和其他地方find,并做了比较,检查了性能比较的INotifyPropertyChanged实现 。


这是对结果的窥视 实施与运行时间

我在http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/在我的博客中介绍了一个Bindable类。Bindable使用字典作为物业包。; 为子类添加必要的重载以使用ref参数pipe理其自己的后台字段已经很简单了。

  • 没有魔法string
  • 没有反思
  • 可以通过改进来抑制默认字典查找

代码:

 public class Bindable : INotifyPropertyChanged { private Dictionary<string, object> _properties = new Dictionary<string, object>(); /// <summary> /// Gets the value of a property /// </summary> /// <typeparam name="T"></typeparam> /// <param name="name"></param> /// <returns></returns> protected T Get<T>([CallerMemberName] string name = null) { Debug.Assert(name != null, "name != null"); object value = null; if (_properties.TryGetValue(name, out value)) return value == null ? default(T) : (T)value; return default(T); } /// <summary> /// Sets the value of a property /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <param name="name"></param> /// <remarks>Use this overload when implicitly naming the property</remarks> protected void Set<T>(T value, [CallerMemberName] string name = null) { Debug.Assert(name != null, "name != null"); if (Equals(value, Get<T>(name))) return; _properties[name] = value; OnPropertyChanged(name); } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) { handler(this, new PropertyChangedEventArgs(propertyName)); } } } 

它可以像这样使用:

 public class Contact : Bindable { public string FirstName { get { return Get<string>(); } set { Set(value); } } } 

我还没有真正有机会自己尝试这个,但下次我设置了一个需要INotifyPropertyChanged的大项目,我打算编写一个Postsharp属性,将在编译时注入代码。 就像是:

 [NotifiesChange] public string FirstName { get; set; } 

会变成:

 private string _firstName; public string FirstName { get { return _firstname; } set { if (_firstname != value) { _firstname = value; OnPropertyChanged("FirstName") } } } 

我不确定这是否会在实践中起作用,我需要坐下来尝试一下,但是我不明白为什么。 我可能需要让它接受一些需要触发多个OnPropertyChanged的情况下的参数(例如,如果我在上面的类中有一个FullName属性)

目前,我在Resharper中使用了一个自定义模板,但即使如此,我也厌倦了所有的属性。


啊,快速的谷歌search(我写这个之前应该做的)显示至less有一个人在这之前做过这样的事情。 不是我心目中的,但足以certificate理论是好的。

是的,更好的方式当然存在。 这里是:

基于这篇有用的文章 ,一步一步的教程缩小了我。

  • 创build新项目
  • 将城堡核心包安装到项目中

安装包Castle.Core

  • 仅安装mvvm灯库

安装包MvvmLightLibs

  • 在项目中添加两个类:

NotifierInterceptor

 public class NotifierInterceptor : IInterceptor { private PropertyChangedEventHandler handler; public static Dictionary<String, PropertyChangedEventArgs> _cache = new Dictionary<string, PropertyChangedEventArgs>(); public void Intercept(IInvocation invocation) { switch (invocation.Method.Name) { case "add_PropertyChanged": handler = (PropertyChangedEventHandler) Delegate.Combine(handler, (Delegate)invocation.Arguments[0]); invocation.ReturnValue = handler; break; case "remove_PropertyChanged": handler = (PropertyChangedEventHandler) Delegate.Remove(handler, (Delegate)invocation.Arguments[0]); invocation.ReturnValue = handler; break; default: if (invocation.Method.Name.StartsWith("set_")) { invocation.Proceed(); if (handler != null) { var arg = retrievePropertyChangedArg(invocation.Method.Name); handler(invocation.Proxy, arg); } } else invocation.Proceed(); break; } } private static PropertyChangedEventArgs retrievePropertyChangedArg(String methodName) { PropertyChangedEventArgs arg = null; _cache.TryGetValue(methodName, out arg); if (arg == null) { arg = new PropertyChangedEventArgs(methodName.Substring(4)); _cache.Add(methodName, arg); } return arg; } } 

ProxyCreator

 public class ProxyCreator { public static T MakeINotifyPropertyChanged<T>() where T : class, new() { var proxyGen = new ProxyGenerator(); var proxy = proxyGen.CreateClassProxy( typeof(T), new[] { typeof(INotifyPropertyChanged) }, ProxyGenerationOptions.Default, new NotifierInterceptor() ); return proxy as T; } } 
  • 创build您的视图模型,例如:

-

  public class MainViewModel { public virtual string MainTextBox { get; set; } public RelayCommand TestActionCommand { get { return new RelayCommand(TestAction); } } public void TestAction() { Trace.WriteLine(MainTextBox); } } 
  • 将绑定放入xaml:

     <TextBox Text="{Binding MainTextBox}" ></TextBox> <Button Command="{Binding TestActionCommand}" >Test</Button> 
  • 把代码行放在代码隐藏文件MainWindow.xaml.cs中,如下所示:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • 请享用。

在这里输入图像描述

注意!!! 所有有界的属性都应该用关键字虚拟来装饰,因为它们被城堡代理用来覆盖。

一个非常像AOP的方法是将INotifyPropertyChanged的东西注入一个已经实例化的对象上。 你可以用Castle DynamicProxy这样做。 这里是一篇文章,解释技术:

将INotifyPropertyChanged添加到现有的对象

看这里: http : //dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

它是用德语写的,但是你可以下载ViewModelBase.cs。 cs-File中的所有注释均以英文书写。

有了这个ViewModelBase类,就可以实现类似于众所周知的依赖属性的可绑定属性:

 public string SomeProperty { get { return GetValue( () => SomeProperty ); } set { SetValue( () => SomeProperty, value ); } } 

根据Thomas从Marc的回答改编的答案,我已经将reflection属性更改后的代码转换为基类:

 public abstract class PropertyChangedBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } protected void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression) { if (selectorExpression == null) throw new ArgumentNullException("selectorExpression"); var me = selectorExpression.Body as MemberExpression; // Nullable properties can be nested inside of a convert function if (me == null) { var ue = selectorExpression.Body as UnaryExpression; if (ue != null) me = ue.Operand as MemberExpression; } if (me == null) throw new ArgumentException("The body must be a member expression"); OnPropertyChanged(me.Member.Name); } protected void SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression, params Expression<Func<object>>[] additonal) { if (EqualityComparer<T>.Default.Equals(field, value)) return; field = value; OnPropertyChanged(selectorExpression); foreach (var item in additonal) OnPropertyChanged(item); } } 

用法与Thomas的答案相同,除了可以传递其他属性来通知。 这是处理需要在网格中刷新的计算列所必需的。

 private int _quantity; private int _price; public int Quantity { get { return _quantity; } set { SetField(ref _quantity, value, () => Quantity, () => Total); } } public int Price { get { return _price; } set { SetField(ref _price, value, () => Price, () => Total); } } public int Total { get { return _price * _quantity; } } 

我有这个驱动器存储在通过DataGridView公开的BindingList中的项目的集合。 它已经消除了我需要对网格进行手动刷新()调用的需要。

让我来介绍一下我自己的叫做Yappi的方法。 它属于运行时代理派生类生成器,为现有的对象或types添加新的function,例如种业项目的dynamic代理。

它允许在基类中实现一次INotifyPropertyChanged,然后以下面的风格声明派生类,仍然支持INotifyPropertyChanged新属性:

 public class Animal:Concept { protected Animal(){} public virtual string Name { get; set; } public virtual int Age { get; set; } } 

派生类或代理构造的复杂性可以隐藏在以下行的后面:

 var animal = Concept.Create<Animal>.New(); 

所有INotifyPropertyChanged实现工作都可以这样完成:

 public class Concept:INotifyPropertyChanged { //Hide constructor protected Concept(){} public static class Create<TConcept> where TConcept:Concept { //Construct derived Type calling PropertyProxy.ConstructType public static readonly Type Type = PropertyProxy.ConstructType<TConcept, Implementation<TConcept>>(new Type[0], true); //Create constructing delegate calling Constructor.Compile public static Func<TConcept> New = Constructor.Compile<Func<TConcept>>(Type); } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs) { var caller = PropertyChanged; if(caller!=null) { caller(this, eventArgs); } } //define implementation public class Implementation<TConcept> : DefaultImplementation<TConcept> where TConcept:Concept { public override Func<TBaseType, TResult> OverrideGetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property) { return PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name); } /// <summary> /// Overriding property setter implementation. /// </summary> /// <typeparam name="TBaseType">Base type for implementation. TBaseType must be TConcept, and inherits all its constraints. Also TBaseType is TDeclaringType.</typeparam> /// <typeparam name="TDeclaringType">Type, declaring property.</typeparam> /// <typeparam name="TConstructedType">Constructed type. TConstructedType is TDeclaringType and TBaseType.</typeparam> /// <typeparam name="TResult">Type of property.</typeparam> /// <param name="property">PropertyInfo of property.</param> /// <returns>Delegate, corresponding to property setter implementation.</returns> public override Action<TBaseType, TResult> OverrideSetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property) { //This code called once for each declared property on derived type's initialization. //EventArgs instance is shared between all events for each concrete property. var eventArgs = new PropertyChangedEventArgs(property.Name); //get delegates for base calls. Action<TBaseType, TResult> setter = PropertyImplementation<TBaseType, TDeclaringType>.GetSetter<TResult>(property.Name); Func<TBaseType, TResult> getter = PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name); var comparer = EqualityComparer<TResult>.Default; return (pthis, value) => {//This code executes each time property setter is called. if (comparer.Equals(value, getter(pthis))) return; //base. call setter(pthis, value); //Directly accessing Concept's protected method. pthis.OnPropertyChanged(eventArgs); }; } } } 

对于重构来说是完全安全的,build模后不需要reflection,而且速度足够快。

所有这些答案都非常好。

我的解决scheme是使用代码片段来完成这项工作。

这使用最简单的调用PropertyChanged事件。

保存此代码段并在使用“fullprop”代码段时使用它。

该位置可以在Visual Studio的'Tools \ Code Snippet Manager …'菜单中find。

 <?xml version="1.0" encoding="utf-8" ?> <CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"> <CodeSnippet Format="1.0.0"> <Header> <Title>inotifypropfull</Title> <Shortcut>inotifypropfull</Shortcut> <HelpUrl>http://ofirzeitoun.wordpress.com/</HelpUrl> <Description>Code snippet for property and backing field with notification</Description> <Author>Ofir Zeitoun</Author> <SnippetTypes> <SnippetType>Expansion</SnippetType> </SnippetTypes> </Header> <Snippet> <Declarations> <Literal> <ID>type</ID> <ToolTip>Property type</ToolTip> <Default>int</Default> </Literal> <Literal> <ID>property</ID> <ToolTip>Property name</ToolTip> <Default>MyProperty</Default> </Literal> <Literal> <ID>field</ID> <ToolTip>The variable backing this property</ToolTip> <Default>myVar</Default> </Literal> </Declarations> <Code Language="csharp"> <![CDATA[private $type$ $field$; public $type$ $property$ { get { return $field$;} set { $field$ = value; var temp = PropertyChanged; if (temp != null) { temp(this, new PropertyChangedEventArgs("$property$")); } } } $end$]]> </Code> </Snippet> </CodeSnippet> </CodeSnippets> 

您可以随意修改电话(使用上述解决scheme)

如果您在.NET 4.5中使用dynamic,则不需要担心INotifyPropertyChanged

 dynamic obj = new ExpandoObject(); obj.Name = "John"; 

如果名称绑定到一些控制,它只是正常工作。

我写了一篇文章,帮助( https://msdn.microsoft.com/magazine/mt736453 )。 您可以使用SolSoft.DataBinding NuGet包。 那么你可以写这样的代码:

 public class TestViewModel : IRaisePropertyChanged { public TestViewModel() { this.m_nameProperty = new NotifyProperty<string>(this, nameof(Name), null); } private readonly NotifyProperty<string> m_nameProperty; public string Name { get { return m_nameProperty.Value; } set { m_nameProperty.SetValue(value); } } // Plus implement IRaisePropertyChanged (or extend BaseViewModel) } 

优点:

  1. 基类是可选的
  2. 没有反映每一个“设定值”
  3. 可以具有取决于其他属性的属性,并且它们都自动地提出适当的事件(文章中有这样的例子)

在实现这些属性时,您可能需要考虑的其他事情是,INotifyPropertyChang * ed都使用事件参数类。

如果您有大量正在设置的属性,则事件参数类实例的数量可能会很大,因此您应该考虑caching它们,因为它们是string爆炸可能发生的区域之一。

看看这个实现和为什么构思的解释。

约什史密斯博客

使用reflection的想法:

 class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; bool Notify<T>(MethodBase mb, ref T oldValue, T newValue) { // Get Name of Property string name = mb.Name.Substring(4); // Detect Change bool changed = EqualityComparer<T>.Default.Equals(oldValue, newValue); // Return if no change if (!changed) return false; // Update value oldValue = newValue; // Raise Event if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); }//if // Notify caller of change return true; }//method string name; public string Name { get { return name; } set { Notify(MethodInfo.GetCurrentMethod(), ref this.name, value); } }//method }//class 

另一个组合的解决scheme是使用StackFrame:

 public class BaseViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void Set<T>(ref T field, T value) { MethodBase method = new StackFrame(1).GetMethod(); field = value; Raise(method.Name.Substring(4)); } protected void Raise(string propertyName) { var temp = PropertyChanged; if (temp != null) { temp(this, new PropertyChangedEventArgs(propertyName)); } } } 

用法:

 public class TempVM : BaseViewModel { private int _intP; public int IntP { get { return _intP; } set { Set<int>(ref _intP, value); } } } 

我在我的基础库中创build了一个扩展方法以供重用:

 public static class INotifyPropertyChangedExtensions { public static bool SetPropertyAndNotify<T>(this INotifyPropertyChanged sender, PropertyChangedEventHandler handler, ref T field, T value, [CallerMemberName] string propertyName = "", EqualityComparer<T> equalityComparer = null) { bool rtn = false; var eqComp = equalityComparer ?? EqualityComparer<T>.Default; if (!eqComp.Equals(field,value)) { field = value; rtn = true; if (handler != null) { var args = new PropertyChangedEventArgs(propertyName); handler(sender, args); } } return rtn; } } 

This works with .Net 4.5 because of CallerMemberNameAttribute . If you want to use it with an earlier .Net version you have to change the method declaration from: ...,[CallerMemberName] string propertyName = "", ... to ...,string propertyName, ...

用法:

 public class Dog : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; string _name; public string Name { get { return _name; } set { this.SetPropertyAndNotify(PropertyChanged, ref _name, value); } } } 

I keep this around as a snippet. C# 6 adds some nice syntax for invoking the handler.

 // INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; private void Set<T>(ref T property, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer<T>.Default.Equals(property, value) == false) { property = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } 

Here is a Unity3D or non-CallerMemberName version of NotifyPropertyChanged

 public abstract class Bindable : MonoBehaviour, INotifyPropertyChanged { private readonly Dictionary<string, object> _properties = new Dictionary<string, object>(); private static readonly StackTrace stackTrace = new StackTrace(); public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// Resolves a Property's name from a Lambda Expression passed in. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="property"></param> /// <returns></returns> internal string GetPropertyName<T>(Expression<Func<T>> property) { var expression = (MemberExpression) property.Body; var propertyName = expression.Member.Name; Debug.AssertFormat(propertyName != null, "Bindable Property shouldn't be null!"); return propertyName; } #region Notification Handlers /// <summary> /// Notify's all other objects listening that a value has changed for nominated propertyName /// </summary> /// <param name="propertyName"></param> internal void NotifyOfPropertyChange(string propertyName) { OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); } /// <summary> /// Notifies subscribers of the property change. /// </summary> /// <typeparam name="TProperty">The type of the property.</typeparam> /// <param name="property">The property expression.</param> internal void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property) { var propertyName = GetPropertyName(property); NotifyOfPropertyChange(propertyName); } /// <summary> /// Raises the <see cref="PropertyChanged" /> event directly. /// </summary> /// <param name="e">The <see cref="PropertyChangedEventArgs" /> instance containing the event data.</param> internal void OnPropertyChanged(PropertyChangedEventArgs e) { var handler = PropertyChanged; if (handler != null) { handler(this, e); } } #endregion #region Getters /// <summary> /// Gets the value of a property /// </summary> /// <typeparam name="T"></typeparam> /// <param name="name"></param> /// <returns></returns> internal T Get<T>(Expression<Func<T>> property) { var propertyName = GetPropertyName(property); return Get<T>(GetPropertyName(property)); } /// <summary> /// Gets the value of a property automatically based on its caller. /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> internal T Get<T>() { var name = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name; return Get<T>(name); } /// <summary> /// Gets the name of a property based on a string. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="name"></param> /// <returns></returns> internal T Get<T>(string name) { object value = null; if (_properties.TryGetValue(name, out value)) return value == null ? default(T) : (T) value; return default(T); } #endregion #region Setters /// <summary> /// Sets the value of a property whilst automatically looking up its caller name. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> internal void Set<T>(T value) { var propertyName = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name; Set(value, propertyName); } /// <summary> /// Sets the value of a property /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <param name="name"></param> internal void Set<T>(T value, string propertyName) { Debug.Assert(propertyName != null, "name != null"); if (Equals(value, Get<T>(propertyName))) return; _properties[propertyName] = value; NotifyOfPropertyChange(propertyName); } /// <summary> /// Sets the value of a property based off an Expression (()=>FieldName) /// </summary> /// <typeparam name="T"></typeparam> /// <param name="value"></param> /// <param name="property"></param> internal void Set<T>(T value, Expression<Func<T>> property) { var propertyName = GetPropertyName(property); Debug.Assert(propertyName != null, "name != null"); if (Equals(value, Get<T>(propertyName))) return; _properties[propertyName] = value; NotifyOfPropertyChange(propertyName); } #endregion } 

This code enables you to write property backing fields like this:

  public string Text { get { return Get<string>(); } set { Set(value); } } 

Furthermore, in resharper if you create a pattern/search snippet you can then also automate you're workflow by converting simple prop fields into the above backing.

Search Pattern:

 public $type$ $fname$ { get; set; } 

Replace Pattern:

 public $type$ $fname$ { get { return Get<$type$>(); } set { Set(value); } } 

I realize this question already has a gazillion answers, but none of them felt quite right for me. My issue is I don't want any performance hits and am willing to put up with a little verbosity for that reason alone. I also don't care too much for auto properties either, which led me to the following solution:

 public abstract class AbstractObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected virtual bool SetValue<TKind>(ref TKind Source, TKind NewValue, params string[] Notify) { //Set value if the new value is different from the old if (!Source.Equals(NewValue)) { Source = NewValue; //Notify all applicable properties foreach (var i in Notify) OnPropertyChanged(i); return true; } return false; } public AbstractObject() { } } 

In other words, the above solution is convenient if you don't mind doing this:

 public class SomeObject : AbstractObject { public string AnotherProperty { get { return someProperty ? "Car" : "Plane"; } } bool someProperty = false; public bool SomeProperty { get { return someProperty; } set { SetValue(ref someProperty, value, "SomeProperty", "AnotherProperty"); } } public SomeObject() : base() { } } 

优点

  • No reflection
  • Only notifies if old value != new value
  • Notify multiple properties at once

缺点

  • No auto properties (you can add support for both, though!)
  • Some verbosity
  • Boxing (small performance hit?)

Alas, it is still better than doing this,

 set { if (!someProperty.Equals(value)) { someProperty = value; OnPropertyChanged("SomeProperty"); OnPropertyChanged("AnotherProperty"); } } 

For every single property, which becomes a nightmare with the additional verbosity ;-(

Note, I do not claim this solution is better performance-wise compared to the others, just that it is a viable solution for those who don't like the other solutions presented.

I came up with this base class to implement the observable pattern, pretty much does what you need ( "automatically" implementing the set and get). I spent line an hour on this as prototype, so it doesn't have many unit tests, but proves the concept. Note it uses the Dictionary<string, ObservablePropertyContext> to remove the need for private fields.

  public class ObservableByTracking<T> : IObservable<T> { private readonly Dictionary<string, ObservablePropertyContext> _expando; private bool _isDirty; public ObservableByTracking() { _expando = new Dictionary<string, ObservablePropertyContext>(); var properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList(); foreach (var property in properties) { var valueContext = new ObservablePropertyContext(property.Name, property.PropertyType) { Value = GetDefault(property.PropertyType) }; _expando[BuildKey(valueContext)] = valueContext; } } protected void SetValue<T>(Expression<Func<T>> expression, T value) { var keyContext = GetKeyContext(expression); var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType); if (!_expando.ContainsKey(key)) { throw new Exception($"Object doesn't contain {keyContext.PropertyName} property."); } var originalValue = (T)_expando[key].Value; if (EqualityComparer<T>.Default.Equals(originalValue, value)) { return; } _expando[key].Value = value; _isDirty = true; } protected T GetValue<T>(Expression<Func<T>> expression) { var keyContext = GetKeyContext(expression); var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType); if (!_expando.ContainsKey(key)) { throw new Exception($"Object doesn't contain {keyContext.PropertyName} property."); } var value = _expando[key].Value; return (T)value; } private KeyContext GetKeyContext<T>(Expression<Func<T>> expression) { var castedExpression = expression.Body as MemberExpression; if (castedExpression == null) { throw new Exception($"Invalid expression."); } var parameterName = castedExpression.Member.Name; var propertyInfo = castedExpression.Member as PropertyInfo; if (propertyInfo == null) { throw new Exception($"Invalid expression."); } return new KeyContext {PropertyType = propertyInfo.PropertyType, PropertyName = parameterName}; } private static string BuildKey(ObservablePropertyContext observablePropertyContext) { return $"{observablePropertyContext.Type.Name}.{observablePropertyContext.Name}"; } private static string BuildKey(string parameterName, Type type) { return $"{type.Name}.{parameterName}"; } private static object GetDefault(Type type) { if (type.IsValueType) { return Activator.CreateInstance(type); } return null; } public bool IsDirty() { return _isDirty; } public void SetPristine() { _isDirty = false; } private class KeyContext { public string PropertyName { get; set; } public Type PropertyType { get; set; } } } public interface IObservable<T> { bool IsDirty(); void SetPristine(); } 

Here's the usage

 public class ObservableByTrackingTestClass : ObservableByTracking<ObservableByTrackingTestClass> { public ObservableByTrackingTestClass() { StringList = new List<string>(); StringIList = new List<string>(); NestedCollection = new List<ObservableByTrackingTestClass>(); } public IEnumerable<string> StringList { get { return GetValue(() => StringList); } set { SetValue(() => StringIList, value); } } public IList<string> StringIList { get { return GetValue(() => StringIList); } set { SetValue(() => StringIList, value); } } public int IntProperty { get { return GetValue(() => IntProperty); } set { SetValue(() => IntProperty, value); } } public ObservableByTrackingTestClass NestedChild { get { return GetValue(() => NestedChild); } set { SetValue(() => NestedChild, value); } } public IList<ObservableByTrackingTestClass> NestedCollection { get { return GetValue(() => NestedCollection); } set { SetValue(() => NestedCollection, value); } } public string StringProperty { get { return GetValue(() => StringProperty); } set { SetValue(() => StringProperty, value); } } } 

I have just found ActiveSharp – Automatic INotifyPropertyChanged , I have yet to use it, but it looks good.

To quote from it's web site…


Send property change notifications without specifying property name as a string.

Instead, write properties like this:

 public int Foo { get { return _foo; } set { SetValue(ref _foo, value); } // <-- no property name here } 

Note that there is no need to include the name of the property as a string. ActiveSharp reliably and correctly figures that out for itself. It works based on the fact that your property implementation passes the backing field (_foo) by ref. (ActiveSharp uses that "by ref" call to identify which backing field was passed, and from the field it identifies the property).

Another Idea…

  public class ViewModelBase : INotifyPropertyChanged { private Dictionary<string, object> _propertyStore = new Dictionary<string, object>(); protected virtual void SetValue<T>(T value, [CallerMemberName] string propertyName="") { _propertyStore[propertyName] = value; OnPropertyChanged(propertyName); } protected virtual T GetValue<T>([CallerMemberName] string propertyName = "") { object ret; if (_propertyStore.TryGetValue(propertyName, out ret)) { return (T)ret; } else { return default(T); } } //Usage //public string SomeProperty { // get { return GetValue<string>(); } // set { SetValue(value); } //} public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { var temp = PropertyChanged; if (temp != null) temp.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } 

=> here my solution with the following features

  public ResourceStatus Status { get { return _status; } set { _status = value; Notify(Npcea.Status,Npcea.Comments); } } 
  1. no refelction
  2. short notation
  3. no magic string in your business code
  4. Reusability of PropertyChangedEventArgs across application
  5. Possibility to notify multiple properties in one statement

用这个

 using System; using System.ComponentModel; using System.Reflection; using System.Reflection.Emit; using System.Runtime.Remoting.Messaging; using System.Runtime.Remoting.Proxies; public static class ObservableFactory { public static T Create<T>(T target) { if (!typeof(T).IsInterface) throw new ArgumentException("Target should be an interface", "target"); var proxy = new Observable<T>(target); return (T)proxy.GetTransparentProxy(); } } internal class Observable<T> : RealProxy, INotifyPropertyChanged, INotifyPropertyChanging { private readonly T target; internal Observable(T target) : base(ImplementINotify(typeof(T))) { this.target = target; } public override IMessage Invoke(IMessage msg) { var methodCall = msg as IMethodCallMessage; if (methodCall != null) { return HandleMethodCall(methodCall); } return null; } public event PropertyChangingEventHandler PropertyChanging; public event PropertyChangedEventHandler PropertyChanged; IMessage HandleMethodCall(IMethodCallMessage methodCall) { var isPropertySetterCall = methodCall.MethodName.StartsWith("set_"); var propertyName = isPropertySetterCall ? methodCall.MethodName.Substring(4) : null; if (isPropertySetterCall) { OnPropertyChanging(propertyName); } try { object methodCalltarget = target; if (methodCall.MethodName == "add_PropertyChanged" || methodCall.MethodName == "remove_PropertyChanged"|| methodCall.MethodName == "add_PropertyChanging" || methodCall.MethodName == "remove_PropertyChanging") { methodCalltarget = this; } var result = methodCall.MethodBase.Invoke(methodCalltarget, methodCall.InArgs); if (isPropertySetterCall) { OnPropertyChanged(methodCall.MethodName.Substring(4)); } return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall); } catch (TargetInvocationException invocationException) { var exception = invocationException.InnerException; return new ReturnMessage(exception, methodCall); } } protected virtual void OnPropertyChanged(string propertyName) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } protected virtual void OnPropertyChanging(string propertyName) { var handler = PropertyChanging; if (handler != null) handler(this, new PropertyChangingEventArgs(propertyName)); } public static Type ImplementINotify(Type objectType) { var tempAssemblyName = new AssemblyName(Guid.NewGuid().ToString()); var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly( tempAssemblyName, AssemblyBuilderAccess.RunAndCollect); var moduleBuilder = dynamicAssembly.DefineDynamicModule( tempAssemblyName.Name, tempAssemblyName + ".dll"); var typeBuilder = moduleBuilder.DefineType( objectType.FullName, TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract); typeBuilder.AddInterfaceImplementation(objectType); typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged)); typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanging)); var newType = typeBuilder.CreateType(); return newType; } } 

}

I resolved in This Way (it's a little bit laboriouse, but it's surely the faster in runtime).

In VB (sorry, but I think it's not hard translate it in C#), I make this substitution with RE:

 (?<Attr><(.*ComponentModel\.)Bindable\(True\)>)( |\r\n)*(?<Def>(Public|Private|Friend|Protected) .*Property )(?<Name>[^ ]*) As (?<Type>.*?)[ |\r\n](?![ |\r\n]*Get) 

有:

 Private _${Name} As ${Type}\r\n${Attr}\r\n${Def}${Name} As ${Type}\r\nGet\r\nReturn _${Name}\r\nEnd Get\r\nSet (Value As ${Type})\r\nIf _${Name} <> Value Then \r\n_${Name} = Value\r\nRaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("${Name}"))\r\nEnd If\r\nEnd Set\r\nEnd Property\r\n 

This transofrm all code like this:

 <Bindable(True)> Protected Friend Property StartDate As DateTime? 

 Private _StartDate As DateTime? <Bindable(True)> Protected Friend Property StartDate As DateTime? Get Return _StartDate End Get Set(Value As DateTime?) If _StartDate <> Value Then _StartDate = Value RaiseEvent PropertyChange(Me, New ComponentModel.PropertyChangedEventArgs("StartDate")) End If End Set End Property 

And If I want to have a more readable code, I can be the opposite just making the following substitution:

 Private _(?<Name>.*) As (?<Type>.*)[\r\n ]*(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)[\r\n ]*(?<Def>(Public|Private|Friend|Protected) .*Property )\k<Name> As \k<Type>[\r\n ]*Get[\r\n ]*Return _\k<Name>[\r\n ]*End Get[\r\n ]*Set\(Value As \k<Type>\)[\r\n ]*If _\k<Name> <> Value Then[\r\n ]*_\k<Name> = Value[\r\n ]*RaiseEvent PropertyChanged\(Me, New (.*ComponentModel\.)PropertyChangedEventArgs\("\k<Name>"\)\)[\r\n ]*End If[\r\n ]*End Set[\r\n ]*End Property 

 ${Attr} ${Def} ${Name} As ${Type} 

I throw to replace the IL code of the set method, but I can't write a lot of compiled code in IL… If a day I write it, I'll say you!

I use the following extension method (using C# 6.0) to make the INPC implemenation as easy as possible:

 public static bool ChangeProperty<T>(this PropertyChangedEventHandler propertyChanged, ref T field, T value, object sender, IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null) { if (comparer == null) comparer = EqualityComparer<T>.Default; if (comparer.Equals(field, value)) { return false; } else { field = value; propertyChanged?.Invoke(sender, new PropertyChangedEventArgs(propertyName)); return true; } } 

The INPC implementation boils down to (you can either implement this every time or create a base class):

 public class INPCBaseClass: INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected bool changeProperty<T>(ref T field, T value, IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null) { return PropertyChanged.ChangeProperty(ref field, value, this, comparer, propertyName); } } 

Then write your properties like this:

 private string testProperty; public string TestProperty { get { return testProperty; } set { changeProperty(ref testProperty, value); } } 

NOTE: You can omit the [CallerMemberName] declaration in the extension method, if you want, but I wanted to keep it flexible.

If you have properties without a backing field you can overload changeProperty :

 protected bool changeProperty<T>(T property, Action<T> set, T value, IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null) { bool ret = changeProperty(ref property, value, comparer, propertyName); if (ret) set(property); return ret; } 

An example use would be:

 public string MyTestProperty { get { return base.TestProperty; } set { changeProperty(base.TestProperty, (x) => { base.TestProperty = x; }, value); } } 

Prism 5 implementation:

 public abstract class BindableBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) { if (object.Equals(storage, value)) return false; storage = value; this.OnPropertyChanged(propertyName); return true; } protected void OnPropertyChanged(string propertyName) { var eventHandler = this.PropertyChanged; if (eventHandler != null) { eventHandler(this, new PropertyChangedEventArgs(propertyName)); } } protected void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression) { var propertyName = PropertySupport.ExtractPropertyName(propertyExpression); this.OnPropertyChanged(propertyName); } } public static class PropertySupport { public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression) { if (propertyExpression == null) { throw new ArgumentNullException("propertyExpression"); } var memberExpression = propertyExpression.Body as MemberExpression; if (memberExpression == null) { throw new ArgumentException("The expression is not a member access expression.", "propertyExpression"); } var property = memberExpression.Member as PropertyInfo; if (property == null) { throw new ArgumentException("The member access expression does not access a property.", "propertyExpression"); } var getMethod = property.GetMethod; if (getMethod.IsStatic) { throw new ArgumentException("The referenced property is a static property.", "propertyExpression"); } return memberExpression.Member.Name; } }