P.K*_*P.K 621 .net c# inotifypropertychanged winforms
微软应该已经实现了一些有趣的东西INotifyPropertyChanged
,就像在自动属性中一样,只需指定{get; set; notify;}
我认为这样做很有意义.或者有任何并发症吗?
我们自己可以在我们的属性中实现类似'notify'的内容.是否有一个优雅的解决方案,INotifyPropertyChanged
在您的班级实施或唯一的方法是通过提高PropertyChanged
每个属性中的事件.
如果没有,我们可以写一些东西来自动生成一段代码来引发PropertyChanged
事件吗?
Mar*_*ell 611
不使用像postharp这样的东西,我使用的最小版本使用如下:
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"); }
}
}
Run Code Online (Sandbox Code Playgroud)
每个属性都是这样的:
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, "Name"); }
}
Run Code Online (Sandbox Code Playgroud)
这不是很大; 如果你愿意,它也可以用作基类.在bool
从回SetField
告诉你,如果它是一个空操作,如果你想申请其他逻辑.
或者更容易使用C#5:
protected bool SetField<T>(ref T field, T value,
[CallerMemberName] string propertyName = null)
{...}
Run Code Online (Sandbox Code Playgroud)
可以像这样调用:
set { SetField(ref name, value); }
Run Code Online (Sandbox Code Playgroud)
编译器将"Name"
自动添加.
C#6.0使实现更容易:
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Run Code Online (Sandbox Code Playgroud)
......现在使用C#7:
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
private string name;
public string Name
{
get => name;
set => SetField(ref name, value);
}
Run Code Online (Sandbox Code Playgroud)
Dan*_*tle 196
.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));
}
}
Run Code Online (Sandbox Code Playgroud)
为函数添加比较器可能是个好主意.
EqualityComparer<T>.Default.Equals
Run Code Online (Sandbox Code Playgroud)
Tho*_*que 162
我真的很喜欢Marc的解决方案,但我认为可以略微改进以避免使用"魔术字符串"(不支持重构).不是将属性名称用作字符串,而是很容易使它成为lambda表达式:
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, () => Name); }
}
Run Code Online (Sandbox Code Playgroud)
只需将以下方法添加到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;
}
Run Code Online (Sandbox Code Playgroud)
Tom*_*der 117
还有Fody,它有一个PropertyChanged加载项,可以让你写这个:
[ImplementPropertyChanged]
public class Person
{
public string GivenNames { get; set; }
public string FamilyName { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
...并在编译时注入属性更改通知.
Pei*_*jen 64
我认为人们应该多关注一下性能,当有很多对象需要绑定时(考虑一个有10,000多行的网格)或者对象的值经常变化(实时监控应用程序),它确实会对UI产生影响.
我在这里和其他地方进行了各种实现并进行了比较,检查了INotifyPropertyChanged实现的性能比较.
这是对结果的看法
TiM*_*och 38
我在博客http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/中介绍了一个Bindable类 .Bindable使用字典作为属性包.为子类添加必要的重载以使用ref参数管理自己的支持字段非常容易.
代码:
public class Bindable : INotifyPropertyChanged {
private Dictionary<string, object> _properties = new Dictionary<string, object>();
/// <summary>
/// Gets the value of a property
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
protected T Get<T>([CallerMemberName] string name = null) {
Debug.Assert(name != null, "name != null");
object value = null;
if (_properties.TryGetValue(name, out value))
return value == null ? default(T) : (T)value;
return default(T);
}
/// <summary>
/// Sets the value of a property
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="name"></param>
/// <remarks>Use this overload when implicitly naming the property</remarks>
protected void Set<T>(T value, [CallerMemberName] string name = null) {
Debug.Assert(name != null, "name != null");
if (Equals(value, Get<T>(name)))
return;
_properties[name] = value;
OnPropertyChanged(name);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Run Code Online (Sandbox Code Playgroud)
它可以像这样使用:
public class Contact : Bindable {
public string FirstName {
get { return Get<string>(); }
set { Set(value); }
}
}
Run Code Online (Sandbox Code Playgroud)
Mar*_*ris 15
我实际上还没有机会尝试这个,但下次我正在设置一个对INotifyPropertyChanged有很大要求的项目我打算写一个Postsharp属性,它将在编译时注入代码.就像是:
[NotifiesChange]
public string FirstName { get; set; }
Run Code Online (Sandbox Code Playgroud)
会变成:
private string _firstName;
public string FirstName
{
get { return _firstname; }
set
{
if (_firstname != value)
{
_firstname = value;
OnPropertyChanged("FirstName")
}
}
}
Run Code Online (Sandbox Code Playgroud)
我不确定这是否会在实践中发挥作用,我需要坐下来尝试一下,但我不明白为什么不这样做.对于需要触发多个OnPropertyChanged的情况,我可能需要让它接受一些参数(例如,如果我在上面的类中有一个FullName属性)
目前我在Resharper中使用了一个自定义模板,但即便如此,我已经厌倦了我所有的属性这么长时间.
嗯,一个快速谷歌搜索(这是我写这之前,我应该这样做)显示,至少有一个人已经做过这样的事情在这里.不完全是我的想法,但足够接近表明理论是好的.
Joh*_*ohn 12
现在已经是 2022 年了。现在官方有了解决方案。
使用Microsoft MVVM Toolkit 中的MVVM 源生成器。
这
[ObservableProperty]
private string? name;
Run Code Online (Sandbox Code Playgroud)
将生成:
private string? name;
public string? Name
{
get => name;
set
{
if (!EqualityComparer<string?>.Default.Equals(name, value))
{
OnNameChanging(value);
OnPropertyChanging();
name = value;
OnNameChanged(value);
OnPropertyChanged();
}
}
}
// Property changing / changed listener
partial void OnNameChanging(string? value);
partial void OnNameChanged(string? value);
protected void OnPropertyChanging([CallerMemberName] string? propertyName = null)
{
PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(propertyName));
}
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Run Code Online (Sandbox Code Playgroud)
它支持 .NET 标准 2.0 和 .NET >= 5.0。
tes*_*der 11
是的,当然存在更好的方法.这里是:
基于这篇有用的文章,一步一步的教程缩小了.
安装包Castle.Core
安装包MvvmLightLibs
NotifierInterceptor
public class NotifierInterceptor : IInterceptor
{
private PropertyChangedEventHandler handler;
public static Dictionary<String, PropertyChangedEventArgs> _cache =
new Dictionary<string, PropertyChangedEventArgs>();
public void Intercept(IInvocation invocation)
{
switch (invocation.Method.Name)
{
case "add_PropertyChanged":
handler = (PropertyChangedEventHandler)
Delegate.Combine(handler, (Delegate)invocation.Arguments[0]);
invocation.ReturnValue = handler;
break;
case "remove_PropertyChanged":
handler = (PropertyChangedEventHandler)
Delegate.Remove(handler, (Delegate)invocation.Arguments[0]);
invocation.ReturnValue = handler;
break;
default:
if (invocation.Method.Name.StartsWith("set_"))
{
invocation.Proceed();
if (handler != null)
{
var arg = retrievePropertyChangedArg(invocation.Method.Name);
handler(invocation.Proxy, arg);
}
}
else invocation.Proceed();
break;
}
}
private static PropertyChangedEventArgs retrievePropertyChangedArg(String methodName)
{
PropertyChangedEventArgs arg = null;
_cache.TryGetValue(methodName, out arg);
if (arg == null)
{
arg = new PropertyChangedEventArgs(methodName.Substring(4));
_cache.Add(methodName, arg);
}
return arg;
}
}
Run Code Online (Sandbox Code Playgroud)
ProxyCreator
public class ProxyCreator
{
public static T MakeINotifyPropertyChanged<T>() where T : class, new()
{
var proxyGen = new ProxyGenerator();
var proxy = proxyGen.CreateClassProxy(
typeof(T),
new[] { typeof(INotifyPropertyChanged) },
ProxyGenerationOptions.Default,
new NotifierInterceptor()
);
return proxy as T;
}
}
Run Code Online (Sandbox Code Playgroud)
-
public class MainViewModel
{
public virtual string MainTextBox { get; set; }
public RelayCommand TestActionCommand
{
get { return new RelayCommand(TestAction); }
}
public void TestAction()
{
Trace.WriteLine(MainTextBox);
}
}
Run Code Online (Sandbox Code Playgroud)
将绑定放入xaml:
<TextBox Text="{Binding MainTextBox}" ></TextBox>
<Button Command="{Binding TestActionCommand}" >Test</Button>
Run Code Online (Sandbox Code Playgroud)将代码行放在代码隐藏文件MainWindow.xaml.cs中,如下所示:
DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();
注意!!!所有有界属性都应使用关键字virtual进行修饰,因为它们由castle proxy用于覆盖.
类似AOP的方法是将INotifyPropertyChanged内容注入已经实例化的对象中.您可以使用Castle DynamicProxy之类的功能执行此操作.这是一篇解释该技术的文章:
将INotifyPropertyChanged添加到现有对象
让我介绍一下我自己的方法,称为Yappi。它属于运行时代理|派生类生成器,向现有对象或类型添加新功能,如 Caste Project 的动态代理。
它允许在基类中实现 INotifyPropertyChanged 一次,然后按以下样式声明派生类,仍然支持新属性的 INotifyPropertyChanged:
public class Animal:Concept
{
protected Animal(){}
public virtual string Name { get; set; }
public virtual int Age { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
派生类或代理构造的复杂性可以隐藏在以下行后面:
var animal = Concept.Create<Animal>.New();
Run Code Online (Sandbox Code Playgroud)
所有 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);
};
}
}
}
Run Code Online (Sandbox Code Playgroud)
它对于重构来说是完全安全的,类型构造后不使用反射并且足够快。
小智 5
请看这里:http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx
它是用德语编写的,但您可以下载ViewModelBase.cs.cs-File中的所有注释都是用英文写的.
使用此ViewModelBase-Class,可以实现类似于众所周知的依赖项属性的可绑定属性:
public string SomeProperty
{
get { return GetValue( () => SomeProperty ); }
set { SetValue( () => SomeProperty, value ); }
}
Run Code Online (Sandbox Code Playgroud)
所有这些答案都非常好。
我的解决方案是使用代码片段来完成这项工作。
这使用了对 PropertyChanged 事件的最简单的调用。
保存此代码片段并像使用“fullprop”代码片段一样使用它。
该位置可以在 Visual Studio 的“工具\代码片段管理器...”菜单中找到。
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>inotifypropfull</Title>
<Shortcut>inotifypropfull</Shortcut>
<HelpUrl>http://ofirzeitoun.wordpress.com/</HelpUrl>
<Description>Code snippet for property and backing field with notification</Description>
<Author>Ofir Zeitoun</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>type</ID>
<ToolTip>Property type</ToolTip>
<Default>int</Default>
</Literal>
<Literal>
<ID>property</ID>
<ToolTip>Property name</ToolTip>
<Default>MyProperty</Default>
</Literal>
<Literal>
<ID>field</ID>
<ToolTip>The variable backing this property</ToolTip>
<Default>myVar</Default>
</Literal>
</Declarations>
<Code Language="csharp">
<![CDATA[private $type$ $field$;
public $type$ $property$
{
get { return $field$;}
set {
$field$ = value;
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs("$property$"));
}
}
}
$end$]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
Run Code Online (Sandbox Code Playgroud)
您可以根据需要修改调用(使用上述解决方案)
虽然显然有很多方法可以做到这一点,但除了 AOP 魔术答案之外,没有一个答案似乎是直接从视图模型设置模型的属性,而不需要引用本地字段。
问题是您无法引用属性。但是,您可以使用操作来设置该属性。
protected bool TrySetProperty<T>(Action<T> property, T newValue, T oldValue, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
{
return false;
}
property(newValue);
RaisePropertyChanged(propertyName);
return true;
}
Run Code Online (Sandbox Code Playgroud)
这可以像下面的代码摘录一样使用。
public int Prop {
get => model.Prop;
set => TrySetProperty(x => model.Prop = x, value, model.Prop);
}
Run Code Online (Sandbox Code Playgroud)
查看此BitBucket 存储库,了解该方法的完整实现以及实现相同结果的几种不同方法,包括使用 LINQ 的方法和使用反射的方法。请注意,这些方法的性能较慢。
归档时间: |
|
查看次数: |
294207 次 |
最近记录: |