web-dev-qa-db-fra.com

Implémentation de INotifyPropertyChanged - existe-t-il un meilleur moyen?

Microsoft aurait dû implémenter quelque chose de vif pour INotifyPropertyChanged, comme dans les propriétés automatiques, il suffit de spécifier {get; set; notify;} Je pense que cela a beaucoup de sens de le faire. Ou y a-t-il des complications pour le faire? 

Pouvons-nous nous-mêmes mettre en œuvre quelque chose comme «notifier» dans nos propriétés. Existe-t-il une solution élégante pour implémenterINotifyPropertyChangeddans votre classe ou le seul moyen de le faire est de déclencher l'événement PropertyChanged dans chaque propriété.

Sinon, pouvons-nous écrire quelque chose pour générer automatiquement le morceau de code afin de générer l'événement PropertyChanged?

588
P.K

Sans utiliser quelque chose comme postsharp, la version minimale que j'utilise utilise quelque chose comme:

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

Chaque propriété est alors juste quelque chose comme:

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

ce qui n'est pas énorme; il peut également être utilisé comme classe de base si vous le souhaitez. Le bool retour de SetField vous indique s'il s'agissait d'un no-op, au cas où vous souhaiteriez appliquer une autre logique.


ou encore plus facile avec C # 5:

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

qui peut s'appeler comme ceci:

set { SetField(ref name, value); }

avec lequel le compilateur ajoutera le "Name" automatiquement.


C # 6.0 facilite la mise en oeuvre:

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

... et maintenant avec C # 7:

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

À partir de .Net 4.5, il existe enfin un moyen simple de le faire.

.Net 4.5 introduit de nouveaux attributs d’information sur l’appelant.

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

C'est probablement une bonne idée d'ajouter un comparateur à la fonction également.

EqualityComparer<T>.Default.Equals

Plus d'exemples ici et ici

Voir aussi Informations sur l’appelant (C # et Visual Basic)

190
Daniel Little

J'aime beaucoup la solution de Marc, mais je pense qu'elle peut être légèrement améliorée pour éviter d'utiliser une "chaîne magique" (qui ne prend pas en charge la refactorisation). Au lieu d'utiliser le nom de la propriété en tant que chaîne, il est facile d'en faire une expression lambda:

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

Ajoutez simplement les méthodes suivantes au code de Marc, cela fera l'affaire:

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, cela a été inspiré par  cet article de blog URL mise à jour

160
Thomas Levesque

Il y a aussi Fody qui a un PropertyChanged add-in, qui vous permet d'écrire ceci:

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

... et au moment de la compilation, injecte des notifications de propriétés modifiées.

109
Tom Gilder

Je pense que les gens devraient prêter un peu plus attention aux performances. Cela a vraiment un impact sur l'interface utilisateur lorsqu'il y a beaucoup d'objets à lier (pensez à une grille de plus de 10 000 lignes) ou si la valeur de l'objet change fréquemment (application de surveillance en temps réel). .

J'ai pris diverses implémentations trouvées ici et ailleurs et ai fait une comparaison, vérifiez-la comparaison de performances d'implémentations INotifyPropertyChanged .


Voici un aperçu du résultat Implemenation vs Runtime

62
Peijen

J'introduis une classe Bindable dans mon blog à http://timoch.com/blog/2013/08/annoyed-with-inotifypropertychange/ Bindable utilise un dictionnaire comme propriété. Il est assez facile d'ajouter les surcharges nécessaires pour qu'une sous-classe puisse gérer son propre champ de sauvegarde à l'aide de paramètres ref.

  • Pas de corde magique
  • Pas de réflexion
  • Peut être amélioré pour supprimer la recherche de dictionnaire par défaut

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

Il peut être utilisé comme ceci:

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

Je n'ai pas encore eu l'occasion de l'essayer moi-même, mais la prochaine fois que je configurerai un projet avec une grande exigence pour INotifyPropertyChanged, j'ai l'intention d'écrire un attribut Postsharp qui injectera le code à la compilation. temps. Quelque chose comme:

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

Va devenir:

private string _firstName;

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

Je ne sais pas si cela fonctionnera dans la pratique et je dois m'asseoir et l'essayer, mais je ne vois pas pourquoi. J'aurai peut-être besoin de lui faire accepter certains paramètres pour les situations dans lesquelles plus d'un OnPropertyChanged doit être déclenché (si, par exemple, j'avais une propriété FullName dans la classe ci-dessus)

Actuellement, j'utilise un modèle personnalisé dans Resharper, mais même avec cela, j'en ai marre que toutes mes propriétés soient si longues.


Ah, une rapide recherche sur Google (que j’aurais dû faire avant d’écrire cela) montre qu’au moins une personne a déjà fait quelque chose comme cela avant ici . Pas exactement ce que j'avais en tête, mais suffisamment proche pour montrer que la théorie est bonne.

15
Martin Harris

Oui, le meilleur moyen existe certainement. C'est ici:

Le tutoriel pas à pas a rétréci, basé sur ceci article utile .

  • Créer un nouveau projet
  • Installer le package principal du château dans le projet

Package d'installation Castle.Core

  • Installer les bibliothèques de lumière mvvm uniquement

Paquet d'installation MvvmLightLibs

  • Ajoutez deux classes dans le projet:

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;
    }
}
  • Créez votre modèle de vue, par exemple:

