MetaTips

Le blog de l'edition logiciel en ligne

Articles

Exploiter les nouveautés de C# 3 et comprendre les delegates / Part 2

Posted by Allouche Mathieu on December 2, 2009 at 4:46 AM

Cet article à pour objectifs de vous faire découvrir les principales nouveautés syntaxiques de C#3, de comprendre les expressions lambda, les délégués, les concepts d'anonymisation, les classes et les méthodes d'extensions. Vous trouverez la une bonne entrée en matière avant d'attaquer les nouveautés introduites dans Linq.

 

Type : Article technologique / Tutorial

Niveau : 4/5

Durée de lecture : 4 heures

Nécessaire :

·         Visual studio 2008 (ou équivalent), C# installé

·         .NET Framework 3.5 minimum

 

Tout au long de l?article, nous allons partir des syntaxes de C# 1.x pour évoluer progressivement vers la syntaxe 2.0 puis 3.x. Nous allons ainsi comprendre les concepts de bases de C# et voir dans quelle mesure le Framework 3.x apporte de réelles innovations.

Pour ce faire, notre problématique de ce jour sera de manipuler une collection d?objets afin de la lister, et de la filtrer selon des critères que ne connaît pas à l?avance le développeur de la classe.

 

Les expressions d?initialisations :

 

En C# 1 :

Voici la classe Day qui permet de décrire un jour. Pour les besoins de la démonstration, nous allons imaginer que la classe, n?est pas modifiable. Nous pouvons observer, qu?elle possède 2 constructeurs, dont un lui permettant d?initialiser une de ses deux données membres.

 

Code de la classe :

 

sealed public class Day

    {

        public int ID;

        public string Name;

 

        public Day()

        {

        }

 

        public Day(string name)

        {

            Name = name;

        }

 

        public override string ToString()

        {

            return (ID + " - " + Name);

        }

    }

 

Code pour utiliser la classe :

 

static void Main(string[] args)

        {

            Console.WriteLine("Exercise  1 :");

 

            ArrayList AllDays = new ArrayList();

            Day D1 = new Day("Lundi"); D1.ID = 1; AllDays.Add(D1);

            Day D2 = new Day("Mardi"); D2.ID = 2; AllDays.Add(D2);

            Day D3 = new Day("Mercredi"); D3.ID = 3; AllDays.Add(D3);

            Day D4 = new Day("Jeudi"); D4.ID = 4; AllDays.Add(D4);

            Day D5 = new Day("Vendredi"); D5.ID = 5; AllDays.Add(D5);

            Day D6 = new Day("Samedi"); D6.ID = 6; AllDays.Add(D6);

 

            Console.WriteLine("List of days before filtering  :");

            foreach (Day d in AllDays)

            {

                Console.WriteLine(d);

            }

        }

 

Très classiquement, après avoir appelé l?initialisateur de la classe Day, nous sommes obligés de conserver l?instance dans une variable locale, afin de pour pouvoir initialiser la propriété ?ID? avant de l?insérer dans la collection ?AllDays?.

 

En C# 3 :

Conceptuellement nouvelle la syntaxe permet de simplifier la ?Day? à sa plus simple expression, mais nous ne le ferons que si nous le pouvons.

 

Le code de la classe :

 

sealed public class Day

    {

        public int ID;

        public string Name;

 

        public override string ToString()

        {

            return (ID + " - " + Name);

        }

    }

 

Code pour utiliser la classe :

 

static void Main(string[] args)

        {

            Console.WriteLine("Exercise  1 :");

 

            ListDay  AllDaysExercise1 = new ListDay

               {new Day{ID=1,Name="Lundi"},

                new Day{ID=2,Name="Mardi"},

                new Day{ID=3,Name="Mercredi"},

                new Day{ID=4,Name="Jeudi"},

                new Day{ID=5,Name="Vendredi"},

                new Day{ID=6,Name="Samedi"}};

 

            foreach (var d in AllDaysExercise1)

            {

                Console.WriteLine(d);

            }

        }

 

