tag(s) Tags: C#
lu 3867 fois
1 pages
Frédéric Mélantois
Surcharger une méthode d'extension
La nouvelle version de C# 3.0 apporte la possibilité d'« étendre » les classes via les méthodes d'extension. Quel est le comportement par défaut du compilateur dans le cas de définitions identiques de deux méthodes d'extension ?
Par Frédéric Mélantois publié le 25/11/2007 à 21:40
 
L'article a nécessité une correction post-publication le 2 décembre. Il m'a paru important de conserver le contenu initial de ce dernier par transparence vis-à-vis des premiers lecteurs mais aussi parce que l'erreur commise a un intérêt pédagogique. Un chapitre « Mise à jour » a donc été ajouté en fin d'article, le reste n'ayant pas été modifié.
Pour définir une méthode d'extension, il suffit de créer une classe statique. Ensuite, le premier paramètre de chaque méthode sera « balisé » par le mot clé « this » afin de désigner le type de l'objet que l'on souhaite « étendre ».
Prenons un exemple. Soit un objet « Personne » ayant pour propriétés : un prénom, un nom et un âge.

public class Personne

{

    public string Nom { get; set; }

    public string Prenom { get; set; }

    public int Age { get; set; }

}

Si vos objets sont alimentés par différentes sources, vous vous apercevrez que le nom est parfois en majuscule, parfois en minuscule, ce qui vous posera problème par exemple pour écrire de façon standard un courrier sous Word.

List<Personne> personnes = new List<Personne>();

personnes.Add(new Personne(){Nom="DURAND",Prenom="Cyril",Age=21});

personnes.Add(new Personne() { Nom = "Kempé", Prenom = "Laurent", Age = 36 });

personnes.Add(new Personne() { Nom = "MELANTOIS", Prenom = "Frédéric", Age = 38 });

personnes.Add(new Personne() { Nom = "Perfetti", Prenom = "Michel", Age = 30 });

Pour nos documents, nous aimerions bien avoir une méthode « PrenomNom ()» dans la classe « Personne », permettant d'avoir le prénom puis le nom en majuscules. Mais dans bien des cas, vous n'aurez pas la possibilité de réécrire cette classe. Les méthodes d'extension viennent alors à notre secours :

namespace EspaceDeNom1

{

    public static class ExtensionPersonne

    {

        //prénom + Nom en majuscule

        //[//System.Runtime.CompilerServices.Extension]

        public static string PrenomNom(this Personne p)

        {

            StringBuilder s = new StringBuilder(p.Prenom);

            s.Append(" ");

            s.Append(p.Nom.ToUpper());

            return s.ToString();

        }

    }

}

Veuillez noter que le compilateur C# refuse l'utilisation de l'attribut « System.Runtime.CompilerServices.ExtensionAttribute » contrairement à Visual Basic. La seule façon de créer une méthode d'extension est d'ajouter le mot clé « this ». Sous le capot, comme nous avons pu le voir dans un précédent article, le « this » est retranscrit en attribut sur la classe et la méthode statique au niveau de l'IL (Intermediate Language).
Le compilateur permet un usage aisé de la méthode :

foreach (string identite in from p in personnes select p.PrenomNom())

{

    Console.WriteLine(identite);

}

Ce qui nous donne le résultat suivant :
 
/content/c4a367d4-f82c-47e0-812a-1f35ce4b86f8/resultat2.jpg
 
