751 Stimmen

Implementieren von INotifyPropertyChanged - gibt es einen besseren Weg?

Microsoft hätte etwas Flottes für INotifyPropertyChanged wie bei den automatischen Eigenschaften, geben Sie einfach {get; set; notify;} Ich halte das für sehr sinnvoll. Oder gibt es dabei irgendwelche Komplikationen?

Können wir selbst so etwas wie "notify" in unseren Eigenschaften implementieren. Gibt es eine anmutige Lösung für die Implementierung von INotifyPropertyChanged in Ihrer Klasse oder die einzige Möglichkeit, dies zu tun, besteht darin, die PropertyChanged Ereignis in jeder Eigenschaft.

Wenn nicht, können wir etwas schreiben, um den Code automatisch zu generieren, der die PropertyChanged Veranstaltung?

0 Stimmen

Siehe stackoverflow.com/questions/1329138/ für eine compilergeprüfte Art der Implementierung von INotifyPropertyChanged. Vermeidung von Eigenschaftsnamen als magische Zeichenkette.

8 Stimmen

9 Stimmen

5voto

Dan Punkte 6383

Zwar gibt es offensichtlich viele Möglichkeiten, dies zu tun, mit Ausnahme der AOP Magie Antworten, keine der Antworten scheinen bei der Einstellung eines Modells Eigenschaft direkt aus dem View-Modell, ohne ein lokales Feld zu verweisen aussehen.

Das Problem ist, dass Sie nicht auf eine Eigenschaft verweisen können. Sie können jedoch eine Aktion verwenden, um diese Eigenschaft festzulegen.

protected bool TrySetProperty<T>(Action<T> property, T newValue, T oldValue, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(oldValue, newValue))
    {
        return false;
    }

    property(newValue);
    RaisePropertyChanged(propertyName);
    return true;
}

Dies kann wie der folgende Codeauszug verwendet werden.

public int Prop {
    get => model.Prop;
    set => TrySetProperty(x => model.Prop = x, value, model.Prop);
}

Sehen Sie sich das an BitBucket-Wiedervorlage für eine vollständige Implementierung der Methode und einige verschiedene Möglichkeiten, das gleiche Ergebnis zu erzielen, einschließlich einer Methode, die LINQ verwendet, und einer Methode, die Reflection verwendet. Bitte beachten Sie, dass diese Methoden leistungsmäßig langsamer sind.

0 Stimmen

Danach habe ich schon lange gesucht. Danke, dass Sie Ihre Implementierung mit uns teilen. Verwenden Sie immer noch diese Methode?

0 Stimmen

@GisMofx Ich habe nicht wirklich viel WPF-Entwicklung in letzter Zeit getan, aber in meinem letzten WPF-Projekt, habe ich diese Methode, zusammen mit einigen der anderen in verwenden NotifyPropertyChange aus meinem Repo, das ich verlinkt habe

5voto

Kelqualyn Punkte 469

Ich möchte Ihnen meinen eigenen Ansatz vorstellen, den ich Jappi . Es gehört zu den Generatoren von Laufzeit-Proxys und abgeleiteten Klassen, die einem bestehenden Objekt oder Typ neue Funktionen hinzufügen, wie das Dynamic Proxy von Caste Project.

Sie ermöglicht es, INotifyPropertyChanged einmal in der Basisklasse zu implementieren und dann abgeleitete Klassen im folgenden Stil zu deklarieren, wobei INotifyPropertyChanged für neue Eigenschaften weiterhin unterstützt wird:

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

Die Komplexität der Konstruktion abgeleiteter Klassen oder Proxys kann hinter der folgenden Zeile versteckt werden:

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

Und alle INotifyPropertyChanged-Implementierungsarbeiten können wie folgt durchgeführt werden:

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

Es ist völlig sicher für Refactoring, verwendet keine Reflexion nach der Typkonstruktion und ist schnell genug.

0 Stimmen

Warum brauchen Sie TDeclaration Typ-Parameter auf PropertyImplementation ? Sicherlich können Sie einen geeigneten Typ zum Aufrufen (nicht callvirt) der Getter/Setter von nur mit TImplementation ?

0 Stimmen

Die Umsetzung funktioniert in den meisten Fällen. Ausnahmen sind: 1. Mit "new" C# keyvord neu definierte Eigenschaften. 2. Eigenschaften einer expliziten Schnittstellenimplementierung.

5voto

Siehe hier: http://dotnet-forum.de/blogs/thearchitect/archive/2012/11/01/die-optimale-implementierung-des-inotifypropertychanged-interfaces.aspx

Es ist in Deutsch geschrieben, aber Sie können die ViewModelBase.cs herunterladen. Alle Kommentare in der cs-Datei sind in Englisch geschrieben.

Mit dieser ViewModelBase-Klasse ist es möglich, bindbare Eigenschaften ähnlich den bekannten Dependency Properties zu implementieren:

public string SomeProperty
{
    get { return GetValue( () => SomeProperty ); }
    set { SetValue( () => SomeProperty, value ); }
}

2 Stimmen

Der Link ist defekt.

4voto

StuffOfInterest Punkte 488

Basierend auf der Antwort von Thomas, die von einer Antwort von Marc abgeleitet wurde, habe ich den Code der reflektierenden Eigenschaft in eine Basisklasse umgewandelt:

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

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

    protected void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
    {
        if (selectorExpression == null)
            throw new ArgumentNullException("selectorExpression");
        var me = selectorExpression.Body as MemberExpression;

        // Nullable properties can be nested inside of a convert function
        if (me == null)
        {
            var ue = selectorExpression.Body as UnaryExpression;
            if (ue != null)
                me = ue.Operand as MemberExpression;
        }

        if (me == null)
            throw new ArgumentException("The body must be a member expression");

        OnPropertyChanged(me.Member.Name);
    }

    protected void SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression, params Expression<Func<object>>[] additonal)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return;
        field = value;
        OnPropertyChanged(selectorExpression);
        foreach (var item in additonal)
            OnPropertyChanged(item);
    }
}

Die Verwendung ist dieselbe wie bei Thomas' Antwort, außer dass Sie zusätzliche Eigenschaften für die Benachrichtigung übergeben können. Dies war notwendig, um berechnete Spalten zu behandeln, die in einem Raster aktualisiert werden müssen.

private int _quantity;
private int _price;

public int Quantity 
{ 
    get { return _quantity; } 
    set { SetField(ref _quantity, value, () => Quantity, () => Total); } 
}
public int Price 
{ 
    get { return _price; } 
    set { SetField(ref _price, value, () => Price, () => Total); } 
}
public int Total { get { return _price * _quantity; } }

Ich habe diese fahren eine Sammlung von Elementen in einer BindingList über ein DataGridView ausgesetzt gespeichert. Es hat die Notwendigkeit für mich zu tun manuelle Refresh()-Aufrufe an das Gitter beseitigt.

3voto

Mike Ward Punkte 3023

Ich behalte das hier als Schnipsel. C# 6 fügt einige schöne Syntax für den Aufruf der Handler.

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X