Simplifier l’écriture de INotifyPropertyChanged en C#

L’implémentation de l’interface INotifyPropertyChanged est très pratique lorsque l’on veut que notre Vue soit informée des changements des propriétés de notre Modèle. Cependant, il est long et rébarbatif d’écrire le setter pour chaque propriété.
Voici donc une idée d’implémentation automatisant tout cela…

Pour commencer, prenons une implémentation standard de INotifyPropertyChanged :

public class MaClasse : INotifyPropertyChanged {
	private string maPropriete;

	public string MaPropriete {
		get { return this.maPropriete; }
		set {
			if (this.maPropriete != value) {
				this.maPropriete = value;
				this.OnPropertyChanged("MaPropriete");
			}
		}
	}

	#region INotifyPropertyChanged
	public event PropertyChangedEventHandler PropertyChanged;

	public void OnPropertyChanged(string propertyName)
	{
		if (this.PropertyChanged != null)
		{
			this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
		}
	}
	#endregion
}

Dans cet exemple, ça serait parfois utile de pouvoir réduire au maximum l’impact de cette implémentation afin de pouvoir rapidement la modifier.
Dans l’idéal, j’aimerais pouvoir écrire ce qui suit :

public class MaClasse : NotifyPropertyChangedObject {

	[Notify]
	public string MaPropriete {
		get;
		set;
	}
}

L’attribut [Notify]

Commençons par créer l’attribut Notify qui permettra de repérer les propriétés à surveiller.

[AttributeUsage(AttributeTargets.Property)]
public class NotifyAttribute : Attribute
{
}

Je pense que ce n’est pas nécessaire d’en décrire le code ;).

NotifyPropertyChanged

La classe NotifyPropertyChanged contiendra toute la logique interprétant les attributs [Notify] mais aussi portera les méthodes et évènements de INotifyPropertyChanged.

Voici donc une base connue :

public class NotifyPropertyChangedObject : INotifyPropertyChanged
{
	public void OnPropertyChanged(string propertyName)
	{
		if (this.PropertyChanged != null)
		{
			this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
		}
	}

	public event PropertyChangedEventHandler PropertyChanged;
}

Ajoutons maintenant le code qui va découvrir les attributs

var prop = from p in this.GetType().GetProperties()
		   where p.GetCustomAttributes(typeof(NotifyAttribute), true).Any()
		   select new p.Name;

values = new Dictionary<string, dynamic>();
prop.ToList().ForEach(p => properties.Add(p, null));

Ici values contient un Dictionary<string, dynamic> qui va permettre de retrouver rapidement les propriétés et leur valeur.

Malheureusement, il n’est pas possible, enfin, je n’ai pas trouvé, comment simplifier jusqu’à obtenir ce que je voulais au début… pour cela, il faut créer une BuildTask pour MSBuild. Je préfère privilégier une solution 100% C#.

Pour cela, je vais utiliser 2 méthodes : GetValue et SetValue à la manière de ce qui existe en Silverlight et WPF et les DependencyProperty. Elles seront définies comme suit :

public T GetValue<T>(string key, T defaultValue = default(T)) {
	if (values.ContainsKey(key)) {
		return (T)values[key];
	}
	return defaultValue;
}

public void SetValue(string key, dynamic value) {
	if (!values.ContainsKey(key))
	{
		values.Add(key, value);
		this.OnPropertyChanged(key);
	}
	else
	{
		if (values[key] != value) {
			values[key] = value;
			this.OnPropertyChanged(key);
		}
	}
}

Rien de bien compliqué… c’est même plutôt très simple !

Résumons !

Donc il nous faut une classe NotifyAttribute qui définit notre attribute [Notify], vous retrouverez le code complet plus haut, et une classe NotifyPropertyChangedObject définit cette fois-ci comme ceci :

public class NotifyPropertyChangedObject : INotifyPropertyChanged
{
	#region NotifyAttribute
	private static Dictionary<string, dynamic> values;
	private static bool isAlreadyInitialize = false;

