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

745voto

Marc Gravell Punkte 970173

Ohne etwas wie Postsharp zu verwenden, verwendet die Minimalversion, die ich verwende, etwas wie:

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

Jede Eigenschaft ist dann einfach etwas wie:

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

die nicht sehr groß ist; sie kann auch als Basisklasse verwendet werden, wenn Sie wollen. Die bool Rückkehr von SetField sagt Ihnen, ob es ein No-op war, falls Sie eine andere Logik anwenden wollen.


oder noch einfacher mit C# 5:

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

die wie folgt aufgerufen werden kann:

set { SetField(ref name, value); }

mit dem der Compiler die "Name" automatisch.


C# 6.0 macht die Implementierung einfacher:

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

...und jetzt mit 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);
}

Und mit C# 8 und nullbaren Referenztypen würde es wie folgt aussehen:

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

5 Stimmen

Netter Trick, Marc! Ich habe eine Verbesserung vorgeschlagen, um einen Lambda-Ausdruck anstelle des Eigenschaftsnamens zu verwenden, siehe meine Antwort

3 Stimmen

DevXpress Xpo macht das so.

12 Stimmen

@Thomas - die Lambda ist alles schön und gut, aber es fügt eine Menge Overhead für etwas, das eigentlich sehr einfach ist. Ein praktischer Trick, aber ich bin nicht sicher, ob er immer praktisch ist.

206voto

Daniel Little Punkte 16296

Ab .Net 4.5 gibt es endlich eine einfache Möglichkeit, dies zu tun.

.Net 4.5 führt ein neues Attribut für Anruferinformationen ein.

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

Es ist wahrscheinlich eine gute Idee, der Funktion auch einen Vergleicher hinzuzufügen.

EqualityComparer<T>.Default.Equals

Weitere Beispiele aquí y aquí

Siehe auch Anruferinformationen (C# und Visual Basic)

14 Stimmen

Hervorragend! Aber warum ist es generisch?

0 Stimmen

@abatishchev Ich denke, es muss nicht sein, ich war gerade spielen mit der Idee, mit der Funktion die Eigenschaft als auch gesetzt. Ich werde sehen, ob ich meine Antwort aktualisieren kann, um die vollständige Lösung bereitzustellen. Die zusätzlichen Beispiele leisten in der Zwischenzeit gute Arbeit.

3 Stimmen

Sie wurde mit C # 5.0 eingeführt. Es hat nichts mit .net 4.5 zu tun, aber dies ist eine großartige Lösung!

170voto

Thomas Levesque Punkte 277723

Mir gefällt Marcs Lösung sehr gut, aber ich denke, sie kann leicht verbessert werden, um die Verwendung einer "magischen Zeichenkette" zu vermeiden (die das Refactoring nicht unterstützt). Anstatt den Eigenschaftsnamen als Zeichenkette zu verwenden, ist es einfach, einen Lambda-Ausdruck daraus zu machen:

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

Fügen Sie einfach die folgenden Methoden zu Marcs Code hinzu, dann klappt's:

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

Übrigens, dies wurde inspiriert durch dieser Blogbeitrag .

7 Stimmen

Es gibt mindestens einen Rahmen, der diese Methode verwendet, ReactiveUI .

1 Stimmen

Sehr spät bedeutete dies, dass man die Reflexion durchlaufen musste, was eine Leistungseinbuße bedeutete. Es könnte akzeptabel sein, aber die Einstellung einer Eigenschaft ist nicht ein Ort, wo ich möchte meine Anwendung zu viele Zyklen zu verbringen.

2 Stimmen

@BrunoBrant Sind Sie sicher, dass es einen Leistungsabfall gibt? Dem Blogbeitrag zufolge erfolgt die Reflexion während der Kompilierung und nicht zur Laufzeit (d. h. statische Reflexion).

143voto

Tom Gilder Punkte 959

Außerdem gibt es Fody die eine AddINotifyPropertyChangedInterface Add-In, mit dem Sie dies schreiben können:

[AddINotifyPropertyChangedInterface]
public class Person 
{        
    public string GivenNames { get; set; }
    public string FamilyName { get; set; }
}

...und zur Kompilierzeit Benachrichtigungen über geänderte Eigenschaften einfügt.

9 Stimmen

Ich denke, das ist genau das, was OP gesucht hat, als er fragte: "Können wir selbst etwas wie 'notify' in unseren Eigenschaften implementieren. Gibt es eine elegante Lösung für die Implementierung von INotifyPropertyChanged in Ihrer Klasse?

3 Stimmen

Dies ist wirklich die einzige elegante Lösung, und sie funktioniert einwandfrei, wie @CADbloke sagte. Und ich war skeptisch über die Weaver als gut, aber ich überprüft/überprüfen Sie den IL-Code hinter und es ist perfekt, es ist einfach, tut alles, was Sie brauchen und keine andere. Es hakt sich auch ein und ruft die Methode auf, die Sie in der Basisklasse dafür vorgesehen haben, ob NotifyOnProp..., OnNotify... spielt keine Rolle, funktioniert also gut mit jeder Basisklasse, die Sie haben könnten und die INotify... implementiert.

2 Stimmen

Sie können leicht überprüfen, was der Weaver tut, werfen Sie einen Blick auf das Build-Ausgabefenster, es listet alle PropertyChanged Dinge, die es gewebt hat. Verwendung der VScolorOutput-Erweiterung mit dem Regex-Muster "Fody/.*?:",LogCustom2,True hebt es in der Farbe "Custom 2" hervor. Ich habe es hellrosa gemacht, damit es leicht zu finden ist. Fody einfach alles, es ist die ordentlichste Art, etwas zu tun, die viele sich wiederholende Typisierung hat.

72voto

Peijen Punkte 83

Ich denke, die Leute sollten ein wenig mehr auf die Leistung achten; es wirkt sich wirklich auf die Benutzeroberfläche aus, wenn viele Objekte gebunden werden müssen (denken Sie an ein Raster mit mehr als 10.000 Zeilen), oder wenn sich der Wert des Objekts häufig ändert (Echtzeit-Überwachungsanwendung).

Ich habe verschiedene Umsetzungen, die ich hier und anderswo gefunden habe, miteinander verglichen; sehen Sie sich das an Leistungsvergleich von INotifyPropertyChanged-Implementierungen .


Hier ein kleiner Einblick in das Ergebnis Implemenation vs Runtime

16 Stimmen

-1: Es gibt keinen Leistungs-Overhead: CallerMemberName werden zur Kompilierzeit in Literalwerte umgewandelt. Versuchen Sie einfach, Ihre Anwendung zu dekompilieren.

0 Stimmen

Hier ist die entsprechende Frage und Antwort: stackoverflow.com/questions/22580623/

1 Stimmen

@JYL, Sie haben Recht, dass CallerMemberName keinen großen Overhead verursacht hat. Ich muss etwas falsch implementiert haben, als ich es das letzte Mal versuchte. Ich werde den Blog und die Antwort aktualisieren, um den Benchmark für CallerMemberName und die Fody-Implementierung später zu berücksichtigen.

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