【问题标题】:Automatically INotifyPropertyChanged自动 INotifyPropertyChanged
【发布时间】:2010-10-06 08:52:58
【问题描述】:

有没有什么方法可以自动获得类中属性更改的通知,而不必在每个 setter 中编写 OnPropertyChanged? (我想知道数百个属性是否已更改)。


安东建议dynamic proxies。实际上,我过去曾将“城堡”库用于类似的东西,虽然它确实减少了我必须编写的代码量,但它增加了我的程序启动时间大约 30 秒(ymmv)——因为它是一个运行时解决方案。

我想知道是否有编译时解决方案,也许使用编译时属性...


Slashene 和 TcKs 给出了生成重复代码的建议 - 不幸的是,并非我的所有属性都是 m_Value = value 的简单案例 - 它们中的许多在设置器中都有自定义代码,因此来自 sn-ps 和 xml 的千篇一律的代码对我的项目来说也不可行。

【问题讨论】:

标签: c# inotifypropertychanged


【解决方案1】:

编辑: NotifyPropertyWeaver 的作者已弃用该工具,转而使用更通用的Fody。 (可以使用migration guide 供人们从织布工转向福迪。)


我在项目中使用的一个非常方便的工具是 Notify Property Weaver Fody

它将自身安装为项目中的构建步骤,并在编译期间注入引发PropertyChanged 事件的代码。

通过将special attributes 放在属性上来使属性引发 PropertyChanged:

[ImplementPropertyChanged]
public string MyProperty { get; set; }

另外,您还可以为依赖于其他属性的属性指定关系

[ImplementPropertyChanged]
public double Radius { get; set; }

[DependsOn("Radius")]
public double Area 
{
    get { return Radius * Radius * Math.PI; }
}

【讨论】:

  • 我喜欢这个主意,但你的链接已经失效了。
  • Fody / PropertyChanged 自动获取属性依赖项,因此在示例中 Area 属性不需要 [DependsOn("Radius")] 属性:该工具会自动检测此依赖项。
  • @IsakSavo 顺便说一句。 Fody是IL Wiever框架,Fody.PropertyChanged是用来实现INotifyPropertyChanged的Plugin
  • 当心在新的 Fody API 中,您不必使用 [ImplementPropertyChanged] 装饰类/属性。它将自动为所有继承它的类实现INotifyPropertyChanged
【解决方案2】:

nameof 运算符于 2015 年 7 月在 C# 6.0 与 .NET 4.6 和 VS2015 中实现。以下对 C# 仍然有效

我们使用下面的代码(来自http://www.ingebrigtsen.info/post/2008/12/11/INotifyPropertyChanged-revisited.aspx)。效果很好:)

public static class NotificationExtensions
{
    #region Delegates

    /// <summary>
    /// A property changed handler without the property name.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="sender">The object that raised the event.</param>
    public delegate void PropertyChangedHandler<TSender>(TSender sender);

    #endregion

    /// <summary>
    /// Notifies listeners about a change.
    /// </summary>
    /// <param name="EventHandler">The event to raise.</param>
    /// <param name="Property">The property that changed.</param>
    public static void Notify(this PropertyChangedEventHandler EventHandler, Expression<Func<object>> Property)
    {
        // Check for null
        if (EventHandler == null)
            return;

        // Get property name
        var lambda = Property as LambdaExpression;
        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = lambda.Body as UnaryExpression;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambda.Body as MemberExpression;
        }

        ConstantExpression constantExpression;
        if (memberExpression.Expression is UnaryExpression)
        {
            var unaryExpression = memberExpression.Expression as UnaryExpression;
            constantExpression = unaryExpression.Operand as ConstantExpression;
        }
        else
        {
            constantExpression = memberExpression.Expression as ConstantExpression;
        }

        var propertyInfo = memberExpression.Member as PropertyInfo;

        // Invoke event
        foreach (Delegate del in EventHandler.GetInvocationList())
        {
            del.DynamicInvoke(new[]
            {
                constantExpression.Value, new PropertyChangedEventArgs(propertyInfo.Name)
            });
        }
    }


    /// <summary>
    /// Subscribe to changes in an object implementing INotifiyPropertyChanged.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="ObjectThatNotifies">The object you are interested in.</param>
    /// <param name="Property">The property you are interested in.</param>
    /// <param name="Handler">The delegate that will handle the event.</param>
    public static void SubscribeToChange<T>(this T ObjectThatNotifies, Expression<Func<object>> Property, PropertyChangedHandler<T> Handler) where T : INotifyPropertyChanged
    {
        // Add a new PropertyChangedEventHandler
        ObjectThatNotifies.PropertyChanged += (s, e) =>
            {
                // Get name of Property
                var lambda = Property as LambdaExpression;
                MemberExpression memberExpression;
                if (lambda.Body is UnaryExpression)
                {
                    var unaryExpression = lambda.Body as UnaryExpression;
                    memberExpression = unaryExpression.Operand as MemberExpression;
                }
                else
                {
                    memberExpression = lambda.Body as MemberExpression;
                }
                var propertyInfo = memberExpression.Member as PropertyInfo;

                // Notify handler if PropertyName is the one we were interested in
                if (e.PropertyName.Equals(propertyInfo.Name))
                {
                    Handler(ObjectThatNotifies);
                }
            };
    }
}