	private void InitializeNotifyAttributes()
	{
		if (isAlreadyInitialize)
		{
			return;
		}

		var prop = from p in this.GetType().GetProperties()
				   where p.GetCustomAttributes(typeof(NotifyAttribute), true).Any()
				   select new { p.Name, PropertyInfo = p };

		values = new Dictionary<string, dynamic>();
		prop.ToList().ForEach(p => values.Add(p.Name, p.PropertyInfo));

		isAlreadyInitialize = true;
	}

	public T GetValue<T>(string key, T defaultValue = default(T)) {
		InitializeNotifyAttributes();
		if (values.ContainsKey(key)) {
			return (T)values[key];
		}
		return defaultValue;
	}

	public void SetValue(string key, dynamic value) {
		InitializeNotifyAttributes();
		if (!values.ContainsKey(key))
		{
			values.Add(key, value);
			this.OnPropertyChanged(key);
		}
		else
		{
			if (values[key] != value) {
				values[key] = value;
				this.OnPropertyChanged(key);
			}
		}
	}
	#endregion

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

	public event PropertyChangedEventHandler PropertyChanged;
}

Notez l’utilisation de InitializeNotifyAttributes qui permet de charger une seule fois la liste des attributs au premier appel des méthodes GetValue et SetValue.

Pour finir, voici le code de la classe que nous avions au départ :

public class MaClasse : NotifyPropertyChangedObject {
	[Notify]
	public string MaPropriete {
		get { return base.GetValue<string>("MaPropriete"); }
		set { SetValue("MaPropriete", value); }
	}
}

Et voilà ! Qu’en pensez-vous ?

Mise à jour

Je vous propose une mise à jour histoire d’aller un peu plus loin.

Ajoutons une propriété NotifyProperty qui contiendra le nom de la propriété dont nous souhaitons notifier le changement.

[AttributeUsage(AttributeTargets.Property, AllowMultiple=true)]
public class NotifyAttribute : Attribute
{
	public NotifyAttribute() { }

	public NotifyAttribute(string notifyProperty) {
		this.NotifyProperty = notifyProperty;
	}

	public string NotifyProperty { get; set; }
}

Puis, dans NotifyPropertyChangedObject, on modifie la récupération des attributs…

public class NotifyPropertyChangedObject : INotifyPropertyChanged
{
	private Dictionary<string, dynamic> values;

	public NotifyPropertyChangedBaseObject()
	{
		values = new Dictionary<string, dynamic>();
	}

	#region NotifyAttribute
	private static Dictionary<string, IEnumerable<string>> properties;
	private static bool isAlreadyInitialize = false;

	private void InitializeNotifyAttributes()
	{
		if (isAlreadyInitialize)
		{
			return;
		}

		// récupération des propriétés et des notifications
		var prop = from p in this.GetType().GetProperties()
				   where p.GetCustomAttributes(typeof(NotifyAttribute), true).Any()
				   select new {
					   p.Name,
					   Attributes = p.GetCustomAttributes(typeof(NotifyAttribute), true)
										.Cast<NotifyAttribute>()
										.Select(a=>string.IsNullOrEmpty(a.NotifyProperty) ? p.Name : a.NotifyProperty)
				   };

		properties = new Dictionary<string, IEnumerable<string>>();
		// création d'un dictionnaire
		prop.ToList().ForEach(p => properties.Add(p.Name, p.Attributes.ToList()));

		isAlreadyInitialize = true;
	}

	public void SetValue<TObject>(Expression<Func<TObject>> expression, dynamic value) {
		InitializeNotifyAttributes();
		var key = GetPropertyName(expression);
		SetValue(key, value);
	}

	public T GetValue<T>(string key, T defaultValue = default(T)) {
		InitializeNotifyAttributes();
		if (values.ContainsKey(key)) {
			return (T)values[key];
		}
		return defaultValue;
	}

