【问题标题】:Implementing INotifyPropertyChanged - does a better way exist?实施 INotifyPropertyChanged - 是否存在更好的方法?
【发布时间】:2021-05-06 00:48:48
【问题描述】:

微软应该为INotifyPropertyChanged 实现了一些快速的东西,比如在自动属性中,只需指定{get; set; notify;} 我认为这样做很有意义。或者有什么并发症吗?

我们自己能否在我们的属性中实现类似“通知”的功能。是否有在您的类中实现 INotifyPropertyChanged 的优雅解决方案,或者唯一的方法是在每个属性中引发 PropertyChanged 事件。

如果不能,我们可以编写一些东西来自动生成代码段来引发PropertyChanged 事件吗?

【问题讨论】:

  • 您可以改用 DependencyObject 和 DependencyProperties。哈!我做了一个有趣的。
  • 当时不可能对 C# 进行更改,因为我们有大量的相互依赖关系。所以当 MVVM 诞生的时候,我想,我们真的没有花太多精力来解决这个问题,我知道 Patterns & Practices 团队在此过程中做了一些尝试(因此你也得到了 MEF 作为其中的一部分研究线程)。今天我认为 [CallerMemberName] 是上述问题的答案。

标签: c# .net winforms inotifypropertychanged


【解决方案1】:

我实际上还没有机会亲自尝试这个,但是下次我要设置一个对 INotifyPropertyChanged 有很大要求的项目时,我打算编写一个 Postsharp 属性,该属性将在编译时间。比如:

[NotifiesChange]
public string FirstName { get; set; }

会变成:

private string _firstName;

public string FirstName
{
   get { return _firstname; }
   set
   {
      if (_firstname != value)
      {
          _firstname = value;
          OnPropertyChanged("FirstName")
      }
   }
}

我不确定这在实践中是否可行,我需要坐下来尝试一下,但我不明白为什么不这样做。对于需要触发多个 OnPropertyChanged 的​​情况(例如,我在上面的类中有一个 FullName 属性),我可能需要让它接受一些参数

目前我在 Resharper 中使用自定义模板,但即便如此,我还是厌倦了我所有的属性都这么长。


啊,Google 的快速搜索(我应该在写这篇文章之前完成)显示至少有人在here 之前做过类似的事情。不完全是我的想法,但足够接近表明这个理论是好的。

【讨论】:

  • 一个名为 Fody 的免费工具似乎可以做同样的事情,充当通用的编译时代码注入器。它可以在 Nuget 中下载,它的 PropertyChanged 和 PropertyChanging 插件包也是如此。
【解决方案2】:

不使用 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"); }
    }
}

每个属性都类似于:

private string name;
public string Name
{
    get { return name; }
    set { SetField(ref name, value, "Name"); }
}

这不是很大;如果需要,它也可以用作基类。从SetField 返回的bool 告诉你它是否是空操作,以防你想应用其他逻辑。


使用 C# 5 甚至更容易:

protected bool SetField<T>(ref T field, T value,
    [CallerMemberName] string propertyName = null)
{...}

可以这样调用:

set { SetField(ref name, value); }

编译器将自动添加"Name"


C# 6.0 使实现更容易:

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

...现在使用 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);
}

而且,对于 C# 8 和 Nullable 引用类型,它看起来像这样:

public event PropertyChangedEventHandler? PropertyChanged;

protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
    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);
}

【讨论】:

  • 马克的绝招!我建议改进使用 lambda 表达式而不是属性名称,请参阅我的答案
  • @Thomas - lambda 一切都很好,但它为实际上非常简单的事情增加了很多开销。一个方便的技巧,但我不确定它是否总是实用的。
  • @Marc - 是的,它可能会降低性能......但是我真的很喜欢它在编译时检查并被“重命名”命令正确重构的事实
  • @Gusdor 幸运的是,使用 C#5 无需妥协 - 您可以通过(如 Pedro77 注释)[CallerMemberName] 获得两者的最佳效果
  • @Gusdor 语言和框架是分开的;您可以使用 C# 5 编译器,以 .NET 4 为目标,然后自己添加缺少的属性 - 它可以正常工作。它只需要具有正确的名称并位于正确的命名空间中。它不需要在特定的程序集中。
【解决方案3】:

我真的很喜欢 Marc 的解决方案,但我认为可以稍微改进以避免使用“魔术字符串”(不支持重构)。与其将属性名称用作字符串,不如将其设为 lambda 表达式:

private string name;
public string Name
{
    get { return name; }
    set { SetField(ref name, value, () => Name); }
}

只需将以下方法添加到 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;
}

顺便说一句,这是受到this blog post 的启发。

