it-swarm.com.de

Implementierung von INotifyPropertyChanged - Gibt es einen besseren Weg?

Microsoft hätte etwas flinkes für INotifyPropertyChanged implementieren sollen, wie in den automatischen Eigenschaften, einfach {get; set; notify;}.__ angeben. Ich denke, es macht viel Sinn, dies zu tun. Oder gibt es irgendwelche Komplikationen? 

Können wir selbst etwas wie "Benachrichtigen" in unseren Immobilien implementieren? Gibt es eine elegante Lösung für die Implementierung vonINotifyPropertyChangedin Ihrer Klasse, oder Sie können dies nur tun, indem Sie das PropertyChanged-Ereignis in jeder Eigenschaft auslösen.

Wenn nicht, können wir etwas schreiben, um den Code automatisch zu generieren, um ein PropertyChanged-Ereignis auszulösen?

588
P.K

Ohne so etwas wie postsharp zu verwenden, verwendet die Minimalversion, die ich verwende, Folgendes:

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 so etwas wie:

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

was nicht riesig ist; Wenn Sie möchten, kann es auch als Basisklasse verwendet werden. Die bool-Rückkehr von SetField sagt Ihnen, ob es sich um ein No-Op handelt, falls Sie eine andere Logik anwenden möchten.


oder noch einfacher mit C # 5:

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

was kann man so nennen:

set { SetField(ref name, value); }

damit fügt der Compiler den "Name" automatisch hinzu.


C # 6.0 erleichtert die Implementierung:

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

... und jetzt mit C # 7:

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

Ab .Net 4.5 gibt es endlich einen einfachen Weg, dies zu tun.

.Net 4.5 führt neue Anruferinformationsattribute 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 hier und hier

Siehe auch Anruferinformationen (C # und Visual Basic)

190
Daniel Little

Ich mag Marc's Lösung wirklich, aber ich denke, es kann etwas verbessert werden, um die Verwendung einer "magischen Zeichenfolge" (die kein Refactoring unterstützt) zu vermeiden. Anstatt den Eigenschaftsnamen als String zu verwenden, können Sie ihn leicht zu einem Lambda-Ausdruck machen:

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

Fügen Sie dem Code von Marc einfach die folgenden Methoden hinzu.

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

BTW, das war inspiriert von  dieser blog post aktualisierte URL

160
Thomas Levesque

Es gibt auch Fody , das über ein PropertyChanged Add-In verfügt.

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

... und zum Zeitpunkt des Kompilierens injiziert Eigenschaften geänderte Benachrichtigungen.

109
Tom Gilder

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 sollen (denken Sie an ein Raster mit 10.000 Zeilen) oder wenn sich der Wert des Objekts häufig ändert (Echtzeit-Überwachungs-App). .

Ich nahm verschiedene Implementierungen, die hier und anderswo gefunden wurden, und führte einen Vergleich durch. Leistungsvergleich von INotifyPropertyChanged-Implementierungen .


Hier ist ein Blick auf das Ergebnis Implemenation vs Runtime

62
Peijen

Ich führe in meinem Blog unter http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/ .__ eine Bindable-Klasse ein. Es ist leicht genug, die notwendigen Überladungen für eine Unterklasse hinzuzufügen, um ihr eigenes Hintergrundfeld mithilfe von ref-Parametern zu verwalten.

  • Keine magische Schnur
  • Keine Reflexion
  • Kann verbessert werden, um die Standardwö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));
        }
    }
}

Es kann wie folgt verwendet werden:

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

Ich hatte bisher noch keine Gelegenheit, dies selbst auszuprobieren, aber beim nächsten Einrichten eines Projekts mit einer großen Anforderung für INotifyPropertyChanged beabsichtige ich, ein Postsharp - Attribut zu schreiben, das den Code beim Kompilieren einfügt Zeit. So 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 nicht sicher, ob dies in der Praxis funktionieren wird, und ich muss mich hinsetzen und es ausprobieren, aber ich sehe nicht, warum nicht. Es kann erforderlich sein, einige Parameter für Situationen zu akzeptieren, in denen mehr als ein OnPropertyChanged-Objekt ausgelöst werden muss (wenn zum Beispiel eine FullName-Eigenschaft in der obigen Klasse vorhanden war).

Derzeit verwende ich eine benutzerdefinierte Vorlage in Resharper, aber selbst wenn ich es satt habe, sind all meine Eigenschaften so lang.