Après transformation, nous pouvons observer, qu?il est possible en une seule ligne d?appeler l?un des constructeurs et d?initialiser ses propriétés. On notera aussi, que dans l?exemple nous avons utilisé une collection typé de type Generics.

 

Remarques : En aucun cas, cette nouvelle syntaxe, ne signe la fin des constructeurs multiples que peuvent avoir certaines classes. En effet, ces constructeurs peuvent effectuer des traitements spécifiques sur certaines valeurs internes. Ils peuvent aussi obliger l?initialisation de certains champs.

 

 

Les délégués (delegate) :

Une notion complètement masquée dans les langages de haut niveau comme le C# est la notion de pointeurs. Bien que masquée, elle bien évidement toujours utilisée. On trouve 2 types de pointeurs. Les pointeurs de données, qui utilisent la pile de données et les pointeurs de fonctions qui utilisent la pile d?exécution. Ils permettent de faire des sauts.

En C# comme dans d?autres langage, les variables par ?référence? utilisent des pointeurs. Ainsi lorsque vous écrivez ceci :

 

Vous n?échangez pas le contenu de la variable, mais un pointeur qui adresse la zone mémoire. Ainsi, lorsque vous modifiez l?un, vous modifiez l?autre, en fait vous modifiez un espace commun dans la pile des données. Ces pointeurs sont managés et les variables sont typées.

 

Les ?delegate? sont des pointeurs sur fonctions comme on peut les trouver en C++, avec ceci de différent qu?ils sont eux aussi managés. C'est-à-dire que les paramètres d?entrées et de retours sont typés et contrôlés, afin d?éviter les débordements de pile.

 

Pour rappel, un pointeur sur la pile d?exécution, permet d?externaliser l?exécution d?un morceau de code. Par exemple les événements auxquels on s?abonne.

 

Nous allons créer un delegate à la sauce C# 1.1 en utilisant les generics du Framework 2.0, pour comprendre comment linq et les expressions lambda fonctionnent.

 

    sealed public class Day

    {

        public int ID;

        public string Name;

 

        public override string ToString()

        {

            return (ID + " - " + Name);

        }

    }

 

    class ObjectList : List  

    {

        public delegate bool ObjectListFilter(Value value);

 

        public List FilterByMethod1(ObjectListFilter filter)

        {

            List newList = new List();

            foreach (T elt in this)

            {

                if (filter(elt)) newList.Add(elt);

            }

            return (newList);

        }

    }

 

Le délégué ?ObjectListFilter? est une déclaration de pointeur sur une fonction générique qui renvoi un booleen. Le paramètre de cette fonction n?est pas connu à l?avance puisqu?il est de type générique. La méthode ?FilterByMethod1?, renvoi une liste d?élément de type ??filtrée.

 

Voici le code d?utilisation :

 

    class Program

    {

      

        static private bool filterDay(Day jour)

        {

            return 3 && jour.Name.Contains("re"));

        }

 

        static void Main(string[] args)

        {

            #region Exercise  1

            Console.WriteLine("Exercise 1 :");

            Console.WriteLine("List of days before filtering  :");

            ObjectListDay AllDaysExercise1 = new ObjectListDay

               {new Day{ID=1,Name="Lundi"},

                new Day{ID=2,Name="Mardi"},

                new Day{ID=3,Name="Mercredi"},

                new Day{ID=4,Name="Jeudi"},

                new Day{ID=5,Name="Vendredi"},

                new Day{ID=6,Name="Samedi"}};

 

            foreach (var d in AllDaysExercise1)

            {

                Console.WriteLine(d);

            }

 

            ObjectListDay.olFilterDay FilterPointer = new ObjectListDay.olFilterDay(filterDay);

           

            Console.WriteLine("List of days after filtering Methode A :");

            var filterDaysExercise1A = AllDaysExercise1.FilterByMethod1(FilterPointer);

            foreach (var d in filterDaysExercise1A)

            {

                Console.WriteLine(d);

            }

         }

 

