重构 INotifyPropertyChanged 设置器
Refactoring INotifyPropertyChanged Setters
我讨厌这样写 INPC setter:
public string Label
{
get {return _label;}
set
{
if (_label == value) return;
_label = value;
NotifyPropertyChanged(() => Label);
}
}
我想像 INPC 一样重构字段的设置;我想将 Expression<Func<T>>
(可能还有支持字段)传递给这样的东西:
public string Label
{
get {return _label;}
set
{
SetProperty(() => Label, ref _label, value);
}
}
... 这是我在基础 class 中提出的实现:
public virtual void SetProperty<T>(Expression<Func<T>> expression, ref T field, T value)
{
if (Equals(field, value)) return;
field = value;
NotifyPropertyChanged(expression);
}
... 它 似乎 可以工作 - 它可以编译,至少 。我的问题是:我在这里遗漏了什么吗? 正在通过 ref 来完成我想做的事情吗?
是的,会的。不过,您仍然会对编写私有支持字段和重复部分感到厌烦:
private int _foo;
public int Foo { get{return _foo;}, set { SetProperty("Foo", ref _foo, value); } }
你无法轻易摆脱它。
INPC 的工作完全基于三件事:
- 需要 属性
的名称
- 需要引发事件
- 并且可选地需要以某种方式在某处实际存储值
储值其实没那么重要。 属性 本身的实际行为很重要,但对 INPC 本身并不重要。 INPC只是名字+活动筹码。
你的方法不错,我经常想这样做。编写 INPCs 实现是..太无聊了。从技术上讲,您 "invented" (抱歉,您不是第一个,我至少看到了十几个类似的实现:))实际上并没有太大区别,您只是将一些通用代码提取到一个方法中.不是火箭科学,但仍然保存了几行。有一点 "expense" 无法严格控制 when/how 引发事件。但这并没有太大的痛苦,因为您总是可以在需要时手动编写事件上升和自定义条件。
不过,有几点值得注意:
if (Equals(field, value))
是一个好的开始,但是对于自定义对象,它可能需要您在许多数据 class 中重写 Equals
和 GetHashCode
,但事实并非如此始终是个好主意,尤其是在 WPF 和绑定中。将 Equals/HashCode-overriding 数据对象传递给 WPF 时,您必须小心并做好准备,有时 WPF 会感到困惑,有时可能无法正确更新绑定(即它可能无法注意到对象已被新对象替换)并为旧实例保留一些绑定边界)。最好对 SetProperty 进行重载,它将采用 IEqualityComparer<T>
代替;虽然化妆品
- 在旧的 .Net 版本中,可以从调用堆栈中自动获取 NAME,但它非常昂贵。例如,这就是为什么许多框架(例如 XPO)故意在某个时候停止这样做并要求您手动提供名称的原因。但是,自 .Net 4.5 以来,"caller information" 便宜得多。请参阅:this article on
[CallerMemberName]
and friends,您将能够删除 "string propertyName"。它会花费一些,但它可能对你来说很方便。
- 说到框架,请参阅
Caliburn Micro
,它是 PropertyChangedBase
class。您可能会发现它非常有用(Caliburn 在 nuget 上可用)或者至少 see the implementation。它使用上述调用者信息作为默认值,并且还有一些不错的功能,例如用于暂时禁用通知的布尔标志 (IsNotifying
)。在某些情况下非常有用。
- 还有商业
PostSharp
实用程序,能够..自动生成所有 INPC 实现。您将 PostSharp 实用程序附加为后期构建步骤,它会从您的属性中读取属性,如果它发现某些 属性 被标记为 to-be-a-INPC,那么它会 重写 IL您的程序集 并向其中添加 INPC 实现。有什么好处? [INPC] public int Foo {get;set;}
完成。但是,请注意,PostSharp 是商业工具。如果你找到一个 IL 编织器,你也许可以自己做这样的事情,它可能基于 Mono 的 Cecil。或者,也许您也会找到一个免费的实用程序。我不记得更多了,抱歉。
编辑:
现在您提到了它 - 您引用了 SetProperty(()=> Label, somePocoClass.Label, value)
,它使用表达式以编译安全的方式查找 属性 的名称(无字符串!refactor/rename 有效)。如果你决定坚持使用 ref,那么通过一些额外的工作,你可以获得 ()=>Label
表达式,分析它并提取名称,然后将其转换并重写为 val => _label = val 委托,并缓存该委托 -字段,然后使用缓存的委托而不是使用 ref 参数,结果是:set{ SetProperty(()=>Label, value); }
这几乎是 "cool" 当前 C# 规范所能达到的最高水平。但是:这样的 SetProperty 变得非常复杂,转换表达式并不轻,但几乎可以一次性成本,但是委托缓存不是 "for free" 并且你必须缓存它们,因为即时计算转换后的委托太重了.我已经尝试过,我看到其他人也尝试过,根据我的观察,大多数人都放弃了。许多同事将其视为魔法,如果出现问题,他们不会尝试去碰它。很酷,很有趣,但是 debugging/patching 像 INPC 这样的简单功能不需要 C# 专家。
编辑 2:
更简单的方法 - 获取一些 T4 模板来为您生成代码。这也是非常流行的处理方式。或者更确切地说,就像 3-4 年前一样。我知道周围有些人真的很喜欢 T4。不知何故,我没有。我想写 PHP 或 ASP-not-Net 的第一个版本.. 但不要在意我在这里的意见 - 看看它们并自己尝试。很多人喜欢。
顺便说一句,抱歉打扰了 - 我刚刚想起一些关于 IL 编织的事情:
对于初学者,请参阅 this article,内容非常丰富,涵盖了所有基础知识以及更多内容 - 动态代理,il 由 Cecil 编织,(...)
"free PostSharp-like ILweaver" - 它叫做 Fody, on GitHub. See for example this article on INPC auto-implementation via Fody
如果您使用的是 .NET 4.5,您可以使用 CallerMemberName
属性来获取调用者,这样就不需要传入表达式了。
这是我用于 INPC 的基本方法
public virtual bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (Equals(field, value))
return false;
field = value;
OnNotifyPropertyChanged(propertyName);
return true;
}
这个用法类似
public string Label
{
get {return _label;}
set
{
bool changed = SetProperty(ref _label, value);
if(changed)
{
//Some other code you want to run only on change.
}
}
}
此模式的一个非常好的功能是它与 ReSharper 的 NotifyPropertyChangedInvocator
属性兼容,因此您可以拥有自动 rt.Click -> "To property with change notification".
我讨厌这样写 INPC setter:
public string Label
{
get {return _label;}
set
{
if (_label == value) return;
_label = value;
NotifyPropertyChanged(() => Label);
}
}
我想像 INPC 一样重构字段的设置;我想将 Expression<Func<T>>
(可能还有支持字段)传递给这样的东西:
public string Label
{
get {return _label;}
set
{
SetProperty(() => Label, ref _label, value);
}
}
... 这是我在基础 class 中提出的实现:
public virtual void SetProperty<T>(Expression<Func<T>> expression, ref T field, T value)
{
if (Equals(field, value)) return;
field = value;
NotifyPropertyChanged(expression);
}
... 它 似乎 可以工作 - 它可以编译,至少
是的,会的。不过,您仍然会对编写私有支持字段和重复部分感到厌烦:
private int _foo;
public int Foo { get{return _foo;}, set { SetProperty("Foo", ref _foo, value); } }
你无法轻易摆脱它。
INPC 的工作完全基于三件事:
- 需要 属性 的名称
- 需要引发事件
- 并且可选地需要以某种方式在某处实际存储值
储值其实没那么重要。 属性 本身的实际行为很重要,但对 INPC 本身并不重要。 INPC只是名字+活动筹码。
你的方法不错,我经常想这样做。编写 INPCs 实现是..太无聊了。从技术上讲,您 "invented" (抱歉,您不是第一个,我至少看到了十几个类似的实现:))实际上并没有太大区别,您只是将一些通用代码提取到一个方法中.不是火箭科学,但仍然保存了几行。有一点 "expense" 无法严格控制 when/how 引发事件。但这并没有太大的痛苦,因为您总是可以在需要时手动编写事件上升和自定义条件。
不过,有几点值得注意:
if (Equals(field, value))
是一个好的开始,但是对于自定义对象,它可能需要您在许多数据 class 中重写Equals
和GetHashCode
,但事实并非如此始终是个好主意,尤其是在 WPF 和绑定中。将 Equals/HashCode-overriding 数据对象传递给 WPF 时,您必须小心并做好准备,有时 WPF 会感到困惑,有时可能无法正确更新绑定(即它可能无法注意到对象已被新对象替换)并为旧实例保留一些绑定边界)。最好对 SetProperty 进行重载,它将采用IEqualityComparer<T>
代替;虽然化妆品- 在旧的 .Net 版本中,可以从调用堆栈中自动获取 NAME,但它非常昂贵。例如,这就是为什么许多框架(例如 XPO)故意在某个时候停止这样做并要求您手动提供名称的原因。但是,自 .Net 4.5 以来,"caller information" 便宜得多。请参阅:this article on
[CallerMemberName]
and friends,您将能够删除 "string propertyName"。它会花费一些,但它可能对你来说很方便。 - 说到框架,请参阅
Caliburn Micro
,它是PropertyChangedBase
class。您可能会发现它非常有用(Caliburn 在 nuget 上可用)或者至少 see the implementation。它使用上述调用者信息作为默认值,并且还有一些不错的功能,例如用于暂时禁用通知的布尔标志 (IsNotifying
)。在某些情况下非常有用。 - 还有商业
PostSharp
实用程序,能够..自动生成所有 INPC 实现。您将 PostSharp 实用程序附加为后期构建步骤,它会从您的属性中读取属性,如果它发现某些 属性 被标记为 to-be-a-INPC,那么它会 重写 IL您的程序集 并向其中添加 INPC 实现。有什么好处?[INPC] public int Foo {get;set;}
完成。但是,请注意,PostSharp 是商业工具。如果你找到一个 IL 编织器,你也许可以自己做这样的事情,它可能基于 Mono 的 Cecil。或者,也许您也会找到一个免费的实用程序。我不记得更多了,抱歉。
编辑:
现在您提到了它 - 您引用了 SetProperty(()=> Label, somePocoClass.Label, value)
,它使用表达式以编译安全的方式查找 属性 的名称(无字符串!refactor/rename 有效)。如果你决定坚持使用 ref,那么通过一些额外的工作,你可以获得 ()=>Label
表达式,分析它并提取名称,然后将其转换并重写为 val => _label = val 委托,并缓存该委托 -字段,然后使用缓存的委托而不是使用 ref 参数,结果是:set{ SetProperty(()=>Label, value); }
这几乎是 "cool" 当前 C# 规范所能达到的最高水平。但是:这样的 SetProperty 变得非常复杂,转换表达式并不轻,但几乎可以一次性成本,但是委托缓存不是 "for free" 并且你必须缓存它们,因为即时计算转换后的委托太重了.我已经尝试过,我看到其他人也尝试过,根据我的观察,大多数人都放弃了。许多同事将其视为魔法,如果出现问题,他们不会尝试去碰它。很酷,很有趣,但是 debugging/patching 像 INPC 这样的简单功能不需要 C# 专家。
编辑 2:
更简单的方法 - 获取一些 T4 模板来为您生成代码。这也是非常流行的处理方式。或者更确切地说,就像 3-4 年前一样。我知道周围有些人真的很喜欢 T4。不知何故,我没有。我想写 PHP 或 ASP-not-Net 的第一个版本.. 但不要在意我在这里的意见 - 看看它们并自己尝试。很多人喜欢。
顺便说一句,抱歉打扰了 - 我刚刚想起一些关于 IL 编织的事情:
对于初学者,请参阅 this article,内容非常丰富,涵盖了所有基础知识以及更多内容 - 动态代理,il 由 Cecil 编织,(...)
"free PostSharp-like ILweaver" - 它叫做 Fody, on GitHub. See for example this article on INPC auto-implementation via Fody
如果您使用的是 .NET 4.5,您可以使用 CallerMemberName
属性来获取调用者,这样就不需要传入表达式了。
这是我用于 INPC 的基本方法
public virtual bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
if (Equals(field, value))
return false;
field = value;
OnNotifyPropertyChanged(propertyName);
return true;
}
这个用法类似
public string Label
{
get {return _label;}
set
{
bool changed = SetProperty(ref _label, value);
if(changed)
{
//Some other code you want to run only on change.
}
}
}
此模式的一个非常好的功能是它与 ReSharper 的 NotifyPropertyChangedInvocator
属性兼容,因此您可以拥有自动 rt.Click -> "To property with change notification".