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

40voto

TiMoch Punkte 928

Ich stelle eine Bindable-Klasse in meinem Blog unter http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/ Bindable verwendet ein Wörterbuch als Eigenschaftstasche. Es ist einfach genug, die notwendigen Überladungen für eine Unterklasse hinzuzufügen, um ihr eigenes Backing-Feld mit ref-Parametern zu verwalten.

  • Keine Zauberschnur
  • Keine Reflexion
  • Kann verbessert werden, um die Standard-Wörterbuchsuche zu unterdrücken

Der Code:

public class Bindable : INotifyPropertyChanged {
    private Dictionary<string, object> _properties = new Dictionary<string, object>();

    /// <summary>
    /// Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    protected T Get<T>([CallerMemberName] string name = null) {
        Debug.Assert(name != null, "name != null");
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T)value;
        return default(T);
    }

    /// <summary>
    /// Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    /// <remarks>Use this overload when implicitly naming the property</remarks>
    protected void Set<T>(T value, [CallerMemberName] string name = null) {
        Debug.Assert(name != null, "name != null");
        if (Equals(value, Get<T>(name)))
            return;
        _properties[name] = value;
        OnPropertyChanged(name);
    }

    public event PropertyChangedEventHandler PropertyChanged;

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

Sie kann wie folgt verwendet werden:

public class Contact : Bindable {
    public string FirstName {
        get { return Get<string>(); }
        set { Set(value); }
    }
}

2 Stimmen

Das ist eine gute Lösung, aber der einzige Nachteil ist, dass es einen kleinen Leistungsabfall beim Einpacken / Auspacken gibt.

0 Stimmen

@MCattle wenn wir die Auswirkungen von Boxing/Unboxing messen wollen, dann können wir auch die Verwendung des Wörterbuchs abschaffen und Get/Set-Signaturen anbieten, die ein Ref-Backing-Feld verwenden. Wir können auch die Mittel zum Zwischenspeichern von PropertyChangedEventArgs-Objekten bereitstellen

1 Stimmen

Ich würde vorschlagen, Folgendes zu verwenden protected T Get<T>(T defaultValue, [CallerMemberName] string name = null) und prüfen Sie auch if (_properties.ContainsKey(name) && Equals(value, Get<T>(default(T), name))) in Set (zum Anheben & Speichern beim ersten Setzen auf den Standardwert)

17voto

Martin Harris Punkte 27587

Ich hatte noch keine Gelegenheit, dies selbst auszuprobieren, aber wenn ich das nächste Mal ein Projekt mit einer großen Anforderung für INotifyPropertyChanged einrichte, habe ich vor, eine Postsharp Attribut, das den Code zur Kompilierungszeit einfügt. Etwas wie:

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

Wird werden:

private string _firstName;

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

Ich bin mir nicht sicher, ob das in der Praxis funktioniert, und ich muss mich hinsetzen und es ausprobieren, aber ich wüsste nicht, warum nicht. Möglicherweise muss ich es einige Parameter für Situationen akzeptieren, in denen mehr als ein OnPropertyChanged ausgelöst werden muss (wenn ich zum Beispiel eine FullName-Eigenschaft in der Klasse oben hatte)

Derzeit verwende ich eine benutzerdefinierte Vorlage in Resharper, aber selbst damit habe ich die Nase voll davon, dass alle meine Eigenschaften so lang sind.


Ah, eine schnelle Google-Suche (die ich hätte durchführen sollen, bevor ich dies schrieb) zeigt, dass mindestens eine Person so etwas schon einmal gemacht hat aquí . Nicht genau das, was ich im Sinn hatte, aber nahe genug, um zu zeigen, dass die Theorie gut ist.

7 Stimmen

Ein kostenloses Tool namens Fody scheint dasselbe zu tun und fungiert als generischer Code-Injektor zur Kompilierzeit. Es ist in Nuget herunterladbar, wie seine PropertyChanged und PropertyChanging Plugin-Pakete sind.

13voto

testCoder Punkte 6817

Ja, es gibt sicherlich bessere Möglichkeiten. Hier ist er:

Schritt-für-Schritt-Anleitung von mir erstellt, basierend auf dieser nützlicher Artikel .

  • Neues Projekt erstellen
  • Installieren Sie das Schlosskernpaket in das Projekt

Install-Paket Castle.Core

  • Nur mvvm light-Bibliotheken installieren