例如这样使用:

public class Employee : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private string _firstName;
    public string FirstName
    {
        get { return this._firstName; }
        set
        {
            this._firstName = value;
            this.PropertyChanged.Notify(()=>this.FirstName);
        }
    }
}

private void firstName_PropertyChanged(Employee sender)
{
    Console.WriteLine(sender.FirstName);
}

employee = new Employee();
employee.SubscribeToChange(() => employee.FirstName, firstName_PropertyChanged);

示例中可能存在一些语法错误。没有测试它。但是你至少应该有这个概念:)

编辑:我现在看到你可能想要更少的工作,但是是的......上面的东西至少让它变得容易多了。并且您可以避免使用字符串引用属性的所有可怕问题。

【讨论】:

  • 我会考虑 a) 将您的 Expression -> 属性名称代码重构为另一种方法,并 b) 在您订阅 PropertyChanged 事件之前解析属性 (否则您将每次运行该代码)
  • @Richard 好主意。随意编辑,只要确保它首先工作,所以我们不会在这里得到错误的代码:)
  • 你是不是刚刚添加了一个类来替换内置的nameof?很久以前,我很好奇,那个时间nameof 在.NET 中不可用吗?现在您绝对应该使用nameof 而不是Reflection,因为它是编译时的,因此速度更快。现在你可能会同意 - Fody 非常适合处理大量 DTO,但是当你没有太多的 DTO 时 - 只需手动执行 - 它在编码时间和性能方面都更快。
【解决方案3】:

Framework 4.5 为我们提供了CallerMemberNameAttribute,这使得将属性名称作为字符串传递是不必要的:

private string m_myProperty;
public string MyProperty
{
    get { return m_myProperty; }
    set
    {
        m_myProperty = value;
        OnPropertyChanged();
    }
}

private void OnPropertyChanged([CallerMemberName] string propertyName = "none passed")
{
    // ... do stuff here ...
}

类似于 Svish 的解决方案,只是用无聊的框架功能代替了 lambda 的强大功能;-)

如果您正在使用安装了KB2468871 的Framework 4.0,您可以通过nuget 安装Microsoft BCL 兼容包,它也提供此属性。

【讨论】:

  • 不适用于依赖项,所以仍然需要一个字符串选项
【解决方案4】:

您可以在 PropertyChanged 委托上使用扩展方法并像这样使用它:

public string Name
{
    get { return name; }
    set
    {
        name = value;
        PropertyChanged.Raise(() => Name);
    }
}

订阅特定属性更改:

var obj = new Employee();

var handler = obj.SubscribeToPropertyChanged(
    o => o.FirstName, 
    o => Console.WriteLine("FirstName is now '{0}'", o.FirstName));

obj.FirstName = "abc";

// Unsubscribe when required
obj.PropertyChanged -= handler;

扩展方法能够通过检查 lambda 表达式树来确定发送者和属性名称,并且不会对性能产生重大影响

public static class PropertyChangedExtensions
{
    public static void Raise<TProperty>(
        this PropertyChangedEventHandler handler, Expression<Func<TProperty>> property)
    {
        if (handler == null)
            return;

        var memberExpr = (MemberExpression)property.Body;
        var propertyName = memberExpr.Member.Name;
        var sender = ((ConstantExpression)memberExpr.Expression).Value;
        handler.Invoke(sender, new PropertyChangedEventArgs(propertyName));
    }

