C# 重构INotifyPropertyChanged Setters

C# 重构INotifyPropertyChanged Setters,c#,wpf,inotifypropertychanged,getter-setter,C#,Wpf,Inotifypropertychanged,Getter Setter,我讨厌写这样的INPC设置器: public string Label { get {return _label;} set { if (_label == value) return; _label = value; NotifyPropertyChanged(() => Label); } } public string Label { get {return _label;} set { Se

我讨厌写这样的INPC设置器:

public string Label
{
   get {return _label;}
   set
   {
      if (_label == value) return;
      _label = value;
      NotifyPropertyChanged(() => Label);
   }
}
public string Label
{
   get {return _label;}
   set
   {
      SetProperty(() => Label, ref _label, value); 
   }
}
我想像重构INPC一样重构字段的设置;我想将
表达式
(可能还有backing字段)传入如下内容:

public string Label
{
   get {return _label;}
   set
   {
      if (_label == value) return;
      _label = value;
      NotifyPropertyChanged(() => Label);
   }
}
public string Label
{
   get {return _label;}
   set
   {
      SetProperty(() => Label, ref _label, value); 
   }
}
。。。下面是我在基类中提出的实现:

  public virtual void SetProperty<T>(Expression<Func<T>> expression, ref T field, T value)
  {
     if (Equals(field, value)) return;
     field = value;
     NotifyPropertyChanged(expression);
  }
public virtual void SetProperty(表达式,ref T字段,T值)
{
if(等于(字段、值))返回;
字段=值;
NotifyPropertyChanged(表达式);
}

。。。而且它似乎是有效的——至少它是编译的。我的问题是:我是否遗漏了什么?路过裁判会完成我的目标吗

是的,会的。不过,您仍然会对编写私有支持字段和重复部分感到有点厌倦:

private int _foo;
public int Foo { get{return _foo;}, set { SetProperty("Foo", ref _foo, value); } }
你不可能轻易逃脱

INPC的工作完全基于三件事:

  • 需要属性的名称
  • 需要提出这一事件
  • 还可以选择在某处实际存储值
价值存储实际上并不那么重要。这对于属性本身的实际行为很重要,但对于INPC本身并不重要。INPC只涉及名称+事件引发

你的方法还可以,我经常也这么做。编写INPCs实现是。。太无聊了。从技术上讲,你“发明”的东西(对不起,你不是第一个,我至少看到了十几个类似的实现:)其实没有什么区别,你只是将一些常用代码提取到一个方法中。不是火箭科学,但仍有几行保留下来。无法严格控制事件何时/如何引发会有一小部分“费用”。但这并不是一件很痛苦的事情,因为您可以随时在需要时编写手动事件上升和自定义条件

然而,有一些事情值得注意:

  • if(Equals(field,value))
    是一个很好的开始,但是对于自定义对象,它可能要求您在许多数据类中重写
    Equals
    GetHashCode
    ,这并不总是一个好主意,尤其是在WPF和绑定中。向WPF传递Equals/HashCode重写数据对象时,您必须小心,并准备好看到WPF有时会混淆,有时可能无法正确更新绑定(即,它可能无法注意到对象已被替换为新对象,并将一些绑定边界保留给旧实例)。最好对SetProperty进行重载,该重载将使用
    IEqualityComparer
    ;不过是化妆品
  • 在旧的.Net版本中,可以从调用堆栈自动获取名称,但代价非常昂贵。例如,这就是为什么许多框架(如XPO)故意在某个时候停止这样做,并要求您手动提供名称。然而,自从.NET4.5以来,“呼叫者信息”要便宜得多。请参阅:您将能够删除“字符串propertyName”。这会花一点钱,但对你来说可能很方便
  • 说到框架,请参见Caliburn Micro及其属性ChangedBase类。您可能会发现它非常有用(Caliburn在nuget上提供),或者,至少。它使用上述调用方信息作为默认值,还具有一些很好的功能,如布尔标志来临时禁用通知(
    IsNotifying
    )。在某些情况下非常有用
  • 还有一个商业
    PostSharp
    实用程序,它能够。。自动生成所有INPC实现。将PostSharp util作为后期构建步骤附加,它从属性中读取属性,如果发现某些属性被标记为to-be-a-INPC,则它将重写程序集的IL并将INPC实现添加到其中。有什么好处<代码>[INPC]公共int Foo{get;set;}并完成。然而,请注意,PostSharp是一家商业公司。如果你找到一个IL-weaver,你也许可以自己做这件事,可能是基于Mono的Cecil。或者你也可以找到一个免费的util。对不起,我不记得太多了
编辑:

现在您提到了它-您引用了
SetProperty(()=>Label,somepocclass.Label,value)
,它使用表达式以编译安全的方式查找属性名称(无字符串!重构/重命名有效)。如果您决定坚持使用ref,那么通过一些额外的工作,您可以获得
()=>标签
表达式,对其进行分析并提取名称,然后将其转换并重写为val=>\u Label=val delegate,并对每个字段缓存该委托,然后使用该缓存委托而不是使用ref参数,结果是:
set{SetProperty()=>标签,值);}

这几乎和当前的C#规格一样“酷”。但是:这样的SetProperty变得非常复杂,转换表达式并不轻松,但几乎可以一次性完成,但委托缓存不是“免费”的,您必须对它们进行缓存,因为计算转换后的委托过于繁重。我试过,我见过其他人试过,从我的观察来看,大多数人都退出了。许多同事认为这是一种魔力,如果有什么不对劲,他们就不会去碰它。很酷,很有趣,但是你不需要C大师来调试/修补简单的功能,比如INPC

编辑2:

更简单的方法是,获取一些T4模板来为您生成代码。这也是非常流行的处理方法。或者更确切地说,就像3-4年前一样。我知道周围有些人真的很喜欢T4。不知怎么的,我没有。我想写PHP或ASP的第一个版本,而不是Net。。但是不要在意我在这里的意见——看看它们,自己试试看。很多人喜欢它

顺便说一句,抱歉搞砸了-我刚刚想起了一些关于IL编织的事情:

  • 对于初学者,请参阅,内容非常丰富,涵盖了所有基础知识和更多动态代理,Cecil的il编织,(…)

  • “自由”号