Vue d'ensemble de Visual Basic 9.0

| Paul Vick | Amanda Silver | Erik Meijer |

Fournir une vue d'ensemble des nouvelles fonctionnalités du langage Visual Basic et des nouvelles améliorations du langage qui supportent la programmation 'data-intensive'. La traduction de l'article original, paru dans le MSDN, à été réalisée par Jean-Marc Rabilloud.

- Titre : Vue d'ensemble de Visual Basic 9.0

- Auteurs : Paul Vick, Amanda Silver, Erik Meijer

- Parution : 30 septembre 2005

- Mise à jour : 29 février 2020

- Licence : Copyright ® 2005 Jean-Marc Rabilloud. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Introduction

- Commencer avec Visual Basic 9.0

- Variables locales typées implicitement

- Initialiseur de collection ou d'objet

- Types anonymes

- Support XML approfondi

- Interprétation de requête

- Méthode de prolongation

- Fonctions imbriquées

- Types acceptant Null (Nullable)

- Délégués souples

- Interfaces dynamiques (ou « typage canard »)

- Identifiant dynamique

- Conclusion

Introduction

Cet article est une traduction de « Overview of Visual Basic 9.0 » rédigé par Erik Meijer, Amanda Silver et Paul Vick, publié dans le MSDN en septembre 2005. « Visual Basic, nom de code Orcas » (Visual Basic 9.0), amène plusieurs améliorations du langage qui ont été introduites avec « Visual Basic code-named Whidbey » (Visual Basic 8.0) pour supporter la programmation « Data-Intensive » - création, modification et extraction sur les bases de données relationnelles, les documents XML et les diagrammes d'objets - d'une façon unifiée. De plus, Visual Basic 9.0 apporte des nouvelles fonctionnalités pour accroître ses facilités au niveau du typage statique dans la mesure du possible et pour le typage dynamique en cas de besoin. Ces nouvelles fonctionnalités sont :

- variables locales typées implicitement ;

- interprétation de requête ;

- initialisation d'objet ;

- types anonymes ;

- intégration complète avec le Framework Linq ;

- support XML approfondi ;

- délégués souples ;

- types acceptant NULL ;

- interfaces dynamiques ;

- identifiant dynamique.

Ce document est une vue d'ensemble informelle de ces nouvelles fonctionnalités. Plus d'informations, y compris des modifications du langage Visual Basic et des aperçus du compilateur, sont disponibles dans le centre pour développeur Visual Basic en anglais ou en français.

Commencer avec Visual Basic 9.0

Pour voir la puissance des nouvelles fonctionnalités du langage en action, commençons avec un exemple réel - CIA World Factbook database.

Cette base de données contient divers renseignements géographiques, économiques, sociaux et politiques sur les pays du monde. Dans l'intérêt de notre exemple, nous allons démarrer avec un schéma contenant le nom de chaque pays et sa capitale, sa surface totale et sa population. On représente ce schéma avec Visual Basic 9.0 en utilisant la classe suivante :

Class Country
Public Property Name As String
Public Property Area As Float
Public Property Population As Integer
End Class

Ici il y a un court extrait de la base de données des pays que nous allons utiliser pour le fonctionnement de notre exemple :

Dim Countries = _
{ new Country{ _
.Name = "Palau", .Area = 458, .Population = 16952 }, _
new Country{ _
.Name = "Monaco", .Area = 1.9, .Population = 31719 }, _
new Country{ _
.Name = "Belize", .Area = 22960, .Population = 219296 }, _
new Country{ _
.Name = "Madagascar", .Area = 587040, .Population = 13670507 } _
}

En partant de cette liste, nous pouvons extraire tous les pays dont la population est inférieure à un million en utilisant l'interprétation de requête suivante :

Dim SmallCountries = Select Country _
From Country In Countries _
Where Country.Population < 1000000
For Each Country As Country In SmallCountries
Console.WriteLine(Country.Name)
Next

Comme seul Madagascar possède 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 le programme pour comprendre les fonctionnalités de Visual Basic 9.0 qui ont rendu ceci si simple à écrire. Premièrement, la déclaration de la variable Countries

Dim Countries = _
{ new County { .Name = "Palau", .Area = 458, .Population = 16952 }, _
... _
}

Utilisons la nouvelle syntaxe d'initialisation d'objet New Country {…, .Area = 458…} pour créer une instance d'objet complexe à l'aide d'une syntaxe concise similaire à l'instruction With.

La syntaxe illustre aussi la déclaration de variable locale implicitement typée, où le compilateur infère le type de la variable locale Countries dans l'expression d'initialisation du côté droit de la déclaration. La déclaration ci-dessus est exactement équivalente à une déclaration explicite de variable locale typée de type Country()

Dim Countries As Country() = {...}

Autrement dit, il s'agit toujours d'une déclaration fortement typée, le compilateur a automatiquement inféré le type de la partie droite de la déclaration, et il n'y a pas besoin pour le programmeur d'écrire manuellement ce type dans le programme.

La déclaration de la variable locale SmallCountries est initialisée avec une syntaxe de type SQL query comprehension pour filtrer tous les pays ayant moins d'un million d'habitants. La similitude au SQL est intentionnelle, permettant aux développeurs qui connaissent déjà le SQL de démarrer avec la syntaxe de requête de Visual Basic plus rapidement.

Dim SmallCountries = Select Country _
From Country In Countries _
Where Country.Population < 1000000

Notez qu'il y a une autre application du typage implicite : le compilateur infère le type de SmallCountries comme IEnumerable(Of Country). Le compilateur traduit l'interprétation de requête elle-même en opérateurs de requête standard. Dans ce cas, la traduction a pu être aussi simple que :

Function F(Country As Country) As Boolean
Return Country.Population < 1000000
End Function

Dim SmallCountries As IEnumerable(Of Country) = _
Countries.Where(AddressOf F)