Ah, eine schnelle Google-Suche (die ich hätte tun sollen, bevor ich das geschrieben habe) zeigt, dass mindestens eine Person vor hier so etwas getan hat. Nicht genau das, was ich mir vorgestellt hatte, aber nahe genug, um zu zeigen, dass die Theorie gut ist.

15
Martin Harris

Ja, es gibt sicherlich einen besseren Weg. Hier ist es:

Schritt für Schritt schrumpfte das Tutorial von mir, basierend auf diesem nützlicher Artikel .

  • Neues Projekt erstellen
  • Installieren Sie das Schlosskernpaket im Projekt

Installationspaket für Castle.Core

  • Installieren Sie nur mvvm light-Bibliotheken

Installationspaket MvvmLightLibs

  • Fügen Sie im 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 Ihr Ansichtsmodell, zum Beispiel:

-

 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 die Codezeile wie folgt in die Code-Behind-Datei MainWindow.xaml.cs ein:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • Genießen.

enter image description here

Achtung !!! Alle beschränkten Eigenschaften sollten mit dem Schlüsselwort virtual dekoriert werden, da sie vom Burg-Proxy zum Überschreiben verwendet werden.

11
testCoder

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

Hinzufügen von INotifyPropertyChanged zu einem vorhandenen Objekt

6
HokieMike

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

Es ist in deutscher Sprache verfasst, aber Sie können die ViewModelBase.cs herunterladen. Alle Kommentare in der cs-Datei sind in englischer Sprache verfasst.

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

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

Basierend auf der Antwort von Thomas, die aus einer Antwort von Marc angepasst wurde, habe ich den Code für die reflektierende Eigenschaft geändert 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 entspricht der Antwort von Thomas, mit der Ausnahme, dass Sie zusätzliche Eigenschaften zur 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 damit eine Sammlung von Elementen in einer BindingList gespeichert, die über eine DataGridView verfügbar gemacht wird. Damit entfällt die Notwendigkeit, manuelle Refresh () - Aufrufe an das Grid durchzuführen.

4
StuffOfInterest

Lassen Sie mich meinen eigenen Ansatz namens Yappi ..__ vorstellen. Er gehört zu Runtime-Proxy-Klassengeneratoren und fügt einem vorhandenen Objekt oder Typ neue Funktionen hinzu, wie beispielsweise dem Dynamic Proxy von Caste Project.

Es erlaubt INotifyPropertyChanged einmal in der Basisklasse zu implementieren und abgeleitete Klassen im folgenden Stil zu deklarieren, wobei INotifyPropertyChanged weiterhin für neue Eigenschaften unterstützt wird:

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

Die Komplexität abgeleiteter Klassen- oder Proxy-Konstruktionen kann hinter der folgenden Zeile verborgen werden:

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

Und alle INotifyPropertyChanged-Implementierungsarbeiten können folgendermaßen 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 das Refactoring, verwendet keine Reflexion nach dem Typaufbau und ist schnell genug.

4
Kelqualyn

Alle diese Antworten sind sehr nett.

Meine Lösung verwendet die Codeausschnitte, um die Arbeit zu erledigen.

Hierbei wird der einfachste Aufruf des PropertyChanged-Ereignisses verwendet.

Speichern Sie dieses Snippet und verwenden Sie es, wenn Sie das 'Fullprop'-Snippet verwenden.

den Speicherort finden Sie im Menü "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 Anruf nach Belieben ändern (um die oben genannten Lösungen zu verwenden)

3
Ofir