-

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

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

        public void TestAction()
        {
            Trace.WriteLine(MainTextBox);
        }
    }
  • Mettez les liaisons dans xaml:

    <TextBox Text="{Binding MainTextBox}" ></TextBox>
    <Button Command="{Binding TestActionCommand}" >Test</Button>
    
  • Mettez la ligne de code dans le fichier code-behind MainWindow.xaml.cs comme ceci:

DataContext = ProxyCreator.MakeINotifyPropertyChanged<MainViewModel>();

  • Prendre plaisir.

enter image description here

Attention!!! Toutes les propriétés délimitées doivent être décorées avec le mot clé virtual car elles ont été utilisées par le proxy Castle pour les remplacer.

11
testCoder

Une approche très similaire à AOP consiste à injecter le contenu INotifyPropertyChanged sur un objet déjà instancié à la volée. Vous pouvez le faire avec quelque chose comme Castle DynamicProxy. Voici un article qui explique la technique:

Ajout de INotifyPropertyChanged à un objet existant

6
HokieMike

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

C'est écrit en allemand, mais vous pouvez télécharger le ViewModelBase.cs. Tous les commentaires dans le fichier cs sont écrits en anglais.

Avec cette classe ViewModelBase, il est possible d'implémenter des propriétés pouvant être liées similaires aux propriétés de dépendance bien connues:

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

D'après la réponse de Thomas qui a été adaptée d'une réponse de Marc, j'ai transformé le code de la propriété de réflexion reflétée en une classe de base:

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

L'utilisation est la même que celle de Thomas, sauf que vous pouvez transmettre des propriétés supplémentaires à notifier. Cela était nécessaire pour gérer les colonnes calculées qui doivent être actualisées dans une grille.

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

J'ai cette conduite une collection d'éléments stockés dans une BindingList exposée via un DataGridView. Cela m'a évité de faire des appels manuels à Refresh () sur la grille.

4
StuffOfInterest

Permettez-moi de présenter ma propre approche appelée Yappi . Elle appartient aux générateurs de classes dérivées proxy, ajoutant de nouvelles fonctionnalités à un objet ou à un type existant, comme le proxy dynamique de Caste Project.

Cela permet d'implémenter INotifyPropertyChanged une fois dans la classe de base, puis de déclarer les classes dérivées dans le style suivant, tout en prenant en charge INotifyPropertyChanged pour les nouvelles propriétés:

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

La complexité de la construction de classe ou de proxy dérivée peut être cachée derrière la ligne suivante:

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

Et tout le travail d'implémentation INotifyPropertyChanged peut être fait comme ceci:

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

Il est totalement sûr pour le refactoring, n'utilise pas de réflexion après la construction du type et assez rapidement.

4
Kelqualyn

Toutes ces réponses sont très gentilles.

Ma solution utilise les extraits de code pour faire le travail.

Ceci utilise l'appel le plus simple à l'événement PropertyChanged.