La syntaxe étendue est transmise à la fonction locale générée par le compilateur comme un délégué AddressOf F de la fonction de prolongation Where, laquelle est définie dans la bibliothèque des opérateurs de requête standard comme un prolongement de l'interface IEnumerable(Of T).
Maintenant que nous avons vu quelques-unes des nouvelles fonctionnalités de Visual Basic 9, découvrons celles-ci de façon plus détaillée.

Variables locales typées implicitement

Dans une déclaration de Variables locales typées implicitement, le type de la variable est inféré de l'expression d'initialisation du côté droit de l'instruction. Par exemple, le compilateur infère le type dans toutes les déclarations suivantes :

Dim Population = 31719
Dim Name = "Belize"
Dim Area = 1.9
Dim Country = New Country{ .Name = "Palau", ...}

Par conséquent, c'est exactement équivalent aux déclarations typées suivantes :

Dim Population As Integer = 31719
Dim Name As String = "Belize"
Dim Area As Float = 1.9
Dim Country As Country = New Country{ .Name = "Palau", ...}

Puisque les types des déclarations de variables locales sont inférés par défaut, peu importe le paramétrage de l'option strict, l'accès à de telles variables est toujours en liaison précoce. Le développeur doit spécifier explicitement les liaisons tardives avec Visual Basic 9, en déclarant explicitement une variable de type Object comme suit :

Dim Country As Object = new Country{ .Name = "Palau", ... }

Exiger un appel explicite pour les liaisons tardives prévient l'utilisation d'une liaison tardive accidentelle et, plus important, cela offre de puissants prolongements aux liaisons tardives pour de nouveaux types de données comme le XML tel que nous le verrons après. Il y aura un commutateur facultatif de niveau projet pour basculer avec le comportement existant.

La variable de boucle dans les instructions For…Next ou For Each…Next peut aussi être implicitement typée. Quand une variable de boucle est indiquée, comme dans For Dim I = 0 To Count, ou comme dans For Each Dim C In SmallCountries, l'identificateur définit une nouvelle variable implicitement typée dont le type est inféré de l'initialiseur ou de la collection et est appliqué à la boucle entière. Cette utilisation de Dim à la droite de For est une nouveauté de Visual Basic 9, comme les variables de boucles implicitement typées.

En appliquant l'inférence de type, nous pouvons réécrire la boucle qui affiche tous les petits pays comme suit :

For Each Dim Country In SmallCountries
Console.WriteLine(Country.Name)
Next

Le type de Country est inféré comme Country, le type de base élémentaire de SmallCountries.

Initialiseur de collection ou d'objet

Avec Visual Basic, l'instruction With simplifie l'accès à plusieurs membres d'une valeur globale sans spécifier l'expression cible plusieurs fois. Dans le bloc de l'instruction With, une expression d'accès à un membre commence par un point comme si celui-ci était précédé de l'expression cible de l'instruction With. Par exemple, les instructions suivantes initialisent une nouvelle instance de Country et par suite initialisent ces champs avec les valeurs requises :

Dim Palau = New Country()
With Palau
.Name = "Palau"
.Area = 458
.Population = 16952
End With

Les nouveaux initialiseurs d'objets de Visual Basic 9.0 sont une forme d'expression basée sur With pour la création concise d'instance d'objet complexe. En utilisant les initialiseurs d'objet, nous pouvons réunir deux instructions dans une seule déclaration (implicitement typée), comme dans :

Dim Palau = New Country { _
.Name = "Palau", _
.Area = 458, _
.Population = 16952
}

Ce style d'initialisation à partir d'expressions est important pour les requêtes. Habituellement, une requête ressemble à une déclaration d'objet initialisé par une clause Select du côté droit du signe égal. Puisque la clause Select renvoie une expression, nous devons pouvoir initialiser l'objet entier avec une expression simple.

Comme nous l'avons vu, les initialiseurs d'objet sont également commodes pour créer des collections d'objets complexes. N'importe quelle collection qui possède une méthode Add peut être initialisée par une expression d'initialisation de collection. Par exemple, donnons la déclaration de cities comme classe partielle.

Partial Class City
Public Property Name As String
Public Property Country As String
Public Property Longitude As Float
Public Property Latitude As Float
End Class

Nous pouvons créer une liste (Of City) de capitales dans notre exemple de pays suivants :

Dim Capitals = New List(Of City){ _
{ .Name = "Antanarivo", _
.Country = "Madagascar", _
.Longitude = 47.4, _
.Lattitude = -18.6 }, _
{ .Name = "Belmopan", _
.Country = "Belize", _
.Longitude = -88.5, _
.Latitude = 17.1 }, _
{ .Name = "Monaco", _
.Country = "Monaco", _
.Longtitude = 7.2, _
.Latitude = 43.7 }, _
{ .Country = "Palau",
.Name = "Koror", _
.Longitude = 135, _
.Latitude = 8 } _
}

Cet exemple utilise aussi les initialiseurs imbriqués d'objet, où les constructeurs des initialiseurs imbriqués sont inférés à partir du contexte. Dans ce cas, chaque initialiseur est exactement équivalent à la forme complète New City{…}.

Types anonymes

Souvent, nous voulons juste enlever ou exclure certains membres d'un type comme résultat d'une requête. Par exemple, nous voudrions juste connaître le nom et le pays de toutes les capitales tropicales, en nous servant des colonnes Latitude et Longitude de la source de données pour identifier le tropique, sans faire apparaître ces données dans le résultat. Avec Visual Basic 9.0, nous ferons cela en créant une nouvelle instance d'objet - sans nommer le type - pour chaque ville C dont la latitude se situe entre le Tropique du Cancer et le Tropique du Capricorne :

Const TropicOfCancer = 23.5
Const TropicOfCapricorn = -23.5

Dim Tropical = Select New{ .Name = City.Name, .Country = City.Country } _
From City In Capitals _
Where TropicOfCancer =< City.Latitude _
AndAlso City.Latitude >= TropicOfCapricorn

