C# 实施 INotifyPropertyChanged - 是否存在更好的方法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1315621/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Implementing INotifyPropertyChanged - does a better way exist?
提问by P.K
Microsoft should have implemented something snappy for INotifyPropertyChanged
, like in the automatic properties, just specify {get; set; notify;}
I think it makes a lot of sense to do it. Or are there any complications to do it?
微软应该为 实现一些活泼的东西INotifyPropertyChanged
,比如在自动属性中,只需指定{get; set; notify;}
我认为这样做很有意义。或者有什么复杂的事情要做吗?
Can we ourselves implement something like 'notify' in our properties. Is there a graceful solution for implementing INotifyPropertyChanged
in your class or the only way to do it is by raising the PropertyChanged
event in each property.
我们自己可以在我们的属性中实现像“通知”这样的东西吗?是否有一种优雅的解决方案可以INotifyPropertyChanged
在您的类中实现,或者唯一的方法是PropertyChanged
在每个属性中引发事件。
If not can we write something to auto-generate the piece of code to raise PropertyChanged
event?
如果不是,我们可以写一些东西来自动生成一段代码来引发PropertyChanged
事件吗?
采纳答案by Marc Gravell
Without using something like postsharp, the minimal version I use uses something like:
不使用 postsharp 之类的东西,我使用的最小版本使用了类似的东西:
public class Data : INotifyPropertyChanged
{
// boiler-plate
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
// props
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
}
Each property is then just something like:
每个属性都是这样的:
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
which isn't huge; it can also be used as a base-class if you want. The bool
return from SetField
tells you if it was a no-op, in case you want to apply other logic.
这不是很大;如果需要,它也可以用作基类。在bool
从回SetField
告诉你,如果它是一个空操作,如果你想申请其他逻辑。
or even easier with C# 5:
甚至使用 C# 5 更容易:
protected bool SetField<T>(ref T field, T value,
[CallerMemberName] string propertyName = null)
{...}
which can be called like this:
可以这样调用:
set { SetField(ref name, value); }
with which the compiler will add the "Name"
automatically.
编译器将"Name"
自动添加。
C# 6.0 makes the implementation easier:
C# 6.0 使实现更容易:
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
...and now with C#7:
...现在使用 C#7:
protected void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
private string name;
public string Name
{
get => name;
set => SetField(ref name, value);
}
回答by Martin Harris
I haven't actually had a chance to try this myself yet, but next time I'm setting up a project with a big requirement for INotifyPropertyChanged I'm intending on writing a Postsharpattribute that will inject the code at compile time. Something like:
我实际上还没有机会自己尝试这个,但是下次我要设置一个对 INotifyPropertyChanged 有很大要求的项目时,我打算编写一个Postsharp属性,该属性将在编译时注入代码。就像是:
[NotifiesChange]
public string FirstName { get; set; }
Will become:
会变成:
private string _firstName;
public string FirstName
{
get { return _firstname; }
set
{
if (_firstname != value)
{
_firstname = value;
OnPropertyChanged("FirstName")
}
}
}
I'm not sure if this will work in practice and I need to sit down and try it out, but I don't see why not. I may need to make it accept some parameters for situations where more than one OnPropertyChanged needs to be triggered (if, for example, I had a FullName property in the class above)
我不确定这在实践中是否可行,我需要坐下来尝试一下,但我不明白为什么不这样做。在需要触发多个 OnPropertyChanged 的情况下,我可能需要让它接受一些参数(例如,如果我在上面的类中有一个 FullName 属性)
Currently I'm using a custom template in Resharper, but even with that I'm getting fed up of all my properties being so long.
目前我在 Resharper 中使用自定义模板,但即便如此,我也厌倦了我所有的属性都太长了。
Ah, a quick Google search (which I should have done before I wrote this) shows that at least one person has done something like this before here. Not exactly what I had in mind, but close enough to show that the theory is good.
啊,一个快速的谷歌搜索(我应该在我写这篇文章之前做的)显示至少有一个人在这里做过这样的事情。不完全是我的想法,但足够接近以表明该理论是好的。
回答by Thomas Levesque
I really like Marc's solution, but I think it can be slightly improved to avoid using a "magic string" (which doesn't support refactoring). Instead of using the property name as a string, it's easy to make it a lambda expression :
我真的很喜欢 Marc 的解决方案,但我认为它可以稍微改进以避免使用“魔术字符串”(不支持重构)。不使用属性名称作为字符串,很容易使它成为一个 lambda 表达式:
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, () => Name); }
}
Just add the following methods to Marc's code, it will do the trick :
只需将以下方法添加到 Marc 的代码中,它就会起作用:
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
if (selectorExpression == null)
throw new ArgumentNullException("selectorExpression");
MemberExpression body = selectorExpression.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
OnPropertyChanged(body.Member.Name);
}
protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(selectorExpression);
return true;
}
BTW, this was inspired by this blog postupdated URL
回答by Alan
Talk about massive overengineering. This is significantly more complex than just doing it the right wayand gives little to no benefit. If your IDE supports code snippets(Visual Studio/MonoDevelop do) then you can make implementing this ridiculously simple. All you'd actually have to type is the type of the property and the property name. The extra three lines of code will be autogenerated.
谈论大规模的过度工程。这比仅以正确的方式执行要复杂得多,而且几乎没有任何好处。如果您的 IDE 支持代码片段(Visual Studio/MonoDevelop支持),那么您可以使实现这个变得非常简单。您实际需要输入的只是属性的类型和属性名称。额外的三行代码将自动生成。
回答by HokieMike
A very AOP-like approach is to inject the INotifyPropertyChanged stuff onto an already instantiated object on the fly. You can do this with something like Castle DynamicProxy. Here is an article that explains the technique:
一种非常类似 AOP 的方法是将 INotifyPropertyChanged 内容动态注入到已经实例化的对象上。你可以用 Castle DynamicProxy 之类的东西来做到这一点。这是一篇解释该技术的文章:
回答by Peter
Other things you may want to consider when implementing these sorts of properties is the fact that the INotifyPropertyChang *ed *ing both use event argument classes.
在实现这些类型的属性时,您可能需要考虑的其他事情是 INotifyPropertyChang *ed *ing 都使用事件参数类。
If you have a large number of properties that are being set then the number of event argument class instances can be huge, you should consider caching them as they are one of the areas that a string explosion can occur.
如果您要设置大量属性,则事件参数类实例的数量可能很大,您应该考虑缓存它们,因为它们是可能发生字符串爆炸的区域之一。
Take a look at this implementation and explanation of why it was conceived.
看一看这个实现并解释为什么会想到它。
回答by Ian Ringrose
I have just found ActiveSharp - Automatic INotifyPropertyChanged, I have yet to use it, but it looks good.
我刚刚找到ActiveSharp - Automatic INotifyPropertyChanged,我还没有使用它,但它看起来不错。
To quote from it's web site...
引用它的网站...
Send property change notifications without specifying property name as a string.
发送属性更改通知而不将属性名称指定为字符串。
Instead, write properties like this:
相反,编写如下属性:
public int Foo
{
get { return _foo; }
set { SetValue(ref _foo, value); } // <-- no property name here
}
Note that there is no need to include the name of the property as a string. ActiveSharp reliably and correctly figures that out for itself. It works based on the fact that your property implementation passes the backing field (_foo) by ref. (ActiveSharp uses that "by ref" call to identify which backing field was passed, and from the field it identifies the property).
请注意,无需将属性名称作为字符串包含在内。ActiveSharp 可靠且正确地自行解决了这一问题。它的工作原理是您的属性实现通过 ref 传递支持字段 (_foo)。(ActiveSharp 使用“by ref”调用来标识传递了哪个支持字段,并从该字段中标识了该属性)。
回答by Kelqualyn
Let me introduce my own approach called Yappi. It belongs to Runtime proxy|derived class generators, adding new functionality to an existing object or type, like Caste Project's Dynamic Proxy.
让我介绍一下我自己的方法,称为Yappi。它属于运行时代理|派生类生成器,为现有对象或类型添加新功能,如 Caste Project 的动态代理。
It allows to implement INotifyPropertyChanged once in base class, and then declare derived classes in following style, still supporting INotifyPropertyChanged for new properties:
它允许在基类中实现一次 INotifyPropertyChanged,然后按照以下样式声明派生类,仍然支持 INotifyPropertyChanged 用于新属性:
public class Animal:Concept
{
protected Animal(){}
public virtual string Name { get; set; }
public virtual int Age { get; set; }
}
Complexity of derived class or proxy construction can be hidden behind the following line:
派生类或代理构造的复杂性可以隐藏在以下行后面:
var animal = Concept.Create<Animal>.New();
And all INotifyPropertyChanged implementation work can be done like this:
并且所有 INotifyPropertyChanged 实现工作都可以这样完成:
public class Concept:INotifyPropertyChanged
{
//Hide constructor
protected Concept(){}
public static class Create<TConcept> where TConcept:Concept
{
//Construct derived Type calling PropertyProxy.ConstructType
public static readonly Type Type = PropertyProxy.ConstructType<TConcept, Implementation<TConcept>>(new Type[0], true);
//Create constructing delegate calling Constructor.Compile
public static Func<TConcept> New = Constructor.Compile<Func<TConcept>>(Type);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
{
var caller = PropertyChanged;
if(caller!=null)
{
caller(this, eventArgs);
}
}
//define implementation
public class Implementation<TConcept> : DefaultImplementation<TConcept> where TConcept:Concept
{
public override Func<TBaseType, TResult> OverrideGetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
{
return PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);
}
/// <summary>
/// Overriding property setter implementation.
/// </summary>
/// <typeparam name="TBaseType">Base type for implementation. TBaseType must be TConcept, and inherits all its constraints. Also TBaseType is TDeclaringType.</typeparam>
/// <typeparam name="TDeclaringType">Type, declaring property.</typeparam>
/// <typeparam name="TConstructedType">Constructed type. TConstructedType is TDeclaringType and TBaseType.</typeparam>
/// <typeparam name="TResult">Type of property.</typeparam>
/// <param name="property">PropertyInfo of property.</param>
/// <returns>Delegate, corresponding to property setter implementation.</returns>
public override Action<TBaseType, TResult> OverrideSetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
{
//This code called once for each declared property on derived type's initialization.
//EventArgs instance is shared between all events for each concrete property.
var eventArgs = new PropertyChangedEventArgs(property.Name);
//get delegates for base calls.
Action<TBaseType, TResult> setter = PropertyImplementation<TBaseType, TDeclaringType>.GetSetter<TResult>(property.Name);
Func<TBaseType, TResult> getter = PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);
var comparer = EqualityComparer<TResult>.Default;
return (pthis, value) =>
{//This code executes each time property setter is called.
if (comparer.Equals(value, getter(pthis))) return;
//base. call
setter(pthis, value);
//Directly accessing Concept's protected method.
pthis.OnPropertyChanged(eventArgs);
};
}
}
}
It is fully safe for refactoring, uses no reflection after type construction and fast enough.
重构是完全安全的,类型构建后不使用反射并且速度足够快。
回答by Daniel Little
As of .Net 4.5 there is finally an easy way to do this.
从 .Net 4.5 开始,终于有一种简单的方法可以做到这一点。
.Net 4.5 introduces a new Caller Information Attributes.
.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));
}
}
It's probably a good idea to add a comparer to the function as well.
向函数添加比较器可能也是一个好主意。
EqualityComparer<T>.Default.Equals
回答by DotNetMastermind
看这里:http: //dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx
It's written in German, but you can download the ViewModelBase.cs. All the comments in the cs-File are written in English.
它是用德语编写的,但您可以下载 ViewModelBase.cs。cs-File 中的所有注释都是用英文写的。
With this ViewModelBase-Class it is possible to implement bindable properties similar to the well known Dependency Properties :
使用这个 ViewModelBase-Class 可以实现类似于众所周知的 Dependency Properties 的可绑定属性:
public string SomeProperty
{
get { return GetValue( () => SomeProperty ); }
set { SetValue( () => SomeProperty, value ); }
}