如何在不将 [=10th=] 名称作为字符串传递的情况下引发 PropertyChanged 事件?
How to rise PropertyChanged event without passing property name as a string?
在 wpf 中,我们经常对可绑定属性使用以下模式:
private Foo _bar = new Foo();
public Foo Bar
{
get { return _bar; }
set
{
_bar = value;
OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string property = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
CallerMemberNameAttribute
施展魔法,从 setter 名称为我们生成 "Bar"
参数。
然而,经常有属性没有 setter 或依赖属性:
private Foo _bar;
public Foo Bar
{
get { return _bar; }
set
{
_bar = value;
OnPropertyChanged();
}
}
public bool IsBarNull
{
get { return _bar == null; }
}
在给定的示例中,当 Bar
发生变化时,IsBarNull
也需要事件。我们可以将 OnPropertyChanged("IsBarNull");
添加到 Bar
setter 中,但是...使用 string
属性是:
- 丑陋;
- 难以重构(VS 的 "Rename" 不会在
string
中重命名 属性 名称);
- 可能是各种错误的根源。
WPF存在了这么久。还没有神奇的解决方案(类似于CallerMemberNameAttribute
)吗?
使用 C# 6 和 nameof
功能:
OnPropertyChange(nameof(IsBarNull));
生成的等效代码为:
OnPropertyChange("IsBarNull");
...但没有脆弱性。
如果您坚持使用早期版本的 C#,您可以为此使用表达式树,但我认为这有点 hack 和潜在的性能问题 (因为每次调用都会重新创建树)。 nameof
不需要任何库支持,只需要一个新的编译器 - 所以如果你升级到 VS 2015(或更高版本,亲爱的未来读者......)你应该没问题。
DevExpress 在其 BindableBase
class 中提供了一种方法 SetProperty
,它反映了您想要设置的 属性:
private Foo _bar;
public Foo Bar
{
get { return _bar; }
set
{
SetProperty(ref _bar, value, () => Bar);
}
}
它会自动寻找正确的 属性 进行更新,并且只在必要时发送 属性 已更改的事件
如果手边没有 C# 6,这里有一些我经常使用的方法,使用表达式:
public static class ExpressionExtensions
{
public static string GetMemberName<T>(this Expression<Func<T, object>> expression)
{
return GetMemberName(expression.Body);
}
public static string GetMemberName (this Expression propertyExpression)
{
var lambda = propertyExpression as LambdaExpression;
MemberExpression memberExpression = null;
if (propertyExpression is UnaryExpression)
{
var unaryExpression = propertyExpression as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else if (lambda != null && lambda.Body is UnaryExpression)
{
var unaryExpression = lambda.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else if (lambda != null)
{
memberExpression = lambda.Body as MemberExpression;
}
else
{
var expression = propertyExpression as MemberExpression;
if (expression != null)
memberExpression = expression;
}
if (memberExpression == null) return null;
var propertyInfo = memberExpression.Member;
return propertyInfo.Name;
}
}
用法:
var propertyName = ExpressionExtensions.GetMemberName<DateTime>(item => item.Day);
在 wpf 中,我们经常对可绑定属性使用以下模式:
private Foo _bar = new Foo();
public Foo Bar
{
get { return _bar; }
set
{
_bar = value;
OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string property = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
CallerMemberNameAttribute
施展魔法,从 setter 名称为我们生成 "Bar"
参数。
然而,经常有属性没有 setter 或依赖属性:
private Foo _bar;
public Foo Bar
{
get { return _bar; }
set
{
_bar = value;
OnPropertyChanged();
}
}
public bool IsBarNull
{
get { return _bar == null; }
}
在给定的示例中,当 Bar
发生变化时,IsBarNull
也需要事件。我们可以将 OnPropertyChanged("IsBarNull");
添加到 Bar
setter 中,但是...使用 string
属性是:
- 丑陋;
- 难以重构(VS 的 "Rename" 不会在
string
中重命名 属性 名称); - 可能是各种错误的根源。
WPF存在了这么久。还没有神奇的解决方案(类似于CallerMemberNameAttribute
)吗?
使用 C# 6 和 nameof
功能:
OnPropertyChange(nameof(IsBarNull));
生成的等效代码为:
OnPropertyChange("IsBarNull");
...但没有脆弱性。
如果您坚持使用早期版本的 C#,您可以为此使用表达式树,但我认为这有点 hack 和潜在的性能问题 (因为每次调用都会重新创建树)。 nameof
不需要任何库支持,只需要一个新的编译器 - 所以如果你升级到 VS 2015(或更高版本,亲爱的未来读者......)你应该没问题。
DevExpress 在其 BindableBase
class 中提供了一种方法 SetProperty
,它反映了您想要设置的 属性:
private Foo _bar;
public Foo Bar
{
get { return _bar; }
set
{
SetProperty(ref _bar, value, () => Bar);
}
}
它会自动寻找正确的 属性 进行更新,并且只在必要时发送 属性 已更改的事件
如果手边没有 C# 6,这里有一些我经常使用的方法,使用表达式:
public static class ExpressionExtensions
{
public static string GetMemberName<T>(this Expression<Func<T, object>> expression)
{
return GetMemberName(expression.Body);
}
public static string GetMemberName (this Expression propertyExpression)
{
var lambda = propertyExpression as LambdaExpression;
MemberExpression memberExpression = null;
if (propertyExpression is UnaryExpression)
{
var unaryExpression = propertyExpression as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else if (lambda != null && lambda.Body is UnaryExpression)
{
var unaryExpression = lambda.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else if (lambda != null)
{
memberExpression = lambda.Body as MemberExpression;
}
else
{
var expression = propertyExpression as MemberExpression;
if (expression != null)
memberExpression = expression;
}
if (memberExpression == null) return null;
var propertyInfo = memberExpression.Member;
return propertyInfo.Name;
}
}
用法:
var propertyName = ExpressionExtensions.GetMemberName<DateTime>(item => item.Day);