C# 自动 INotifyPropertyChanged

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/527602/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 06:50:43  来源:igfitidea点击:

Automatically INotifyPropertyChanged

c#inotifypropertychanged

提问by Tim Gradwell

Is there any way to automatically get notified of property changes in a class without having to write OnPropertyChanged in every setter? (I have hundreds of properties that I want to know if they have changed).

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



Anton suggests dynamic proxies. I've actually used the "Castle" library for something similar in the past, and while it does reduce the amount of code I've had to write, it added around 30 seconds to my program startup time (ymmv) - because it's a runtime solution.

Anton 建议使用动态代理。实际上,我过去曾将“Castle”库用于类似的事情,虽然它确实减少了我必须编写的代码量,但它使我的程序启动时间 (ymmv) 增加了大约 30 秒 - 因为它是一个运行时解决方案。

I'm wondering if there is a compile time solution, maybe using compile-time attributes...

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



Slashene and TcKs give suggestions which generates repetitive code - unfortunately, not all my properties are a simple case of m_Value = value - lots of them have custom code in the setters, so cookie-cutter code from snippets and xml aren't really feasible for my project either.

Slashene 和 TcKs 提供了生成重复代码的建议 - 不幸的是,并非我的所有属性都是 m_Value = value 的简单情况 - 它们中的很多在 setter 中都有自定义代码,因此来自片段和 xml 的 cookie-cutter 代码对于我的项目。

回答by TcKs

I don't know no standard way, but I know two workarounds:

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

1) PostSharpcan do it for you after the compilation. It is very usefull, but it take some time on every build.

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

2) Custom tool i Visual Studio. You can combine it with "partial class". Then you can create custom tool for your XML and you can generate source code from the xml.

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

For example this xml:

例如这个xml:

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

can be source for this code:

可以是此代码的来源:

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

回答by Nicolas Dorier

Implement a type safe INotifyPropertyChanged: See here

实现类型安全INotifyPropertyChanged见这里

Then make your own code snippet :

然后制作自己的代码片段:

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

With Code snippet designerand you have done ! Easy, secure way to build your INotifyPropertyChanged.

使用代码片段设计器,您就大功告成了!轻松、安全地构建您的INotifyPropertyChanged.

回答by Neil Barnwell

You could look at Castle or Spring.NET and implementing interceptor functionality?

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

回答by Svish

The nameof operator was implemented in C# 6.0 with .NET 4.6 and VS2015 in July 2015. The following is still valid for C# < 6.0

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

We use the code below (From http://www.ingebrigtsen.info/post/2008/12/11/INotifyPropertyChanged-revisited.aspx). Works great :)

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

Used for example this way:

例如以这种方式使用:

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

Some syntax errors in the example may exist. Didn't test it. But you should have the concept there at least :)

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

EDIT:I see now that you may have wanted even less work, but yeah... the stuff above at least makes it a lot easier. And you prevent all the scary problems with refering to properties using strings.

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

回答by almog.ori

you might want to look into Aspect-Oriented Programming as a whole

你可能想从整体上研究面向方面的编程

Frameworks => you could look at linfu

框架 => 你可以看看linfu

回答by champier

Amelioration to call event in children classes:

改善儿童班的呼叫事件:

Called thanks to: this.NotifyPropertyChange(() => PageIndex);

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

Add this in the NotificationExtensions class:

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

回答by Ian Ringrose

I have just found ActiveSharp - Automatic INotifyPropertyChanged, I have yet to use it, but it looks good.

我刚刚找到ActiveSharp - Automatic INotifyPropertyChanged,我还没有使用它,但它看起来不错。

To quote from it's web site...

引用它的网站...



Send property change notifications without specifying property name as a string.

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

Instead, write properties like this:

相反,编写如下属性:

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

Note that there is no need to include the name of the property as a string. ActiveSharp reliably and correctly figures that out for itself. It works based on the fact that your property implementation passes the backing field (_foo) by ref. (ActiveSharp uses that "by ref" call to identify which backing field was passed, and from the field it identifies the property).

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

回答by Isak Savo

EDIT:The author of NotifyPropertyWeaver has deprecated the tool in favor of the more general Fody. (A migration guidefor people moving from weaver to fody is available.)

编辑:NotifyPropertyWeaver 的作者已弃用该工具,转而使用更通用的Fody。(提供了从织布工到福迪的迁移指南。)



A very convenient tool I've used for my projects is Notify Property WeaverFody.

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

It installs itself as a build step in your projects and during compilation injects code that raises the PropertyChangedevent.

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

Making properties raise PropertyChanged is done by putting special attributeson them:

使属性引发 PropertyChanged 是通过在它们上放置特殊属性来完成的:

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

As a bonus, you can also specify relationships for properties that depend on other properties

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

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

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

回答by Konstantin Spirin

You can have extension method on your PropertyChanged delegate and use it like this:

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

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

Subscription to a specific property change:

订阅特定属性更改:

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;

extension method is able to determine sender and property name just by inspecting lambda expression tree and without major performance impact:

扩展方法能够通过检查 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;
    }
}

If PropertyChangedevent is declared in a base type then it won't be visible as a delegate field in the derived classes. In this case a workaround is to declare a protected field of type PropertyChangedEventHandlerand explicitly implement event's addand removeaccessors:

如果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);
        }
    }
}

回答by takrl

The Framework 4.5 provides us with the CallerMemberNameAttribute, which makes passing the property name as a string unnecessary:

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

Similar to Svish's solution, just replacing lambda awesomeness with boring framework functionality ;-)

类似于 Svish 的解决方案,只是用乏味的框架功能替换了 lambda 的魅力;-)

If you're working on Framework 4.0 with KB2468871installed, you can install the Microsoft BCL Compatibility Packvia nuget, which also provides this attribute.

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