Bud*_*dda 41 .net reflection lambda binding inotifypropertychanged
能够在没有明确指定已更改属性的名称的情况下引发"PropertyChanged"事件是一件好事.我想做这样的事情:
public string MyString
{
get { return _myString; }
set
{
ChangePropertyAndNotify<string>(val=>_myString=val, value);
}
}
private void ChangePropertyAndNotify<T>(Action<T> setter, T value)
{
setter(value);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(setter.Method.Name));
}
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,收到的名称是lambda方法的名称:"<set_MyString> b__0".
谢谢.
TCC*_*TCC 39
添加了C#6答案
在C#6(以及Visual Studio 2015附带的VB版本)中,我们拥有了nameof比以往更简单的操作符.在我下面的原始答案中,我使用C#5功能(调用者信息属性)来处理"自我更改"通知的常见情况.该nameof操作员可在所有情况下使用,并在"关联属性更改"通知情景特别有用.
为简单起见,我认为我将保留调用者信息属性方法以用于常见的自我更改通知.更少的输入意味着更少的拼写错误和复制/粘贴诱导错误的机会...这里的编译器确保您选择一个有效的类型/成员/变量,但它不能确保您选择正确的类型/成员/变量.然后使用new nameof运算符进行相关属性更改通知很简单.下面的示例演示了调用者信息属性的关键行为...如果参数由调用者指定,则该属性对参数没有影响(即,仅当参数值被省略时才为参数值提供调用者信息呼叫者,召集者).
值得注意的是nameof,PropertyChanged事件处理程序也可以使用该运算符.现在,您可以使用运算符PropertyName将事件中的值(即a string)与特定属性进行比较nameof,从而消除更多魔术字符串.
参考信息nameof:https://msdn.microsoft.com/en-us/library/dn986596.aspx
例:
public class Program
{
void Main()
{
var dm = new DataModel();
dm.PropertyChanged += propertyChangedHandler;
}
void propertyChangedHandler(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == nameof(DataModel.NumberSquared))
{
//do something spectacular
}
}
}
public class DataModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
public class DataModel : DataModelBase
{
//a simple property
string _something;
public string Something
{
get { return _something; }
set { _something = value; OnPropertyChanged(); }
}
//a property with another related property
int _number;
public int Number
{
get { return _number; }
set
{
_number = value;
OnPropertyChanged();
OnPropertyChanged(nameof(this.NumberSquared));
}
}
//a related property
public int NumberSquared { get { return Number * Number; } }
}
Run Code Online (Sandbox Code Playgroud)
原来的C#5答案
从C#5开始,最好使用调用者信息属性,这在编译时解决,无需反思.
我在基类中实现它,派生类只是OnPropertyChanged从属性设置器中调用方法.如果某个属性隐式更改了另一个值,我也可以在属性设置器中使用该方法的"显式"版本,然后不再"安全",但这是我接受的罕见情况.
或者,您可以将此方法用于自我更改通知,并使用@Jehof给出的相关属性更改通知的答案...这将具有无魔术字符串的优势,对于自我更改通知的常见情况执行速度最快.
这个最新的建议在下面实现(我想我会开始使用它!)
public class DataModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
OnPropertyChangedExplicit(propertyName);
}
protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection)
{
var memberExpression = (MemberExpression)projection.Body;
OnPropertyChangedExplicit(memberExpression.Member.Name);
}
void OnPropertyChangedExplicit(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
}
public class DataModel : DataModelBase
{
//a simple property
string _something;
public string Something
{
get { return _something; }
set { _something = value; OnPropertyChanged(); }
}
//a property with another related property
int _number;
public int Number
{
get { return _number; }
set
{
_number = value;
OnPropertyChanged();
OnPropertyChanged(() => NumberSquared);
}
}
//a related property
public int NumberSquared { get { return Number * Number; } }
}
Run Code Online (Sandbox Code Playgroud)
Fra*_*nov 29
更新:原始代码不是Windows Phone友好的,因为它依赖于LambdaExpression.Compile()来获取事件源对象.这是更新的扩展方法(也删除了参数检查):
public static void Raise<T>(this PropertyChangedEventHandler handler, Expression<Func<T>> propertyExpression)
{
if (handler != null)
{
var body = propertyExpression.Body as MemberExpression;
var expression = body.Expression as ConstantExpression;
handler(expression.Value, new PropertyChangedEventArgs(body.Member.Name));
}
}
Run Code Online (Sandbox Code Playgroud)
用法保持如下.
您可以使用反射调用属性getter的lambda函数获取属性名称.请注意,您实际上不必调用该lambda,只需要它用于反射:
public static class INotifyPropertyChangedHelper
{
public static void Raise<T>(this PropertyChangedEventHandler handler, Expression<Func<T>> propertyExpression)
{
if (handler != null)
{
var body = propertyExpression.Body as MemberExpression;
if (body == null)
throw new ArgumentException("'propertyExpression' should be a member expression");
var expression = body.Expression as ConstantExpression;
if (expression == null)
throw new ArgumentException("'propertyExpression' body should be a constant expression");
object target = Expression.Lambda(expression).Compile().DynamicInvoke();
var e = new PropertyChangedEventArgs(body.Member.Name);
handler(target, e);
}
}
public static void Raise<T>(this PropertyChangedEventHandler handler, params Expression<Func<T>>[] propertyExpressions)
{
foreach (var propertyExpression in propertyExpressions)
{
handler.Raise<T>(propertyExpression);
}
}
}
Run Code Online (Sandbox Code Playgroud)
以下是如何在类中使用该帮助程序来为一个或多个属性引发事件:
PropertyChanged.Raise(() => this.Now);
PropertyChanged.Raise(() => this.Age, () => this.Weight);
Run Code Online (Sandbox Code Playgroud)
请注意,这也帮助在情况下,无操作的PropertyChanged是null.
在下面的示例中,您必须传递3个值(支持字段,新值,属性为lambda),但没有魔术字符串,属性更改事件仅在真正不相等时才会引发.
class Sample : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set { this.SetProperty(ref _name, value, () => this.Name); }
}
protected void SetProperty<T>(ref T backingField, T newValue, Expression<Func<T>> propertyExpression)
{
if (backingField == null && newValue == null)
{
return;
}
if (backingField == null || !backingField.Equals(newValue))
{
backingField = newValue;
this.OnPropertyChanged(propertyExpression);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyExpression.GetPropertyName()));
}
}
}
Run Code Online (Sandbox Code Playgroud)
以下代码包含从lambda表达式获取属性名称的扩展方法.
public static class Extensions
{
public static string GetPropertyName<TProperty>(this Expression<Func<TProperty>> propertyExpression)
{
return propertyExpression.Body.GetMemberExpression().GetPropertyName();
}
public static string GetPropertyName(this MemberExpression memberExpression)
{
if (memberExpression == null)
{
return null;
}
if (memberExpression.Member.MemberType != MemberTypes.Property)
{
return null;
}
var child = memberExpression.Member.Name;
var parent = GetPropertyName(memberExpression.Expression.GetMemberExpression());
if (parent == null)
{
return child;
}
else
{
return parent + "." + child;
}
}
public static MemberExpression GetMemberExpression(this Expression expression)
{
var memberExpression = expression as MemberExpression;
if (memberExpression != null)
{
return memberExpression;
}
var unaryExpression = expression as UnaryExpression;
if (unaryExpression != null)
{
memberExpression = (MemberExpression)unaryExpression.Operand;
if (memberExpression != null)
{
return memberExpression;
}
}
return null;
}
public static void ShouldEqual<T>(this T actual, T expected, string name)
{
if (!Object.Equals(actual, expected))
{
throw new Exception(String.Format("{0}: Expected <{1}> Actual <{2}>.", name, expected, actual));
}
}
}
Run Code Online (Sandbox Code Playgroud)
最后一些测试代码:
class q3191536
{
public static void Test()
{
var sample = new Sample();
var propertyChanged = 0;
sample.PropertyChanged +=
new PropertyChangedEventHandler((sender, e) =>
{
if (e.PropertyName == "Name")
{
propertyChanged += 1;
}
}
);
sample.Name = "Budda";
sample.Name.ShouldEqual("Budda", "sample.Name");
propertyChanged.ShouldEqual(1, "propertyChanged");
sample.Name = "Tim";
sample.Name.ShouldEqual("Tim", sample.Name);
propertyChanged.ShouldEqual(2, "propertyChanged");
sample.Name = "Tim";
sample.Name.ShouldEqual("Tim", sample.Name);
propertyChanged.ShouldEqual(2, "propertyChanged");
}
}
Run Code Online (Sandbox Code Playgroud)
我正在使用扩展方法
public static class ExpressionExtensions {
public static string PropertyName<TProperty>(this Expression<Func<TProperty>> projection) {
var memberExpression = (MemberExpression)projection.Body;
return memberExpression.Member.Name;
}
}
Run Code Online (Sandbox Code Playgroud)
结合以下方法。该方法在实现INotifyPropertyChanged接口的类中定义(通常是派生我的其他类的基类)。
protected void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> projection) {
var e = new PropertyChangedEventArgs(projection.PropertyName());
OnPropertyChanged(e);
}
Run Code Online (Sandbox Code Playgroud)
然后我可以按如下方式引发PropertyChanged-Event
private double _rate;
public double Rate {
get {
return _rate;
}
set {
if (_rate != value) {
_rate = value;
OnPropertyChanged(() => Rate );
}
}
}
Run Code Online (Sandbox Code Playgroud)
使用此方法,可以轻松地重命名Properties(在Visual Studio中),从而确保也更新了相应的PropertyChanged调用。
| 归档时间: |
|
| 查看次数: |
37016 次 |
| 最近记录: |