Microsoft dévoile C# v3 et le projet Linq Par Sami Jaber (webmaster@dotnetguru.org)

Annoncé comme exceptionnelle, la PDC tient ses promesses cette année avec une avalanche  d'annonces. A commencer par la prochaine version de C# (V3) et la naissance d'une nouvelle ère dans le développement d'applications Windows. La persistance, quelle soit  XML ou relationnelle sera au coeur du Framework .NET et du Runtime. Qui l'aurait cru il y a seulement deux ans..

as de doute, la PDC 2005 marque un tournant majeur dans la voie que semble empreinter l'éditeur de Redmond pour l'implémentation de ses produits futurs. Avec l'annonce du projet Linq et de C# V3, nombre d'idées reçus vont certainement volet en éclat. Si cette année "révolution" rime avec "innovation", l'éditeur devra confirmer les  espérances que bon nombre de développeurs placent en C#. Voyons concrètement ce que Microsoft nous propose.

Les nouveautés de C# V3

Il faut avouer que depuis quelques temps, les intentions d'Anders Heljberg étaient plus ou moins connus. L'architecte principal du langage C# souhaitait remettre l'accès aux données au centre des préoccupations de l'éditeur. Même si les rumeurs et les extrapolations allaient bon train, peu d'entre nous y voyaient les prémices de l'actuel Framework Linq. Microsoft, en l'espace de deux ans, s'est attaché à recréer tout un Framework de requêtage ou plutôt une sorte d'abstraction permettant le mapping (un terme jamais prononcé durant les sessions) de n'importe quel objet vers un stockage XML ou relationnel. Une abstraction entièrement basée sur ... les expressions lambda et les méthodes anonymes. Toute cette métamorphose a été rendu possible grâce à l'extension du langage et de la CLR. [ndm : Si vous ne connaissez pas les méthodes anonymes, cet article de Patrick Smacchia sera une excellente entrée en matière]

Avant de nous intéresser à linq, voyons rapidement les nouveautés de C# V3.

C# V3

Les nouveautés concernant C# V3 vont faire frémir les trois quart de la population de développeurs Microsoft. Ceux qui avaient encore un soupçon d'espoir de voir un jour réduire le fossé existant entre ex-développeurs VB (encore réfractaires à certaines évolutions de .NET) et développeurs C# confirmés, risquent d'en prendre un sacré coup au moral. Le langage devient de plus en plus élitiste et des concepts plus ou moins réservés habituellement aux langages fonctionnels (de la famille des Lisp) font leur apparition de manière quasiment généralisée.

Variables locales implicitement typées

Les variables locales implicitement typées permettent de déclarer un objet sans spécifier son type. Exemple :

        var x;                             // Erreur, aucun initialiseur permettant de retrouver le type
        var y = { 1, 2, 3 };     // Erreur, initialiseur de collection non permit
        var z = null;                // Erreur, type null non autorisé

        var i = 5;
        var s = "Hello";
        var d = 1.0;
        var numbers = new int[] { 1, 2, 3 };
        var orders = new Dictionary<int, Order>();
 

Les expressions précédentes sont équivalentes à :

        int i = 5;
        string s = "Hello";
        double d = 1.0;
        int[] numbers = new int[] { 1, 2, 3 };
        Dictionary<int, Order> orders = new Dictionary<int, Order>();

Les méthodes d'extension

Si les méthodes d'extension sont bien parties pour faire bondir certains observateurs, replacés dans le contexte du langage de requête Linq, on comprend beaucoup mieux leur intérêt (même si ce concept n'a strictement rien d'objet).

Lorsqu'un utilisateur invoque une méthode sur n'importe quelle instance d'un objet, .NET vérifie s'il existe une méthode susceptible de répondre à son besoin dans la classe source de l'objet. Le cas échéant il recherche la dite méthode dans les éventuelles classes d'extension. S'il trouve une signature correspondant à l'appel il invoque cette méthode en passant en paramètre l'instance en question. Exemple : 