Installieren-Paket MvvmLightLibs

  • Fügen Sie dem Projekt zwei Klassen hinzu:

NotifierInterceptor

public class NotifierInterceptor : IInterceptor
    {
        private PropertyChangedEventHandler handler;
        public static Dictionary<String, PropertyChangedEventArgs> _cache =
          new Dictionary<string, PropertyChangedEventArgs>();

        public void Intercept(IInvocation invocation)
        {
            switch (invocation.Method.Name)
            {
                case "add_PropertyChanged":
                    handler = (PropertyChangedEventHandler)
                              Delegate.Combine(handler, (Delegate)invocation.Arguments[0]);
                    invocation.ReturnValue = handler;
                    break;
                case "remove_PropertyChanged":
                    handler = (PropertyChangedEventHandler)
                              Delegate.Remove(handler, (Delegate)invocation.Arguments[0]);
                    invocation.ReturnValue = handler;
                    break;
                default:
                    if (invocation.Method.Name.StartsWith("set_"))
                    {
                        invocation.Proceed();
                        if (handler != null)
                        {
                            var arg = retrievePropertyChangedArg(invocation.Method.Name);
                            handler(invocation.Proxy, arg);
                        }
                    }
                    else invocation.Proceed();
                    break;
            }
        }

        private static PropertyChangedEventArgs retrievePropertyChangedArg(String methodName)
        {
            PropertyChangedEventArgs arg = null;
            _cache.TryGetValue(methodName, out arg);
            if (arg == null)
            {
                arg = new PropertyChangedEventArgs(methodName.Substring(4));
                _cache.Add(methodName, arg);
            }
            return arg;
        }
    }

ProxyCreator

public class ProxyCreator
{
    public static T MakeINotifyPropertyChanged<T>() where T : class, new()
    {
        var proxyGen = new ProxyGenerator();
        var proxy = proxyGen.CreateClassProxy(
          typeof(T),
          new[] { typeof(INotifyPropertyChanged) },
          ProxyGenerationOptions.Default,
          new NotifierInterceptor()
          );
        return proxy as T;
    }
}
  • Erstellen Sie z. B. Ihr Ansichtsmodell:

-

 public class MainViewModel
    {
        public virtual string MainTextBox { get; set; }

        public RelayCommand TestActionCommand
        {
            get { return new RelayCommand(TestAction); }
        }

        public void TestAction()
        {
            Trace.WriteLine(MainTextBox);
        }
    }
  • Bindungen in xaml einfügen:

    <TextBox Text="{Binding MainTextBox}" ></TextBox>
    <Button Command="{Binding TestActionCommand}" >Test</Button>
  • Fügen Sie eine Codezeile in die Code-Behind-Datei MainWindow.xaml.cs wie folgt ein:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • Viel Spaß!

enter image description here

Achtung!!! Alle gebundenen Eigenschaften sollten mit dem Schlüsselwort virtual dekoriert werden, da sie von Castle Proxy für Overriding verwendet werden.

0 Stimmen

Mich würde interessieren, welche Version von Castle Sie verwenden. Ich verwende 3.3.0 und die Methode CreateClassProxy hat diese Parameter nicht: type , interfaces to apply , interceptors .

0 Stimmen

Vergessen Sie's, ich habe das generische CreateClassProxy<T> Methode. Ganz anders ... hmmm, ich frage mich, warum die generische Methode so eingeschränkt ist :(

8voto

HokieMike Punkte 645

Ein sehr AOP-ähnlicher Ansatz besteht darin, das INotifyPropertyChanged-Zeug in ein bereits instanziiertes Objekt zu injizieren, und zwar on the fly. Sie können dies mit etwas wie Castle DynamicProxy tun. Hier ist ein Artikel, der diese Technik erklärt:

Hinzufügen von INotifyPropertyChanged zu einem bestehenden Objekt

5voto

Ofir Punkte 2114

Alle diese Antworten sind sehr schön.

Meine Lösung ist die Verwendung der Code-Schnipsel, um die Aufgabe zu erledigen.

Dabei wird der einfachste Aufruf des PropertyChanged-Ereignisses verwendet.

Speichern Sie dieses Snippet und verwenden Sie es wie das Snippet "fullprop".

den Standort finden Sie unter 'Tools \Code Snippet Manager...' in Visual Studio.

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

Sie können den Aufruf nach Belieben ändern (um die oben genannten Lösungen zu verwenden)

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