【讨论】:

  • 至少有一个框架使用这种方法,ReactiveUI
  • 很晚,这意味着要通过反射,这意味着性能下降。这可能是可以接受的,但设置属性并不是我希望我的应用程序花费很多周期的地方。
  • @BrunoBrant 你确定会影响性能吗?根据博客文章,反射发生在编译时而不是运行时(即静态反射)。
  • 我相信你的整个 OnPropertyChanged 已经过时了 C# 6 的 nameof 操作符,让这个怪物更时尚了。
  • @Traubenfuchs,实际上,C#5 的 CallerMemberName 属性使它更加简单,因为您根本不需要传递任何东西......
【解决方案4】:

谈论大规模的过度工程。这比just doing it the right way 复杂得多,几乎没有任何好处。如果您的 IDE 支持 code snippets(Visual Studio/MonoDevelop 支持),那么您可以使实现这一点变得非常简单。您实际上需要输入的只是属性的类型和属性名称。额外的三行代码将自动生成。

【讨论】:

  • 为了远离魔法字符串,你也可以使用这篇博文中的代码:blog.m.jedynak.pl/2009/02/static-typed-propety-names.html
  • 代码 sn-ps 在您编写代码时很不错,但可能会成为维护的痛苦。
  • 代码 sn-ps 根本解决不了问题。问题是重构属性名称时没有重命名魔术字符串。
【解决方案5】:

一种非常类似于 AOP 的方法是将 INotifyPropertyChanged 内容动态注入到已经实例化的对象上。您可以使用 Castle DynamicProxy 之类的东西来做到这一点。这是一篇解释该技术的文章:

Adding INotifyPropertyChanged to an existing object

