微软应该为INotifyPropertyChanged实现一些时髦的东西,就像在自动属性中,只需要指定{get;设置;通知;} 我认为这样做很有意义。或者做这个手术有什么并发症吗?
我们能在属性中实现类似notify的东西吗。在你的类中实现INotifyPropertyChanged是否有一个优雅的解决方案,或者唯一的方法是在每个属性中引发PropertyChanged事件。
如果不是,我们可以写一些东西来自动生成一段代码来引发PropertyChanged事件?
微软应该为INotifyPropertyChanged实现一些时髦的东西,就像在自动属性中,只需要指定{get;设置;通知;} 我认为这样做很有意义。或者做这个手术有什么并发症吗?
我们能在属性中实现类似notify的东西吗。在你的类中实现INotifyPropertyChanged是否有一个优雅的解决方案,或者唯一的方法是在每个属性中引发PropertyChanged事件。
如果不是,我们可以写一些东西来自动生成一段代码来引发PropertyChanged事件?
当前回答
我正在编写一个处理INotifyPropertyChanged的库,主要思想是使用动态代理来通知更改。
回购在这里:CaulyKan/NoMorePropertyChanged
使用这个库,你可以写:
public dynamic Test1Binding { get; set; }
public TestDTO Test1
{
get { return (TestDTO)Test1Binding; }
set { SetBinding(nameof(Test1Binding), value); }
}
然后将所有绑定和修改转到Test1Binding,无论TestDTO有多复杂,它都会自动通知PropertyChange和CollectionChanged。
它还可以处理依赖关系。
[DependsOn("Test1Binding.TestString")]
public string Test2
{
get { return Test1Binding.TestString; }
}
请给我一些建议。
其他回答
从。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)
根据Thomas的回答,这是从Marc的回答改编而来的,我把反射属性更改代码变成了一个基类:
public abstract class PropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
protected void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
if (selectorExpression == null)
throw new ArgumentNullException("selectorExpression");
var me = selectorExpression.Body as MemberExpression;
// Nullable properties can be nested inside of a convert function
if (me == null)
{
var ue = selectorExpression.Body as UnaryExpression;
if (ue != null)
me = ue.Operand as MemberExpression;
}
if (me == null)
throw new ArgumentException("The body must be a member expression");
OnPropertyChanged(me.Member.Name);
}
protected void SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression, params Expression<Func<object>>[] additonal)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return;
field = value;
OnPropertyChanged(selectorExpression);
foreach (var item in additonal)
OnPropertyChanged(item);
}
}
用法与Thomas的答案相同,只是您可以传递要通知的其他属性。这对于处理需要在网格中刷新的计算列是必要的。
private int _quantity;
private int _price;
public int Quantity
{
get { return _quantity; }
set { SetField(ref _quantity, value, () => Quantity, () => Total); }
}
public int Price
{
get { return _price; }
set { SetField(ref _price, value, () => Price, () => Total); }
}
public int Total { get { return _price * _quantity; } }
我有这个驱动存储在BindingList中的项的集合,通过DataGridView暴露。它消除了我对网格进行手动Refresh()调用的需要。
我刚刚发现ActiveSharp - Automatic INotifyPropertyChanged,我还没有使用它,但它看起来不错。
从它的网站上引用…
发送属性更改通知 没有指定属性名为 字符串。
相反,可以这样写属性:
public int Foo
{
get { return _foo; }
set { SetValue(ref _foo, value); } // <-- no property name here
}
注意,不需要将属性的名称作为字符串包含。ActiveSharp可靠而正确地为自己计算出来。它的工作基于这样一个事实,即你的属性实现通过ref传递支持字段(_foo) (ActiveSharp使用“by ref”调用来识别哪个支持字段被传递,并从字段标识属性)。
我用这种方式解决(这有点费力,但在运行时肯定更快)。
在VB中(抱歉,但我认为用c#翻译它并不难),我用RE做了这个替换:
(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)( |\r\n)*(?<Def>(Public|Private|Friend|Protected) .*Property )(?<Name>[^ ]*) As (?<Type>.*?)[ |\r\n](?![ |\r\n]*Get)
:
Private _${Name} As ${Type}\r\n${Attr}\r\n${Def}${Name} As ${Type}\r\nGet\r\nReturn _${Name}\r\nEnd Get\r\nSet (Value As ${Type})\r\nIf _${Name} <> Value Then \r\n_${Name} = Value\r\nRaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("${Name}"))\r\nEnd If\r\nEnd Set\r\nEnd Property\r\n
这样转换所有代码:
<Bindable(True)>
Protected Friend Property StartDate As DateTime?
In
Private _StartDate As DateTime?
<Bindable(True)>
Protected Friend Property StartDate As DateTime?
Get
Return _StartDate
End Get
Set(Value As DateTime?)
If _StartDate <> Value Then
_StartDate = Value
RaiseEvent PropertyChange(Me, New ComponentModel.PropertyChangedEventArgs("StartDate"))
End If
End Set
End Property
如果我想有一个更可读的代码,我可以相反,只是做下面的替换:
Private _(?<Name>.*) As (?<Type>.*)[\r\n ]*(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)[\r\n ]*(?<Def>(Public|Private|Friend|Protected) .*Property )\k<Name> As \k<Type>[\r\n ]*Get[\r\n ]*Return _\k<Name>[\r\n ]*End Get[\r\n ]*Set\(Value As \k<Type>\)[\r\n ]*If _\k<Name> <> Value Then[\r\n ]*_\k<Name> = Value[\r\n ]*RaiseEvent PropertyChanged\(Me, New (.*ComponentModel\.)PropertyChangedEventArgs\("\k<Name>"\)\)[\r\n ]*End If[\r\n ]*End Set[\r\n ]*End Property
With
${Attr} ${Def} ${Name} As ${Type}
我抛出来替换set方法的IL代码,但我不能用IL编写大量编译代码…如果哪天我写了,我就说你!
我在我的基本库中创建了一个扩展方法用于重用:
public static class INotifyPropertyChangedExtensions
{
public static bool SetPropertyAndNotify<T>(this INotifyPropertyChanged sender,
PropertyChangedEventHandler handler, ref T field, T value,
[CallerMemberName] string propertyName = "",
EqualityComparer<T> equalityComparer = null)
{
bool rtn = false;
var eqComp = equalityComparer ?? EqualityComparer<T>.Default;
if (!eqComp.Equals(field,value))
{
field = value;
rtn = true;
if (handler != null)
{
var args = new PropertyChangedEventArgs(propertyName);
handler(sender, args);
}
}
return rtn;
}
}
这适用于. net 4.5,因为有CallerMemberNameAttribute。 如果你想在一个早期的。net版本中使用它,你必须改变方法声明:…,[CallerMemberName] string propertyName = "",………,字符串propertyName…
用法:
public class Dog : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string _name;
public string Name
{
get { return _name; }
set
{
this.SetPropertyAndNotify(PropertyChanged, ref _name, value);
}
}
}