Ich habe einen Artikel geschrieben, der dabei hilft ( https://msdn.Microsoft.com/magazine/mt736453 ). Sie können das SolSoft.DataBinding NuGet-Paket verwenden. Dann können Sie Code wie folgt schreiben:

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

Leistungen:

  1. basisklasse ist optional
  2. keine Reflexion über jeden 'eingestellten Wert'
  3. kann Eigenschaften haben, die von anderen Eigenschaften abhängen, und sie lösen automatisch die entsprechenden Ereignisse aus (Artikel hat ein Beispiel dafür)
2
Mark Sowul

Hier ist eine Unity3D- oder Nicht-CallerMemberName-Version von NotifyPropertyChanged

public abstract class Bindable : MonoBehaviour, INotifyPropertyChanged
{
    private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();
    private static readonly StackTrace stackTrace = new StackTrace();
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    ///     Resolves a Property's name from a Lambda Expression passed in.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="property"></param>
    /// <returns></returns>
    internal string GetPropertyName<T>(Expression<Func<T>> property)
    {
        var expression = (MemberExpression) property.Body;
        var propertyName = expression.Member.Name;

        Debug.AssertFormat(propertyName != null, "Bindable Property shouldn't be null!");
        return propertyName;
    }

    #region Notification Handlers

    /// <summary>
    ///     Notify's all other objects listening that a value has changed for nominated propertyName
    /// </summary>
    /// <param name="propertyName"></param>
    internal void NotifyOfPropertyChange(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    /// <summary>
    ///     Notifies subscribers of the property change.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="property">The property expression.</param>
    internal void NotifyOfPropertyChange<TProperty>(Expression<Func<TProperty>> property)
    {
        var propertyName = GetPropertyName(property);
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Raises the <see cref="PropertyChanged" /> event directly.
    /// </summary>
    /// <param name="e">The <see cref="PropertyChangedEventArgs" /> instance containing the event data.</param>
    internal void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    #endregion

    #region Getters

    /// <summary>
    ///     Gets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);
        return Get<T>(GetPropertyName(property));
    }

    /// <summary>
    ///     Gets the value of a property automatically based on its caller.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    internal T Get<T>()
    {
        var name = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        return Get<T>(name);
    }

    /// <summary>
    ///     Gets the name of a property based on a string.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="name"></param>
    /// <returns></returns>
    internal T Get<T>(string name)
    {
        object value = null;
        if (_properties.TryGetValue(name, out value))
            return value == null ? default(T) : (T) value;
        return default(T);
    }

    #endregion

    #region Setters

    /// <summary>
    ///     Sets the value of a property whilst automatically looking up its caller name.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    internal void Set<T>(T value)
    {
        var propertyName = stackTrace.GetFrame(1).GetMethod().Name.Substring(4); // strips the set_ from name;
        Set(value, propertyName);
    }

    /// <summary>
    ///     Sets the value of a property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="name"></param>
    internal void Set<T>(T value, string propertyName)
    {
        Debug.Assert(propertyName != null, "name != null");
        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    /// <summary>
    ///     Sets the value of a property based off an Expression (()=>FieldName)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="value"></param>
    /// <param name="property"></param>
    internal void Set<T>(T value, Expression<Func<T>> property)
    {
        var propertyName = GetPropertyName(property);

        Debug.Assert(propertyName != null, "name != null");

        if (Equals(value, Get<T>(propertyName)))
            return;
        _properties[propertyName] = value;
        NotifyOfPropertyChange(propertyName);
    }

    #endregion
}

Dieser Code ermöglicht das Schreiben von Eigenschaftsfeldern wie folgt:

  public string Text
    {
        get { return Get<string>(); }
        set { Set(value); }
    }

Darüber hinaus können Sie in resharper beim Erstellen eines Muster-/Such-Snippets auch Ihren Arbeitsablauf automatisieren, indem Sie einfache Prop-Felder in die obige Unterstützung konvertieren.

Suchmuster:

public $type$ $fname$ { get; set; }

Muster ersetzen:

public $type$ $fname$
{
    get { return Get<$type$>(); }
    set { Set(value); }
}
2
Scott Barnes

Wenn Sie in .NET 4.5 Dynamik verwenden, müssen Sie sich keine Gedanken über INotifyPropertyChanged machen. 

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

wenn Name an ein Steuerelement gebunden ist, funktioniert es einfach gut. 

2
Dilshod

Ich habe eine Erweiterungsmethode in meiner Basisbibliothek zur Wiederverwendung erstellt:

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

Dies funktioniert mit .Net 4.5 wegen CallerMemberNameAttribute . Wenn Sie es mit einer früheren .Net-Version verwenden möchten, müssen Sie die Methodendeklaration von ...,[CallerMemberName] string propertyName = "", ... in ...,string propertyName, ... ändern.

Verwendungszweck:

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

    public string Name
    {
        get { return _name; }
        set
        {
            this.SetPropertyAndNotify(PropertyChanged, ref _name, value);
        }
    }
}
2
giammin

Ich halte das hier als Ausschnitt. C # 6 fügt einige Nice-Syntax hinzu, um den Handler aufzurufen.

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

Eine andere kombinierte Lösung verwendet 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));
        }
    }
}

