web-dev-qa-db-ja.com

自動的にINotifyPropertyChanged

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


アントンは 動的プロキシ を提案しています。私は実際に「Castle」ライブラリを過去に似たようなものに使用しました、そしてそれは私が書かなければならなかったコードの量を減らしますが、それは私のプログラムの起動時間(ymmv)に約30秒を追加しました-それはランタイムソリューション。

おそらくコンパイル時の属性を使用して、コンパイル時の解決策があるかどうか疑問に思っています...


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

57
Tim Gradwell

EDIT:NotifyPropertyWeaverの作成者は、より一般的な Fody を優先してツールを廃止しました。 (A 移行ガイド ウィーバーからフォーディに移動する人向けです。)


私のプロジェクトで使用した非常に便利なツールは プロパティウィーバーに通知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; }
}
43
Isak Savo

Nameofオペレーターは、2015年7月に.NET 4.6とVS2015を備えたC#6.0で実装されました。以下は、C#<6.0でも引き続き有効です

以下のコードを使用します(From 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);

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

編集:もう少し作業が必要だったかもしれませんが、ええと...少なくとも上記のものを使用すると、作業がはるかに簡単になります。また、文字列を使用してプロパティを参照することによるすべての恐ろしい問題を回避できます。

39
Svish

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互換機能パックをインストールできます。この属性も提供します。

31
takrl

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タイプの保護フィールドを宣言し、イベントのaddおよびremoveアクセサーを明示的に実装することです。

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

タイプセーフを実装するINotifyPropertyChangedここを参照

次に、独自のコードスニペットを作成します。

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

コードスニペットデザイナー を使用すると、完了です。 INotifyPropertyChangedを構築する簡単で安全な方法。

10
Nicolas Dorier

標準的な方法はわかりませんが、次の2つの回避策があります。

1) PostSharp は、コンパイル後に実行できます。非常に便利ですが、ビルドごとに時間がかかります。

2)カスタムツールi 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" );
        }
    }
}
4
TcKs

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

フレームワーク=>あなたは見ることができます linf

4
almog.ori

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

それのウェブサイトから引用するには...


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

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

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

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

2
Ian Ringrose

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

2
Neil Barnwell

quiickerを実装するためだけに、snippetを使用できます

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

m-V-VMパターンに従うプロジェクトのViewModelクラスでは、プロパティのセッター内から(INotifyPropertyChangedインターフェイスの実装を支援するために) "PropertyChanged"イベントを発生させる必要があることがよくあります。これは退屈な作業であり、いつかはコンパイラーをサービスとして使用することで解決できると期待しています...

スニペットコア(フルクレジットは私ではない作者に送られます)は次のとおりです

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

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

おかげで呼び出されます: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) });
        }
    }
1
champier

人々がそれを使用したいと思うあらゆる方法を処理できるプロパティ変更の単一の実装はありません。最善の策は、ここであなたのために仕事をするためのヘルパークラスを生成することです私が使うものの例です

/// <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 );
1
MikeT