717

INotifyPropertyChangedマイクロソフトは、自動プロパティのように、 のために何かきびきびしたものを実装するべき{get; set; notify;} でした。それとも、それを行うのに何か複雑なことはありますか?

プロパティに「通知」のようなものを自分で実装できますか。クラスに実装するための適切な解決策はありINotifyPropertyChangedますか、またはそれを行う唯一の方法はPropertyChanged、各プロパティでイベントを発生させることです。

PropertyChanged そうでない場合、イベントを発生させるコードを自動生成する何かを書くことはできますか?

4

34 に答える 34

698

postharpのようなものを使用せずに、私が使用する最小バージョンは次のようなものを使用します。

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    protected bool SetField<T>(ref T field, T value, string propertyName)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

各プロパティは次のようになります。

private string name;
public string Name
{
    get { return name; }
    set { SetField(ref name, value, "Name"); }
}

大きくはありません。必要に応じて、基本クラスとして使用することもできます。からのbool戻り値SetFieldは、他のロジックを適用する場合に備えて、それがノーオペレーションであったかどうかを示します。


またはC#5でさらに簡単に:

protected bool SetField<T>(ref T field, T value,
    [CallerMemberName] string propertyName = null)
{...}

これは次のように呼び出すことができます:

set { SetField(ref name, value); }

コンパイラが"Name"自動的に追加するものです。


C#6.0を使用すると、実装が簡単になります。

protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

...そして今C#7で:

protected void OnPropertyChanged(string propertyName)
   => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value,[CallerMemberName] string propertyName =  null)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}

private string name;
public string Name
{
    get => name;
    set => SetField(ref name, value);
}

また、C#8とNullable参照型を使用すると、次のようになります。

public event PropertyChangedEventHandler? PropertyChanged;

protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = "")
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}

private string name;
public string Name
{
    get => name;
    set => SetField(ref name, value);
}
于 2009-08-22T16:43:11.403 に答える
204

.Net 4.5 の時点で、これを行う簡単な方法がついにあります。

.Net 4.5 では、新しい発信者情報属性が導入されました。

private void OnPropertyChanged<T>([CallerMemberName]string caller = null) {
     // make sure only to call this if the value actually changes

     var handler = PropertyChanged;
     if (handler != null) {
        handler(this, new PropertyChangedEventArgs(caller));
     }
}

関数に比較子を追加することもおそらく良い考えです。

EqualityComparer<T>.Default.Equals

その他の例ここここ

発信者情報 (C# および Visual Basic)も参照してください。

于 2012-05-15T07:00:42.847 に答える
166

私はMarcのソリューションが本当に好きですが、「マジックストリング」(リファクタリングをサポートしていない)の使用を避けるために少し改善できると思います。プロパティ名を文字列として使用する代わりに、ラムダ式にするのは簡単です。

private string name;
public string Name
{
    get { return name; }
    set { SetField(ref name, value, () => Name); }
}

マークのコードに次のメソッドを追加するだけで、うまくいきます。

protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
    if (selectorExpression == null)
        throw new ArgumentNullException("selectorExpression");
    MemberExpression body = selectorExpression.Body as MemberExpression;
    if (body == null)
        throw new ArgumentException("The body must be a member expression");
    OnPropertyChanged(body.Member.Name);
}

protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(selectorExpression);
    return true;
}

ところで、これはこのブログ投稿に触発されました。

于 2009-08-22T17:49:34.687 に答える
70

I think people should pay a little more attention to performance; it really does impact the UI when there are a lot of objects to be bound (think of a grid with 10,000+ rows), or if the object's value changes frequently (real-time monitoring app).

I took various implementation found here and elsewhere and did a comparison; check it out perfomance comparison of INotifyPropertyChanged implementations.


Here is a peek at the result Implemenation vs Runtime

于 2013-10-30T16:43:39.327 に答える
16

実際にこれを自分で試す機会はまだありませんが、次回INotifyPropertyChangedの大きな要件を持つプロジェクトをセットアップするときは、コンパイル時にコードを挿入するPostsharp属性を作成する予定です。何かのようなもの:

[NotifiesChange]
public string FirstName { get; set; }

となります:

private string _firstName;

public string FirstName
{
   get { return _firstname; }
   set
   {
      if (_firstname != value)
      {
          _firstname = value;
          OnPropertyChanged("FirstName")
      }
   }
}

