【问题标题】:Simple small INotifyPropertyChanged implementation简单的小 INotifyPropertyChanged 实现
【发布时间】:2010-10-03 18:59:05
【问题描述】:

假设我有以下课程:

public MainFormViewModel
{
    public String StatusText {get; set;}
}

让我对 StatusText 的更改以反映到绑定到它的任何控件的最简单的最小方法是什么?

显然我需要使用 INotifyPropertyChanged,但有没有一种很酷的方法不会弄乱我的代码?需要很多文件?等等?

注意:如果这是一个骗局,那么我很抱歉。我搜索并找不到任何东西,但使用 T4 code Generation 这听起来并不容易(至少要设置)。

【问题讨论】:

    标签: wpf inotifypropertychanged


    【解决方案1】:

    不幸的是,C# 没有提供一种简单的机制来自动执行此操作……suggested 一直在创建这样的新语法:

    public observable int Foo { get; set; }
    

    但我怀疑它是否会被包含在语言中......

    一个可能的解决方案是使用像 Postsharp 这样的 AOP 框架,这样你只需要用一个属性来装饰你的属性:

    public MainFormViewModel : INotifyPropertyChanged
    {
        [NotifyPropertyChanged]
        public String StatusText {get; set;}
    }
    

    (没试过,但我很确定 Postsharp 允许你做那种事情......)


    更新:好的,我设法让它工作。请注意,这是一个非常粗略的实现,使用私有字段上的反射来检索委托......当然可以改进,但我会留给你;)

    [Serializable]
    public class NotifyPropertyChangedAttribute : LocationInterceptionAspect
    {
        public override void OnSetValue(LocationInterceptionArgs args)
        {
            object oldValue = args.GetCurrentValue();
            object newValue = args.Value;
            base.OnSetValue(args);
            if (args.Instance is INotifyPropertyChanged)
            {
                if (!Equals(oldValue, newValue))
                {
                    RaisePropertyChanged(args.Instance, args.LocationName);
                }
            }
        }
    
        private void RaisePropertyChanged(object instance, string propertyName)
        {
            PropertyChangedEventHandler handler = GetPropertyChangedHandler(instance);
            if (handler != null)
                handler(instance, new PropertyChangedEventArgs(propertyName));
        }
    
        private PropertyChangedEventHandler GetPropertyChangedHandler(object instance)
        {
            Type type = instance.GetType().GetEvent("PropertyChanged").DeclaringType;
            FieldInfo propertyChanged = type.GetField("PropertyChanged",
                                                      BindingFlags.Instance | BindingFlags.NonPublic);
            if (propertyChanged != null)
                return propertyChanged.GetValue(instance) as PropertyChangedEventHandler;
    
            return null;
        }
    }
    

    请注意,您的类仍然需要实现INotifyPropertyChanged 接口。您只是不必在属性设置器中显式引发事件。

    【讨论】:

    • 你确定这是 Postsharp 的链接吗?他们似乎没有为 C# 提供任何工具。
    • 糟糕,抱歉,链接错误...我已修复
    • 这看起来很酷,但它必须是商业版(或者比看起来更难)。我无法让它与社区版一起使用。
    • 我用一个工作示例更新了我的答案。它适用于.NET 4.0,不确定其他版本...
    • 那个 GetField 方法似乎总是返回 null
    【解决方案2】:

    试试这个http://code.google.com/p/notifypropertyweaver/

    你需要做的就是实现INotifyPropertyChanged

    所以你的代码看起来像

    public MainFormViewModel : INotifyPropertyChanged
    {
        public String StatusText {get; set;}
    
        #region INotifyPropertyChanged Implementation
    }
    

    构建任务将编译这个(你永远看不到下面的代码)

    public MainFormViewModel : INotifyPropertyChanged
    {
        public String StatusText {get; set;}
        private string statusText;
    
        public string StatusText 
        {
           get { return statusText; }
           set
           {
               if (value!= statusText)
               {
                   statusText = value;
                   OnPropertyChanged("StatusText");
               }
           }
        }
    
        #region INotifyPropertyChanged Implementation
    }
    

    【讨论】:

      【解决方案3】:

      通过利用EqualityComparer.Default,您可以将属性设置器代码减少到一行,如下所示:

      private int unitsInStock;
      public int UnitsInStock 
      {
        get { return unitsInStock; }
        set { SetProperty(ref unitsInStock, value, "UnitsInStock"); }
      }
      
      public event PropertyChangedEventHandler PropertyChanged;
      
      protected void SetProperty<T>(ref T field, T value, string name) 
      {
        if (!EqualityComparer<T>.Default.Equals(field, value)) 
        {
          field = value;
          var handler = PropertyChanged;
          if (handler != null) 
          {
            handler(this, new PropertyChangedEventArgs(name));
          }
        }
      }
      

      如果您的视图模型从定义 SetProperty 方法和 PropertyChanged 事件的基类继承,那么在您的子视图模型中支持 INotifyPropertyChanged 所需的代码量将变得非常少(1 行)。

      这种方法比其他答案中提到的代码编织方法更冗长,但不需要您修改构建过程来完成它。

      一定要看看即将发布的C# 5 Caller Info attributes,看起来它们可以让我们避免在方法中使用魔法字符串,而不会产生反射的性能成本。

      更新(2012 年 3 月 1 日):

      .NET 4.5 Beta 已经发布,有了它,您可以进一步细化上述代码,从而消除调用者中对字符串文字的需求:

      private int unitsInStock;
      public int UnitsInStock
      {
          get { return unitsInStock; }
          set 
          { 
              SetProperty(ref unitsInStock, value);
          }
      }
      
      public event PropertyChangedEventHandler PropertyChanged;
      
      private void SetProperty<T>(ref T field, T value, [CallerMemberName] string name = "")
      {
          if (!EqualityComparer<T>.Default.Equals(field, value))
          {
              field = value;
              var handler = PropertyChanged;
              if (handler != null)
              {
                handler(this, new PropertyChangedEventArgs(name));
              }
          }
      }
      

      我有一个blog post 会更详细地讨论它。

      【讨论】:

        【解决方案4】:

        我一直很喜欢这种方法

        private string m_myString;
        public string MyString
        {
            get { return m_myString; }
            set 
            {
                if (m_myString != value)
                {
                     m_myString = value;
                     NotifyPropertyChanged("MyString");
                }
            }
        }
        
        
        private void NotifyPropertyChanged(string property)
        {
            if (PropertyChanged != null)
                 PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
        

        或者为了减少代码膨胀

        set 
        {
            m_myString = value;
            NotifyPropertyChanged("MyString");
        }
        

        【讨论】:

        • 价值。在多线程应用程序中,您应该在调用 PropertyChanged 之前获取它的本地副本。原因是代码“if (PropertyChanged != null)”可能会在您调用 PropertyChanged 时通过 bu,它可能为 null。我知道极端情况,但很难调试。
        • 我没有考虑过这种情况。感谢您的提醒。
        • Simon 有一个很好的观点,但一般来说,属性更改通知只允许在 UI 线程上。如果修改PropertyChanged 事件的线程不是引发事件的同一线程,则您的代码中可能存在错误。
        【解决方案5】:

        我有一个名为“模型”的基类。它公开了一个名为 DataPoints 的受保护对象,该对象本质上是一个字典。

        C#

        public String StatusText {
            get { 
                return (string)DataPoints["StatusText"]; 
            } 
            set { 
                DataPoints["StatusText"] = value; 
            }
        }
        

        VB

        public Property StatusText as String 
            get 
                return DataPoints!StatusText 
            end get 
            set
                DataPoints!StatusText = value
            end set
        end property
        

        当您在 DataPoints 字典中设置一个值时,它会执行以下操作:

        1. 检查以确保值实际更改。
        2. 保存新值
        3. 将 IsDirty 属性设置为 true。
        4. 引发命名属性以及 IsDirty 和 IsValid 属性的 Property Changed 事件。

        由于它是一个字典,它也使得从数据库或 XML 文件中加载对象变得非常容易。

        现在您可能认为读写字典很昂贵,但我一直在进行大量性能测试,在我的 WPF 应用程序中没有发现任何明显的影响。

        【讨论】:

          【解决方案6】:

          PropertyChanged.Fody NuGet 包执行此操作。

          https://github.com/Fody/PropertyChanged

          • PropertyChanged.Fody 包添加到您的项目中。
          • 在您的模型中引用 PropertyChangedusing PropertyChanged;
          • [ImplementPropertyChanged] 属性添加到您的类。

          类中的所有属性现在都将神奇地实现INotifyPropertyChanged。注意 - Fody 通过修改发出的 IL 来工作,因此您将永远不会真正看到 VS 中的代码 - 它只是神奇地做到了。

          其他文档: https://github.com/Fody/PropertyChanged/wiki/Attributes

          【讨论】:

            猜你喜欢
            • 2013-09-16
            • 1970-01-01
            • 2010-10-04
            • 2013-07-03
            • 2014-07-30
            • 2014-06-02
            • 1970-01-01
            • 2014-09-30
            • 2011-04-08
            相关资源
            最近更新 更多