Verwendungszweck:

public class TempVM : BaseViewModel
{
    private int _intP;
    public int IntP
    {
        get { return _intP; }
        set { Set<int>(ref _intP, value); }
    }
}
1
Ofir

Mir ist klar, dass diese Frage bereits eine Fülle von Antworten hat, aber keine von ihnen fühlte sich für mich richtig an. Mein Thema ist, dass ich keine Performance-Hits will und ich bin bereit, aus diesem Grund ein wenig Ausführlichkeit zu ertragen. Ich interessiere mich auch nicht besonders für die automatischen Eigenschaften, was mich zu folgender Lösung führte:

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()
    {
    }
}

Mit anderen Worten, die obige Lösung ist praktisch, wenn Sie dies nicht stört:

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()
    {
    }
}

Pros

  • Keine Reflexion
  • Meldet nur, wenn der alte Wert! = Neuer Wert ist
  • Mehrere Eigenschaften gleichzeitig benachrichtigen

Nachteile

  • Keine automatischen Eigenschaften (Sie können jedoch Unterstützung für beide hinzufügen!)
  • Etwas Ausführlichkeit
  • Boxen (kleiner Leistungstreffer?)

Leider ist es immer noch besser als dies zu tun,

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

Für jede einzelne Eigenschaft, die mit der zusätzlichen Ausführlichkeit zum Albtraum wird ;-(

Beachten Sie, ich behaupte nicht, dass diese Lösung im Vergleich zu den anderen eine bessere Leistung darstellt, nur dass sie eine praktikable Lösung für diejenigen ist, die die anderen vorgestellten Lösungen nicht mögen.

1
James M

Ich habe mir diese Basisklasse ausgedacht, um das beobachtbare Muster zu implementieren, tut ziemlich genau das, was Sie brauchen ( "automatisch" Implementieren des Sets und Holen). Als Prototyp habe ich eine Stunde damit verbracht, so dass es nicht viele Komponententests gibt, aber das Konzept beweist. Beachten Sie, dass mit Dictionary<string, ObservablePropertyContext> keine privaten Felder benötigt werden.

  public class ObservableByTracking<T> : IObservable<T>
  {
    private readonly Dictionary<string, ObservablePropertyContext> _expando;
    private bool _isDirty;

    public ObservableByTracking()
    {
      _expando = new Dictionary<string, ObservablePropertyContext>();

      var properties = this.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
      foreach (var property in properties)
      {
        var valueContext = new ObservablePropertyContext(property.Name, property.PropertyType)
        {
          Value = GetDefault(property.PropertyType)
        };

        _expando[BuildKey(valueContext)] = valueContext;
      }
    }

    protected void SetValue<T>(Expression<Func<T>> expression, T value)
    {
      var keyContext = GetKeyContext(expression);
      var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);

      if (!_expando.ContainsKey(key))
      {
        throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
      }

      var originalValue = (T)_expando[key].Value;
      if (EqualityComparer<T>.Default.Equals(originalValue, value))
      {
        return;
      }

      _expando[key].Value = value;
      _isDirty = true;
    }

    protected T GetValue<T>(Expression<Func<T>> expression)
    {
      var keyContext = GetKeyContext(expression);
      var key = BuildKey(keyContext.PropertyName, keyContext.PropertyType);

      if (!_expando.ContainsKey(key))
      {
        throw new Exception($"Object doesn't contain {keyContext.PropertyName} property.");
      }

      var value = _expando[key].Value;
      return (T)value;
    }

    private KeyContext GetKeyContext<T>(Expression<Func<T>> expression)
    {
      var castedExpression = expression.Body as MemberExpression;
      if (castedExpression == null)
      {
        throw new Exception($"Invalid expression.");
      }

      var parameterName = castedExpression.Member.Name;

      var propertyInfo = castedExpression.Member as PropertyInfo;
      if (propertyInfo == null)
      {
        throw new Exception($"Invalid expression.");
      }

      return new KeyContext {PropertyType = propertyInfo.PropertyType, PropertyName = parameterName};
    }

    private static string BuildKey(ObservablePropertyContext observablePropertyContext)
    {
      return $"{observablePropertyContext.Type.Name}.{observablePropertyContext.Name}";
    }

    private static string BuildKey(string parameterName, Type type)
    {
      return $"{type.Name}.{parameterName}";
    }

    private static object GetDefault(Type type)
    {
      if (type.IsValueType)
      {
        return Activator.CreateInstance(type);
      }
      return null;
    }

    public bool IsDirty()
    {
      return _isDirty;
    }

    public void SetPristine()
    {
      _isDirty = false;
    }

    private class KeyContext
    {
      public string PropertyName { get; set; }
      public Type PropertyType { get; set; }
    }
  }

  public interface IObservable<T>
  {
    bool IsDirty();
    void SetPristine();
  }