Nous pouvons constater qu'un des noms comporte une majuscule avec accent, ce qui ne nous convient pas. Imaginons que nous ne sommes pas en possession des sources de la méthode d'extension ou que celle-ci se trouve dans une assembly déjà déployée. Nous avons très largement employé la méthode d'extension « PrenomNom() » un peu partout dans notre projet. Celle-ci satisfait nombre de vos collègues mais pas vous dans votre propre utilisation. Vous pouvez alors soit modifier son utilisation par un remplacement systématique par une autre méthode soit « surcharger » la méthode d'extension. Cette dernière solution est bien évidemment bien moins lourde !
La surcharge d'une méthode d'extension est possible sous certaines conditions. Celle-ci doit se trouver dans la même assembly et le même espace de nom que l'appel à la méthode d'extension. Si ces conditions ne sont pas remplies, le compilateur renverra une erreur signifiant un appel ambigu de méthodes d'extension. Ecrivons rapidement une méthode d'extension (la performance extrême dans les exemples n'est pas l'objet de l'article)

public static class ExtensionPersonne

{

    internal static string UpperToStandardUpper(this string chaine)

    {

        string accent =    "ÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØÙÚÛÜ";

        string sansAccent = "AAAAAACEEEEIIIINOOOOOOUUUU";

        char[] tableauSansAccent = sansAccent.ToCharArray();

        char[] tableauAccent = accent.ToCharArray();

        for (int i = 0; i < accent.Length; i++)

        {

            chaine = chaine.Replace(tableauAccent[i].ToString(), tableauSansAccent[i].ToString());

        }

        return chaine;

    }

 

    public static string PrenomNom(this Personne p)

    {

        StringBuilder s = new StringBuilder(p.Prenom);

        s.Append(" ");

        s.Append(p.Nom.ToUpper().UpperToStandardUpper());

        return s.ToString();

    }

}

Si nous ajoutons ce code dans le même espace de nom que l'appel, une surcharge de méthode d'extension s'opère. De sorte que la requête suivante :

foreach (string identite in from p in personnes select p.PrenomNom())

{

    Console.WriteLine(identite);

}

renvoie :
 
/content/c4a367d4-f82c-47e0-812a-1f35ce4b86f8/Resultat3.JPG
 
Nous venons de réaliser avec succès une surcharge de méthode d'extension. Cet aspect nous apparaît bien pratique. Toutefois, sachez que bien évidemment vous pouvez passer outre par l'invocation direct des méthodes :

foreach (string identite in from p in personnes select EspaceDeNom1.ExtensionPersonne.PrenomNom(p))

    ou

foreach (string identite in from p in personnes select ConsoleApplication1.ExtensionPersonne.PrenomNom(p))

Les méthodes d'extension sont très séduisantes mais peuvent générer des conflits. En effet, il suffit d'utiliser deux espaces de noms différents comportant une signature de méthode d'extension identique. C'est alors que le compilateur génère une erreur d'appel ambigu de méthodes d'extension. Comment éviter au maximum ces conflits ? Comme le suggère la documentation MSDN, il faut rassembler ses méthodes d'extension sous un même espace de nom bien évocateur : « Extensions ». Si ceci est une bonne pratique, car elle permet de localiser très rapidement l'ensemble de vos méthodes d'extension, cela n'évitera pas les conflits si un éditeur de logiciel a créé une même signature que vous. Une solution peut être de préfixer chacun de vos noms de méthode d'extension. C'est à vous de bien penser au nom que vous allez donner à votre méthode. Il existe une solution pour limiter l'impact de vos méthodes d'extension, c'est de changer la portée de « public » à « internal ». Nous l'avons vu dans un précédent exemple avec la méthode « UpperToStandardUpper ». Sa portée est donc limitée à l'assembly de sorte que les utilisateurs de cette dernière n'ont pas accès à cette méthode et n'auront donc pas de conflit. C'est une pratique à généraliser dès les premières heures d'utilisation de c# 3.0 ou de Visual Basic 9
Dans certains cas, il peut être intéressant de surcharger une méthode d'extension. Si vous lisez cet article, vous avez sans doute déjà utilisé Linq et en particulier Linq to SQL. Pour ceux d'entre vous qui l'utiliseront, se posera une problématique concernant la couche d'accès aux données que vous aurez construite. En effet, si nous proposons une méthode renvoyant un IEnumerable<T> dans notre DAL

public IEnumerable<PERSONNE> GetPersonnes()

{

    DataClasses1DataContext t = new DataClasses1DataContext(_myDatabaseConnectionString);

    var c = from h in t.PERSONNEs select h;

    return c;

}

Vous ne pourrez empêcher personne d'écrire dans la couche de présentation ceci :

IQueryable<PERSONNE> q = (IQueryable<PERSONNE>) MyDAL.GetPersonnes();

var r = from s in q where s.Personne_Age > 30 select s;

ce qui aura pour incidence de créer un nouvel accès à la base de données par l'usage du IQueryable. Pour éviter, cette problématique, il faut que notre couche d'accès ne renvoie que des IEnumerable aucunement IQueryable. Nous pouvons réitérer de manière à ne plus avoir d'IQueryable de la façon suivante :

public IEnumerable<PERSONNE> GetPersonnes()

{

    DataClasses1DataContext t = new DataClasses1DataContext(_myDatabaseConnectionString);

    var c = from h in t.PERSONNEs select h;

    return from i in c.AsEnumerable() select i;

}

Mais si nous devons appliquer ces changements à chaque méthode de notre couche d'accès, ce travail devient fastidieux et nous risquons d'en oublier. La surcharge de méthodes d'extension peut venir à notre secours dans ce cas, en surchargeant la méthode d'extension « Select » de Linq To Objects. Voici le code de la surcharge :

internal static class ExtensionLinq

{

    internal static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source,

   Func<TSource, TResult> selector)

    {

        IEnumerable<TSource> sourceEnum = source;

        foreach (TSource s in sourceEnum)

            yield return selector.Invoke(s);

    }

}