	public void SetValue(string key, dynamic value) {
		InitializeNotifyAttributes();
		// si la valeur est différente de l'ancienne
		// on l'enregistre et on déclenche les notifications

		if (!values.ContainsKey(key))
		{
			values.Add(key, value);
			properties[key].ToList().ForEach(p => this.OnPropertyChanged(p));
		}
		else
		{
			if (values[key] != value) {
				values[key] = value;
				properties[key].ToList().ForEach(p => this.OnPropertyChanged(p));
			}
		}
	}
	#endregion

	public void OnPropertyChanged<T>(Expression<Func<T>> action)
	{
		var propertyName = GetPropertyName(action);
		OnPropertyChanged(propertyName);
	}

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

	private static string GetPropertyName<T>(Expression<Func<T>> action)
	{
		var expression = (MemberExpression)action.Body;
		var propertyName = expression.Member.Name;
		return propertyName;
	}

	private static PropertyInfo GetProperty<T>(Expression<Func<T>> action) {
		return typeof(T).GetProperty(GetPropertyName(action));
	}

	public event PropertyChangedEventHandler PropertyChanged;
}

Cette fois-ci, il est possible de mettre plusieurs attributs [Notify] sur une propriété afin de prévenir de la modification de plusieurs propriétés. Par exemple, votre classe possède 3 propriétés FirstName, LastName et FullName avec FullName définie comme une concaténation des 2 premières.
A la modification de FirstName et LastName, il faut aussi pouvoir notifier de la mise à jour de FullName :

public class Person : NotifyPropertyChangedObject {

	[Notify]
	[Notify("FullName")]
	public string FirstName {
		get { return GetValue<string>("FirstName"); }
		set { SetValue("FirstName", value); }
	}

	[Notify]
	[Notify("FullName")]
	public string LastName {
		get { return GetValue<string>("LastName"); }
		set { SetValue("LastName", value); }
	}

	public string FullName {
		get { return string.Concat(this.FirstName, " ", this.LastName); }
	}
}

Nombre de vue : 221