    public static PropertyChangedEventHandler SubscribeToPropertyChanged<T, TProperty>(
        this T obj, Expression<Func<T, TProperty>> property, Action<T> handler)
        where T : INotifyPropertyChanged
    {
        if (handler == null)
            return null;

        var memberExpr = (MemberExpression)property.Body;
        var propertyName = memberExpr.Member.Name;

        PropertyChangedEventHandler subscription = (sender, eventArgs) =>
        {
            if (propertyName == eventArgs.PropertyName)
                handler(obj);
        };

        obj.PropertyChanged += subscription;

        return subscription;
    }
}

如果PropertyChanged 事件在基类型中声明,则它不会作为派生类中的委托字段可见。在这种情况下,一种解决方法是声明 PropertyChangedEventHandler 类型的受保护字段并显式实现事件的 addremove 访问器:

public class Base : INotifyPropertyChanged
{
    protected PropertyChangedEventHandler propertyChanged;
    public event PropertyChangedEventHandler PropertyChanged
    {
        add { propertyChanged += value; }
        remove { propertyChanged -= value; }
    }
}

public class Derived : Base
{
    string name;

    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            propertyChanged.Raise(() => Name);
        }
    }
}

【讨论】:

  • 这很棒。不幸的是,您不能将 PropertyChanged 粘贴在基类中,然后通过子类中的 PropertyChanged.Raise() 访问它。否则,这是一个很好的解决方案。
  • @znelson,我添加了派生类的解决方法。
  • 效果很好 - 谢谢!这真的应该被标记为答案。
【解决方案5】:

实现类型安全 INotifyPropertyChanged : See here

然后制作自己的代码 sn-p :

private $Type$ _$PropertyName$;
public $Type$ $PropertyName$
{
    get
    {
        return _$PropertyName$;
    }
    set
    {
        if(value != _$PropertyName$)
        {
            _$PropertyName$ = value;
            OnPropertyChanged(o => o.$PropertyName$);               
        }
    }
}

有了Code snippet designer,你就完成了!轻松、安全地构建您的INotifyPropertyChanged