namespace DNG.Extensions {
      public static class MyExtension {
            public static int ToInt32(this string s) {
                  return Int32.Parse(s);
            }
            public static T ElementAt<T>(this T[] source, int index) {
                  T result = source[index];
                 
return result;
            }
      }
}

Vous aurez remarqué au passage le nouveau mot clé "this" préfixant le premier argument. L'utilisation des extensions consiste à importer la classe statique puis à faire appel à une méthode d'instance :

        using DNG.Extensions;

        string s = "1234";
        int i = s.ToInt32();                         // Exécute MyExtension.ToInt32(s)
       
int[] digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        int a = digits.ElementAt(4);          // Exécute MyExtension.ElementAt(digits, 4)

 Convaincu ?

Les expressions lambda

Dans la continuité des méthodes anonymes de C# V2, les expressions lambda sont indéniablement la nouveauté la plus importante de C# V3. Cette fonctionnalité permet en particulier au Framework linq de proposer un typage sous forme de lambda expressions.

Voici quelques exemple d'expressions lambda :

x => x + 1                 // Implicitement typé, expression de type corps
x => { return x + 1; }      // Implicitement typé, corps de délégué
(int x) => x + 1            // Explicitement typé, expression de type corps
(int x) => { return x + 1; } // Explicitement typé, corps de délégué
(x, y) => x * y             // Paramètres multiples
() => Console.WriteLine()   // Une expression sans paramètre, étrange hein :-)

Ceux maîtrisant mieux les expressions sous la forme de délégués ont juste à imaginer qu'une expression lambda est simplement un délégué. Nous reviendrons dans d'autres articles sur leur utilisation, mais linq reste le meilleur cas d'exemple des expressions lambda.

L'inférence de type

Lorsqu'une méthode générique est appelée sans spécifier d'argument, un processus appelé "inférence de type" s'attache à retrouver les différentes paramètres de la méthode "à inférer". Typiquement, une expression lambda passée en argument à une méthode générique déclenchera cette opération. Exemple :

List<Customer> customers = GetCustomerList();
IEnumerable
<string> names = customers.Select(c => c.Name);

Évidemment, cette fonctionnalité a également une incidence sur la manière de résoudre la surcharge lorsqu'une expression lamda est passé en argument.

Les initialiseurs d'objets et de collections

