|
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... |