Cette surcharge permet de ne plus avoir à ce soucier dans la couche de présentation des manipulations telles que :

IQueryable<PERSONNE> q = (IQueryable<PERSONNE>) MyDAL.GetPersonnes();

var r = from s in q where s.Personne_Age > 30 select s;

Elles deviennent impossibles. De sorte, que les requêtes Linq en couche de présentation ne se font qu'en mémoire. Vous aurez remarqué que j'ai pris soin d'écrire ma surcharge avec la portée « internal » de sorte qu'il n'y ait pas d'erreur d'appel ambigu de méthodes d'extension dans ma couche de présentation en faisant usage de Linq.
Je tiens vivement à remercier Flavien pour m'avoir notifié une ligne de code inutile. En ont découlé des discussions très instructives avec Flavien CHARLON et Matthieu MEZIL qui m'ont amené à retester différents cas de figure et à montrer que la surcharge du « Select » pour « Linq To SQL » n'était pas une solution satisfaisante.
Rappelons le code proposé :

internal static class ExtensionLinq

{

    internal static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source,

   Func<TSource, TResult> selector)

    {

        IEnumerable<TSource> sourceEnum = source;

        foreach (TSource s in sourceEnum)

            yield return selector.Invoke(s);

    }

}

la ligne « IEnumerable<TSource> sourceEnum = source; » est complètement inutile car le « yield return » fabrique sous le capot un objet privé implémentant IEnumerable<T> et non IQueryable<T>. Voici le code que j'aurai dû écrire :

 internal static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source,

Func<TSource, TResult> selector)

 {

    foreach (TSource s in source)

        yield return selector.Invoke(s);

 }

On pourrait être satisfait mais le Select ne fonctionne pas comme on le souhaiterait. En effet, si au lieu de renvoyer toutes les colonnes de la table Personne, nous sélectionnons uniquement le nom par exemple :

var c = from h in dt.PERSONNEs where h.Personne_Age > 30 select h.Personne_Nom;

La requête effectuée vers la base de donnée renverra toutes les colonnes avant que n'opère le « Select » que nous avons écrit. Cette solution n'est donc pas satisfaisante. Si l'on souhaite que la base de données ne renvoie que les colonnes que l'on a réellement besoin, il faut faire appel à la méthode d'extension « Select » de « System.Linq.Queryable » :

internal static IEnumerable<TResult> Select<TSource, TResult>(this IQueryable<TSource> source,

    System.Linq.Expressions.Expression<Func<TSource, TResult>> selector)

{

    IQueryable iq = Queryable.Select(source, selector);

    foreach (TResult s in iq)

        yield return s;

}

Si vous utilisez le Debugger ou le système de log du « DataContext », vous pourrez constater que cette surcharge répond désormais à notre exigence.
Toutefois, les discutions avec mes camarades m'ont amené à effectuer de nombreux tests. J'ai alors pu constater que cette surcharge ne fonctionnait pas dans certains cas. Par exemple, si vous écrivez une requête du style « select * from matable where condition », le compilateur ne génèrera que « Table<matable>.Where(condition) » car le « Select » est inutile car toutes les colonnes sont demandées. On peut donc se féliciter d'un tel comportement pour ce qui est de la performance. Mais, la surcharge que je vous propose, ne fonctionne donc pas dans tous les cas. Je vous recommande donc de ne pas l'employer dans votre couche d'accès. Vous pouvez écrire une méthode d'extension qui empêchera de pouvoir requêter de nouveau via « Linq to SQL » :

internal static IEnumerable<TSource> ToLinqToObject<TSource>(this IQueryable<TSource> source)

{

    foreach (TSource s in source)

        yield return s;

}

Son utilisation se fera comme suit :

public IEnumerable<string> GetPersonnes()

{

    DataClasses1DataContext t = new DataClasses1DataContext(myDatabaseConnectionString);

    var c = from h in t.PERSONNEs where h.Personne_Age > 30 select h;

    return c.ToLinqToObject();//return from i in c.AsEnumerable() select i;

}

Voici les liens des différentes discussions avec Flavien et Matthieu :
Nous venons de montrer que les surcharges de méthodes d'extension pouvaient s'avérer très utiles mais qu'elles doivent être manipuler avec précautions. Voici quelques liens qui peuvent vous être utiles :

 Commentaires (2) - Surcharger une méthode d'extension 