Le type inféré de la variable Tropical est une collection d'instances de type anonyme, qui est IEnumerable(Of { Name As String, Country As String }). Le compilateur Visual Basic va créer une nouvelle classe générique, par exemple, _Name_As_String_Country_As_String_ , dont le nom et le type des membres seront inférés de l'initialiseur, comme pour :

Class _Name_As_String_Country_As_String_
Public Property Name As String
Public Property Country As String
Public Default Property Item(Index As Integer) As Object
...
End Class

Au sein d'un même programme, le compilateur peut fusionner les types anonymes identiques. Deux initialiseurs d'objet anonyme qui indiquent une séquence de propriétés de même nom et types dans le même ordre produiront une instance du même type anonyme. À l'extérieur, les types anonymes générés par Visual Basic sont remplacés par Object ce qui permet au compilateur de passer uniformément les types anonymes comme arguments et résultats des fonctions. Lors de l'utilisation dans le code Visual Basic, le compilateur marque la classe générée avec un attribut personnalisé spécial pour rappeler que le type _Name_As_String_Country_As_String_ représente à ce moment le type anonyme { Name As String, Country As String }.
Comme les types anonymes sont habituellement utilisés pour exclure des membres d'un type existant, VISUAL BASIC 9.0 autorise la notation sténographique d'exclusion New { City.Name, City.Country } pour abréger la forme longue { .Name = City.Name, .Country = City.Country }. Quand c'est utilisé dans le résultat d'une interprétation de requête, nous pouvons abréger les initialiseurs d'exclusion, entre autres, comme suit :

Dim Tropical = Select City.Name, City.Country _
From City In Capitals _
Where TropicOfCancer =< City.Latitude _
AndAlso City.Latitude >= TropicOfCapricorn

Notez que ces deux formes abrégées ont le même sens que la forme longue.

Support XML approfondi

XLinq est une nouvelle API en mémoire conçue pour accroître les capacités du nouveau .Net Framework avec des capacités comme le Framework du langage de requête intégré. Comme l'interprétation de requête ajoute une syntaxe familière sur les opérateurs de requête sous-jacents du Framework .NET, Visual Basic 9.0 fournit un support approfondi pour XLinq à travers XML literals et Late Binding over XML.

Pour illustrer les littéraux XML, interrogeons la source de données à plat Countries et Capitals pour construire un modèle hiérarchique XML qui imbrique la capitale comme élément enfant de chaque pays et calcule la densité de population comme un attribut.

Pour trouver la capitale d'un pays donné, nous faisons une jointure avec le membre name de chaque pays et le membre country de chaque ville. En donnant un pays et sa capitale, nous pouvons alors facilement construire le fragment de XML en complétant les trous inclus par des valeurs calculées. Nous voulons écrire un « trou » pour l'attribut Name avec des parenthèses, comme dans Name=(Country.Name), et un « trou » pour l'élément enfant avec la syntaxe spéciale parenthèses brisées empruntée d'ASP.NET comme dans <Name><%= City.Name %></Name>. C'est là que notre requête combine les littéraux XML et l'interprétation de requête :

Dim CountriesWithCapital As XElement = _
<Countries>
<%= Select <Country Name=(Country.Name)
Density=(Country.Population/Country.Area)>
<Capital>
<Name><%= City.Name %></Name>
<Longitude><%= City.Longitude %></Longtitude>
<Latitude><%= City.Latitude %></Latitude>
</Capital>
</Country> _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country %>
</Countries>

Notez que le type XElement pourrait être omis de la déclaration et, dans ce cas, serait inféré comme n'importe quelle déclaration de variable locale. Nous laissons le type explicite ici pour faire une remarque ci-dessous.

Dans cette déclaration, nous voulons que le résultat de la requête soit substitué dans l'élément <Countries>. Ainsi la requête Select est le contenu du premier trou, délimité par les balises familières d'ASP.NET <%= and %> à l'intérieur de <Countries>. Puisque le résultat de la requête est une expression ainsi que les littéraux XML, il est normal, alors, d'imbriquer un autre littéral XML dans le Select lui-même. Ce littéral imbriqué contient lui-même le trou de l'attribut imbriqué Country.Name et le calcul du ratio de la densité de population Country.Population/Country.Area et le trou pour le nom et les coordonnées de la capitale.

Après compilation et exécution, la requête ci-dessus retourne le document XML suivant (légèrement reformaté pour économiser de l'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 comme des objets normaux de System.Xml.XLinq garantissant une interopérabilité complète entre Visual Basic et les autres langages utilisant XLinq. Dans notre requête exemple, le code produit par le compilateur (si nous pouvions le voir) serait :

Dim CountriesWithCapital As XElement = _
New XElement("Countries", _
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)))
From Country In Countries, City In Capitals _
Where Country.Name = City.Country)

Sans compter la construction XML, Visual Basic 9.0 simplifie aussi l'accès aux structures XML via les liaisons tardives par dessus XML ; c'est-à-dire, des identifiants dans le code Visual Basic qui sont liés à l'exécution aux attributs et éléments XML correspondants. Par exemple, nous pouvons imprimer la densité de population de tous les pays de l'exemple comme suit :

1. Utiliser l'axe enfant CountriesWithCapital.Country pour obtenir tous les éléments « Country » de la structure XML CountriesWithCapital ;

2. Utiliser l'axe attribut Country.@Density pour obtenir l'attribut « Density » de l'élément Country ;

3. Utiliser l'axe descendant Country…Latitude - écrit littéralement avec trois points dans le code source - pour obtenir tous les enfants « Latitude » de l'élément Country sans tenir compte de l'endroit de la hiérarchie où il se trouve ;

4. Utiliser l'extenseur d'index de IEnumerable(Of T) pour sélectionner le premier élément de la séquence résultante.

Si nous faisons tout cela en même temps, le code ressemblera à :

For Each Dim Country In CountriesWithCapital.Country
Console.WriteLine("Density = "+ Country.@Density)
Console.WriteLine("Latitude = "+ Country...Latitude(0))
Next