Hier ist die Verwendung

public class ObservableByTrackingTestClass : ObservableByTracking<ObservableByTrackingTestClass>
  {
    public ObservableByTrackingTestClass()
    {
      StringList = new List<string>();
      StringIList = new List<string>();
      NestedCollection = new List<ObservableByTrackingTestClass>();
    }

    public IEnumerable<string> StringList
    {
      get { return GetValue(() => StringList); }
      set { SetValue(() => StringIList, value); }
    }

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

    public int IntProperty
    {
      get { return GetValue(() => IntProperty); }
      set { SetValue(() => IntProperty, value); }
    }

    public ObservableByTrackingTestClass NestedChild
    {
      get { return GetValue(() => NestedChild); }
      set { SetValue(() => NestedChild, value); }
    }

    public IList<ObservableByTrackingTestClass> NestedCollection
    {
      get { return GetValue(() => NestedCollection); }
      set { SetValue(() => NestedCollection, value); }
    }

    public string StringProperty
    {
      get { return GetValue(() => StringProperty); }
      set { SetValue(() => StringProperty, value); }
    }
  }
1
Homero Barbosa

Eine Idee mit Reflektion:

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

Andere Dinge, die Sie bei der Implementierung dieser Arten von Eigenschaften berücksichtigen sollten, sind die Tatsache, dass die INotifyPropertyChang * -Eingabe beide Ereignisargumentenklassen verwendet.

Wenn eine große Anzahl von Eigenschaften festgelegt wird, kann die Anzahl der Ereignisargumentklasseninstanzen sehr groß sein. Sie sollten das Zwischenspeichern dieser Eigenschaften in Betracht ziehen, da dies einer der Bereiche ist, in denen eine Zeichenfolgenexplosion auftreten kann.

Schauen Sie sich diese Implementierung an und erklären Sie, warum sie konzipiert wurde.

Josh Smiths Blog

1
Peter

Ich schlage vor, ReactiveProperty zu verwenden ... Dies ist die kürzeste Methode außer Fody.

public class Data : INotifyPropertyChanged
{
    // boiler-plate
    ...
    // props
    private string name;
    public string Name
    {
        get { return name; }
        set { SetField(ref name, value, "Name"); }
    }
}

stattdessen

public class Data
{
    // Don't need boiler-plate and INotifyPropertyChanged

    // props
    public ReactiveProperty<string> Name { get; } = new ReactiveProperty<string>();
}

( DOCS )

1
soi

Obwohl es offensichtlich viele Möglichkeiten gibt, dies zu tun, mit Ausnahme der AOP-Zauberantworten scheint keine der Antworten darauf zu achten, dass die Eigenschaften eines Modells direkt aus dem Ansichtsmodell festgelegt werden, ohne dass ein lokales Feld als Referenz verwendet werden muss.

Das Problem ist, dass Sie keine Eigenschaft referenzieren 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 Code-Auszug verwendet werden.

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

In diesem BitBucket-Repo finden Sie eine vollständige Implementierung der Methode und einige Möglichkeiten, um dasselbe Ergebnis zu erzielen, einschließlich einer Methode mit LINQ und einer Methode mit Reflektion. Beachten Sie, dass diese Methoden in Bezug auf die Leistung langsamer sind.

1
Dan

=> hier meine Lösung mit folgenden Funktionen

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. keine Verweisung
  2. kurze notation
  3. keine magische Zeichenfolge in Ihrem Geschäftscode
  4. Wiederverwendbarkeit von PropertyChangedEventArgs in der gesamten Anwendung
  5. Möglichkeit, mehrere Eigenschaften in einer Anweisung zu benachrichtigen
0
Bruno

Benutze das

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

}

0
Dude505

Eine andere Idee...

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

Ich habe gerade gefunden ActiveSharp - Automatic INotifyPropertyChanged , ich muss es noch verwenden, aber es sieht gut aus.

Um von seiner Website zu zitieren ...