これが実際に機能するかどうかはわかりません。座って試してみる必要がありますが、なぜそうなのかわかりません。複数のOnPropertyChangedをトリガーする必要がある状況では、いくつかのパラメーターを受け入れるようにする必要がある場合があります(たとえば、上記のクラスにFullNameプロパティがある場合)

現在、Resharperでカスタムテンプレートを使用していますが、それでも、すべてのプロパティが非常に長いことにうんざりしています。


ああ、グーグルで簡単に検索すると(これを書く前にやるべきだった)、少なくとも1人の人がここまでにこのようなことをしたことがわかります。正確には私が考えていたものではありませんが、理論が優れていることを示すのに十分近いです。

于 2009-08-22T09:45:27.570 に答える
8

非常に AOP に似たアプローチは、既にインスタンス化されたオブジェクトに INotifyPropertyChanged をオンザフライで挿入することです。Castle DynamicProxy のようなものでこれを行うことができます。このテクニックを説明する記事は次のとおりです。

INotifyPropertyChanged を既存のオブジェクトに追加する

于 2009-08-25T01:59:13.420 に答える
5

ここを見てください: http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

ドイツ語で書かれていますが、ViewModelBase.cs をダウンロードできます。cs-File のコメントはすべて英語で書かれています。

この ViewModelBase-Class を使用すると、よく知られている Dependency Properties に似たバインド可能なプロパティを実装できます。

public string SomeProperty
{
    get { return GetValue( () => SomeProperty ); }
    set { SetValue( () => SomeProperty, value ); }
}
于 2012-11-04T01:25:54.090 に答える
4

Yappiと呼ばれる私自身のアプローチを紹介します。Caste Project の Dynamic Proxy のように、既存のオブジェクトまたは型に新しい機能を追加して、ランタイム プロキシ|派生クラス ジェネレータに属します。

基本クラスで INotifyPropertyChanged を 1 回実装し、次のスタイルで派生クラスを宣言し、新しいプロパティの INotifyPropertyChanged を引き続きサポートできます。

public class Animal:Concept
{
    protected Animal(){}
    public virtual string Name { get; set; }
    public virtual int Age { get; set; }
}

派生クラスまたはプロキシ構築の複雑さは、次の行の背後に隠れている可能性があります。

var animal = Concept.Create<Animal>.New();

また、すべての INotifyPropertyChanged 実装作業は次のように実行できます。

public class Concept:INotifyPropertyChanged
{
    //Hide constructor
    protected Concept(){}

    public static class Create<TConcept> where TConcept:Concept
    {
        //Construct derived Type calling PropertyProxy.ConstructType
        public static readonly Type Type = PropertyProxy.ConstructType<TConcept, Implementation<TConcept>>(new Type[0], true);
        //Create constructing delegate calling Constructor.Compile
        public static Func<TConcept> New = Constructor.Compile<Func<TConcept>>(Type);
    }


    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
    {
        var caller = PropertyChanged;
        if(caller!=null)
        {
            caller(this, eventArgs);
        }
    }

    //define implementation
    public class Implementation<TConcept> : DefaultImplementation<TConcept> where TConcept:Concept
    {
        public override Func<TBaseType, TResult> OverrideGetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
        {
            return PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);
        }
        /// <summary>
        /// Overriding property setter implementation.
        /// </summary>
        /// <typeparam name="TBaseType">Base type for implementation. TBaseType must be TConcept, and inherits all its constraints. Also TBaseType is TDeclaringType.</typeparam>
        /// <typeparam name="TDeclaringType">Type, declaring property.</typeparam>
        /// <typeparam name="TConstructedType">Constructed type. TConstructedType is TDeclaringType and TBaseType.</typeparam>
        /// <typeparam name="TResult">Type of property.</typeparam>
        /// <param name="property">PropertyInfo of property.</param>
        /// <returns>Delegate, corresponding to property setter implementation.</returns>
        public override Action<TBaseType, TResult> OverrideSetter<TBaseType, TDeclaringType, TConstructedType, TResult>(PropertyInfo property)
        {
            //This code called once for each declared property on derived type's initialization.
            //EventArgs instance is shared between all events for each concrete property.
            var eventArgs = new PropertyChangedEventArgs(property.Name);
            //get delegates for base calls.
            Action<TBaseType, TResult> setter = PropertyImplementation<TBaseType, TDeclaringType>.GetSetter<TResult>(property.Name);
            Func<TBaseType, TResult> getter = PropertyImplementation<TBaseType, TDeclaringType>.GetGetter<TResult>(property.Name);

            var comparer = EqualityComparer<TResult>.Default;

            return (pthis, value) =>
            {//This code executes each time property setter is called.
                if (comparer.Equals(value, getter(pthis))) return;
                //base. call
                setter(pthis, value);
                //Directly accessing Concept's protected method.
                pthis.OnPropertyChanged(eventArgs);
            };
        }
    }
}