COMMENTAIRES 18 commentaires

  1. Arnaud Weil dit :

    Merci de partager, l’utilisation ressemble furieusement à ce l’idée que je répands depuis mes années auprès de mes stagiaires. Toute ressemblance est purement fortuite. 😉

    A première vue, le code utilisant la réflexion, ça peut poser des problèmes de performance. Un post-compilateur permettrait une meilleure performance.

  2. Merci Arnaud.

    J’essayerais de mettre à jour l’article en proposant la solution de “Pré-compilation”.

  3. Bill dit :

    Soit j’ai pas compris, soit j’aime pas du tout :-/
    En gros, tu dois créér une classe pour ton attribut, poser ton attribut (1ligne par Membre), refaire le get set avec deux fois des magic string, alors que de base, il suffit de faire dans le set un simple OnNotifyPropertyChanged(“MaPropriété”);

    donc trois lignes de code par membre, deux magic string au lieu d’une, et des perfs amoindries, ou alors tes modèles ont trois propriétés qui se battent en duel. Perso, sur un vrai projet WPF, vu la latence même de la techno, il est impensable d’implémenter des bottlenecks complémentaires.
    c’est quoi la plus-value au final vu que ton code est plus verbeux, même si c’est un poil plus lisible?

    d’ailleurs, tu te contredis en disant “rébarbatif d’écrire le setter pour chaque propriété” alors que là, t’es OBLIGé de le modifier à CHAQUE fois, y compris pour les auto-property, donc non vraiment, soit ton code est pas complet, soit en l’état, j’ai l’impression d’avoir un magnifique worst-practice basé pourtant sur une bonne idée.

  4. Bonjour,

    Je trouve dommage de se cacher derrière un pseudo et une fausse adresse email pour commenter sur un blog, il est de bon ton d’assumer ses propos en public.
    Toutefois, je vais répondre à tes questions :
    Oui, il est nécessaire de créer une classe pour mon attribut : cette classe peut servir dans tout un tas de projets… ce n’est donc pas forcément inutile. Ensuite, j’ajoute cet attribut aux propriétés qui notifient leurs changements, ce mot est important ici car je ne déclenche pas l’évènement PropertyChanged à chaque passage dans le setter.
    En réalité ma propriété est initialement écrite comme ceci :

    public string MaPropriete {
       get { return this.maPropriete; }
       set {
          if (maPropriete != value) {
             maPropriete = value;
             OnProprertyChanged("MaPropriete");
          }
       }
    }

    ce que je transforme en :

    [Notify]
    public string MaPropriete {
       get { return GetValue("MaPropriete"); }
       set { SetValue("MaPropriete", value); }
    }

    Je ne vois pas en quoi le code que j’écris est plus verbeux !?!

    Soit !
    Passons maintenant au second point : les performances.
    La réflection s’est beaucoup améliorée depuis .net 1.0, les performances sont tout à fait honorables et il serait vraiment dommage de passer à côté de ce qu’elle peut apporter.
    Revenons sur ma classe NotifyPropertyChangedObject : si tu as bien lu le code tu as dû voir que j’enregistre les propriétés possédant un attribut [Notify] dans un dictionnaire static, c’est à dire que je le partage avec toutes les instances de mon type, je ne fais donc la réflection qu’une seule et unique fois par type, ce n’est pas vraiment suffisant pour gacher les performances de l’application.

    Quant aux auto-properties, elles ne sont pas impactées et restent telle qu’elles était : ces propriétés ne notifient pas de leurs modifications.
    Mais j’aurais aimé pouvoir écrire :

    [Notify]
    public string MaPropriete { get; set; }

    mais pour cela, il faut que j’écrive une BuildTask pour modifier le code juste avant la compilation. Ce sera peut-être l’objet d’un prochain article.

    Stay tune !
    Sébastien

  5. Grégory dit :

    Bonjour,

    Il y a quelques temps j’ai cherché des infos sur ce sujet mais c’était assez fouilli avec chacun venant avec sa version de la solution.

    Je croyais que Caliburn Micro intégrait cela ?
    Désolé si je me plante mais comme je ne suis pas sûr de ce que j’avance.
    Ou bien dans un autre framework que j’aurais vu.

    Grégory

  6. Bonjour,

    Je ne connais pas la solution de Caliburn Micro.
    Il existe cependant des solutions avec Mono.Cecil ou bien PostSharp.

  7. Jérémy JANISZEWSKI dit :

    Bonjour,

    une petite idée d’amélioration :

    Imaginons que notre propriété doit notifier disons x propriétés, il nous faudrait écrire quelque chose comme :

    [Notify]
    [Notify(“a”)]
    [Notify(“b”)]
    ….
    public string X
    {
    get { …. }
    set { …. }
    }

    Alors que l’on pourrait écrire la chose comme cela :

    [Notify]
    [Notify(“a”, “b”, …]
    public string X
    {
    get { …. }
    set { …. }
    }

    Pour cela on a juste à remplacer la variable NotifyProperty par : string[] NotifyProperties et dans le constructeur, on met (params string[] notifyProperties)

    Enfin dans la méthode Initialize, on fait (j’ai fait ça très vite)

    var k = (from p in GetType().GetProperties()
    where p.GetCustomAttributes(typeof(NotifyAttribute), true).Any()
    select new
    {
    Name = p.Name,
    Attributes = from a in p.GetCustomAttributes(typeof(NotifyAttribute), true).Cast()
    select a.NotifyProperties == null ? new string[] { p.Name } : a.NotifyProperties
    });

    List l = new List();

    foreach (var x in k)
    {
    l.Clear();
    foreach (string[] s in x.Attributes)
    l.AddRange(s);

    properties.Add(x.Name, l);
    }

    Cependant, hâte de lire l’article sur la BuildTask, car j’essaie mais j’ai vraiment du mal, car ça peut être hyper interessant pour bypasser l’écriture “interne” des get / set

  8. En effet Jérémy, il est aussi possible de mettre plusieurs propriétés dans le constructeur de l’attribut…
    Cependant, je préfère avoir un attribut par propriété, je trouve que c’est plus propre et que ça ne change rien aux performances de l’application.

    Merci.

  9. Simon Mourier dit :

    Tout cette usine à gaz ne sera bientôt plus nécessaire avec C# 5 et les attributs Caller Info. Ouf :-)

    http://blogs.msdn.com/b/csharpfaq/archive/2012/02/29/visual-studio-11-beta-is-here.aspx

  10. Simon : je ne suis pas tout à fait d’accord.
    Les attributs [CallerInfo] permettront de passer automatiquement des valeurs à des méthodes, mais ne permettront pas d’utiliser la syntaxe courte des AutoProperties :

    [Notify]
    public string MaPropriete { get; set; }

    Ceci-dit, les [CallerInfo] sont un complément aux paramètres avec valeur par défaut.

  11. Eric dit :

    Bonjour Sebastien,

    Tout d’abord merci pour ton article que je trouve intéressant !
    J’ai juste 2 petites questions si ça ne t’embête pas d’y répondre…

    1. je n’ai pas bien compris pourquoi

    [Notify]
    public string MaPropriete {
    get { return GetValue(“MaPropriete”); }
    set { SetValue(“MaPropriete”, value); }
    }

    déclenche moins souvent l’évènement PropertyChanged que

    public string MaPropriete {
    get { return this.maPropriete; }
    set {
    if (maPropriete != value) {
    maPropriete = value;
    OnProprertyChanged(“MaPropriete”);
    }
    }
    }

    ?

    2. Et sinon, je me demandais comment tu implémenterais ta solution dans une architecture MVVM où (d’après ce que j’ai compris) la propriété du VM n’est qu’un relai vers celle du Model :

    public string MaPropriete {
    get { return monObjetModel.MaPropriete; }
    set {
    if (monObjetModel.MaPropriete != value) {
    monObjetModel.MaPropriete = value;
    OnProprertyChanged(“MaPropriete”);
    }
    }
    }

    J’avoue que je n’ai pas eu encore l’occasion de travailler sur ces technos, du coup je manque un peu de repères…

    Eric

  12. Merci Eric pour ton commentaire.

    Pour répondre à ta première question : SetValue ne déclenche pas moins l’évènement que l’autre solution. Ce n’est qu’une réécriture.

    Dans un context MVVM, la VM pose en effet problème avec cette implémentation. J’avoue m’en être rendu compte trop tard après la publication de l’article. Je reviendrais sans doute dessus d’ici quelques jours (semaines ?).

  13. Lionel Lalande dit :

    Pour ceux que ça intéressent, il existe une extension VS qui permet de faire la même chose mais c’est déjà tout prêt (menu pour modifier le csproj + utilisation d’injection de code) : http://visualstudiogallery.msdn.microsoft.com/bd351303-db8c-4771-9b22-5e51524fccd3?SRC=VSIDE.
    à tester…

  14. Merci Lionel… ça semble super intéressant.

  15. Tony THONG dit :

    Bonjour,

    une autre solution consiste tout simplement a generer dynamiquement une classe heritiere (servant de proxy) qui implemente automatiquement l’interface et passer par une fabrique pour obtenir des instances.
    Cela evite une procedure supplementaire a la compilation.

    Cordialement.

  16. […] Pour appliquer ce pattern en WPF par exemple (cela s’applique aussi au SilverLight il me semble) nous allons utiliser le puissant système de Databindingmais pour commencer il faut que notre view model c’est-à-dire, tout ce que vous mettiez avant dans le code behind et que vous avez mis dans une classe séparée, puisse signaler à qui veut bien l’entendre qu’une de ses propriétés à changer. Pour cela on utilise un système qui s’appelle l’implémentation du INotifyPropertyChanged. Ne prenez pas peur c’est plutôt simple il suffit d’ajouter à votre solution la classe  NotifyPropertyChangedObject dont le code est à ce lien : http://blog.soat.fr/2012/02/simplifier-lecriture-de-inotifypropertychanged-en-c/ […]

  17. Feneck91 dit :

    Personnellement ce trouve ce code très cool. J’y ai apporté mes modifications… Tu n’as pas besoin de passer la key, il suffit de passer un paramètre de type : [CallerMemberName] string _strPropertyName = null
    De plus le Notify se fait dans le thread GUI pour notifier les IHM quand un autre thread met à jour la valeur.

    Voici ce que donne ton code revu et quelque peu modifié :

        /// <summary>
        /// Article : http://blog.soat.fr/2012/02/simplifier-lecriture-de-inotifypropertychanged-en-c/
        /// 
        /// Attribut Notify qui permet de repérer les propriétés à surveiller.
        [AttributeUsage(AttributeTargets.Property, AllowMultiple=true)]
        public class NotifyAttribute : Attribute
        {
            public NotifyAttribute() { }
     
            public NotifyAttribute(string _strNotifyProperty)
            {
                NotifyProperty = _strNotifyProperty;
            }
     
            public string NotifyProperty { get; set; }
        }
    
        /// <summary>
        /// Article : http://blog.soat.fr/2012/02/simplifier-lecriture-de-inotifypropertychanged-en-c/
        /// 
        /// Gestion automatique des notifications.
        /// Contient toute la logique interprétant les attributs [Notify] mais aussi portera les méthodes et évènements de INotifyPropertyChanged.
        /// </summary>
        public class NotifyPropertyChangedObject : INotifyPropertyChanged
        {
            /// <summary>
            /// Dictionnaire des valeurs à surveiller.
            /// </summary>
            private static Dictionary<string, dynamic>  Values                  { get; set; }       = new Dictionary<string, dynamic>();
    
            #region ========= Notification =========
    
            private static Dictionary<string, IEnumerable<string>> Properties   { get; set; }
    
            /// <summary>
            /// Est-il déja intialisé ?
            /// </summary>
            private static bool                         m_bIsAlreadyInitialize = false;
     
            private void InitializeNotifyAttributes()
            {
                if (m_bIsAlreadyInitialize)
                {
                    return;
                }
     
                // récupération des propriétés et des notifications
                var prop = from p in this.GetType().GetProperties()
                           where p.GetCustomAttributes(typeof(NotifyAttribute), true).Any()
                           select new {
                               p.Name,
                               Attributes = p.GetCustomAttributes(typeof(NotifyAttribute), true)
                                                .Cast<NotifyAttribute>()
                                                .Select(a => string.IsNullOrEmpty(a.NotifyProperty) ? p.Name : a.NotifyProperty)
                           };
     
                Properties = new Dictionary<string, IEnumerable<string>>();
                // création d'un dictionnaire
                prop.ToList().ForEach(p => Properties.Add(p.Name, p.Attributes.ToList()));
    
                m_bIsAlreadyInitialize = true;
            }
    
            public void SetValue<TObject>(Expression<Func<TObject>> _expression, dynamic _value)
            {
                InitializeNotifyAttributes();
                SetValue(GetPropertyName(_expression), _value);
            }
    
            /// <summary>
            /// Récupérer la valeur.
            /// </summary>
            /// <typeparam name="T">Type de la donnée.</typeparam>
            /// <param name="_strPropertyName">Clef (son nom).</param>
            /// <param name="_defaultValue">Sa valeur par défaut.</param>
            /// <returns>La valeur ou sa valeur par défaut.</returns>
            public T GetValue<T>(T _defaultValue = default(T), [CallerMemberName] string _strPropertyName = null)
            {
                InitializeNotifyAttributes();
                if (Values.ContainsKey(_strPropertyName))
                {
                    return (T) Values[_strPropertyName];
                }
                return _defaultValue;
            }
    
            /// <summary>
            /// Set de la valeur.
            /// </summary>
            /// <param name="_strKey">Clef (son nom).</param>
            /// <param name="_dynamicValue">sa valeur</param>
            public void SetValue(dynamic _dynamicValue, [CallerMemberName] string _strPropertyName = null)
            {
                InitializeNotifyAttributes();
                if (!Values.ContainsKey(_strPropertyName))
                {
                    Values.Add(_strPropertyName, _dynamicValue);
                    NotifyPropertyChanged(_strPropertyName);
                }
                else
                {
                    if (Values[_strPropertyName] != _dynamicValue)
                    {
                        Values[_strPropertyName] = _dynamicValue;
                        NotifyPropertyChanged(_strPropertyName);
                    }
                }
            }
            #endregion
     
            public void NotifyPropertyChanged<T>(Expression<Func<T>> action)
            {
                NotifyPropertyChanged(GetPropertyName(action));
            }
    
            /// <summary>
            /// 
            /// </summary>
            /// <param name="_strPropertyName"></param>
            public void NotifyPropertyChanged(string _strPropertyName)
            {
                var action = (Action<string>) ((propertyName)  =>
                {
                    PropertyChangedEventHandler handler = PropertyChanged;
                    if (handler != null)
                    {
                        handler(this, new PropertyChangedEventArgs(propertyName));
                    }
                });
    
                if (DispatcherHelper.UIDispatcher.CheckAccess())
                {   // Fait dans le thread principal
                    if (Properties.ContainsKey(_strPropertyName))
                    {
                        Properties[_strPropertyName].ForEach(propertyName =>
                        {
                            DispatcherHelper.UIDispatcher.Invoke(DispatcherPriority.Render, (Action)(() => { action(propertyName); }));
                        });
                    }
                    else
                    {
                        DispatcherHelper.UIDispatcher.Invoke(DispatcherPriority.Render, (Action)(() => { action(_strPropertyName); }));
                    }
                }
                else
                {
                    if (Properties.ContainsKey(_strPropertyName))
                    {
                        Properties[_strPropertyName].ForEach(propertyName =>
                        {
                            DispatcherHelper.UIDispatcher.BeginInvoke(DispatcherPriority.Render, (Action)(() => { action(propertyName); }));
                        });
                    }
                    else
                    {
                        DispatcherHelper.UIDispatcher.BeginInvoke(DispatcherPriority.Render, (Action)(() => { action(_strPropertyName); }));
                    }
                }
            }
    
            private static string GetPropertyName<T>(Expression<Func<T>> _action)
            {
                var expression = (MemberExpression) _action.Body;
                var propertyName = expression.Member.Name;
                return propertyName;
            }
     
            private static PropertyInfo GetProperty<T>(Expression<Func<T>> _action)
            {
                return typeof(T).GetProperty(GetPropertyName(_action));
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
        }
    

    Un exemple d’utilisation (bon, une classe logger qui sert à afficher ce qui se passe (grosse copie de plusieurs Go)
    C’est juste pour montrer l’utilisation :

        /// <summary>
        /// Permet de logger des travaux réalisés.
        ///
        /// Permet d'afficher de grande quantité de messages colorisé avec une hiérarchie.
        /// Cette classe représente une ligne de log.
        /// </summary>
        public class LogEntry : NotifyPropertyChangedObject
                              , INotifyPropertyChanged
        {
            /// <summary>
            /// Date du message.
            /// </summary>
            public DateTime             DateTime                                { get; set; }
    
            /// <summary>
            /// Message de la ligne (copie de ....)
            /// </summary>
            [Notify]
            public string               Message                                 {  get { return GetValue<string>(); }
                                                                                   set { SetValue(value); }}
    
            /// <summary>
            /// Message de la ligne (copie de ....)
            /// </summary>
            [Notify]
            public string               StatusText                              {  get { return GetValue<string>(); }
                                                                                   set { SetValue(value); }}
    
            /// <summary>
            /// Couleur de la ligne (copie de ....)
            /// </summary>
            public Brush                StatusColorBrush                        {  get { return new SolidColorBrush(GetValue<Color>(Colors.Blue, "StatusColor")); }}
    
            /// <summary>
            /// Status de la ligne
            /// </summary>
            [Notify]
            [Notify("StatusColorBrush")]
            public Color                StatusColor                             {  set { SetValue(value); }}
    
            /// <summary>
            /// Context.
            /// 
            /// Laissé libre au développeur.
            /// </summary>
            public object               Context                                 {  get; set; }
    
            /// <summary>
            /// Parent.
            /// </summary>
            public LogEntry             Parent                                  { get; private set; }
    
            /// <summary>
            /// Profondeur de l'arbre.
            /// 
            /// Permet de connaître la profondeur de l'arbre.
            /// </summary>
            public int                  Depth                                   { get; private set; }
    
            /// <summary>
            /// Créer une nouvelle entrée.
            /// </summary>
            /// <param name="_parent"></param>
            /// <param name="_strMessage"></param>
            /// <param name="_strStatusText">Texte du status, peut-être null.</param>
            /// <param name="_colStatus">Couleur pour le status.</param>
            public LogEntry(LogEntry _parent, string _strMessage, string _strStatusText, Color ? _colStatus = null)
            {
                DateTime    = DateTime.Now;
                Message     = _strMessage;
                StatusText  = _strStatusText;
                StatusColor = _colStatus.HasValue ? _colStatus.Value : Colors.Black;
                Parent   = _parent;
                Depth    = (Parent == null) ? 0 : Parent.Depth + 1;
                if (Parent is CollapsibleLogEntry)
                {
                    var action = (Action) (()  =>
                    {
                        (Parent as CollapsibleLogEntry).Contents.Add(this);
                    });
    
                    if (DispatcherHelper.UIDispatcher.CheckAccess())
                    {   // Fait dans le thread principal
                        DispatcherHelper.UIDispatcher.Invoke(DispatcherPriority.Render, action);
                    }
                    else
                    {
                        DispatcherHelper.UIDispatcher.BeginInvoke(DispatcherPriority.Render, action);
                    }
                }
            }
        }
    
        /// <summary>
        /// Entrée contenant la possibilité d'être expand / collapsed
        /// </summary>
        public class CollapsibleLogEntry : LogEntry
        {
            public ObservableCollection<LogEntry> Contents { get; set; } = new ObservableCollection<LogEntry>();
    
            /// <summary>
            /// Créer une nouvelle entrée de type collapsable.
            /// </summary>
            /// <param name="_parent"></param>
            /// <param name="_strMessage"></param>
            /// <param name="_strStatusText">Texte du status, peut-être null.</param>
            /// <param name="_colStatus">Couleur pour le status.</param>
            public CollapsibleLogEntry(LogEntry _parent, string _strMessage, string _strStatusText = null, Color ? _colStatus = null)
                : base(_parent, _strMessage, _strStatusText, _colStatus)
            {
            }
        }
    
  18. Feneck91 dit :

    Remplacez :
    private static Dictionary Values { get; set; } = new Dictionary();
    par :
    private Dictionary Values { get; set; } = new Dictionary();

    en effet, j’ai copié les différentes versions et je n’ai pas vu ce changement, du coup toutes les instances partageaient la même valeur !
    Et ce n’est pas celà que l’on veut….
    Si le modérateur peut faire ce changement, il peut effacer ce message…

AJOUTER UN COMMENTAIRE