自动INotifyPropertyChanged

Tim*_*ell 57 c# inotifypropertychanged

有没有办法自动获得类中属性更改的通知,而无需在每个setter中编写OnPropertyChanged?(如果有更改,我有数百个我想知道的属性).


安东建议使用动态代理.我实际上使用过"Castle"库来获得类似的东西,虽然它确实减少了我必须编写的代码量,但它增加了大约30秒到我的程序启动时间(ymmv) - 因为它是一个运行时方案.

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


Slashene和TcKs给出了产生重复代码的建议 - 遗憾的是,并非所有属性都是m_Value = value的简单情况 - 很多都在setter中有自定义代码,所以来自代码片段和xml的cookie-cutter代码实际上不可行我的项目也是.

Isa*_*avo 43

编辑: NotifyPropertyWeaver的作者已弃用该工具,转而支持更一般的Fody.(有关人员从织女到动物的迁移指南可供使用.)


我用于项目的一个非常方便的工具是Notify Property Weaver Fody.

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

使属性提升PropertyChanged是通过在它们上面添加特殊属性来完成的:

[ImplementPropertyChanged]
public string MyProperty { get; set; }
Run Code Online (Sandbox Code Playgroud)

作为奖励,您还可以指定依赖于其他属性的属性的关系

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

[DependsOn("Radius")]
public double Area 
{
    get { return Radius * Radius * Math.PI; }
}
Run Code Online (Sandbox Code Playgroud)

  • Fody/PropertyChanged会自动获取属性依赖关系,因此在示例中,Area属性不需要[DependsOn("Radius")]属性:该工具会自动检测此依赖关系. (2认同)
  • @IsakSavo 顺便说一句。`Fody` 是 IL Wiever 框架,`Fody.PropertyChanged` 是用于实现 `INotifyPropertyChanged` 的插件 (2认同)

Svi*_*ish 39

nameof运算符是在2015年7月使用.NET 4.6和VS2015在C#6.0中实现的.以下内容对C#<6.0仍然有效

我们使用下面的代码(来自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);
                }
            };
    }
}
Run Code Online (Sandbox Code Playgroud)

例如这样使用:

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);
Run Code Online (Sandbox Code Playgroud)

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

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

  • 我考虑a)将您的Expression - >属性名称代码重构为另一个方法,并且b)解析属性_before_您订阅了PropertyChanged事件(否则您每次都运行该代码) (2认同)

tak*_*krl 31

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 ...
}
Run Code Online (Sandbox Code Playgroud)

类似于Svish的解决方案,只需用无聊的框架功能取代lambda awesomeness ;-)

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


Kon*_*rin 11

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

public string Name
{
    get { return name; }
    set
    {
        name = value;
        PropertyChanged.Raise(() => Name);
    }
}
Run Code Online (Sandbox Code Playgroud)

订阅特定房产变更:

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;
Run Code Online (Sandbox Code Playgroud)

扩展方法只能通过检查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;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Nic*_*ier 10

实现类型安全INotifyPropertyChanged:请参见此处

然后制作自己的代码段:

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

使用Code代码段设计器,您已经完成了!简单,安全的方式来建立您的INotifyPropertyChanged.


TcK*_*cKs 4

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

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>
Run Code Online (Sandbox Code Playgroud)

可以是此代码的来源:

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" );
        }
    }
}
Run Code Online (Sandbox Code Playgroud)