La méthode ?static private bool filterDay(Day jour)?, est la fonction qui va s?exécuter de manière déportée dans le code de l?utilisateur final. Pour cela on va créer un handler, c?est à dire le pointeur (le delegue est la signature du pointeur) :

 

ObjectListDay.olFilterDay FilterPointer = new ObjectListDay.olFilterDay(filterDay);

 

La requête de filtrage est ainsi externalisée.

 

Pour simplifier l?écriture, nous allons utiliser une nouveauté du Framework 2.0, que sont les délégués anonymes, qui ne sont qu?une extension des méthodes anonymes.

 

Pour rappel, une méthode anonyme n?est qu?une méthode? sans nom, c'est-à-dire un morceau de code représentant le corps d?une méthode qu?on peut placer là où un pointeur de code (un delegate) est attendu, et ce, dans le respect de la déclaration du delegate (C# reste un langage très fortement typé;). Nous pouvons donc écrire :

            Console.WriteLine("List of days after filtering Methode B :");

            var filterDaysExercise1B = AllDaysExercise1.FilterByMethod1(delegate(Day jour) { return 3 && jour.Name.Contains("re")); });

            foreach (var d in filterDaysExercise1B)

            {

                Console.WriteLine(d);

            }

Vous devez surement reconnaitre la méthode précédente. Nous observons la fusion de la déclaration du pointeur et de la déclaration de la méthode.

 

Si vous suivi jusqu'à present, la suite est une formalité et vous allez comprendre sans difficulté ce que sont les expressions lambda. Il s?agit en fait d?une simplification syntaxique :

 

delegate(Day jour) { return 3 && jour.Name.Contains("re")); }

 

Devient :

3 && jour.Name.Contains("re")

 

Nous écrirons donc :

 

            Console.WriteLine("List of days after filtering Methode C :");

            var 3 && jour.Name.Contains("re"));

            foreach (var d in filterDaysExercise1C)

            {

                Console.WriteLine(d);

            }

 

Jusqu'à présent, nous pouvons nous faire la remarque, que la simplification syntaxique est très présente au niveau du code d?utilisation. Il existe aussi la possibilité d?utiliser une simplification coté classe, grâce à l?utilisation du mot clé ?Func? :

 

    class ObjectList : List  

    {

        public delegate bool ObjectListFilter(Value value);

 

        public List FilterByMethod1(ObjectListFilter filter)

        {

            List newList = new List();

            foreach (T elt in this)

            {

                if (filter(elt)) newList.Add(elt);

            }

            return (newList);

        }

    }

 

Devient :

 

    class ObjectList : List  

    {

 

public List FilterByMethod1(Funcbool filter)

        {

            List newList = new List();

            foreach (T elt in this)

            {

                if (filter(elt)) newList.Add(elt);

            }

            return (newList);

        }

    }

 

Les méthodes d?extensions :

Voici l?une des briques, sans quoi Linq serait beaucoup moins souple. L?objectif étant d?adjoindre à une classe de base que vous ne maitrisez pas une méthode. Dans notre exemple, nous allons ajouter notre méthode de filtrage ? FilterByExtension? à une liste générique ?ListDay?, afin de ne plus avoir à écrire la classe ObjectList.

 

D?abord nous écrirons une classe ?MethodsExtensionForListT? qui supportera la méthode d?extension. Celle-ci doit être statique afin de pouvoir être appelée sans être instanciée. Le mot clé ?this ?suivi du type de l?objet indique à quels objets la méthode s?applique.

 

Categories: Developpements

Post a Comment

Oops!

Oops, you forgot something.

Oops!

The words you entered did not match the given text. Please try again.

Already a member? Sign In

0 Comments