Jiri's Shared IT knowledge

vendredi, juillet 28, 2006

Microsoft dévoile C# v3 et le projet Linq

Pas 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(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 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 ?

Suite de l'article "LINQ"