CLR
Nouveautés de la bibliothèque de classes de base .NET 4.5
La bibliothèque de classes de base Microsoft .NET concerne les notions fondamentales. Bien que certaines des constructions de base soient stables et changent peu (par exemple, System.Int32 et System.String), Microsoft investit encore beaucoup dans ce domaine. Cet article est consacré aux grandes (et à certaines petites) améliorations qui ont été apportées à la bibliothèque de classes de base dans le .NET Framework 4.5.
En lisant cet article, gardez à l'esprit qu'il est basé sur le .NET Framework 4.5 Beta, et non sur le produit et les API finaux. Les fonctionnalités sont donc susceptibles de changer.
Si vous souhaitez consulter une vue d'ensemble d'autres domaines du .NET Framework, par exemple Windows Communication Foundation (WCF) ou Windows Presentation Foundation (WPF), accédez à la page des nouveautés de .NET Framework 4.5 Beta (bit.ly/p6We9u).
Programmation asynchrone simplifiée
L'utilisation des E/S asynchrones présente plusieurs avantages. Elle permet d'éviter le blocage de l'interface utilisateur et peut réduire le nombre de threads que le système d'exploitation doit utiliser. Et pourtant, il y a de grandes chances pour que vous n'en profitiez pas parce que la programmation asynchrone était auparavant relativement complexe. Le plus gros problème était que le modèle de programmation asynchrone précédent était conçu autour des paires de méthodes Begin/End. Pour illustrer le fonctionnement de ce modèle, observez la méthode synchrone simple suivante qui copie un flux :
public void CopyTo(Stream source, Stream destination)
{
byte[] buffer = new byte[0x1000];
int numRead;
while ((numRead = source.Read(buffer, 0, buffer.Length)) != 0)
{
destination.Write(buffer, 0, numRead);
}
}
Afin de rendre cette méthode asynchrone à l'aide de la méthode de programmation asynchrone précédente, vous devez écrire le code illustré à la figure 1.
Figure 1 Copie d'un flux à l'aide de l'ancienne méthode asynchrone
public void CopyToAsyncTheHardWay(Stream source, Stream destination)
{
byte[] buffer = new byte[0x1000];
Action<IAsyncResult> readWriteLoop = null;
readWriteLoop = iar =>
{
for (bool isRead = (iar == null); ; isRead = !isRead)
{
switch (isRead)
{
case true:
iar = source.BeginRead(buffer, 0, buffer.Length,
readResult =>
{
if (readResult.CompletedSynchronously) return;
readWriteLoop(readResult);
}, null);
if (!iar.CompletedSynchronously) return;
break;
case false:
int numRead = source.EndRead(iar);
if (numRead == 0)
{
return;
}
iar = destination.BeginWrite(buffer, 0, numRead,
writeResult =>
{
if (writeResult.CompletedSynchronously) return;
destination.EndWrite(writeResult);
readWriteLoop(null);
}, null);
if (!iar.CompletedSynchronously) return;
destination.EndWrite(iar);
break;
}
}
};
readWriteLoop(null);
}
Il est clair que la version asynchrone est loin d'être aussi simple à comprendre que son homologue synchrone. Il est difficile de diffuser l'intention à partir du code réutilisable nécessaire pour que des constructions de langage de programmation de base, telles que des boucles, fonctionnent lorsque des délégués sont impliqués. Si vous n'avez pas encore pris peur, essayez d'ajouter la gestion et l'annulation d'exceptions.
For heureusement, cette version de la bibliothèque de classe de base apporte un nouveau modèle de programmation qui repose sur Task et Task<T>. C# et Visual Basic ont ajouté une excellente prise en charge des langages grâce à l'ajout des mots-clés async et await (d'ailleurs, F# proposait déjà la prise en charge de langage correspondante via ses workflows et il a en fait inspiré cette fonctionnalité). Les compilateurs peuvent donc désormais se charger de la plupart du code réutilisable, voire de tout ce code, que vous deviez écrire auparavant. La nouvelle prise en charge de langages, ainsi que certains ajouts d'API au .NET Framework, fonctionnent ensemble pour rendre l'écriture de méthodes asynchrones aussi simple que celle du code synchrone. Voyez par vous-même ! Seules les modifications suivantes sont nécessaires pour rendre la méthode CopyTo asynchrone :
public async Task CopyToAsync(Stream source, Stream destination)
{
byte[] buffer = new byte[0x1000];
int numRead;
while ((numRead = await
source.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
await destination.WriteAsync(buffer, 0, numRead);
}
}
Il y aurait beaucoup de choses intéressantes à dire sur la programmation asynchrone à l'aide des nouvelles fonctionnalités de langage, mais j'aimerais me concentrer sur l'impact de la bibliothèque de classes de base et sur vous, en tant qu'utilisateur de cette bibliothèque. Si vous êtes extrêmement curieux, cependant, vous pouvez approfondir le sujet en lisant la page de MSDN Library consacrée à la programmation asynchrone avec Async et Await (C# et Visual Basic) (bit.ly/nXerAc).
Pour créer vos propres opérations asynchrones, vous avez besoin de blocs de construction de bas niveau. À partir de ces blocs, vous pouvez créer des méthodes plus complexes, telles que la méthode CopyToAsync dont nous venons de parler. Cette méthode requiert uniquement les méthodes ReadAsync et WriteAsync de la classe Stream comme blocs de construction. La figure 2 répertorie certaines des API asynchrones les plus importantes ajoutées à la bibliothèque de classes de base.
Figure 2 Méthodes asynchrones de la bibliothèque de classes de base
| Type | Méthodes |
| System.IO.Stream | ReadAsync WriteAsync FlushAsync CopyToAsync |
| System.IO.TextReader | ReadAsync ReadBlockAsync ReadLineAsync ReadToEndAsync |
| System.IO.TextWriter | WriteAsync WriteLineAsync FlushAsync |
Notez que nous n'avons pas ajouté de versions asynchrones des API avec une très petite granularité, par exemple TextReader.Peek. En effet, les API asynchrones ajoutent également des informations supplémentaires et nous souhaitions éviter de diriger les développeurs dans la mauvaise direction par accident. Cela signifie également que nous avons décidé spécifiquement de ne pas fournir de versions asynchrones pour les méthodes sur BinaryReader ou BinaryWriter. Pour utiliser ces API, nous vous recommandons d'utiliser Task.Run afin de démarrer une nouvelle opération asynchrone, puis les API synchrones depuis cette opération. Mais évitez de procéder par appel de méthode. Le conseil général est le suivant : essayez de rendre vos opérations asynchrones aussi grosses que possible. Par exemple, si vous souhaitez lire 1 000 Int32 provenant d'un flux à l'aide de BinaryReader, il est préférable d'exécuter et d'attendre une tâche pour lire les 1 000 de façon synchrone au lieu d'exécuter et d'attendre individuellement les 1 000 tâches, chacune lisant un seul Int32.
Pour en savoir plus sur l'écriture du code asynchrone, je vous recommande de lire le blog sur la programmation parallèle avec .NET (blogs.msdn.com/b/pfxteam).
Interfaces de collection en lecture seule
La fonctionnalité des interfaces de collection en lecture seule était demandée depuis longtemps dans le cadre de la bibliothèque de classes de base (à ne pas confondre avec les collections inaltérables, voir « Différentes notions sur les collections en lecture seule », plus loin dans cet article, pour en savoir plus). Notre position est que le modèle de fonctionnalité facultative est le plus adapté à la modélisation de cet aspect particulier (être en lecture seule) Ce modèle comporte une API qui permet aux utilisateurs de tester si une fonctionnalité donnée n'est pas prise en charge et de lever une exception NotSupportedException.
L'avantage de ce modèle est qu'il requiert moins de types dans la mesure où vous n'avez pas besoin de modéliser les combinaisons de fonctionnalités. Par exemple, la classe Stream offre plusieurs fonctionnalités qui sont toutes exprimées par des accesseurs get booléens (CanSeek, CanRead, CanWrite et CanTimeout). Cela permet à la bibliothèque de classes de base d'avoir un seul type pour les flux, tout en étant capable de prendre en charge chaque combinaison des fonctionnalités de diffusion en continu.
Au fil des ans, nous sommes arrivés à la conclusion que l'ajout d'une interface de collections en lecture seule valait la peine d'être effectué, malgré la complexité que cela apportait. Tout d'abord, j'aimerais vous montrer les interfaces, puis discuter des fonctionnalités qu'elles offrent. La figure 3 illustre un diagramme de classes Visual Studio comportant les interfaces de collection (altérables), tandis que la figure 4 illustre les interfaces en lecture seule correspondantes.
.jpg)
Figure 3 Interfaces de collection altérables
.jpg)
Figure 4 Interfaces de collection en lecture seule
Notez que IReadOnlyCollection<T> ne figure pas dans le .NET Framework 4.5 Beta, ne soyez donc pas surpris si vous ne la trouvez pas. Dans la version bêta, IReadOnlyList<T> et IReadOnlyDictionary<TKey,TValue> sont dérivées directement de IEnumerable<T> et chaque interface définit sa propre propriété Count.
IEnumerable<T> est covariante. Cela signifie que si une méthode accepte IEnumerable<Shape>, vous pouvez l'appeler avec IEnumerable<Circle> (en supposant que Circle soit dérivé de Shape). Cet aspect est utile dans des scénarios avec des hiérarchies de types et des algorithmes applicables à des types spécifiques, par exemple une application capable de dessiner diverses formes. IEnumerable<T> suffit pour la plupart des scénarios qui gèrent les collections de types, mais vous avez parfois besoin de plus de puissance que celle fournie par cette interface :
- Materialization.IEnumerable<T> ne vous permet pas d'indiquer si la collection est déjà disponible (« matérialisée ») ou si elle est calculée chaque fois que vous l'itérez (par exemple, si elle représente une requête LINQ). Lorsqu'un algorithme requiert plusieurs itérations sur la collection, cela peut entraîner une dégradation des performances si le calcul de la séquence est coûteux. Cela peut également provoquer des bogues subtils en raison de non-concordances d'identités lorsque les objets sont générés à nouveau sur les passes suivantes.
- Count.IEnumerable<T> ne fournit pas de compte. En fait, il est même possible qu'elle n'en ait pas puisqu'il peut s'agir d'une séquence infinie. Dans la plupart des cas, cependant, la méthode d'extension statique Enumerable.Count fait parfaitement l'affaire. Tout d'abord, elle crée des opérations spéciales pour les types de collection connus, par exemple ICollection<T> afin d'éviter l'itération de toute la séquence. Ensuite, dans la plupart des cas, le calcul du résultat n'est pas coûteux. Toutefois, selon la taille de la collection, cela peut faire une différence.
- Indexing.IEnumerable<T> ne permet pas d'accéder aux éléments de façon aléatoire. Certains algorithmes, tels que le tri rapide, dépendent de la capacité à accéder à un élément grâce à son index. Là encore, une méthode d'extension statique (Enumerable.ElementAt) utilise un chemin de code si l'énumérable est soutenu par une IList<T>. Toutefois, si l'indexation est utilisée dans une boucle, une analyse linéaire peut entraîner des conséquences désastreuses pour les performances dans la mesure où elle transformera votre bel algorithme O(n) en algorithme O(n2). C'est pourquoi, si vous avez besoin d'un accès aléatoire, eh bien, vous avez vraiment besoin d'un accès aléatoire.
Alors, pourquoi ne pas utiliser simplement ICollection<T>/IList<T> au lieu de IEnumerable<T> ? Parce que vous perdez la covariance et que vous ne pouvez plus différencier les méthodes qui lisent uniquement les collections de celles qui peuvent également les modifier, un point dont l'importance est de plus en plus cruciale lorsque vous utilisez la programmation asynchrone ou les multithreads en général. En d'autres termes, vous voulez le beurre et l'argent du beurre.
C'est là qu'entrent en scène IReadOnlyCollection<T> et IReadOnlyList<T>. IReadOnlyCollection<T> est globalement identique à IEnumerable<T>, mais ajoute une propriété Count. Cela permet de créer des algorithmes capables d'exprimer le besoin en collections matérialisées ou en collections avec une taille connue, finie. IReadOnlyList<T> développe ce point en ajoutant un indexeur. Ces deux interfaces sont covariantes, ce qui signifie que si une méthode accepte une IReadOnlyList<Shape>, vous pouvez l'appeler avec une List<Circle> :
class Shape { /*...*/ }
class Circle : Shape { /*...*/ }
void LayoutShapes(IReadOnlyList<Shape> shapes) { /*...*/ }
void LayoutCircles()
{
List<Circle> circles = GetCircles();
LayoutShapes(circles);
}
Malheureusement, notre système de type ne permet pas de rendre covariants les types de T, à moins qu'aucune méthode ne prenne T en entrée. Par conséquent, nous ne pouvons pas ajouter IndexOf method à IReadOnlyList<T>. Nous pensons qu'il s'agit d'un petit sacrifice par rapport à l'absence de prise en charge de la covariance.
Toutes nos implémentations de collections intégrées, par exemple les tableaux, List<T>, Collection<T> et ReadOnlyCollection<T>, implémentent également les interfaces de collection en lecture seule. Dans la mesure où une collection peut désormais être traitée comme collection en lecture seule, les algorithmes peuvent déclarer leur intention plus précisément, sans limiter leur niveau de réutilisation (ils peuvent être utilisés sur tous les types de collection). Dans l'exemple précédent, le consommateur de LayoutShapes a pu passer dans une List<Circle>, mais LayoutShapes accepterait également un tableau de Circle ou une Collection<Circle>.
Ces types de collection présentent également l'avantage d'offrir une expérience exceptionnelle lors de l'utilisation du Runtime Windows (WinRT). WinRT fournit ses propres types de collection, par exemple Windows.Foundation.Collections.IIterable<T> et Windows.Foundation.Collections.IVector<T>. La couche de métadonnées du CLR les projette directement sur les types de données correspondants de la bibliothèque de classes de base. Par exemple, lors de l'utilisation de WinRT depuis le .NET Framework, IIterable<T> devient IEnumerable<T> et IVector<T> devient IList<T>. En fait, un développeur qui utilise Visual Studio et IntelliSense serait même incapable de dire que WinRT a différents types de collection. Étant donné que WinRT fournit également des versions en lecture seule (par exemple, IVectorView<T>), les nouvelles interfaces en lecture seule complètent le tableau afin que tous les types de collection puissent être partagés facilement entre le .NET Framework et WinRT.
Différentes notions sur les collections en lecture seule
- Altérable (ou pas en lecture seule) Le type de collection le plus courant dans le monde .NET. Il s'agit de collections telles que List<T> qui permettent de lire, ajouter, supprimer et modifier des éléments.
- Lecture seule Ces collections ne peuvent pas être modifiées depuis l'extérieur. Toutefois, cette notion de collection ne garantit pas que le contenu ne changera jamais. Par exemple, les collections de clés et de valeurs d'un dictionnaire ne peuvent pas être mises à jour directement, mais l'ajout dans le dictionnaire met à jour les collections de clés et de valeurs indirectement.
- Inaltérable Une fois créées, ces collections ne seront jamais modifiées. Cette propriété est vraiment intéressante pour les multithreads. Si une structure de données compliquée est totalement inaltérable, elle peut toujours être passée à un processus de travail en arrière-plan. Vous n'avez pas besoin de vous soucier du risque que quelqu'un la modifie en même temps. Ce type de collection n'est pas fourni par la bibliothèque de classes de base de Microsoft .NET Framework à l'heure actuelle.
- Gelable Ces collections se comportent comme des collections altérables jusqu'à ce qu'elles soient gelées. Elles se comportent ensuite comme des collections inaltérables. Bien que la bibliothèque de classes de base ne définisse pas ces collections, vous pouvez les trouver dans Windows Presentation Foundation.
Andrew Arnott a écrit un billet excellent sur son blog, dans lequel il décrit les différentes notions relatives aux collections plus en détails (bit.ly/pDNNdM).
Prise en charge des archives .zip
La prise en charge de la lecture et de l'écriture des archives .zip standard était également demandée depuis longtemps. Le .NET Framework 3.0 et les versions ultérieures prennent en charge la lecture et l'écriture des archives dans la spécification Open Packaging Convention (bit.ly/ddsfZ7). Toutefois, System.IO.Packaging a été conçue de façon à prendre en charge cette spécification et ne peut généralement pas être utilisée pour traiter les archives .zip ordinaires.
Cette version ajoute une excellente prise en charge des .zip via System.IO.Compression.ZipArchive. En outre, nous avons résolu des problèmes de longue date liés aux performances et à la qualité de compression dans notre implémentation de DeflateStream. Depuis .NET Framework 4.5, la classe DeflateStream utilise la bibliothèque populaire zlib. Elle fournit ainsi une meilleure implémentation de l'algorithme deflate et, dans la plupart des cas, un fichier compressé plus petit que dans les versions précédentes de .NET Framework.
L'extraction de toute une archive sur disque ne prend qu'une seule ligne :
ZipFile.ExtractToDirectory(@"P:\files.zip", @"P:\files");
Nous avons également veillé à ce que les opérations courantes ne nécessitent pas la lecture de toute l'archive dans la mémoire. Par exemple, l'extraction d'un seul fichier d'une archive de grande taille peut être effectuée de la façon suivante :
using (ZipArchive zipArchive =
ZipFile.Open(@"P:\files.zip", ZipArchiveMode.Read))
{
foreach (ZipArchiveEntry entry in zipArchive.Entries)
{
if (entry.Name == "file.txt")
{
using (Stream stream = entry.Open())
{
ProcessFile(stream);
}
}
}
}
Dans le cas présent, la seule partie qui est chargée dans la mémoire est le sommaire de l'archive .zip. Le fichier extrait est totalement diffusé en continu, ce qui signifie qu'il n'a pas besoin de tenir dans la mémoire. Cela permet de traiter arbitrairement de grandes archives .zip, même si la mémoire est limitée.
La création d'archives .zip fonctionne de façon similaire. Afin de créer une archive .zip à partir d'un répertoire, vous ne devez écrire qu'une seule ligne :
ZipFile.CreateFromDirectory(@"P:\files", @"P:\files.zip");
Bien sûr, vous pouvez également construire manuellement une archive .zip, ce qui vous donne un contrôle complet de la structure interne. Le code suivant illustre la méthode de création d'une archive .zip dans laquelle seuls les fichiers de code source sont ajoutés (en outre, l'archive .zip contient désormais un sous-répertoire nommé SourceCode) :
IEnumerable<string> files =
Directory.EnumerateFiles(@"P:\files", "*.cs");
using (ZipArchive zipArchive =
ZipFile.Open(@"P:\files.zip", ZipArchiveMode.Create))
{
foreach (string file in files)
{
var entryName = Path.Combine("SourceCode", Path.GetFileName(file));
zipArchive.CreateEntryFromFile(file, entryName);
}
}
L'utilisation des flux vous permet également de créer des archives .zip dans lesquelles les entrées ne sont pas soutenues par un fichier réel. Il en va de même pour l'archive .zip elle-même. Par exemple, vous pouvez utiliser le constructeur ZipArchive qui prend un flux au lieu de ZipFile.Open. Cela vous sera utile, par exemple, pour construire un serveur Web qui crée instantanément des archives .zip à partir du contenu stocké dans une base de données et qui écrit également les résultats directement dans le flux de réponse plutôt que sur le disque.
Il est intéressant d'expliquer un peu plus un détail relatif à la conception de l'API. Vous avez probablement remarqué que les méthodes statiques pratiques sont définies dans le ZipFile de la classe, tandis que l'instance réelle a le type ZipArchive. Pourquoi ?
Depuis le .NET Framework 4.5, nous concevons les API en pensant à la portabilité. Certaines plateformes .NET ne prennent pas en charge les chemins d'accès aux fichiers, comme les applications de style Metro .NET sur Windows 8. Sur cette plateforme, l'accès au système de fichiers est demandé de façon à activer un modèle basé sur les capacités. Pour lire ou écrire des fichiers, vous ne pouvez plus utiliser les API Win32 standard, mais vous devez utiliser les API WinRT.
Comme je vous l'ai montré, la fonctionnalité .zip elle-même ne requiert aucun chemin de fichier. Nous séparons donc la fonctionnalité en deux parties :
- System.IO.Compression.dll. Cet assembly contient la fonctionnalité .zip à usage général. Il ne prend pas en charge les chemins d'accès aux fichiers. La classe principale de cet assembly est ZipArchive.
- System.IO.Compression.FileSystem.dll. Cet assembly fournit une classe statique ZipFile qui définit les méthodes d'extension et les assistances classiques. Ces API ajoutent à la fonctionnalité .zip les méthodes pratiques sur les plateformes .NET qui prennent en charge les chemins d'accès aux systèmes de fichiers.
Pour en savoir plus sur l'écriture du code .NET portable, consultez la page consacrée aux bibliothèques de classes portables dans MSDN Library, sur bit.ly/z2r3eM.
Améliorations diverses
Bien évidemment, il y a toujours plus à dire. Après avoir travaillé pendant si longtemps sur une version, vous avez parfois l'impression que l'on vous demande de donner le nom de votre enfant préféré lorsque vous devez citer les fonctionnalités les plus importantes. Dans les sections qui suivent, j'aimerais mettre en évidence quelques points qui méritent d'être mentionnés, mais n'ont pas encore reçu toute l'attention qu'ils méritent dans cet article :
AssemblyMetadataAttribute Dans le .NET Framework, nous avons appris une chose au sujet des attributs : quel que soit leur nombre, il vous en faut tout de même plus (d'ailleurs, le .NET Framework 4 avait déjà plus de 300 attributs de niveau assembly). AssemblyMetadataAttribute est un attribut d'assembly à usage général qui permet d'associer une paire clé-valeur de type chaîne à un assembly. Cela peut être utilisé pour pointer vers la page d'accueil du produit ou vers l'étiquette de contrôle de version qui correspond à la source à partir de laquelle l'assembly a été créé.
WeakReference<T> Le type WeakReference existant non générique présente deux problèmes. Tout d'abord, il oblige les utilisateurs à effectuer une conversion lorsqu'il est nécessaire d'accéder à la cible. Mais surtout, il comporte un défaut de conception qui le rend vulnérable aux conditions de concurrence : il expose une première API afin de vérifier si l'objet est actif (ISAlive) et une autre, distincte, afin d'obtenir l'objet réel (Target). WeakReference<T> résout ce problème en fournissant l'API unique TryGetTarget, qui effectue les deux opérations de façon atomique.
Comparer<T>.Create(Comparison<T>) La bibliothèque de classes de base fournit deux façons d'implémenter les comparateurs de collections. La première utilise l'interface IComparer<T> et la seconde, le délégué Comparison<T>. Il est simple de convertir IComparer<T> en Comparison<T>. La plupart des langages offrent une conversion implicite d'une méthode vers un type de délégué, vous pouvez donc aisément construire la méthode Comparison<T> à partir de la méthode de comparaison IComparer<T>. Toutefois, dans l'autre sens, vous deviez implémenter vous-même une IComparer<T>. Dans le .NET Framework 4.5, nous avons ajouté une méthode statique Create sur Comparer<T> qui, étant donné une Comparison<T>, vous donne une implémentation de IComparer<T>.
ArraySegment<T> Dans le .NET Framework 2.0, nous avons ajouté la structure ArraySegment<T>, qui vous permet de représenter un sous-ensemble d'un tableau donné sans qu'une copie soit nécessaire. Malheureusement, ArraySegment<T> n'implémentait aucune interface de collection, ce qui vous empêchait de la passer aux méthodes qui interviennent sur les interfaces de collection. Dans cette version, nous avons résolu ce problème et ArraySegment<T> implémente désormais IEnumerable, IEnumerable<T>, ICollection<T> et IList<T>, ainsi que IReadOnlyCollection<T> et IReadOnlyList<T>.
SemaphoreSlim.WaitAsync Il s'agit de la seule primitive de synchronisation qui prend en charge l'attente de prise du verrou. Pour en savoir plus sur cette logique, consultez la page des nouveautés sur le parallélisme dans .NET 4.5 Beta (bit.ly/AmAUIF).
ReadOnlyDictionary<TKey,TValue> Depuis le .NET Framework 2.0, la bibliothèque de classes de base fournit ReadOnlyCollection<T>, qui joue le rôle de wrapper en lecture seule autour d'une instance de collection donnée. Cela permet aux implémenteurs des modèles d'objets d'exposer les collections que l'utilisateur ne peut pas modifier. Dans cette version, nous avons ajouté le même concept pour les dictionnaires.
BinaryReader, BinaryWriter, StreamReader, StreamWriter : l'option pour ne pas supprimer le flux sous-jacent Les classes de lecture et d'écriture de niveau supérieur acceptent toutes une instance de flux dans leurs constructeurs. Dans les versions précédentes, cela signifiait que la propriété de ce flux était transmise à l'instance de lecture/écriture. Cela impliquait que la suppression de cette dernière supprimait également le flux sous-jacent. Tout cela est bien beau lorsque vous n'avez qu'une lecture/écriture, mais cela complique les choses lorsque vous devez composer plusieurs API différentes qui fonctionnent toutes sur des flux, mais ont besoin d'utiliser les instances de lecture/écriture dans le cadre de leur implémentation. Dans les versions précédentes, le mieux à faire était de ne pas supprimer la lecture/écriture et de laisser un commentaire dans la source qui expliquait le problème (cette approche vous obligeait également à purger manuellement l'écriture pour éviter les pertes de données). Le .NET Framework 4.5 vous permet d'exprimer ce contrat en utilisant un constructeur de lecture/écriture qui prend le paramètre leaveOpen dans lequel vous pouvez spécifier explicitement que la lecture/écriture ne doit pas supprimer le flux sous-jacent.
Console.IsInputRedirected, Console.IsOutputRedirected et Console.IsErrorRedirected Les programmes de ligne de commande prennent en charge la redirection des entrées et des sorties. Pour la plupart des applications, cette opération est transparente. Vous avez toutefois parfois besoin d'un comportement différent lorsque la redirection est active. Par exemple, la sortie de console colorée n'est pas utile et la définition de la position du curseur échouera. Ces propriétés permettent les requêtes pour savoir si l'un des flux standard a été redirigé.
Console.OutputEncoding et Console.InputEncoding peuvent maintenant être définis sur Encoding.Unicode La définition de Console.OutputEncoding sur Encoding.Unicode permet au programme d'écrire des caractères qui n'ont pas pu être représentés dans la page de code OEM associée à la console. Cette fonctionnalité facilite également l'affichage du texte dans plusieurs scripts sur la console. Vous trouverez plus de détails dans la documentation révisée à venir de MSDN Library pour la classe Console.
ExceptionDispatchInfo La gestion des erreurs est un aspect important de la création des composants du framework. Dans certains cas, il n'est pas suffisant de lever à nouveau des exceptions (en C#, via « throw »), car cela peut uniquement se produire dans le cadre d'un gestionnaire d'exceptions. Certains composants du framework, tels que l'infrastructure Task, doivent lever à nouveau l'exception ultérieurement (par exemple, après avoir marshalé jusqu'au thread initial). Par le passé, cela signifiait que la trace de pile initiale et la classification des rapports d'erreurs Windows (également connue sous le nom de compartiments Watson) étaient perdues, dans la mesure où le fait de lever à nouveau le même objet d'exception ne faisait que remplacer ces informations. ExceptionDispatchInfo permet de capturer un objet d'exception existant et de le lever à nouveau sans perdre aucune des informations importantes enregistrées dans l'objet d'exception.
Regex.Timeout Les expressions régulières constituent une excellente façon de valider les entrées. Toutefois, peu de personnes savent que certaines expressions régulières peuvent être incroyablement coûteuses à calculer lorsqu'elles sont appliquées à des entrées de texte spécifiques. En fait, elles ont une complexité exponentielle. Cela pose notamment un problème dans les environnements de serveur, dans lesquels les expressions régulières réelles sont soumises à la configuration. Étant donné qu'il est difficile, voire impossible, de prédire le comportement d'exécution d'une expression régulière donnée, l'approche la plus traditionnelle dans ces cas de figure consiste à appliquer une contrainte sur la durée pendant laquelle le moteur Regex tentera de correspondre à une entrée donnée. C'est la raison pour laquelle Regex a désormais plusieurs API qui acceptent un délai d'expiration : Regex.IsMatch, Regex.Match, Regex.Matches, Regex.Split et Regex.Replace.
N'hésitez pas à participer
Les fonctionnalités évoquées dans cet article sont disponibles dans Visual Studio 11 Beta. Cette version est conforme aux normes élevées pour une pré-version de logiciel, c'est pourquoi nous la prenons en charge dans les environnements de production. Vous pouvez télécharger la version bêta sur bit.ly/9JWDT9. Étant donné qu'il s'agit d'une pré-version de logiciel, vos commentaires nous intéressent, que vous ayez rencontré des problèmes (connect.microsoft.com/visualstudio) ou que vous ayez des idées pour de nouvelles fonctionnalités (visualstudio.uservoice.com). Je vous suggère également de vous abonner au blog de mon équipe, à l'adresse blogs.msdn.com/b/bclteam, afin d'être informé des modifications ou annonces à venir.
Immo Landwerth est responsable de programme auprès de l'équipe CLR chez Microsoft, où il travaille sur la bibliothèque de classes de base Microsoft .NET Framework, la conception des API et les bibliothèques de classes portables. Vous pouvez le contacter via le blog de l'équipe de la bibliothèque de classes de base à l'adresse blogs.msdn.com/b/bclteam.
Je remercie les experts techniques suivants d'avoir relu cet article : Nicholas Blumhardt, Greg Paperin, Daniel Plaisted, Evgeny Roubinchtein, Alok Shriram, Chris Szurgot, Stephen Toub et Mircea Trofin