Enregistrez cet extrait et utilisez-le lorsque vous utilisez l'extrait 'fullprop'.

l'emplacement se trouve dans le menu "Outils\Gestionnaire d'extraits de code ..." de 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>

Vous pouvez modifier l'appel à votre guise (pour utiliser les solutions ci-dessus)

3
Ofir

J'ai écrit un article qui aide à cela ( https://msdn.Microsoft.com/magazine/mt736453 ). Vous pouvez utiliser le package SolSoft.DataBinding NuGet. Ensuite, vous pouvez écrire le code comme ceci:

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

Avantages:

  1. la classe de base est facultative
  2. pas de reflet sur chaque 'valeur définie'
  3. peut avoir des propriétés qui dépendent d'autres propriétés et elles déclenchent toutes automatiquement les événements appropriés (cet exemple en est un article)
2
Mark Sowul

Voici une version Unity3D ou non-CallerMemberName de 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
}

Ce code vous permet d'écrire des champs de sauvegarde de propriété comme ceci:

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

En outre, si vous créez un extrait de modèle/de recherche, vous pouvez également automatiser votre flux de travail en convertissant des champs prop simples dans le support ci-dessus.

Modèle de recherche:

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

Remplacer le motif:

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

Si vous utilisez Dynamics dans .NET 4.5, vous n'avez pas à vous soucier de INotifyPropertyChanged

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

si Name est lié à un contrôle quelconque, cela fonctionne parfaitement. 

2
Dilshod

J'ai créé une méthode d'extension dans ma bibliothèque de base pour la réutilisation:

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

Cela fonctionne avec .Net 4.5 à cause de CallerMemberNameAttribute . Si vous voulez l'utiliser avec une version antérieure de .Net, vous devez changer la déclaration de la méthode de: ...,[CallerMemberName] string propertyName = "", ... à ...,string propertyName, ...

Usage:

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

Je garde cela comme un extrait. C # 6 ajoute de la syntaxe Nice pour appeler le gestionnaire.

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

Une autre solution combinée utilise 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));
        }
    }
}

Usage:

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

Je me rends compte que cette question a déjà des dizaines de milliards de réponses, mais aucune d’entre elles ne me convenait parfaitement. Mon problème est que je ne veux pas de succès et que je suis prêt à accepter un peu de verbosité pour cette seule raison. Je ne me soucie pas non plus trop des propriétés automatiques, ce qui m'a amené à la solution suivante:

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

En d'autres termes, la solution ci-dessus est pratique si cela ne vous dérange pas:

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

Avantages

  • Pas de réflexion
  • Ne notifie que si ancienne valeur! = Nouvelle valeur
  • Notifier plusieurs propriétés à la fois

Les inconvénients

  • Aucune propriété automatique (vous pouvez ajouter un support pour les deux, cependant!)
  • Un peu de verbosité
  • Boxe (petite performance?)

Hélas, il vaut toujours mieux que cela,

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