Discussion démarée par Frédéric Mélantois le 17/12/2007 à 17:41 , 1 commentaire(s).
Discussion démarée par Frédéric Mélantois le 27/11/2007 à 13:16 , 1 commentaire(s).

 Dernières Publications      

Utilisation de jQuery avec ASP.NET MVC
  Développer une IHM à page unique avec ASP.NET MVC et jQuery
par Nicolas Moyère posté le 30/06/2008 à 10:28, lu 824 fois, #0
Tags: ASP.NET MVC, Ajax
Windows Media Center et WCF : développez votre maison intelligente
  Le développement d'applications pour Windows Media Center est facilité avec l'arrivée du SDK 5.3. Même si l'on sent un modèle objet bien lourd derrière, il devient plus facile d'exposer les fonctionnalités de WMC sous la forme de services WCF.
par Frédéric Colin posté le 23/06/2008 à 08:04, lu 891 fois, #0
Notions avancées avec Biztalk Server 2006 R2
  Utilisation des notions d'interchange, corrélation et convoi avec BizTalk Server 2006 R2
par Kader Yildirim posté le 09/06/2008 à 08:04, lu 705 fois, #0
Lucene Persistence Engine pour Evaluant Universal Storage Services
  Suite à l'article de Laurent Kempé, voici un moteur de stockage pour EUSS permettant l'indexation d'entités métier avec Lucene.
par Nicolas Penin posté le 01/06/2008 à 23:38, lu 1091 fois, #1
Tags: C#, Linq
XMLA Trivia : Découverte du XMLA
  Le XMLA (XML for Analysis) est un langage normalisé par plusieurs éditeurs BI pour simplifier l'accès aux données aux cubes et aux métadonnées des bases multidimensionnelles.
par Renaud Harduin posté le 25/05/2008 à 11:57, lu 1008 fois, #1
Exploiter les données CSV via Linq en toute simplicité
  A partir du requêteur dynamique fourni en exemple avec Visual Studio 2008, nous allons essayer de remplir les propriétés d'un ensemble d'objets à partir des données d'un fichier CSV. Nous enrichirons aussi le parseur de nos propres fonctions.
par Frédéric Mélantois posté le 17/05/2008 à 11:41, lu 2785 fois, #0
Comment manipuler simplement le contenu d'un fichier WordML ?
  Manipulations autour du format WordML
par Fabien Reinle posté le 14/05/2008 à 23:55, lu 1405 fois, #0
Polymorphisme et contrats de données WCF
  WCF aborde les types polymorphes du point de vue de la sérialisation. En effet, la connaissance du type réel potentiel est rendue nécessaire dès la description du contrat de données. Une fois n'est pas coutume, j'ai réalisé l'exemple en VB.NET.
par Frédéric Colin posté le 14/05/2008 à 08:40, lu 2931 fois, #2

 Dernières Actualités      

Reprise du projet Reflector par RedGate
  La nouvelle était connue depuis quelques jours par les développeurs de plugins, mais c’est désormais officiel : Lutz Roeder, le responsable de Reflector confie à la société RedGate le futur du projet....
Microsoft publie Visual Studio 2008 Service Pack 1
  Il est recommandé d’utiliser l’outil Visual Studio 2008 Service Pack preparation Tool avant de faire l’installation du Service Pack si vous avez installé des versions béta sur votre machine. Une fois que...
Tags: Framework .NET, Visual Studio 2008
Evaluant dévoile ses sources
  L'ensemble des projets R&D réalisés par les consultants de la SSII Evaluant sont en cours de publication sur CodePlex . L'objectif est de les centraliser et surtout d'augmenter leur visibilité. L'avantage...
Le Silverlight Tour en français!
  Le Silverlight Tour passe maintenant dans les pays francophones! En effet RunAtServer Consulting est partenaire du Silverlight Tour pour la gestion de cette formation Silverlight en français à commencer...
Microsoft publie ASP.NET AJAX 4.0 CodePlex Preview 1
  Cette pré-version contient les améliorations suivantes: Client-side template rendering Declarative instantiation of behaviors and controls DataView control Markup extensions Bindings Vous pouvez en lire...
Tags: Ajax
Deep Earth – Une belle utilisation de Virtual Earth et de Silverlight Deep Zoom
  Ce projet très intéressant est disponible sur Codeplex et vous pouvez voir une démo sur la page suivante . Bien entendu comme touts les projets sur Codeplex vous avez accès aux sources....
Tags: Silverlight