【讨论】:

    【解决方案6】:

    在实现这些类型的属性时您可能需要考虑的其他事情是 INotifyPropertyChang *ed *ing 都使用事件参数类。

    如果您要设置大量属性,那么事件参数类实例的数量可能会很大,您应该考虑缓存它们,因为它们是可能发生字符串爆炸的区域之一。

    查看此实现并解释其构思的原因。

    Josh Smiths Blog

    【讨论】:

      【解决方案7】:

      刚刚找到ActiveSharp - Automatic INotifyPropertyChanged,还没用,不过看起来不错。

      引用它的网站...


      发送属性更改通知 没有将属性名称指定为 字符串。

      改为这样写属性:

      public int Foo
      {
          get { return _foo; }
          set { SetValue(ref _foo, value); }  // <-- no property name here
      }
      

      请注意,无需将属性名称包含为字符串。 ActiveSharp 可靠且正确地为自己解决了这个问题。它的工作原理是您的属性实现通过 ref 传递支持字段 (_foo)。 (ActiveSharp 使用“by ref”调用来识别传递了哪个支持字段,并从该字段中识别属性)。

      【讨论】:

        【解决方案8】:

        让我介绍一下我自己的方法,称为Yappi。 它属于运行时代理|派生类生成器,为现有对象或类型添加新功能,例如 Caste Project 的动态代理。

        它允许在基类中实现一次INotifyPropertyChanged,然后按照以下样式声明派生类,仍然支持新属性的INotifyPropertyChanged:

        public class Animal:Concept
        {
            protected Animal(){}
            public virtual string Name { get; set; }
            public virtual int Age { get; set; }
        }
        

        派生类或代理构造的复杂性可以隐藏在以下行之后:

        var animal = Concept.Create<Animal>.New();
        

        而所有 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);
                    };
                }
            }
        }
        

        重构完全安全,类型构造后不使用反射,速度足够快。

        【讨论】:

        • 为什么PropertyImplementation上需要TDeclaration类型参数?当然,您可以找到合适的类型来调用(而不是 callvirt)getter/setter 只使用TImplementation
        • Timplementation 在大多数情况下都有效。例外情况是: 1. 使用“新”C# keyvord 重新定义的属性。 2.显式接口实现的属性。
        【解决方案9】:

        从 .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
        

        更多示例herehere

        另见Caller Information (C# and Visual Basic)

        【讨论】:

        • 太棒了!但为什么它是通用的?
        • @abatishchev 我想不一定是这样,我只是在玩让函数也设置属性的想法。我会看看我是否可以更新我的答案以提供完整的解决方案。与此同时,额外的例子做得很好。
        • 它是由 C# 5.0 引入的。它与 .net 4.5 无关,但这是一个很好的解决方案!
        • @J. Lennon .net 4.5 仍然与它有关,毕竟属性来自某处msdn.microsoft.com/en-au/library/…
        • @Lavinski 将您的应用程序更改为例如 .NET 3.5 并看看什么会起作用(在 vs2012 中)
        【解决方案10】:

        看这里:http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

        它是用德语编写的,但您可以下载 ViewModelBase.cs。 cs-File中所有的cmets都是英文写的。

        使用这个 ViewModelBase-Class 可以实现类似于众所周知的依赖属性的可绑定属性:

        public string SomeProperty
        {
            get { return GetValue( () => SomeProperty ); }
            set { SetValue( () => SomeProperty, value ); }
        }
        

        【讨论】:

        • 链接已损坏。
        【解决方案11】:

        一个使用反射的想法:

        class ViewModelBase : INotifyPropertyChanged {
        
            public event PropertyChangedEventHandler PropertyChanged;
        
            bool Notify<T>(MethodBase mb, ref T oldValue, T newValue) {
        
                // Get Name of Property
                string name = mb.Name.Substring(4);
        
                // Detect Change
                bool changed = EqualityComparer<T>.Default.Equals(oldValue, newValue);
        
                // Return if no change
                if (!changed) return false;
        
                // Update value
                oldValue = newValue;
        
                // Raise Event
                if (PropertyChanged != null) {
                    PropertyChanged(this, new PropertyChangedEventArgs(name));
                }//if
        
                // Notify caller of change
                return true;
        
            }//method
        
            string name;
        
            public string Name {
                get { return name; }
                set {
                    Notify(MethodInfo.GetCurrentMethod(), ref this.name, value);
                }
            }//method
        
        }//class
        

        【讨论】:

        • 这很酷,我喜欢它胜过表达方式。不利的一面是,应该会更慢。
        【解决方案12】:

        另一个想法...

         public class ViewModelBase : INotifyPropertyChanged
        {
            private Dictionary<string, object> _propertyStore = new Dictionary<string, object>();
            protected virtual void SetValue<T>(T value, [CallerMemberName] string propertyName="") {
                _propertyStore[propertyName] = value;
                OnPropertyChanged(propertyName);
            }
            protected virtual T GetValue<T>([CallerMemberName] string propertyName = "")
            {
                object ret;
                if (_propertyStore.TryGetValue(propertyName, out ret))
                {
                    return (T)ret;
                }
                else
                {
                    return default(T);
                }
            }
        
            //Usage
            //public string SomeProperty {
            //    get { return GetValue<string>();  }
            //    set { SetValue(value); }
            //}
        
            public event PropertyChangedEventHandler PropertyChanged;
            protected void OnPropertyChanged(string propertyName)
            {
                var temp = PropertyChanged;
                if (temp != null)
                    temp.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        

        【讨论】:

          【解决方案13】:

          如果您在 .NET 4.5 中使用动态,则无需担心INotifyPropertyChanged

          dynamic obj = new ExpandoObject();
          obj.Name = "John";
          

          如果 Name 绑定到某个控件,它就可以正常工作。

          【讨论】:

          • 使用这个有什么缺点吗?
          【解决方案14】:

          所有这些答案都很好。

          我的解决方案是使用代码 sn-ps 来完成这项工作。

          这使用了对 PropertyChanged 事件的最简单调用。

          保存这个 sn-p 并像使用 'fullprop' sn-p 一样使用它。

          该位置可以在 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>
          

          您可以随意修改调用(使用上述解决方案)

          【讨论】:

            【解决方案15】:

            另一个组合解决方案是使用 StackFrame:

            public class BaseViewModel : INotifyPropertyChanged
            {
                public event PropertyChangedEventHandler PropertyChanged;
            
                protected void Set<T>(ref T field, T value)
                {
                    MethodBase method = new StackFrame(1).GetMethod();
                    field = value;
                    Raise(method.Name.Substring(4));
                }
            
                protected void Raise(string propertyName)
                {
                    var temp = PropertyChanged;
                    if (temp != null)
                    {
                        temp(this, new PropertyChangedEventArgs(propertyName));
                    }
                }
            }
            

            用法:

            public class TempVM : BaseViewModel
            {
                private int _intP;
                public int IntP
                {
                    get { return _intP; }
                    set { Set<int>(ref _intP, value); }
                }
            }
            

            【讨论】:

            • 这么快吗?对堆栈框架的访问是否不受某些权限要求的约束?这在使用 async/await 的情况下是否强大?
            • @StéphaneGourichon 不,不是。在大多数情况下,访问堆栈帧意味着相当大的性能损失。
            • 注意,内联可能会在Release模式下隐藏get_Foo方法。
            【解决方案16】:

            =>here我的解决方案具有以下功能

             public ResourceStatus Status
             {
                 get { return _status; }
                 set
                 {
                     _status = value;
                     Notify(Npcea.Status,Npcea.Comments);
                 }
             }
            
            1. 没有反射
            2. 短符号
            3. 您的业务代码中没有魔术字符串
            4. PropertyChangedEventArgs 跨应用程序的可重用性
            5. 可以在一个语句中通知多个属性

            【讨论】:

              【解决方案17】:

              还有Fody,它有一个PropertyChanged 插件,可以让你这样写:

              [ImplementPropertyChanged]
              public class Person 
              {        
                  public string GivenNames { get; set; }
                  public string FamilyName { get; set; }
              }
              

              ...并在编译时注入属性更改通知。

              【讨论】:

              • 我认为这正是 OP 在他们询问“我们自己是否可以在我们的属性中实现类似 'notify' 的东西时所寻找的。是否有一个优雅的解决方案可以在您的类中实现 INotifyPropertyChanged”
              • 这确实是唯一优雅的解决方案,并且正如@CADbloke 所说,它可以完美地工作。我也对织布工持怀疑态度,但我检查/重新检查了背后的 IL 代码,它很完美,很简单,只做你需要的一切,别无其他。它还挂钩并调用您在基类中为其指定的任何方法名称,无论是 NotifyOnProp...、OnNotify... 都无关紧要,因此可以与您可能拥有并实现 INotify.. 的任何基类很好地配合使用。 .
              • 您可以轻松地仔细检查编织器正在做什么,看看构建输出窗口,它列出了它编织的所有 PropertyChanged 东西。使用带有正则表达式模式"Fody/.*?:",LogCustom2,True 的 VScolorOutput 扩展以“自定义 2”颜色突出显示它。我把它做成了亮粉色,所以很容易找到。只需 Fody 一切,这是执行任何需要大量重复输入的最简洁的方法。
              • @mahmoudnezarsarhan 不,不是,我记得它的配置方式略有变化,但 Fody PropertyChanged 仍然活跃。
              • 它似乎已从 Fody 中删除
              【解决方案18】:

              是的,当然存在更好的方法。 这里是:

              分步教程由我缩小,基于此useful article

              • 创建新项目
              • 将城堡核心包安装到项目中

              安装包Castle.Core

              • 仅安装 mvvm light 库

              安装包 MvvmLightLibs

              • 在项目中添加两个类:

              通知拦截器

              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;
                      }
                  }
              

              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;
                  }
              }
              
              • 创建您的视图模型,例如:

              -

               public class MainViewModel
                  {
                      public virtual string MainTextBox { get; set; }
              
                      public RelayCommand TestActionCommand
                      {
                          get { return new RelayCommand(TestAction); }
                      }
              
                      public void TestAction()
                      {
                          Trace.WriteLine(MainTextBox);
                      }
                  }
              
              • 将绑定放入 xaml:

                <TextBox Text="{Binding MainTextBox}" ></TextBox>
                <Button Command="{Binding TestActionCommand}" >Test</Button>
                
              • 将代码行放入代码隐藏文件 MainWindow.xaml.cs 中,如下所示:

              DataContext = ProxyCreator.MakeINotifyPropertyChanged&lt;MainViewModel&gt;();

              • 享受吧。

              注意!!!所有有界属性都应该用 关键字 virtual 因为城堡代理使用它们进行覆盖。

              【讨论】:

              • 我很想知道您使用的是哪个版本的 Castle。我使用的是 3.3.0,而 CreateClassProxy 方法没有这些参数:typeinterfaces to applyinterceptors
              • 没关系,我使用的是通用的CreateClassProxy&lt;T&gt; 方法。非常不同......嗯,想知道为什么泛型方法如此有限。 :(
              【解决方案19】:

              我在我的博客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));
                      }
                  }
              }
              

              可以这样使用:

              public class Contact : Bindable {
                  public string FirstName {
                      get { return Get<string>(); }
                      set { Set(value); }
                  }
              }
              

              【讨论】:

              • 这是一个不错的解决方案,但唯一的缺点是涉及装箱/拆箱时对性能的影响很小。
              • 我建议使用 protected T Get&lt;T&gt;(T defaultValue, [CallerMemberName] string name = null) 并在 Set 中检查 if (_properties.ContainsKey(name) &amp;&amp; Equals(value, Get&lt;T&gt;(default(T), name)))(首次设置为默认值时提高并保存)
              • @Miquel 添加对自定义默认值的支持肯定会很有用,但是您应该小心,仅在值实际更改时才引发更改的事件。将属性设置为其具有的相同值不应引发事件。我必须承认在大多数情况下它是无害的,但是我已经有好几次属性被设置为相同值的数千次,而事件破坏了 UI 响应能力。
              • @stakx 我有一些基于此构建的应用程序,以支持用于撤消/重做的备忘录模式或在 nhibernate 不可用的应用程序中启用工作单元模式
              • 我真的很喜欢这个特殊的解决方案:简短的符号、没有动态代理的东西、没有 IL 干预等。虽然,你可以通过消除指定的需要来缩短它 T 每次通过使 Get 返回动态来获取 Get。我知道,这会影响运行时性能,但是现在 getter 和 setter 的代码终于可以始终相同,并且在一行,赞美主!附言在将 valuetypes 的默认值作为动态返回时,您应该在 Get 方法中(一次编写基类时)格外小心。确保始终返回正确的默认值(可以做到)
              【解决方案20】:

              我认为人们应该更多地关注性能;当有很多对象要绑定时(想想一个超过 10,000 行的网格),或者对象的值经常变化(实时监控应用程序),它确实会影响 UI。

              我在这里和其他地方找到了各种实现并进行了比较;看看perfomance comparison of INotifyPropertyChanged implementations


              这里是结果的一瞥

              【讨论】:

              • -1 :没有性能开销:CallerMemberName 在编译时更改为文字值。只需尝试反编译您的应用程序。
              • 这里是相应的问答:stackoverflow.com/questions/22580623/…
              • @JYL,你是对的,CallerMemberName 没有增加很大的开销。我上次尝试时一定是实施了错误。稍后我将更新博客和答案以反映 CallerMemberName 和 Fody 实现的基准。
              • 如果您在 UI 中有超过 10,000 个网格,那么您可能应该结合处理性能的方法,例如每页仅显示 10、50、100、250 次点击的分页...
              • Austin Rhymer ,如果你有大数据 + 50 使用数据虚拟化不需要加载所有数据它只会加载当前 scolling 显示区域上可见的数据!
              【解决方案21】:

              我在我的基础库中创建了一个扩展方法以供重复使用:

              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 = "", ... 更改为 ...,string propertyName, ...

              用法:

              public class Dog : INotifyPropertyChanged
              {
                  public event PropertyChangedEventHandler PropertyChanged;
                  string _name;
              
                  public string Name
                  {
                      get { return _name; }
                      set
                      {
                          this.SetPropertyAndNotify(PropertyChanged, ref _name, value);
                      }
                  }
              }
              

              【讨论】:

                【解决方案22】:

                使用这个

                using System;
                using System.ComponentModel;
                using System.Reflection;
                using System.Reflection.Emit;
                using System.Runtime.Remoting.Messaging;
                using System.Runtime.Remoting.Proxies;
                
                
                public static class ObservableFactory
                {
                    public static T Create<T>(T target)
                    {
                        if (!typeof(T).IsInterface)
                            throw new ArgumentException("Target should be an interface", "target");
                
                        var proxy = new Observable<T>(target);
                        return (T)proxy.GetTransparentProxy();
                    }
                }
                
                internal class Observable<T> : RealProxy, INotifyPropertyChanged, INotifyPropertyChanging
                {
                    private readonly T target;
                
                    internal Observable(T target)
                        : base(ImplementINotify(typeof(T)))
                    {
                        this.target = target;
                    }
                
                    public override IMessage Invoke(IMessage msg)
                    {
                        var methodCall = msg as IMethodCallMessage;
                
                        if (methodCall != null)
                        {
                            return HandleMethodCall(methodCall);
                        }
                
                        return null;
                    }
                
                    public event PropertyChangingEventHandler PropertyChanging;
                    public event PropertyChangedEventHandler PropertyChanged;
                
                
                
                    IMessage HandleMethodCall(IMethodCallMessage methodCall)
                    {
                        var isPropertySetterCall = methodCall.MethodName.StartsWith("set_");
                        var propertyName = isPropertySetterCall ? methodCall.MethodName.Substring(4) : null;
                
                        if (isPropertySetterCall)
                        {
                            OnPropertyChanging(propertyName);
                        }
                
                        try
                        {
                            object methodCalltarget = target;
                
                            if (methodCall.MethodName == "add_PropertyChanged" || methodCall.MethodName == "remove_PropertyChanged"||
                                methodCall.MethodName == "add_PropertyChanging" || methodCall.MethodName == "remove_PropertyChanging")
                            {
                                methodCalltarget = this;
                            }
                
                            var result = methodCall.MethodBase.Invoke(methodCalltarget, methodCall.InArgs);
                
                            if (isPropertySetterCall)
                            {
                                OnPropertyChanged(methodCall.MethodName.Substring(4));
                            }
                
                            return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
                        }
                        catch (TargetInvocationException invocationException)
                        {
                            var exception = invocationException.InnerException;
                            return new ReturnMessage(exception, methodCall);
                        }
                    }
                
                    protected virtual void OnPropertyChanged(string propertyName)
                    {
                        var handler = PropertyChanged;
                        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
                    }
                
                    protected virtual void OnPropertyChanging(string propertyName)
                    {
                        var handler = PropertyChanging;
                        if (handler != null) handler(this, new PropertyChangingEventArgs(propertyName));
                    }
                
                    public static Type ImplementINotify(Type objectType)
                    {
                        var tempAssemblyName = new AssemblyName(Guid.NewGuid().ToString());
                
                        var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
                            tempAssemblyName, AssemblyBuilderAccess.RunAndCollect);
                
                        var moduleBuilder = dynamicAssembly.DefineDynamicModule(
                            tempAssemblyName.Name,
                            tempAssemblyName + ".dll");
                
                        var typeBuilder = moduleBuilder.DefineType(
                            objectType.FullName, TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract);
                
                        typeBuilder.AddInterfaceImplementation(objectType);
                        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged));
                        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanging));
                        var newType = typeBuilder.CreateType();
                        return newType;
                    }
                }
                

                }

                【讨论】:

                  【解决方案23】:

                  我以这种方式解决(有点费力,但运行时肯定更快)。

                  在 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?
                  

                  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
                  

                  ${Attr} ${Def} ${Name} As ${Type}
                  

                  我扔去替换set方法的IL代码,但是我不能在IL里写很多编译后的代码……如果有一天我写出来,我就说你!

                  【讨论】:

                    【解决方案24】:

                    根据 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; } }
                    

                    我有这个驱动存储在通过 DataGridView 公开的 BindingList 中的项目集合。它消除了我对网格进行手动 Refresh() 调用的需要。

                    【讨论】:

                      【解决方案25】:

                      我把它作为一个sn-p保留。 C# 6 为调用处理程序添加了一些不错的语法。

                      // INotifyPropertyChanged
                      
                      public event PropertyChangedEventHandler PropertyChanged;
                      
                      private void Set<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
                      {
                          if (EqualityComparer<T>.Default.Equals(property, value) == false)
                          {
                              property = value;
                              PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                          }
                      }
                      

                      【讨论】:

                        【解决方案26】:

                        我使用以下扩展方法(使用 C# 6.0)使 INPC 实现尽可能简单:

                        public static bool ChangeProperty<T>(this PropertyChangedEventHandler propertyChanged, ref T field, T value, object sender,
                            IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
                        {
                            if (comparer == null)
                                comparer = EqualityComparer<T>.Default;
                        
                            if (comparer.Equals(field, value))
                            {
                                return false;
                            }
                            else
                            {
                                field = value;
                                propertyChanged?.Invoke(sender, new PropertyChangedEventArgs(propertyName));
                                return true;
                            }
                        }
                        

                        INPC 实现归结为(您可以每次都实现,也可以创建一个基类):

                        public class INPCBaseClass: INotifyPropertyChanged
                        {
                            public event PropertyChangedEventHandler PropertyChanged;
                        
                            protected bool changeProperty<T>(ref T field, T value,
                                IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
                            {
                                return PropertyChanged.ChangeProperty(ref field, value, this, comparer, propertyName);
                            }
                        }
                        

                        然后像这样写你的属性:

                        private string testProperty;
                        public string TestProperty
                        {
                            get { return testProperty; }
                            set { changeProperty(ref testProperty, value); }
                        }
                        

                        注意:如果需要,您可以在扩展方法中省略 [CallerMemberName] 声明,但我想保持灵活性。

                        如果您有没有支持字段的属性,您可以重载changeProperty

                        protected bool changeProperty<T>(T property, Action<T> set, T value,
                            IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
                        {
                            bool ret = changeProperty(ref property, value, comparer, propertyName);
                            if (ret)
                                set(property);
                            return ret;
                        }
                        

                        一个例子是:

                        public string MyTestProperty
                        {
                            get { return base.TestProperty; }
                            set { changeProperty(base.TestProperty, (x) => { base.TestProperty = x; }, value); }
                        }
                        

                        【讨论】:

                          【解决方案27】:

                          Prism 5 实施:

                          public abstract class BindableBase : INotifyPropertyChanged
                          {
                              public event PropertyChangedEventHandler PropertyChanged;
                          
                              protected virtual bool SetProperty<T>(ref T storage,
                                                                    T value,
                                                                    [CallerMemberName] string propertyName = null)
                              {
                                  if (object.Equals(storage, value)) return false;
                          
                                  storage = value;
                                  this.OnPropertyChanged(propertyName);
                          
                                  return true;
                              }
                          
                              protected void OnPropertyChanged(string propertyName)
                              {
                                  var eventHandler = this.PropertyChanged;
                                  if (eventHandler != null)
                                  {
                                      eventHandler(this, new PropertyChangedEventArgs(propertyName));
                                  }
                              }
                          
                              protected void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
                              {
                                  var propertyName = PropertySupport.ExtractPropertyName(propertyExpression);
                                  this.OnPropertyChanged(propertyName);
                              }
                          }
                          
                          public static class PropertySupport
                          {
                              public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
                              {
                                  if (propertyExpression == null)
                                  {
                                      throw new ArgumentNullException("propertyExpression");
                                  }
                          
                                  var memberExpression = propertyExpression.Body as MemberExpression;
                                  if (memberExpression == null)
                                  {
                                      throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
                                  }
                          
                                  var property = memberExpression.Member as PropertyInfo;
                                  if (property == null)
                                  {
                                      throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
                                  }
                          
                                  var getMethod = property.GetMethod;
                                  if (getMethod.IsStatic)
                                  {
                                      throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
                                  }
                          
                                  return memberExpression.Member.Name;
                              }
                          }
                          

                          【讨论】:

                            【解决方案28】:

                            这里是 NotifyPropertyChanged 的​​ Unity3D 或非 CallerMemberName 版本

                            public abstract class Bindable : MonoBehaviour, INotifyPropertyChanged
                            {
                                private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
                                private static readonly StackTrace stackTrace = new StackTrace();
                                public event PropertyChangedEventHandler PropertyChanged;
                            
                                /// <summary>
                                ///     Resolves a Property's name from a Lambda Expression passed in.
                                /// </summary>
                                /// <typeparam name="T"></typeparam>
                                /// <param name="property"></param>
                                /// <returns></returns>
                                internal string GetPropertyName<T>(Expression<Func<T>> property)
                                {
                                    var expression = (MemberExpression) property.Body;
                                    var propertyName = expression.Member.Name;
                            
                                    Debug.AssertFormat(propertyName != null, "Bindable Property shouldn't be null!");
                                    return propertyName;
                                }
                            
                                #region Notification Handlers
                            
                                /// <summary>
                                ///     Notify's all other objects listening that a value has changed for nominated propertyName
                                /// </summary>
                                /// <param name="propertyName"></param>
                                internal void NotifyOfPropertyChange(string propertyName)
                                {
                                    OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
                                }
                            
                                /// <summary>
                                ///     Notifies subscribers of the property change.
                                /// </summary>
                                /// <typeparam name="TProperty">The type of the property.</typeparam>
                                /// <param name="property">The property expression.</param>
                                internal void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
                                {
                                    var propertyName = GetPropertyName(property);
                                    NotifyOfPropertyChange(propertyName);
                                }
                            
                                /// <summary>
                                ///     Raises the <see cref="PropertyChanged" /> event directly.
                                /// </summary>
                                /// <param name="e">The <see cref="PropertyChangedEventArgs" /> instance containing the event data.</param>
                                internal void OnPropertyChanged(PropertyChangedEventArgs e)
                                {
                                    var handler = PropertyChanged;
                                    if (handler != null)
                                    {
                                        handler(this, e);
                                    }
                                }
                            
                                #endregion
                            
                                #region Getters
                            
                                /// <summary>
                                ///     Gets the value of a property
                                /// </summary>
                                /// <typeparam name="T"></typeparam>
                                /// <param name="name"></param>
                                /// <returns></returns>
                                internal T Get<T>(Expression<Func<T>> property)
                                {
                                    var propertyName = GetPropertyName(property);
                                    return Get<T>(GetPropertyName(property));
                                }
                            
                                /// <summary>
                                ///     Gets the value of a property automatically based on its caller.
                                /// </summary>
                                /// <typeparam name="T"></typeparam>
                                /// <returns></returns>
                                internal T Get<T>()
                                {
                                    var name = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
                                    return Get<T>(name);
                                }
                            
                                /// <summary>
                                ///     Gets the name of a property based on a string.
                                /// </summary>
                                /// <typeparam name="T"></typeparam>
                                /// <param name="name"></param>
                                /// <returns></returns>
                                internal T Get<T>(string name)
                                {
                                    object value = null;
                                    if (_properties.TryGetValue(name, out value))
                                        return value == null ? default(T) : (T) value;
                                    return default(T);
                                }
                            
                                #endregion
                            
                                #region Setters
                            
                                /// <summary>
                                ///     Sets the value of a property whilst automatically looking up its caller name.
                                /// </summary>
                                /// <typeparam name="T"></typeparam>
                                /// <param name="value"></param>
                                internal void Set<T>(T value)
                                {
                                    var propertyName = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
                                    Set(value, propertyName);
                                }
                            
                                /// <summary>
                                ///     Sets the value of a property
                                /// </summary>
                                /// <typeparam name="T"></typeparam>
                                /// <param name="value"></param>
                                /// <param name="name"></param>
                                internal void Set<T>(T value, string propertyName)
                                {
                                    Debug.Assert(propertyName != null, "name != null");
                                    if (Equals(value, Get<T>(propertyName)))
                                        return;
                                    _properties[propertyName] = value;
                                    NotifyOfPropertyChange(propertyName);
                                }
                            
                                /// <summary>
                                ///     Sets the value of a property based off an Expression (()=>FieldName)
                                /// </summary>
                                /// <typeparam name="T"></typeparam>
                                /// <param name="value"></param>
                                /// <param name="property"></param>
                                internal void Set<T>(T value, Expression<Func<T>> property)
                                {
                                    var propertyName = GetPropertyName(property);
                            
                                    Debug.Assert(propertyName != null, "name != null");
                            
                                    if (Equals(value, Get<T>(propertyName)))
                                        return;
                                    _properties[propertyName] = value;
                                    NotifyOfPropertyChange(propertyName);
                                }
                            
                                #endregion
                            }
                            

                            此代码使您能够编写如下属性支持字段:

                              public string Text
                                {
                                    get { return Get<string>(); }
                                    set { Set(value); }
                                }
                            

                            此外,在 resharper 中,如果您创建一个模式/搜索 sn-p,您还可以通过将简单的 prop 字段转换为上述支持来自动化您的工作流程。

                            搜索模式:

                            public $type$ $fname$ { get; set; }
                            

                            替换模式:

                            public $type$ $fname$
                            {
                                get { return Get<$type$>(); }
                                set { Set(value); }
                            }
                            

                            【讨论】:

                              【解决方案29】:

                              我写了一篇文章来帮助解决这个问题 (https://msdn.microsoft.com/magazine/mt736453)。您可以使用 SolSoft.DataBinding NuGet 包。然后你可以这样写代码:

                              public class TestViewModel : IRaisePropertyChanged
                              {
                                public TestViewModel()
                                {
                                  this.m_nameProperty = new NotifyProperty<string>(this, nameof(Name), null);
                                }
                              
                                private readonly NotifyProperty<string> m_nameProperty;
                                public string Name
                                {
                                  get
                                  {
                                    return m_nameProperty.Value;
                                  }
                                  set
                                  {
                                    m_nameProperty.SetValue(value);
                                  }
                                }
                              
                                // Plus implement IRaisePropertyChanged (or extend BaseViewModel)
                              }
                              

                              好处:

                              1. 基类是可选的
                              2. 不考虑每个“设定值”
                              3. 可以具有依赖于其他属性的属性,并且它们都会自动引发相应的事件(文章中有一个示例)

                              【讨论】:

                                【解决方案30】:

                                我意识到这个问题已经有无数个答案,但没有一个适合我。我的问题是我不希望任何性能受到影响,并且愿意仅仅因为这个原因而忍受一点冗长。我也不太关心汽车属性,这导致我采用了以下解决方案:

                                public abstract class AbstractObject : INotifyPropertyChanged
                                {
                                    public event PropertyChangedEventHandler PropertyChanged;
                                
                                    public void OnPropertyChanged(string propertyName)
                                    {
                                        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                                    }
                                
                                    protected virtual bool SetValue<TKind>(ref TKind Source, TKind NewValue, params string[] Notify)
                                    {
                                        //Set value if the new value is different from the old
                                        if (!Source.Equals(NewValue))
                                        {
                                            Source = NewValue;
                                
                                            //Notify all applicable properties
                                            foreach (var i in Notify)
                                                OnPropertyChanged(i);
                                
                                            return true;
                                        }
                                
                                        return false;
                                    }
                                
                                    public AbstractObject()
                                    {
                                    }
                                }
                                

                                换句话说,如果您不介意这样做,上述解决方案很方便:

                                public class SomeObject : AbstractObject
                                {
                                    public string AnotherProperty
                                    {
                                        get
                                        {
                                            return someProperty ? "Car" : "Plane";
                                        }
                                    }
                                
                                    bool someProperty = false;
                                    public bool SomeProperty
                                    {
                                        get
                                        {
                                            return someProperty;
                                        }
                                        set
                                        {
                                            SetValue(ref someProperty, value, "SomeProperty", "AnotherProperty");
                                        }
                                    }
                                
                                    public SomeObject() : base()
                                    {
                                    }
                                }
                                

                                优点

                                • 无反射
                                • 仅在旧值 != 新值时通知
                                • 一次通知多个属性

                                缺点

                                • 没有自动属性(不过,您可以添加对两者的支持!)
                                • 有些冗长
                                • 拳击(对性能的影响很小?)

                                唉,还是比这样做好,

                                set
                                {
                                    if (!someProperty.Equals(value))
                                    {
                                        someProperty = value;
                                        OnPropertyChanged("SomeProperty");
                                        OnPropertyChanged("AnotherProperty");
                                    }
                                }
                                

                                对于每一个单独的属性,它都会变得更加冗长;-(

                                请注意,我并不是说这个解决方案在性能方面比其他解决方案更好,只是对于那些不喜欢其他解决方案的人来说,它是一个可行的解决方案。

                                【讨论】:

                                  猜你喜欢
                                  • 2016-07-09
                                  • 2015-10-03
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2023-03-30
                                  • 2010-10-29
                                  相关资源
                                  最近更新 更多