Pour chaque propriété unique, ce qui devient un cauchemar avec la verbosité supplémentaire ;-(

Notez que je ne prétends pas que cette solution est meilleure en termes de performances que les autres, mais simplement que c'est une solution viable pour ceux qui n'aiment pas les autres solutions présentées.

1
James M

Je suis venu avec cette classe de base pour implémenter le modèle observable, fait à peu près ce dont vous avez besoin ( "automatiquement" implémentant le set et get). J'ai passé une heure en ligne sur ce prototype, de sorte qu'il ne comporte pas beaucoup de tests unitaires, mais prouve le concept. Notez qu'il utilise le Dictionary<string, ObservablePropertyContext> pour supprimer le besoin de champs privés.

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

Voici l'utilisation

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

Une idée en utilisant la réflexion:

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

Il est également utile de prendre en compte l’implémentation de ce type de propriétés dans le fait que INotifyPropertyChang * ed * ing utilise les deux classes d’argument d’événement.

Si vous définissez un grand nombre de propriétés, le nombre d'instances de classe d'arguments d'événement peut être énorme. Vous devez envisager de les mettre en cache, car elles constituent l'une des zones pouvant provoquer une explosion de chaîne.

Jetez un coup d'œil à cette implémentation et expliquez-vous pourquoi elle a été conçue.

Blog de Josh Smiths

1
Peter

Je suggère d'utiliser ReactiveProperty . C'est la méthode la plus courte sauf Fody.

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

au lieu

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

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

( DOCS )

1
soi

Bien qu'il y ait évidemment beaucoup de façons de le faire, à l'exception des réponses magiques AOP, aucune ne semble vouloir définir la propriété d'un modèle directement à partir du modèle d'affichage sans avoir de champ local à référencer.

Le problème est que vous ne pouvez pas référencer une propriété. Cependant, vous pouvez utiliser une action pour définir cette propriété.

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

Ceci peut être utilisé comme l'extrait de code suivant.

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

Découvrez ce BitBucket repo pour une implémentation complète de la méthode et quelques manières différentes d’atteindre le même résultat, y compris une méthode qui utilise LINQ et une méthode qui utilise la réflexion. Notez que ces méthodes sont plus lentes en termes de performances.

1
Dan

=> ici ma solution avec les fonctionnalités suivantes

 public ResourceStatus Status
 {
     get { return _status; }
     set
     {
         _status = value;
         Notify(Npcea.Status,Npcea.Comments);
     }
 }
  1. pas de refelction
  2. notation courte
  3. pas de chaîne magique dans votre code d'entreprise
  4. Réutilisabilité de PropertyChangedEventArgs à travers l'application
  5. Possibilité de notifier plusieurs propriétés dans une seule déclaration
0
Bruno

Utilisez ceci

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

Une autre idée...

 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

Je viens de trouver ActiveSharp - Automatic INotifyPropertyChanged , je ne l'ai pas encore utilisé, mais ça a l'air bien.

Pour citer son site web ...


Envoyer des notifications de changement de propriété sans spécifier le nom de la propriété en tant que chaîne.

A la place, écrivez des propriétés comme celle-ci:

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

Notez qu'il n'est pas nécessaire d'inclure le nom de la propriété sous forme de chaîne. ActiveSharp le calcule correctement et de manière fiable. Cela fonctionne sur la base du fait que l'implémentation de votre propriété passe le champ de support (_foo) par réf. (ActiveSharp utilise cet appel "par référence" pour identifier le champ de sauvegarde qui a été transmis et identifie la propriété à partir du champ).

0
Ian Ringrose

J'utilise la méthode d'extension suivante (en utilisant C # 6.0) pour rendre la mise en oeuvre INPC aussi simple que possible:

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

L'implémentation INPC se résume à (vous pouvez l'implémenter à chaque fois ou créer une classe de base):

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

Ensuite, écrivez vos propriétés comme ceci:

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

Remarque: vous pouvez omettre la déclaration [CallerMemberName] dans la méthode d'extension, si vous le souhaitez, mais je voulais la garder flexible.

Si vous avez des propriétés sans champ de sauvegarde, vous pouvez surcharger 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;
}

Un exemple d'utilisation serait:

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

J'écris une bibliothèque qui traite avec INotifyPropertyChanged, et l'idée principale est d'utiliser un proxy dynamique pour notifier les modifications.

le repo est ici: CaulyKan/NoMorePropertyChanged

avec cette bibliothèque, vous pouvez écrire:

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

Effectuez ensuite toutes les liaisons et modifications dans Test1Binding, qui informera automatiquement PropertyChange et CollectionChanged, quelle que soit la complexité de TestDTO.

il peut aussi gérer des dépendances.

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

S'il vous plaît donnez-moi quelques suggestions.

0
Cauly

J'ai résolu dans This Way (c'est un peu laborieux, mais c'est sûrement le plus rapide dans l'exécution).

Dans VB (désolé, mais je pense que ce n'est pas difficile de le traduire en C #), je fais cette substitution avec RE:

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

avec:

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

Cela transforme tout le code comme ceci:

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

Dans

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

Et si je veux un code plus lisible, je peux être le contraire en effectuant la substitution suivante:

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

Avec

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

Je lance pour remplacer le code IL de la méthode set, mais je ne peux pas écrire beaucoup de code compilé en IL ... Si un jour je l'écris, je vous dirai!

0
Lucio Menci

Prisme 5 implémentation:

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