リファクタリングに対して完全に安全であり、型の構築後にリフレクションを使用せず、十分に高速です。

于 2011-07-27T12:13:57.250 に答える
4

これらの答えはすべてとてもいいです。

私の解決策は、コードスニペットを使用して仕事をすることです。

これは、PropertyChanged イベントへの最も単純な呼び出しを使用します。

このスニペットを保存して、「fullprop」スニペットを使用するときに使用します。

場所は、Visual Studio の [Tools\Code Snippet Manager...] メニューにあります。

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>inotifypropfull</Title>
            <Shortcut>inotifypropfull</Shortcut>
            <HelpUrl>http://ofirzeitoun.wordpress.com/</HelpUrl>
            <Description>Code snippet for property and backing field with notification</Description>
            <Author>Ofir Zeitoun</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Declarations>
                <Literal>
                    <ID>type</ID>
                    <ToolTip>Property type</ToolTip>
                    <Default>int</Default>
                </Literal>
                <Literal>
                    <ID>property</ID>
                    <ToolTip>Property name</ToolTip>
                    <Default>MyProperty</Default>
                </Literal>
                <Literal>
                    <ID>field</ID>
                    <ToolTip>The variable backing this property</ToolTip>
                    <Default>myVar</Default>
                </Literal>
            </Declarations>
            <Code Language="csharp">
                <![CDATA[private $type$ $field$;

    public $type$ $property$
    {
        get { return $field$;}
        set { 
            $field$ = value;
            var temp = PropertyChanged;
            if (temp != null)
            {
                temp(this, new PropertyChangedEventArgs("$property$"));
            }
        }
    }
    $end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

好きなように呼び出しを変更できます(上記のソリューションを使用するため)

于 2013-07-04T11:38:18.657 に答える
3

これをスニペットとして保持します。C# 6 では、ハンドラーを呼び出すための便利な構文がいくつか追加されています。

// INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

private void Set<T>(ref T property, T value, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(property, value) == false)
    {
        property = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
于 2015-01-14T00:23:12.943 に答える
3

再利用するために、基本ライブラリに拡張メソッドを作成しました。

public static class INotifyPropertyChangedExtensions
{
    public static bool SetPropertyAndNotify<T>(this INotifyPropertyChanged sender,
               PropertyChangedEventHandler handler, ref T field, T value, 
               [CallerMemberName] string propertyName = "",
               EqualityComparer<T> equalityComparer = null)
    {
        bool rtn = false;
        var eqComp = equalityComparer ?? EqualityComparer<T>.Default;
        if (!eqComp.Equals(field,value))
        {
            field = value;
            rtn = true;
            if (handler != null)
            {
                var args = new PropertyChangedEventArgs(propertyName);
                handler(sender, args);
            }
        }
        return rtn;
    }
}

CallerMemberNameAttributeにより、これは .Net 4.5 で機能します。以前の .Net バージョンで使用する場合は、メソッド宣言を次のように変更する必要があります...,[CallerMemberName] string propertyName = "", ......,string propertyName, ...

使用法:

public class Dog : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    string _name;

    public string Name
    {
        get { return _name; }
        set
        {
            this.SetPropertyAndNotify(PropertyChanged, ref _name, value);
        }
    }
}
于 2014-01-27T11:22:40.230 に答える
2

別の組み合わせたソリューションは、StackFrame を使用することです。

public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void Set<T>(ref T field, T value)
    {
        MethodBase method = new StackFrame(1).GetMethod();
        field = value;
        Raise(method.Name.Substring(4));
    }

    protected void Raise(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
        {
            temp(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

使用法:

public class TempVM : BaseViewModel
{
    private int _intP;
    public int IntP
    {
        get { return _intP; }
        set { Set<int>(ref _intP, value); }
    }
}
于 2013-07-04T11:47:28.523 に答える
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:31:55.970 に答える
2

これに役立つ記事を書きました ( https://msdn.microsoft.com/magazine/mt736453 )。SolSoft.DataBinding NuGet パッケージを使用できます。次に、次のようなコードを記述できます。

public class TestViewModel : IRaisePropertyChanged
{
  public TestViewModel()
  {
    this.m_nameProperty = new NotifyProperty<string>(this, nameof(Name), null);
  }

  private readonly NotifyProperty<string> m_nameProperty;
  public string Name
  {
    get
    {
      return m_nameProperty.Value;
    }
    set
    {
      m_nameProperty.SetValue(value);
    }
  }

  // Plus implement IRaisePropertyChanged (or extend BaseViewModel)
}

利点:

  1. 基本クラスはオプションです
  2. すべての「設定値」に反映されない
  3. 他のプロパティに依存するプロパティを持つことができ、それらはすべて自動的に適切なイベントを発生させます (記事にこの例があります)
于 2016-07-01T21:26:05.917 に答える
2

私はこの方法で解決しました(少し面倒ですが、実行時間は確かに高速です)。

VB では (申し訳ありませんが、C# に翻訳するのは難しくないと思います)、RE で次のように置き換えます。

(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)( |\r\n)*(?<Def>(Public|Private|Friend|Protected) .*Property )(?<Name>[^ ]*) As (?<Type>.*?)[ |\r\n](?![ |\r\n]*Get)

と:

Private _${Name} As ${Type}\r\n${Attr}\r\n${Def}${Name} As ${Type}\r\nGet\r\nReturn _${Name}\r\nEnd Get\r\nSet (Value As ${Type})\r\nIf _${Name} <> Value Then \r\n_${Name} = Value\r\nRaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs("${Name}"))\r\nEnd If\r\nEnd Set\r\nEnd Property\r\n

このトランスフォームはすべてのコードを次のようにします。

<Bindable(True)>
Protected Friend Property StartDate As DateTime?

Private _StartDate As DateTime?
<Bindable(True)>
Protected Friend Property StartDate As DateTime?
    Get
        Return _StartDate
    End Get
    Set(Value As DateTime?)
        If _StartDate <> Value Then
            _StartDate = Value
            RaiseEvent PropertyChange(Me, New ComponentModel.PropertyChangedEventArgs("StartDate"))
        End If
    End Set
End Property

そして、より読みやすいコードが必要な場合は、次の置換を行うだけで反対になります。

Private _(?<Name>.*) As (?<Type>.*)[\r\n ]*(?<Attr><(.*ComponentModel\.)Bindable\(True\)>)[\r\n ]*(?<Def>(Public|Private|Friend|Protected) .*Property )\k<Name> As \k<Type>[\r\n ]*Get[\r\n ]*Return _\k<Name>[\r\n ]*End Get[\r\n ]*Set\(Value As \k<Type>\)[\r\n ]*If _\k<Name> <> Value Then[\r\n ]*_\k<Name> = Value[\r\n ]*RaiseEvent PropertyChanged\(Me, New (.*ComponentModel\.)PropertyChangedEventArgs\("\k<Name>"\)\)[\r\n ]*End If[\r\n ]*End Set[\r\n ]*End Property

${Attr} ${Def} ${Name} As ${Type}

set メソッドの IL コードを置き換えるためにスローしますが、コンパイルされたコードを IL でたくさん書くことはできません.

于 2014-05-02T15:39:16.417 に答える
2

.NET 4.5 でダイナミクスを使用している場合は、心配する必要はありませんINotifyPropertyChanged

dynamic obj = new ExpandoObject();
obj.Name = "John";

Name が何らかのコントロールにバインドされている場合、問題なく動作します。

于 2013-03-27T20:23:01.720 に答える
1

リフレクションを使用したアイデア:

class ViewModelBase : INotifyPropertyChanged {

    public event PropertyChangedEventHandler PropertyChanged;

    bool Notify<T>(MethodBase mb, ref T oldValue, T newValue) {

        // Get Name of Property
        string name = mb.Name.Substring(4);

        // Detect Change
        bool changed = EqualityComparer<T>.Default.Equals(oldValue, newValue);

        // Return if no change
        if (!changed) return false;

        // Update value
        oldValue = newValue;

        // Raise Event
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }//if

        // Notify caller of change
        return true;

    }//method

    string name;

    public string Name {
        get { return name; }
        set {
            Notify(MethodInfo.GetCurrentMethod(), ref this.name, value);
        }
    }//method

}//class
于 2013-02-17T11:38:43.757 に答える
1

これを使って

using System;
using System.ComponentModel;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;


public static class ObservableFactory
{
    public static T Create<T>(T target)
    {
        if (!typeof(T).IsInterface)
            throw new ArgumentException("Target should be an interface", "target");

        var proxy = new Observable<T>(target);
        return (T)proxy.GetTransparentProxy();
    }
}

internal class Observable<T> : RealProxy, INotifyPropertyChanged, INotifyPropertyChanging
{
    private readonly T target;

    internal Observable(T target)
        : base(ImplementINotify(typeof(T)))
    {
        this.target = target;
    }

    public override IMessage Invoke(IMessage msg)
    {
        var methodCall = msg as IMethodCallMessage;

        if (methodCall != null)
        {
            return HandleMethodCall(methodCall);
        }

        return null;
    }

    public event PropertyChangingEventHandler PropertyChanging;
    public event PropertyChangedEventHandler PropertyChanged;



    IMessage HandleMethodCall(IMethodCallMessage methodCall)
    {
        var isPropertySetterCall = methodCall.MethodName.StartsWith("set_");
        var propertyName = isPropertySetterCall ? methodCall.MethodName.Substring(4) : null;

        if (isPropertySetterCall)
        {
            OnPropertyChanging(propertyName);
        }

        try
        {
            object methodCalltarget = target;

            if (methodCall.MethodName == "add_PropertyChanged" || methodCall.MethodName == "remove_PropertyChanged"||
                methodCall.MethodName == "add_PropertyChanging" || methodCall.MethodName == "remove_PropertyChanging")
            {
                methodCalltarget = this;
            }

            var result = methodCall.MethodBase.Invoke(methodCalltarget, methodCall.InArgs);

            if (isPropertySetterCall)
            {
                OnPropertyChanged(methodCall.MethodName.Substring(4));
            }

            return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
        }
        catch (TargetInvocationException invocationException)
        {
            var exception = invocationException.InnerException;
            return new ReturnMessage(exception, methodCall);
        }
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanging(string propertyName)
    {
        var handler = PropertyChanging;
        if (handler != null) handler(this, new PropertyChangingEventArgs(propertyName));
    }

    public static Type ImplementINotify(Type objectType)
    {
        var tempAssemblyName = new AssemblyName(Guid.NewGuid().ToString());

        var dynamicAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
            tempAssemblyName, AssemblyBuilderAccess.RunAndCollect);

        var moduleBuilder = dynamicAssembly.DefineDynamicModule(
            tempAssemblyName.Name,
            tempAssemblyName + ".dll");

        var typeBuilder = moduleBuilder.DefineType(
            objectType.FullName, TypeAttributes.Public | TypeAttributes.Interface | TypeAttributes.Abstract);

        typeBuilder.AddInterfaceImplementation(objectType);
        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanged));
        typeBuilder.AddInterfaceImplementation(typeof(INotifyPropertyChanging));
        var newType = typeBuilder.CreateType();
        return newType;
    }
}

}

于 2014-03-15T16:05:42.573 に答える
1

INPC の実装をできるだけ簡単にするために、次の拡張メソッド (C# 6.0 を使用) を使用します。

public static bool ChangeProperty<T>(this PropertyChangedEventHandler propertyChanged, ref T field, T value, object sender,
    IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
    if (comparer == null)
        comparer = EqualityComparer<T>.Default;

    if (comparer.Equals(field, value))
    {
        return false;
    }
    else
    {
        field = value;
        propertyChanged?.Invoke(sender, new PropertyChangedEventArgs(propertyName));
        return true;
    }
}

INPC の実装は次のようになります (毎回これを実装するか、基底クラスを作成することができます)。

public class INPCBaseClass: INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool changeProperty<T>(ref T field, T value,
        IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
    {
        return PropertyChanged.ChangeProperty(ref field, value, this, comparer, propertyName);
    }
}

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

private string testProperty;
public string TestProperty
{
    get { return testProperty; }
    set { changeProperty(ref testProperty, value); }
}

注: 必要に応じて、拡張メソッドの宣言を省略できますが[CallerMemberName]、柔軟性を維持したかったのです。

バッキング フィールドのないプロパティがある場合は、次のようにオーバーロードできますchangeProperty

protected bool changeProperty<T>(T property, Action<T> set, T value,
    IEqualityComparer<T> comparer = null, [CallerMemberName] string propertyName = null)
{
    bool ret = changeProperty(ref property, value, comparer, propertyName);
    if (ret)
        set(property);
    return ret;
}

使用例は次のとおりです。

public string MyTestProperty
{
    get { return base.TestProperty; }
    set { changeProperty(base.TestProperty, (x) => { base.TestProperty = x; }, value); }
}
于 2015-09-24T08:49:46.037 に答える
1

この質問にはすでに膨大な数の回答があることを認識していますが、どれも私にとって適切ではないと感じました. 私の問題は、パフォーマンスの低下を望んでおらず、その理由だけで少し冗長になることをいとわないことです。また、自動プロパティもあまり気にしないため、次の解決策に至りました。

public abstract class AbstractObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual bool SetValue<TKind>(ref TKind Source, TKind NewValue, params string[] Notify)
    {
        //Set value if the new value is different from the old
        if (!Source.Equals(NewValue))
        {
            Source = NewValue;

            //Notify all applicable properties
            foreach (var i in Notify)
                OnPropertyChanged(i);

            return true;
        }

        return false;
    }

    public AbstractObject()
    {
    }
}

つまり、上記の解決策は、これを行うことを気にしない場合に便利です。

public class SomeObject : AbstractObject
{
    public string AnotherProperty
    {
        get
        {
            return someProperty ? "Car" : "Plane";
        }
    }

    bool someProperty = false;
    public bool SomeProperty
    {
        get
        {
            return someProperty;
        }
        set
        {
            SetValue(ref someProperty, value, "SomeProperty", "AnotherProperty");
        }
    }

    public SomeObject() : base()
    {
    }
}

長所

  • 反射なし
  • 古い値 != 新しい値の場合のみ通知します
  • 複数のプロパティを一度に通知する

短所

  • 自動プロパティはありません (ただし、両方のサポートを追加できます!)
  • やや冗長
  • ボクシング (小さなパフォーマンス ヒット?)

残念ながら、これを行うよりもまだ良いですが、

set
{
    if (!someProperty.Equals(value))
    {
        someProperty = value;
        OnPropertyChanged("SomeProperty");
        OnPropertyChanged("AnotherProperty");
    }
}

すべての単一のプロパティに対して、冗長性が追加されて悪夢になります;-(

このソリューションが他のソリューションと比較してパフォーマンスの面で優れていると主張するわけではありません。提示された他のソリューションが気に入らない人にとっては実行可能なソリューションであることに注意してください。

于 2017-02-17T04:20:44.663 に答える
1

これらの種類のプロパティを実装するときに考慮する必要があるその他のことは、INotifyPropertyChang *ed *ing が両方ともイベント引数クラスを使用するという事実です。

設定されているプロパティが多数ある場合、イベント引数クラス インスタンスの数が膨大になる可能性があります。これらは文字列爆発が発生する可能性のある領域の 1 つであるため、それらをキャッシュすることを検討する必要があります。

この実装と、それが考案された理由の説明を見てください。

ジョシュ・スミスのブログ

于 2009-08-25T02:35:36.363 に答える
0

=>ここで、次の機能を備えた私のソリューション

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. 反省なし
  2. 略記
  3. ビジネスコードに魔法の文字列がありません
  4. アプリケーション全体での PropertyChangedEventArgs の再利用性
  5. 1 つのステートメントで複数のプロパティを通知する可能性
于 2013-07-04T11:58:54.513 に答える
0

別のアイデア...

 public class ViewModelBase : INotifyPropertyChanged
{
    private Dictionary<string, object> _propertyStore = new Dictionary<string, object>();
    protected virtual void SetValue<T>(T value, [CallerMemberName] string propertyName="") {
        _propertyStore[propertyName] = value;
        OnPropertyChanged(propertyName);
    }
    protected virtual T GetValue<T>([CallerMemberName] string propertyName = "")
    {
        object ret;
        if (_propertyStore.TryGetValue(propertyName, out ret))
        {
            return (T)ret;
        }
        else
        {
            return default(T);
        }
    }

    //Usage
    //public string SomeProperty {
    //    get { return GetValue<string>();  }
    //    set { SetValue(value); }
    //}

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        var temp = PropertyChanged;
        if (temp != null)
            temp.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
于 2013-03-12T08:00:42.980 に答える
-2

大規模なオーバーエンジニアリングについて話します。これは、正しい方法で行うよりもはるかに複雑であり、ほとんどまたはまったくメリットがありません。IDEがコードスニペットをサポートしている場合(Visual Studio / MonoDevelopはサポートしています)、これを非常に簡単に実装できます。実際に入力する必要があるのは、プロパティのタイプとプロパティ名だけです。余分な3行のコードは自動生成されます。

于 2009-08-22T18:18:18.263 に答える