Tout et n'importe quoi ...(de préférence)

Blog de CLT-Services : vie de l'entreprise et infos pratiques

INotifyPropertyChanged avec Expression Lambda

Tuesday, 19 May 2009 23:52 by alex

Dans le Framework .NET 2.0 il existe une interface nommée INotifyPropertyChanged destinée à notifier un client lorsque la valeur d'une propriété change. Si on prend le cas de Silverlight, elle servira dans une liaison bidirectionnelle entre le client et l'objet métier.

Prenons un petit exemple:

J'ai une classe Marker et je décide de lui faire implémenter l'interface.

public class Marker : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;

  private string description;
  public string Description
  {
     get {return description;}
     set
        {
           if(value != description)
              NotifyPropertyChanged("Description");
        }

   }

   private void NotifyPropertyChanged(String propertyName)
   {
     if (PropertyChanged != null)
     {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
     }
   }

}

Côté client on a une interface avec une textbox nous permettant de modifier la description pour un Marker donné. On utilise un Bind en mode TwoWay. TwoWay signifie que la texbox est dans un premier temps initialisée avec la valeur de l'objet. Si on change la valeur de la textbox, la valeur de l'objet métier sera également mise à jour, ainsi que tous les éléments qui ont une liaison sur cette propriété car l'évènement PropertyChanged est levé.

Maintenant, imaginons qu'un développeur reprend votre  class, change le nom de votre propriété en "Desc", met à jour jour tous les binding. Que va-t-il se passer ? Il n'y aura aucune erreur lors de la compilation, mais lors de l'exécution on va s'apercevoir que la string passée à la méthode NotifyPropertyChanged n'a pas été modifiée.

Comment contourner ce problème ?

Une méthode est d'utiliser les expressions lambda, avec un délégué générique et une arborescence d’expression Linq.

Mais avant de commencer quelques rappels:

Qu'est-ce qu'un délégué ? Pour faire simple on dira qu'il s'agit d'un pointeur sur une méthode. On peut les utiliser avec des méthodes nommées, anonymes ou avec des expressions lambdas. La signature de la méthode doit correspondre à la signature du délégué.

Méthode Nommée:

public delegate int Del(int i);

public int Calc(int i)

  return i + i;
}

void Main()
{

  Del d = Show;

  d(2);
}

Méthode Anonyme:

 public delegate int Del(int i);

void Main()
{

  Del d =  delegate(int i){ return i + i;};

d(2);
}

Lambda Expression:

public delegate int Del(int i);

void Main()
{

  Del d =  i => i + i;

d(2);
}

Dans le dernier exemple notre expression lambda i => i + i sert à instancier notre délégué.

Le Framework contient également des délégués génériques pouvant encapsuler une méthode de 0 à 5 paramètres de type T et une valeur de retour de type TResult.  Func<T,TResult>,  Func<T1, T2, TResult> ....

Avec ces informations on peut désigner la propriété de notre classe Marker

Func<Marker,String> del = m => m.Description;

On créé et instancie un délégué qui encapsule une méthode, avec un paramètre m de type Marker, et une valeur de retour string correspondant au type de Description.

Nous allons ensuite créer une arborescence d’expression Linq pour cela il faut importer System.Linq.Expression présent avec C# 3. Une arborescence représente un ensemble d’expressions: MemberExpression ici m.Description , ParameterExpression  ici m ...  Pour l’obtenir il suffit de l’assigner au type Expression<TDelegate> c’est à dire à Expression<Func<Marker,String>> dans notre cas. Le compilateur se chargera alors  de construire une arborescence d’expression représentant notre expression lambda.

Notre classe au début de l’article peut alors s’écrire de la manière suivante

public class Marker : INotifyPropertyChanged
{
  public event PropertyChangedEventHandler PropertyChanged;

  private string description;
  public string Description
  {
     get {return description;}
     set
        {
           if(value != description)
              NotifyPropertyChanged(m => m.Description);
        }

   }

   private void NotifyPropertyChanged<TResult>(Expression<Func<Marker,TResult>> arbo)
   { 
   } 
}

Il n’est pas nécessaire de préciser le type String lors de l’appel de la méthode  NotifyPropertyChanged car celui-ci sera obtenu automatiquement lors de la compilation.

Il nous reste plus qu’à parcourir notre arborescence pour tester qu’elle contient bien un type Property et récupérer son nom.

Pour cela on va compléter la méthode NotifyPropertyChanged.

   private void NotifyPropertyChanged<TResult>(Expression<Func<Marker,TResult>> arbo) 
{ 

if (this.PropertyChanged != null)
            {
                var memberExpression = arbo.Body as MemberExpression;

                if (memberExpression == null||
                    memberExpression.Member.MemberType != MemberTypes.Property)
                {
                    throw new InvalidOperationException("NotifyPropertyChanged has to be called from pulic property");
                }
                this.PropertyChanged(this, new PropertyChangedEventArgs(memberExpression.Member.Name));
            }

Et voilà si jamais le nom de la propriété change mais pas l’expression lambda, le code ne compilera pas.

 

Pour aller plus loin

Les arborescences d’expression peuvent également être compilée via la méthode compile() en du code exécutable.

Ex:

Expression<Func<int,int>> MultiplyBy5 = p => 5 * p;

var del = MultiplyBy5.Compile();
int result = del (10);

 

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Categories:   Linq | C#
Actions:   E-mail | Permalink | Comments (1) | Comment RSSRSS comment feed

Related posts

Comments

June 9. 2010 10:18

pingback

Pingback from alexandre-arnaudet.fr

| C#, Silverlight, WPF, Linq …

alexandre-arnaudet.fr

Add comment


(Will show your Gravatar icon)  

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



Live preview

July 29. 2010 16:46

Search