Le compilateur sait qu'il doit utiliser la liaison tardive au lieu d'objets normaux quand l'expression cible d'une déclaration, d'une assignation, ou d'une initialisation est de type object au lieu d'un type plus spécifique. À l'identique, le compilateur sait qu'il doit utiliser des liaisons tardives XML quand l'expression cible un type ou une collection, XElement, XDocument, ou XAttribute.
Pour obtenir le résultat d'une liaison tardive XML, le compilateur traduit comme suit :

- l'axe enfant CountriesWithCapital.Country est traduit tel quel dans l'appel XLinq CountriesWithCapital.Elements(« Country ») , lequel retourne la collection de tous les éléments enfants nommés « Country » de l'élément Country ;

- l'axe attribut Country.@Density transformée en Country.Attribute(« Density ») qui retourne le seul attribut enfant nommé « Density » de Country ;

- l'axe descendant Country…Latitude(0) converti en une combinaison de ElementAt(Country.Descendants(Latitude),0) , qui retourne la collection de tous les éléments nommés à n'importe quelle profondeur sous Country.

Interprétation de requête

L'interprétation de requête (Query comprehensions) fournit un langage intégré pour les requêtes qui est très similaire au SQL, mais adapté pour s'intégrer au « look and feel » Visual basic d'une part, et d'autre part pour s'intégrer facilement au nouveau Framework du langage de requête intégré.

Ceux qui sont familiers avec l'implementation of SQL reconnaîtront dans la séquence des opérateurs sous-jacents du Framework .NET, la plupart des opérateurs compositionnels d'algèbre relationnelle comme les exclusions, sélections, produits croisés, regroupements et tris présents dans les plans de requêtes à l'intérieur des processeurs de requête.

Parce que la sémantique de l'interprétation de requête est définie par une traduction en séquence d'opérateur, les opérateurs sous-jacents sont liés à quelques séquences d'opérateurs qui sont dans leur portée. Ceci implique qu'en important une implémentation particulière, la syntaxe d'interprétation de requête peut être efficacement reliée par l'utilisateur. En particulier, l'interprétation de requête peut être reliée à une séquence d'opérateurs qui utilise l'infrastructure DLinq, ou un optimiseur de requête local qui tente de distribuer l'exécution des requêtes sur plusieurs sources de données locales ou distantes. Cette reliaison à une séquence sous-jacente d'opérateurs est similaire dans l'esprit au modèle classique de fournisseur COM qui, par différentes implémentations de la même interface peut accepter une grande variété de choix opérationnels ou de déploiements sans modification du code de l'application.

Le classique filtre d'interprétation Select…From…Where… extrait toutes les valeurs qui satisfont le prédicat de la clause Where. Un des premiers exemples montrait comment trouver tous les pays ayant moins d'un million d'habitants :

Dim SmallCountries = Select Country _
From Country In Countries _
Where Country.Population < 1000000

À l'intérieur de la séquence, l'identifiant It est lié à la « ligne » courante. Comme pour Me, les membres de It sont automatiquement dans la portée. La notion de It correspond à l'élément XQuery de contexte « . » et peut être utilisé comme « * » en SQL. Par exemple, nous pouvons retourner la collection de tous les pays avec leur capitale en utilisant la requête suivante :

Dim CountriesWithCapital = _
Select It _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country

Le type inféré de cette déclaration locale est IEnumerable(Of { Country As Country, City As City }).
Avec la clause Order By, nous pouvons trier le résultat d'une requête avec n'importe quel nombre de clés de tri. Par exemple, la requête suivante renvoie une liste des noms de tous les pays, triés par longitude croissante puis par population décroissante :

Dim Sorted = Select Country.Name _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
Order By City.Longtitude Asc, Country.Population Desc

Les opérateurs d'agrégat tel que Min, Max, Count, Avg… agissent sur des collections et les « agrègent » comme des valeurs uniques. Nous pouvons compter le nombre de petits pays en utilisant la requête suivante :

Dim N As Integer = _
Select Count(Country) _
From Country In Countries _
Where Country.Population < 1000000

À l'instar du SQL, nous fournissons une syntaxe spéciale pour des agrégats, qui est extrêmement pratique pour coupler des opérations multiples d'agrégation. Par exemple, pour compter les petits pays et calculer leur densité moyenne en une instruction, nous pouvons écrire :

Dim R As { Total As Integer, Density As Double } = _
Select New { .Total = Count(Country), _
.Density = Avg(Country.Population/Country.Area) } _
From Country In Countries _
Where Country.Population < 1000000

Cette forme d'agrégat est un raccourci pour appliquer une fonction compilée d'agrégation par-dessus le résultat d'un jeu normal de résultats sans agrégation.
Les fonctions d'agrégat sont le plus souvent combinées avec un partage de la collection source. Par exemple, nous pouvons regrouper tous les pays par le fait qu'ils sont tropicaux, puis agréger le compte de chaque groupe. Pour faire cela, nous introduisons la clause Group By. La fonction d'aide IsTropical encapsule le test pour savoir si le pays a un climat tropical :

Partial Class Country
Function IsTropical() As Boolean
Return TropicOfCancer =< Me.Latitude _
AndAlso Me.Latitude >= TropicOfCapricorn
End Function
End Class

Cette fonction d'aide donnée, nous utilisons exactement la même agrégation qu'au-dessus, mais d'abord nous partageons la collection d'entrées Country and Capital en groupe ou Country.IsTropical est le même. Dans notre cas, il y a deux groupes ; un qui contient les pays tropicaux Palau, Belize, et Madagascar ; et un autre qui contient le pays non tropical Monaco.

Key

Country

City

Country.IsTropical() = True

Palau

Koror

Country.IsTropical() = True

Belize

Belmopan

Country.IsTropical() = True

Madagascar

Antanarivo

Country.IsTropical() = False

Monaco

Monaco

Alors, nous agrégeons les valeurs de chaque groupe en calculant le nombre total et la densité moyenne. Le type résultant est maintenant une collection de paires Total As Integer and Density As Double :