Senden Sie Benachrichtigungen zu Eigenschaftsänderungen ohne den Eigenschaftennamen als .__ anzugeben. Schnur.

Schreiben Sie stattdessen Eigenschaften wie folgt:

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

Beachten Sie, dass der Name der Eigenschaft nicht als Zeichenfolge angegeben werden muss. ActiveSharp erkennt dies zuverlässig und korrekt. Es basiert auf der Tatsache, dass Ihre Eigenschaftsimplementierung das Hintergrundfeld (_foo) mit ref übergibt. (ActiveSharp verwendet diesen Aufruf "by ref", um zu ermitteln, welches Backing-Feld übergeben wurde, und aus dem Feld wird die Eigenschaft identifiziert.).

0
Ian Ringrose

Ich verwende die folgende Erweiterungsmethode (mit C # 6.0), um die INPC-Implementierung so einfach wie möglich zu gestalten:

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

Die INPC-Implementierung läuft darauf hinaus (Sie können dies entweder jedes Mal implementieren oder eine Basisklasse erstellen):

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

Dann schreibe deine Eigenschaften so:

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

HINWEIS: Sie können die [CallerMemberName]-Deklaration in der Erweiterungsmethode weglassen, wenn Sie möchten, aber ich wollte, dass sie flexibel bleibt.

Wenn Sie Eigenschaften ohne Sicherungsfeld haben, können Sie changeProperty überladen:

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

Ein Beispiel wäre:

public string MyTestProperty
{
    get { return base.TestProperty; }
    set { changeProperty(base.TestProperty, (x) => { base.TestProperty = x; }, value); }
}
0
Tim Pohlmann

Ich schreibe eine Bibliothek, die sich mit INotifyPropertyChanged beschäftigt, und die Hauptidee ist die Verwendung eines dynamischen Proxys, um Änderungen zu melden.

das Repo ist hier: CaulyKan/NoMorePropertyChanged

mit dieser Bibliothek können Sie schreiben:

    public dynamic Test1Binding { get; set; }
    public TestDTO Test1
    {
        get { return (TestDTO)Test1Binding; }
        set { SetBinding(nameof(Test1Binding), value); }
    }

Nehmen Sie dann alle Bindungen und Änderungen für Test1Binding vor, wodurch PropertyChange und CollectionChanged automatisch benachrichtigt werden, unabhängig davon, wie komplex TestDTO ist.

es kann auch Abhängigkeiten behandeln.

    [DependsOn("Test1Binding.TestString")]
    public string Test2
    {
        get { return Test1Binding.TestString; }
    }

Bitte geben Sie mir einige Vorschläge.

0
Cauly

Ich habe mich auf diese Weise gelöst (es ist etwas mühsam, aber es ist sicherlich schneller in der Laufzeit).

In VB (sorry, aber ich denke, es ist nicht schwer, es in C # zu übersetzen), mache ich diese Ersetzung mit RE:

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

mit:

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

Dieser transofrm alles Code wie folgt:

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

Im

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

Und wenn ich einen besser lesbaren Code haben möchte, kann ich das Gegenteil machen, indem ich folgende Ersetzung mache:

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

Mit

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

Ich werde den IL-Code der set-Methode ersetzen, aber ich kann nicht viel kompilierten Code in IL schreiben ... Wenn ich ihn eines Tages schreibe, sage ich Sie!

0
Lucio Menci

Prism 5-Implementierung:

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

    protected virtual bool SetProperty<T>(ref T storage,
                                          T value,
                                          [CallerMemberName] string propertyName = null)
    {
        if (object.Equals(storage, value)) return false;

        storage = value;
        this.OnPropertyChanged(propertyName);

        return true;
    }

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

    protected void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
    {
        var propertyName = PropertySupport.ExtractPropertyName(propertyExpression);
        this.OnPropertyChanged(propertyName);
    }
}

public static class PropertySupport
{
    public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
    {
        if (propertyExpression == null)
        {
            throw new ArgumentNullException("propertyExpression");
        }

        var memberExpression = propertyExpression.Body as MemberExpression;
        if (memberExpression == null)
        {
            throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
        }

        var property = memberExpression.Member as PropertyInfo;
        if (property == null)
        {
            throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
        }

        var getMethod = property.GetMethod;
        if (getMethod.IsStatic)
        {
            throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
        }

        return memberExpression.Member.Name;
    }
}
0
Jeson Martajaya