Par Erik Meijer, Amanda Silver et Paul Vick, Microsoft France
Résumé : Fournit une vue d'ensemble des nouvelles fonctionnalités du langage Visual Basic et de ses nouvelles extensions, qui prennent en charge la programmation d'applications recourant de façon intensive aux données.
Get an Overview of Visual Basic 9.0 in Visual Studio 2005
Sur cette page
Introduction
Mise en route de Visual Basic 9.0
Variables locales typées de façon implicite
Initialiseurs d'objets et de tableaux
Types anonymes
Prise en charge étendue du code XML
Interprétation des requêtes
Différences des expressions de requête par rapport au langage SQL : Composition et données hiérarchiques
Méthodes d'extension et expressions lambda
Types nullables
Délégués non stricts
Conclusion
Introduction
Visual Basic a toujours eu pour mission principale de créer des applications métier pragmatiques, orientées données. Alors que le transfert vers .NET a apporté toute la puissance d'une infrastructure unifiée et d'une plate-forme gérée aux développeurs d'applications, la nouvelle version de Visual Basic inclut une série de fonctionnalités qui permettent d'améliorer de façon radicale la productivité du développeur lors de la création d'applications orientés données. Les extensions de langage introduisent des fonctionnalités de requête universelles qui s'appliquent à toutes les sources de données, qu'elles proviennent de graphiques relationnels, hiérarchiques ou de documents XML.
Ce document offre une vue d'ensemble informelle de ces nouvelles fonctionnalités. Vous trouverez plus d'informations, y compris les mises à jour de la définition de langage Visual Basic et les aperçus de compilation dans le Centre de développement Visual Basic.
Mise en route de Visual Basic 9.0
Pour prendre la mesure de la puissance de ces fonctionnalités, commençons par un exemple pris dans le monde réel, la base de données CIA World Factbook. Cette base de données contient une variété d'informations géographiques, économiques, sociales et politiques sur tous les pays du monde. Dans le cadre de notre exemple, nous commençons par un schéma pour le nom de chaque pays et sa capitale, sa superficie et sa population. Nous représentons ce schéma dans Visual Basic 9.0 à l'aide de la classe suivante (le pseudo code est utilisé par souci de concision) :
Class Country
Public Property Name As String
Public Property Area As Long
Public Property Population As Integer
End Class
Voici un petit sous-ensemble de la base de données de pays que nous utiliserons dans notre exemple :
Dim countries = {
New Country With { .Name = "Palau", .Area = 458, .Population = 16952 }, _
New Country With { .Name = "Monaco", .Area = 1.9, .Population = 31719 }, _
New Country With { .Name = "Belize", .Area = 22960, .Population = 219296 }, _
New Country With { .Name = "Madagascar", .Area = 587040, .Population = 13670507}
}
Avec cette liste, nous pouvons rechercher tous les pays dont la population est inférieure à un million d'habitants au moyen de l'expression de requête suivante :
Dim smallCountries = From country In countries _
Where country.Population < 1000000 _=""
Select="" country=""
For="" Each="" country="" In="" SmallCountries=""
Console.WriteLine(country.Name="")
Next=""
Dans la mesure où seul Madagascar compte plus d'un million d'habitants, le programme ci-dessus imprimerait la liste suivante de noms de pays, une fois compilé et exécuté :
Palau
Monaco
Belize
Examinons ce programme pour identifier les fonctionnalités de Visual Basic 9.0 qui rendent l'écriture si simple. Tout d'abord, la déclaration de chacune des expressions qui représente les pays utilise la nouvelle syntaxe d'initialisation d'objet New Country With {..., .Area = 458, ...} pour créer une instance d'objet complexe à l'aide d'une syntaxe d'expression concise, semblable à l'instruction With existante.
Cette déclaration illustre également les déclarations de variables locales typées de façon implicite, où le compilateur déduit le type de la variable locale, Country, à partir de l'expression d'initialiseur, dans la partie droite de la déclaration. La déclaration ci-dessus est un équivalent exact d'une déclaration de variable locale typée de façon explicite, du type Country().
Dim countries As Country() = {...}
En résumé, il s'agit bien d'une déclaration fortement typée. Le compilateur a déduit de façon automatique le type de la partie droite de la déclaration locale et le programmeur n'a pas à entrer ce type manuellement.
La déclaration de variable locale, SmallCountries, est initialisée avec une expression de requête de type SQL pour filtrer les pays qui ont moins d'un million d'habitants. La ressemblance avec SQL est intentionnelle, car elle permet aux programmeurs qui connaissent déjà SQL de passer directement à la syntaxe de requête de Visual Basic.
Dim smallCountries = From country In Countries _
Where country.Population < 1000000 _=""
Select="" country=""
Cet exemple de code présente une autre application du typage implicite : Le compilateur déduit le type de SmallCountries comme IEnumerable(Of Country ) en fonction du type de résultat de l'expression de requête. Le compilateur traduit en retour l'expression de requête en appels à l'API activée pour LINQ, qui implémente les opérateurs de requête pour tous les types qui implémentent IEnumerable(Of T). Dans ce cas, la traduction est aussi simple que ce qui suit :
Dim smallCountries As IEnumerable(Of Country) = _
Countries.Where(Function(country) country.Population < 1000000). _=""
Select(Function(country="") country="")
La syntaxe étendue dépend des expressions lambda, qui représentent des fonctions en ligne qui renvoient le résultat d'une expression. L'expression lambda est convertie en un délégué et passée à la fonction d'extension Where, qui est définie dans la bibliothèque d'opérateurs de requête standard en tant qu'extension de l'interface IEnumerable (Of T).
Maintenant que nous avons présenté quelques-unes des nouvelles fonctionnalités de Visual Basic 9.0, nous allons approfondir notre présentation.
Variables locales typées de façon implicite
Dans une déclaration de variable locale typée de façon implicite, le type de la variable locale est déduit à partir de l'expression d'initialisation, dans la partie droite d'une instruction de déclaration locale. Par exemple, le compilateur déduit les types de toutes les déclarations de variables suivantes :
Dim population = 31719
Dim name = "Belize"
Dim area = 1.9
Dim country = New Country With { .Name = "Palau", ...}
Ainsi, elles sont précisément équivalentes à ce qui suit, soit des déclarations typées de façon explicite :
Dim population As Integer = 31719
Dim name As String = "Belize"
Dim area As Float = 1.9
Dim country As Country = New Country With { .Name = "Palau", ...}
Dans la mesure où les types de déclaration de variable locale sont déduits avec la nouvelle fonctionnalité Option Infer On (valeur par défaut des nouveaux projets), quelle que soit la valeur de Option Strict, l'accès à ces variables est toujours à liaison anticipée. Le programmeur doit spécifier de façon explicite la liaison tardive dans Visual Basic 9.0, en déclarant de façon explicite des variables telles que Object, comme suit :
Dim country As Object = New Country With { .Name = "Palau", ... }
La déduction du type prévient l'activation accidentelle de liaisons tardives et, plus important, elle permet à des extensions puissantes de se lier à de nouveaux types de données, tels que XML, comme nous le verrons plus bas.
La variable de contrôle de boucle d'une instruction For...Next ou For Each..Next peut également être typée de façon implicite. Lorsque ce type de variable est spécifié, comme dans For I = 0 To SmallCountries.Count ou For Each country In smallCountries, l'identificateur définit une nouvelle variable locale typée de façon implicite, dont le type est déduit à partir de l'initialiseur ou de l'expression de collection et porte sur toute la boucle. Grâce à cette application de la déduction du type, nous pouvons récrire la boucle qui imprime tous les petits pays comme suit :
For Each country In smallCountries
Console.WriteLine(country.Name)
Next
Le type de pays est défini par déduction sur Country, qui est le type d'élément de SmallCountries.
Initialiseurs d'objets et de tableaux
Dans Visual Basic, l'instruction With simplifie l'accès aux membres multiples d'une valeur agrégée sans avoir à spécifier l'expression cible plusieurs fois. Dans le bloc d'instructions With, une expression d'accès aux membres commençant par un point est évaluée comme si le point suivait une l'expression cible de l'instruction With. Par exemple, les instructions suivantes initialisent une nouvelle instance Country et initialisent ensuite ses champs en fonction des valeurs requises :
Dim palau As New Country()
With palau
.Name = "Palau"
.Area = 458
.Population = 16952
End With
Les nouveaux initialiseurs d'objets de Visual Basic 9.0 sont des expressions de With qui permettent de créer des instances d'objet complexes avec concision. Les initialiseurs d'objet permettent de regrouper les deux instructions ci-dessus dans une déclaration locale unique (typée de façon implicite), comme suit :
Dim palau = New Country With { _
.Name = "Palau", _
.Area = 458, _
.Population = 16952 _
}
Ce type d'initialisation d'objet à partir d'expressions est important pour les requêtes. De manière générale, une requête ressemble à une déclaration d'objet initialisée par une clause Select, à droite du signe égal. Dans la mesure où la clause Select renvoie une expression, nous devons pouvoir initialiser l'ensemble de l'objet avec une seule expression.
Comme indiqué plus haut, les initialiseurs d'objet permettent également de créer des collections d'objets complexes. Les tableaux peuvent être initialisés et les types d'élément déduits à l'aide d'une expression d'initialisation de tableau. Par exemple, en nous appuyant sur la déclaration des villes en tant que classe,
Class City
Public Property Name As String
Public Property Country As String
Public Property Longitude As Long
Public Property Latitude As Long
End Class
nous pouvons créer un tableau de capitales pour notre d'exemple, comme suit :
Dim Capitals = { _
New City With { _
.Name = "Antanarivo", _
.Country = "Madagascar", _
.Longitude = 47.4, _
.Latitude = -18.6 }, _
New City With { _
.Name = "Belmopan", _
.Country = "Belize", _
.Longitude = -88.5, _
.Latitude = 17.1 }, _
New City With { _
.Name = "Monaco", _
.Country = "Monaco", _
.Longitude = 7.2, _
.Latitude = 43.7 }, _
New City With { _
.Country = "Palau",
.Name = "Koror", _
.Longitude = 135, _
.Latitude = 8 } _
}
Types anonymes
Souvent, nous devons juste supprimer ou « projeter » certains membres d'un type comme résultat d'une requête. Par exemple, nous pouvons demander uniquement le nom et le pays de toutes les capitales situées sur ou près des tropiques, à l'aide des colonnes Latitude et Longitude des données source pour identifier les tropiques, tout en masquant ces colonnes dans le résultat. Dans Visual Basic 9.0, nous créons pour ce faire une nouvelle instance d'objet, sans en nommer le type, pour chaque ville, C, dont la latitude est comprise entre les tropiques du Cancer et du Capricorne :
Const TropicOfCancer = 23.5
Const TropicOfCapricorn = -23.5
Dim tropical = From city In Capitals _
Where TropicOfCancer <= city.Latitude _
AndAlso city.Latitude >= TropicOfCapricorn _
Select New With {city.Name, city.Country}
Le type déduit de la variable locale, Tropical, est une collection d'instances d'un type anonyme, soit (en pseudo-code) IEnumerable(Of { Name As String, Country As String }). Le compilateur de Visual Basic crée ensuite une classe implicite, par exemple _Name_As_String_Country_As_String_, dont les noms de membre et les types sont déduits à partir de l'initialiseur d'objet, comme suit :
Class _Name_As_String_Country_As_String_
Public Property Name As String
Public Property Country As String
...
End Class
Au sein du même programme, le compilateur fusionne des types anonymes identiques. Deux initialiseurs d'objet anonymes qui spécifient une séquence de propriétés des mêmes noms et types, dans le même ordre, produisent des instances du même type anonyme. En externe, les types anonymes générés par Visual Basic sont effacés dans Object, ce qui permet au compilateur de passer de façon uniforme des types anonymes en tant qu'arguments et résultats de fonctions.
Dans la mesure où les types anonymes sont généralement utilisés pour projeter des membres d'un type existant, Visual Basic 9.0 autorise la notation sténographique de projection New With { city.Name, city.Country } afin d'abréger la forme longue, New With { .Name = city.Name, .Country = city.Country }. Dans le cadre d'une expression de résultat d'une interprétation de requête, nous pouvons abréger davantage les initialiseurs de projection, comme suit :
Dim Tropical = From city In Capitals _
Where TropicOfCancer <= city.Latitude _
AndAlso city.Latitude >= TropicOfCapricorn _
Select city.Name, city.Country
Les deux formes abrégées ont exactement la même signification que la forme longue ci-dessus.
Prise en charge étendue du code XML
LINQ to XML constitue une nouvelle API de programmation du code XML en mémoire, conçue spécialement pour exploiter les fonctionnalités les plus récentes de .NET Framework, telles que l'infrastructure LINQ (Language-Integrated Query). Tout comme les interprétations des requêtes apportent une syntaxe familière et pratique reprenant les opérateurs de requête standard de .NET Framework, Visual Basic 9.0 offre une prise en charge étendue de LINQ to XML à l'aide des littéraux XML et des propriétés XML.
Pour illustrer les littéraux XML, interrogeons les sources de données relationnelles plates, Countries et Capitals, pour créer un modèle XML hiérarchique qui incorpore la capitale de chaque pays en tant qu'élément enfant et calcule la densité de la population en tant qu'attribut.
Pour rechercher la capitale d'un pays donné, nous créons une liaison entre le membre nom de chaque pays et le membre pays de chaque ville. À partir du pays et de sa capitale, nous pouvons facilement construire le fragment de code XML en remplissant les espaces d'expression intégrés avec des valeurs calculées. Nous pouvons créer cet « espace » d'expression Visual Basic avec une syntaxe proche de celle d'ASP, comme dans Name=<%= country.Name %> ou Name><%= city.Name %>< /Name>.< La requête ainsi obtenue, qui combine les littéraux XML et les interprétations des requêtes, est illustrée ci-dessous :
Dim countriesWithCapital As XElement = _
<Countries>
<%= From country In Countries, city In Capitals _
Where country.Name = city.Country _
Select <Country Name=<%= country.Name %>
Density=<%= country.Population / country.Area %>>
<Capital>
<Name><%= city.Name %></Name>
<Longitude><%= city.Longitude %></Longitude>
<Latitude><%= city.Latitude %></Latitude>
</Capital>
</Country> _
%>
</Countries>
Le type XElement peut être omis de la déclaration, auquel cas il serait déduit, comme toute autre déclaration locale.
Dans cette déclaration, le résultat de la requête Select doit être remplacé dans l'élément <Countries>
. Ainsi, la requête Select vient remplir le premier espace, délimité par des balises facilement identifiables de type ASP <%= et %> dans <Countries>
. Dans la mesure où le résultat d'une requête Select est une expression et que les littéraux XML sont des expressions, il est normal, ensuite, d'incorporer un autre littéral XML dans le corps de la requête Select. Ce littéral incorporé dispose lui-même d'espaces d'attribut incorporés pour Country.Name et la densité calculée de la population Country.Population/Country.Area, et d'espaces incorporés pour le nom et les coordonnées de la capitale.
Une fois compilée et exécutée, la requête ci-dessus renvoie le document XML suivant (reformaté légèrement par rapport à l'IDE standard pour des raisons d'espace)
<Countries>
<Country Name="Palau" Density="0.037117903930131008">
<Capital>
<Name>Koror</Name>
<Longitude>135</Longitude>
<Latitude>8</Latitude>
</Capital>
</Country>
<Country Name="Monaco" Density="16694.21052631579">
<Capital>
<Name>Monaco</Name>
<Longitude>7.2</Longitude>
<Latitude>3.7</Latitude>
</Capital>
</Country>
<Country Name="Belize" Density="9.5512195121951216">
<Capital>
<Name>Belmopan</Name>
<Longitude>-88.5</Longitude>
<Latitude>17.1</Latitude>
</Capital>
</Country>
<Country Name="Madagascar" Density="23.287181452711909">
<Capital>
<Name>Antananarivo</Name>
<Longitude>47.4</Longitude>
<Latitude>-18.6</Latitude>
</Capital>
</Country>
</Countries>
Visual Basic 9.0 compile les littéraux XML pour obtenir des objets System.Xml.Linq standard, ce qui assure une interopérabilité complète entre Visual Basic et les autres langages recourant à LINQ to XML. Dans notre exemple de requête, le code produit par le compilateur (si nous pouvions le voir) aurait la forme suivante :
Dim countriesWithCapital As XElement = _
New XElement("Countries", _
From country In Countries, city In Capitals _
Where country.Name = city.Country _
Select New XElement("Country", _
New XAttribute("Name", country.Name), _
New XAttribute("Density", country.Population/country.Area), _
New XElement("Capital", _
New XElement("Name", city.Name), _
New XElement("Longitude", city.Longitude), _
New XElement("Latitude", city.Latitude))))
Outre la création de code XML, Visual Basic 9.0 simplifie également l'accès aux structures XML à l'aide des propriétés XML. De façon plus précise, les identificateurs du code Visual Basic sont liés lors de l'exécution aux attributs et éléments XML correspondants. Par exemple, nous pouvons imprimer la densité de la population de tous les pays de notre exemple, comme suit :
-
Utilisez l'axe enfant countriesWithCapital.<Country> pour obtenir tous les éléments « Country » à partir de la structure XML countriesWithCapital.
-
Utilisez l'axe d'attributs country.@Density pour obtenir l'attribut « Density » de l'élément Country.
-
Utilisez l'axe des descendants country...<Latitude>, écrit littéralement sous forme de trois points dans le code, pour obtenir tous les enfants « Latitude » de l'élément Country, quelle que soit leur profondeur dans la hiérarchie.
-
Définissez la propriété extension .Value sur IEnumerable(Of XElement) pour sélectionner la valeur du premier élément de la séquence résultante ou de l'indexeur d'extension (i) pour sélectionner l'élément i-ème.
En cumulant toutes ces fonctionnalités, il est possible de condenser et simplifier le code de façon spectaculaire :
For Each country In countriesWithCapital.<Country>
Console.WriteLine("Density = " & country.@Density)
Console.WriteLine("Latitude = " & country...<Latitude>.Value)
Next
Le compilateur sait appliquer des liaisons tardives sur les objets normaux lorsque l'expression cible d'une déclaration, une affectation ou une initialisation est de type Object de façon spécifique. De même, le compilateur sait appliquer des liaisons au code XML lorsque l'expression cible est de type, ou une collection de, XElement, XDocument ou XAttribute.
En cas d'une liaison tardive de code XML, le compilateur procède à la traduction suivante :
-
L'expression de l'axe enfant countriesWithCapital.<Country> se traduit par un appel brut de LINQ to XML, countriesWithCapital.Elements("Country"), qui renvoie la collection de tous les éléments enfants appelés « Country » de l'élément Country.
-
L'expression de l'axe des attributs country.@Density se traduit par Country.Attribute("Density").Value, qui renvoie l'attribut enfant unique appelé « Density » de Country;.
-
L'expression de l'axe des descendants country...<Latitude> se traduit par un appel LINQ to XML brut, country.Descendants(“Latitude”), qui renvoie la collection de tous les éléments appelés, quelle que soit leur profondeur par rapport à country.
Interprétation des requêtes
Un opérateur de requête est un opérateur tel que Select, Oder By ou Where qui peut être appliqué à une collection de valeurs en une seule opération. Une expression de requête est une expression qui applique une série d'opérateurs de requête à une collection particulière. Par exemple, l'expression de requête suivante prend une collection de pays et renvoie les noms de tous les pays qui comptent moins d'un million d'habitants :
Dim smallCountries = From country In Countries _
Where country.Population < 1000000 _=""
Select="" country=""
La syntaxe de l'expression de requête est conçue pour être aussi proche que possible de la syntaxe relationnelle SQL standard, afin de permettre à toute personne connaissant le code SQL d'utiliser rapidement des expressions de requête. Cela ne veut pas dire, cependant, que la syntaxe est limitée par SQL, ni que les expressions de requête sont une simple adaptation de SQL pour Visual Basic. Dans la mesure où SQL a été conçu pour un modèle purement relationnel, certains de ses idiomes ne sont pas adaptés à un système typé qui non seulement tolère, mais aussi encourage, les hiérarchies. De plus, certains éléments syntaxiques et sémantiques du code SQL créent des conflits ou s'intègrent mal à la syntaxe ou la sémantique Visual Basic existante. Ainsi, alors que certaines expressions de requête paraîtront familières à toute personne habituée à manipuler le code SQL, ces dernières recouvrent des différences importantes dont il faut avoir conscience.
Les expressions de requête sont traduites en appels aux opérateurs de séquence sous-jacents pour les types pouvant faire l'objet d'une requête et spécifiés en tant que type source dans la clause From. Étant donné que les opérateurs de séquence sont généralement définis en tant que méthodes d'extension sur le type de source, ils sont liés aux opérateurs de séquence en termes de portée. Ceci implique que lors de l'importation d'une implémentation donnée, la syntaxe de l'expression de requête peut être de nouveau limitée à différentes API activées pour LINQ. C'est ainsi que les expressions de requête peuvent être liées de nouveau à une implémentation qui utilise LINQ to SQL ou LINQ to Objects (moteur d'exécution de requête local qui exécute la requête en mémoire).
Certains opérateurs de requête tels que From, Select et Group By, introduisent un type spécial de variable locale appelé variable de plage. Par défaut, la portée d'une variable de plage va de l'opérateur d'introduction à un opérateur qui masque cette variable et représente une propriété ou une colonne de la ligne dans une collection en fonction des résultats de l'évaluation de la requête. Par exemple, dans la requête suivante :
Dim smallCountries = From country In Countries _
Where country.Population < 1000000 _=""
Select="" country=""
L'opérateur From introduit une variable de plage country typée sous le nom « Country ». L'opérateur de requête suivant, Where, fait référence à la variable de plage country pour représenter chaque client dans l'expression de filtrage country.population < 1000000.
Certains opérateurs de requête, tels que Distinct, n'utilisent ou ne modifient pas la variable de contrôle. D'autres opérateurs de requête tels que Select masquent les variables de plage actuelles dans le cadre de leur portée et introduisent de nouvelles variables de plage. Par exemple, dans la requête :
Dim smallCountries = From country In Countries _
Select country.Name, Pop = country.Population
Order By Pop
L'opérateur de requête Order By a uniquement accès aux variables de plage Name et Pop qui sont introduites par l'opérateur Select. Si l'opérateur Order By avait tenté de référencer Country, une erreur de compilation se serait produite.
Si une requête se termine sans opérateur Select, le type d'élément résultant de la collection revient à avoir effectué une projection Select avec toutes les variables de contrôle incluse dans la portée :
Dim countriesWithCapital = From country In Countries, city In Capitals _
Where country.Name = city.Country
The inferred type for this local declaration is (in pseudo-code to represent an anonymous type)
IEnumerable(Of { Country As Country, City As City }).
Différences des expressions de requête par rapport au langage SQL : Composition et données hiérarchiques
Les expressions de requête de Visual Basic 9.0 sont entièrement compositionnelles, ce qui signifie que les interprétations de requête peuvent être incorporées de façon arbitraire ou créées en ajoutant une requête avec des opérateurs de requête supplémentaires. Cet aspect compositionnel facilite l'interprétation des requêtes complexes en traitant de façon isolée les différentes sous-expressions et permet de suivre facilement la sémantique et les types qui découlent de chaque opérateur de requête. Cependant, l'aspect compositionnel érigé en tant que principe de conception offre un environnement d'écriture totalement différent par rapport au code SQL, où les requêtes sont analysées comme un bloc monolithique.
De plus, les API activées pour LINQ ont tendance à implémenter les opérateurs de séquence avec une exécution différée. L'exécution différée signifie que la requête n'est pas évaluée tant que ses résultats n'ont pas été énumérés. Pour LINQ to SQL, ceci signifie que la requête n'est pas accédée à distance par le code SQL tant que ses résultats ne sont pas requis. Ceci veut dire que la division des requêtes en plusieurs instructions ne signifie pas que la base de données est consultée plusieurs fois. Par conséquent, les requêtes qui seraient normalement incorporées dans SQL deviennent des requêtes compositionnelles dans LINQ.
L'une des raisons pour lesquelles SQL n'est pas compositionnel est que le modèle de données relationnel sous-jacent lui-même n'est pas de type compositionnel. Par exemple, les tables ne peuvent pas contenir de sous-tables. En d'autres termes, toutes les tables doivent être plates. Par conséquent, au lieu de fractionner des expressions complexes pour obtenir des unités plus petites, les programmeurs SQL écrivent des expressions monolithiques qui donnent des tables plates, conformément au modèle de données SQL. Dans la mesure où Visual Basic repose sur le système de typage CLR, il n'y a aucune restriction concernant les types qui peuvent apparaître sous forme de composants d'autres types. En dehors des règles de typage statiques, aucune restriction ne s'applique quant au type d'expressions pouvant apparaître en tant que composants d'une autre expression. Par conséquent, non seulement les lignes, les objets et le code XML, mais aussi Active Directory, les fichiers, les entrées de registre et ainsi de suite ont tous le même rang dans les sources et les résultats de requêtes.
Opérateurs de requête
Les personnes familières avec l'implémentation de SQL reconnaîtront, dans les opérateurs de séquence .NET sous-jacents, de nombreux opérateurs algébriques relationnels compositionnels tels que projection, selection, cross-product, grouping et sorting qui représentent les plans de requête dans le processeur de requêtes.
-
L'opérateur From introduit une ou plusieurs variables de plage et spécifie la collection faisant l'objet de la requête ou calcule une valeur pour la variable de plage.
-
L'opérateur Select spécifie la forme de la collection de sortie.
-
Les opérateurs Where et Distinct limitent les valeurs de la collection.
-
L'opérateur Order By impose un tri à la collection.
-
Les opérateurs Skip, Skip While, Take et Take While renvoient un sous-ensemble d'une collection en fonction de l'ordre ou d'une condition.
-
Les opérateurs Union, Union All, Except et Intersect regroupent deux collections dans une collection unique.
-
L'opérateur Group By groupe la collection en fonction d'une ou plusieurs clés.
-
Les opérateurs Avg, Sum, Count, Min et Max agrègent une collection et renvoient une valeur.
-
Les opérateurs Any et All agrègent une collection et renvoient une valeur booléenne en fonction d'une condition.
-
L'opérateur Join prend deux collections et produit une collection unique en fonction de concordances de clés dérivées à partir des éléments.
-
L'opérateur Group Join effectue une liaison groupée de deux collections en fonction de clés concordantes, extraites à partir des éléments.
La syntaxe complète de l'ensemble des opérateurs figure dans la documentation complète du langage. Cependant, à des fins d'illustration, le code suivant recherche les capitales des pays et trie les noms de pays en fonction de la latitude de la capitale :
Dim countriesWithCapital = _
From country In Countries _
Join city In Capitals On country.Name Equals city.Country _
Order By city.Latitude _
Select country.Name
Pour les requêtes qui calculent une valeur scalaire en fonction d'une collection, l'opérateur Aggregate porte sur la collection. La requête suivante compte les petits pays et calcule leur densité moyenne avec une seule instruction :
Dim popInfo = _
Aggregate country In Countries _
Where country.Population < 1000000 _
Into Total = Count(), Density = Average(country.Population/country.Area)
Les fonctions agrégées sont généralement sollicités lors de la partition de la collection source. Par exemple, nous pouvons grouper tous pays selon leur position par rapport aux tropiques puis agréger les valeurs de chaque groupe. Pour ce faire, utilisez les opérateurs agrégés en conjonction avec les clauses Group By et Group Join. Dans l'exemple ci-dessous, la fonction d'aide IsTropical permet de déterminer si une ville a un climat tropical :
Function IsTropical() As Boolean
Return TropicOfCancer => Me.Latitude AndAlso Me.Latitude <= TropicOfCapricorn
End Function
Avec cette fonction d'aide, nous appliquons le même processus d'agrégation que ci-dessus, mais nous commençons par répartir la collection d'entrée des paires Country et Capital en groupes correspondant aux valeurs de Country.IsTropical. Dans ce cas, il y a deux groupes de ce type : L'un contient les pays tropicaux Palau, Belize et Madagascar et l'autre le seul pays non tropical, Monaco.
| Légende | Pays | Ville |
| Country.IsTropical() = True | Palaos | Koror |
| | Belize | Belmopan |
| | Madagascar | Antanarive |
| Country.IsTropical() = False | Monaco | Monaco |
Ensuite, nous agrégeons les valeurs de ces groupes en calculant les densités totale et moyenne. Le type de résultat est désormais une collection de paires de Total As Integer et Density As Double :
Dim countriesByClimate = _
From country In Countries _
Join city In Capitals On country.Name Equals city.Country _
Group By country.IsTropical()
Into Total = Count(), Density = Average(country.Population/country.Area)
La requête ci-dessus mentionnée masque une complexité considérable. La requête ci-dessous produit les mêmes résultats à l'aide d'expressions lambda et de méthodes d'extension pour formuler la requête avec la syntaxe d'appel de méthode.
Dim countriesByClimate7 = _
countries. _
SelectMany( _
Function(country) Capitals, _
Function(country, city) New With {country, city}). _
Where(Function(it) it.country.Name = it.city.Country). _
GroupBy( _
Function(it) it.city.IsTropical(), _
Function(IsTropical, Group) _
New With { _
IsTropical, _
.Total = Group.Count(), _
.Density = Group.Average( _
Function(it) it.country.Population / it.country.Area _
) _
} _
)
Méthodes d'extension et expressions lambda
La puissance de l'infrastructure de requête standard .NET Framework repose sur les méthodes d'extension et les expressions lambda. Les méthodes d'extension sont des méthodes partagées qui sont marquées avec des attributs personnalisés qui permettent de les appeler avec la syntaxe de méthode d'instance. La plupart de méthodes d'extension ont des signatures similaires. Le premier argument est l'instance à laquelle la méthode s'applique et le deuxième correspond au prédicat à appliquer. Par exemple, la méthode Where, vers laquelle la clause Where est traduite, comporte la signature suivante :
Module IEnumerableExtensions
<Extension> _
Function Where (Of TSource) _
(Source As IEnumerable(Of TSource), _
predicate As Func(Of TSource, Boolean)) As IEnumerable(Of TSource)
...
End Function
End Module
Dans la mesure où de nombreux opérateurs standard de requête tels que Where, Select, SelectMany, etc. sont définis en tant que méthodes d'extension qui reçoivent des délégués de type Func(Of S,T) comme arguments, le compilateur contourne la phase de production des délégués qui représentent le prédicat. Le compilateur crée des fermetures, des délégués qui capturent leur contexte, et les passe à l'appel de méthode sous-jacent. Par exemple, avec la requête suivante, le compilateur produit des expressions lambda qui représentent les délégués à passer aux fonctions Select et Where :
Dim smallCountries = From country In countries _
Where country.Population < 1000000 _
Select country.Name
Le compilateur produit deux expressions lambda pour la projection et le prédicat, respectivement :
Function(Country As Country) country.Name
Function (Country As Country) country.Population < 1000000
La requête ci-dessus est ensuite traduite en appels de méthode vers l'infrastructure de requête standard et passe la source et l'expression lambda à appliquer en tant qu'arguments :
Dim smallCountries = _
Enumerable.Select( _
Enumerable.Where(countries, _
Function (country As Country) country.Population < 1000000), _
Function(country As Country) country.Name)
Types nullables
Les bases de données relationnelles disposent d'une sémantique pour les valeurs nullables qui sont souvent incohérentes avec les langages de programmation ordinaires, voire inconnues des programmeurs. Dans les applications recourant de façon intensive à des données, il est essentiel de traiter cette sémantique de façon claire et correcte. Par conséquent, dans .NET Framework 2.0, le CLR dispose d'une prise en charge supplémentaire de ce type de valeur qui applique le type générique Nullable(Of T As Structure). Ce type permet de déclarer les versions nullables de types de valeur tels que Integer, Boolean, Date, etc. Pour des raisons qui deviendront vite apparentes, la syntaxe de Visual Basic pour les types nullables est T?.
Par exemple, dans la mesure où tous les pays ne sont pas indépendants, nous pouvons ajouter un nouveau membre à la classe Country pour la date d'indépendance, le cas échéant :
Partial Class Country
Public Property Independence As Date?
End Class
La date d'indépendance de Palau est le #01/10/94#, mais les Îles Vierges Britanniques est un territoire dépendant du Royaume-Uni et, par conséquent, sa date d'indépendance a la valeur Nothing.
Dim palau = _
New Country With { _
.Name = "Palau", _
.Area = 458, _
.Population = 16952, _
.Independence = #10/1/1994# }
Dim virginIslands = _
New Country With { _
.Name = "Virgin Islands", _
.Area = 150, _
.Population = 13195, _
.Independence = Nothing }
Visual Basic 9.0 prend en charge la logique à trois valeurs et la propagation arithmétique de null pour les valeurs nullables, ce qui signifie que si l'un des opérandes d'une opération arithmétique, de comparaison, logique ou au niveau du bit, shift, chaîne ou type a la valeur Nothing, le résultat sera Nothing. Si les deux opérandes ont des valeurs réelles, l'opération s'exécute sur les valeurs sous-jacentes des opérandes et le résultat est converti en type nullable.
Dans la mesure où Palau.Independence et VirginIslands.Independence ont le type Date?, le compilateur exploite la propagation arithmétique de null pour les soustractions ci-dessous, ce qui permet de déduire que la déclaration locale de PLength et VILength sera TimeSpan? dans les deux cas.
Dim pLength = #8/24/2005# - Palau.Independence ‘ 3980.00:00:00
La valeur de PLength est de 3980.00:00:00, car aucun des opérandes ne renvoie Nothing. D'un autre côté, dans la mesure où la valeur de VirginIslands.Independence est Nothing, le résultat est à nouveau de type TimeSpan?, mais la valeur de VILength sera Nothing en raison de la propagation de null.
Dim vILength = #8/24/2005# - virginIslands.Independence ‘ Nothing
Comme dans SQL, les opérateurs de comparaison procèdent à la propagation de null et les opérateurs logiques ont recours à la logique à trois valeurs. Dans les instructions If et While, Nothing est interprété comme False. Par conséquent, dans l'exemple de code suivant, la branche Else est prise :
If vILength < TimeSpan.FromDays(10000)
...
Else
...
End If
Avec la logique à trois valeurs, l'égalité vérifie X = Nothing et Nothing = X renvoie toujours Nothing. Pour vérifier si X égale Nothing, nous devons utiliser la comparaison logique à deux valeurs, X Is Nothing ou Nothing Is X.
Délégués non stricts
Lors de la création d'un délégué avec AddressOf ou Handles dans Visual Basic 8.0, l'une des méthodes devant opérer la liaison avec l'identificateur délégué doit correspondre exactement à la signature du type du délégué. Dans l'exemple ci-dessous, la signature de la sous-routine OnClick doit correspondre exactement la signature du délégué du gestionnaire d'événements Delegate Sub EventHandler(sender As Object, e As EventArgs), qui est déclaré dans le cadre du type Button :
Dim WithEvents btn As New Button()
Sub OnClick(sender As Object, e As EventArgs) Handles B.Click
MessageBox.Show("Hello World from" & btn.Text)
End Sub
Cependant, lors de l'appel des fonctions et des sous-routines non-déléguées, Visual Basic ne nécessite pas une concordance exacte entre les arguments réels et l'une des méthodes que nous tentons d'appeler. Comme le fragment suivant l'indique, nous pouvons appeler la sous-routine OnClick à l'aide d'un argument réel de type Button et de type MouseEventArgs, qui sont des sous-types des paramètres formels Object et EventArgs, respectivement :
Dim m As New MouseEventArgs(MouseButtons.Left, 2, 47, 11,0)
OnClick(btn, m)
À l'inverse, supposons que nous définissions une sous-routine RelaxedOnClick qui recevrait deux paramètres Object et que nous serions autorisés à l'appeler avec des arguments réels de type Object et EventArgs :
Sub RelaxedOnClick(sender As Object, e As Object) Handles btn.Click
MessageBox.Show("Hello World from" & btn.Text))
End Sub
Dim e As EventArgs = m
Dim s As Object = btn
RelaxedOnClick(btn,e)
Dans Visual Basic 9.0, la liaison avec les délégués n'est pas stricte pour assurer la cohérence avec l'appel de méthode. Ainsi, s'il est possible d'appeler une fonction ou une sous-routine avec des arguments réels qui correspondent exactement au paramètre formel et renvoient les types d'un délégué, nous pouvons lier cette fonction ou cette sous-routine au délégué. En d'autres termes, la liaison de délégués et la définition suivent la même logique de résolution de la surcharge que cet appel de méthode.
Ceci implique que dans Visual Basic 9.0, nous pouvons désormais lier une sous-routine RelaxedOnClick qui prend deux paramètres Object à l'événement Click d'un argument Button :
Sub RelaxedOnClick(sender As Object, e As Object) Handles btn.Click
MessageBox.Show(("Hello World from" & btn.Text)
End Sub
Les deux arguments du gestionnaire d'événements, sender et EventArgs, sont rarement importants. Par contre, le gestionnaire accède à l'état du contrôle sur lequel l'événement est enregistré directement et ignore ses deux arguments. Pour prendre en charge ce cas commun, la définition des délégués peut être moins stricte afin de leur permettre de n'accepter aucun argument, si aucune ambigüité n'en découle. En d'autres termes, nous pouvons écrire simplement le code suivant :
Sub RelaxedOnClick Handles btn.Click
MessageBox.Show("Hello World from" & btn.Text)
End Sub
Il est sous-entendu que la déclaration non stricte de délégués s'applique également lors de la construction de délégués avec une expression AddressOf ou toute autre expression de création de délégué, même lorsque le groupe de méthodes est un appel à liaison tardive :
Dim F As EventHandler = AddressOf RelaxedOnClick
Dim G As New EventHandler(AddressOf btn.Click)
Conclusion
Visual Basic 9.0 unifie l'accès aux données indépendamment de la source, qu'elle provienne de bases de données relationnelles, documents XML ou objets graphiques arbitraires, persistants ou enregistrés en mémoire. L'unification couvre les styles, les techniques, les outils et les modèles de programmation. La syntaxe particulièrement souple de Visual Basic facilite l'ajout d'extensions telles que les littéraux XML et les expressions de requête de type SQL à un niveau avancé. Ceci réduit de façon radicale la « surface » de la nouvelle API de requête intégrée au langage de .NET LINK, permet de mieux découvrir les fonctionnalités d'accès aux données avec IntelliSense et les balises actives et enfin améliore considérablement le débogage et la vérification lors de la compilation en retirant les syntaxes étrangères des données de chaîne dans Visual Basic.
De plus, des fonctionnalités telles que l'inférence de type, les initialiseurs d'objet et les délégués déclarés de façon non stricte permettent de réduire considérablement les redondances de codage et le nombre d'exceptions aux règles que les programmeurs doivent mémoriser ou rechercher, sans pour autant compromettre les performances.
La liste des nouvelles fonctionnalités de Visual Basic 9.0 peut paraître longue, cependant nous espérons que les thèmes abordés ici vous convaincront qu'elles sont cohérentes, opportunes et contribuent à faire de Visual Basic le meilleur langage de programmation possible. Nous espérons avoir également stimulé votre imagination et que vous ferez le même constat que nous : ce n'est que le début et le meilleur est encore à venir.