63

すべてのセッターにOnPropertyChangedを記述しなくても、クラスのプロパティ変更の通知を自動的に受け取る方法はありますか?(私はそれらが変更されたかどうか知りたい何百ものプロパティを持っています)。


アントンは動的プロキシを提案しています。私は実際に「Castle」ライブラリを過去に似たようなものに使用しました。これにより、作成する必要のあるコードの量は減りますが、プログラムの起動時間(ymmv)が約30秒長くなります。ランタイムソリューション。

コンパイル時の属性を使用して、コンパイル時のソリューションがあるかどうか疑問に思っています...


SlasheneとTcKは、反復コードを生成する提案を提供します-残念ながら、すべてのプロパティがm_Value = valueの単純なケースであるとは限りません-それらの多くはセッターにカスタムコードを持っているため、スニペットとxmlからのcookie-cutterコードは実際には実行可能ではありません私のプロジェクトでも。

4

13 に答える 13

48

編集: NotifyPropertyWeaver の作成者は、より一般的なFodyを支持してツールを非推奨にしました。( weaver から fody に移行するための移行ガイドが用意されています。)


プロジェクトで使用した非常に便利なツールはNotify Property Weaver Fodyです。

プロジェクトのビルド ステップとして自身をインストールし、コンパイル中にPropertyChangedイベントを発生させるコードを挿入します。

プロパティに PropertyChanged を発生させるには、それらに特別な属性を設定します。

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

おまけとして、他のプロパティに依存するプロパティの関係を指定することもできます

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

[DependsOn("Radius")]
public double Area 
{
    get { return Radius * Radius * Math.PI; }
}
于 2011-11-01T09:00:34.473 に答える
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);
                }
            };
    }
}

たとえば、次のように使用します。

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

この例には、いくつかの構文エラーが存在する可能性があります。テストしませんでした。しかし、少なくともそこにはコンセプトが必要です:)

編集:あなたはもっと少ない仕事を望んでいたかもしれませんが、ええ...少なくとも上記のものはそれをずっと簡単にします. また、文字列を使用してプロパティを参照する際の恐ろしい問題をすべて回避できます。

于 2009-02-09T11:48:04.320 に答える
35

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のソリューションと同様に、ラムダの素晴らしさを退屈なフレームワーク機能に置き換えるだけです;-)

KB2468871がインストールされた Framework 4.0 で作業している場合は、この属性も提供するnugetを介してMicrosoft BCL 互換性パックをインストールできます。

于 2012-09-13T14:25:17.363 に答える
11

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;

拡張メソッドは、ラムダ式ツリーを検査するだけで、パフォーマンスに大きな影響を与えることなく、送信者とプロパティ名を特定できます。

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);
        }
    }
}
于 2012-05-30T10:22:06.273 に答える
4

アスペクト指向プログラミング全体を調べたいと思うかもしれません

フレームワーク => linfuを見ることができます

于 2010-05-17T13:33:47.367 に答える
4

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

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

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.

For example this 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" );
        }
    }
}
于 2009-02-09T10:13:13.657 に答える
2

Castle または Spring.NET を見て、インターセプター機能を実装できますか?

于 2009-02-09T11:33:30.890 に答える
2

ActiveSharp - Automatic INotifyPropertyChangedを見つけたところです。まだ使用していませんが、良さそうです。

公式サイトから引用すると…


プロパティ名を文字列として指定せずにプロパティ変更通知を送信します。

代わりに、次のようにプロパティを記述します。

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

プロパティの名前を文字列として含める必要がないことに注意してください。ActiveSharp は、それ自体を確実かつ正確に把握します。これは、プロパティの実装が参照によってバッキング フィールド (_foo) を渡すという事実に基づいて機能します。(ActiveSharp は、その "by ref" 呼び出しを使用して、渡されたバッキング フィールドを識別し、そのフィールドからプロパティを識別します)。

于 2011-06-01T11:35:22.600 に答える
1

実装をより迅速にするために、スニペットを使用できます

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

MV-VM パターンに従うプロジェクトの ViewModel クラスでは、多くの場合、プロパティのセッター内から "PropertyChanged" イベントを発生させる必要があります (INotifyPropertyChanged インターフェイスの実装を支援するため)。これは退屈な作業であり、いつの日か Compiler as a Service を使用して解決されることを願っています...

スニペットのコア (完全なクレジットは私ではなく著者に帰属します) は次のとおりです。

  <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> 
于 2013-05-07T18:09:39.703 に答える
1

子クラスでイベントを呼び出す改善:

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) });
        }
    }
于 2011-05-27T09:15:29.980 に答える
1

人々が使用したいすべての方法を処理できる 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 );
于 2013-06-04T13:48:40.690 に答える
-3

自動プロパティ宣言の上でこの属性を使用するだけです

[NotifyParentProperty(true)]
public object YourProperty { get; set; }
于 2013-01-23T10:20:35.510 に答える