【讨论】:

    【解决方案6】:

    我不知道没有标准方法,但我知道两种解决方法:

    1) PostSharp 可以编译后为你做。它非常有用,但每次构建都需要一些时间。

    2) Visual Studio 中的自定义工具。您可以将其与“部分课程”结合使用。然后您可以为您的 XML 创建自定义工具,您可以从 xml 生成源代码。

    例如这个xml:

    <type scope="public" type="class" name="MyClass">
        <property scope="public" type="string" modifier="virtual" name="Text" notify="true" />
    </type>
    

    可以是此代码的来源:

    public partial class MyClass {
        private string _text;
        public virtual string Text {
            get { return this._Text; }
            set {
                this.OnPropertyChanging( "Text" );
                this._Text = value;
                this.OnPropertyChanged( "Text" );
            }
        }
    }
    

    【讨论】:

      【解决方案7】:

      您可能希望将面向方面的编程作为一个整体来研究

      Frameworks => 你可以看看linfu

      【讨论】:

      • 希望我能做的不仅仅是 +1,我强烈建议在处理 WPF 基础架构时使用一些面向方面的框架
      【解决方案8】:

      您可以看看 Castle 或 Spring.NET 并实现拦截器功能吗?

      【讨论】:

        【解决方案9】:

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

        引用它的网站...


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

        改为这样写属性:

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

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

        【讨论】:

          【解决方案10】:

          改进在子类中调用事件:

          感谢: this.NotifyPropertyChange(() => PageIndex);

          在 NotificationExtensions 类中添加这个:

              /// <summary>
              /// <para>Lève l'évènement de changement de valeur sur l'objet <paramref name="sender"/>
              /// pour la propriété utilisée dans la lambda <paramref name="property"/>.</para>
              /// </summary>
              /// <param name="sender">L'objet portant la propriété et l'évènement.</param>
              /// <param name="property">Une expression lambda utilisant la propriété subissant la modification.</param>
              public static void NotifyPropertyChange(this INotifyPropertyChanged sender, Expression<Func<Object>> property)
              {
                  if (sender == null)
                      return;
          
                  // Récupère le nom de la propriété utilisée dans la lambda en argument
                  LambdaExpression lambda = property as LambdaExpression;
                  MemberExpression memberExpression;
                  if (lambda.Body is UnaryExpression)
                  {
                      UnaryExpression unaryExpression = lambda.Body as UnaryExpression;
                      memberExpression = unaryExpression.Operand as MemberExpression;
                  }
                  else
                  {
                      memberExpression = lambda.Body as MemberExpression;
                  }
                  ConstantExpression constantExpression = memberExpression.Expression as ConstantExpression;
                  PropertyInfo propertyInfo = memberExpression.Member as PropertyInfo;
          
          
                  // il faut remonter la hierarchie, car meme public, un event n est pas visible dans les enfants
                  FieldInfo eventField;
                  Type baseType = sender.GetType();
                  do
                  {
                      eventField = baseType.GetField(INotifyPropertyChangedEventFieldName, BindingFlags.Instance | BindingFlags.NonPublic);
                      baseType = baseType.BaseType;
                  } while (eventField == null);
          
                  // on a trouvé l'event, on peut invoquer tt les delegates liés
                  MulticastDelegate eventDelegate = eventField.GetValue(sender) as MulticastDelegate;
                  if (eventDelegate == null) return; // l'event n'est bindé à aucun delegate
                  foreach (Delegate handler in eventDelegate.GetInvocationList())
                  {
                      handler.Method.Invoke(handler.Target, new Object[] { sender, new PropertyChangedEventArgs(propertyInfo.Name) });
                  }
              }
          

          【讨论】:

            【解决方案11】:

            为了让实现更快,你可以使用 sn-p

            来自http://aaron-hoffman.blogspot.it/2010/09/visual-studio-code-snippet-for-notify.html

            遵循 M-V-VM 模式的项目的 ViewModel 类通常需要从属性的设置器中引发“PropertyChanged”事件(以协助 INotifyPropertyChanged 接口实现)。这是一项乏味的任务,希望有朝一日可以通过使用编译器即服务来解决......

            片段核心(全部功劳归作者所有,但作者不是我)如下

              <Code Language= "csharp "> 
                <![CDATA[public $type$ $property$ 
            { 
                get { return _$property$; } 
                set 
                { 
                    if (_$property$ != value) 
                    { 
                        _$property$ = value; 
                        OnPropertyChanged($property$PropertyName); 
                    } 
                } 
            } 
            private $type$ _$property$; 
            public const string $property$PropertyName = "$property$";$end$]]> 
            </Code> 
            

            【讨论】:

              【解决方案12】:

              没有一个单一的 Property Changed 实现可以处理人们想要使用它的所有方式。最好的办法是生成一个助手类来为您完成工作 这是我使用的一个示例

              /// <summary>
              /// Helper Class that automates most of the actions required to implement INotifyPropertyChanged
              /// </summary>
              public static class HPropertyChanged
              {
                  private static Dictionary<string, PropertyChangedEventArgs> argslookup = new Dictionary<string, PropertyChangedEventArgs>();
                  public static string ThisPropertyName([CallerMemberName]string name = "")
                  {
                      return name;
                  }
              
                  public static string GetPropertyName<T>(Expression<Func<T>> exp)
                  {
                      string rtn = "";
                      MemberExpression mex = exp.Body as MemberExpression;
                      if(mex!=null)
                          rtn = mex.Member.Name;
                      return rtn;
                  }
              
                  public static void SetValue<T>(ref T target, T newVal, object sender, PropertyChangedEventHandler handler, params string[] changed)
                  {
                      if (!target.Equals(newVal))
                      {
                          target = newVal;
                          PropertyChanged(sender, handler, changed);
                      }
                  }
                  public static void SetValue<T>(ref T target, T newVal, Action<PropertyChangedEventArgs> handler, params string[] changed)
                  {
                      if (!target.Equals(newVal))
                      {
                          target = newVal;
                          foreach (var item in changed)
                          {
                              handler(GetArg(item));
                          }
                      }
                  }
              
                  public static void PropertyChanged(object sender,PropertyChangedEventHandler handler,params string[] changed)
                  {
                      if (handler!=null)
                      {
                          foreach (var prop in changed)
                          {
                              handler(sender, GetArg(prop));
                          }
                      }
                  }
                  public static PropertyChangedEventArgs GetArg(string name)
                  {
                      if (!argslookup.ContainsKey(name)) argslookup.Add(name, new PropertyChangedEventArgs(name));
                      return argslookup[name];
                  }
              }
              

              编辑: 有人建议我从帮助类转换为值包装器,从那以后我一直在使用这个,我发现它工作得很好

              public class NotifyValue<T>
              {
                  public static implicit operator T(NotifyValue<T> item)
                  {
                      return item.Value;
                  }
              
                  public NotifyValue(object parent, T value = default(T), PropertyChangingEventHandler changing = null, PropertyChangedEventHandler changed = null, params object[] dependenies)
                  {
                      _parent = parent;
                      _propertyChanged = changed;
                      _propertyChanging = changing;
              
                      if (_propertyChanged != null)
                      {
                          _propertyChangedArg =
                              dependenies.OfType<PropertyChangedEventArgs>()
                              .Union(
                                  from d in dependenies.OfType<string>()
                                  select new PropertyChangedEventArgs(d)
                              );
              
                      }
                      if (_propertyChanging != null)
                      {
                          _propertyChangingArg =
                              dependenies.OfType<PropertyChangingEventArgs>()
                              .Union(
                                  from d in dependenies.OfType<string>()
                                  select new PropertyChangingEventArgs(d)
                              );
                      }
                      _PostChangeActions = dependenies.OfType<Action>();
              
                  }
              
                  private T _Value;
              
                  public T Value
                  {
                      get { return _Value; }
                      set
                      {
                          SetValue(value);
                      }
                  }
              
                  public bool SetValue(T value)
                  {
                      if (!EqualityComparer<T>.Default.Equals(_Value, value))
                      {
                          OnPropertyChnaging();
                          _Value = value;
                          OnPropertyChnaged();
                          foreach (var action in _PostChangeActions)
                          {
                              action();
                          }
                          return true;
                      }
                      else
                          return false;
                  }
              
                  private void OnPropertyChnaged()
                  {
                      var handler = _propertyChanged;
                      if (handler != null)
                      {
                          foreach (var arg in _propertyChangedArg)
                          {
                              handler(_parent, arg);
                          }           
                      }
                  }
              
                  private void OnPropertyChnaging()
                  {
                      var handler = _propertyChanging;
                      if(handler!=null)
                      {
                          foreach (var arg in _propertyChangingArg)
                          {
                              handler(_parent, arg);
                          }
                      }
                  }
              
                  private object _parent;
                  private PropertyChangedEventHandler _propertyChanged;
                  private PropertyChangingEventHandler _propertyChanging;
                  private IEnumerable<PropertyChangedEventArgs> _propertyChangedArg;
                  private IEnumerable<PropertyChangingEventArgs> _propertyChangingArg;
                  private IEnumerable<Action> _PostChangeActions;
              }
              

              使用示例

              private NotifyValue<int> _val;
              public const string ValueProperty = "Value";
              public int Value
              {
                  get { return _val.Value; }
                  set { _val.Value = value; }
              }
              

              然后在构造函数中你做

              _val = new NotifyValue<int>(this,0,PropertyChanged,PropertyChanging,ValueProperty );
              

              【讨论】:

              • C#6.0 引入了 nameof 关键字,其作用类似于 typeof 关键字来自动提取反射数据,让您无需常量
              • 我喜欢。我做过类似的事情,但更轻量级(正如你提到的,每个人的要求都不同)。我称我的为 EncapsulatedProperty。它只有一个 Value 属性、隐式转换和一个 ValueChanged 事件。如果我在 7 年前问我的问题时就这样做了,我会对这个解决方案感到满意 :-)
              【解决方案13】:

              只需在您的自动属性声明上方使用此属性

              [NotifyParentProperty(true)]
              public object YourProperty { get; set; }
              

              【讨论】:

              • PropertyChanged 永远不会被此机制引发。一定有别的东西。
              猜你喜欢
              • 2011-03-21
              • 2014-01-12
              • 2016-08-09
              • 1970-01-01
              • 2011-12-19
              • 2015-02-21
              • 2011-08-04
              • 2011-12-20
              相关资源
              最近更新 更多