Au rang des fonctionnalités controversées, les initialiseurs tiendront sûrement une bonne place. Ils remettent en cause un des fondements principaux de la programmation objet qui est que toute initialisation non proposée par un constructeur (d'instance ou statique) ne peut être considéré comme conforme au contrat de la classe.

public class Point {
      int x, y;
      public int X { get { return x; } set { x = value; } }
      public int Y { get { return y; } set { y = value; } }

}

var a = new Point { X = 0, Y = 1 };

Cette écriture est sémantiquement égale à :

var a = new Point();
a.X = 0;
a.Y = 1;

Pour aller plus loin, la classe suivante est un rectangle constitué de points :

public class Rectangle {
      Point p1, p2;
      public Point P1 { get { return p1; } set { p1 = value; } }
      public Point P2 { get { return p2; } set { p2 = value; } }
}

Un rectangle peut donc être créé en utilisant des initialiseurs de points de la manière suivante :

var r = new Rectangle {
      P1 = new Point { X = 0, Y = 1 },
      P2 = new Point { X = 2, Y = 3 }
};

Ce qui génèrera le code suivant après traitement du compilateur :

var r = new Rectangle();
var __p1 = new Point();

__p1.X = 0;
__p1.Y = 1;
r.P1 = __p1;
var __p2 = new Point();
__p2.X = 2;
__p2.Y = 3;

r.P2 = __p2;

Où __p1 et ___p2 sont des variables temporaires inaccessibles à l'utilisateur.  

 Convaincu ?

Linq

Le principe de Linq consiste à proposer un ensemble d'opérateurs pouvant être redéfinis et enrichis par toute implémentation tierce. Microsoft fournit deux implémentations : Dlinq pour la partie mapping objet/relationnel et Xlink pour la partie XML. Cette architecture est illustré par le schéma suivant :

Une requête simple

Le langage propose dorénavant la possibilité de requêter directement une collection (implémentant IEnumerable) en utilisant des mots clés réservés, démonstration :

using System;
using
System.Query;
using
System.Collections.Generic; 

class app {
  static void Main() {
    string[] auteurs = { "Roger", "Bilou", "Thomas",
                       "Titi", "Lou", "Eric",
                       "Toto"};
    IEnumerable<string> expr = from s in auteurs
                               where s.Length == 5
                               orderby s
                               select s.ToUpper();
    foreach (string item in expr)
      Console.WriteLine(item);  }

}

L'exécution de cette fonction affichera : ROGER, BILOU .

Sous le capot, les mots clés Where, OrderBy et Select sont pré-processés par le compilateur pour générer une expression plus proche de la sémantique habituelle des langages objets. Le code suivant est le résultat de la compilation

IEnumerable<string> expr = names
                           .Where(s => s.Length == 5)
                           .OrderBy(s => s)
                           .Select(s => s.ToUpper());

 Ceux qui ont l'habitude des outils de mapping objet/relationnel reconnaîtront une syntaxe proche de l'arbre d'expressions utilisé par des outils tels que Hibernate. En revanche l'innovation est criante lorsqu'il s'agit du typage de l'expression. Là où la plupart des outils de mapping se reposent sur un requêtage plutôt textuel et donc faiblement typé, C# propose l'exécution d'une expression lambda. Il suffit d'appréhender le paramètres s=>s.Length == 5  comme le code d'un simple délégué .NET qui sera passé au moteur de mapping.

Lors de la phase de requêtage, C# créé un arbre d'expression (un arbre de délégués) constitué de toute une panoplie d'opérateurs spécialisés, de prédicats aux projections en passant par les filtres et les tris, puis réalise l'interprétation de cet arbre lors de l'invocation finale ... Toute la force du lambda calcul au service de C# et du mapping objet. Une approche totalement déroutante qui va certainement susciter de grands débats dans les jours prochains.

Mapping Objet/XML

Xlink est la partie mapping XML. Ce Framework propose toute une hiérarchie de classes (un équivalent de DOM). La création d'un document consiste à tirer partie de ces classes de la manière suivante :

string myNs = "{http://dng.com}";

XElement auteurs=
      new XElement(myNs+"auteurs",
            new XElement(myNs+"auteur",
                  new XElement(myNs+"nom", "Thomas Gil"),
                  new XElement(myNs+"telephone", "098766",
                      new XAttribute("ou", "maison")),
                  new XElement(myNs+"telephone", "098765",
                      new XAttribute("ou", "bureau")),
                  new XElement(myNs+"adresse",
                        new XElement(myNs+"rue", "5, rue titi"),
                        new XElement(myNs+"ville", "Paris"),
                  )
            )
      );

Ce code génèrera le document suivant :

<auteurs xmlns="http://dng.com">
      <auteur>
            <nom>Thomas gil</nom>
            <telephone ou="maison">098766</telephone>
            <telephone ou="bureau">098765</telephone>
            <adresse>
                  <rue>5, rue titi</rue>
                  <ville>Mercer Island</ville>
            </adresse>
      </auteur>
</auteurs> 

 Rechercher une information avec l'API Query consistera à  faire :

XElement auteurs = new XElement("auteurs",
                        from p in auteurs
                        select new XElement("auteur",
                            new XElement("nom", p.Nom),
                            from ph in p.Telephone
                            select new XElement("telephone", ph)
                        )

                    );

Console.WriteLine(auteurs);

Mapping Objet/Relationnel

Le meilleur pour la fin. Que ceux qui pensaient que Microsoft avait abandonné à tout jamais le mapping objet/relationnel se rassurent. Non seulement il revient en force mais il sera au coeur de la plupart des futures applications WinFX de l'éditeur. S'il est vrai aujourd'hui qu'il reste encore des pans de fonctionnalités à combler, l'essentiel est là, il s'appelle dlinq. Assez de littérature, voyons maintenant du code. Voici un exemple de création et modification d'enregistrement dans une base relationnelle sur le même modèle que l'exemple XML précédent :

Northwind db = new Northwind("c:\\northwind\\northwnd.mdf");
// Recherche un auteur
particulier
string
id = "1";
var auteur = db.Auteurs.First(c => c.AuteurID == id);

// Change le nom de l'auteur

auteur.Nom = "L'ami S'ami";

// Recherche un article de cet auteur

Article article= auteur.Articles[0];

// L'enlève de la table et de la liste des articles

db.Articles.Remove(article);

// Créé un nouvel article et l'ajoute à la collection

Article art = new Article{ articleId = 12345 };
auteur.Articles.Add(art);

// Demande au persistence manager de sauver l'objet

db.SubmitChanges();

 Vous souhaitez faire une requête ? Rien de plus simple :

var result =
      from c in db.Auteurs
      where c.Ville == "Paris"
      select c;

foreach (Auteur auteur in result)
      Console.WriteLine(auteur.Nom);

Et le modèle du domaine ? Le voilà :

[Table(Name="Articles")]
public class Article
{
      [Column(Id=true)]
      public int ArticleID;

      [Column]
      public string ArticleID;

      private EntityRef<Auteur> _Auteur;   

      [Association(Storage="_Auteur", ThisKey="AuteurID")]
      public Auteur Auteur{
            get { return this._Auteur.Entity; }
            set { this._Auteur.Entity = value; }
      }
}

Une forme d'écriture très proche de ce qu'on retrouve sur le marché dans le domaine. Aujourd'hui, le Framework  propose plusieurs opérateurs, de nombreuses fonctionnalités sont couvertes telles que :

- le Lazy Loading (appelé Deffered Loading)
- la gestion des relations (0,n - 1,1, etc..)
- la gestion du cycle de vie d'une entité
- les procédures stockées
- Un générateur de modèle du domaine à partir d'un fichier de configuration (appelé SQLMetal)
- Un générateur de fichier de configuration à partir d'un modèle du domaine
- L'attachement et le détachement

En revanche, il reste encore à couvrir :

- L'héritage et les requêtes polymorphiques

Quant aux transactions, voici un exemple qui devrait rassurer :

using(TransactionScope ts = new TransactionScope()) {
      db.UseLocalTransactionsOnly = true;
      db.SubmitChanges();
      ts.Complete();
}

Tout naturellement l'API System.Transactions aura la charge de gérer les propriétés transactionnelles des entités. Enfin !

Conclusion

C# V3 et le Framework Linq sont une révolution pour Microsoft même si aujourd'hui il est encore difficile d'entrevoir l'impact de tels changements. Jamais l'éditeur n'avait pris un tel risque technologique tant d'un point de vue de l'implémentation que du de design. Depuis la création de DNG, nous n'avons cesse d'insister sur l'importance des concepts de séparation des couches, de persistance, d'architecture orientées services. Tout ce dont on rêvait pour .NET prend aujourd'hui forme avec même une tournure plutôt radicale qui a tendance à dérouter. Utiliser des expressions lambda comme base d'un langage de requête, il fallait oser. Dorénavant à la question "where do you want to go today ?" difficile de répondre autrement que par "to the fucking shiping date ". Car à force d'attendre , on risque de perdre patience...


       Téléchargez Linq : http://msdn.microsoft.com/vcsharp/future/