Dim CountriesByClimate _
As IEnumerable(Of Total As Integer, Density As Double }) =
Select New { .Total = Count(Country), _
.Density = Avg(Country.Population/Country.Area) } _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
Group By Country.IsTropical()

La question ci-dessus cache une complexité considérable puisque le résultat de la clause Group By est en réalité une collection de valeurs groupées de type IEnumerable(Of Grouping(Of { Boolean, { Country As Country, City As City })) , comme dans la table ci-dessus. Chacun contient un membre Key dérivé de l'extraction sur clé Country.IsTropical() et un groupe qui contient la collection unique de pays et de villes pour qui cette clé d'extraction est la même. Le compilateur Visual Basic synthétise la fonction d'agrégation définie par l'utilisateur, puis pour chaque groupe calcule le résultat de l'agrégat.
Notez que dans l'exemple précédent, chaque groupe contient à la fois le pays et la capitale, alors que nous n'avons besoin que du pays pour obtenir le résultat final de la requête. La clause Group By autorise une présélection des groupes. Par exemple, nous pouvons partager les noms des pays par hémisphère en utilisant l'interprétation suivante :

Dim ByHemisphere As IEnumerable(Of Grouping(Of Boolean, String)) = _
Select It _
From Country In Countries, City In Capitals _
Where Country.Name = City.Country
Group Country.Name By City.Latitude >= 0

Ce qui renvoie la collection { New Grouping { .Key = False, .Group = { « Madagascar », « Belize » }}, New Grouping { .Key = True, .Group = { « Palau » }}.

L'interprétation de requête dans Visual Basic 9.0 est complètement compositional dans le sens que l'interprétation de requête peut être imbriquée arbitrairement, restreinte uniquement par les règles du typage statique. La compositionnalité permet de comprendre une grosse requête par la simple compréhension de chaque expression individuelle. La compositionnalité permet facilement de définir la sémantique et les règles du typage du langage. La compositionnalité comme principe de conception est assez différente des principes qui sont à la base du SQL. Le langage SQL n'est pas entièrement compositionnel et possède plutôt une composition ad hoc avec de nombreux cas spéciaux qui s'est accrue au fur et à mesure que l'expérience des bases de données grandissait. Du fait de cette faiblesse compositionnelle, il n'est généralement pas possible de comprendre une requête SQL complexe par compréhension de morceaux individuels.

L'une des raisons des faiblesses compositionnelles du langage SQL est que le modèle relationnel de données sous-jacent n'est pas lui-même compositionnel. Par exemple, les tables ne peuvent pas contenir de sous-tables ; autrement dit, toutes les tables sont à plat. Ceci fait, que plutôt que de casser les expressions complexes en petites unités, les programmeurs SQL écrivent les expressions monolithiques qui résultent de ces tables plates, s'ajustant au modèle de données SQL. Pour citer Jim Gray « tout ce qui n'est pas récursif en informatique n'est pas bon ». Puisque Visual Basic est basé sur le système de type C. L. R., il n'y a pas de restrictions sur quel type peut apparaître comme composant d'un autre type. Hormis des règles de typage statique, il n'y a aucune restriction au genre d'expressions qui peuvent apparaître comme composants d'autres expressions. Ceci implique, que non seulement les lignes, les objets, et le XML, mais aussi Active Directory, les fichiers, les entrées de registre, et ainsi de suite, sont tous citoyen de première classe comme source de requête ou comme résultat de celles-ci.

Méthode de prolongation

Une grande partie de la puissance fondamentale de l'infrastructure standard de requête vient des méthodes de prolongation. En fait le compilateur traduit toute interprétation de requête directement en méthode de prolongation de l'opérateur de requête standard définie par l'espace de noms qui est dans la portée. Les méthodes de prolongation sont des méthodes partagées, marquées avec un attribut personnalisé qui leur permettent d'être appelées à travers des méthodes d'instance. En effet, les méthodes de prolongation prolongent les types existants et les types construits avec des méthodes additionnelles.

Puisque les méthodes de prolongation sont principalement à l'intention des concepteurs de bibliothèques, Visual Basic n'offre pas une syntaxe directe pour les déclarer. À la place, les auteurs attachent directement les attributs personnalisés requis au module et aux membres pour les désigner comme méthode de prolongation. L'exemple suivant définit une méthode de prolongation Count sur une collection arbitraire :

<System.Runtime.CompilerServices.Extension> _
Module MyExtensions
<System.Runtime.CompilerServices.Extension> _
Function Count(Of T)([Me] As IEnumerable(Of T)) As Integer
For Each Dim It In [Me]
Count += 1
Next
End Function
End Module

Rappelez-vous que la syntaxe crochet est un mot clé d'échappement, permettant l'usage de Me comme nom d'une variable ordinaire. Puisque la méthode de prolongation est une méthode partagée cela simulera une méthode d'instance, ce qui convient pour utiliser l'identifiant Me comme nom de l'entrée comme dans une méthode réelle d'instance, mais il doit être échappé avec des crochets puisque c'est un mot clé, ce qui n'est pas vraiment permis dans une méthode partagée.

Les méthodes de prolongation sont juste des méthodes partagées régulières, par conséquent nous pouvons appeler la fonction Count comme nous appellerions n'importe quelle autre fonction partagée en Visual Basic, en fournissant juste explicitement la collection d'instances sur laquelle opérer :

Dim TotalSmallCountries = _
MyExtensions.Count(Select Country _
From Country In Countries _
Where Country.Population < 1000000)

Les méthodes de prolongation entrent dans la portée avec l'instruction classique Imports. Ces méthodes de prolongation peuvent apparaître comme des méthodes additionnelles du type donné dans leur premier paramètre.

Imports MyExtensions

Dim TotalSmallCountries = _
(Select Country _
From Country In Countries _
Where Country.Population < 1000000).Count()

Les méthodes de prolongation ont une priorité moindre que les méthodes régulières d'instance ; si le traitement normal d'une invocation d'expressions ne trouve pas de méthodes d'instance applicable, le compilateur essaiera d'interpréter l'appel comme une invocation de méthodes de prolongation.

La voie la plus naturelle pour écrire cette requête, toutefois, est d'utiliser la syntaxe d'agrégat, comme nous l'avons vu précédemment :

Dim TotalSmallCountries = _
Select Count(Country) _
From Country In Countries _
Where Country.Population < 1000000

Fonctions imbriquées

La plupart des opérateurs standards de requête telle que Where, Select, SelectMany, etc., sont définis comme des méthodes de prolongation qui prennent des délégués de type Func(Of S,T) comme arguments. Afin que le compilateur puisse traduire les interprétations en opérateurs sous-jacents, ou afin que les programmeurs Visual Basic puissent appeler ces opérateurs directement, il y a nécessité de pouvoir créer facilement des délégués. En particulier, nous devons pouvoir créer des délégués appelés « closure » qui capturent leur contexte. Le mécanisme Visual Basic pour créer ces « closure » passe par des déclarations de fonction ou de procédure imbriquée.

Pour voir l'usage des fonctions imbriquées, nous appellerons directement des opérateurs de requête fondamentaux comme définis dans l'espace de nom System.Query. L'une des méthodes de prolongation est la fonction TakeWhile qui rapporte les éléments d'une séquence si le test est vrai et, alors, saute la fin de la séquence.

<Extension> _
Shared Function TakeWhile(Of T) _
(source As IEnumerable(Of T), Predicate As Func(Of T, Boolean)) _
As IEnumerable(Of T)

L'opérateur OrderByDescending trie la collection d'arguments dans l'ordre décroissant selon le critère prouvé.

<Extension> _
Shared Function OrderByDescending (T, K As IComparable(Of K)) _
(Source As IEnumerable(Of T), KeySelector As Func(Of T, K)) _
As OrderedSequence(Of T)

Une autre façon de trouver tous les petits pays consiste à les trier d'abord par leur population puis d'utiliser TakeWhile pour récupérer tous les pays ayant moins d'un million d'habitants.

Function Population(Country As Country) As Integer
Return Country.Population
End Function

Function LessThanAMillion(Country As Country) As Boolean
Return Country.Population < 1000000
End Function

Dim SmallCountries = _
Countries.OrderBy(AddresOf Population) _
.TakeWhile(AddresOf LessThanAMillion)

Bien que ce ne soit pas nécessaire pour l'interprétation de requête, Visual Basic accepte la syntaxe directe pour les fonctions et procédures anonymes (appelées « lambda expressions »), qui seront traduites par le compilateur comme des déclarations de fonction locale.

Types acceptant Null (Nullable)

Les bases de données relationnelles présentent la sémantique pour les valeurs NULL qui sont souvent contradictoires avec des langages de programmation ordinaires et souvent peu familières aux programmeurs. Dans les applications « Data-Intensive », il est fondamental que le programme manipule clairement et correctement cette sémantique. Reconnaissant cette nécessité, dans « Whidbey », le CLR a ajouté un support à l'exécution pour la gestion des NULL en utilisant le type générique Nullable(Of T As Struct). En utilisant ce type nous pouvons déclarer des versions acceptant NULL comme type de valeur tel que Integer, Boolean, Date, etc., pour des raisons qui deviendront évidentes, la syntaxe Visual Basic pour les types acceptants NULL est T ?.

Par exemple, puisque tous les pays ne sont pas indépendants, nous pouvons ajouter un nouveau membre à la classe Country qui représente leur date d'indépendance, si elle existe :

Partial Class Country
Public Property Independence As Date?
End Class

Comme pour les types tableaux, nous pouvons aussi ajouter le modificateur nullable au nom de la propriété, comme dans la déclaration suivante :

Partial Class Country
Public Property Independence? As Date
End Class

La date d'indépendance pour Palau est le #10/1/1994#, mais les îles vierges britanniques sont un territoire dépendant du Royaume-Uni, par conséquent leur date d'indépendance est Nothing.

Dim Palau = _
New Country { _
.Name = "Palau", _
.Area = 458, _
.Population = 16952, _
.Independence = #10/1/1994# }

Dim VirginIslands = _
New Country { _
.Name = "Virgin Islands", _
.Area = 150, _
.Population= 13195, _
.Independence = Nothing }

Visual Basic 9.0 supportera une logique trois valeurs et une propagation arithmétique de NULL pour les valeurs acceptant NULL, ce qui signifie que si un des opérandes, d'une opération arithmétique, d'une comparaison logique ou au niveau du bit, d'un décalage, d'une opération de chaîne, ou sur un type, est Nothing, le résultat sera Nothing. Si les deux opérandes sont des valeurs appropriées, l'opération est effectuée sur les valeurs sous-jacentes des opérandes et le résultat est converti en nullable.

Comme à la fois Palau.Independence et VirginIslands.Independence ont un type Date?, le compilateur utilisera l'arithmétique de propagation de NULL pour les soustractions, et par conséquent le type inféré pour les déclarations locales PLength et VILength sera dans les deux cas TimeSpan?.

Dim PLength = #8/24/2005# - Palau.Independence          REM 3980.00:00:00

La valeur de PLength est 3980.00:00:00 puisqu’aucun des deux opérandes n’est Nothing. D'autre part, puisque la valeur VirginIslands.Independence est Nothing, le résultat est encore de type TimeSpan?, mais la valeur de VILength sera Nothing à cause de la propagation de NULL.

Dim VILength = #8/24/2005# - VirginIslands.Independence REM Nothing

Comme en SQL, les opérateurs de comparaison feront la propagation de NULL, et les opérateurs logiques utiliseront une logique trois valeurs. Dans les instructions If et While, Nothing est interprété comme False ; par conséquent dans le code suivant la branche Else sera prise :

If VILength < TimeSpan.FromDays(10000)
...
Else
...
End If

Notez que dans une logique trois valeurs, l'égalité qui vérifie X = Nothing, et Nothing = X est toujours évalué comme Nothing. Pour vérifier si X est Nothing, vous devez utiliser la logique de comparaison X Is Nothing ou Nothing Is X.

Le runtime traite les valeurs acceptant NULL particulièrement lorsqu'il y a boxing ou Unboxing de et vers un type Object. Lorsqu'il y a Boxing d'une valeur nullable qui vaut Nothing (dans ce cas, sa propriété HasValue vaut False), cette valeur est « boxée » vers une référence NULL. Lorsqu'il y a Boxing d'une valeur appropriée (dans ce cas, sa propriété HasValue vaut True), la valeur sous-jacente est d'abord détypée puis boxée. Pour cette raison, aucun objet sur le tas n'a le type dynamique Nullable (of T) ; tous ces types apparents sont plutôt juste T. Nous pouvons unboxer les valeurs d'object soit vers T, soit vers Nullable (of T). Toutefois, cela implique que la liaison tardive ne peut pas décider dynamiquement d'utiliser une logique deux ou trois valeurs. Par exemple, quand nous faisons en liaison précoce une addition de deux nombres, si l'un des deux est Nothing, la propagation Null est utilisée, et le résultat sera Nothing :

Dim A As Integer? = Nothing
Dim B As Integer? = 4711
Dim C As Integer? = A+B REM C = Nothing

Cependant, quand nous faisons la même addition en liaison tardive, le résultat sera 4711, puisque la liaison tardive utilisera une logique standard basée sur le fait que le type dynamique à la fois de A et de B est Integer et non Integer?. Dans ce cas, Nothing est interprété comme 0 :

Dim X As Object = A
Dim Y As Object = B
Dim Z As Object = X+Y REM Z = 4711

Afin d'assurer la sémantique correcte, nous devons forcer le compilateur à employer la surcharge de propagation Null

Operator +(x As Object?, y As Object?) As Object?

En convertissant les deux opérandes dans un type acceptant Null grâce à l'opérateur ? :

Dim X As Object = A
Dim Y As Object = B
Dim Z As Object? = X?+Y REM Z = Nothing

Notez que cela implique que nous pouvons toujours créer T? à partir de T. Le CLR utilise Nullable(Of T As Struct) pour forcer les types d'arguments non nullables seulement. Le compilateur Visual Basic remplace T ? par T si T est un type ne supportant pas Null, ou par Nullable(of T) dans l'autre cas. Le compilateur garde suffisamment de métadonnées internes pour se rappeler que dans le programme Visual Basic, le type statique dans les deux cas est T ?

Délégués souples

Lorsqu'on crée un délégué utilisant AddressOf ou Handles dans Visual Basic 8.0, une méthode ciblée pour la liaison avec le délégué doit avoir strictement la même signature que le type du délégué. Dans l'exemple ci-dessous, la signature de la routine OnClick doit exactement correspondre à la signature du délégué du gestionnaire d'événement Delegate Sub EventHandler(sender As Object, e As EventArgs), qui est déclaré masqué dans le type Button :

Dim WithEvents B As New Button()

Sub OnClick(sender As Object, e As EventArgs) Handles B.Click
MessageBox.Show("Hello World from" + B.Text)
End Sub

Cependant, quand nous appelons une fonction ou une procédure qui n'est pas un délégué, Visual Basic n'exige pas que les arguments correspondent exactement à ceux de la méthode invoquée. Comme l'extrait suivant le montre, nous pouvons actuellement appeler la procédure OnClick en passant un argument de type Button et un 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(B, M)

Réciproquement, supposez que nous définissions une procédure RelaxedOnClick qui accepte deux paramètres Object, et qui nous permette de l'appeler avec nos arguments Object et EventArgs :

Sub RelaxedOnClick(sender As Object, e As Object) Handles B.Click
MessageBox.Show("Hello World from" + B.Text))
End Sub
Dim E As EventArgs = M
Dim S As Object = B
RelaxedOnClick(B,E)

Dans Visual Basic 9.0, les liaisons avec les délégués ont été assouplies pour être cohérentes avec l'appel de méthode. C'est-à-dire, si c'est possible d'appeler une fonction ou une procédure avec les arguments réels qui correspondent exactement aux paramètres formels et de renvoyer un type délégué, nous pouvons lier cette fonction ou procédure au délégué.

Cela implique qu'avec Visual Basic 9.0, nous pouvons maintenant lier une procédure RelaxedOnClick qui prend deux objets en paramètre, avec l'événement Click d'un bouton :

Sub RelaxedOnClick(sender As Object, e As Object) Handles B.Click
MessageBox.Show(("Hello World from" + B.Text)
End Sub

Les deux arguments d'un gestionnaire d'événement, sender et EventArgs, sont rarement utiles. Au lieu de cela, le gestionnaire accède à l'état de la commande sur laquelle l'événement est enregistré directement et ignore ses deux arguments. Pour gérer ce cas, les délégués peuvent être assouplis pour ne prendre aucun argument s'il n'y a pas d'ambiguïté. Autrement dit, nous pouvons simplement écrire :

Sub RelaxedOnClick Handles B.Click
MessageBox.Show("Hello World from" + B.Text)
End Sub

Il est bien évident que cet assouplissement s'applique aussi quand nous construisons un délégué à l'aide de l'expression AddressOf, même si la méthode subit un appel en liaison tardive :

Dim F As EventHandler = AddressOf RelaxedOnClick
Dim G As New EventHandler(AddressOf B.Click)

Interfaces dynamiques (ou « typage canard »)

Dans un langage au typage uniquement statique comme C#, Java ou Visual Basic avec Option Strict On , nous pouvons uniquement appeler les membres d'une expression cible qui existe au moment de la compilation. Par exemple, la deuxième assignation ci-dessous lèvera une erreur à la compilation puisque la classe Country n'a pas de membre Inflation :

Dim Palau As Country = Countries(0)
Dim Inflation = Country.Inflation

Cependant, dans de nombreux cas, il est nécessaire d'accéder à un membre même si on ne connaît pas son type statique. Avec Option Strict Off, Visual Basic permet la liaison tardive à un membre d'une cible de type Object. Bien que puissant et extrêmement flexible, lier tardivement a un coût. En particulier, nous perdons l'intelliSense, l'inférence de type et nous devons transtyper pour revenir dans le monde des liaisons précoces.

Souvent, même lorsque nous faisons une liaison tardive, nous supposons que la valeur adhère à une certaine « interface ». Tant que l'objet satisfait cette interface, nous sommes contents. La communauté du langage dynamique appelle cela « le typage canard » : si ça marche comme un canard et que ça parle comme un canard, alors c'est un canard. Pour illustrer l'idée du typage canard, l'exemple ci-dessous renvoie aléatoirement un Country ou un nouveau type anonyme représentant une personne, dont toutes les deux ont une propriété Name de type String :

Function NamedThing() As Object
Dim Static RandomNumbers = New Random()
Dim I = RandomNumbers.Next(9)
If  I < 5
NamedThing = Countries(I)
Else
NamedThing = New{ .Name = "John Doe", .Age = 42-I }
End If
End Function
Dim Name = CStr(NamedThing().Name)

Quand nous faisons l'appel tardif CStr(NamedThing()) , nous faisons statiquement la supposition que la valeur retournée par NamedThing() est un membre Name de type String. En utilisant les nouvelles interfaces dynamiques, nous pouvons faire la supposition explicitement. Une cible dont le type statique est une interface dynamique est liée tardivement, mais pourtant l'accès au membre est statiquement typé. Cela signifie que nous gardons entièrement l'IntelliSense et l'inférence de type, et que nous n'avons plus besoin de transtyper ou de typer explicitement :

<Dynamic> _
Interface IHasName
Property Name As String
End Interface
Dim Thing As IHasName = NamedThing()
Dim Name = Thing.Name                REM Inferred As String.

Identifiant dynamique

Les interfaces dynamiques gèrent le fait que le programmeur suppose qu'il connaît statiquement le nom et la signature des membres qu'il implique dans une liaison tardive. Toutefois, dans certains scénarios véritablement dynamiques, il est imaginable de ne même pas connaître le nom du membre appelé statiquement. Les identifiants dynamiques sont déterminés pour ces appels extrêmement tardifs là où l'identifiant d'une invocation est calculé dynamiquement.

Le prochain exemple déclare trois classes, en trois langues différentes - anglais, néerlandais et français- chacune d'elles ayant un membre « Name » dans sa langue respective :

Class English
Name As String
End Class

Class Nederlands
Naam As String
End Class
Class Français
Nom As String
End Class

Afin d'accéder à ce champ Name sur une personne, nous faisons une réflexion au-dessus de la valeur pour obtenir le nom du type et rechercher le nom du membre dans une table. Nous pouvons alors utiliser un identifiant dynamique pour appeler le bon membre de notre personne :

Dim Dictionary = New Dictionary(Of String, String) { _
.Add("English", "Name"), _
.Add("Nederlands", "Naam"), _
.Add("Français", "Nom") }
Dim Person As Object = New Français { .Nom = "Eiffel" }
Dim Name As String = Person.(Dictionary(Person.GetType().Name))

Conclusion

VISUAL BASIC 9.0 introduit une variété de nouveautés. Dans ce document, nous avons présenté ces nouveautés dans une série d'exemples liés, mais les thèmes fondamentaux mériteraient la même attention.

- Donnée relationnelle, objet et XML. Visual Basic 9.0 unifie l'accès aux données indépendamment de la source qui peut être une base de données relationnelle, un document XML ou un diagramme d'objets arbitraires qu'ils soient persistants ou stockés en mémoire. L'unification repose sur des styles, techniques, outils, et modèles de programmation. La syntaxe particulièrement flexible de Visual Basic rend aisée l'intégration des littéraux XML ou des interprétations de requête SQL-Like dans le langage. Ceci réduit fortement la surface des nouvelles API de requête pour .NET, augmente la découverte de fonctionnalités d'accès aux données à travers IntelliSense ou des SmartTags, et améliore considérablement le débogage par la disparition des syntaxes étrangères des chaînes de données au sein du langage hôte. Dans le futur, nous avons l'intention d'augmenter l'uniformité des données XML entre autres en accroissant les schémas de XSD.

- Accroître le dynamisme avec tous les avantages du typage statique. Les avantages du typage statique sont bien connus : repérer les bugs à la compilation plutôt qu'à l'exécution, performances accrues en liaison précoce, clarté d'un code explicite et ainsi de suite. Cependant, parfois, le typage dynamique rend le code plus concis, plus épuré et plus souple. Si un langage ne doit pas supporter directement le typage dynamique, lorsque les programmeurs en ont besoin, ils peuvent implémenter des morceaux de structure dynamique au travers de la réflexion, des dictionnaires, des tables de répartition, et d'autres techniques. Cela engendre des opportunités de bug et du coût de maintenance. En supportant le typage statique quand c'est possible et le typage dynamique quand c'est nécessaire, Visual Basic offre le meilleur des deux mondes aux programmeurs.

- Réduire le temps d'apprentissage. Des fonctionnalités comme l'inférence, l'initialisation d'objets et les délégués souples réduisent grandement le code redondant et le nombre d'exceptions aux règles que les programmeurs ont besoin d'apprendre ou de rechercher, sans impacter les performances. Des fonctionnalités comme les interfaces dynamiques qui supportent IntelliSense même en liaison tardive, augmentent grandement la découverte de nouvelles fonctionnalités.

Bien qu'il puisse sembler que la liste des nouvelles fonctionnalités de Visual Basic 9.0 est longue, nous espérons que les thèmes ci-dessus vous convaincront qu'il est logique, opportun, et utile de faire de Visual Basic le système de programmation le plus fin du monde. Nous espérons que votre imagination sera stimulée, aussi, et que vous nous rejoindrez en vous rendant compte que c'est vraiment juste le commencement et qu'il y a encore de plus grandes choses à venir.