CSharp - 实施INotifyPropertyChanged是否存在更好的办法?

  显示原文与译文双语对照的内容

微软应该已经为 INotifyPropertyChanged 实现了一些快速的东西,比如在自动属性中,只需要指定 {get; set; notify;} 就可以了。 还是有任何复杂的事情要做?

我们可以在我们的属性中实现像'通知'这样的东西。 有没有一个优美的解决方案用于实现 INotifyPropertyChanged 突出在你的类或者方法,唯一的它是通过提高 PropertyChanged 事件在每个属性中。

如果不能,我们可以写一些代码给auto-generate来引发 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"); }
 }

这并不重要;如果你愿意,它也可以用作 base-class 。 boolSetField 返回告诉你是否为 no-op,如果你想应用其他逻辑。


或者更容易使用 C# 5:


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

还有:


set { SetField(ref name, value); }

编译器将在其中自动添加 "Name"

我非常喜欢marc的解决方案,但我认为它可以稍微改进以避免使用"魔术字符串"( 它不支持重构) 。 不使用属性名作为字符串,它很容易成为lambda表达式:


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

在. 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 )

我认为人们应该更加关注性能,这确实会影响到当有很多对象被绑定( 考虑带有 10,000 + 行的网格) 或者对象的值频繁变化时用户界面的影响。

我在这里和其他地方找到了各种实现,并进行了比较,查看了实现的性能比较。


下面是对结果的一瞥 Implemenation vs Runtime

另外还有 Fody 这要求有一个优先级 add-in,可以用于写入这里:


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

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

我还没有机会亲自尝试,但是下次我在设置一个对INotifyPropertyChanged的大需求,我打算编写一个图表的属性,它将在编译时注入代码。 就像这样:


[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中,但即使我已经厌倦了我的所有属性。


啊,谷歌搜索的话( 在我写这个之前我应该做的) 表明,至少有一个人已经在这里 之前行了这样的事情。 我还不清楚,但够近,表明理论是好的。

是的,更好的方法确实存在。 这就是:

根据这个有用的文章,循序渐进的教程被我缩小了。

  • 创建新工程
  • 将castle核心软件包安装到项目中

Install-Package Castle.Core

  • 只安装mvvm轻型库

Install-Package 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;
 }
}

  • 创建视图模型,例如:

-


 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>
    
    
  • 将代码行放在code-behind文件 MainWindow.xaml.cs 中,如下所示:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • 享受吧。

enter image description here

注意所有有界属性都应该用关键字virtual修饰,因为它们由castle代理用来重写。

...