Sunteți pe pagina 1din 2174

Contents

Documentation .NET
Prise en main de .NET
Présentation de .NET
Composants architecturaux de .NET
Vue d’ensemble de .NET Standard
Nouveautés de .NET Standard
Versions cibles de .NET Framework
Glossaire .NET
Conseils sur la bibliothèque open source
Choisir entre .NET Core et .NET Framework pour les applications serveur
Code managé
Qu’est-ce que le code managé ?
Gestion automatique de la mémoire
Common Language Runtime (CLR)
Indépendance du langage
Vue d'ensemble
Composants indépendants du langage
Bibliothèques de framework
Vue d'ensemble
Vue d'ensemble de la bibliothèque de classes
Utiliser des types de base
Système de type commun
Conversion de types dans .NET
Tables de conversion de types
Choisir entre des types de tuples et anonymes
Mettre en forme les nombres, les dates et autres types
Vue d'ensemble
Chaînes de format numériques standard
Chaînes de format numériques personnalisées
Chaînes de format de date et d’heure standard
Chaînes de format de date et d’heure personnalisées
Chaînes de format TimeSpan standard
Chaînes de format TimeSpan personnalisées
Chaînes de format d’énumération
Mise en forme composite
Articles Comment faire
Remplir un nombre avec des zéros non significatifs
Extraire le jour de la semaine à partir d’une date
Utiliser des fournisseurs de formats numériques personnalisés
Effectuer un aller-retour de valeurs de date et d’heure
Afficher les millisecondes dans les valeurs de date et d’heure
Afficher les dates dans des calendriers non grégoriens
Manipuler des chaînes
Bonnes pratiques pour l’utilisation de chaînes
Opérations de chaînes de base
Vue d'ensemble
Créer de nouvelles chaînes
Découper et supprimer des caractères
Remplir des chaînes
Comparer des chaînes
Changer la casse
Utiliser la classe StringBuilder
Procédure : effectuer des manipulations de chaînes de base
Expressions régulières dans .NET
Vue d'ensemble
Informations de référence sur le langage
Vue d'ensemble
Caractères d’échappement
Classes de caractères
Ancres
Constructions de regroupement
Quantificateurs
Constructions de référence arrière
Constructions d’alternative
Substitutions
Options des expressions régulières
Constructions diverses
Bonnes pratiques pour les expressions régulières
Modèle objet d’expression régulière
Comportement des expressions régulières
Vue d'ensemble
Rétroaction
Compilation et réutilisation
Sécurité des threads
Exemples
Rechercher les HREF
Changer les formats de date
Extraire un protocole et un numéro de port d’une URL
Supprimer des caractères non valides d’une chaîne
Vérifier que des chaînes sont dans un format d’adresse e-mail valide
Encodage de caractères dans .NET
Utilisation des classes d’encodage des caractères
Analyser des chaînes
Vue d'ensemble
Analyser des chaînes numériques
Analyser des chaînes de date et heure
Analyser d’autres chaînes
Bibliothèques de classes .NET
Analyseurs
Vue d'ensemble
Analyseur d’API
Analyseur de portabilité
Analyseur de framework
Gestion des exceptions
Vue d'ensemble
Classe et propriétés d’exception
Articles Comment faire
Utiliser le bloc try-catch pour intercepter les exceptions
Utiliser des exceptions spécifiques dans un bloc Catch
Lever explicitement des exceptions
Créer des exceptions définies par l’utilisateur
Créer des exceptions définies par l’utilisateur avec des messages d’exception
localisés
Utiliser des blocs Finally
Utiliser des gestionnaires d’exceptions filtrés par l’utilisateur
Gérer les exceptions COM Interop
meilleures pratiques recommandées.
Assemblys dans .NET
Gestion de la mémoire
Nettoyer les ressources non managées
Vue d'ensemble
Implémenter une méthode Dispose
Implémenter une méthode DisposeAsync
Utiliser des objets implémentant IDisposable
Garbage collection
Vue d'ensemble
Notions de base
GC de station de travail et de serveur
GC d’arrière-plan
Tas d’objets volumineux
Garbage collection et performances
Collections forcées
Modes de latence
Optimisation pour l’hébergement web partagé
Notifications du garbage collection
Supervision des ressource de domaine d’application
Références faibles
Types génériques
Vue d'ensemble
Introduction aux types génériques
Collections génériques dans .NET
Délégués génériques pour la manipulation de tableaux et de listes
Interfaces génériques
Covariance et contravariance
Délégués et expressions lambda
LINQ
Système de type commun et spécification de langage commun
Traitement parallèle, concurrence et programmation asynchrone
Vue d'ensemble
Programmation asynchrone
Vue d'ensemble
Approfondissement de la programmation asynchrone
Modèles de programmation asynchrone
Programmation parallèle
Vue d'ensemble
Bibliothèque parallèle de tâches
Parallélisme des données
Procédure : écrire une boucle Parallel.For simple
Procédure : écrire une boucle Parallel.ForEach simple
Procédure : écrire une boucle Parallel.For avec des variables locales de thread
Procédure : écrire une boucle Parallel.ForEach avec des variables locales de
partition
Procédure : annuler une boucle Parallel.For ou ForEach
Procédure : gérer des exceptions dans des boucles parallèles
Procédure : accélérer les petits corps de boucles
Procédure : Itérer les répertoires de fichiers avec la classe parallèle
Programmation asynchrone basée sur les tâches
Chaînage des tâches à l’aide de tâches de continuation
Tâches enfants attachées et détachées
Annulation de tâches
Gestion des exceptions
Procédure : utiliser Parallel_Invoke pour exécuter des opérations parallèles
Procédure : retourner une valeur à partir d’une tâche
Procédure : annuler une tâche et ses enfants
Procédure : créer des tâches précalculées
Procédure : parcourir un arbre binaire avec des tâches parallèles
Procédure : désencapsuler une tâche imbriquée
Procédure : empêcher une tâche enfant de s’attacher à son parent
Dataflow
Procédure : Écrire et lire des messages dans un bloc de flux de données
Procédure : implémenter un modèle de flux de données producteur-
consommateur
Procédure : Exécuter des actions quand un bloc de flux de données reçoit des
données
Procédure pas à pas : création d’un pipeline de dataflow
Procédure : Dissocier des blocs de flux de données
Procédure pas à pas : utilisation d’un dataflow dans une application Windows
Forms
Procédure : annuler un bloc de dataflow
Procédure pas à pas : Créer un type de bloc de flux de données personnalisé
Procédure : Utiliser JoinBlock pour lire des données de plusieurs sources
Procédure : Spécifier le degré de parallélisme dans un bloc de flux de données
Procédure : spécifier un planificateur de tâches dans un bloc de dataflow
Procédure pas à pas : Utiliser BatchBlock et BatchedJoinBlock pour améliorer
l’efficacité
Utilisation de la bibliothèque parallèle de tâches (TPL) avec d’autres modèles
asynchrones
Bibliothèque parallèle de tâches (TPL) et programmation asynchrone .NET
Framework
Procédure : inclure dans un wrapper des modèles EAP dans une tâche
Pièges potentiels dans le parallélisme des données et des tâches
Parallel LINQ (PLINQ)
Introduction à PLINQ
Fonctionnement de l'accélération dans PLINQ
Conservation de l'ordre en PLINQ
Options de fusion en PLINQ
Pièges potentiels avec PLINQ
Procédure : créer et exécuter une requête PLINQ simple
Procédure : contrôler l’ordre dans une requête PLINQ
Procédure : combiner des requêtes LINQ parallèles et séquentielles
Procédure : gérer des exceptions dans une requête PLINQ
Procédure : annuler une requête PLINQ
Procédure : écrire une fonction d’agrégation PLINQ personnalisée
Procédure : spécifier le mode d’exécution avec PLINQ
Procédure : spécifier des options de fusion avec PLINQ
Procédure : itérer les répertoires de fichiers avec PLINQ
Procédure : mesurer les performances de requêtes PLINQ
Données PLINQ, exemple
Structures de données pour la programmation parallèle
Outils de diagnostic parallèle
Partitionneurs personnalisés pour PLINQ et la bibliothèque TPL
Vue d'ensemble
Procédure : implémenter des partitions dynamiques
Procédure : implémenter un partitionneur pour un partitionnement statique
Expressions lambda dans PLINQ et la bibliothèque TPL
Informations supplémentaires
Thread
Types liés à la mémoire et à l’étendue
Vue d'ensemble
Instructions d’utilisation de la mémoire<T> et de l’étendue<T>
Interopérabilité native
Vue d'ensemble
P/Invoke
Marshaling de types
Personnaliser le marshaling de structures
Personnaliser le marshaling de paramètres
Aide sur l’interopérabilité
Charsets et marshaling
COM interop
Vue d'ensemble
Wrappers COM
Vue d'ensemble
Wrapper appelable par le runtime
Wrapper appelable par COM
Qualification des types .NET pour COM Interop
Appliquer des attributs d’interopérabilité
Exceptions
Collections et structures de données
Vue d'ensemble
Sélectionner une classe de collections
Types de collections couramment utilisés
Quand utiliser les collections génériques
Comparaisons et tris dans les collections
Types de collections triées
Types Hashtable et Dictionary
Collections thread-safe
Valeurs numériques dans .NET
Vue d'ensemble
Types SIMD
Dates, heures et fuseaux horaires
Événements
Vue d'ensemble
Déclencher et consommer des événements
Gérer plusieurs événements avec des propriétés d’événements
Consommer des événements dans une application Web Forms
Modèle de conception Observateur
Vue d'ensemble
meilleures pratiques recommandées.
Procédure : Implémenter un fournisseur
Procédure : Implémenter un observateur
Processus d’exécution managée
Métadonnées et composants autodescriptifs
Créer des applications console
E/S de fichier et de flux
Vue d'ensemble
Formats de chemin de fichier sur les systèmes Windows
Tâches d’E/S courantes
Procédure : Copier des répertoires
Procédure : énumérer des répertoires et des fichiers
Procédure : lire et écrire dans un fichier de données créé récemment
Procédure : ouvrir un fichier journal et y ajouter des éléments
Procédure : écrire du texte dans un fichier
Procédure : lire le texte d’un fichier
Procédure : lire les caractères d’une chaîne
Procédure : écrire des caractères dans une chaîne
Procédure : ajouter ou supprimer des entrées dans la liste de contrôle d’accès
Procédure : compresser et extraire des fichiers
Composition de flux
Procédure : effectuer une conversion entre les flux .NET Framework et les flux
Windows Runtime
E/S sur fichier asynchrones
Gérer les erreurs d’E/S
Stockage isolé
Types d'isolation
Procédure : obtenir des magasins pour le stockage isolé
Procédure : énumérer des magasins pour le stockage isolé
Procédure : supprimer des magasins dans le stockage isolé
Procédure : anticiper des conditions d’espace insuffisant avec le stockage isolé
Procédure : créer des fichiers et des répertoires dans un stockage isolé
Procédure : rechercher des fichiers et des répertoires existants dans un stockage
isolé
Procédure : lire et écrire des fichiers dans un stockage isolé
Procédure : supprimer des fichiers et des répertoires dans un stockage isolé
Canaux
Procédure : utiliser des canaux anonymes pour la communication entre processus en
local
Procédure : utiliser des canaux nommés pour la communication entre processus en
réseau
Pipelines
Utiliser des tampons
Fichiers mappés en mémoire
Globalisation et localisation
Attributs
Vue d'ensemble
Appliquer des attributs
Utiliser des attributs personnalisés
Récupérer les informations stockées dans les attributs
Règles de conception du .NET Framework
Vue d'ensemble
Instructions de nommage
Conventions de mise en majuscules
Conventions générales de nommage
Noms des assemblys et des DLL
Noms des espaces de noms
Noms des classes, structures et interfaces
Noms des membres de type
Paramètres de nommage
Ressources d’affectation de noms
Instructions de conception de types
Choisir entre une classe et un struct
Conception de classes abstraites
Conception de classes statiques
Conception d'interfaces
Conception de structs
Conception d’énums
Types imbriqués
Instructions de conception des membres
Surcharge de membres
Conception de propriétés
Conception de constructeurs
Conception d’événements
Conception de champs
Méthodes d’extension
Surcharges d’opérateurs
Conception de paramètres
Conception pour une extensibilité
Classes unsealed
Membres protégés
Événements et rappels
Membres virtuels
Abstractions (types et interfaces abstraits)
Classes de base pour l’implémentation d’abstractions
Sceller
Instructions de conception d’exceptions
Levée d’exceptions
Utiliser des types d’exceptions standard
Exceptions et performances
Indications relatives à l'utilisation
Tableaux
Attributs
Collections
Sérialisation
Utilisation de System.Xml
Opérateurs d'égalité
Modèles de conception courants
Propriétés de dépendance
Propriétés de dépendance
Microsoft.Data.Sqlite
Documents et données XML
Sécurité
Sérialisation
Vue d'ensemble
Sérialisation JSON
Vue d'ensemble
Guide pratique pour sérialiser et désérialiser JSON
Guide pratique pour écrire des convertisseurs personnalisés
Guide pratique pour migrer à partir de Newtonsoft.Json
Sérialisation binaire
Vue d'ensemble
Concepts de la sérialisation
Sérialisation de base
Sérialisation sélective
Sérialisation personnalisée
Étapes du processus de sérialisation
Sérialisation avec tolérance de version
Indications concernant la sérialisation
Procédure : Segmenter des données sérialisées
Procédure : déterminer si un objet .NET Standard est sérialisable
Exemple
Sérialisation XML et SOAP
Vue d'ensemble
Sérialisation XML en profondeur
Exemples
Outil de définition de schéma XML
Contrôler la sérialisation XML avec des attributs
Attributs qui contrôlent la sérialisation XML
Sérialisation XML avec les services web XML
Attributs qui contrôlent la sérialisation encodée avec SOAP
Articles Comment faire
Sérialiser un objet
Désérialiser un objet
Utiliser l’outil de définition de schéma XML pour générer des classes et des
documents de schéma XML
Contrôler la sérialisation des classes dérivées
Spécifier un autre nom d’élément pour un flux XML
Qualifier des noms d’éléments et d’attributs XML
Sérialiser un objet en tant que flux XML encodé avec SOAP
Substituer la sérialisation XML encodée avec SOAP
Éléments de sérialisation XML
system.xml.serialization
dateTimeSerialization
schemaImporterExtensions
Ajouter des éléments pour schemaImporterExtensions
xmlSerializer
Outils
Outil XML Serializer Generator (Sgen.exe)
Outil XML Schema Definition (Xsd.exe)
Développer pour plusieurs plateformes
Vue d'ensemble
Bibliothèque de classes portable
Utiliser la bibliothèque de classes portables avec MVVM
Ressources d’application pour les bibliothèques qui ciblent plusieurs plateformes
Prise en charge de .NET Framework pour les applications Microsoft Store et Windows
Runtime
Passer un URI au Windows Runtime
Bien démarrer
31/03/2020 • 2 minutes to read • Edit Online

Il existe plusieurs façons de bien démarrer avec .NET. Parce que .NET est une plate-forme massive, il ya plusieurs
articles dans cette documentation qui peut vous aider à démarrer avec .NET, chacun d’un point de vue différent.

Commencer à utiliser les langages .NET


Pour les tutoriels de démarrage en C, Visual Basic et F, voir :
Prise en main de C#
Tutoriels C#
Tutoriels de démarrage de F
Lancez-vous dans Visual Basic

Bien démarrer avec .NET Core


Pour .NET Core-specific guidance, voir:
Bien démarrer avec .NET Core
tutoriels .NET Core

Commencer à utiliser .NET Standard


Pour un tutoriel d’introduction, voir Construire une bibliothèque .NET Standard dans Visual Studio.

Bien démarrer avec .NET Core sur Docker


L’article Introduction à .NET et à Docker montre comment vous pouvez utiliser .NET Core sur des conteneurs
Windows Docker.
Présentation de .NET
18/03/2020 • 18 minutes to read • Edit Online

.NET est une plateforme de développement généraliste. Elle comporte plusieurs fonctionnalités clés, telles que la
prise en charge de plusieurs langages de programmation, des modèles de programmation asynchrone et
simultanée et une interopérabilité native, qui permettent un large éventail de scénarios sur plusieurs plateformes.
Cet article propose une visite guidée de certaines fonctionnalités principales de .NET. Consultez la rubrique
Composants architecturaux de .NET pour en savoir plus sur les parties architecturales de .NET et sur leur finalité.

Guide pratique pour exécuter les exemples de code


Pour savoir comment configurer un environnement de développement pour exécuter les exemples de code,
consultez la rubrique Bien démarrer. Copiez et collez les exemples de code à partir de cette page dans votre
environnement pour les exécuter.

Langages de programmation
.NET prend en charge plusieurs langages de programmation. Les implémentations de .NET implémentent le
Common Language Infrastructure (CLI), qui, entre autres, spécifie un runtime indépendant du langage et une
interopérabilité des langages. Cela signifie que vous choisissez n’importe quel langage .NET pour générer des
applications et services sur .NET.
Microsoft développe et prend en charge activement trois langues .NET : C, F et Visual Basic.
C# est simple, puissant, de type sécurisé et orienté objet, tout en conservant l’expressivité et l’élégance des
langages de style C. Les utilisateurs familiarisés avec le langage C et les langages similaires ont peu de
difficultés à s’adapter à C#. Consultez le Guide C# pour en savoir plus sur C#.
F# est un langage de programmation multiplateforme et fonctionnel qui prend également en charge la
programmation orientée objet et impérative traditionnelle. Consultez le Guide F# pour en savoir plus sur F#.
Visual Basic est un langage facile à apprendre que vous utilisez pour créer une variété d’applications qui
s’exécutent sur .NET. Parmi les langues .NET, la syntaxe de Visual Basic est la plus proche du langage humain
ordinaire, ce qui rend souvent plus facile pour les personnes nouvelles au développement de logiciels.

Gestion automatique de la mémoire


.NET utilise le garbage collection (GC) pour fournir une gestion automatique de la mémoire pour les programmes.
Le récupérateur de mémoire opère avec une approche différée de la gestion de la mémoire, préférant le débit de
l’application à la collecte immédiate de la mémoire. Pour plus d’informations sur le garbage collector .NET,
consultez Notions de base du garbage collection (GC).
Les deux lignes suivantes allouent de la mémoire :

var title = ".NET Primer";


var list = new List<string>();

Il n’existe aucun mot clé analogue pour libérer de la mémoire, car la libération de mémoire se produit
automatiquement quand le récupérateur de mémoire réclame la mémoire dans son exécution planifiée.
Le garbage collector est un des services qui garantissent la sûreté de la mémoire. Un programme est sûr du point
de vue de la mémoire s’il accède uniquement à la mémoire allouée. Par exemple, le runtime garantit qu’une
application n’accède pas à la mémoire non allouée au-delà des limites d’un tableau.
Dans l’exemple suivant, le runtime lève une exception IndexOutOfRangeException pour appliquer la sûreté de la
mémoire :

int[] numbers = new int[42];


int number = numbers[42]; // Will throw an exception (indexes are 0-based)

Utilisation des ressources non managées


Certains objets font référence à des ressources non managées. Les ressources non managées sont des ressources
qui ne sont pas automatiquement gérées par le runtime .NET. Par exemple, un handle de fichier est une ressource
non managée. Un objet FileStream est un objet managé, mais il fait référence à un handle de fichier qui ne l’est pas.
Quand vous avez fini d’utiliser l’objet FileStream, vous devez libérer le handle de fichier.
Dans .NET, les objets qui font référence à des ressources non managées implémentent l’interface IDisposable.
Quand vous avez fini d’utiliser l’objet, vous appelez la méthode Dispose() de l’objet qui est chargée de libérer les
ressources non managées. .Les langues NET fournissent une using déclaration pratique pour ces objets, comme le
montre l’exemple suivant :

using System.IO;

using (FileStream stream = GetFileStream(context))


{
// Operations on the stream
}

Une fois que le bloc using est fini, le runtime .NET appelle automatiquement la méthode Dispose() de l’objet
stream qui libère le handle de fichier. Le runtime agit également ainsi quand une exception entraîne le contrôle à
laisser le bloc.
Pour plus de détails, consultez les rubriques suivantes :
Pour C#, consultez la rubrique using, instruction (référence C#).
Pour F#, consultez Gestion des ressources : mot clé use.
Pour Visual Basic, voir le sujet Using Statement (Visual Basic).

Cohérence des types


Un objet est une instance d’un type spécifique. Les seules opérations autorisées pour un objet donné sont celles de
son type. Un type Dog peut avoir des méthodes Jump et WagTail , mais pas une méthode SumTotal . Un
programme appelle uniquement les méthodes appartenant à un type donné. Tous les autres appels entraînent une
erreur au moment de la compilation ou une exception au moment de l’exécution (en cas d’utilisation de
fonctionnalités dynamiques ou du type object ).
Les langages .NET sont orientés objet avec des hiérarchies de classes de base et dérivées. Le runtime .NET autorise
uniquement les casts et les appels d’objet qui s’alignent sur la hiérarchie d’objets. N’oubliez pas que chaque type
défini dans un langage .NET dérive du type Object de base.
Dog dog = AnimalShelter.AdoptDog(); // Returns a Dog type.
Pet pet = (Pet)dog; // Dog derives from Pet.
pet.ActCute();
Car car = (Car)dog; // Will throw - no relationship between Car and Dog.
object temp = (object)dog; // Legal - a Dog is an object.

La cohérence des types est également utilisée pour aider à appliquer l’encapsulation en garantissant la fidélité des
mots clés d’accesseur. Les mots clés d’accesseur sont des artefacts qui contrôlent l’accès aux membres d’un type
donné par un autre code. Ils servent généralement à différentes sortes de données dans un type utilisées pour
gérer son comportement.

private Dog _nextDogToBeAdopted = AnimalShelter.AdoptDog()

C#, Visual Basic et F# prennent en charge l’inférence de type locale. L’inférence de type signifie que le compilateur
déduit le type de l’expression à gauche à partir de l’expression à droite. Cela ne signifie pas que la cohérence des
types est interrompue ou évitée. Le type résultant a un type fort avec tout ce que cela implique. À partir de
l’exemple précédent, dog est réécrit pour présenter l’inférence de type, et le reste de l’exemple reste inchangé :

var dog = AnimalShelter.AdoptDog();


var pet = (Pet)dog;
pet.ActCute();
Car car = (Car)dog; // will throw - no relationship between Car and Dog
object temp = (object)dog; // legal - a Dog is an object
car = (Car)temp; // will throw - the runtime isn't fooled
car.Accelerate() // the dog won't like this, nor will the program get this far

F a encore plus de capacités d’inférence de type que l’inférence de type type type méthode-locale trouvée dans C et
Visual Basic. Pour plus d’informations, consultez Type Inference (Inférence de type).

Délégués et expressions lambda


Un délégué est représenté par une signature de méthode. Toute méthode avec cette signature peut être assignée au
délégué et est exécutée quand celui-ci est appelé.
Les délégués sont semblables aux pointeurs de fonction C++, à la différence qu’ils sont de type sécurisé. Ils
représentent une sorte de méthode déconnectée au sein du système de type CLR. Les méthodes classiques sont
attachées à une classe et peuvent être appelées directement uniquement par des conventions d’appel statiques ou
d’instance.
Dans .NET, les délégués sont souvent utilisés dans les gestionnaires d’événements, dans la définition des opérations
asynchrones et dans les expressions lambda, qui sont la pierre angulaire de LINQ. Pour en savoir plus, consultez la
rubrique Délégués et expressions lambda.

Génériques
Les génériques permettent au programmeur d’introduire un paramètre de type quand il désigne leurs classes qui
permet au code client (les utilisateurs du type) de spécifier le type exact à utiliser à la place du paramètre de type.
Les génériques ont été ajoutés pour aider les programmeurs à implémenter des structures de données génériques.
Avant leur arrivée, pour qu’un List type tel que le type soit générique, object il faudrait travailler avec des
éléments de type . Cela a eu diverses performances et problèmes sémantiques, ainsi que d’éventuelles erreurs
subtiles de temps de course. Une erreur de temps de ruissellement commune est lorsqu’une structure de données
contient, par exemple, des intégraux et des cordes, et qu’une InvalidCastException erreur est lancée pendant le
traitement des membres de la liste.
L’exemple suivant montre une exécution de programme de base utilisant une instance des types List<T> :

using System;
using System.Collections.Generic;

namespace GenericsSampleShort
{
public static void Main(string[] args)
{
// List<string> is the client way of specifying the actual type for the type parameter T
List<string> listOfStrings = new List<string> { "First", "Second", "Third" };

// listOfStrings can accept only strings, both on read and write.


listOfStrings.Add("Fourth");

// Below will throw a compile-time error, since the type parameter


// specifies this list as containing only strings.
listOfStrings.Add(1);
}
}

Pour plus d’informations, consultez la rubrique Vue d’ensemble des types génériques (Génériques).

Programmation asynchrone
La programmation asynchrone est un concept de première classe dans .NET avec prise en charge asynchrone dans
le runtime, des bibliothèques de framework et des constructions de langage .NET. En interne, ils sont basés sur des
objets (comme Task ), qui tirent parti du système d’exploitation pour effectuer aussi efficacement que possible des
tâches utilisant des E/S.
Pour en savoir plus sur la programmation asynchrone dans .NET, commencez par la rubrique Vue d’ensemble
d’Async.

LINQ (Language-Integrated Query)


LINQ est un ensemble puissant de fonctionnalités pour C et Visual Basic qui vous permet d’écrire un code simple et
déclaratif pour l’exploitation de données. Les données peuvent se présenter sous plusieurs formes (comme des
objets en mémoire, une base de données SQL ou un document XML), mais le code LINQ que vous écrivez ne diffère
généralement pas d’une source de données à l’autre.
Pour en savoir plus et obtenir des exemples, consultez la rubrique LINQ (Language Integrated Query).

Interopérabilité native
Chaque système d’exploitation inclut une interface de programmation d’application (API) qui fournit des services
système. .NET offre plusieurs moyens d’appeler ces API.
Le principal moyen d’effectuer une interopérabilité native est via l’« appel de code non managé » ou P/Invoke, en
forme abrégée, fonctionnalité prise en charge sur les plateformes Linux et Windows. Un moyen de faire une
interopérabilité native sur Windows uniquement est connu sous le nom de « COM Interop », utilisé pour travailler
avec des composants COM dans du code managé. Il est basé sur l’infrastructure de P/Invoke, mais fonctionne
légèrement différemment.
Une grande partie de la prise en charge d’interopérabilité dans Mono (et donc dans Xamarin) pour Java et
Objective-C est générée de la même façon, autrement dit, ils utilisent les mêmes principes.
Pour plus d’informations sur l’interopérabilité native, consultez l’article Interopérabilité native.
Code unsafe
En fonction de la prise en charge du langage, le CLR vous permet d’accéder à la mémoire native et d’effectuer une
opération arithmétique de pointeur par le biais de code unsafe . Ces opérations sont nécessaires pour certains
algorithmes et pour l’interopérabilité du système. L’utilisation de code unsafe, bien que puissante, est déconseillée,
sauf si elle est nécessaire pour assurer l’interopérabilité avec les API système ou implémenter l’algorithme le plus
efficace. Le code unsafe peut ne pas s’exécuter de la même façon dans différents environnements et perd les
avantages d’un récupérateur de mémoire et de la cohérence des types. Nous vous recommandons de restreindre et
centraliser le code unsafe autant que possible et de tester ce code de manière approfondie.
L’exemple suivant est une version modifiée de la méthode ToString() à partir de la classe StringBuilder . Il illustre
comment l’utilisation de code unsafe peut implémenter efficacement un algorithme en déplaçant directement des
segments de mémoire :

public override String ToString()


{
if (Length == 0)
return String.Empty;

string ret = string.FastAllocateString(Length);


StringBuilder chunk = this;
unsafe
{
fixed (char* destinationPtr = ret)
{
do
{
if (chunk.m_ChunkLength > 0)
{
// Copy these into local variables so that they are stable even in the presence of ----s
(hackers might do this)
char[] sourceArray = chunk.m_ChunkChars;
int chunkOffset = chunk.m_ChunkOffset;
int chunkLength = chunk.m_ChunkLength;

// Check that we will not overrun our boundaries.


if ((uint)(chunkLength + chunkOffset) <= ret.Length && (uint)chunkLength <=
(uint)sourceArray.Length)
{
fixed (char* sourcePtr = sourceArray)
string.wstrcpy(destinationPtr + chunkOffset, sourcePtr, chunkLength);
}
else
{
throw new ArgumentOutOfRangeException("chunkLength",
Environment.GetResourceString("ArgumentOutOfRange_Index"));
}
}
chunk = chunk.m_ChunkPrevious;
} while (chunk != null);
}
}
return ret;
}

Étapes suivantes
Si vous êtes intéressé par une présentation des fonctionnalités de C#, consultez Tour of C# (Présentation de C#).
Si vous êtes intéressé par une présentation des fonctionnalités de F#, consultez Visite guidée de F#.
Si vous voulez vous familiariser avec l’écriture de votre propre code, visitez la page Bien démarrer.
Pour obtenir des informations sur les composants importants de .NET, consultez Composants architecturaux de
.NET.
Composants architecturaux de .NET
27/03/2020 • 10 minutes to read • Edit Online

Une application .NET est développée pour une ou plusieurs implémentations de .NET et s’exécute dans ces
dernières. Les implémentations de .NET incluent .NET Framework, .NET Core et Mono. Il existe une spécification
d’API commune à toutes les implémentations de .NET, appelée .NET Standard. Cet article présente brièvement
chacun de ces concepts.

.NET Standard
.NET Standard est un ensemble d’API qui sont mis en œuvre par la Bibliothèque de classe de base d’une mise en
œuvre .NET. Plus formellement, il s’agit d’une spécification d’API .NET qui constituent un ensemble cohérent de
contrats par rapport auxquels vous compilez votre code. Ces contrats sont implémentés dans chaque
implémentation de .NET. Cela permet la portabilité entre les différentes implémentations de .NET ; votre code peut
dès lors « s’exécuter partout ».
.NET Standard est également un cadre cible. Si votre code cible une version de .NET Standard, il peut s’exécuter sur
n’importe quelle implémentation .NET qui prend en charge cette version de .NET Standard.
Pour en savoir plus sur .NET Standard et comment le cibler, voir .NET Standard.

Implémentations de .NET
Chaque implémentation de .NET inclut les composants suivants :
Un ou plusieurs runtimes. Exemples : CLR pour .NET Framework, CoreCLR et CoreRT pour .NET Core.
Une bibliothèque de classes qui implémente .NET Standard et qui peut implémenter des API supplémentaires.
Exemples : bibliothèque de classes de base .NET Framework, bibliothèque de classes de base .NET Core.
Le cas échéant, un ou plusieurs frameworks d’application. Exemples : ASP.NET, Windows Formset Windows
Presentation Foundation (WPF) sont inclus dans le cadre .NET et .NET Core.
Le cas échéant, des outils de développement. Certains outils de développement sont partagés entre plusieurs
implémentations.
Il existe quatre implémentations de .NET principales que Microsoft développe et gère activement : .NET Core, .NET
Framework, Mono et UWP.
.NET Core
.NET Core est une implémentation multiplateforme de .NET conçue pour gérer les charges de travail serveur et
cloud à l’échelle. Il s’exécute sur Windows, macOS et Linux. Comme il implémente .NET Standard, tout code qui
cible .NET Standard peut s’exécuter sur .NET Core. ASP.NET, Windows Forms et Windows Presentation Foundation
(WPF) s’exécutent tous sur .NET Core.
Pour en savoir plus sur .NET Core, consultez le Guide .NET Core et Choix entre .NET Core et .NET Framework pour
les applications serveur.
.NET Framework
.NET Framework est la mise en œuvre originale .NET qui existe depuis 2002. Versions 4.5 et plus tard implémenter
.NET Standard, de sorte que le code qui cible .NET Standard peut s’exécuter sur ces versions de .NET Framework. Il
contient des API supplémentaires spécifiques à Windows, notamment des API pour le développement bureautique
Windows avec Windows Forms et WPF. .NET Framework est optimisé pour la génération d’applications de bureau
Windows.
Pour en savoir plus sur .NET Framework, consultez le Guide cadre .NET.
Mono
Mono est une implémentation de .NET qui est principalement utilisée quand un runtime réduit est requis. C’est le
temps d’exécution qui alimente les applications Xamarin sur Android, macOS, iOS, tvOS, et watchOS et se
concentre principalement sur une petite empreinte. Mono alimente également les jeux créés à l’aide du moteur
Unity.
Il prend en charge toutes les versions de .NET Standard publiées.
Historiquement, Mono implémentait l’API plus volumineuse de .NET Framework et émulait certaines des
fonctionnalités les plus populaires sur Unix. Il est parfois utilisé pour exécuter des applications .NET qui s’appuient
sur ces fonctionnalités sous Unix.
Mono est généralement utilisé avec un compilateur juste-à-temps, mais il comporte également un compilateur
statique complet (compilation Ahead Of Time) qui est utilisé sur des plateformes comme iOS.
Pour en savoir plus sur Mono, consultez la documentation Mono.
Plateforme Windows universelle (UWP)
UWP est une implémentation de .NET qui sert à générer des logiciels et des applications Windows tactiles
modernes pour l’Internet des objets (IoT). Il est conçu pour unifier les différents types d’appareils que vous
voudrez peut-être cibler, y compris les PC, tablettes, téléphones, et même la Xbox. UWP fournit de nombreux
services, comme un magasin d’applications centralisé, un environnement d’exécution (AppContainer) et un
ensemble d’API Windows à utiliser à la place de Win32 (WinRT). Les applications peuvent être écrites en C, C,
Visual Basic et JavaScript. Lors de l’utilisation de C et Visual Basic, les API .NET sont fournis par .NET Core.
Pour en savoir plus sur UWP, consultez Introduction à la plateforme Windows universelle.

Runtimes .NET
Un runtime est l’environnement d’exécution d’un programme managé. Le système d’exploitation fait partie de
l’environnement d’exécution, mais pas du runtime .NET. Voici quelques exemples de runtimes .NET :
CLR (Common Language Runtime) pour .NET Framework
CoreCLR (Core Common Language Runtime) pour .NET Core
.NET Native pour la plateforme Windows universelle
Le runtime Mono pour Xamarin.iOS, Xamarin.Android, Xamarin.Mac et le framework de bureau Mono

Outils .NET et infrastructure commune


Vous avez accès à un ensemble complet d’outils et de composants d’infrastructure qui fonctionnent avec toutes les
implémentations de .NET. Ces outils et composants comprennent :
Les langages .NET et leurs compilateurs
Le système de projet .NET (basé sur les fichiers .csproj, .vbproj et .fsproj)
MSBuild, le moteur de construction utilisé pour construire des projets
NuGet, le gestionnaire de paquets de Microsoft pour .NET
Outils d’orchestration de génération open source, tels que CAKE et FAKE

Normes applicables
Les spécifications de la langue et de l’infrastructure de langue commune (CLI) sont normalisées par Ecma
International®. Les premières éditions de ces normes ont été publiées par Ecma en décembre 2001.
Des révisions ultérieures des normes ont été élaborées par les groupes de travail TC49-TG2 (C) et TC49-TG3 (CLI)
au sein du Comité technique des langues de programmation(TC49),et adoptées par l’Assemblée générale d’Ecma
et par la suite par l’ISO/IEC JTC 1 par le biais du processus ISO Fast-Track.
Dernières normes
Les documents officiels Ecma suivants sont disponibles pour le C et le CLI (TR-84) :
La norme linguistique CMD (version 5.0) : ECMA-334.pdf
L’infrastructure linguistique commune : Disponible sous forme pdf et sous forme de zip.
Informations dérivées du fichier Par tition IV XML : Disponible en format pdf et zip.
Les documents officiels isO/IEC sont disponibles sur la page normes publiques ISO/IEC. Ces liens sont directement
à partir de cette page:
Technologies de l’information - Langages de programmation - C ' : ISO/IEC 23270:2018
Technologies de l’information — Common Language Infrastructure (CLI) Par titions I to VI : ISO/IEC
23271:2012
Technologies de l’information — Infrastructure linguistique commune (CLI) — Rappor t technique
sur l’information dérivé du fichier Par tition IV XML : ISO/IEC TR 23272:2011

Voir aussi
Choix entre .NET Core et .NET Framework pour les applications serveur
.NET Standard
Guide de base .NET
Guide-cadre .NET
Guide de voyage CMD
Guide de voyage F
Guide de base visuel
.NET Standard
17/04/2020 • 21 minutes to read • Edit Online

.NET Standard est une spécification formelle de .NET API qui sont destinés à être disponibles sur toutes les
implémentations .NET. La motivation derrière .NET Standard est d’établir une plus grande uniformité dans
l’écosystème .NET. ECMA 335 continue d’établir l’uniformité pour le comportement de mise en œuvre .NET, et
tandis que ECMA 335 spécifie un petit ensemble de bibliothèques standard, la spécification .NET Standard
englobe un plus large éventail d’API .NET.
.NET Standard permet les scénarios clés suivants :
Définit un ensemble uniforme d’API de bibliothèque de classes de base pour toutes les implémentations de
.NET à implémenter, indépendamment de la charge de travail.
Permet aux développeurs de générer des bibliothèques portables utilisables sur toutes les implémentations
de .NET, à l’aide de ce même ensemble d’API.
Réduit ou même élimine une compilation conditionnelle de source partagée résultant des API .NET,
uniquement pour les API de système d’exploitation.
Les différentes implémentations de .NET ciblent des versions spécifiques de .NET Standard. Chaque version de
l’implémentation de .NET publie la version la plus récente de .NET Standard qu’elle prend en charge, une
instruction qui signifie qu’elle prend également en charge les versions précédentes. Par exemple, .NET
Framework 4.6 implémente .NET Standard 1.3, ce qui signifie qu’il expose toutes les API définies dans les
versions standard .NET 1.0 à 1.3. De même, .NET Framework 4.6.1 implémente .NET Standard 1.4, tandis que
.NET Core 1.0 implémente .NET Standard 1.6.

Prise en charge des implémentations de .NET


Le tableau suivant liste les versions minimales des plateformes qui prennent en charge chaque version de .NET
Standard. Cela signifie que les versions ultérieures d’une plateforme listée prennent également en charge la
version correspondante de .NET Standard. Par exemple, .NET Core 2.2 prend en charge .NET Standard 2.0 et les
versions antérieures.

. N ET
STA N DA
RD 1. 0 1. 1 1. 2 1. 3 1. 4 1. 5 1. 6 2. 0 2. 1

.NET 1.0 1.0 1.0 1.0 1.0 1.0 1.0 2 3.0


Core

.NET 4.5 4.5 4.5.1 4.6 4.6.1 4.6.1 2 4.6.1 2 4.6.1 2 N/A3
Framew
ork 1

Mono 4.6 4.6 4.6 4.6 4.6 4.6 4.6 5.4 6.4

Xamarin 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.14 12.16
.iOS

Xamarin 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.8 5.16
.Mac
. N ET
STA N DA
RD 1. 0 1. 1 1. 2 1. 3 1. 4 1. 5 1. 6 2. 0 2. 1

Xamarin 7.0 7.0 7.0 7.0 7.0 7.0 7.0 8.0 10.0
.Androi
d

Platefor 10.0 10.0 10.0 10.0 10.0 10.0.16 10.0.16 10.0.16 TBD
me 299 299 299
Window
s
universe
lle

Unity 2018.1 2018.1 2018.1 2018.1 2018.1 2018.1 2018.1 2018.1 TBD

1 Les versions répertoriées pour .NET Framework s’appliquent à .NET Core 2.0 SDK et aux versions ultérieures de l’outillage. Les anciennes versions
utilisaient une cartographie différente pour .NET Standard 1.5 et plus. Vous pouvez télécharger l’outillage pour les outils .NET Core pour Visual
Studio 2015 si vous ne pouvez pas passer à Visual Studio 2017 ou une version ultérieure.

2 Les versions énumérées ici représentent les règles que NuGet utilise pour déterminer si une bibliothèque standard .NET donnée est applicable.
Bien que NuGet considère .NET Framework 4.6.1 comme soutenant .NET Standard 1.5 à 2.0, il ya plusieurs problèmes avec la consommation .NET
bibliothèques standard qui ont été construits pour ces versions à partir de .NET Framework 4.6.1 projets. Pour les projets cadre .NET qui doivent
utiliser ces bibliothèques, nous vous recommandons de mettre à niveau le projet pour cibler .NET Framework 4.7.2 ou plus.

3 .NET Framework ne supporte pas .NET Standard 2.1 ou versions ultérieures. Pour plus de détails, voir l’annonce de .NET Standard 2.1.

Les colonnes représentent les versions de .NET Standard. Chaque cellule d’en-tête est un lien vers un
document qui indique les API ajoutées à cette version de .NET Standard.
Les lignes représentent les différentes implémentations .NET.
Le numéro de version dans chaque cellule indique la version minimale de l’implémentation nécessaire pour
cibler cette version de .NET Standard.
Pour un tableau interactif, consultez Versions .NET Standard.
Pour trouver la version la plus élevée de .NET Standard que vous pouvez cibler, procédez comme suit :
1. Recherchez la ligne indiquant l’implémentation de .NET sur laquelle vous voulez exécuter.
2. Recherchez la colonne de cette ligne qui indique votre version, en allant de droite à gauche.
3. L’en-tête de colonne indique la version .NET Standard que votre cible prend en charge. Vous pouvez
également cibler une version .NET Standard antérieure. Les versions .NET Standard ultérieures prendront
également en charge votre implémentation.
4. Répétez ce processus pour chaque plateforme que vous voulez cibler. Si vous avez plusieurs plateformes
cibles, vous devez choisir la version la moins élevée parmi elles. Par exemple, si vous voulez exécuter sur le
.NET Framework 4.5 et .NET Core 1.0, la version .NET Standard le plus élevée que vous pouvez utiliser est
.NET Standard 1.1.
Version de .NET Standard à cibler
Lors du choix d’une version de .NET Standard, vous devez envisager ce compromis :
Plus la version est élevée, plus nombreuses sont les API disponibles.
Moins la version est élevée, plus nombreuses sont les plateformes qui l’implémentent.
D’une façon générale, nous vous recommandons de cibler la version la moins élevée possible de .NET Standard.
Par conséquent, après avoir trouvé la version de .NET Standard la plus élevée que vous pouvez cibler, procédez
comme suit :
1. Ciblez la version moins élevée suivante de .NET Standard et générez votre projet.
2. Si votre projet est généré correctement, répétez l’étape 1. Dans le cas contraire, reciblez-le sur la version plus
élevée suivante : c’est cette version que vous devez utiliser.
Le ciblage de versions inférieures de .NET Standard génère cependant un certain nombre de dépendances de
support. Si votre projet cible .NET Standard 1.x, nous vous recommandons de cibler également .NET Standard
2.0. Ceci simplifie le graphique de dépendance pour les utilisateurs de votre bibliothèque qui exécutent des
infrastructures compatibles .NET Standard 2.0 et réduit le nombre de packages qu’ils doivent télécharger.
Règles de contrôle de version de .NET standard
Il existe deux règles principales de contrôle de version :
Additive : les versions de .NET Standard sont des cercles logiquement concentriques : les versions plus
élevées intègrent toutes les API des versions précédentes. Il n’y a pas de ruptures entre les versions.
Immuable : une fois livrées, les versions de .NET Standard sont figées. Les nouvelles API sont disponibles
d’abord dans les implémentations de .NET spécifiques, comme .NET Core. Si le comité de révision de .NET
Standard estime que les nouvelles API doivent être disponibles pour les implémentations de .NET, elles sont
ajoutées dans une nouvelle version de .NET Standard.

Caractéristique
La spécification de .NET Standard est un ensemble d’API normalisé. La spécification est gérée par les entités
chargées de l’implémentation de .NET, en particulier Microsoft (inclut .NET Framework, .NET Core et Mono) et
Unity. Un processus de commentaires publics est utilisé dans le cadre de l’établissement des nouvelles versions
de .NET Standard via GitHub.
Artefacts officiels
La spécification officielle est un ensemble de fichiers .cs qui définissent les API qui font partie de la norme. Le
répertoire ref dans le dépôt dotnet/standard définit les API .NET Standard.
Le métapackage NETStandard.Library (source) décrit l’ensemble des bibliothèques qui définissent (en partie)
une ou plusieurs versions de .NET Standard.
Un composant donné, comme System.Runtime , décrit :
Une partie de .NET Standard (seulement son étendue).
Plusieurs versions de .NET Standard, pour cette étendue.
Des artefacts dérivés sont fournis pour une lecture plus pratique et pour activer certains scénarios de
développement (par exemple, utilisation d’un compilateur).
Liste des API au format Markdown
Assemblys de référence, distribués comme packages NuGet et référencés par le métapackage
NETStandard.Library.
Représentation des packages
Les assemblys de référence de .NET Standard sont distribués principalement via les packages NuGet. Les
implémentations sont fournies de façons différentes, en fonction de l’implémentation de .NET.
Les packages NuGet ciblent un ou plusieurs frameworks. Les packages .NET Standard ciblent le framework
«.NET Standard ». Vous pouvez cibler le .NET Framework Standard avec le Moniker de framework cible compact
netstandard (par exemple netstandard1.4 ). Les bibliothèques destinées à s’exécuter sur plusieurs runtimes
doivent cibler ce framework. Pour l’ensemble d’API le plus large, ciblez netstandard2.0 , car le nombre d’API
disponibles a plus que doublé entre .NET Standard 1.6 et 2.0.
Le NETStandard.Library métapackage fait référence à l’ensemble complet des paquets NuGet qui définissent
.NET Standard. La méthode la plus courante pour cibler netstandard consiste à référencer ce métapackage. Il
décrit et donne accès à la quarantaine de bibliothèques .NET et les API associées qui définissent .NET Standard.
Vous pouvez référencer d’autres packages qui ciblent netstandard pour avoir accès à d’autres API.
Gestion de version
La spécification n’est pas singulière, mais représente un ensemble d’API dont la croissance est incrémentielle et
les versions linéaires. La première version de la norme établit un ensemble d’API de référence. Les versions
ultérieures ajoutent des API et héritent des API définies par les versions précédentes. Il n’existe aucune
disposition établie pour supprimer des API de la norme.
.NET Standard n’est spécifique à aucune implémentation de .NET et ne correspond pas au schéma de contrôle de
version de ces runtimes.
Les API ajoutées à une implémentation (par exemple, .NET Framework, .NET Core et Mono) peuvent être
considérées comme des candidats à ajouter à la spécification, en particulier si elles sont jugées fondamentales.
Des versions de .NET Standard sont créées en fonction des versions Release des implémentations de .NET, ce qui
vous permet de cibler les nouvelles API à partir d’une bibliothèque de classes portable .NET Standard. Les
mécanismes du contrôle de version sont décrits plus en détail dans Gestion de version .NET Core.
Le contrôle de version de .NET Standard est important pour son utilisation. Pour une version donnée de .NET
Standard, vous pouvez utiliser des bibliothèques qui ciblent cette même version ou une version moins élevée.
L’approche suivante décrit le flux de travail de l’utilisation des bibliothèques de classes portables .NET Standard,
propre au ciblage de .NET Standard.
Sélectionnez une version de .NET Standard à utiliser pour votre bibliothèque de classes portable.
Utilisez des bibliothèques qui dépendent de la même version de .NET Standard ou d’une version inférieure.
Si vous trouvez une bibliothèque qui dépend d’une version plus élevée de .NET Standard, vous devez
adopter cette même version ou décider de ne pas utiliser cette bibliothèque.

Cible .NET Standard


Vous pouvez créer des bibliothèques .NET Standard en combinant le framework netstandard et le métapackage
NETStandard.Library. Vous pouvez voir des exemples de ciblage .NET Standard avec des outils .NET Core.

Mode de compatibilité du .NET Framework


Le mode de compatibilité du .NET Framework a été introduit dans .NET Standard 2.0. Ce mode de compatibilité
permet aux projets .NET Standard de référencer des bibliothèques .NET Framework comme si elles étaient
compilées pour .NET Standard. Le référencement de bibliothèques .NET Framework ne fonctionne pas pour tous
les projets, par exemple pour les bibliothèques qui utilisent des API WPF (Windows Presentation Foundation).
Pour plus d’informations, consultez Mode de compatibilité du .NET Framework.

Bibliothèques .NET Standard et Visual Studio


Afin de construire des bibliothèques Standard .NET dans Visual Studio, assurez-vous d’avoir Visual Studio 2019
ou Visual Studio 2017 version 15.3 ou plus tard installé sur Windows, ou Visual Studio pour mac version 7.1 ou
plus tard installé sur macOS.
Si vous devez seulement utiliser les bibliothèques .NET Standard 2.0 dans vos projets, vous pouvez également le
faire dans Visual Studio 2015. Cependant, le client NuGet 3.6 ou ultérieur doit être installé. Vous pouvez
télécharger le client NuGet pour Visual Studio 2015 à partir de la page Téléchargements NuGet.

Comparaison avec les bibliothèques de classes portables


.NET Standard remplace les bibliothèques de classes portables. .NET Standard améliore l’expérience de la
création de bibliothèques portables en organisant un BCL standard et en établissant une plus grande uniformité
à travers les implémentations .NET en conséquence. Une bibliothèque qui cible .NET Standard est une
bibliothèque de classes portable ou une « bibliothèque de classes portable .NET Standard ». Les bibliothèques
de classes portables existantes sont basées sur un profil.
.NET Standard et les profils de bibliothèque de classes portable ont été créés pour des raisons similaires, mais
diffèrent de façon significative.
Points communs :
Définit les API qui peuvent être utilisées pour le partage de code binaire.
Différences :
.NET Standard est un ensemble organisé d’API, tandis que les profils de bibliothèque de classes portable sont
définis par des intersections de plateformes existantes.
.NET Standard a des versions linéaires, contrairement aux profils de bibliothèque de classes portable.
Les profils PCL représentent les plates-formes Microsoft tandis que .NET Standard est agnostique plate-
forme.
Compatibilité des bibliothèques de classes portables
.NET Standard est compatible avec un sous-ensemble de profils de bibliothèque de classes portable. Les
versions 1.0, 1.1 et 1.2 de .NET Standard se recouvrent chacune avec un ensemble de profils de bibliothèque de
classes portable. Ce chevauchement a été créé pour deux raisons :
Permettre aux bibliothèques de classes portables .NET Standard de référencer les bibliothèques de classes
portables basées sur un profil.
Permettre aux bibliothèques de classes portables basées sur un profil d’être packagées comme des
bibliothèques de classes portables .NET Standard.
La compatibilité des bibliothèques de classes portables est fournie par le package NuGet
Microsoft.NETCore.Portable.Compatibility. Cette dépendance est nécessaire quand vous référencez des
packages NuGet qui contiennent des bibliothèques de classes portables basées sur un profil.
Les bibliothèques de classes portables basées sur un profil packagées en netstandard sont plus faciles à utiliser
que les bibliothèques de classes portables basées sur un profil packagées de manière habituelle. Les packages
netstandard sont compatibles avec les utilisateurs existants.

Vous pouvez voir l’ensemble des profils PCL qui sont compatibles avec .NET Standard:

P RO F IL DE B IB L IOT H ÈQ UE DE C L A SSES P L AT EF O RM ES DE B IB L IOT H ÈQ UE DE


P O RTA B L E . N ET STA N DA RD C L A SSES P O RTA B L E

Profile7 1.1 .NET Framework 4.5, Windows 8

Profile31 1.0 Windows 8.1, Windows Phone


Silverlight 8.1

Profile32 1.2 Windows 8.1, Windows Phone 8.1

Profile44 1.2 .NET Framework 4.5.1, Windows 8.1

Profile49 1.0 .NET Framework 4.5, Windows Phone


Silverlight 8
P RO F IL DE B IB L IOT H ÈQ UE DE C L A SSES P L AT EF O RM ES DE B IB L IOT H ÈQ UE DE
P O RTA B L E . N ET STA N DA RD C L A SSES P O RTA B L E

Profile78 1.0 .NET Framework 4.5, Windows 8,


Windows Phone Silverlight 8

Profile84 1.0 Windows Phone 8.1, Windows Phone


Silverlight 8.1

Profile111 1.1 .NET Framework 4.5, Windows 8,


Windows Phone 8.1

Profile151 1.2 .NET Framework 4.5.1, Windows 8.1,


Windows Phone 8.1

Profile157 1.0 Windows 8.1, Windows Phone 8.1,


Windows Phone Silverlight 8.1

Profile259 1.0 .NET Framework 4.5, Windows 8,


Windows Phone 8.1, Windows Phone
Silverlight 8

Voir aussi
Versions de .NET Standard
Construire une bibliothèque .NET Standard
Ciblage multiplateforme
Quoi de neuf dans .NET Standard
31/03/2020 • 7 minutes to read • Edit Online

.NET Standard est une spécification formelle qui définit un ensemble versionné d’API qui doivent être disponibles
sur les implémentations .NET qui se conforment à cette version de la norme. .NET Standard s’adresse aux
développeurs de bibliothèques. Une bibliothèque qui cible une version .NET Standard peut être utilisée sur
n’importe quelle implémentation .NET Framework, .NET Core ou Xamarin prenant en charge cette version de la
norme.
.NET Standard est inclus avec le .NET Core SDK, ainsi qu’avec Visual Studio lorsque vous sélectionnez la charge de
travail .NET Core.

Implémentations de .NET prises en charge


.NET Standard 2.0 est soutenu par les implémentations suivantes .NET:
.NET Core 2.0 ou ultérieur
.NET Framework 4.6.1 ou ultérieur
Mono 5.4 ou ultérieur
Xamarin.iOS 10.14 ou ultérieur
Xamarin.Mac 3.8 ou ultérieur
Xamarin.Android 8.0 ou ultérieur
Plateforme Windows universelle 10.0.16299 ou ultérieure

Quoi de neuf dans .NET Standard 2.0


.NET Standard 2.0 comprend les nouvelles fonctionnalités suivantes:
Un ensemble d’API largement étendu
Grâce à la version 1.6, .NET Standard comprenait un sous-ensemble relativement petit d’API. Ce sous-ensemble
excluait de nombreuses API utilisées dans .NET Framework ou Xamarin. Cela complique le développement en
obligeant les développeurs à trouver des remplacements appropriés pour les API courantes lorsqu’ils développent
des applications et des bibliothèques ciblant plusieurs implémentations .NET. .NET Standard 2.0 répond à cette
limitation en ajoutant plus de 20.000 API de plus que ce qui était disponible dans .NET Standard 1.6, la version
précédente de la norme. Pour une liste des API qui ont été ajoutées à .NET Standard 2.0, voir .NET Standard 2.0 vs
1.6.
Voici certains des ajouts à l’espace de noms System dans .NET Standard 2.0 :
Prise en charge de la classe AppDomain.
Meilleure prise en charge de l’utilisation de tableaux provenant d’autres membres de la classe Array.
Meilleure prise en charge de l’utilisation d’attributs provenant d’autres membres de la classe Attribute.
Meilleure prise en charge du calendrier et d’autres options de mise en forme pour les valeurs DateTime.
Ajout d’une fonctionnalité d’arrondi Decimal.
Ajout d’une fonctionnalité à la classe Environment.
Meilleur contrôle du récupérateur de mémoire via la classe GC.
Meilleure prise en charge de la comparaison de chaînes, de l’énumération et de la normalisation dans la classe
String.
Prise en charge des ajustements à l’heure d’été et des durées de transition dans les classes
TimeZoneInfo.AdjustmentRule et TimeZoneInfo.TransitionTime.
Importante amélioration de la fonctionnalité dans la classe Type.
Meilleure prise en charge de la désérialisation des objets d’exception par ajout d’un constructeur d’exception
avec les paramètres SerializationInfo et StreamingContext.
Prise en charge des bibliothèques .NET Framework
L’écrasante majorité des bibliothèques ciblent .NET Framework plutôt que .NET Standard. Cependant, la plupart des
appels dans ces bibliothèques sont à des API qui sont inclus dans .NET Standard 2.0. En commençant par .NET
Standard 2.0, vous pouvez accéder à des bibliothèques cadre .NET à partir d’une bibliothèque .NET Standard en
utilisant un cal de compatibilité. Cette couche de compatibilité est transparente pour les développeurs ; vous n’avez
rien à faire pour tirer parti des bibliothèques .NET Framework.
L’exigence unique est que les API appelées par la bibliothèque de classe cadre .NET doivent être incluses dans la
norme .NET 2.0.
Prise en charge de Visual Basic
Vous pouvez désormais développer des bibliothèques .NET Standard dans Visual Basic. Visual Studio 2019 et Visual
Studio 2017 version 15.3 ou plus tard avec la charge de travail .NET Core installé comprennent un modèle .NET
Standard Class Library. Pour les développeurs Visual Basic qui utilisent d’autres outils de développement et
environnements, vous pouvez utiliser la commande dotnet new pour créer un projet de bibliothèque .NET Standard.
Pour plus d’informations, consultez Prise en charge des outils pour les bibliothèques .NET Standard.
Prise en charge des outils pour les bibliothèques .NET Standard
Avec la sortie de .NET Core 2.0 et .NET Standard 2.0, Visual Studio 2017 et le .NET Core CLI incluent le support
d’outillage pour la création de bibliothèques .NET Standard.
Si vous installez Visual Studio avec la charge de travail de développement multiplateforme .NET Core, vous
pouvez créer un projet de bibliothèque .NET Standard 2.0 en utilisant un modèle de projet, comme le montre le
chiffre suivant :
C#
Visual Basic
Si vous utilisez le CLI .NET Core, la nouvelle commande dotnet suivante crée un projet de bibliothèque de classe qui
cible .NET Standard 2.0 :

dotnet new classlib

Voir aussi
.NET Standard
Présentation de .NET Standard
Cadres cibles dans les projets de type SDK
18/03/2020 • 8 minutes to read • Edit Online

Quand vous ciblez un framework dans une application ou une bibliothèque, vous spécifiez l’ensemble d’API que
vous souhaitez rendre accessibles à l’application ou à la bibliothèque. Vous spécifiez le framework cible dans
votre fichier projet à l’aide des monikers du framework cible (TFM).
Une application ou une bibliothèque peut cibler une version de .NET Standard. Les versions .NET Standard
représentent des ensembles d’API standard sur toutes les implémentations de .NET. Par exemple, une bibliothèque
peut cibler .NET Standard 1.6 et accéder aux API qui fonctionnent sur .NET Core et .NET Framework en utilisant la
même base de code.
Une application ou une bibliothèque peut également cibler une implémentation spécifique de .NET pour accéder
aux API spécifiques à l’implémentation. Ainsi, une application qui cible Xamarin.iOS (par exemple, Xamarin.iOS10 )
accède à des wrappers d’API iOS fournis par Xamarin pour iOS 10, ou une application qui cible la plateforme
Windows universelle (UWP, uap10.0 ) a accès aux API de compilation pour les appareils qui exécutent
Windows 10.
Pour certains frameworks cibles (par exemple, .NET Framework), les API sont définies par les assemblys que le
framework installe sur un système et peuvent inclure des API de framework d’application (par exemple, ASP.NET).
Pour les frameworks cibles basés sur le package (par exemple, .NET Standard et .NET Core), les API sont définies
par les packages inclus dans l’application ou la bibliothèque. Un métapackage est un package NuGet qui n’a
aucun contenu propre, mais qui est une liste de dépendances (autres packages). Un framework cible basé sur un
package NuGet spécifie implicitement un métapackage qui référence tous les packages constituant le framework.

Versions les plus récentes des frameworks cibles


Le tableau ci-dessous définit les frameworks cibles les plus courants, la façon dont ils sont référencés et la version
de .NET Standard qu’ils implémentent. Ces versions de framework cible sont les dernières versions stables. Les
préversions ne sont pas mentionnées. Un moniker du framework cible est un format de jeton standardisé pour la
spécification du framework cible d’une bibliothèque ou d’une application .NET.

L AT EST M O N IK ER DU F RA M EW O RK IM P L ÉM EN T É
F RA M EW O RK C IB L E VERSIO N STA B L E C IB L E VERSIO N . N ET STA N DA RD

.NET Standard 2.1 netstandard2.1 N/A

.NET Core 3.1 netcoreapp3.1 2.1

.NET Framework 4.8 net48 2

Versions de framework cible prises en charge


Un framework cible est généralement référencé par un TFM. Le tableau suivant présente les frameworks cibles
pris en charge par le SDK .NET Core et le client NuGet. Les équivalents sont indiqués entre crochets. Par exemple,
win81 est un TFM équivalent de netcore451 .
F RA M EW O RK C IB L E T FM

.NET Standard netstandard1.0


netstandard1.1
netstandard1.2
netstandard1.3
netstandard1.4
netstandard1.5
netstandard1.6
netstandard2.0
netstandard2.1

.NET Core netcoreapp1.0


netcoreapp1.1
netcoreapp2.0
netcoreapp2.1
netcoreapp2.2
netcoreapp3.0
netcoreapp3.1

.NET Framework net11


net20
net35
net40
net403
net45
net451
net452
net46
net461
net462
net47
net471
net472
net48

Windows Store netcore [netcore45]


netcore45 [win] [win8]
netcore451 [win81]

.NET Micro Framework netmf

Silverlight sl4
sl5

Windows Phone wp [wp7]


wp7
wp75
wp8
wp81
wpa81

Plateforme Windows universelle uap [uap10.0]


uap10.0 [win10] [netcore50]

Comment spécifier des frameworks cibles


Les frameworks cibles sont spécifiés dans votre fichier projet. Quand vous spécifiez un framework cible unique,
utilisez l’élément TargetFramework . Le fichier de projet d’application de console suivant montre comment cibler
.NET Core 3.0 :

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>

</Project>

Quand vous spécifiez plusieurs frameworks cibles, vous pouvez référencer conditionnellement des assemblys
pour chaque framework cible. Dans votre code, vous pouvez effectuer une compilation conditionnelle par rapport
à ces assemblys en utilisant des symboles de préprocesseur avec la structure logique if-then-else.
Le fichier projet de bibliothèque suivant cible des API de .NET Standard ( netstandard1.4 ) et des API de .NET
Framework ( net40 et net45 ). Utilisez l’élément pluriel TargetFrameworks avec plusieurs frameworks cibles.
Notez la façon dont les attributs Condition incluent les packages spécifiques à l’implémentation quand la
bibliothèque est compilée pour les deux TFM .NET Framework :

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard1.4;net40;net45</TargetFrameworks>
</PropertyGroup>

<!-- Conditionally obtain references for the .NET Framework 4.0 target -->
<ItemGroup Condition=" '$(TargetFramework)' == 'net40' ">
<Reference Include="System.Net" />
</ItemGroup>

<!-- Conditionally obtain references for the .NET Framework 4.5 target -->
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Reference Include="System.Net.Http" />
<Reference Include="System.Threading.Tasks" />
</ItemGroup>

</Project>

Au sein de votre application ou bibliothèque, vous écrivez du code conditionnel à compiler pour chaque
framework cible :

public class MyClass


{
static void Main()
{
#if NET40
Console.WriteLine("Target framework: .NET Framework 4.0");
#elif NET45
Console.WriteLine("Target framework: .NET Framework 4.5");
#else
Console.WriteLine("Target framework: .NET Standard 1.4");
#endif
}
}

Le système de construction est au courant des symboles de préprocesseur représentant les cadres cibles indiqués
dans le tableau des versions-cadres cibles pris en charge lorsque vous utilisez des projets de type SDK. Quand
vous utilisez un symbole représentant un TFM .NET Standard ou .NET Core, remplacez le point par un trait de
soulignement et remplacez les lettres minuscules par des lettres majuscules (par exemple, le symbole pour
netstandard1.4 est NETSTANDARD1_4 ).
La liste complète des symboles de préprocesseur pour les frameworks cibles de .NET Core est la suivante :

VERSIO N S C IB L ES DE . N ET F RA M EW O RK SY M B O L ES

.NET Framework NETFRAMEWORK , NET20 , NET35 , NET40 , NET45 , NET451 ,


NET452 , NET46 , NET461 , NET462 , NET47 , NET471 ,
NET472 , NET48

.NET Standard NETSTANDARD , NETSTANDARD1_0 , NETSTANDARD1_1 ,


NETSTANDARD1_2 , NETSTANDARD1_3 , NETSTANDARD1_4 ,
NETSTANDARD1_5 , NETSTANDARD1_6 , NETSTANDARD2_0 ,
NETSTANDARD2_1

.NET Core NETCOREAPP , NETCOREAPP1_0 , NETCOREAPP1_1 ,


NETCOREAPP2_0 , NETCOREAPP2_1 , NETCOREAPP2_2 ,
NETCOREAPP3_0 , NETCOREAPP3_1

Frameworks cibles dépréciés


Les frameworks cibles suivants sont dépréciés. Les packages ciblant ces frameworks cibles doivent migrer vers les
versions de remplacement indiquées.

T F M DÉP RÉC IÉ REM P L A C EM EN T

aspnet50 netcoreapp
aspnetcore50
dnxcore50
dnx
dnx45
dnx451
dnx452

dotnet netstandard
dotnet50
dotnet51
dotnet52
dotnet53
dotnet54
dotnet55
dotnet56

netcore50 uap10.0

win netcore45

win8 netcore45

win81 netcore451

win10 uap10.0

winrt netcore45
Voir aussi
Packages, métapackages et frameworks
Développer des bibliothèques avec des outils multiplateformes
.NET Standard
Gestion des versions de .NET Core
dotnet/standard GitHub repository (Dépôt GitHub dotnet/standard)
NuGet Tools GitHub Repository (Dépôt GitHub des outils NuGet)
Framework Profiles in .NET (Profils de framework dans .NET)
Glossaire .NET
18/07/2020 • 23 minutes to read • Edit Online

L’objectif principal de ce glossaire est de préciser la signification de certains termes et acronymes qui apparaissent
fréquemment dans la documentation .NET sans y être définis.

AOT
Compilateur Ahead Of Time.
Semblable au compilateur JIT, ce compilateur convertit également le langage IL en code machine. Contrairement à
la compilation JIT, la compilation AOT se produit avant que l’application ne soit exécutée et est généralement
effectuée sur une autre machine. Étant donné que les chaînes d’outils AOA ne sont pas compilées au moment de
l’exécution, elles n’ont pas à réduire le temps passé à compiler. Ainsi, elles peuvent dédier plus de temps à
l’optimisation. Étant donné que le contexte d’AOT est l’ensemble de l’application, le compilateur AOT effectue
également une liaison entre modules et une analyse de la totalité du programme, ce qui signifie que toutes les
références sont suivies et qu’un seul exécutable est produit.
Consultez CoreRT et .NET Native.

ASP.NET
Implémentation ASP.NET d’origine fournie avec .NET Framework.
ASP.NET est parfois un terme général qui désigne les deux implémentations d’ASP.NET, y compris ASP.NET Core.
C’est le contexte qui détermine la signification véhiculée par le terme. Reportez-vous à ASP.NET 4. x pour indiquer
clairement que vous n’utilisez pas ASP.NET pour signifier les deux implémentations.
Voir Documentation d’ASP.NET.

ASP.NET Core
Une implémentation multiplateforme, hautes performances et open source de ASP.NET reposant sur .NET Core.
Voir Documentation ASP.NET Core.

assembly
Fichier . dll / . exe qui peut contenir une collection d’API pouvant être appelées par des applications ou d’autres
assemblys.
Un assembly peut inclure des types comme des interfaces, des classes, des structures, des énumérations et des
délégués. Les assemblys qui se trouvent dans le dossier bin d’un projet sont parfois appelés binaires. Voir aussi
bibliothèque.

CLR
Common Language Runtime.
La signification exacte dépend du contexte, mais le Common Language Runtime fait généralement référence au
runtime de .NET Framework. Le CLR gère l’allocation et la gestion de la mémoire. Le CLR est également un
ordinateur virtuel qui exécute non seulement des applications, mais génère et compile du code à la volée à l’aide
d’un compilateur JIT . L’implémentation CLR Microsoft actuelle est Windows uniquement.

CoreCLR
.NET Core Common Language Runtime.
Ce CLR repose sur la même base de code que le CLR. À l’origine, CoreCLR était le runtime de Silverlight et était
conçu pour s’exécuter sur plusieurs plateformes, notamment Windows et OS X. CoreCLR fait désormais partie de
.NET Core et représente une version simplifiée du CLR. C’est toujours un runtime multiplateforme, qui prend
désormais en charge de nombreuses distributions Linux. CoreCLR est également une machine virtuelle avec des
capacités JIT et d’exécution de code.

CoreFx
Bibliothèque de classes de base .NET Core

TIP
FX signifie Framework.

Ensemble de bibliothèques qui composent le système. * (et, dans une certaine mesure, Microsoft. * ) espaces. La
bibliothèque de classes de base est un framework de niveau inférieur à usage général sur lequel reposent les
frameworks d’applications de niveau supérieur, tels qu’ASP.NET Core. Le code source de la bibliothèque de classes
de base .NET Core se trouve dans le référentiel du Runtime .net Core. Toutefois, la plupart des API .NET Core étant
également disponibles dans .NET Framework, vous pouvez considérer CoreFX comme une duplication (fork) de la
bibliothèque de classes de base .NET Framework.

CoreRT
Runtime .NET Core.
Contrairement à CLR/CoreCLR, CoreRT n’est pas une machine virtuelle, ce qui signifie qu’il n’inclut pas les
fonctionnalités de génération et d’exécution de code à la volée en raison de l’absence d’un compilateur JIT.
Toutefois, elle inclut le catalogue global et la capacité d’identification du type au moment de l’exécution (RTTI) et de
la réflexion. Toutefois, son système de type est conçu pour que les métadonnées de réflexion ne soient pas
nécessaires. Le fait de ne pas exiger de métadonnées permet d’avoir une chaîne d’outils AOA qui peut lier des
métadonnées superflues et, plus important encore, identifier du code que l’application n’utilise pas. CoreRT est en
cours de développement.
Consultez Introduction à .net native et CoreRT.

interplateformes
Possibilité de développer et d’exécuter une application qui peut être utilisée sur plusieurs systèmes d’exploitation
différents, tels que Linux, Windows et iOS, sans avoir à les réécrire spécifiquement pour chacune d’elles. Cela
permet la réutilisation et la cohérence du code entre les applications sur différentes plateformes.

écosystème
Tous les logiciels d’exécution, outils de développement et ressources de communautés qui permettent de générer et
d’exécuter des applications pour une technologie donnée.
Le terme « écosystème .NET » diffère des termes tels que « pile .NET » en ce sens qu’il inclut les bibliothèques et les
applications tierces. Voici un exemple dans une phrase :
« L’objectif de .NET Standard est d’établir une meilleure uniformité dans l’écosystème .NET. »

framework
En général, ensemble complet d’API qui facilite le développement et le déploiement d’applications basées sur une
technologie particulière. Selon ce sens général, ASP.NET Core et Windows Forms sont des exemples de frameworks
d’application. Voir aussi bibliothèque.
Le mot « framework » a une signification technique plus spécifique dans les termes suivants :
.NET Framework
Framework cible
TFM (moniker de la version cible de .Net Framework)
Dans la documentation existante, « framework » fait parfois référence à une implémentation de .NET. Par exemple,
un article peut appeler .NET Core un framework. Nous envisageons d’éliminer de la documentation cet usage qui
prête à confusion.

GC
Garbage collector.
Le garbage collector est une implémentation de la gestion automatique de la mémoire. Le GC libère la mémoire
occupée par les objets qui ne sont plus en cours d’utilisation.
Consultez Nettoyage de la mémoire.

IL
Langage intermédiaire.
Les langages .NET de haut niveau, tels que C#, se compilent en un jeu d’instructions indépendant du matériel, qui
est appelé Langage intermédiaire (IL). IL est parfois appelé MSIL (Microsoft IL) ou CIL (Common IL).

JIT
Compilateur juste-à-temps.
Semblable au compilateur AOT, ce compilateur convertit le langage IL en code machine que le processeur
comprend. Contrairement à la compilation AOT, la compilation JIT se produit à la demande et est effectuée sur la
machine sur laquelle le code doit s’exécuter. Étant donné que la compilation JIT se produit pendant l’exécution de
l’application, la compilation fait partie du temps d’exécution. Ainsi, les compilateurs JIT doivent trouver un équilibre
entre le temps consacré à l’optimisation du code et les économies pouvant découler du code résultant. Toutefois, un
compilateur JIT connaît le matériel réel et peut éviter aux développeurs d’avoir à transmettre différentes
implémentations.

implémentation de .NET
Une implémentation de .NET comprend les éléments suivants :
Un ou plusieurs runtimes. Exemples : CLR, CoreCLR, CoreRT.
Une bibliothèque de classes qui implémente une version de .NET Standard et qui peut inclure des API
supplémentaires. Exemples : bibliothèque de classes de base .NET Framework, bibliothèque de classes de base
.NET Core.
Le cas échéant, un ou plusieurs frameworks d’application. Exemples : ASP.NET, Windows Forms et WPF) sont
inclus dans .NET Framework.
Le cas échéant, des outils de développement. Certains outils de développement sont partagés entre plusieurs
implémentations.
Exemples d’implémentations de .NET :
.NET Framework
.NET Core
Plateforme Windows universelle (UWP)

bibliothèque
Ensemble d’API que peuvent appeler les applications ou d’autres bibliothèques. Une bibliothèque .NET est
composée d’un ou plusieurs assemblys.
Les mots bibliothèque et framework sont souvent utilisés indifféremment.

métapackage
Package NuGet ne disposant pas de sa propre bibliothèque, mais qui est simplement une liste de dépendances. Les
packages inclus peuvent éventuellement établir l’API pour un framework cible.
Voir packages, offres et infrastructures

Mono
Mono est une implémentation de .NET open source multiplateforme qui est principalement utilisée quand un
runtime réduit est requis. C’est le runtime qui alimente les applications Xamarin sur Android, Mac, iOS, tvOS et
Watchos, et se concentre principalement sur les applications qui requièrent un faible encombrement.
Il prend en charge toutes les versions de .NET Standard publiées.
Historiquement, Mono implémentait l’API plus volumineuse de .NET Framework et émulait certaines des
fonctionnalités les plus populaires sur Unix. Il est parfois utilisé pour exécuter des applications .NET qui s’appuient
sur ces fonctionnalités sous Unix.
Mono est généralement utilisé avec un compilateur juste-à-temps, mais il comporte également un compilateur
statique complet (compilation Ahead Of Time) qui est utilisé sur des plateformes comme iOS.
Pour en savoir plus sur Mono, consultez la documentation Mono.

.NET
Terme générique désignant .NET Standard, ainsi que toutes les charges de travail et les implémentations de .NET.
Toujours entièrement en majuscules, jamais « .net ».
Consultez le Guide .net

.NET Core
Une implémentation multiplateforme, hautes performances et open source de .NET. Inclut CoreCLR (Core Common
Language Runtime), CoreRT (Core AOT Runtime, en cours de développement), la bibliothèque de classes de base et
le SDK Core.
Consultez .NET Core.

CLI .NET Core


Chaîne d’outils multiplateforme pour développer des applications .NET Core.
Consultez CLI .net Core.

SDK .NET Core


Ensemble de bibliothèques et d’outils qui permettent aux développeurs de créer des applications et des
bibliothèques .NET Core. Inclut la CLI .NET Core pour la génération d’applications, les bibliothèques .NET Core et le
runtime pour la génération et l’exécution d’applications et l’exécutable dotnet (dotnet.exe) qui exécute les
commandes CLI et les applications.
Consultez Vue d’ensemble du SDK .NET Core.

.NET Framework
Implémentation de .NET qui s’exécute uniquement sur Windows. Inclut le Common Language Runtime (CLR), la
bibliothèque de classes de base et des bibliothèques de framework d’application telles qu’ASP.NET, Windows Forms
et WPF.
Consultez Guide du .NET Framework.

.NET Native
Chaîne d’outils de compilateur qui génère du code natif Ahead Of Time (AOT), par opposition à juste-à-temps (JIT).
La compilation se produit sur la machine du développeur, à l’image du fonctionnement d’un éditeur de liens et d’un
compilateur C++. Elle supprime le code inutilisé et consacre plus de temps à l’optimisation du code. Elle extrait le
code des bibliothèques et le fusionne dans le fichier exécutable. Le résultat est un module unique qui représente
l’application entière.
UWP fut le premier framework d’application pris en charge par .NET Native. De nos jours, nous prenons en charge
la génération d’applications console natives pour Windows, macOS et Linux.
Consultez Présentation de .NET Native et CoreRT.

.NET Standard
Spécification formelle des API .NET disponibles dans chaque implémentation de .NET.
La spécification .NET Standard est parfois appelée bibliothèque dans la documentation. Comme une bibliothèque
inclut des implémentations d’API, outre des spécifications (interfaces), il est trompeur d’appeler .NET Standard une
« bibliothèque ». Nous prévoyons de supprimer cette utilisation de la documentation, sauf en référence au nom du
métapackage .NET Standard ( NETStandard.Library ).
Consultez .NET Standard.

NGEN
Génération (d’images) native
Vous pouvez considérer cette technologie comme un compilateur JIT persistant. Elle compile généralement le code
sur la machine où le code est exécuté, mais la compilation se produit en règle générale au moment de l’installation.

package
Un package NuGet — ou simplement un package — est un fichier .zip qui comporte un ou plusieurs assemblys
portant le même nom, ainsi que des métadonnées supplémentaires, telles que le nom de l’auteur.
Le fichier .zip porte l’extension .nupkg et peut contenir des composants, tels que des fichiers .dll et des fichiers .xml,
à utiliser avec plusieurs frameworks et versions cibles. Quand ils sont installés dans une application ou une
bibliothèque, les composants appropriés sont sélectionnés en fonction du framework cible spécifié par l’application
ou la bibliothèque. Les composants qui définissent l’interface se trouvent dans le dossier ref, tandis que les
ressources qui définissent l’implémentation se trouvent dans le dossier lib.

plateforme
Système d’exploitation et le matériel sur lequel il s’exécute, tel que Windows, macOS, Linux, iOS et Android.
Voici quelques exemples d’utilisation dans des phrases :
« .NET Core est une implémentation multiplateforme de .NET ».
« Les profils de bibliothèque de classes portable représentent les plateformes Microsoft, alors que .NET Standard
est indépendant de la plateforme. »
La documentation .NET utilise fréquemment « plateforme .NET » pour désigner soit une implémentation de .NET,
soit la pile .NET y compris toutes les implémentations. Ces deux utilisations ayant tendance à être confondues avec
la signification principale (système d’exploitation/matériel), nous envisageons de les supprimer de la
documentation.

runtime
Environnement d’exécution d’un programme managé.
Le système d’exploitation fait partie de l’environnement d’exécution, mais pas du runtime .NET. Voici quelques
exemples de runtimes .NET :
CLR (Common Language Runtime)
Core Common Language Runtime (CoreCLR)
.NET Native (pour la plateforme Windows universelle)
Runtime Mono
Parfois, la documentation de .NET utilise « runtime » pour désigner une implémentation de .NET. Par exemple, dans
les phrases suivantes, « runtime » doit être remplacé par « implémentation » :
« Les différents runtimes .NET implémentent des versions spécifiques de .NET Standard. »
« Les bibliothèques destinées à s’exécuter sur plusieurs runtimes doivent cibler ce framework. » (s’applique à
.NET Standard)
« Les différents runtimes .NET implémentent des versions spécifiques de .NET Standard. … Chaque version du
runtime .NET publie la version .NET Standard la plus élevée qu’elle prend en charge... »
Nous envisageons de supprimer cette utilisation incohérente.

pile
Ensemble de technologies de programmation qui sont utilisées conjointement pour générer et exécuter des
applications.
L’expression « la pile .NET » fait référence à .NET Standard et à toutes les implémentations de .NET. L’expression
« une pile .NET » peut faire référence à une implémentation de .NET.

version cible de .NET Framework


Ensemble d’API sur lequel repose une bibliothèque ou une application .NET.
Une application ou une bibliothèque peut cibler une version de .NET Standard (par exemple, .NET Standard 2.0), qui
est la spécification d’un ensemble standard d’API parmi toutes les implémentations de .NET. Une application ou une
bibliothèque peut également cibler une version d’une implémentation spécifique de .NET ; dans ce cas, elle a accès
aux API spécifiques à l’implémentation. Par exemple, une application qui cible Xamarin.iOS accède aux wrappers
d’API iOS fournis par Xamarin.
Pour certains frameworks cibles (par exemple, .NET Framework), les API disponibles sont définies par les assemblys
qu’une implémentation de .NET installe sur un système. Les API peuvent inclure des API de framework d’application
(par exemple, ASP.NET ou WinForms). Pour les frameworks cibles basés sur le package (par exemple, .NET Standard
et .NET Core), les API de framework sont définies par les packages installés dans l’application ou la bibliothèque.
Dans ce cas, le framework cible spécifie implicitement un métapackage qui référence tous les packages constituant
le framework.
Consultez Versions cibles de .NET Framework.

TFM
Moniker de la version cible de .Net Framework.
Format de jeton standardisé pour la spécification du framework cible d’une bibliothèque ou d’une application .NET.
Les frameworks cibles sont en général référencés par un nom court, tel que net462 . Les TFM de forme longue (par
exemple, .NETFramework,Version=4.6.2) existent, mais ne sont généralement pas utilisés pour spécifier un
framework cible.
Consultez Versions cibles de .NET Framework.

UWP
Plateforme Windows universelle.
Implémentation de .NET qui sert à générer des logiciels et des applications Windows tactiles modernes pour
l’Internet des objets (IoT). Il est conçu pour unifier les différents types d’appareils que vous pouvez cibler, y compris
les PC, les tablettes, les téléphones et même la Xbox. UWP fournit de nombreux services, comme un magasin
d’applications centralisé, un environnement d’exécution (AppContainer) et un ensemble d’API Windows à utiliser à
la place de Win32 (WinRT). Les applications peuvent être écrites en C++, C#, Visual Basic et JavaScript. Lorsque
vous utilisez C# et Visual Basic, les API .NET sont fournies par .NET Core.

Voir aussi
Guide .NET
Guide de .NET Framework
.NET Core
Présentation de ASP.NET
Présentation de ASP.NET Core
Conseils sur la bibliothèque open source
18/03/2020 • 2 minutes to read • Edit Online

Cette aide fournit des recommandations à l’attention des développeurs qui créent des bibliothèques .NET de
qualité. Cette documentation se concentre sur le quoi et le pourquoi de la création d’une bibliothèque .NET, mais
pas sur le comment.
Aspects des bibliothèques .NET open source de qualité :
Inclusives - Les bonnes bibliothèques .NET s’efforcent de prendre en charge plusieurs plateformes, langages de
programmation et applications.
Stables - Les bonnes bibliothèques .NET coexistent dans l’écosystème .NET en s’exécutant dans les applications
créées avec de nombreuses bibliothèques.
Conçues pour évoluer - Les bibliothèques .NET doivent s’améliorer et évoluer au fil du temps tout en prenant
en charge les utilisateurs existants.
Débogables - Les bibliothèques .NET doivent utiliser les derniers outils afin de créer une excellente expérience
de débogage pour les utilisateurs.
Approuvées - Les bibliothèques .NET ont la confiance des développeurs en publiant sur NuGet à l’aide des
bonnes pratiques de sécurité.
Découvrir

Types de suggestions
Chaque article présente quatre types de suggestions : À faire , Envisager , Éviter et À ne pas faire . Le type de
suggestion indique si celle-ci doit être suivie ou pas.
Vous devez presque toujours suivre une suggestion À faire . Par exemple :
️ À FAIRE : Distribuer votre bibliothèque à l’aide d’un package NuGet.

En revanche, les recommandations Envisager doivent généralement être appliquées, mais il existe des exceptions
à la règle qui sont fondées, c’est pourquoi vous ne devez pas vous inquiéter si vous ne les suivez pas :
️ ENVISAGER d’utiliser SemVer 2.0.0 pour versionner votre package NuGet.

Les suggestions Éviter indiquent quelque chose qui n’est généralement pas une bonne idée, mais enfreindre les
règles peut parfois avoir du sens :
❌AVOID NuGet références paquet qui exigent une version exacte.
Et enfin, les suggestions À ne pas faire désignent quelque chose que vous ne devez presque jamais faire :
❌NE publiez PAS de versions de votre bibliothèque, nommées en force et non. Par exemple, Contoso.Api et
Contoso.Api.StrongNamed .

S U IVA N T
.NET Core et .NET Framework pour les applications
serveur
30/04/2020 • 13 minutes to read • Edit Online

Il existe deux implémentations .NET prises en charge pour la création d’applications côté serveur : .NET Framework
et .NET Core. Toutes deux partagent de nombreux composants et vous permettent de partager du code entre les
deux. Toutefois, il existe des différences fondamentales entre les deux et votre choix dépend de ce que vous
souhaitez accomplir. Cet article fournit des conseils sur l’utilisation de chacune.
Utilisez .NET Core pour votre application serveur quand :
vous avez des besoins multiplateformes ;
Vous ciblez des microservices.
Vous utilisez des conteneurs d’ancrage.
Vous avez besoin de systèmes scalables et hautes performances.
Vous avez besoin de versions .NET côte à côte par application.
Utilisez .NET Framework pour votre application serveur quand :
Votre application utilise le .NET Framework (nous vous recommandons de privilégier l’extension à la migration).
Votre application utilise des packages NuGet ou des bibliothèques .NET tiers non disponibles pour .NET Core.
Votre application utilise des technologies .NET non disponibles pour .NET Core.
Votre application utilise une plateforme qui ne prend pas en charge .NET Core. Windows, macOS et Linux
prennent en charge .NET Core.

Quand choisir .NET Core


Les sections suivantes donnent une explication plus détaillée des raisons indiquées précédemment justifiant le
choix de .NET Core.
Besoins multiplateformes
Si votre application (web/service) doit s’exécuter sur plusieurs plateformes (Windows, Linux et macOS), utilisez
.NET Core.
.NET Core prend en charge les systèmes d’exploitation précédemment mentionnés comme station de travail de
développement. Visual Studio fournit un environnement de développement intégré (IDE) pour Windows et macOS.
Vous pouvez également utiliser Visual Studio Code, qui s’exécute sur macOS, Linux et Windows. Visual Studio Code
prend en charge .NET Core, notamment IntelliSense et le débogage. La plupart des éditeurs tiers, tels que Sublime,
Emacs et VI, fonctionnent avec .NET Core. Ces éditeurs tiers obtiennent l’éditeur IntelliSense en utilisant
Omnisharp. Vous pouvez également éviter tout éditeur de code et utiliser directement le CLI .net Core, disponible
pour toutes les plateformes prises en charge.
Architecture en microservices
Une architecture en microservices permet une combinaison de technologies au-delà des limites d’un service. Cette
combinaison de technologies favorise l’adoption progressive de .NET Core pour les nouveaux microservices qui
utilisent d’autres microservices ou services. Par exemple, vous pouvez combiner des microservices ou services
développés avec .NET Framework, Java, Ruby ou d’autres technologies monolithiques.
Il existe de nombreuses plateformes d’infrastructure. Azure Service Fabric est conçu pour les systèmes de
microservice volumineux et complexes. Azure App Service est un bon choix pour les microservices sans état. Les
alternatives aux microservices basées sur Docker s’intègrent à tout type d’approche des microservices, comme
expliqué dans la section Conteneurs. Toutes ces plateformes prennent en charge .NET Core et s’avèrent idéales
pour l’hébergement de vos microservices.
Pour plus d’informations sur l’architecture de microservices, consultez microservices .net. Architecture pour les
applications .NET en conteneur.
Containers
Les conteneurs sont couramment utilisés conjointement avec une architecture en microservices. Les conteneurs
peuvent également servir à mettre en conteneur des applications ou services web qui suivent un modèle
d’architecture. Le .NET Framework peut être utilisé pour les conteneurs Windows, mais par sa modularité et sa
légèreté, .NET Core est un meilleur choix pour les conteneurs. Quand vous créez et déployez un conteneur, la taille
de son image est beaucoup plus petite avec .NET Core qu’avec le .NET Framework. Grâce à sa nature
multiplateforme, vous pouvez déployer des applications serveur sur des conteneurs Docker Linux, par exemple.
Vous pouvez héberger les conteneurs Docker dans votre propre infrastructure Windows ou Linux, ou dans un
service cloud comme Azure Kubernetes Service. Azure Kubernetes Service permet de gérer, d’orchestrer et de
mettre à l’échelle des applications sur conteneur dans le cloud.
Systèmes hautes performances et évolutifs
Quand votre système a besoin de performances et d’une scalabilité optimales, .NET Core et ASP.NET Core sont vos
meilleures options. Un runtime serveur hautes performances pour Windows Server et Linux fait de .NET un
framework web particulièrement attractif d’après les bancs d’essai TechEmpower.
Niveau de performance et scalabilité sont particulièrement pertinents pour les architectures en microservices, où
des centaines de microservices peuvent être en cours d’exécution. Avec ASP.NET Core, les systèmes sont exécutés
avec un nombre bien inférieur de serveurs/machines virtuelles. Cette réduction engendre une baisse des coûts
d’infrastructure et d’hébergement.
Versions .NET côte à côte par niveau d’application
Pour installer des applications avec des dépendances sur différentes versions de .NET, nous vous recommandons
.NET Core. .NET Core prend en charge l’installation côte à côte de différentes versions du Runtime .NET Core sur le
même ordinateur. Ainsi, plusieurs services peuvent cohabiter sur le même serveur, chacun d’eux sur sa propre
version de .NET Core. De plus, les risques et les coûts liés aux opérations informatiques et aux mises à niveau des
applications s’en trouvent réduits.
L’installation côte à côte n’est pas possible avec .NET Framework. Il s’agit d’un composant Windows, et une seule
version peut exister sur un ordinateur à la fois. Chaque version de .NET Framework remplace la version
précédente. Si vous installez une nouvelle application qui cible une version ultérieure de .NET Framework, vous
pouvez interrompre les applications existantes qui s’exécutent sur l’ordinateur, car la version précédente a été
remplacée.

Quand choisir .NET Framework


.NET Core offre des avantages significatifs pour les nouvelles applications et les nouveaux modèles d’application.
Toutefois, .NET Framework reste le choix naturel pour de nombreux scénarios existants et, par conséquent, .NET
Framework n’est pas remplacé par .NET Core pour toutes les applications serveur.
Applications .NET Framework actuelles
Dans la plupart des cas, vous ne devez pas migrer vos applications existantes vers .NET Core. Il est plutôt
recommandé d’utiliser .NET Core quand vous étendez une application existante, telle que l’écriture d’un service
web dans ASP.NET Core.
ASP.NET Core sur .NET Framework
Pour plus d’informations sur la prise en charge de ASP.NET Core sur .NET Framework, consultez stratégie de
support .net Core.
Des bibliothèques tierces ou des packages NuGet ne sont pas disponibles pour .NET Core
Les bibliothèques adoptent rapidement .NET Standard. .NET Standard permet de partager du code dans toutes les
implémentations .NET, y compris .NET Core. Avec .NET 2.0 Standard, cela est encore plus simple :
La surface de l’API est désormais beaucoup plus grande.
Il a introduit un mode de compatibilité .NET Framework.
Ce mode de compatibilité permet aux projets .NET Standard et .NET Core de référencer des bibliothèques
.NET Framework. Pour en savoir plus sur le mode de compatibilité, consultez Annonce de .NET 2.0 Standard.
Vous devez utiliser .NET Framework uniquement dans les cas où les bibliothèques ou les packages NuGet utilisent
des technologies qui ne sont pas disponibles dans .NET Standard ou .NET Core.
Technologies .NET non disponibles pour .NET Core
Certaines technologies du .NET Framework ne sont pas disponibles dans .NET Core. Il se peut que certaines d’entre
elles soient disponibles dans les prochaines versions release de .NET Core. D’autres, qui ne s’appliquent pas aux
nouveaux modèles d’application ciblés par .NET Core, risquent de ne jamais être disponibles. La liste suivante
présente les technologies les plus courantes absentes dans .NET Core :
ASP.NET Web Forms applications : ASP.NET Web Forms sont disponibles uniquement dans .NET Framework.
ASP.NET Core ne peut pas être utilisé pour Web Forms ASP.NET. Il n’est pas prévu d’intégrer Web Forms
ASP.NET à .NET Core.
Applications Pages Web ASP.NET : le framework Pages Web ASP.NET n’est pas inclus dans ASP.NET Core.
Implémentation des services WCF. Même s’il existe une bibliothèque cliente WCF pour utiliser des services
WCF à partir de .net Core, l’implémentation de serveur WCF n’est actuellement disponible que dans .NET
Framework. Ce scénario ne fait pas partie du plan actuel pour .NET Core, mais il est envisagé à l’avenir.
Services liés aux flux de travail : Windows Workflow Foundation (WF), les services de workflow (WCF + WF
dans un seul service) et les WCF Data Services (anciennement « ADO.NET Data Services ») sont disponibles
uniquement dans .NET Framework. Il n’est pas prévu d’intégrer ces technologies à .NET Core.
Prise en charge des langages : Visual Basic et F# sont pris en charge dans .NET Core, mais pas pour tous les
types de projet. Pour obtenir la liste des modèles de projet pris en charge, consultez Options de modèle
pour dotnet new.
La plateforme ne prend pas en charge .NET Core
Certaines plateformes Microsoft ou tierces ne prennent pas en charge .NET Core. Certains services Azure
fournissent un kit SDK qui n’est pas encore utilisable sur .NET Core. Il s’agit d’une circonstance transitoire, car tous
les services Azure utilisent .NET Core. En attendant, vous pouvez utiliser l’API REST équivalente au lieu du kit de
développement logiciel (SDK) client.

Voir aussi
Choisir entre ASP.NET et ASP.NET Core
ASP.NET Core ciblant .NET Framework
Versions cibles de .NET Framework
Guide .NET Core
Portage vers .NET Core à partir du .NET Framework
Introduction à .NET et à Docker
Vue d’ensemble des composants .NET
Microservices .NET. Architecture pour les applications .NET en conteneur
Qu’est-ce que le code managé ?
18/07/2020 • 5 minutes to read • Edit Online

Quand vous utilisez .NET Framework, vous rencontrez souvent le terme « code managé ». Ce document explique ce
que signifie ce terme et fournit des informations supplémentaires sur le sujet.
Pour faire simple, le code managé est du code dont l’exécution est gérée par un runtime. Dans ce cas, le runtime en
question est appelé le Common Language Runtime ou CLR, indépendamment de l’implémentation (Mono ou
.NET Framework, ou .NET Core). CLR est chargé de prendre le code managé, de le compiler en code machine, puis
de l’exécuter. Par ailleurs, le runtime fournit plusieurs services importants comme la gestion automatique de la
mémoire, les limites de sécurité, la cohérence des types, etc.
Par opposition, le « code non managé » est la façon dont vous pouvez exécuter un programme C/C++. Dans un
environnement non managé, le programmeur est responsable de presque tout. Le programme réel est,
essentiellement, un fichier binaire que le système d’exploitation charge en mémoire et démarre. Tout le reste, depuis
la gestion de la mémoire aux considérations de sécurité, est à la charge du programmeur.
Le code managé est écrit dans un des langages de haut niveau qui peuvent être exécutés sur .NET, tels que C#,
Visual Basic ou F#. Quand vous compilez le code écrit dans ces langages avec leur compilateur respectif, vous
n’obtenez pas de code machine. Vous obtenez un code en langage intermédiaire que le runtime compile ensuite
et exécute. C++ est la seule exception à cette règle, car il peut également produire des fichiers binaires natifs, non
managés qui s’exécutent sur Windows.

Langage intermédiaire et exécution


Qu’est-ce que le « langage intermédiaire » (ou IL en abrégé) ? Il s’agit d’un produit de compilation de code écrit
dans des langages .NET de haut niveau. Quand vous compilez votre code écrit dans un de ces langages, vous
obtenez un fichier binaire constitué de langage intermédiaire. Notez que le langage intermédiaire est indépendant
de tout langage spécifique qui s’exécute sur le runtime. Il existe même une spécification distincte pour ce langage,
que vous pouvez lire si vous le souhaitez.
Une fois que vous avez produit du langage intermédiaire à partir de votre code de haut niveau, vous pouvez
l’exécuter. C’est là que le CLR intervient et démarre le processus de compilation juste-à-temps , ou JIT-ing de
votre code à partir du langage intermédiaire en code machine qui peut être exécuté sur un processeur. De cette
façon, le CLR sait exactement ce que votre code est en train de faire et peut donc le gérer efficacement.
Le langage intermédiaire est parfois appelé langage CIL (Common Intermediate Language) ou langage MSIL
(Microsoft Intermediate Language).

Interopérabilité du code non managé


Bien entendu, le CLR permet de franchir les limites entre les environnements managé et non managé, et il y a
beaucoup de code qui le fait, même dans les bibliothèques de classes de base. Cela s’appelle l’interopérabilité ou
simplement interop en abrégé. Ces dispositions vous permettent, par exemple, d’encapsuler une bibliothèque non
managée et d’y effectuer des appels. Toutefois, notez qu’une fois que vous l’avez fait, quand le code franchit les
limites du runtime, la gestion réelle de l’exécution est à nouveau entre les mains du code non managé et subit donc
les mêmes restrictions.
De la même manière, C# est un langage qui vous permet d’utiliser des constructions non managées comme les
pointeurs directement dans le code en utilisant ce que l’on appelle le contexte unsafe , qui désigne un élément de
code dont l’exécution n’est pas gérée par le CLR.
Ressources complémentaires
Vue d’ensemble de l' .NET Framework
Pointeurs et code unsafe
Interopérabilité native
Automatic Memory Management
18/07/2020 • 15 minutes to read • Edit Online

La gestion automatique de la mémoire est l’un des services que le Common Language Runtime fournit au cours de
l’exécution managée. Le Garbage collector du Common Language Runtime gère l’allocation et la libération de
mémoire pour une application. Les développeurs n’ont donc plus à écrire du code pour exécuter leurs tâches de
gestion de mémoire lors du développement d’applications managées. La gestion automatique de la mémoire
permet d'éliminer des problèmes fréquents tels que l'oubli de libération d'un objet ou les fuites de mémoire ou
encore les tentatives d'accès à la mémoire à la recherche d'un objet qui a déjà été libéré. Cette section décrit la
façon dont le « garbage collector » alloue et libère la mémoire.

Allocation de mémoire
Lorsque vous initialisez un nouveau processus, le runtime réserve une région d'espace d'adressage contigu pour le
processus. Cet espace d'adressage est appelé le tas managé. Le tas managé garde un pointeur vers l'adresse qui
sera allouée au nouvel objet du tas. À l’origine, ce pointeur indique l’adresse de base du tas managé. Tous les types
référence sont alloués sur le tas managé. Lorsqu’une application crée le premier type référence, la mémoire est
allouée pour le type à l’adresse de base du tas managé. Lorsque l'application crée l'objet suivant, le « garbage
collector » lui alloue de la mémoire dans l'espace d'adressage qui suit immédiatement le premier objet. Aussi
longtemps que de l'espace d'adressage est disponible, le « garbage collector » continue à allouer de l'espace pour
de nouveaux objets selon la même procédure.
L'allocation de mémoire à partir du tas managé est plus rapide que l'allocation de mémoire non managée. Étant
donné que le runtime alloue de la mémoire pour un objet en ajoutant une valeur à un pointeur, elle est
pratiquement aussi rapide que l'allocation de mémoire à partir de la pile. En outre, puisque les nouveaux objets
auxquels un espace mémoire est alloué sont stockés à la suite dans le tas managé, une application peut accéder
très rapidement aux objets.

Libération de la mémoire
Le moteur d'optimisation du « garbage collector » détermine le meilleur moment pour lancer une opération
garbage collection sur base des allocations de mémoire effectuées. Lorsque le « garbage collector » effectue une
opération garbage collection, il libère la mémoire pour les objets qui ne sont plus utilisées par l'application. Il
détermine les objets qui ne sont plus utilisés en examinant les racines de l'application. Chaque application possède
un jeu de racines. Chaque racine fait référence à un objet du tas managé ou, à défaut, a la valeur Null. Les racines
de l’application comprennent des champs statiques, des variables et des paramètres locaux sur la pile d’un thread
et des Registres du processeur. Le Garbage collector a accès à la liste des racines actives entretenues par le
compilateur juste-à-temps (JIT) et le runtime. En utilisant cette liste, il examine des racines d'application et crée, au
cours du processus, un graphique qui contient tous les objets accessibles à partir des racines.
Les objets non compris dans le graphique ne sont pas accessibles à partir des racines de l'application. Le « garbage
collector » examine les objets inaccessibles et libère la mémoire qui leur a été allouée. Au cours d'une opération
garbage collection, le « garbage collector » examine le tas managé pour y détecter les blocs de mémoire occupés
par des objets inaccessibles. Chaque fois qu'il détecte un objet inaccessible, il utilise une fonction de copie de
mémoire pour compacter les objets accessibles en mémoire et libérer les blocs d'espaces d'adressage alloués aux
objets inaccessibles. Lorsque la mémoire allouée aux objets accessibles a été réduite, le « garbage collector »
procède aux corrections de pointeurs nécessaires pour que les racines des applications pointent vers les nouveaux
emplacements des objets. Il positionne aussi le pointeur du tas managé après le dernier objet accessible. Vous
remarquerez que la mémoire est compactée uniquement si une collection détecte un nombre significatif d'objets
inaccessibles. Si tous les objets du tas managé survivent à une collection, il n'est pas nécessaire de procéder à un
compactage de la mémoire.
Pour améliorer les performances, le runtime alloue de la mémoire pour les objets de grandes dimensions dans un
tas séparé. Le « garbage collector » libère automatiquement la mémoire des objets de grande dimension.
Cependant, pour éviter de déplacer de grands objets en mémoire, la mémoire n'est pas compactée.

Générations et performances
Pour optimiser les performances du garbage collector, le tas managé est divisé en trois générations : 0, 1 et 2.
L'algorithme de garbage collection du runtime est basé sur plusieurs généralisations que l'industrie informatique a
observées en expérimentant des modèles de garbage collection. En premier lieu, il est plus rapide de compacter la
mémoire pour une partie du tas managé que pour le tas tout entier. Deuxièmement, les nouveaux objets ont des
durées de vie plus courtes que les objets plus anciens. Enfin, les nouveaux objets sont fréquemment liés entre eux
et l'application y accède à peu près au même moment.
Le garbage collector du runtime stocke les nouveaux objets dans la génération 0. Les objets qui sont créés à un
stade précoce de la durée de vie de l'application et qui survivent aux collectes sont promus et stockés dans les
générations 1 et 2. Le processus de promotion d'objet est décrit ultérieurement dans cette rubrique. Parce qu'il est
plus rapide de compacter une partie du tas managé que le tas tout entier, ce schéma permet au « garbage
collector » de libérer la mémoire dans une génération spécifique plutôt que de libérer la mémoire de l'intégralité
du tas managé chaque fois qu'une opération garbage collection est effectuée.
En fait, le « garbage collector » procède à une opération collection lorsque la génération 0 est complète. Si une
application tente de créer un nouvel objet lorsque la génération 0 est complète, le « garbage collector » observe
qu'il n'y a plus d'espace d'adressage disponible dans la génération 0 à allouer à l'objet. Le « garbage collector »
effectue une opération garbage collection en essayant de libérer de l'espace d'adressage pour l'objet dans la
génération 0. Le « garbage collector » commence par examiner les objets de la génération 0 plutôt que tous les
objets du tas managé. Cette approche est la plus efficace parce que les nouveaux objets ont en règle générale une
durée de vie courte et qu'une grande part des objets de la génération 0 ne seront plus utilisés par l'application
lorsqu'une opération garbage collection sera effectuée. En outre, une opération garbage collection de la génération
0 récupère souvent à elle seule suffisamment de mémoire pour permettre à l’application de continuer à créer de
nouveaux objets.
Après avoir effectué une collection de génération 0, le Garbage collector compacte la mémoire pour les objets
accessibles comme expliqué dans la section Libération de la mémoire, plus haut dans cette rubrique. Le garbage
collector promeut ensuite ces objets et considère que cette partie du tas managé appartient à la génération 1. Étant
donné que les objets qui survivent aux collectes ont tendance à avoir de plus longues durées de vie, il est logique
de les promouvoir à une génération supérieure. En conséquence, le « garbage collector »n'a pas à réexaminer les
objets des générations 1 et 2 chaque fois qu'il effectue une opération garbage collection de la génération 0.
Après avoir exécuté sa première opération garbage collection de la génération 0 et promu les objets accessibles à
la génération 1, le garbage collector considère que le reste du tas managé appartient à la génération 0. Il continue à
allouer de la mémoire pour les nouveaux objets de la génération 0 jusqu'à ce qu'elle soit complète et qu'il soit
nécessaire d'effectuer une autre collecte. À ce stade, le moteur d'optimisation du « garbage collector » détermine
s'il est nécessaire d'examiner les objets des générations plus anciennes. Par exemple, si une collecte de
génération 0 ne libère pas assez de mémoire pour que l'application puisse terminer sa création d'objet, le garbage
collector peut exécuter une collecte de génération 1, puis de génération 2. Si cela ne libère pas assez de mémoire, le
garbage collector peut exécuter une collecte de génération 2, 1 et 0. Après chaque collecte, le garbage collector
condense les objets accessibles dans la génération 0 et les promeut à la génération 1. Les objets qui survivent aux
collectes sont promus et stockés dans les générations 1 et 2. Comme le garbage collector ne prend en charge que
trois générations, les objets de génération 2 qui survivent à une collecte restent dans la génération 2 jusqu'à ce
qu'ils soient considérés comme impossibles à atteindre lors d'une prochaine collecte.

Libération de mémoire pour des ressources non managées


Pour la majorité des objets créés par votre application, vous pouvez laisser au « garbage collector » le soin de
réaliser automatiquement les tâches de gestion de mémoire requises. Cependant, les ressources non managées
requièrent un nettoyage explicite. Le type le plus répandu de ressource non managée est un objet qui enveloppe
une ressource de système d'exploitation telle qu'un handle de fichier ou de fenêtre ou une connexion réseau. Bien
que le « garbage collector » soit en mesure de suivre la durée de vie d'un objet managé qui encapsule une
ressource non managée, il ne possède pas de connaissances spécifiques sur la façon de nettoyer une ressource.
Lors de la création d'un objet qui encapsule une ressource non managée, il est recommandé de fournir le code
nécessaire pour nettoyer la ressource non managée dans une méthode Dispose publique. En fournissant une
méthode Dispose , vous donnez la possibilité aux utilisateurs de votre objet d'en libérer explicitement la mémoire
lorsqu'ils ont fini de s'en servir. Lorsque vous utilisez un objet qui encapsule une ressource non managée, vous
devez garder à l'esprit la méthode Dispose et l'appeler si nécessaire. Pour obtenir plus d’informations sur le
nettoyage de ressources non managées et un exemple de modèle de conception pour l’implémentation de la
méthode Dispose , consultez Garbage collection.

Voir aussi
GC
Garbage collection
Processus d’exécution managée
Vue d’ensemble du Common Language Runtime
(CLR)
18/07/2020 • 9 minutes to read • Edit Online

Le .NET Framework fournit un environnement d'exécution, appelé le Common Language Runtime, qui exécute le
code et offre des services qui simplifient le processus de développement.
Les compilateurs et les outils exposent le fonctionnement du Common Language Runtime et vous permettent
d'écrire du code qui bénéficie de cet environnement d'exécution managée. Le code que vous développez à l'aide
d'un compilateur de langage ciblant le runtime est appelé code managé ; il tire parti de fonctionnalités telles que
l'intégration interlangage, la gestion interlangage des exceptions, la sécurité améliorée, la prise en charge du
versioning et du déploiement, un modèle simplifié de l'interaction des composants, ainsi que des services de
débogage et de gestion de profils.

NOTE
Les compilateurs et les outils peuvent générer une sortie que le Common Language Runtime peut consommer parce que le
système de type, le format des métadonnées et l'environnement d'exécution (système d'exécution virtuel) sont tous définis
par une norme publique, la spécification CLI (Common Language Infrastructure) ECMA. Pour plus d’informations, consultez
ECMA C# and Common Language Infrastructure Specifications (Spécifications CLI et ECMA C#).

Pour permettre au runtime de fournir des services au code managé, les compilateurs de langage doivent générer
des métadonnées qui décrivent les types, les membres et les références de votre code. Les métadonnées sont
stockées avec le code ; chaque fichier exécutable portable (PE) chargeable du Common Language Runtime contient
des métadonnées. Le runtime utilise les métadonnées pour rechercher et charger des classes, placer des instances
en mémoire, résoudre des appels de méthode, générer un code natif, appliquer la sécurité et définir les limites du
contexte d'exécution.
Le runtime gère automatiquement la disposition des objets et manage les références aux objets, les libérant quand
ils ne sont plus utilisés. Les objets dont la durée de vie est managée de cette façon sont appelés données managées.
Le garbage collection élimine les fuites de mémoire ainsi que d'autres erreurs de programmation courantes. Si
votre code est managé, vous pouvez utiliser des données managées, des données non managées, ou à la fois des
données managées et non managées dans votre application .NET Framework. Dans la mesure où les compilateurs
de langage fournissent leurs propres types, tels que des types primitifs, vous ne pouvez pas toujours savoir (ou
avoir besoin de savoir) si vos données sont managées.
Le Common Language Runtime facilite la conception de composants et d'applications dont les objets interagissent
entre les langages. Les objets écrits dans des langages différents peuvent communiquer les uns avec les autres, et
leurs comportements peuvent être fortement intégrés. Par exemple, vous pouvez définir une classe puis utiliser un
langage différent pour dériver une classe de votre classe d'origine ou appeler une méthode pour la classe d'origine.
Vous pouvez également passer une instance d'une classe à une méthode d'une classe écrite dans un autre langage.
Cette intégration interlangage n'est possible que parce que les outils et les compilateurs de langage ciblant le
runtime utilisent un système de type commun défini par le runtime, et qu'ils suivent les règles établies par le
runtime en matière de définition de nouveaux types, ainsi que de création, d'utilisation, de persistance de types et
de liaison avec ceux-ci.
Au sein de leurs métadonnées, tous les composants managés comportent des informations relatives aux
composants et aux ressources pour lesquels ils ont été générés. Le runtime exploite ces informations pour s'assurer
que votre composant ou application possède les versions spécifiées de tous les éléments dont il a besoin, ce qui
rend votre code moins enclin aux arrêts provoqués par une dépendance sans correspondance. Les informations
d'inscription et les données d'état ne sont plus stockées dans le Registre où il peut être difficile de les implanter et
de les gérer. Au lieu de cela, les informations relatives aux types que vous définissez (et leurs dépendances) sont
stockées avec le code en tant que métadonnées, rendant les tâches de réplication et de suppression de composants
beaucoup moins compliquées.
Les outils et les compilateurs de langage exposent le fonctionnement du runtime selon des modes qui se veulent
utiles et intuitifs pour les développeurs. Cela signifie que certaines fonctionnalités du runtime peuvent se faire
remarquer davantage dans un environnement que dans un autre. Les fonctionnalités du runtime dépend des
compilateurs de langage ou des outils utilisés. Par exemple, si vous êtes un développeur Visual Basic, vous pouvez
remarquer qu'avec le Common Language Runtime, le langage Visual Basic dispose davantage de fonctionnalités
orientées objet qu'avant. Le runtime fournit les avantages suivants :
Amélioration des performances.
Possibilité d'utiliser facilement des composants développés dans d'autres langages.
Types extensibles fournis par une bibliothèque de classes.
Fonctionnalités de langage telles que l'héritage, les interfaces et la surcharge pour la programmation
orientée objet.
Prise en charge du modèle de thread libre explicite qui permet la création d'applications évolutives
multithread.
Prise en charge de la gestion structurée des exceptions.
Prise en charge des attributs personnalisés.
Garbage collection.
Utilisation des délégués plutôt que des pointeurs fonction pour une sécurité de type et une sécurité accrues.
Pour plus d'informations sur les délégués, consultez Système de type commun.

Versions CLR
Le numéro de version de .NET Framework ne correspond pas nécessairement au numéro de version du CLR qu’il
contient. Pour obtenir la liste des versions de .NET Framework et leurs versions CLR correspondantes, consultez
versions et dépendances de .NET Framework. Les versions de .NET Core ont une seule version de produit,
autrement dit, il n’existe pas de version CLR distincte. Pour obtenir la liste des versions de .NET Core, consultez
Télécharger .net Core.

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Processus d’exécution managée Décrit les étapes nécessaires pour tirer parti du Common
Language Runtime.

Gestion automatique de la mémoire Explique comment le « garbage collector » alloue et libère la


mémoire.

Vue d’ensemble de l' .NET Framework Décrit les concepts fondamentaux du .NET Framework, tels
que le système de type commun (CTS, Common Type System),
l'interopérabilité interlangage, l'exécution managée, les
domaines d'application et les assemblys.
IN T IT UL É DESC RIP T IO N

Système de type commun Décrit la manière dont les types sont déclarés, utilisés et
managés dans le runtime à l'appui de l'intégration
interlangage.
Indépendance du langage et composants
indépendants du langage
18/07/2020 • 106 minutes to read • Edit Online

.NET est indépendant du langage. Cela signifie qu’en tant que développeur, vous pouvez développer dans un des
nombreux langages qui ciblent des implémentations de .NET, comme C#, F# et Visual Basic. Vous pouvez accéder
aux types et aux membres des bibliothèques de classes développées pour des implémentations de .NET sans avoir
à connaître le langage dans lequel ils ont été initialement écrits ni à suivre les conventions du langage d’origine. Si
vous êtes un développeur de composants, votre composant est accessible par n’importe quelle application .NET,
quelle que soit sa langue.

NOTE
Cette première partie de cet article traite de la création de composants indépendants du langage, c’est-à-dire de composants
qui peuvent être utilisés par des applications écrites dans n’importe quel langage. Vous pouvez également créer un
composant ou une application unique à partir de code source écrit dans plusieurs langages. Consultez Interopérabilité
multilingue dans la deuxième partie de cet article.

Pour interagir entièrement avec d’autres objets écrits dans un langage quelconque, les objets ne doivent exposer
aux appelants que les fonctionnalités communes à tous les langages. Cet ensemble commun de fonctionnalités est
défini par la spécification CLS (Common Language Specification), qui est un ensemble de règles qui s’appliquent
aux assemblys générés. La spécification CLS (Common Language Specification) est définie dans la Partition I,
clauses 7 à 11 du document ECMA-335 Standard: Common Language Infrastructure.
Si votre composant est conforme à la spécification CLS, il est garanti d'être conforme CLS et accessible à partir du
code dans les assemblys écrits dans n'importe quel langage de programmation qui prend en charge la spécification
CLS. Vous pouvez déterminer si votre composant est conforme à la spécification CLS (Common Language
Specification) au moment de la compilation en appliquant l’attribut CSLCompliantAttribute à votre code source.
Pour plus d’informations, consultez Attribut CLSCompliantAttribute.

Règles de conformité CLS


Cette section présente les règles de création d'un composant conforme à CLS. Pour obtenir une liste complète des
règles, consultez la Partition I, clause 11 du document ECMA-335 Standard: Common Language Infrastructure.

NOTE
La spécification CLS (Common Language Specification) présente chaque règle de conformité CLS telle qu'elle s'applique aux
consommateurs (les développeurs qui accèdent par programme à un composant conforme CLS), aux infrastructures (les
développeurs qui utilisent un compilateur de langage pour créer des bibliothèques conformes CLS) et aux extendeurs (les
développeurs qui créent un outil tel qu'un compilateur de langage ou un analyseur de code qui crée des composants
conformes CLS). Cet article se concentre sur les règles applicables aux infrastructures. Notez, toutefois, que certaines des
règles qui s’appliquent aux extendeurs peuvent également s’appliquer aux assemblys créés à l’aide de Reflection. Emit.

Pour concevoir un composant indépendant du langage, vous n'avez qu'à appliquer les règles de conformité CLS à
l'interface publique de votre composant. Votre implémentation privée n'a pas besoin d'être conforme à la
spécification.
IMPORTANT
Les règles de conformité CLS s'appliquent uniquement à l'interface publique d'un composant, pas à son implémentation
privée.

Par exemple, les entiers non signés autres que Byte ne sont pas conformes CLS. Étant donné que la classe Person
de l’exemple suivant expose une propriété Age de type UInt16, le code suivant affiche un avertissement du
compilateur.

using System;

[assembly: CLSCompliant(true)]

public class Person


{
private UInt16 personAge = 0;

public UInt16 Age


{ get { return personAge; } }
}
// The attempt to compile the example displays the following compiler warning:
// Public1.cs(10,18): warning CS3003: Type of 'Person.Age' is not CLS-compliant

<Assembly: CLSCompliant(True)>

Public Class Person


Private personAge As UInt16

Public ReadOnly Property Age As UInt16


Get
Return personAge
End Get
End Property
End Class
' The attempt to compile the example displays the following compiler warning:
' Public1.vb(9) : warning BC40027: Return type of function 'Age' is not CLS-compliant.
'
' Public ReadOnly Property Age As UInt16
' ~~~

Vous pouvez rendre la classe Person conforme CLS en remplaçant le type UInt16 de la propriété Age par Int16,
qui est un entier signé 16 bits conforme CLS. Il n'est pas nécessaire de modifier le type du champ privé personAge .

using System;

[assembly: CLSCompliant(true)]

public class Person


{
private Int16 personAge = 0;

public Int16 Age


{ get { return personAge; } }
}
<Assembly: CLSCompliant(True)>

Public Class Person


Private personAge As UInt16

Public ReadOnly Property Age As Int16


Get
Return CType(personAge, Int16)
End Get
End Property
End Class

L'interface publique d'une bibliothèque inclut les éléments suivants :


Définitions des classes publiques.
Définitions des membres publics des classes publiques et définitions des membres accessibles aux classes
dérivées (à savoir, membres protégés).
Paramètres et types de retour des méthodes publiques des classes publiques, et paramètres et types de
retour des méthodes accessibles aux classes dérivées.
Les règles de conformité CLS sont répertoriées dans le tableau suivant. Le texte des règles est repris mot pour mot
du document ECMA-335 Standard: Common Language Infrastructure, qui est protégé par copyright 2012 par Ecma
International. Vous trouverez des informations plus détaillées sur ces règles dans les sections suivantes.

C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Accessibilité Accessibilité des membres L'accessibilité ne devra pas 10


être changée lors du
remplacement de méthodes
héritées, sauf en cas de
remplacement d'une
méthode héritée d'un
assembly différent avec
accessibilité
family-or-assembly . Dans
ce cas, le remplacement
devra posséder l'accessibilité
family .
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Accessibilité Accessibilité des membres La visibilité et l'accessibilité 12


des types et des membres
seront déterminées de telle
sorte que les types utilisés
dans la signature d'un
membre seront visibles et
accessibles lorsque le
membre proprement dit est
visible et accessible. Par
exemple, une méthode
publique qui est visible à
l'extérieur de son assembly
n'aura pas d'argument dont
le type est visible
uniquement dans l'assembly.
La visibilité et l'accessibilité
des types composant un
type générique instancié
utilisé dans la signature d'un
membre seront visibles et
accessibles lorsque le
membre proprement dit est
visible et accessible. Par
exemple, un type générique
instancié présent dans la
signature d'un membre qui
est visible à l'extérieur de
l'assembly n'aura pas
d'argument générique dont
le type est visible
uniquement dans l'assembly.

Tableaux Tableaux Les tableaux auront des 16


éléments avec un type
conforme à CLS, et toutes les
dimensions du tableau
auront des limites inférieures
égales à zéro. Seul le fait
qu'un élément soit un
tableau et le type d'élément
du tableau seront requis
pour distinguer les
surcharges. Lorsque la
surcharge est basée sur deux
ou plusieurs types tableau,
les types d’éléments doivent
être des types nommés.

Attributs Attributs Les attributs doivent être de 41


type System.Attribute ou
d’un type hérité de celui-ci.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Attributs Attributs La spécification CLS autorise 34


uniquement un sous-
ensemble des encodages
d'attributs personnalisés. Les
seuls types qui doivent
apparaître dans ces codages
sont (voir Partition IV) :
System.Type, System.String,
System.Char,
System.Boolean,
System.Byte, System.Int16,
System.Int32, System.Int64,
System.Single,
System.Double et tout type
d’énumération reposant sur
un type entier de base
conforme CLS.

Attributs Attributs La spécification CLS 35


n'autorise pas les
modificateurs obligatoires
visibles publiquement (
modreq , voir Partition II),
mais autorise les
modificateurs facultatifs (
modopt , voir Partition II)
qu'elle ne comprend pas.

Constructeurs Constructeurs Un constructeur d'objet 21


appellera un constructeur
d'instance de sa classe de
base avant tout accès aux
données d'instance héritées.
(Cela ne s'applique pas aux
types de valeurs, qui n'ont
pas besoin de constructeurs.)

Constructeurs Constructeurs Un constructeur d'objet ne 22


sera pas appelé sauf dans le
cadre de la création d'un
objet, et un objet ne sera pas
initialisé deux fois.

Énumérations Énumérations Le type sous-jacent d'une 7


énumération devra être un
type d'entier CLS intégré, le
nom du champ devra être
« valeur__ » et ce champ
devra être marqué
RTSpecialName .
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Énumérations Énumérations Il existe deux sortes 8


distinctes d’énumérations,
signalées par la présence ou
l’absence de l’attribut
personnalisé
System.FlagsAttribute (voir
Partition IV, Library). L'un
représente des valeurs
entières nommées ; l'autre
représente les indicateurs
binaires nommés qui
peuvent être combinés pour
générer une valeur sans
nom. La valeur d'une enum
n'est pas limitée aux valeurs
spécifiées.

Énumérations Énumérations Les champs static littéraux 9


d’une énumération auront le
type de l’énumération elle-
même.

Événements Événements Les méthodes qui 29


implémentent un événement
doivent être marquées
SpecialName dans les
métadonnées.

Événements Événements L’accessibilité d’un 30


événement et de ses
accesseurs sera identique.

Événements Événements Les méthodes add et 31


remove d'un événement
devront toutes les deux être
présentes ou absentes.

Événements Événements Les add remove 32


méthodes et d’un
événement doivent chacune
accepter un paramètre dont
le type définit le type de
l’événement et qui doit être
dérivé de System. Delegate.

Événements Événements Les événements adhéreront 33


à un modèle d’attribution de
nom spécifique. L’attribut
SpecialName dont il est
question dans la règle 29 de
la spécification CLS doit être
ignoré dans les
comparaisons de noms
appropriées et doit respecter
les règles d’identificateur.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Exceptions Exceptions Les objets levés doivent être 40


de type System.Exception ou
d’un type hérité de celui-ci.
Néanmoins, les méthodes
conformes à CLS ne sont pas
requises pour bloquer la
propagation d'autres types
d'exceptions.

Général Règles de conformité CLS Les règles CLS s'appliquent 1


uniquement aux éléments
d'un type qui sont
accessibles ou visibles en
dehors de l'assembly de
définition.

Général Règles de conformité CLS Les membres de types non 2


conformes à CLS ne seront
pas marqués comme
conformes à CLS.

Génériques Types et membres Les types imbriqués devront 42


génériques posséder au moins autant de
paramètres génériques que
le type englobant. Les
paramètres génériques d'un
type imbriqué ont la même
position que les paramètres
génériques du type
englobant correspondant.

Génériques Types et membres Le nom d’un type générique 43


génériques devra encoder le nombre de
paramètres de type déclarés
sur le type non imbriqué ou
récemment introduits dans
le type s’il est imbriqué,
selon les règles définies ci-
dessus.

Génériques Types et membres Un type générique devra 44


génériques redéclarer les contraintes
suffisantes afin de garantir
que les contraintes sur le
type de base ou les
interfaces seraient satisfaites
par les contraintes de type
générique.

Génériques Types et membres Les types utilisés comme 45


génériques contraintes sur les
paramètres génériques
devront eux-mêmes être
conformes à CLS.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Génériques Types et membres La visibilité et l'accessibilité 46


génériques des membres (y compris des
types imbriqués) d'un type
générique instancié devront
être définies dans
l'instanciation spécifique
plutôt que dans la
déclaration générale du type
générique. Sachant cela, les
règles de visibilité et
d'accessibilité de la règle 12
de la spécification CLS
s'appliquent toujours.

Génériques Types et membres À chaque méthode 47


génériques générique abstraite ou
virtuelle doit correspondre
une implémentation
concrète (non abstraite) par
défaut

Interfaces Interfaces Les interfaces conformes à 18


CLS ne devront pas
nécessiter la définition de
méthodes non conformes à
CLS pour pouvoir les
implémenter.

Interfaces Interfaces Les interfaces conformes à 19


CLS ne devront pas définir
de méthodes statiques ni de
champs.

Membres Membres de types en Les méthodes et les champs 36


général static globaux ne sont pas
conformes CLS.

Membres -- La valeur d'un champ 13


statique littéral est spécifiée
via l'utilisation de
métadonnées d'initialisation
de champ. Un littéral
conforme à CLS doit avoir
une valeur spécifiée dans les
métadonnées d'initialisation
de champ qui est
exactement du même type
que le littéral (ou du type
sous-jacent, si ce littéral est
une enum ).
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Membres Membres de types en La contrainte vararg ne fait 15


général pas partie de la
spécification CLS, et la seule
convention d’appel prise en
charge par la
spécification CLS est la
convention d’appel managée
standard.

Conventions d’affectation de Conventions d’affectation de Les assemblys doivent suivre 4


noms noms l’Annexe 7 du Rapport
technique 15 de la norme
Unicode 3.0 régissant
l’ensemble des caractères
autorisés pour lancer les
identificateurs et être inclus
dans ces derniers. Cette
annexe est disponible en
ligne sous Unicode
Normalization Forms
(Formes de normalisation
Unicode). Les identificateurs
doivent être dans un format
canonique défini par la
forme C de normalisation
Unicode. Dans le cadre de la
spécification CLS, deux
identificateurs sont les
mêmes si leurs mappages en
minuscules (comme spécifié
par les mappages en
minuscules un-à-un
insensibles aux paramètres
régionaux Unicode) sont
identiques. Autrement dit,
pour que deux identificateurs
soient considérés comme
différents dans le cadre de la
spécification CLS, ils doivent
être différenciés par d’autres
éléments que leur casse.
Toutefois, pour remplacer
une définition héritée,
l’infrastructure CLI nécessite
l’utilisation de l’encodage
exact de la déclaration
d’origine.

Surcharge Conventions d’affectation de Tous les noms introduits 5


noms dans une portée conforme
CLS doivent être distincts,
indépendamment de leur
type, sauf quand les noms
sont identiques et résolus
par surcharge. Par exemple,
alors que CTS autorise un
type à utiliser le même nom
pour une méthode et un
champ, CLS ne l’autorise pas.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Surcharge Conventions d’affectation de Les champs et les types 6


noms imbriqués seront distincts
par comparaison
d'identificateurs seule, même
si CTS autorise la distinction
de signatures différentes. Les
méthodes, les propriétés et
les événements qui ont le
même nom (par
comparaison d’identificateur)
ne doivent pas différer
uniquement par le type de
retour, sauf dans les cas
spécifiés dans la règle CLS 39

Surcharge Surcharges Seules les propriétés et les 37


méthodes peuvent être
surchargées.

Surcharge Surcharges Les propriétés et les 38


méthodes peuvent être
surchargées en fonction du
nombre et des types de leurs
paramètres uniquement, à
l'exception des opérateurs de
conversion nommés
op_Implicit et
op_Explicit , qui peuvent
également être surchargés
selon leur type de retour.

Surcharge -- Si deux ou plusieurs 48


méthodes conformes CLS
déclarées dans un type ont
le même nom et, pour un jeu
spécifique d’instanciations de
types, ont le même
paramètre et les mêmes
types de retour, alors toutes
ces méthodes sont
sémantiquement
équivalentes à ces
instanciations de type.

Propriétés Propriétés Les méthodes qui 24


implémentent les méthodes
getter et setter d’une
propriété doivent être
marquées SpecialName
dans les métadonnées.

Propriétés Propriétés Les accesseurs d’une 26


propriété doivent tous être
statiques, virtuels ou être
des instances.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Propriétés Propriétés Le type d’une propriété 27


devra correspondre au type
de retour de la méthode
getter et au type du dernier
argument de la méthode
setter. Les types des
paramètres de la propriété
devront correspondre aux
types des paramètres de la
méthode getter et aux types
de tous les paramètres de la
méthode setter, sauf le
dernier. Tous ces types
devront être conformes à
CLS et ne pas être des
pointeurs managés (à savoir,
ils ne doivent pas être passés
par référence).

Propriétés Propriétés Les propriétés adhéreront à 28


un modèle d’attribution de
nom spécifique. L'attribut
SpecialName dont il est
question dans la règle 24 de
la spécification CLS sera
ignoré dans les
comparaisons de noms
appropriées et respectera les
règles d'identificateur. Une
propriété aura une méthode
getter, une méthode setter
ou les deux.

Conversion de type Conversion de type Si op_Implicit ou op_Explicit 39


est fourni, un autre moyen
est utilisé pour fournir la
contrainte.

Types Types et signatures de Les types de valeurs 3


membres de types encadrés ne sont pas
conformes à CLS.

Types Types et signatures de Tous les types apparaissant 11


membres de types dans une signature devront
être conformes à CLS. Tous
les types composant un type
générique instancié devront
être conformes à CLS.

Types Types et signatures de Les références typées ne 14


membres de types sont pas conformes CLS.

Types Types et signatures de Les types de pointeurs non 17


membres de types managés ne sont pas
conformes à CLS.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Types Types et signatures de Les classes, les types de 20


membres de types valeurs et les interfaces
conformes CLS ne doivent
pas nécessiter
l’implémentation de
membres non conformes
CLS

Types Types et signatures de System.Object est conforme 23


membres de types CLS. Toute autre classe
conforme à CLS héritera
d'une classe conforme à CLS.

Index des sous-sections :


Types et signatures de membres de types
Conventions d’affectation de noms
Conversion de type
Tableaux
Interfaces
Énumérations
Membres de types en général
Accessibilité des membres
Types et membres génériques
Constructeurs
Propriétés
Événements
Surcharges
Exceptions
Attributs
Types et signatures de membres de types
Le type System.Object est conforme CLS et correspond au type de base de tous les types d’objets du système de
types .Net Framework. Dans le .NET Framework, l’héritage est implicite (par exemple, la classe String hérite
implicitement de la classe Object ) ou explicite (par exemple, la classe CultureNotFoundException hérite
explicitement de la classe ArgumentException, qui hérite explicitement de la classe Exception. Pour qu'un type
dérivé soit conforme à CLS, son type de base doit également être conforme à CLS.
L'exemple suivant montre un type dérivé dont le type de base n'est pas conforme à CLS. Il définit une classe
Counter de base qui utilise un entier 32 bits non signé en tant que compteur. La classe fournissant une
fonctionnalité de compteur en encapsulant un entier non signé, elle est marquée comme non conforme à CLS. Par
conséquent, une classe dérivée, NonZeroCounter , n'est pas non plus conforme à CLS.
using System;

[assembly: CLSCompliant(true)]

[CLSCompliant(false)]
public class Counter
{
UInt32 ctr;

public Counter()
{
ctr = 0;
}

protected Counter(UInt32 ctr)


{
this.ctr = ctr;
}

public override string ToString()


{
return $"{ctr}). ";
}

public UInt32 Value


{
get { return ctr; }
}

public void Increment()


{
ctr += (uint) 1;
}
}

public class NonZeroCounter : Counter


{
public NonZeroCounter(int startIndex) : this((uint) startIndex)
{
}

private NonZeroCounter(UInt32 startIndex) : base(startIndex)


{
}
}
// Compilation produces a compiler warning like the following:
// Type3.cs(37,14): warning CS3009: 'NonZeroCounter': base type 'Counter' is not
// CLS-compliant
// Type3.cs(7,14): (Location of symbol related to previous warning)
<Assembly: CLSCompliant(True)>

<CLSCompliant(False)> _
Public Class Counter
Dim ctr As UInt32

Public Sub New


ctr = 0
End Sub

Protected Sub New(ctr As UInt32)


ctr = ctr
End Sub

Public Overrides Function ToString() As String


Return $"{ctr}). "
End Function

Public ReadOnly Property Value As UInt32


Get
Return ctr
End Get
End Property

Public Sub Increment()


ctr += CType(1, UInt32)
End Sub
End Class

Public Class NonZeroCounter : Inherits Counter


Public Sub New(startIndex As Integer)
MyClass.New(CType(startIndex, UInt32))
End Sub

Private Sub New(startIndex As UInt32)


MyBase.New(CType(startIndex, UInt32))
End Sub
End Class
' Compilation produces a compiler warning like the following:
' Type3.vb(34) : warning BC40026: 'NonZeroCounter' is not CLS-compliant
' because it derives from 'Counter', which is not CLS-compliant.
'
' Public Class NonZeroCounter : Inherits Counter
' ~~~~~~~~~~~~~~

Tous les types qui apparaissent dans les signatures de membres, notamment le type de retour d’une méthode ou
un type de propriété, doivent être conformes à CLS. En outre, pour les types génériques :
Tous les types qui composent un type générique instancié doivent être conformes à CLS.
Tous les types utilisés comme contraintes sur des paramètres génériques doivent eux-mêmes être
conformes à CLS.
Le système de type commun .NET inclut un certain nombre de types intégrés pris en charge directement par le
Common Langage Runtime et qui sont spécialement encodés dans les métadonnées d’un assembly. Parmi les types
intrinsèques, les types répertoriés dans le tableau suivant sont conformes à CLS.

T Y P E C O N F O RM E À C L S DESC RIP T IO N

Poids Entier 8 bits non signé

Int16 Entier 16 bits signé


T Y P E C O N F O RM E À C L S DESC RIP T IO N

Int32 Entier 32 bits signé

Int64 Entier 64 bits signé

Unique Valeur à virgule flottante simple précision

Double Valeur à virgule flottante double précision

Booléen Type de valeur true ou false

Char Unité de code encodée en UTF-16

Décimal Nombre décimal à virgule fixe

IntPtr Pointeur ou handle d'une taille définie par la plateforme

Chaîne Collection de zéro, un ou plusieurs objets Char

Les types intrinsèques répertoriés dans le tableau suivant ne sont pas conformes à CLS.

T Y P E N O N C O N F O RM E DESC RIP T IO N A LT ERN AT IVE À L A C O N F O RM IT É C L S

SByte Type de données entier signé 8 bits Int16

UInt16 Entier 16 bits non signé Int32

UInt32 Entier non signé 32 bits Int64

UInt64 Entier non signé 64 bits Int64 (peut déborder), BigInteger ou


Double

UIntPtr Pointeur ou handle non signé IntPtr

La bibliothèque de classes du .NET Framework ou toute autre bibliothèque de classes peut inclure d'autres types
non conformes à CLS. Par exemple :
Types de valeurs encadrés. L'exemple C# suivant crée une classe qui a une propriété publique de type int*
nommée Value . Comme int* est un type de valeur encadré, le compilateur le signale comme non conforme à
CLS.
using System;

[assembly:CLSCompliant(true)]

public unsafe class TestClass


{
private int* val;

public TestClass(int number)


{
val = (int*) number;
}

public int* Value {


get { return val; }
}
}
// The compiler generates the following output when compiling this example:
// warning CS3003: Type of 'TestClass.Value' is not CLS-compliant

Références typées, qui sont des constructions spéciales qui contiennent une référence à un objet et une
référence à un type.
Si un type n’est pas conforme CLS, vous devez lui appliquer l’attribut CLSCompliantAttribute avec un paramètre
isCompliant dont la valeur est false . Pour plus d’informations, consultez la section Attribut
CLSCompliantAttribute.
L'exemple suivant illustre le problème de conformité CLS dans une signature de méthode et dans une instanciation
de type générique. Il définit une classe InvoiceItem avec une propriété de type UInt32, une propriété de type
Nullable(Of UInt32) et un constructeur avec des paramètres de type UInt32 et Nullable(Of UInt32) . Vous obtenez
quatre avertissements du compilateur lorsque vous essayez de compiler cet exemple.
using System;

[assembly: CLSCompliant(true)]

public class InvoiceItem


{
private uint invId = 0;
private uint itemId = 0;
private Nullable<uint> qty;

public InvoiceItem(uint sku, Nullable<uint> quantity)


{
itemId = sku;
qty = quantity;
}

public Nullable<uint> Quantity


{
get { return qty; }
set { qty = value; }
}

public uint InvoiceId


{
get { return invId; }
set { invId = value; }
}
}
// The attempt to compile the example displays the following output:
// Type1.cs(13,23): warning CS3001: Argument type 'uint' is not CLS-compliant
// Type1.cs(13,33): warning CS3001: Argument type 'uint?' is not CLS-compliant
// Type1.cs(19,26): warning CS3003: Type of 'InvoiceItem.Quantity' is not CLS-compliant
// Type1.cs(25,16): warning CS3003: Type of 'InvoiceItem.InvoiceId' is not CLS-compliant
<Assembly: CLSCompliant(True)>

Public Class InvoiceItem

Private invId As UInteger = 0


Private itemId As UInteger = 0
Private qty AS Nullable(Of UInteger)

Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))


itemId = sku
qty = quantity
End Sub

Public Property Quantity As Nullable(Of UInteger)


Get
Return qty
End Get
Set
qty = value
End Set
End Property

Public Property InvoiceId As UInteger


Get
Return invId
End Get
Set
invId = value
End Set
End Property
End Class
' The attempt to compile the example displays output similar to the following:
' Type1.vb(13) : warning BC40028: Type of parameter 'sku' is not CLS-compliant.
'
' Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
' ~~~
' Type1.vb(13) : warning BC40041: Type 'UInteger' is not CLS-compliant.
'
' Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
' ~~~~~~~~
' Type1.vb(18) : warning BC40041: Type 'UInteger' is not CLS-compliant.
'
' Public Property Quantity As Nullable(Of UInteger)
' ~~~~~~~~
' Type1.vb(27) : warning BC40027: Return type of function 'InvoiceId' is not CLS-compliant.
'
' Public Property InvoiceId As UInteger

Pour supprimer les avertissements du compilateur, remplacez les types non conforme à CLS de l'interface publique
InvoiceItem par des types conformes :
using System;

[assembly: CLSCompliant(true)]

public class InvoiceItem


{
private uint invId = 0;
private uint itemId = 0;
private Nullable<int> qty;

public InvoiceItem(int sku, Nullable<int> quantity)


{
if (sku <= 0)
throw new ArgumentOutOfRangeException("The item number is zero or negative.");
itemId = (uint) sku;

qty = quantity;
}

public Nullable<int> Quantity


{
get { return qty; }
set { qty = value; }
}

public int InvoiceId


{
get { return (int) invId; }
set {
if (value <= 0)
throw new ArgumentOutOfRangeException("The invoice number is zero or negative.");
invId = (uint) value; }
}
}
Assembly: CLSCompliant(True)>

Public Class InvoiceItem

Private invId As UInteger = 0


Private itemId As UInteger = 0
Private qty AS Nullable(Of Integer)

Public Sub New(sku As Integer, quantity As Nullable(Of Integer))


If sku <= 0 Then
Throw New ArgumentOutOfRangeException("The item number is zero or negative.")
End If
itemId = CUInt(sku)
qty = quantity
End Sub

Public Property Quantity As Nullable(Of Integer)


Get
Return qty
End Get
Set
qty = value
End Set
End Property

Public Property InvoiceId As Integer


Get
Return CInt(invId)
End Get
Set
invId = CUInt(value)
End Set
End Property
End Class

Outre les types spécifiques répertoriés, certaines catégories de types ne sont pas conformes à CLS. Celles-ci
incluent les types de pointeurs non managés et les types de pointeurs de fonctions. L'exemple suivant génère un
avertissement du compilateur, car il utilise un pointeur vers un entier pour créer un tableau d'entiers.

using System;

[assembly: CLSCompliant(true)]

public class ArrayHelper


{
unsafe public static Array CreateInstance(Type type, int* ptr, int items)
{
Array arr = Array.CreateInstance(type, items);
int* addr = ptr;
for (int ctr = 0; ctr < items; ctr++) {
int value = *addr;
arr.SetValue(value, ctr);
addr++;
}
return arr;
}
}
// The attempt to compile this example displays the following output:
// UnmanagedPtr1.cs(8,57): warning CS3001: Argument type 'int*' is not CLS-compliant
using System;

[assembly: CLSCompliant(true)]

public class ArrayHelper


{
unsafe public static Array CreateInstance(Type type, int* ptr, int items)
{
Array arr = Array.CreateInstance(type, items);
int* addr = ptr;
for (int ctr = 0; ctr < items; ctr++) {
int value = *addr;
arr.SetValue(value, ctr);
addr++;
}
return arr;
}
}
// The attempt to compile this example displays the following output:
// UnmanagedPtr1.cs(8,57): warning CS3001: Argument type 'int*' is not CLS-compliant

Pour les classes abstraites conformes CLS (autrement dit, les classes marquées comme abstract en C#), tous les
membres de la classe doivent également être conformes CLS.
Conventions d’affectation de noms
Étant donné que certains langages de programmation ne respectent pas la casse, les identificateurs (tels que les
noms d'espaces de noms, de types et de membres) doivent se différencier par autre chose que la casse. Deux
identificateurs sont considérés comme équivalents si leurs mappages en minuscules sont identiques. L'exemple C#
suivant définit deux classes publiques : Person et person . Étant donné qu'elles ne diffèrent que par leur casse, le
compilateur C# les signale comme étant non conformes à CLS.

using System;

[assembly: CLSCompliant(true)]

public class Person : person


{

public class person


{

}
// Compilation produces a compiler warning like the following:
// Naming1.cs(11,14): warning CS3005: Identifier 'person' differing
// only in case is not CLS-compliant
// Naming1.cs(6,14): (Location of symbol related to previous warning)

Les identificateurs de langage de programmation, tels que les noms d’espaces de noms, de types et de membres,
doivent être conformes à la norme Unicode. Cela signifie que :
Le premier caractère d'un identificateur peut être une lettre majuscule, une lettre minuscule, une initiale
majuscule, une lettre de modificateur, une autre lettre ou un nombre sous forme de lettre, Unicode. Pour plus
d’informations sur les catégories de caractères Unicode, consultez l’énumération
System.Globalization.UnicodeCategory.
Les caractères suivants peuvent provenir de n’importe quelle catégorie comme premier caractère, et
peuvent également inclure des marques de non-espacement, des marques de l’espacement, des nombres
décimaux, des signes de ponctuation de connecteur et des codes de mise en forme.
Avant de comparer les identificateurs, vous devez éliminer par filtrage les codes de mise en forme et convertir les
identificateurs au formulaire de normalisation Unicode C, car un caractère unique peut être représenté par
plusieurs unités de code encodées en UTF-16. Les séquences de caractères qui produisent les mêmes unités de
code dans un formulaire de normalisation Unicode C ne sont pas conformes à CLS. L'exemple suivant définit une
propriété nommée Å , qui est constituée du caractère SYMBOLE ANGSTRÖM (U+212B), et une deuxième
propriété nommée Å , qui est constituée du caractère LETTRE MAJUSCULE LATINE A AVEC DIACRITIQUE ROND EN
CHEF (U+00C5). Le compilateur C# signale le code source comme non conforme CLS.

public class Size


{
private double a1;
private double a2;

public double Å
{
get { return a1; }
set { a1 = value; }
}

public double Å
{
get { return a2; }
set { a2 = value; }
}
}
// Compilation produces a compiler warning like the following:
// Naming2a.cs(16,18): warning CS3005: Identifier 'Size.Å' differing only in case is not
// CLS-compliant
// Naming2a.cs(10,18): (Location of symbol related to previous warning)
// Naming2a.cs(18,8): warning CS3005: Identifier 'Size.Å.get' differing only in case is not
// CLS-compliant
// Naming2a.cs(12,8): (Location of symbol related to previous warning)
// Naming2a.cs(19,8): warning CS3005: Identifier 'Size.Å.set' differing only in case is not
// CLS-compliant
// Naming2a.cs(13,8): (Location of symbol related to previous warning)
<Assembly: CLSCompliant(True)>
Public Class Size
Private a1 As Double
Private a2 As Double

Public Property Å As Double


Get
Return a1
End Get
Set
a1 = value
End Set
End Property

Public Property Å As Double


Get
Return a2
End Get
Set
a2 = value
End Set
End Property
End Class
' Compilation produces a compiler warning like the following:
' Naming1.vb(9) : error BC30269: 'Public Property Å As Double' has multiple definitions
' with identical signatures.
'
' Public Property Å As Double
' ~

Les noms de membres dans une portée donnée (tels que les espaces de noms dans un assembly, les types dans un
espace de noms ou les membres dans un type) doivent être uniques, à l'exception des noms résolus par la
surcharge. Cette spécification est plus stricte que celle du système de type commun, qui autorise plusieurs
membres d'une portée à avoir des noms identiques tant qu'il s'agit de types de membres différents (par exemple,
l'un est une méthode et l'autre est un champ). En particulier, pour les membres de types :
Les champs et les types imbriqués se distinguent par le nom uniquement.
Les méthodes, les propriétés et les événements qui portent le même nom doivent différer par autre chose
que le type de retour.
L'exemple suivant illustre la spécification selon laquelle les noms de membres doivent être uniques dans leur
portée. Il définit une classe nommée Converter qui inclut quatre membres nommés Conversion . Trois sont des
méthodes, le dernier est une propriété. La méthode qui inclut un paramètre Int64 est nommée de manière unique,
contrairement aux deux méthodes contenant un paramètre Int32 , car la valeur de retour n'est pas considérée
comme faisant partie de la signature d'un membre. La propriété Conversion enfreint également cette spécification,
car les propriétés ne peuvent pas avoir le même nom que les méthodes surchargées.
using System;

[assembly: CLSCompliant(true)]

public class Converter


{
public double Conversion(int number)
{
return (double) number;
}

public float Conversion(int number)


{
return (float) number;
}

public double Conversion(long number)


{
return (double) number;
}

public bool Conversion


{
get { return true; }
}
}
// Compilation produces a compiler error like the following:
// Naming3.cs(13,17): error CS0111: Type 'Converter' already defines a member called
// 'Conversion' with the same parameter types
// Naming3.cs(8,18): (Location of symbol related to previous error)
// Naming3.cs(23,16): error CS0102: The type 'Converter' already contains a definition for
// 'Conversion'
// Naming3.cs(8,18): (Location of symbol related to previous error)
<Assembly: CLSCompliant(True)>

Public Class Converter


Public Function Conversion(number As Integer) As Double
Return CDbl(number)
End Function

Public Function Conversion(number As Integer) As Single


Return CSng(number)
End Function

Public Function Conversion(number As Long) As Double


Return CDbl(number)
End Function

Public ReadOnly Property Conversion As Boolean


Get
Return True
End Get
End Property
End Class
' Compilation produces a compiler error like the following:
' Naming3.vb(8) : error BC30301: 'Public Function Conversion(number As Integer) As Double'
' and 'Public Function Conversion(number As Integer) As Single' cannot
' overload each other because they differ only by return types.
'
' Public Function Conversion(number As Integer) As Double
' ~~~~~~~~~~
' Naming3.vb(20) : error BC30260: 'Conversion' is already declared as 'Public Function
' Conversion(number As Integer) As Single' in this class.
'
' Public ReadOnly Property Conversion As Boolean
' ~~~~~~~~~~

Les langages individuels incluent des mots clés uniques de sorte que les langages qui ciblent le Common Langage
Runtime doivent également fournir un mécanisme de référencement des identificateurs (tels que les noms de
types) qui coïncident avec les mots clés. Par exemple, case est un mot clé en C# et Visual Basic. Toutefois, l'exemple
Visual Basic suivant peut supprimer l'ambiguïté entre une classe nommée case et le mot clé case en utilisant des
accolades ouvrantes et fermantes. Sans cela, l'exemple générerait le message d'erreur : « Mot clé non valide en tant
qu'identificateur » et échouerait lors de la compilation.

Public Class [case]


Private _id As Guid
Private name As String

Public Sub New(name As String)


_id = Guid.NewGuid()
Me.name = name
End Sub

Public ReadOnly Property ClientName As String


Get
Return name
End Get
End Property
End Class

L’exemple C# suivant peut instancier la classe case en utilisant le symbole @ pour lever l’ambiguïté de
l’identificateur par rapport au mot clé de langage. Sans cela, le compilateur C# afficherait deux messages d'erreur :
« Type attendu » et « Terme d'expression non valide 'case' ».
using System;

public class Example


{
public static void Main()
{
@case c = new @case("John");
Console.WriteLine(c.ClientName);
}
}

Conversion de type
La spécification CLS (Common Language Specification) définit deux opérateurs de conversion :
op_Implicit , qui est utilisé pour les conversions étendues qui n'entraînent pas la perte de données ou de
précision. Par exemple, la structure Decimal inclut un opérateur op_Implicit surchargé pour convertir les
valeurs de types intégraux et les valeurs Char en valeurs Decimal .
op_Explicit , qui est utilisé pour les conversions restrictives qui peuvent entraîner la perte d'amplitude (une
valeur est convertie en une valeur qui entraîne une plus petite plage) ou de précision. Par exemple, la
structure Decimal inclut un opérateur op_Explicit surchargé pour convertir les valeurs Double et Single en
Decimal , et convertir les valeurs Decimal en valeurs intégrales, Double , Single et Char .

Toutefois, tous les langages ne prennent pas en charge la surcharge d'opérateur ou la définition d'opérateurs
personnalisés. Si vous décidez d'implémenter ces opérateurs de conversion, vous devez également fournir une
autre façon d'effectuer la conversion. Nous vous recommandons de fournir les méthodes From Xxx et To Xxx.
L'exemple suivant définit les conversions implicites et explicites conformes à CLS. Il crée une UDouble classe qui
représente un nombre à virgule flottante double précision signé. Il s'applique aux conversions implicites de
UDouble à Double et aux conversions explicites de UDouble à Single , de Double à UDouble et de Single à
UDouble . Il définit également une méthode ToDouble comme une alternative à l'opérateur de conversion implicite
et les méthodes ToSingle , FromDouble et FromSingle comme alternatives aux opérateurs de conversion explicite.

using System;

public struct UDouble


{
private double number;

public UDouble(double value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

number = value;
}

public UDouble(float value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

number = value;
}

public static readonly UDouble MinValue = (UDouble) 0.0;


public static readonly UDouble MaxValue = (UDouble) Double.MaxValue;

public static explicit operator Double(UDouble value)


{
return value.number;
}

public static implicit operator Single(UDouble value)


{
if (value.number > (double) Single.MaxValue)
throw new InvalidCastException("A UDouble value is out of range of the Single type.");

return (float) value.number;


}

public static explicit operator UDouble(double value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

return new UDouble(value);


}

public static implicit operator UDouble(float value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

return new UDouble(value);


}

public static Double ToDouble(UDouble value)


{
return (Double) value;
}

public static float ToSingle(UDouble value)


{
return (float) value;
}

public static UDouble FromDouble(double value)


{
return new UDouble(value);
}

public static UDouble FromSingle(float value)


{
return new UDouble(value);
}
}
Public Structure UDouble
Private number As Double

Public Sub New(value As Double)


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
number = value
End Sub

Public Sub New(value As Single)


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
number = value
End Sub

Public Shared ReadOnly MinValue As UDouble = CType(0.0, UDouble)


Public Shared ReadOnly MaxValue As UDouble = Double.MaxValue

Public Shared Widening Operator CType(value As UDouble) As Double


Return value.number
End Operator

Public Shared Narrowing Operator CType(value As UDouble) As Single


If value.number > CDbl(Single.MaxValue) Then
Throw New InvalidCastException("A UDouble value is out of range of the Single type.")
End If
Return CSng(value.number)
End Operator

Public Shared Narrowing Operator CType(value As Double) As UDouble


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
Return New UDouble(value)
End Operator

Public Shared Narrowing Operator CType(value As Single) As UDouble


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
Return New UDouble(value)
End Operator

Public Shared Function ToDouble(value As UDouble) As Double


Return CType(value, Double)
End Function

Public Shared Function ToSingle(value As UDouble) As Single


Return CType(value, Single)
End Function

Public Shared Function FromDouble(value As Double) As UDouble


Return New UDouble(value)
End Function

Public Shared Function FromSingle(value As Single) As UDouble


Return New UDouble(value)
End Function
End Structure

Tableaux
Les tableaux conformes à CLS respectent les règles suivantes :
Toutes les dimensions d'un tableau doivent avoir une limite inférieure égale à zéro. L'exemple suivant crée
un tableau non conforme à CLS avec une limite inférieure égale à un. En dépit de la présence de l’attribut
CLSCompliantAttribute , le compilateur ne détecte pas que le tableau retourné par la Numbers.GetTenPrimes
méthode n’est pas conforme CLS.

[assembly: CLSCompliant(true)]

public class Numbers


{
public static Array GetTenPrimes()
{
Array arr = Array.CreateInstance(typeof(Int32), new int[] {10}, new int[] {1});
arr.SetValue(1, 1);
arr.SetValue(2, 2);
arr.SetValue(3, 3);
arr.SetValue(5, 4);
arr.SetValue(7, 5);
arr.SetValue(11, 6);
arr.SetValue(13, 7);
arr.SetValue(17, 8);
arr.SetValue(19, 9);
arr.SetValue(23, 10);

return arr;
}
}

<Assembly: CLSCompliant(True)>

Public Class Numbers


Public Shared Function GetTenPrimes() As Array
Dim arr As Array = Array.CreateInstance(GetType(Int32), {10}, {1})
arr.SetValue(1, 1)
arr.SetValue(2, 2)
arr.SetValue(3, 3)
arr.SetValue(5, 4)
arr.SetValue(7, 5)
arr.SetValue(11, 6)
arr.SetValue(13, 7)
arr.SetValue(17, 8)
arr.SetValue(19, 9)
arr.SetValue(23, 10)
Return arr
End Function
End Class

Tous les éléments du tableau doivent se composer de types conformes à CLS. L'exemple suivant définit deux
méthodes qui retournent des tableaux non conformes à CLS. La première retourne un tableau de valeurs
UInt32. La deuxième retourne un tableau Object qui inclut les valeurs Int32 et UInt32 . Bien que le
compilateur identifie le premier tableau comme non conforme en raison de son type UInt32 , il ne parvient
pas à reconnaître que le second tableau inclut des éléments non conformes à CLS.
using System;

[assembly: CLSCompliant(true)]

public class Numbers


{
public static UInt32[] GetTenPrimes()
{
uint[] arr = { 1u, 2u, 3u, 5u, 7u, 11u, 13u, 17u, 19u };
return arr;
}

public static Object[] GetFivePrimes()


{
Object[] arr = { 1, 2, 3, 5u, 7u };
return arr;
}
}
// Compilation produces a compiler warning like the following:
// Array2.cs(8,27): warning CS3002: Return type of 'Numbers.GetTenPrimes()' is not
// CLS-compliant

<Assembly: CLSCompliant(True)>

Public Class Numbers


Public Shared Function GetTenPrimes() As UInt32()
Return { 1ui, 2ui, 3ui, 5ui, 7ui, 11ui, 13ui, 17ui, 19ui }
End Function
Public Shared Function GetFivePrimes() As Object()
Dim arr() As Object = { 1, 2, 3, 5ui, 7ui }
Return arr
End Function
End Class
' Compilation produces a compiler warning like the following:
' warning BC40027: Return type of function 'GetTenPrimes' is not CLS-compliant.

La résolution de surcharge pour les méthodes qui ont des paramètres de tableau repose sur le fait qu’il s’agit
de tableaux et sur leur type d’élément. C'est pourquoi la définition suivante d'une méthode GetSquares
surchargée est conforme à CLS.
using System;
using System.Numerics;

[assembly: CLSCompliant(true)]

public class Numbers


{
public static byte[] GetSquares(byte[] numbers)
{
byte[] numbersOut = new byte[numbers.Length];
for (int ctr = 0; ctr < numbers.Length; ctr++) {
int square = ((int) numbers[ctr]) * ((int) numbers[ctr]);
if (square <= Byte.MaxValue)
numbersOut[ctr] = (byte) square;
// If there's an overflow, assign MaxValue to the corresponding
// element.
else
numbersOut[ctr] = Byte.MaxValue;

}
return numbersOut;
}

public static BigInteger[] GetSquares(BigInteger[] numbers)


{
BigInteger[] numbersOut = new BigInteger[numbers.Length];
for (int ctr = 0; ctr < numbers.Length; ctr++)
numbersOut[ctr] = numbers[ctr] * numbers[ctr];

return numbersOut;
}
}

Imports System.Numerics

<Assembly: CLSCompliant(True)>

Public Module Numbers


Public Function GetSquares(numbers As Byte()) As Byte()
Dim numbersOut(numbers.Length - 1) As Byte
For ctr As Integer = 0 To numbers.Length - 1
Dim square As Integer = (CInt(numbers(ctr)) * CInt(numbers(ctr)))
If square <= Byte.MaxValue Then
numbersOut(ctr) = CByte(square)
' If there's an overflow, assign MaxValue to the corresponding
' element.
Else
numbersOut(ctr) = Byte.MaxValue
End If
Next
Return numbersOut
End Function

Public Function GetSquares(numbers As BigInteger()) As BigInteger()


Dim numbersOut(numbers.Length - 1) As BigInteger
For ctr As Integer = 0 To numbers.Length - 1
numbersOut(ctr) = numbers(ctr) * numbers(ctr)
Next
Return numbersOut
End Function
End Module

Interfaces
Les interfaces conformes à CLS peuvent définir des propriétés, des événements et des méthodes virtuelles
(méthodes sans implémentation). Une interface conforme à CLS ne peut avoir aucune des caractéristiques
suivantes :
Méthodes statiques ou champs statiques. Le compilateur C# génère des erreurs de compilation si un
membre statique est défini dans une interface.
Champs. Le compilateur C# génère des erreurs de compilation si un champ est défini dans une interface.
Méthodes qui ne sont pas conformes à CLS. Par exemple, la définition d'interface suivante inclut une
méthode, INumber.GetUnsigned , qui est marquée comme non conforme à CLS. Cet exemple génère un
avertissement du compilateur.

using System;

[assembly:CLSCompliant(true)]

public interface INumber


{
int Length();
[CLSCompliant(false)] ulong GetUnsigned();
}
// Attempting to compile the example displays output like the following:
// Interface2.cs(8,32): warning CS3010: 'INumber.GetUnsigned()': CLS-compliant interfaces
// must have only CLS-compliant members

<Assembly: CLSCompliant(True)>

Public Interface INumber


Function Length As Integer
<CLSCompliant(False)> Function GetUnsigned As ULong
End Interface
' Attempting to compile the example displays output like the following:
' Interface2.vb(9) : warning BC40033: Non CLS-compliant 'function' is not allowed in a
' CLS-compliant interface.
'
' <CLSCompliant(False)> Function GetUnsigned As ULong
' ~~~~~~~~~~~

En raison de cette règle, pour implémenter des membres non conformes à CLS, il n'est pas nécessaire
d'utiliser des types conformes à CLS. Si une infrastructure conforme à CLS expose une classe qui
implémente une interface non conforme à CLS, elle doit également fournir des implémentations concrètes
de tous les membres non conformes.
Les compilateurs de langages conformes à CLS doivent également permettre à une classe de fournir des
implémentations séparées des membres qui ont les mêmes nom et signature dans plusieurs interfaces. C# prend
en charge des implémentations d’interface explicites pour fournir des implémentations différentes de méthodes
portant le même nom. L'exemple suivant illustre ce scénario en définissant une classe Temperature qui implémente
les interfaces ICelsius et IFahrenheit en tant qu'implémentations d'interface explicites.
using System;

[assembly: CLSCompliant(true)]

public interface IFahrenheit


{
decimal GetTemperature();
}

public interface ICelsius


{
decimal GetTemperature();
}

public class Temperature : ICelsius, IFahrenheit


{
private decimal _value;

public Temperature(decimal value)


{
// We assume that this is the Celsius value.
_value = value;
}

decimal IFahrenheit.GetTemperature()
{
return _value * 9 / 5 + 32;
}

decimal ICelsius.GetTemperature()
{
return _value;
}
}
public class Example
{
public static void Main()
{
Temperature temp = new Temperature(100.0m);
ICelsius cTemp = temp;
IFahrenheit fTemp = temp;
Console.WriteLine("Temperature in Celsius: {0} degrees",
cTemp.GetTemperature());
Console.WriteLine("Temperature in Fahrenheit: {0} degrees",
fTemp.GetTemperature());
}
}
// The example displays the following output:
// Temperature in Celsius: 100.0 degrees
// Temperature in Fahrenheit: 212.0 degrees
Assembly: CLSCompliant(True)>

Public Interface IFahrenheit


Function GetTemperature() As Decimal
End Interface

Public Interface ICelsius


Function GetTemperature() As Decimal
End Interface

Public Class Temperature : Implements ICelsius, IFahrenheit


Private _value As Decimal

Public Sub New(value As Decimal)


' We assume that this is the Celsius value.
_value = value
End Sub

Public Function GetFahrenheit() As Decimal _


Implements IFahrenheit.GetTemperature
Return _value * 9 / 5 + 32
End Function

Public Function GetCelsius() As Decimal _


Implements ICelsius.GetTemperature
Return _value
End Function
End Class

Module Example
Public Sub Main()
Dim temp As New Temperature(100.0d)
Console.WriteLine("Temperature in Celsius: {0} degrees",
temp.GetCelsius())
Console.WriteLine("Temperature in Fahrenheit: {0} degrees",
temp.GetFahrenheit())
End Sub
End Module
' The example displays the following output:
' Temperature in Celsius: 100.0 degrees
' Temperature in Fahrenheit: 212.0 degrees

Énumérations
Les énumérations conformes à CLS doivent suivre ces règles :
Le type sous-jacent de l’énumération doit être un entier conforme CLS intrinsèque (Byte, Int16, Int32 ou
Int64). Par exemple, le code suivant tente de définir une énumération dont le type sous-jacent est UInt32 et
génère un avertissement du compilateur.
using System;

[assembly: CLSCompliant(true)]

public enum Size : uint {


Unspecified = 0,
XSmall = 1,
Small = 2,
Medium = 3,
Large = 4,
XLarge = 5
};

public class Clothing


{
public string Name;
public string Type;
public string Size;
}
// The attempt to compile the example displays a compiler warning like the following:
// Enum3.cs(6,13): warning CS3009: 'Size': base type 'uint' is not CLS-compliant

<Assembly: CLSCompliant(True)>

Public Enum Size As UInt32


Unspecified = 0
XSmall = 1
Small = 2
Medium = 3
Large = 4
XLarge = 5
End Enum

Public Class Clothing


Public Name As String
Public Type As String
Public Size As Size
End Class
' The attempt to compile the example displays a compiler warning like the following:
' Enum3.vb(6) : warning BC40032: Underlying type 'UInt32' of Enum is not CLS-compliant.
'
' Public Enum Size As UInt32
' ~~~~

Un type d'énumération doit avoir un champ d'instance unique nommé Value__ qui est marqué avec
l'attribut FieldAttributes.RTSpecialName . Cela vous permet de référencer implicitement la valeur du champ.
Une énumération inclut les champs statiques littéraux du même type que l'énumération elle-même. Par
exemple, si vous définissez une énumération State avec les valeurs State.On et State.Off , State.On et
State.Off sont les deux champs statiques littéraux dont le type est State .

Il existe deux types d'énumérations :


Une énumération qui représente un jeu de valeurs entières nommées qui s'excluent mutuellement. Ce
type d’énumération est indiqué par l’absence de l’attribut personnalisé System.FlagsAttribute.
Une énumération qui représente un jeu d'indicateurs binaires qui peuvent être combinés pour
générer une valeur sans nom. Ce type d’énumération est indiqué par la présence de l’attribut
personnalisé System.FlagsAttribute.
Pour plus d’informations, consultez la documentation de la structure Enum.
La valeur d'une énumération ne se limite pas à la plage de ses valeurs spécifiées. En d'autres termes, la plage de
valeurs d'une énumération est la plage de sa valeur sous-jacente. Vous pouvez utiliser la méthode
Enum.IsDefined pour déterminer si une valeur spécifiée est membre d'une énumération.

Membres de types en général


La spécification CLS (Common Language Specification) nécessite que tous les champs et toutes les méthodes
soient accessibles en tant que membres d'une classe particulière. Par conséquent, les méthodes et les champs
statiques globaux (autrement dit, les méthodes ou les champs statiques définis en dehors d'un type) ne sont pas
conformes à CLS. Si vous essayez d’inclure un champ global ou une méthode globale dans votre code source, le
compilateur C# génère une erreur de compilateur.
La spécification CLS (Common Language Specification) prend uniquement en charge la convention d'appel
managée standard. Elle ne prend pas en charge les conventions d'appel non managées ni les méthodes avec des
listes d'arguments variables marquées avec le mot clé varargs . Pour les listes d’arguments variables compatibles
avec la convention d’appel managée standard, utilisez l’attribut ParamArrayAttribute ou l’implémentation de
chaque langage, comme le mot clé params en C# et le mot clé ParamArray en Visual Basic.
Accessibilité des membres
Le remplacement d'un membre hérité ne peut pas modifier l'accessibilité de ce membre. Par exemple, une méthode
publique dans une classe de base ne peut pas être remplacée par une méthode privée dans une classe dérivée. Il
existe une exception : un membre protected internal (en C#) ou Protected Friend (en Visual Basic) dans un
assembly qui est remplacé par un type dans un autre assembly. Dans ce cas, l'accessibilité du remplacement est
Protected .

L’exemple suivant illustre l’erreur générée quand l’attribut CLSCompliantAttribute a la valeur true , et que Person ,
qui est une classe dérivée de Animal , tente de remplacer l’accessibilité publique de la propriété Species par une
accessibilité privée. L'exemple se compile correctement si son accessibilité devient publique.
using System;

[assembly: CLSCompliant(true)]

public class Animal


{
private string _species;

public Animal(string species)


{
_species = species;
}

public virtual string Species


{
get { return _species; }
}

public override string ToString()


{
return _species;
}
}

public class Human : Animal


{
private string _name;

public Human(string name) : base("Homo Sapiens")


{
_name = name;
}

public string Name


{
get { return _name; }
}

private override string Species


{
get { return base.Species; }
}

public override string ToString()


{
return _name;
}
}

public class Example


{
public static void Main()
{
Human p = new Human("John");
Console.WriteLine(p.Species);
Console.WriteLine(p.ToString());
}
}
// The example displays the following output:
// error CS0621: 'Human.Species': virtual or abstract members cannot be private
<Assembly: CLSCompliant(True)>

Public Class Animal


Private _species As String

Public Sub New(species As String)


_species = species
End Sub

Public Overridable ReadOnly Property Species As String


Get
Return _species
End Get
End Property

Public Overrides Function ToString() As String


Return _species
End Function
End Class

Public Class Human : Inherits Animal


Private _name As String

Public Sub New(name As String)


MyBase.New("Homo Sapiens")
_name = name
End Sub

Public ReadOnly Property Name As String


Get
Return _name
End Get
End Property

Private Overrides ReadOnly Property Species As String


Get
Return MyBase.Species
End Get
End Property

Public Overrides Function ToString() As String


Return _name
End Function
End Class

Public Module Example


Public Sub Main()
Dim p As New Human("John")
Console.WriteLine(p.Species)
Console.WriteLine(p.ToString())
End Sub
End Module
' The example displays the following output:
' 'Private Overrides ReadOnly Property Species As String' cannot override
' 'Public Overridable ReadOnly Property Species As String' because
' they have different access levels.
'
' Private Overrides ReadOnly Property Species As String

Les types de la signature d'un membre doivent être accessibles chaque fois que ce membre est accessible. Par
exemple, cela signifie qu’un membre public ne peut pas inclure un paramètre dont le type est privé, protégé ou
interne. L'exemple suivant illustre l'erreur de compilateur obtenue lorsqu'un constructeur de classe StringWrapper
expose une valeur d'énumération StringOperationType interne qui détermine comment une valeur de chaîne doit
être encapsulée.
using System;
using System.Text;

public class StringWrapper


{
string internalString;
StringBuilder internalSB = null;
bool useSB = false;

public StringWrapper(StringOperationType type)


{
if (type == StringOperationType.Normal) {
useSB = false;
}
else {
useSB = true;
internalSB = new StringBuilder();
}
}

// The remaining source code...


}

internal enum StringOperationType { Normal, Dynamic }


// The attempt to compile the example displays the following output:
// error CS0051: Inconsistent accessibility: parameter type
// 'StringOperationType' is less accessible than method
// 'StringWrapper.StringWrapper(StringOperationType)'

Imports System.Text

<Assembly:CLSCompliant(True)>

Public Class StringWrapper

Dim internalString As String


Dim internalSB As StringBuilder = Nothing
Dim useSB As Boolean = False

Public Sub New(type As StringOperationType)


If type = StringOperationType.Normal Then
useSB = False
Else
internalSB = New StringBuilder()
useSB = True
End If
End Sub

' The remaining source code...


End Class

Friend Enum StringOperationType As Integer


Normal = 0
Dynamic = 1
End Enum
' The attempt to compile the example displays the following output:
' error BC30909: 'type' cannot expose type 'StringOperationType'
' outside the project through class 'StringWrapper'.
'
' Public Sub New(type As StringOperationType)
' ~~~~~~~~~~~~~~~~~~~

Types et membres génériques


Les types imbriqués ont toujours au moins autant de paramètres génériques que leur type englobant. Ceux-ci
correspondent par position aux paramètres génériques du type englobant. Le type générique peut également
inclure de nouveaux paramètres génériques.
La relation entre les paramètres de type générique d'un type conteneur et ses types imbriqués peut être masquée
par la syntaxe des différents langages. Dans l'exemple suivant, un type générique Outer<T> contient deux classes
imbriquées, Inner1A et Inner1B<U> . Les appels à la méthode ToString , dont chaque classe hérite de
Object.ToString , indiquent que chaque classe imbriquée inclut les paramètres de type de sa classe conteneur.

using System;

[assembly:CLSCompliant(true)]

public class Outer<T>


{
T value;

public Outer(T value)


{
this.value = value;
}

public class Inner1A : Outer<T>


{
public Inner1A(T value) : base(value)
{ }
}

public class Inner1B<U> : Outer<T>


{
U value2;

public Inner1B(T value1, U value2) : base(value1)


{
this.value2 = value2;
}
}
}

public class Example


{
public static void Main()
{
var inst1 = new Outer<String>("This");
Console.WriteLine(inst1);

var inst2 = new Outer<String>.Inner1A("Another");


Console.WriteLine(inst2);

var inst3 = new Outer<String>.Inner1B<int>("That", 2);


Console.WriteLine(inst3);
}
}
// The example displays the following output:
// Outer`1[System.String]
// Outer`1+Inner1A[System.String]
// Outer`1+Inner1B`1[System.String,System.Int32]
<Assembly:CLSCompliant(True)>

Public Class Outer(Of T)


Dim value As T

Public Sub New(value As T)


Me.value = value
End Sub

Public Class Inner1A : Inherits Outer(Of T)


Public Sub New(value As T)
MyBase.New(value)
End Sub
End Class

Public Class Inner1B(Of U) : Inherits Outer(Of T)


Dim value2 As U

Public Sub New(value1 As T, value2 As U)


MyBase.New(value1)
Me.value2 = value2
End Sub
End Class
End Class

Public Module Example


Public Sub Main()
Dim inst1 As New Outer(Of String)("This")
Console.WriteLine(inst1)

Dim inst2 As New Outer(Of String).Inner1A("Another")


Console.WriteLine(inst2)

Dim inst3 As New Outer(Of String).Inner1B(Of Integer)("That", 2)


Console.WriteLine(inst3)
End Sub
End Module
' The example displays the following output:
' Outer`1[System.String]
' Outer`1+Inner1A[System.String]
' Outer`1+Inner1B`1[System.String,System.Int32]

Les noms des types génériques sont encodés sous la forme nom'n, où nom est le nom du type, ` est un caractère
littéral et n est le nombre de paramètres déclarés sur le type ou, pour les types génériques imbriqués, le nombre de
paramètres de type récemment introduits. Cet encodage de noms de types génériques intéresse principalement les
développeurs qui utilisent la réflexion pour accéder aux types génériques conformes à CLS dans une bibliothèque.
Si les contraintes sont appliquées à un type générique, tous les types utilisés en tant que contraintes doivent
également être conformes à CLS. L'exemple suivant définit une classe nommée BaseClass qui n'est pas conforme à
CLS et une classe générique nommée BaseCollection dont le paramètre de type doit dériver de BaseClass .
Toutefois, comme BaseClass n'est pas conforme à CLS, le compilateur émet un avertissement.
using System;

[assembly:CLSCompliant(true)]

[CLSCompliant(false)] public class BaseClass


{}

public class BaseCollection<T> where T : BaseClass


{}
// Attempting to compile the example displays the following output:
// warning CS3024: Constraint type 'BaseClass' is not CLS-compliant

Assembly: CLSCompliant(True)>

<CLSCompliant(False)> Public Class BaseClass


End Class

Public Class BaseCollection(Of T As BaseClass)


End Class
' Attempting to compile the example displays the following output:
' warning BC40040: Generic parameter constraint type 'BaseClass' is not
' CLS-compliant.
'
' Public Class BaseCollection(Of T As BaseClass)
' ~~~~~~~~~

Si un type générique est dérivé d'un type de base générique, il doit redéclarer toutes les contraintes pour s'assurer
que les contraintes du type de base sont également satisfaites. L'exemple suivant définit un Number<T> qui peut
représenter un type numérique. Il définit également une classe FloatingPoint<T> qui représente une valeur à
virgule flottante. Toutefois, le code source ne se compile pas, car il n'applique pas la contrainte Number<T> (ce T doit
être un type de valeur) à FloatingPoint<T> .
using System;

[assembly:CLSCompliant(true)]

public class Number<T> where T : struct


{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;

public Number(T value)


{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}

public T Add(T value)


{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}

public T Subtract(T value)


{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}

public class FloatingPoint<T> : Number<T>


{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
// The attempt to compile the example displays the following output:
// error CS0453: The type 'T' must be a non-nullable value type in
// order to use it as parameter 'T' in the generic type or method 'Number<T>'
<Assembly:CLSCompliant(True)>

Public Class Number(Of T As Structure)


' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double

Public Sub New(value As T)


Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub

Public Function Add(value As T) As T


Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function

Public Function Subtract(value As T) As T


Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class

Public Class FloatingPoint(Of T) : Inherits Number(Of T)


Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class
' The attempt to compile the example displays the following output:
' error BC32105: Type argument 'T' does not satisfy the 'Structure'
' constraint for type parameter 'T'.
'
' Public Class FloatingPoint(Of T) : Inherits Number(Of T)
' ~

L'exemple se compile correctement si la contrainte est ajoutée à la classe FloatingPoint<T> .


using System;

[assembly:CLSCompliant(true)]

public class Number<T> where T : struct


{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;

public Number(T value)


{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}

public T Add(T value)


{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}

public T Subtract(T value)


{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}

public class FloatingPoint<T> : Number<T> where T : struct


{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
<Assembly:CLSCompliant(True)>

Public Class Number(Of T As Structure)


' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double

Public Sub New(value As T)


Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub

Public Function Add(value As T) As T


Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function

Public Function Subtract(value As T) As T


Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class

Public Class FloatingPoint(Of T As Structure) : Inherits Number(Of T)


Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class

La spécification CLS (Common Language Specification) impose un modèle conservateur par instanciation pour les
types imbriqués et les membres protégés. Les types génériques ouverts ne peuvent pas exposer de champs ou de
membres avec des signatures qui contiennent une instanciation spécifique d'un type générique imbriqué et
protégé. Les types non génériques qui étendent une instanciation spécifique d'une classe de base ou une interface
générique ne peuvent pas exposer de champs ou de membres avec des signatures qui contiennent une
instanciation différente d'un type générique imbriqué et protégé.
L’exemple suivant définit un type générique, C1<T> , et une classe protégée, C1<T>.N . C1<T> a deux méthodes : M1
et M2 . Toutefois, la méthode M1 n’est pas conforme CLS, car elle essaie de retourner un objet C1<int>.N de C1<T>
. Une deuxième classe, C2 , est dérivée de C1<long> . Elle a deux méthodes, M3 et M4 . M3 ’est pas conforme CLS,
car elle essaie de retourner un objet C1<int>.N d’une sous-classe de C1<long> . Les compilateurs de langage
peuvent être encore plus restrictifs. Dans cet exemple, Visual Basic affiche une erreur lorsqu'il tente de compiler M4
.
using System;

[assembly:CLSCompliant(true)]

public class C1<T>


{
protected class N { }

protected void M1(C1<int>.N n) { } // Not CLS-compliant - C1<int>.N not


// accessible from within C1<T> in all
// languages
protected void M2(C1<T>.N n) { } // CLS-compliant – C1<T>.N accessible
// inside C1<T>
}

public class C2 : C1<long>


{
protected void M3(C1<int>.N n) { } // Not CLS-compliant – C1<int>.N is not
// accessible in C2 (extends C1<long>)

protected void M4(C1<long>.N n) { } // CLS-compliant, C1<long>.N is


// accessible in C2 (extends C1<long>)
}
// Attempting to compile the example displays output like the following:
// Generics4.cs(9,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant
// Generics4.cs(18,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant

<Assembly:CLSCompliant(True)>

Public Class C1(Of T)


Protected Class N
End Class

Protected Sub M1(n As C1(Of Integer).N) ' Not CLS-compliant - C1<int>.N not
' accessible from within C1(Of T) in all
End Sub ' languages

Protected Sub M2(n As C1(Of T).N) ' CLS-compliant – C1(Of T).N accessible
End Sub ' inside C1(Of T)
End Class

Public Class C2 : Inherits C1(Of Long)


Protected Sub M3(n As C1(Of Integer).N) ' Not CLS-compliant – C1(Of Integer).N is not
End Sub ' accessible in C2 (extends C1(Of Long))

Protected Sub M4(n As C1(Of Long).N)


End Sub
End Class
' Attempting to compile the example displays output like the following:
' error BC30508: 'n' cannot expose type 'C1(Of Integer).N' in namespace
' '<Default>' through class 'C1'.
'
' Protected Sub M1(n As C1(Of Integer).N) ' Not CLS-compliant - C1<int>.N not
' ~~~~~~~~~~~~~~~~
' error BC30389: 'C1(Of T).N' is not accessible in this context because
' it is 'Protected'.
'
' Protected Sub M3(n As C1(Of Integer).N) ' Not CLS-compliant - C1(Of Integer).N is not
'
' ~~~~~~~~~~~~~~~~
'
' error BC30389: 'C1(Of T).N' is not accessible in this context because it is 'Protected'.
'
' Protected Sub M4(n As C1(Of Long).N)
' ~~~~~~~~~~~~~
Constructeurs
Les constructeurs des classes et structures conformes à CLS doivent suivre ces règles :
Un constructeur d'une classe dérivée doit appeler le constructeur d'instance de sa classe de base avant
d'accéder aux données d'instance héritées. Cette spécification est due au fait que les constructeurs de classes
de base ne sont pas hérités par leurs classes dérivées. Cette règle ne s'applique pas aux structures, qui ne
prennent pas en charge l'héritage direct.
En général, les compilateurs appliquent cette règle indépendamment de la conformité CLS, comme indiqué
dans l'exemple suivant. Il crée une classe Doctor dérivée d'une classe Person , mais la classe Doctor ne
parvient pas à appeler le constructeur de classe Person pour initialiser les champs d'instance hérités.
using System;

[assembly: CLSCompliant(true)]

public class Person


{
private string fName, lName, _id;

public Person(string firstName, string lastName, string id)


{
if (String.IsNullOrEmpty(firstName + lastName))
throw new ArgumentNullException("Either a first name or a last name must be provided.");

fName = firstName;
lName = lastName;
_id = id;
}

public string FirstName


{
get { return fName; }
}

public string LastName


{
get { return lName; }
}

public string Id
{
get { return _id; }
}

public override string ToString()


{
return String.Format("{0}{1}{2}", fName,
String.IsNullOrEmpty(fName) ? "" : " ",
lName);
}
}

public class Doctor : Person


{
public Doctor(string firstName, string lastName, string id)
{
}

public override string ToString()


{
return "Dr. " + base.ToString();
}
}
// Attempting to compile the example displays output like the following:
// ctor1.cs(45,11): error CS1729: 'Person' does not contain a constructor that takes 0
// arguments
// ctor1.cs(10,11): (Location of symbol related to previous error)
<Assembly: CLSCompliant(True)>

Public Class Person


Private fName, lName, _id As String

Public Sub New(firstName As String, lastName As String, id As String)


If String.IsNullOrEmpty(firstName + lastName) Then
Throw New ArgumentNullException("Either a first name or a last name must be provided.")
End If

fName = firstName
lName = lastName
_id = id
End Sub

Public ReadOnly Property FirstName As String


Get
Return fName
End Get
End Property

Public ReadOnly Property LastName As String


Get
Return lName
End Get
End Property

Public ReadOnly Property Id As String


Get
Return _id
End Get
End Property

Public Overrides Function ToString() As String


Return String.Format("{0}{1}{2}", fName,
If(String.IsNullOrEmpty(fName), "", " "),
lName)
End Function
End Class

Public Class Doctor : Inherits Person


Public Sub New(firstName As String, lastName As String, id As String)
End Sub

Public Overrides Function ToString() As String


Return "Dr. " + MyBase.ToString()
End Function
End Class
' Attempting to compile the example displays output like the following:
' Ctor1.vb(46) : error BC30148: First statement of this 'Sub New' must be a call
' to 'MyBase.New' or 'MyClass.New' because base class 'Person' of 'Doctor' does
' not have an accessible 'Sub New' that can be called with no arguments.
'
' Public Sub New()
' ~~~

Un constructeur d'objet ne peut être appelé que pour créer un objet. En outre, un objet ne peut pas être
initialisé deux fois. Par exemple, cela signifie que Object.MemberwiseClone ne doit pas appeler de
constructeurs.
Propriétés
Les propriétés dans les types conformes à CLS doivent suivre ces règles :
Une propriété doit posséder une méthode setter, getter ou les deux. Dans un assembly, celles-ci sont
implémentées comme des méthodes spéciales, ce qui signifie qu’elles apparaîtront comme des méthodes
distinctes (l’accesseur Get est nommé get _ NomPropriété et l’accesseur Set est set _ PropertyName)
marqué comme SpecialName dans les métadonnées de l’assembly. Le compilateur C# applique
automatiquement cette règle sans qu’il soit nécessaire d’appliquer l’attribut CLSCompliantAttribute.
Le type d’une propriété correspond au type de retour de la méthode getter de la propriété et du dernier
argument de la méthode setter. Ces types doivent être conformes à CLS et les arguments ne peuvent pas
être assignés à la propriété par référence (autrement dit, ils ne peuvent pas être des pointeurs managés).
Si une propriété possède une méthode getter et une méthode setter, elles doivent être toutes les deux
virtuelles, statiques ou être toutes les deux des instances. Le compilateur C# applique automatiquement
cette règle par le biais de la syntaxe de définition de propriété.
Événements
Un événement est défini par son nom et son type. Le type d'événement est un délégué utilisé pour indiquer
l'événement. Par exemple, l'événement DbConnection.StateChange est de type StateChangeEventHandler . Outre
l'événement lui-même, trois méthodes avec des noms basés sur le nom de l'événement fournissent une
implémentation de l'événement et sont marquées comme SpecialName dans les métadonnées de l'assembly :
Une méthode pour ajouter un gestionnaire d’événements, appelée add EventName. Par exemple, la
méthode d'abonnement aux événements pour l'événement DbConnection.StateChange est nommée
add_StateChange .

Une méthode pour supprimer un gestionnaire d’événements, appelée remove EventName. Par exemple, la
méthode de suppression pour l'événement DbConnection.StateChange est nommée remove_StateChange .
Une méthode pour indiquer que l’événement s’est produit, nommée raise _NomÉvénement.

NOTE
La plupart des règles de la spécification CLS concernant les événements sont implémentées par les compilateurs de langage
et sont transparentes aux développeurs de composants.

L'accessibilité des méthodes permettant d'ajouter, de supprimer et de déclencher un événement doit être identique.
Les méthodes doivent également toutes être statiques, instanciées ou virtuelles. Les méthodes d'ajout et de
suppression d'un événement ont un paramètre dont le type est le type du délégué d'événement. Les méthodes
d'ajout et de suppression doivent être toutes les deux présentes ou absentes.
L'exemple suivant définit une classe conforme à CLS nommée Temperature qui déclenche un événement
TemperatureChanged si le changement de température entre deux lectures est égal ou supérieur à une valeur seuil.
La classe Temperature définit explicitement une méthode raise_TemperatureChanged afin de pouvoir exécuter de
manière sélective les gestionnaires d'événements.

using System;
using System.Collections;
using System.Collections.Generic;

[assembly: CLSCompliant(true)]

public class TemperatureChangedEventArgs : EventArgs


{
private Decimal originalTemp;
private Decimal newTemp;
private DateTimeOffset when;

public TemperatureChangedEventArgs(Decimal original, Decimal @new, DateTimeOffset time)


{
originalTemp = original;
newTemp = @new;
when = time;
}

public Decimal OldTemperature


{
get { return originalTemp; }
}

public Decimal CurrentTemperature


{
get { return newTemp; }
}

public DateTimeOffset Time


{
get { return when; }
}
}

public delegate void TemperatureChanged(Object sender, TemperatureChangedEventArgs e);

public class Temperature


{
private struct TemperatureInfo
{
public Decimal Temperature;
public DateTimeOffset Recorded;
}

public event TemperatureChanged TemperatureChanged;

private Decimal previous;


private Decimal current;
private Decimal tolerance;
private List<TemperatureInfo> tis = new List<TemperatureInfo>();

public Temperature(Decimal temperature, Decimal tolerance)


{
current = temperature;
TemperatureInfo ti = new TemperatureInfo();
ti.Temperature = temperature;
tis.Add(ti);
ti.Recorded = DateTimeOffset.UtcNow;
this.tolerance = tolerance;
}

public Decimal CurrentTemperature


{
get { return current; }
set {
TemperatureInfo ti = new TemperatureInfo();
ti.Temperature = value;
ti.Recorded = DateTimeOffset.UtcNow;
previous = current;
current = value;
if (Math.Abs(current - previous) >= tolerance)
raise_TemperatureChanged(new TemperatureChangedEventArgs(previous, current, ti.Recorded));
}
}

public void raise_TemperatureChanged(TemperatureChangedEventArgs eventArgs)


{
if (TemperatureChanged == null)
return;

foreach (TemperatureChanged d in TemperatureChanged.GetInvocationList()) {


if (d.Method.Name.Contains("Duplicate"))
Console.WriteLine("Duplicate event handler; event handler not executed.");
else
d.Invoke(this, eventArgs);
}
}
}

public class Example


{
public Temperature temp;

public static void Main()


{
Example ex = new Example();
}

public Example()
{
temp = new Temperature(65, 3);
temp.TemperatureChanged += this.TemperatureNotification;
RecordTemperatures();
Example ex = new Example(temp);
ex.RecordTemperatures();
}

public Example(Temperature t)
{
temp = t;
RecordTemperatures();
}

public void RecordTemperatures()


{
temp.TemperatureChanged += this.DuplicateTemperatureNotification;
temp.CurrentTemperature = 66;
temp.CurrentTemperature = 63;
}

internal void TemperatureNotification(Object sender, TemperatureChangedEventArgs e)


{
Console.WriteLine("Notification 1: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature);
}

public void DuplicateTemperatureNotification(Object sender, TemperatureChangedEventArgs e)


{
Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature);
}
}

Imports System.Collections
Imports System.Collections.Generic

<Assembly: CLSCompliant(True)>

Public Class TemperatureChangedEventArgs : Inherits EventArgs


Private originalTemp As Decimal
Private newTemp As Decimal
Private [when] As DateTimeOffset

Public Sub New(original As Decimal, [new] As Decimal, [time] As DateTimeOffset)


originalTemp = original
newTemp = [new]
[when] = [time]
End Sub

Public ReadOnly Property OldTemperature As Decimal


Get
Return originalTemp
End Get
End Property

Public ReadOnly Property CurrentTemperature As Decimal


Get
Return newTemp
End Get
End Property

Public ReadOnly Property [Time] As DateTimeOffset


Get
Return [when]
End Get
End Property
End Class

Public Delegate Sub TemperatureChanged(sender As Object, e As TemperatureChangedEventArgs)

Public Class Temperature


Private Structure TemperatureInfo
Dim Temperature As Decimal
Dim Recorded As DateTimeOffset
End Structure

Public Event TemperatureChanged As TemperatureChanged

Private previous As Decimal


Private current As Decimal
Private tolerance As Decimal
Private tis As New List(Of TemperatureInfo)

Public Sub New(temperature As Decimal, tolerance As Decimal)


current = temperature
Dim ti As New TemperatureInfo()
ti.Temperature = temperature
ti.Recorded = DateTimeOffset.UtcNow
tis.Add(ti)
Me.tolerance = tolerance
End Sub

Public Property CurrentTemperature As Decimal


Get
Return current
End Get
Set
Dim ti As New TemperatureInfo
ti.Temperature = value
ti.Recorded = DateTimeOffset.UtcNow
previous = current
current = value
If Math.Abs(current - previous) >= tolerance Then
raise_TemperatureChanged(New TemperatureChangedEventArgs(previous, current, ti.Recorded))
End If
End Set
End Property

Public Sub raise_TemperatureChanged(eventArgs As TemperatureChangedEventArgs)


If TemperatureChangedEvent Is Nothing Then Exit Sub

Dim ListenerList() As System.Delegate = TemperatureChangedEvent.GetInvocationList()


For Each d As TemperatureChanged In TemperatureChangedEvent.GetInvocationList()
If d.Method.Name.Contains("Duplicate") Then
Console.WriteLine("Duplicate event handler; event handler not executed.")
Else
d.Invoke(Me, eventArgs)
End If
Next
End Sub
End Class

Public Class Example


Public WithEvents temp As Temperature

Public Shared Sub Main()


Dim ex As New Example()
End Sub

Public Sub New()


temp = New Temperature(65, 3)
RecordTemperatures()
Dim ex As New Example(temp)
ex.RecordTemperatures()
End Sub

Public Sub New(t As Temperature)


temp = t
RecordTemperatures()
End Sub

Public Sub RecordTemperatures()


temp.CurrentTemperature = 66
temp.CurrentTemperature = 63

End Sub

Friend Shared Sub TemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _


Handles temp.TemperatureChanged
Console.WriteLine("Notification 1: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature)
End Sub

Friend Shared Sub DuplicateTemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _


Handles temp.TemperatureChanged
Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature)
End Sub
End Class

Surcharges
La spécification CLS impose les conditions suivantes aux membres surchargés :
Les membres peuvent être surchargés selon le nombre de paramètres et le type d'un paramètre. La
convention d’appel, le type de retour, les modificateurs personnalisés appliqués à la méthode ou à son
paramètre et le fait que les paramètres soient transmis par valeur ou par référence ne sont pas pris en
considération lors de la différenciation des surcharges. Pour obtenir un exemple, consultez le code de
l’exigence indiquant que les noms doivent être uniques dans une portée, dans la section Conventions
d’affectation de noms.
Seules les propriétés et les méthodes peuvent être surchargées. Les champs et les événements ne peuvent
pas être surchargés.
Les méthodes génériques peuvent être surchargées selon le nombre de leurs paramètres génériques.

NOTE
Les opérateurs op_Explicit et op_Implicit sont des exceptions à la règle indiquant que la valeur de retour n'est pas
considérée comme faisant partie d'une signature de méthode pour la résolution de la surcharge. Ces deux opérateurs
peuvent être surchargés selon leurs paramètres et leur valeur de retour.

Exceptions
Les objets d’exception doivent dériver de System.Exception ou d’un autre type dérivé de System.Exception .
L'exemple suivant illustre l'erreur de compilateur obtenue lorsqu'une classe personnalisée nommée ErrorClass est
utilisée pour la gestion des exceptions.

using System;

[assembly: CLSCompliant(true)]

public class ErrorClass


{
string msg;

public ErrorClass(string errorMessage)


{
msg = errorMessage;
}

public string Message


{
get { return msg; }
}
}

public static class StringUtilities


{
public static string[] SplitString(this string value, int index)
{
if (index < 0 | index > value.Length) {
ErrorClass badIndex = new ErrorClass("The index is not within the string.");
throw badIndex;
}
string[] retVal = { value.Substring(0, index - 1),
value.Substring(index) };
return retVal;
}
}
// Compilation produces a compiler error like the following:
// Exceptions1.cs(26,16): error CS0155: The type caught or thrown must be derived from
// System.Exception
Imports System.Runtime.CompilerServices

<Assembly: CLSCompliant(True)>

Public Class ErrorClass


Dim msg As String

Public Sub New(errorMessage As String)


msg = errorMessage
End Sub

Public ReadOnly Property Message As String


Get
Return msg
End Get
End Property
End Class

Public Module StringUtilities


<Extension()> Public Function SplitString(value As String, index As Integer) As String()
If index < 0 Or index > value.Length Then
Dim BadIndex As New ErrorClass("The index is not within the string.")
Throw BadIndex
End If
Dim retVal() As String = { value.Substring(0, index - 1),
value.Substring(index) }
Return retVal
End Function
End Module
' Compilation produces a compiler error like the following:
' Exceptions1.vb(27) : error BC30665: 'Throw' operand must derive from 'System.Exception'.
'
' Throw BadIndex
' ~~~~~~~~~~~~~~

Pour corriger cette erreur, la classe ErrorClass doit hériter de System.Exception . Par ailleurs, la propriété Message
doit être remplacée. L'exemple suivant corrige ces erreurs pour définir une classe ErrorClass conforme à CLS.
using System;

[assembly: CLSCompliant(true)]

public class ErrorClass : Exception


{
string msg;

public ErrorClass(string errorMessage)


{
msg = errorMessage;
}

public override string Message


{
get { return msg; }
}
}

public static class StringUtilities


{
public static string[] SplitString(this string value, int index)
{
if (index < 0 | index > value.Length) {
ErrorClass badIndex = new ErrorClass("The index is not within the string.");
throw badIndex;
}
string[] retVal = { value.Substring(0, index - 1),
value.Substring(index) };
return retVal;
}
}

Imports System.Runtime.CompilerServices

<Assembly: CLSCompliant(True)>

Public Class ErrorClass : Inherits Exception


Dim msg As String

Public Sub New(errorMessage As String)


msg = errorMessage
End Sub

Public Overrides ReadOnly Property Message As String


Get
Return msg
End Get
End Property
End Class

Public Module StringUtilities


<Extension()> Public Function SplitString(value As String, index As Integer) As String()
If index < 0 Or index > value.Length Then
Dim BadIndex As New ErrorClass("The index is not within the string.")
Throw BadIndex
End If
Dim retVal() As String = { value.Substring(0, index - 1),
value.Substring(index) }
Return retVal
End Function
End Module

Attributs
Dans les assemblys .NET Framework, les attributs personnalisés fournissent un mécanisme extensible pour stocker
des attributs personnalisés et récupérer les métadonnées concernant la programmation des objets, tels que les
assemblys, les types, les membres et les paramètres de méthode. Les attributs personnalisés doivent dériver de
System.Attribute ou d’un type dérivé de System.Attribute .
L'exemple suivant enfreint cette règle. Il définit une classe NumericAttribute qui ne dérive pas de System.Attribute
. Une erreur du compilateur se produit uniquement lorsque l’attribut non conforme CLS est appliqué, et non
lorsque la classe est définie.

using System;

[assembly: CLSCompliant(true)]

[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Struct)]
public class NumericAttribute
{
private bool _isNumeric;

public NumericAttribute(bool isNumeric)


{
_isNumeric = isNumeric;
}

public bool IsNumeric


{
get { return _isNumeric; }
}
}

[Numeric(true)] public struct UDouble


{
double Value;
}
// Compilation produces a compiler error like the following:
// Attribute1.cs(22,2): error CS0616: 'NumericAttribute' is not an attribute class
// Attribute1.cs(7,14): (Location of symbol related to previous error)

<Assembly: CLSCompliant(True)>

<AttributeUsageAttribute(AttributeTargets.Class Or AttributeTargets.Struct)> _
Public Class NumericAttribute
Private _isNumeric As Boolean

Public Sub New(isNumeric As Boolean)


_isNumeric = isNumeric
End Sub

Public ReadOnly Property IsNumeric As Boolean


Get
Return _isNumeric
End Get
End Property
End Class

<Numeric(True)> Public Structure UDouble


Dim Value As Double
End Structure
' Compilation produces a compiler error like the following:
' error BC31504: 'NumericAttribute' cannot be used as an attribute because it
' does not inherit from 'System.Attribute'.
'
' <Numeric(True)> Public Structure UDouble
' ~~~~~~~~~~~~~
Le constructeur ou les propriétés d'un attribut conforme à CLS peuvent exposer uniquement les types suivants :
Booléen
Poids
Char
Double
Int16
Int32
Int64
Unique
Chaîne
Type
Tout type d'énumération dont le type sous-jacent est Byte , Int16 , Int32 ou Int64 .

L’exemple suivant définit une classe DescriptionAttribute qui dérive de Attribute. Le constructeur de classe a un
paramètre de type Descriptor , la classe n'est donc pas conforme à CLS. Le compilateur C# émet un avertissement
mais se compile correctement.

using System;

[assembly:CLSCompliantAttribute(true)]

public enum DescriptorType { type, member };

public class Descriptor


{
public DescriptorType Type;
public String Description;
}

[AttributeUsage(AttributeTargets.All)]
public class DescriptionAttribute : Attribute
{
private Descriptor desc;

public DescriptionAttribute(Descriptor d)
{
desc = d;
}

public Descriptor Descriptor


{ get { return desc; } }
}
// Attempting to compile the example displays output like the following:
// warning CS3015: 'DescriptionAttribute' has no accessible
// constructors which use only CLS-compliant types
<Assembly:CLSCompliantAttribute(True)>

Public Enum DescriptorType As Integer


Type = 0
Member = 1
End Enum

Public Class Descriptor


Public Type As DescriptorType
Public Description As String
End Class

<AttributeUsage(AttributeTargets.All)> _
Public Class DescriptionAttribute : Inherits Attribute
Private desc As Descriptor

Public Sub New(d As Descriptor)


desc = d
End Sub

Public ReadOnly Property Descriptor As Descriptor


Get
Return desc
End Get
End Property
End Class

Attribut CLSCompliantAttribute
L’attribut CLSCompliantAttribute permet d’indiquer si un élément de programme est conforme à la spécification
CLS (Common Language Specification). Le constructeur CLSCompliantAttribute.CLSCompliantAttribute(Boolean)
inclut un seul paramètre obligatoire, isCompliant, qui indique si l’élément de programme est conforme CLS.
Au moment de la compilation, le compilateur détecte les éléments non conformes qui sont présumés conformes à
CLS et émet alors un avertissement. Le compilateur n'émet pas d'avertissements pour les types ou les membres
qui sont explicitement déclarés comme étant non conformes.
Les développeurs de composants peuvent utiliser l'attribut CLSCompliantAttribute de deux façons :
Pour définir les parties de l'interface publique exposées par un composant qui sont conformes à CLS et
celles qui ne sont pas conformes à CLS. Lorsque l'attribut est utilisé pour marquer des éléments de
programme particuliers comme étant conformes à CLS, son utilisation garantit que ces éléments sont
accessibles à partir de tous les langages et outils qui ciblent le .NET Framework.
Pour vérifier que l'interface publique de la bibliothèque de composants expose uniquement les éléments de
programme conformes à CLS. Si les éléments ne sont pas conformes à CLS, les compilateurs publieront
généralement un avertissement.

WARNING
Dans certains cas, les compilateurs de langages imposent les règles conformes à CLS que l'attribut CLSCompliantAttribute
soit utilisé ou non. Par exemple, la définition d’un membre *static dans une interface est une infraction à une règle CLS.
Toutefois, si vous définissez un membre *static dans une interface, le compilateur C# affiche un message d’erreur et ne
compile pas l’application.

L’attribut CLSCompliantAttributeest marqué avec un attribut AttributeUsageAttribute qui a la valeur


AttributeTargets.All . Cette valeur permet d'appliquer l'attribut CLSCompliantAttribute à un élément de
programme, notamment aux assemblys, modules, types (classes, structures, énumérations, interfaces et délégués),
membres de types (constructeurs, méthodes, propriétés, champs et événements), paramètres, paramètres
génériques et valeurs de retour. Toutefois, dans la pratique, vous devez appliquer l'attribut uniquement aux
assemblys, aux types et aux membres de types. Sinon, les compilateurs ignorent l'attribut et continuent à générer
des avertissements de compilateur lorsqu'ils rencontrent un paramètre non conforme, un paramètre générique ou
une valeur de retour dans l'interface publique de votre bibliothèque.
La valeur de l'attribut CLSCompliantAttribute est héritée par les éléments de programme contenus. Par exemple, si
un assembly est marqué comme étant conforme à CLS, ses types sont également conformes à CLS. Si un type est
marqué comme étant conforme à CLS, ses membres et types imbriqués seront également conformes à CLS.
Vous pouvez remplacer explicitement la conformité héritée en appliquant l'attribut CLSCompliantAttribute à un
élément de programme contenu. Par exemple, vous pouvez utiliser l’attribut CLSCompliantAttribute avec une valeur
isCompliant égale à false pour définir un type non conforme dans un assembly conforme, et avec une valeur
isCompliant égale à true pour définir un type conforme dans un assembly non conforme. Vous pouvez également
définir des membres non conformes dans un type conforme. Toutefois, un type non conforme ne peut pas avoir de
membres conformes, vous ne pouvez donc pas utiliser l’attribut avec une valeur isCompliant égale à true pour
remplacer l’héritage d’un type non conforme.
Lorsque vous développez des composants, vous devez toujours utiliser l'attribut CLSCompliantAttribute pour
indiquer si votre assembly, ses types et ses membres sont conformes à CLS.
Pour créer des composants conformes à CLS :
1. Utilisez CLSCompliantAttribute pour marquer votre assembly comme étant conforme à CLS.
2. Marquez les types exposés publiquement de l'assembly qui ne sont pas conformes à CLS comme non
conformes.
3. Marquez tous les membres exposés publiquement dans des types conformes à CLS comme non conformes.
4. Fournissez une alternative conforme à CLS pour les membres non conformes.
Si vous avez correctement marqué tous vos types et membres non conformes, le compilateur ne doit émettre
aucun avertissement de non-conformité. Toutefois, vous devez indiquer quels membres ne sont pas conformes à
CLS et répertorier leurs alternatives conformes à CLS dans votre documentation produit.
L'exemple suivant utilise l'attribut CLSCompliantAttribute pour définir un assembly conforme à CLS et un type,
CharacterUtilities , qui a deux membres non conformes à CLS. Les deux membres étant référencés avec l'attribut
CLSCompliant(false) , le compilateur ne génère aucun avertissement. La classe fournit également une alternative
conforme à CLS pour les deux méthodes. Normalement, nous ajouterions seulement deux surcharges à la méthode
ToUTF16 pour fournir des alternatives conformes à CLS. Toutefois, les méthodes ne pouvant pas être surchargées
selon la valeur de retour, les noms des méthodes conformes à CLS sont différents des noms des méthodes non
conformes.
using System;
using System.Text;

[assembly:CLSCompliant(true)]

public class CharacterUtilities


{
[CLSCompliant(false)] public static ushort ToUTF16(String s)
{
s = s.Normalize(NormalizationForm.FormC);
return Convert.ToUInt16(s[0]);
}

[CLSCompliant(false)] public static ushort ToUTF16(Char ch)


{
return Convert.ToUInt16(ch);
}

// CLS-compliant alternative for ToUTF16(String).


public static int ToUTF16CodeUnit(String s)
{
s = s.Normalize(NormalizationForm.FormC);
return (int) Convert.ToUInt16(s[0]);
}

// CLS-compliant alternative for ToUTF16(Char).


public static int ToUTF16CodeUnit(Char ch)
{
return Convert.ToInt32(ch);
}

public bool HasMultipleRepresentations(String s)


{
String s1 = s.Normalize(NormalizationForm.FormC);
return s.Equals(s1);
}

public int GetUnicodeCodePoint(Char ch)


{
if (Char.IsSurrogate(ch))
throw new ArgumentException("ch cannot be a high or low surrogate.");

return Char.ConvertToUtf32(ch.ToString(), 0);


}

public int GetUnicodeCodePoint(Char[] chars)


{
if (chars.Length > 2)
throw new ArgumentException("The array has too many characters.");

if (chars.Length == 2) {
if (! Char.IsSurrogatePair(chars[0], chars[1]))
throw new ArgumentException("The array must contain a low and a high surrogate.");
else
return Char.ConvertToUtf32(chars[0], chars[1]);
}
else {
return Char.ConvertToUtf32(chars.ToString(), 0);
}
}
}
Imports System.Text

<Assembly:CLSCompliant(True)>

Public Class CharacterUtilities


<CLSCompliant(False)> Public Shared Function ToUTF16(s As String) As UShort
s = s.Normalize(NormalizationForm.FormC)
Return Convert.ToUInt16(s(0))
End Function

<CLSCompliant(False)> Public Shared Function ToUTF16(ch As Char) As UShort


Return Convert.ToUInt16(ch)
End Function

' CLS-compliant alternative for ToUTF16(String).


Public Shared Function ToUTF16CodeUnit(s As String) As Integer
s = s.Normalize(NormalizationForm.FormC)
Return CInt(Convert.ToInt16(s(0)))
End Function

' CLS-compliant alternative for ToUTF16(Char).


Public Shared Function ToUTF16CodeUnit(ch As Char) As Integer
Return Convert.ToInt32(ch)
End Function

Public Function HasMultipleRepresentations(s As String) As Boolean


Dim s1 As String = s.Normalize(NormalizationForm.FormC)
Return s.Equals(s1)
End Function

Public Function GetUnicodeCodePoint(ch As Char) As Integer


If Char.IsSurrogate(ch) Then
Throw New ArgumentException("ch cannot be a high or low surrogate.")
End If
Return Char.ConvertToUtf32(ch.ToString(), 0)
End Function

Public Function GetUnicodeCodePoint(chars() As Char) As Integer


If chars.Length > 2 Then
Throw New ArgumentException("The array has too many characters.")
End If
If chars.Length = 2 Then
If Not Char.IsSurrogatePair(chars(0), chars(1)) Then
Throw New ArgumentException("The array must contain a low and a high surrogate.")
Else
Return Char.ConvertToUtf32(chars(0), chars(1))
End If
Else
Return Char.ConvertToUtf32(chars.ToString(), 0)
End If
End Function
End Class

Si vous développez une application plutôt qu'une bibliothèque (autrement dit, si vous n'exposez pas des types ou
des membres qui peuvent être utilisés par d'autres développeurs d'applications), la conformité CLS des éléments
de programme que votre application consomme n'a d'intérêt que si votre langage ne les prend pas en charge. Dans
ce cas, votre compilateur de langage générera une erreur lorsque vous essaierez d'utiliser un élément non
conforme à CLS.

Interopérabilité interlangage
L'indépendance du langage a plusieurs significations possibles. Une des significations implique la consommation
de façon transparente des types écrits dans un langage à partir d’une application écrite dans un autre langage. Une
deuxième signification, qui est le sujet de cet article, consiste à combiner du code écrit dans plusieurs langages
dans un même assembly .NET Framework.
L'exemple suivant illustre l'interopérabilité interlangage en créant une bibliothèque de classes nommée Utilities.dll
qui comprend deux classes, NumericLib et StringLib . La classe NumericLib est écrite en C# et la classe StringLib
est écrite en Visual Basic. Voici le code source de StringUtil.vb , qui comprend un seul membre, ToTitleCase , dans
sa classe StringLib .

Imports System.Collections.Generic
Imports System.Runtime.CompilerServices

Public Module StringLib


Private exclusions As List(Of String)

Sub New()
Dim words() As String = { "a", "an", "and", "of", "the" }
exclusions = New List(Of String)
exclusions.AddRange(words)
End Sub

<Extension()> _
Public Function ToTitleCase(title As String) As String
Dim words() As String = title.Split()
Dim result As String = String.Empty

For ctr As Integer = 0 To words.Length - 1


Dim word As String = words(ctr)
If ctr = 0 OrElse Not exclusions.Contains(word.ToLower()) Then
result += word.Substring(0, 1).ToUpper() + _
word.Substring(1).ToLower()
Else
result += word.ToLower()
End If
If ctr <= words.Length - 1 Then
result += " "
End If
Next
Return result
End Function
End Module

Voici le code source de NumberUtil.cs, qui définit une classe NumericLib qui a deux membres, IsEven et NearZero .
using System;

public static class NumericLib


{
public static bool IsEven(this IConvertible number)
{
if (number is Byte ||
number is SByte ||
number is Int16 ||
number is UInt16 ||
number is Int32 ||
number is UInt32 ||
number is Int64)
return ((long) number) % 2 == 0;
else if (number is UInt64)
return ((ulong) number) %2 == 0;
else
throw new NotSupportedException("IsEven called for a non-integer value.");
}

public static bool NearZero(double number)


{
return number < .00001;
}
}

Pour placer les deux classes dans un même assembly, vous devez les compiler dans des modules. Pour compiler le
fichier de code source Visual Basic dans un module, utilisez cette commande :

vbc /t:module StringUtil.vb

Pour compiler le fichier de code source C# dans un module, utilisez cette commande :

csc /t:module NumberUtil.cs

Vous utilisez ensuite l’outil de liaison (Link.exe) pour compiler les deux modules en un assembly :

link numberutil.netmodule stringutil.netmodule /out:UtilityLib.dll /dll

L'exemple suivant appelle ensuite les méthodes NumericLib.NearZero et StringLib.ToTitleCase . Le code Visual
Basic et le code C# peuvent accéder aux méthodes des deux classes.

using System;

public class Example


{
public static void Main()
{
Double dbl = 0.0 - Double.Epsilon;
Console.WriteLine(NumericLib.NearZero(dbl));

string s = "war and peace";


Console.WriteLine(s.ToTitleCase());
}
}
// The example displays the following output:
// True
// War and Peace
Module Example
Public Sub Main()
Dim dbl As Double = 0.0 - Double.Epsilon
Console.WriteLine(NumericLib.NearZero(dbl))

Dim s As String = "war and peace"


Console.WriteLine(s.ToTitleCase())
End Sub
End Module
' The example displays the following output:
' True
' War and Peace

Pour compiler le code Visual Basic, utilisez cette commande :

vbc example.vb /r:UtilityLib.dll

Pour compiler avec C#, changez le nom du compilateur vbc en csc et changez l’extension de fichier .vb en .cs :

csc example.cs /r:UtilityLib.dll


Indépendance du langage et composants
indépendants du langage
18/07/2020 • 108 minutes to read • Edit Online

Le .NET Framework est indépendant du langage. Cela signifie qu'en tant que développeur, vous pouvez
développer dans l'un des nombreux langages qui ciblent le .NET Framework, tels que C#, C++/CLI, Eiffel, F#,
IronPython, IronRuby, PowerBuilder, Visual Basic, Visual COBOL et Windows PowerShell. Vous pouvez accéder aux
types et aux membres des bibliothèques de classes développées pour le .NET Framework sans avoir à connaître le
langage dans lequel ils ont été initialement écrits ni à suivre les conventions du langage d'origine. Si vous
développez des composants, votre composant est accessible par toute application .NET Framework,
indépendamment de son langage.

NOTE
La première partie de cet article décrit la création de composants indépendants du langage, c'est-à-dire de composants qui
peuvent être utilisés par des applications écrites dans n'importe quel langage. Vous pouvez également créer un composant
ou une application unique à partir de code source écrit dans plusieurs langages. Consultez Interopérabilité multilingue dans
la deuxième partie de cet article.

Pour interagir entièrement avec d’autres objets écrits dans un langage quelconque, les objets ne doivent exposer
aux appelants que les fonctionnalités communes à tous les langages. Cet ensemble commun de fonctionnalités est
défini par la spécification CLS (Common Language Specification), qui est un ensemble de règles qui s’appliquent
aux assemblys générés. La spécification CLS (Common Language Specification) est définie dans la Partition I,
clauses 7 à 11 du document ECMA-335 Standard: Common Language Infrastructure.
Si votre composant est conforme à la spécification CLS, il est garanti d'être conforme CLS et accessible à partir du
code dans les assemblys écrits dans n'importe quel langage de programmation qui prend en charge la
spécification CLS. Vous pouvez déterminer si votre composant est conforme à la spécification CLS (Common
Language Specification) au moment de la compilation en appliquant l'attribut CLSCompliantAttribute à votre code
source. Pour plus d’informations, consultez Attribut CLSCompliantAttribute.
Contenu de cet article :
Règles de conformité CLS
Types et signatures de membres de types
Conventions d’affectation de noms
Conversion de type
Tableaux
Interfaces
Énumérations
Membres de types en général
Accessibilité des membres
Types et membres génériques
Constructeurs
Propriétés
Événements
Surcharges
Exceptions
Attributs
Attribut CLSCompliantAttribute
Interopérabilité interlangage

Règles de conformité CLS


Cette section présente les règles de création d'un composant conforme à CLS. Pour obtenir une liste complète des
règles, consultez la Partition I, clause 11 du document ECMA-335 Standard: Common Language Infrastructure.

NOTE
La spécification CLS (Common Language Specification) présente chaque règle de conformité CLS telle qu'elle s'applique aux
consommateurs (les développeurs qui accèdent par programme à un composant conforme CLS), aux infrastructures (les
développeurs qui utilisent un compilateur de langage pour créer des bibliothèques conformes CLS) et aux extendeurs (les
développeurs qui créent un outil tel qu'un compilateur de langage ou un analyseur de code qui crée des composants
conformes CLS). Cet article se concentre sur les règles applicables aux infrastructures. Notez, toutefois, qu'une partie des
règles qui s'appliquent aux extendeurs peut également s'appliquer aux assemblys créés à l'aide de Reflection.Emit.

Pour concevoir un composant indépendant du langage, vous n'avez qu'à appliquer les règles de conformité CLS à
l'interface publique de votre composant. Votre implémentation privée n'a pas besoin d'être conforme à la
spécification.

IMPORTANT
Les règles de conformité CLS s'appliquent uniquement à l'interface publique d'un composant, pas à son implémentation
privée.

Par exemple, les entiers non signés autres que Byte ne sont pas conformes à CLS. Étant donné que la classe
Person de l'exemple suivant expose une propriété Age de type UInt16, le code suivant affiche un avertissement
du compilateur.

using System;

[assembly: CLSCompliant(true)]

public class Person


{
private UInt16 personAge = 0;

public UInt16 Age


{ get { return personAge; } }
}
// The attempt to compile the example displays the following compiler warning:
// Public1.cs(10,18): warning CS3003: Type of 'Person.Age' is not CLS-compliant
<Assembly: CLSCompliant(True)>

Public Class Person


Private personAge As UInt16

Public ReadOnly Property Age As UInt16


Get
Return personAge
End Get
End Property
End Class
' The attempt to compile the example displays the following compiler warning:
' Public1.vb(9) : warning BC40027: Return type of function 'Age' is not CLS-compliant.
'
' Public ReadOnly Property Age As UInt16
' ~~~

Vous pouvez rendre la classe Person conforme à CLS en remplaçant le type Age de la propriété UInt16 par Int16,
qui est un entier signé 16 bits conforme à CLS. Il n'est pas nécessaire de modifier le type du champ privé
personAge .

using System;

[assembly: CLSCompliant(true)]

public class Person


{
private Int16 personAge = 0;

public Int16 Age


{ get { return personAge; } }
}

<Assembly: CLSCompliant(True)>

Public Class Person


Private personAge As UInt16

Public ReadOnly Property Age As Int16


Get
Return CType(personAge, Int16)
End Get
End Property
End Class

L'interface publique d'une bibliothèque inclut les éléments suivants :


Définitions des classes publiques.
Définitions des membres publics des classes publiques et définitions des membres accessibles aux classes
dérivées (à savoir, membres protégés).
Paramètres et types de retour des méthodes publiques des classes publiques, et paramètres et types de
retour des méthodes accessibles aux classes dérivées.
Les règles de conformité CLS sont répertoriées dans le tableau suivant. Le texte des règles est repris mot pour
mot du document ECMA-335 Standard: Common Language Infrastructure, qui est protégé par copyright 2012 par
Ecma International. Vous trouverez des informations plus détaillées sur ces règles dans les sections suivantes.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Accessibilité Accessibilité des membres L'accessibilité ne devra pas 10


être changée lors du
remplacement de méthodes
héritées, sauf en cas de
remplacement d'une
méthode héritée d'un
assembly différent avec
accessibilité
family-or-assembly .
Dans ce cas, le
remplacement devra
posséder l'accessibilité
family .

Accessibilité Accessibilité des membres La visibilité et l'accessibilité 12


des types et des membres
seront déterminées de telle
sorte que les types utilisés
dans la signature d'un
membre seront visibles et
accessibles lorsque le
membre proprement dit est
visible et accessible. Par
exemple, une méthode
publique qui est visible à
l'extérieur de son assembly
n'aura pas d'argument dont
le type est visible
uniquement dans l'assembly.
La visibilité et l'accessibilité
des types composant un
type générique instancié
utilisé dans la signature d'un
membre seront visibles et
accessibles lorsque le
membre proprement dit est
visible et accessible. Par
exemple, un type générique
instancié présent dans la
signature d'un membre qui
est visible à l'extérieur de
l'assembly n'aura pas
d'argument générique dont
le type est visible
uniquement dans l'assembly.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Tableaux Tableaux Les tableaux auront des 16


éléments avec un type
conforme à CLS, et toutes
les dimensions du tableau
auront des limites
inférieures égales à zéro.
Seul le fait qu'un élément
soit un tableau et le type
d'élément du tableau seront
requis pour distinguer les
surcharges. Lorsque la
surcharge est basée sur
deux ou plusieurs types de
tableau, les types d'élément
seront des types nommés.

Attributs Attributs Les attributs seront de type 41


System.Attribute ou d'un
type qui hérite de celui-ci.

Attributs Attributs La spécification CLS autorise 34


uniquement un sous-
ensemble des encodages
d'attributs personnalisés.
Les seuls types autorisés à
figurer dans ces encodages
seront (voir Partition IV) :
System.Type, System.String,
System.Char,
System.Boolean,
System.Byte, System.Int16,
System.Int32, System.Int64,
System.Single,
System.Double et tout type
d'énumération reposant sur
un type entier de base
conforme à CLS.

Attributs Attributs La spécification CLS 35


n'autorise pas les
modificateurs obligatoires
visibles publiquement (
modreq , voir Partition II),
mais autorise les
modificateurs facultatifs (
modopt , voir Partition II)
qu'elle ne comprend pas.

Constructeurs Constructeurs Un constructeur d'objet 21


appellera un constructeur
d'instance de sa classe de
base avant tout accès aux
données d'instance héritées.
(Cela ne s'applique pas aux
types de valeurs, qui n'ont
pas besoin de
constructeurs.)
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Constructeurs Constructeurs Un constructeur d'objet ne 22


sera pas appelé sauf dans le
cadre de la création d'un
objet, et un objet ne sera
pas initialisé deux fois.

Énumérations Énumérations Le type sous-jacent d'une 7


énumération devra être un
type d'entier CLS intégré, le
nom du champ devra être
« valeur__ » et ce champ
devra être marqué
RTSpecialName .

Énumérations Énumérations Il existe deux types distincts 8


d'énumérations, signalés par
la présence ou l'absence de
l'attribut personnalisé
System.FlagsAttribute (voir
Partition IV, Profiles and
Libraries). L'un représente
des valeurs entières
nommées ; l'autre
représente les indicateurs
binaires nommés qui
peuvent être combinés pour
générer une valeur sans
nom. La valeur d'une enum
n'est pas limitée aux valeurs
spécifiées.

Énumérations Énumérations Les champs static littéraux 9


d’une énumération auront le
type de l’énumération elle-
même.

Événements Événements Les méthodes qui 29


implémentent un
événement doivent être
marquées SpecialName
dans les métadonnées.

Événements Événements L’accessibilité d’un 30


événement et de ses
accesseurs sera identique.

Événements Événements Les méthodes add et 31


remove d'un événement
devront toutes les deux être
présentes ou absentes.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Événements Événements Les méthodes add et 32


remove d'un événement
devront chacune accepter
un paramètre dont le type
définit le type de
l'événement et qui sera
dérivé de System.Delegate.

Événements Événements Les événements adhéreront 33


à un modèle d’attribution de
nom spécifique. L'attribut
SpecialName dont il est
question dans la règle 29 de
la spécification CLS sera
ignoré dans les
comparaisons de noms
appropriées et respectera les
règles d'identificateur.

Exceptions Exceptions Les objets levés seront de 40


type System.Exception ou
d'un type qui hérite de
celui-ci. Néanmoins, les
méthodes conformes à CLS
ne sont pas requises pour
bloquer la propagation
d'autres types d'exceptions.

Général Conformité CLS : les règles Les règles CLS s'appliquent 1


uniquement aux éléments
d'un type qui sont
accessibles ou visibles en
dehors de l'assembly de
définition.

Général Conformité CLS : les règles Les membres de types non 2


conformes à CLS ne seront
pas marqués comme
conformes à CLS.

Génériques Types et membres Les types imbriqués devront 42


génériques posséder au moins autant
de paramètres génériques
que le type englobant. Les
paramètres génériques d'un
type imbriqué ont la même
position que les paramètres
génériques du type
englobant correspondant.

Génériques Types et membres Le nom d’un type générique 43


génériques devra encoder le nombre de
paramètres de type déclarés
sur le type non imbriqué ou
récemment introduits dans
le type s’il est imbriqué,
selon les règles définies ci-
dessus.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Génériques Types et membres Un type générique devra 4444


génériques redéclarer les contraintes
suffisantes afin de garantir
que les contraintes sur le
type de base ou les
interfaces seraient satisfaites
par les contraintes de type
générique.

Génériques Types et membres Les types utilisés comme 45


génériques contraintes sur les
paramètres génériques
devront eux-mêmes être
conformes à CLS.

Génériques Types et membres La visibilité et l'accessibilité 46


génériques des membres (y compris des
types imbriqués) d'un type
générique instancié devront
être définies dans
l'instanciation spécifique
plutôt que dans la
déclaration générale du type
générique. Sachant cela, les
règles de visibilité et
d'accessibilité de la règle 12
de la spécification CLS
s'appliquent toujours.

Génériques Types et membres À chaque méthode 47


génériques générique abstraite ou
virtuelle doit correspondre
une implémentation
concrète (non abstraite) par
défaut.

Interfaces Interfaces Les interfaces conformes à 18


CLS ne devront pas
nécessiter la définition de
méthodes non conformes à
CLS pour pouvoir les
implémenter.

Interfaces Interfaces Les interfaces conformes à 19


CLS ne devront pas définir
de méthodes statiques ni de
champs.

Membres Membres de types en Les méthodes et les champs 36


général static globaux ne sont pas
conformes CLS.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Membres -- La valeur d'un champ 13


statique littéral est spécifiée
via l'utilisation de
métadonnées d'initialisation
de champ. Un littéral
conforme à CLS doit avoir
une valeur spécifiée dans les
métadonnées d'initialisation
de champ qui est
exactement du même type
que le littéral (ou du type
sous-jacent, si ce littéral est
une enum ).

Membres Membres de types en La contrainte vararg ne fait 15


général pas partie de la
spécification CLS, et la seule
convention d’appel prise en
charge par la
spécification CLS est la
convention d’appel managée
standard.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Conventions d’affectation de Conventions d’affectation de Les assemblys doivent 4


noms noms suivre l’Annexe 7 du Rapport
technique 15 du standard
Unicode 3.0 régissant
l’ensemble des caractères
autorisés au début et dans
les identificateurs. Cette
annexe est disponible en
ligne à l’adresse
https://www.unicode.org/uni
code/reports/tr15/tr15-
18.html. Les identificateurs
doivent être dans un format
canonique défini par la
forme C de normalisation
Unicode. Dans le cadre de la
spécification CLS, deux
identificateurs sont les
mêmes si leurs mappages
en minuscules (comme
spécifié par les mappages en
minuscules un-à-un
insensibles aux paramètres
régionaux Unicode) sont
identiques. Autrement dit,
pour que deux
identificateurs soient
considérés comme différents
dans le cadre de la
spécification CLS, ils doivent
être différenciés par d’autres
éléments que leur casse.
Toutefois, pour remplacer
une définition héritée,
l’infrastructure CLI nécessite
l’utilisation de l’encodage
exact de la déclaration
d’origine.

Surcharge Conventions d’affectation de Tous les noms introduits 5


noms dans une portée conforme
CLS doivent être distincts,
indépendamment de leur
type, sauf quand les noms
sont identiques et résolus
par surcharge. Par exemple,
alors que CTS autorise un
type à utiliser le même nom
pour une méthode et un
champ, CLS ne l'autorise
pas.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Surcharge Conventions d’affectation de Les champs et les types 6


noms imbriqués seront distincts
par comparaison
d'identificateurs seule,
même si CTS autorise la
distinction de signatures
différentes. Les méthodes,
les propriétés et les
événements qui portent le
même nom (par
comparaison
d’identificateurs) doivent
différer par d’autres
éléments que le seul type de
retour, sauf dans les cas
spécifiés dans la règle 39 de
la spécification CLS.

Surcharge Surcharges Seules les propriétés et les 37


méthodes peuvent être
surchargées.

Surcharge Surcharges Les propriétés et les 38


méthodes peuvent être
surchargées en fonction du
nombre et des types de
leurs paramètres
uniquement, à l'exception
des opérateurs de
conversion nommés
op_Implicit et
op_Explicit , qui peuvent
également être surchargés
selon leur type de retour.

Surcharge -- Si deux ou plusieurs 48


méthodes conformes CLS
déclarées dans un type ont
le même nom et, pour un
jeu spécifique
d’instanciations de types,
ont le même paramètre et
les mêmes types de retour,
alors toutes ces méthodes
sont sémantiquement
équivalentes à ces
instanciations de type.

Types Types et signatures de System.Object est conforme 23


membres de types à CLS. Toute autre classe
conforme à CLS héritera
d'une classe conforme à
CLS.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Propriétés Propriétés Les méthodes qui 24


implémentent les méthodes
getter et setter d’une
propriété doivent être
marquées SpecialName
dans les métadonnées.

Propriétés Propriétés Les accesseurs d’une 26


propriété devront tous être
statiques, virtuels ou être
des instances.

Propriétés Propriétés Le type d’une propriété 27


devra correspondre au type
de retour de la méthode
getter et au type du dernier
argument de la méthode
setter. Les types des
paramètres de la propriété
devront correspondre aux
types des paramètres de la
méthode getter et aux types
de tous les paramètres de la
méthode setter, sauf le
dernier. Tous ces types
devront être conformes à
CLS et ne pas être des
pointeurs managés (à savoir,
ils ne doivent pas être
passés par référence).

Propriétés Propriétés Les propriétés adhéreront à 28


un modèle d’attribution de
nom spécifique. L'attribut
SpecialName dont il est
question dans la règle 24 de
la spécification CLS sera
ignoré dans les
comparaisons de noms
appropriées et respectera les
règles d'identificateur. Une
propriété aura une méthode
getter, une méthode setter
ou les deux.

Conversion de type Conversion de type Si op_Implicit ou 39


op_Explicit est fourni, un
autre moyen sera utilisé
pour fournir la contrainte.

Types Types et signatures de Les types de valeurs 3


membres de types encadrés ne sont pas
conformes à CLS.
C AT EGO RY C O N SULT EZ RÈGL E N UM ÉRO DE RÈGL E

Types Types et signatures de Tous les types apparaissant 11


membres de types dans une signature devront
être conformes à CLS. Tous
les types composant un
type générique instancié
devront être conformes à
CLS.

Types Types et signatures de Les références typées ne 14


membres de types sont pas conformes CLS.

Types Types et signatures de Les types de pointeurs non 17


membres de types managés ne sont pas
conformes à CLS.

Types Types et signatures de Les classes, les types de 20


membres de types valeurs et les interfaces
conformes à CLS ne
nécessiteront pas
l'implémentation de
membres non conformes à
CLS.

Types et signatures de membres de types


Le type System.Object est conforme à CLS et correspond au type de base de tous les types d'objets du système de
types du .Net Framework. Dans le .NET Framework, l'héritage est implicite (par exemple, la classe String hérite
implicitement de la classe Object ) ou explicite (par exemple, la classe CultureNotFoundException hérite
explicitement de la classe ArgumentException, qui hérite explicitement de la classe SystemException, qui hérite
explicitement de la classe Exception). Pour qu'un type dérivé soit conforme à CLS, son type de base doit également
être conforme à CLS.
L'exemple suivant montre un type dérivé dont le type de base n'est pas conforme à CLS. Il définit une classe
Counter de base qui utilise un entier 32 bits non signé en tant que compteur. La classe fournissant une
fonctionnalité de compteur en encapsulant un entier non signé, elle est marquée comme non conforme à CLS. Par
conséquent, une classe dérivée, NonZeroCounter , n'est pas non plus conforme à CLS.
using System;

[assembly: CLSCompliant(true)]

[CLSCompliant(false)]
public class Counter
{
UInt32 ctr;

public Counter()
{
ctr = 0;
}

protected Counter(UInt32 ctr)


{
this.ctr = ctr;
}

public override string ToString()


{
return String.Format("{0}). ", ctr);
}

public UInt32 Value


{
get { return ctr; }
}

public void Increment()


{
ctr += (uint) 1;
}
}

public class NonZeroCounter : Counter


{
public NonZeroCounter(int startIndex) : this((uint) startIndex)
{
}

private NonZeroCounter(UInt32 startIndex) : base(startIndex)


{
}
}
// Compilation produces a compiler warning like the following:
// Type3.cs(37,14): warning CS3009: 'NonZeroCounter': base type 'Counter' is not
// CLS-compliant
// Type3.cs(7,14): (Location of symbol related to previous warning)
<Assembly: CLSCompliant(True)>

<CLSCompliant(False)> _
Public Class Counter
Dim ctr As UInt32

Public Sub New


ctr = 0
End Sub

Protected Sub New(ctr As UInt32)


ctr = ctr
End Sub

Public Overrides Function ToString() As String


Return String.Format("{0}). ", ctr)
End Function

Public ReadOnly Property Value As UInt32


Get
Return ctr
End Get
End Property

Public Sub Increment()


ctr += CType(1, UInt32)
End Sub
End Class

Public Class NonZeroCounter : Inherits Counter


Public Sub New(startIndex As Integer)
MyClass.New(CType(startIndex, UInt32))
End Sub

Private Sub New(startIndex As UInt32)


MyBase.New(CType(startIndex, UInt32))
End Sub
End Class
' Compilation produces a compiler warning like the following:
' Type3.vb(34) : warning BC40026: 'NonZeroCounter' is not CLS-compliant
' because it derives from 'Counter', which is not CLS-compliant.
'
' Public Class NonZeroCounter : Inherits Counter
' ~~~~~~~~~~~~~~

Tous les types qui apparaissent dans les signatures de membres, notamment le type de retour d’une méthode ou
un type de propriété, doivent être conformes à CLS. En outre, pour les types génériques :
Tous les types qui composent un type générique instancié doivent être conformes à CLS.
Tous les types utilisés comme contraintes sur des paramètres génériques doivent eux-mêmes être
conformes à CLS.
Le système de type commun (CTS, Common Type System) du .NET Framework inclut un certain nombre de types
intégrés pris en charge directement par le Common Langage Runtime et qui sont spécialement encodés dans les
métadonnées d'un assembly. Parmi les types intrinsèques, les types répertoriés dans le tableau suivant sont
conformes à CLS.

T Y P E C O N F O RM E À C L S DESC RIP T IO N

Byte Entier 8 bits non signé


T Y P E C O N F O RM E À C L S DESC RIP T IO N

Int16 Entier 16 bits signé

Int32 Entier 32 bits signé

Int64 Entier 64 bits signé

Single Valeur à virgule flottante simple précision

Double Valeur à virgule flottante double précision

Boolean Type de valeur true ou false

Char Unité de code encodée en UTF-16

Decimal Nombre décimal à virgule fixe

IntPtr Pointeur ou handle d'une taille définie par la plateforme

String Collection de zéro, un ou plusieurs objets Char

Les types intrinsèques répertoriés dans le tableau suivant ne sont pas conformes à CLS.

T Y P E N O N C O N F O RM E DESC RIP T IO N A LT ERN AT IVE À L A C O N F O RM IT É C L S

SByte Type de données entier signé 8 bits Int16

TypedReference Pointeur vers un objet et son type au None


moment de l'exécution

UInt16 Entier 16 bits non signé Int32

UInt32 Entier non signé 32 bits Int64

UInt64 Entier non signé 64 bits Int64 (peut dépasser la capacité),


BigInteger ou Double

UIntPtr Pointeur ou handle non signé IntPtr

La bibliothèque de classes du .NET Framework ou toute autre bibliothèque de classes peut inclure d'autres types
non conformes à CLS. Par exemple :
Types de valeurs encadrés. L'exemple C# suivant crée une classe qui a une propriété publique de type
int* nommée Value . Comme int* est un type de valeur encadré, le compilateur le signale comme non
conforme à CLS.
using System;

[assembly:CLSCompliant(true)]

public unsafe class TestClass


{
private int* val;

public TestClass(int number)


{
val = (int*) number;
}

public int* Value {


get { return val; }
}
}
// The compiler generates the following output when compiling this example:
// warning CS3003: Type of 'TestClass.Value' is not CLS-compliant

Références typées, qui sont des constructions spéciales qui contiennent une référence à un objet et une
référence à un type. Les références typées sont représentées dans le .NET Framework par la classe
TypedReference.
Si un type n'est pas conforme à CLS, vous devez lui appliquer l'attribut CLSCompliantAttribute avec une valeur
isCompliant de false . Pour plus d’informations, consultez la section attribut CLSCompliantAttribute .

L'exemple suivant illustre le problème de conformité CLS dans une signature de méthode et dans une
instanciation de type générique. Il définit une classe InvoiceItem avec une propriété de type UInt32, une propriété
de type Nullable(Of UInt32) et un constructeur avec les paramètres de type UInt32 et Nullable(Of UInt32) . Vous
obtenez quatre avertissements du compilateur lorsque vous essayez de compiler cet exemple.
using System;

[assembly: CLSCompliant(true)]

public class InvoiceItem


{
private uint invId = 0;
private uint itemId = 0;
private Nullable<uint> qty;

public InvoiceItem(uint sku, Nullable<uint> quantity)


{
itemId = sku;
qty = quantity;
}

public Nullable<uint> Quantity


{
get { return qty; }
set { qty = value; }
}

public uint InvoiceId


{
get { return invId; }
set { invId = value; }
}
}
// The attempt to compile the example displays the following output:
// Type1.cs(13,23): warning CS3001: Argument type 'uint' is not CLS-compliant
// Type1.cs(13,33): warning CS3001: Argument type 'uint?' is not CLS-compliant
// Type1.cs(19,26): warning CS3003: Type of 'InvoiceItem.Quantity' is not CLS-compliant
// Type1.cs(25,16): warning CS3003: Type of 'InvoiceItem.InvoiceId' is not CLS-compliant
<Assembly: CLSCompliant(True)>

Public Class InvoiceItem

Private invId As UInteger = 0


Private itemId As UInteger = 0
Private qty AS Nullable(Of UInteger)

Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))


itemId = sku
qty = quantity
End Sub

Public Property Quantity As Nullable(Of UInteger)


Get
Return qty
End Get
Set
qty = value
End Set
End Property

Public Property InvoiceId As UInteger


Get
Return invId
End Get
Set
invId = value
End Set
End Property
End Class
' The attempt to compile the example displays output similar to the following:
' Type1.vb(13) : warning BC40028: Type of parameter 'sku' is not CLS-compliant.
'
' Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
' ~~~
' Type1.vb(13) : warning BC40041: Type 'UInteger' is not CLS-compliant.
'
' Public Sub New(sku As UInteger, quantity As Nullable(Of UInteger))
' ~~~~~~~~
' Type1.vb(18) : warning BC40041: Type 'UInteger' is not CLS-compliant.
'
' Public Property Quantity As Nullable(Of UInteger)
' ~~~~~~~~
' Type1.vb(27) : warning BC40027: Return type of function 'InvoiceId' is not CLS-compliant.
'
' Public Property InvoiceId As UInteger
' ~~~~~~~~~

Pour supprimer les avertissements du compilateur, remplacez les types non conforme à CLS de l'interface
publique InvoiceItem par des types conformes :
using System;

[assembly: CLSCompliant(true)]

public class InvoiceItem


{
private uint invId = 0;
private uint itemId = 0;
private Nullable<int> qty;

public InvoiceItem(int sku, Nullable<int> quantity)


{
if (sku <= 0)
throw new ArgumentOutOfRangeException("The item number is zero or negative.");
itemId = (uint) sku;

qty = quantity;
}

public Nullable<int> Quantity


{
get { return qty; }
set { qty = value; }
}

public int InvoiceId


{
get { return (int) invId; }
set {
if (value <= 0)
throw new ArgumentOutOfRangeException("The invoice number is zero or negative.");
invId = (uint) value; }
}
}
<Assembly: CLSCompliant(True)>

Public Class InvoiceItem

Private invId As UInteger = 0


Private itemId As UInteger = 0
Private qty AS Nullable(Of Integer)

Public Sub New(sku As Integer, quantity As Nullable(Of Integer))


If sku <= 0 Then
Throw New ArgumentOutOfRangeException("The item number is zero or negative.")
End If
itemId = CUInt(sku)
qty = quantity
End Sub

Public Property Quantity As Nullable(Of Integer)


Get
Return qty
End Get
Set
qty = value
End Set
End Property

Public Property InvoiceId As Integer


Get
Return CInt(invId)
End Get
Set
invId = CUInt(value)
End Set
End Property
End Class

Outre les types spécifiques répertoriés, certaines catégories de types ne sont pas conformes à CLS. Celles-ci
incluent les types de pointeurs non managés et les types de pointeurs de fonctions. L'exemple suivant génère un
avertissement du compilateur, car il utilise un pointeur vers un entier pour créer un tableau d'entiers.

using System;

[assembly: CLSCompliant(true)]

public class ArrayHelper


{
unsafe public static Array CreateInstance(Type type, int* ptr, int items)
{
Array arr = Array.CreateInstance(type, items);
int* addr = ptr;
for (int ctr = 0; ctr < items; ctr++) {
int value = *addr;
arr.SetValue(value, ctr);
addr++;
}
return arr;
}
}
// The attempt to compile this example displays the following output:
// UnmanagedPtr1.cs(8,57): warning CS3001: Argument type 'int*' is not CLS-compliant

Pour les classes abstraites conformes à CLS (autrement dit, les classes marquées comme abstract en C# ou
comme MustInherit en Visual Basic), tous les membres de la classe doivent également être conformes à CLS.
Conventions d’affectation de noms
Étant donné que certains langages de programmation ne respectent pas la casse, les identificateurs (tels que les
noms d'espaces de noms, de types et de membres) doivent se différencier par autre chose que la casse. Deux
identificateurs sont considérés comme équivalents si leurs mappages en minuscules sont identiques. L'exemple
C# suivant définit deux classes publiques : Person et person . Étant donné qu'elles ne diffèrent que par leur casse,
le compilateur C# les signale comme étant non conformes à CLS.

using System;

[assembly: CLSCompliant(true)]

public class Person : person


{
}

public class person


{
}
// Compilation produces a compiler warning like the following:
// Naming1.cs(11,14): warning CS3005: Identifier 'person' differing
// only in case is not CLS-compliant
// Naming1.cs(6,14): (Location of symbol related to previous warning)

Les identificateurs de langage de programmation, comme les noms d’espaces de noms, de types et de membres,
doivent être conformes au document Unicode Standard 3.0, Technical Report 15, Annex 7 (Norme Unicode 3.0,
Rapport technique 15, Annexe 7). Cela signifie que :
Le premier caractère d'un identificateur peut être une lettre majuscule, une lettre minuscule, une initiale
majuscule, une lettre de modificateur, une autre lettre ou un nombre sous forme de lettre, Unicode. Pour
plus d'informations sur les catégories de caractères Unicode, consultez l'énumération
System.Globalization.UnicodeCategory.
Les caractères suivants peuvent provenir de n'importe laquelle des catégories, comme le premier caractère,
et peuvent également inclure des marques de non-espacement, des nombres décimaux, des ponctuations
de connecteur et des codes de mise en forme.
Avant de comparer les identificateurs, vous devez éliminer par filtrage les codes de mise en forme et convertir les
identificateurs au formulaire de normalisation Unicode C, car un caractère unique peut être représenté par
plusieurs unités de code encodées en UTF-16. Les séquences de caractères qui produisent les mêmes unités de
code dans un formulaire de normalisation Unicode C ne sont pas conformes à CLS. L'exemple suivant définit une
propriété nommée Å , qui est constituée du caractère SYMBOLE ANGSTRÖM (U+212B), et une deuxième
propriété nommée Å , qui est constituée du caractère LETTRE MAJUSCULE LATINE A AVEC DIACRITIQUE ROND
EN CHEF (U+00C5). Les compilateurs C# et Visual Basic marquent le code source comme non conforme à CLS.
public class Size
{
private double a1;
private double a2;

public double Å
{
get { return a1; }
set { a1 = value; }
}

public double Å
{
get { return a2; }
set { a2 = value; }
}
}
// Compilation produces a compiler warning like the following:
// Naming2a.cs(16,18): warning CS3005: Identifier 'Size.Å' differing only in case is not
// CLS-compliant
// Naming2a.cs(10,18): (Location of symbol related to previous warning)
// Naming2a.cs(18,8): warning CS3005: Identifier 'Size.Å.get' differing only in case is not
// CLS-compliant
// Naming2a.cs(12,8): (Location of symbol related to previous warning)
// Naming2a.cs(19,8): warning CS3005: Identifier 'Size.Å.set' differing only in case is not
// CLS-compliant
// Naming2a.cs(13,8): (Location of symbol related to previous warning)

<Assembly: CLSCompliant(True)>
Public Class Size
Private a1 As Double
Private a2 As Double

Public Property Å As Double


Get
Return a1
End Get
Set
a1 = value
End Set
End Property

Public Property Å As Double


Get
Return a2
End Get
Set
a2 = value
End Set
End Property
End Class
' Compilation produces a compiler warning like the following:
' Naming1.vb(9) : error BC30269: 'Public Property Å As Double' has multiple definitions
' with identical signatures.
'
' Public Property Å As Double
' ~

Les noms de membres dans une portée donnée (tels que les espaces de noms dans un assembly, les types dans
un espace de noms ou les membres dans un type) doivent être uniques, à l'exception des noms résolus par la
surcharge. Cette spécification est plus stricte que celle du système de type commun, qui autorise plusieurs
membres d'une portée à avoir des noms identiques tant qu'il s'agit de types de membres différents (par exemple,
l'un est une méthode et l'autre est un champ). En particulier, pour les membres de types :
Les champs et les types imbriqués se distinguent par le nom uniquement.
Les méthodes, les propriétés et les événements qui portent le même nom doivent différer par autre chose
que le type de retour.
L'exemple suivant illustre la spécification selon laquelle les noms de membres doivent être uniques dans leur
portée. Il définit une classe nommée Converter qui inclut quatre membres nommés Conversion . Trois sont des
méthodes, le dernier est une propriété. La méthode qui inclut un paramètre Int64 est nommée de manière unique,
contrairement aux deux méthodes contenant un paramètre Int32, car la valeur de retour n'est pas considérée
comme faisant partie de la signature d'un membre. La propriété Conversion enfreint également cette
spécification, car les propriétés ne peuvent pas avoir le même nom que les méthodes surchargées.

using System;

[assembly: CLSCompliant(true)]

public class Converter


{
public double Conversion(int number)
{
return (double) number;
}

public float Conversion(int number)


{
return (float) number;
}

public double Conversion(long number)


{
return (double) number;
}

public bool Conversion


{
get { return true; }
}
}
// Compilation produces a compiler error like the following:
// Naming3.cs(13,17): error CS0111: Type 'Converter' already defines a member called
// 'Conversion' with the same parameter types
// Naming3.cs(8,18): (Location of symbol related to previous error)
// Naming3.cs(23,16): error CS0102: The type 'Converter' already contains a definition for
// 'Conversion'
// Naming3.cs(8,18): (Location of symbol related to previous error)
<Assembly: CLSCompliant(True)>

Public Class Converter


Public Function Conversion(number As Integer) As Double
Return CDbl(number)
End Function

Public Function Conversion(number As Integer) As Single


Return CSng(number)
End Function

Public Function Conversion(number As Long) As Double


Return CDbl(number)
End Function

Public ReadOnly Property Conversion As Boolean


Get
Return True
End Get
End Property
End Class
' Compilation produces a compiler error like the following:
' Naming3.vb(8) : error BC30301: 'Public Function Conversion(number As Integer) As Double'
' and 'Public Function Conversion(number As Integer) As Single' cannot
' overload each other because they differ only by return types.
'
' Public Function Conversion(number As Integer) As Double
' ~~~~~~~~~~
' Naming3.vb(20) : error BC30260: 'Conversion' is already declared as 'Public Function
' Conversion(number As Integer) As Single' in this class.
'
' Public ReadOnly Property Conversion As Boolean
' ~~~~~~~~~~

Les langages individuels incluent des mots clés uniques de sorte que les langages qui ciblent le Common Langage
Runtime doivent également fournir un mécanisme de référencement des identificateurs (tels que les noms de
types) qui coïncident avec les mots clés. Par exemple, case est un mot clé en C# et Visual Basic. Toutefois,
l'exemple Visual Basic suivant peut supprimer l'ambiguïté entre une classe nommée case et le mot clé case en
utilisant des accolades ouvrantes et fermantes. Sans cela, l'exemple générerait le message d'erreur : « Mot clé non
valide en tant qu'identificateur » et échouerait lors de la compilation.

Public Class [case]


Private _id As Guid
Private name As String

Public Sub New(name As String)


_id = Guid.NewGuid()
Me.name = name
End Sub

Public ReadOnly Property ClientName As String


Get
Return name
End Get
End Property
End Class

L'exemple C# suivant peut instancier la classe case en utilisant le symbole @ pour lever l'ambiguïté de
l'identificateur par rapport au mot clé de langage. Sans cela, le compilateur C# afficherait deux messages d'erreur :
« Type attendu » et « Terme d'expression non valide 'case' ».
using System;

public class Example


{
public static void Main()
{
@case c = new @case("John");
Console.WriteLine(c.ClientName);
}
}

Conversion de type
La spécification CLS (Common Language Specification) définit deux opérateurs de conversion :
op_Implicit , qui est utilisé pour les conversions étendues qui n'entraînent pas la perte de données ou de
précision. Par exemple, la structure Decimal inclut un opérateur op_Implicit surchargé pour convertir les
valeurs de types intégraux et les valeurs Char en valeurs Decimal.
op_Explicit , qui est utilisé pour les conversions restrictives qui peuvent entraîner la perte d'amplitude
(une valeur est convertie en une valeur qui entraîne une plus petite plage) ou de précision. Par exemple, la
structure Decimal inclut un opérateur op_Explicit surchargé pour convertir les valeurs Double et Single
en Decimal et convertir les valeurs Decimal en valeurs intégrales, Double, Single et Char.
Toutefois, tous les langages ne prennent pas en charge la surcharge d'opérateur ou la définition d'opérateurs
personnalisés. Si vous décidez d'implémenter ces opérateurs de conversion, vous devez également fournir une
autre façon d'effectuer la conversion. Nous vous recommandons de fournir From les méthodes xxx et To xxx .
L'exemple suivant définit les conversions implicites et explicites conformes à CLS. Il crée une classe UDouble qui
représente un nombre à virgule flottante double précision signé. Il s'applique aux conversions implicites de
UDouble à Double et aux conversions explicites de UDouble à Single, de Double à UDouble et de Single à UDouble
. Il définit également une méthode ToDouble comme une alternative à l'opérateur de conversion implicite et les
méthodes ToSingle , FromDouble et FromSingle comme alternatives aux opérateurs de conversion explicite.

using System;

public struct UDouble


{
private double number;

public UDouble(double value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

number = value;
}

public UDouble(float value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

number = value;
}

public static readonly UDouble MinValue = (UDouble) 0.0;


public static readonly UDouble MaxValue = (UDouble) Double.MaxValue;

public static explicit operator Double(UDouble value)


{
return value.number;
}

public static implicit operator Single(UDouble value)


{
if (value.number > (double) Single.MaxValue)
throw new InvalidCastException("A UDouble value is out of range of the Single type.");

return (float) value.number;


}

public static explicit operator UDouble(double value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

return new UDouble(value);


}

public static implicit operator UDouble(float value)


{
if (value < 0)
throw new InvalidCastException("A negative value cannot be converted to a UDouble.");

return new UDouble(value);


}

public static Double ToDouble(UDouble value)


{
return (Double) value;
}

public static float ToSingle(UDouble value)


{
return (float) value;
}

public static UDouble FromDouble(double value)


{
return new UDouble(value);
}

public static UDouble FromSingle(float value)


{
return new UDouble(value);
}
}
Public Structure UDouble
Private number As Double

Public Sub New(value As Double)


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
number = value
End Sub

Public Sub New(value As Single)


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
number = value
End Sub

Public Shared ReadOnly MinValue As UDouble = CType(0.0, UDouble)


Public Shared ReadOnly MaxValue As UDouble = Double.MaxValue

Public Shared Widening Operator CType(value As UDouble) As Double


Return value.number
End Operator

Public Shared Narrowing Operator CType(value As UDouble) As Single


If value.number > CDbl(Single.MaxValue) Then
Throw New InvalidCastException("A UDouble value is out of range of the Single type.")
End If
Return CSng(value.number)
End Operator

Public Shared Narrowing Operator CType(value As Double) As UDouble


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
Return New UDouble(value)
End Operator

Public Shared Narrowing Operator CType(value As Single) As UDouble


If value < 0 Then
Throw New InvalidCastException("A negative value cannot be converted to a UDouble.")
End If
Return New UDouble(value)
End Operator

Public Shared Function ToDouble(value As UDouble) As Double


Return CType(value, Double)
End Function

Public Shared Function ToSingle(value As UDouble) As Single


Return CType(value, Single)
End Function

Public Shared Function FromDouble(value As Double) As UDouble


Return New UDouble(value)
End Function

Public Shared Function FromSingle(value As Single) As UDouble


Return New UDouble(value)
End Function
End Structure

Tableaux
Les tableaux conformes à CLS respectent les règles suivantes :
Toutes les dimensions d'un tableau doivent avoir une limite inférieure égale à zéro. L'exemple suivant crée
un tableau non conforme à CLS avec une limite inférieure égale à un. Notez que, malgré la présence de
l'attribut CLSCompliantAttribute, le compilateur ne détecte pas que le tableau retourné par la méthode
Numbers.GetTenPrimes n'est pas conforme à CLS.

[assembly: CLSCompliant(true)]

public class Numbers


{
public static Array GetTenPrimes()
{
Array arr = Array.CreateInstance(typeof(Int32), new int[] {10}, new int[] {1});
arr.SetValue(1, 1);
arr.SetValue(2, 2);
arr.SetValue(3, 3);
arr.SetValue(5, 4);
arr.SetValue(7, 5);
arr.SetValue(11, 6);
arr.SetValue(13, 7);
arr.SetValue(17, 8);
arr.SetValue(19, 9);
arr.SetValue(23, 10);

return arr;
}
}

<Assembly: CLSCompliant(True)>

Public Class Numbers


Public Shared Function GetTenPrimes() As Array
Dim arr As Array = Array.CreateInstance(GetType(Int32), {10}, {1})
arr.SetValue(1, 1)
arr.SetValue(2, 2)
arr.SetValue(3, 3)
arr.SetValue(5, 4)
arr.SetValue(7, 5)
arr.SetValue(11, 6)
arr.SetValue(13, 7)
arr.SetValue(17, 8)
arr.SetValue(19, 9)
arr.SetValue(23, 10)

Return arr
End Function
End Class

Tous les éléments du tableau doivent se composer de types conformes à CLS. L'exemple suivant définit
deux méthodes qui retournent des tableaux non conformes à CLS. La première retourne un tableau de
valeurs UInt32. Le second retourne un tableau Object qui inclut les valeurs Int32 et UInt32. Bien que le
compilateur identifie le premier tableau comme non conforme en raison de son type UInt32, il ne parvient
pas à reconnaître que le second tableau inclut des éléments non conformes à CLS.
using System;

[assembly: CLSCompliant(true)]

public class Numbers


{
public static UInt32[] GetTenPrimes()
{
uint[] arr = { 1u, 2u, 3u, 5u, 7u, 11u, 13u, 17u, 19u };
return arr;
}

public static Object[] GetFivePrimes()


{
Object[] arr = { 1, 2, 3, 5u, 7u };
return arr;
}
}
// Compilation produces a compiler warning like the following:
// Array2.cs(8,27): warning CS3002: Return type of 'Numbers.GetTenPrimes()' is not
// CLS-compliant

<Assembly: CLSCompliant(True)>

Public Class Numbers


Public Shared Function GetTenPrimes() As UInt32()
Return {1ui, 2ui, 3ui, 5ui, 7ui, 11ui, 13ui, 17ui, 19ui}
End Function

Public Shared Function GetFivePrimes() As Object()


Dim arr() As Object = {1, 2, 3, 5ui, 7ui}
Return arr
End Function
End Class
' Compilation produces a compiler warning like the following:
' warning BC40027: Return type of function 'GetTenPrimes' is not CLS-compliant.
'
' Public Shared Function GetTenPrimes() As UInt32()
' ~~~~~~~~~~~~

La résolution de surcharge pour les méthodes qui ont des paramètres de tableau repose sur le fait qu’il
s’agit de tableaux et sur leur type d’élément. C'est pourquoi la définition suivante d'une méthode
GetSquares surchargée est conforme à CLS.
using System;
using System.Numerics;

[assembly: CLSCompliant(true)]

public class Numbers


{
public static byte[] GetSquares(byte[] numbers)
{
byte[] numbersOut = new byte[numbers.Length];
for (int ctr = 0; ctr < numbers.Length; ctr++) {
int square = ((int) numbers[ctr]) * ((int) numbers[ctr]);
if (square <= Byte.MaxValue)
numbersOut[ctr] = (byte) square;
// If there's an overflow, assign MaxValue to the corresponding
// element.
else
numbersOut[ctr] = Byte.MaxValue;
}
return numbersOut;
}

public static BigInteger[] GetSquares(BigInteger[] numbers)


{
BigInteger[] numbersOut = new BigInteger[numbers.Length];
for (int ctr = 0; ctr < numbers.Length; ctr++)
numbersOut[ctr] = numbers[ctr] * numbers[ctr];

return numbersOut;
}
}

Imports System.Numerics

<Assembly: CLSCompliant(True)>

Public Module Numbers


Public Function GetSquares(numbers As Byte()) As Byte()
Dim numbersOut(numbers.Length - 1) As Byte
For ctr As Integer = 0 To numbers.Length - 1
Dim square As Integer = (CInt(numbers(ctr)) * CInt(numbers(ctr)))
If square <= Byte.MaxValue Then
numbersOut(ctr) = CByte(square)
' If there's an overflow, assign MaxValue to the corresponding
' element.
Else
numbersOut(ctr) = Byte.MaxValue
End If
Next
Return numbersOut
End Function

Public Function GetSquares(numbers As BigInteger()) As BigInteger()


Dim numbersOut(numbers.Length - 1) As BigInteger
For ctr As Integer = 0 To numbers.Length - 1
numbersOut(ctr) = numbers(ctr) * numbers(ctr)
Next
Return numbersOut
End Function
End Module

Interfaces
Les interfaces conformes à CLS peuvent définir des propriétés, des événements et des méthodes virtuelles
(méthodes sans implémentation). Une interface conforme à CLS ne peut avoir aucune des caractéristiques
suivantes :
Méthodes statiques ou champs statiques. Les compilateurs C# et Visual Basic génèrent des erreurs du
compilateur si vous définissez un membre statique dans une interface.
Champs. Les compilateurs C# et Visual Basic génèrent des erreurs du compilateur si vous définissez un
champ dans une interface.
Méthodes qui ne sont pas conformes à CLS. Par exemple, la définition d'interface suivante inclut une
méthode, INumber.GetUnsigned , qui est marquée comme non conforme à CLS. Cet exemple génère un
avertissement du compilateur.

using System;

[assembly:CLSCompliant(true)]

public interface INumber


{
int Length();
[CLSCompliant(false)] ulong GetUnsigned();
}
// Attempting to compile the example displays output like the following:
// Interface2.cs(8,32): warning CS3010: 'INumber.GetUnsigned()': CLS-compliant interfaces
// must have only CLS-compliant members

<Assembly: CLSCompliant(True)>

Public Interface INumber


Function Length As Integer

<CLSCompliant(False)> Function GetUnsigned As ULong


End Interface
' Attempting to compile the example displays output like the following:
' Interface2.vb(9) : warning BC40033: Non CLS-compliant 'function' is not allowed in a
' CLS-compliant interface.
'
' <CLSCompliant(False)> Function GetUnsigned As ULong
' ~~~~~~~~~~~

En raison de cette règle, pour implémenter des membres non conformes à CLS, il n'est pas nécessaire
d'utiliser des types conformes à CLS. Si une infrastructure conforme à CLS expose une classe qui
implémente une interface non conforme à CLS, elle doit également fournir des implémentations concrètes
de tous les membres non conformes.
Les compilateurs de langages conformes à CLS doivent également permettre à une classe de fournir des
implémentations séparées des membres qui ont les mêmes nom et signature dans plusieurs interfaces. C# et
Visual Basic prennent en charge des implémentations d’interface explicites pour fournir des implémentations
différentes de méthodes portant le même nom. Visual Basic prend également en charge le mot clé Implements ,
qui vous permet de désigner explicitement l'interface et le membre implémentés par un membre particulier.
L'exemple suivant illustre ce scénario en définissant une classe Temperature qui implémente les interfaces
ICelsius et IFahrenheit en tant qu'implémentations d'interface explicites.
using System;

[assembly: CLSCompliant(true)]

public interface IFahrenheit


{
decimal GetTemperature();
}

public interface ICelsius


{
decimal GetTemperature();
}

public class Temperature : ICelsius, IFahrenheit


{
private decimal _value;

public Temperature(decimal value)


{
// We assume that this is the Celsius value.
_value = value;
}

decimal IFahrenheit.GetTemperature()
{
return _value * 9 / 5 + 32;
}

decimal ICelsius.GetTemperature()
{
return _value;
}
}
public class Example
{
public static void Main()
{
Temperature temp = new Temperature(100.0m);
ICelsius cTemp = temp;
IFahrenheit fTemp = temp;
Console.WriteLine("Temperature in Celsius: {0} degrees",
cTemp.GetTemperature());
Console.WriteLine("Temperature in Fahrenheit: {0} degrees",
fTemp.GetTemperature());
}
}
// The example displays the following output:
// Temperature in Celsius: 100.0 degrees
// Temperature in Fahrenheit: 212.0 degrees
<Assembly: CLSCompliant(True)>

Public Interface IFahrenheit


Function GetTemperature() As Decimal
End Interface

Public Interface ICelsius


Function GetTemperature() As Decimal
End Interface

Public Class Temperature : Implements ICelsius, IFahrenheit


Private _value As Decimal

Public Sub New(value As Decimal)


' We assume that this is the Celsius value.
_value = value
End Sub

Public Function GetFahrenheit() As Decimal _


Implements IFahrenheit.GetTemperature
Return _value * 9 / 5 + 32
End Function

Public Function GetCelsius() As Decimal _


Implements ICelsius.GetTemperature
Return _value
End Function
End Class

Module Example
Public Sub Main()
Dim temp As New Temperature(100.0d)
Console.WriteLine("Temperature in Celsius: {0} degrees",
temp.GetCelsius())
Console.WriteLine("Temperature in Fahrenheit: {0} degrees",
temp.GetFahrenheit())
End Sub
End Module
' The example displays the following output:
' Temperature in Celsius: 100.0 degrees
' Temperature in Fahrenheit: 212.0 degrees

Énumérations
Les énumérations conformes à CLS doivent suivre ces règles :
Le type sous-jacent de l'énumération doit être un entier conforme à CLS intrinsèque (Byte, Int16, Int32 ou
Int64). Par exemple, le code suivant tente de définir une énumération dont le type sous-jacent est UInt32 et
génère un avertissement du compilateur.
using System;

[assembly: CLSCompliant(true)]

public enum Size : uint {


Unspecified = 0,
XSmall = 1,
Small = 2,
Medium = 3,
Large = 4,
XLarge = 5
};

public class Clothing


{
public string Name;
public string Type;
public string Size;
}
// The attempt to compile the example displays a compiler warning like the following:
// Enum3.cs(6,13): warning CS3009: 'Size': base type 'uint' is not CLS-compliant

<Assembly: CLSCompliant(True)>

Public Enum Size As UInt32


Unspecified = 0
XSmall = 1
Small = 2
Medium = 3
Large = 4
XLarge = 5
End Enum

Public Class Clothing


Public Name As String
Public Type As String
Public Size As Size
End Class
' The attempt to compile the example displays a compiler warning like the following:
' Enum3.vb(6) : warning BC40032: Underlying type 'UInt32' of Enum is not CLS-compliant.
'
' Public Enum Size As UInt32
' ~~~~

Un type d'énumération doit avoir un champ d'instance unique nommé Value__ qui est marqué avec
l'attribut FieldAttributes.RTSpecialName. Cela vous permet de référencer implicitement la valeur du champ.
Une énumération inclut les champs statiques littéraux du même type que l'énumération elle-même. Par
exemple, si vous définissez une énumération State avec les valeurs State.On et State.Off , State.On et
State.Off sont les deux champs statiques littéraux dont le type est State .

Il existe deux types d'énumérations :


Une énumération qui représente un jeu de valeurs entières nommées qui s'excluent mutuellement.
Ce type d'énumération est indiqué par l'absence de l'attribut personnalisé System.FlagsAttribute.
Une énumération qui représente un jeu d'indicateurs binaires qui peuvent être combinés pour
générer une valeur sans nom. Ce type d'énumération est indiqué par la présence de l'attribut
personnalisé System.FlagsAttribute.
Pour plus d'informations, consultez la documentation de la structure Enum.
La valeur d'une énumération ne se limite pas à la plage de ses valeurs spécifiées. En d'autres termes, la
plage de valeurs d'une énumération est la plage de sa valeur sous-jacente. Vous pouvez utiliser la méthode
Enum.IsDefined pour déterminer si une valeur spécifiée est membre d'une énumération.
Membres de types en général
La spécification CLS (Common Language Specification) nécessite que tous les champs et toutes les méthodes
soient accessibles en tant que membres d'une classe particulière. Par conséquent, les méthodes et les champs
statiques globaux (autrement dit, les méthodes ou les champs statiques définis en dehors d'un type) ne sont pas
conformes à CLS. Si vous essayez d'inclure un champ global ou une méthode globale dans votre code source, les
compilateurs C# et Visual Basic génèrent une erreur de compilateur.
La spécification CLS (Common Language Specification) prend uniquement en charge la convention d'appel
managée standard. Elle ne prend pas en charge les conventions d'appel non managées ni les méthodes avec des
listes d'arguments variables marquées avec le mot clé varargs . Pour les listes d’arguments variables compatibles
avec la convention d’appel managée standard, utilisez l’attribut ParamArrayAttribute ou l’implémentation de
chaque langage, telle que le mot clé params en C# et le mot clé ParamArray en Visual Basic.
Accessibilité des membres
Le remplacement d'un membre hérité ne peut pas modifier l'accessibilité de ce membre. Par exemple, une
méthode publique dans une classe de base ne peut pas être remplacée par une méthode privée dans une classe
dérivée. Il existe une exception : un membre protected internal (en C#) ou Protected Friend (en Visual Basic)
dans un assembly qui est remplacé par un type dans un autre assembly. Dans ce cas, l'accessibilité du
remplacement est Protected .
L'exemple suivant illustre l'erreur générée lorsque l'attribut CLSCompliantAttribute a la valeur true , et que
Human , qui est une classe dérivée de Animal , essaie de remplacer l'accessibilité publique de la propriété Species
par une accessibilité privée. L'exemple se compile correctement si son accessibilité devient publique.
using System;

[assembly: CLSCompliant(true)]

public class Animal


{
private string _species;

public Animal(string species)


{
_species = species;
}

public virtual string Species


{
get { return _species; }
}

public override string ToString()


{
return _species;
}
}

public class Human : Animal


{
private string _name;

public Human(string name) : base("Homo Sapiens")


{
_name = name;
}

public string Name


{
get { return _name; }
}

private override string Species


{
get { return base.Species; }
}

public override string ToString()


{
return _name;
}
}

public class Example


{
public static void Main()
{
Human p = new Human("John");
Console.WriteLine(p.Species);
Console.WriteLine(p.ToString());
}
}
// The example displays the following output:
// error CS0621: 'Human.Species': virtual or abstract members cannot be private
<Assembly: CLSCompliant(True)>

Public Class Animal


Private _species As String

Public Sub New(species As String)


_species = species
End Sub

Public Overridable ReadOnly Property Species As String


Get
Return _species
End Get
End Property

Public Overrides Function ToString() As String


Return _species
End Function
End Class

Public Class Human : Inherits Animal


Private _name As String

Public Sub New(name As String)


MyBase.New("Homo Sapiens")
_name = name
End Sub

Public ReadOnly Property Name As String


Get
Return _name
End Get
End Property

Private Overrides ReadOnly Property Species As String


Get
Return MyBase.Species
End Get
End Property

Public Overrides Function ToString() As String


Return _name
End Function
End Class

Public Module Example


Public Sub Main()
Dim p As New Human("John")
Console.WriteLine(p.Species)
Console.WriteLine(p.ToString())
End Sub
End Module
' The example displays the following output:
' 'Private Overrides ReadOnly Property Species As String' cannot override
' 'Public Overridable ReadOnly Property Species As String' because
' they have different access levels.
'
' Private Overrides ReadOnly Property Species As String

Les types de la signature d'un membre doivent être accessibles chaque fois que ce membre est accessible. Par
exemple, cela signifie qu'un membre public ne peut pas inclure un paramètre dont le type est privé, protégé ou
interne. L'exemple suivant illustre l'erreur de compilateur obtenue lorsqu'un constructeur de classe StringWrapper
expose une valeur d'énumération StringOperationType interne qui détermine comment une valeur de chaîne doit
être encapsulée.
using System;
using System.Text;

public class StringWrapper


{
string internalString;
StringBuilder internalSB = null;
bool useSB = false;

public StringWrapper(StringOperationType type)


{
if (type == StringOperationType.Normal) {
useSB = false;
}
else {
useSB = true;
internalSB = new StringBuilder();
}
}

// The remaining source code...


}

internal enum StringOperationType { Normal, Dynamic }


// The attempt to compile the example displays the following output:
// error CS0051: Inconsistent accessibility: parameter type
// 'StringOperationType' is less accessible than method
// 'StringWrapper.StringWrapper(StringOperationType)'

Imports System.Text

<Assembly: CLSCompliant(True)>

Public Class StringWrapper

Dim internalString As String


Dim internalSB As StringBuilder = Nothing
Dim useSB As Boolean = False

Public Sub New(type As StringOperationType)


If type = StringOperationType.Normal Then
useSB = False
Else
internalSB = New StringBuilder()
useSB = True
End If
End Sub

' The remaining source code...


End Class

Friend Enum StringOperationType As Integer


Normal = 0
Dynamic = 1
End Enum
' The attempt to compile the example displays the following output:
' error BC30909: 'type' cannot expose type 'StringOperationType'
' outside the project through class 'StringWrapper'.
'
' Public Sub New(type As StringOperationType)
' ~~~~~~~~~~~~~~~~~~~

Types et membres génériques


Les types imbriqués ont toujours au moins autant de paramètres génériques que leur type englobant. Ceux-ci
correspondent par position aux paramètres génériques du type englobant. Le type générique peut également
inclure de nouveaux paramètres génériques.
La relation entre les paramètres de type générique d'un type conteneur et ses types imbriqués peut être masquée
par la syntaxe des différents langages. Dans l'exemple suivant, un type générique Outer<T> contient deux classes
imbriquées, Inner1A et Inner1B<U> . Les appels à la méthode ToString , dont chaque classe hérite de
Object.ToString, indiquent que chaque classe imbriquée inclut les paramètres de type de sa classe conteneur.

using System;

[assembly:CLSCompliant(true)]

public class Outer<T>


{
T value;

public Outer(T value)


{
this.value = value;
}

public class Inner1A : Outer<T>


{
public Inner1A(T value) : base(value)
{ }
}

public class Inner1B<U> : Outer<T>


{
U value2;

public Inner1B(T value1, U value2) : base(value1)


{
this.value2 = value2;
}
}
}

public class Example


{
public static void Main()
{
var inst1 = new Outer<String>("This");
Console.WriteLine(inst1);

var inst2 = new Outer<String>.Inner1A("Another");


Console.WriteLine(inst2);

var inst3 = new Outer<String>.Inner1B<int>("That", 2);


Console.WriteLine(inst3);
}
}
// The example displays the following output:
// Outer`1[System.String]
// Outer`1+Inner1A[System.String]
// Outer`1+Inner1B`1[System.String,System.Int32]
<Assembly: CLSCompliant(True)>

Public Class Outer(Of T)


Dim value As T

Public Sub New(value As T)


Me.value = value
End Sub

Public Class Inner1A : Inherits Outer(Of T)


Public Sub New(value As T)
MyBase.New(value)
End Sub
End Class

Public Class Inner1B(Of U) : Inherits Outer(Of T)


Dim value2 As U

Public Sub New(value1 As T, value2 As U)


MyBase.New(value1)
Me.value2 = value2
End Sub
End Class
End Class

Public Module Example


Public Sub Main()
Dim inst1 As New Outer(Of String)("This")
Console.WriteLine(inst1)

Dim inst2 As New Outer(Of String).Inner1A("Another")


Console.WriteLine(inst2)

Dim inst3 As New Outer(Of String).Inner1B(Of Integer)("That", 2)


Console.WriteLine(inst3)
End Sub
End Module
' The example displays the following output:
' Outer`1[System.String]
' Outer`1+Inner1A[System.String]
' Outer`1+Inner1B`1[System.String,System.Int32]

Les noms de types génériques sont encodés sous la forme nom ` n, où nom est le nom de type, ` est un caractère
littéral et n est le nombre de paramètres déclarés sur le type, ou, pour les types génériques imbriqués, le nombre
de paramètres de type récemment introduits. Cet encodage de noms de types génériques intéresse
principalement les développeurs qui utilisent la réflexion pour accéder aux types génériques conformes à CLS
dans une bibliothèque.
Si les contraintes sont appliquées à un type générique, tous les types utilisés en tant que contraintes doivent
également être conformes à CLS. L'exemple suivant définit une classe nommée BaseClass qui n'est pas conforme
à CLS et une classe générique nommée BaseCollection dont le paramètre de type doit dériver de BaseClass .
Toutefois, comme BaseClass n'est pas conforme à CLS, le compilateur émet un avertissement.
using System;

[assembly:CLSCompliant(true)]

[CLSCompliant(false)] public class BaseClass


{}

public class BaseCollection<T> where T : BaseClass


{}
// Attempting to compile the example displays the following output:
// warning CS3024: Constraint type 'BaseClass' is not CLS-compliant

<Assembly: CLSCompliant(True)>

<CLSCompliant(False)> Public Class BaseClass


End Class

Public Class BaseCollection(Of T As BaseClass)


End Class
' Attempting to compile the example displays the following output:
' warning BC40040: Generic parameter constraint type 'BaseClass' is not
' CLS-compliant.
'
' Public Class BaseCollection(Of T As BaseClass)
' ~~~~~~~~~

Si un type générique est dérivé d'un type de base générique, il doit redéclarer toutes les contraintes pour s'assurer
que les contraintes du type de base sont également satisfaites. L'exemple suivant définit un Number<T> qui peut
représenter un type numérique. Il définit également une classe FloatingPoint<T> qui représente une valeur à
virgule flottante. Toutefois, le code source ne se compile pas, car il n'applique pas la contrainte Number<T> (ce T
doit être un type de valeur) à FloatingPoint<T> .
using System;

[assembly:CLSCompliant(true)]

public class Number<T> where T : struct


{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;

public Number(T value)


{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}

public T Add(T value)


{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}

public T Subtract(T value)


{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}

public class FloatingPoint<T> : Number<T>


{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
// The attempt to comple the example displays the following output:
// error CS0453: The type 'T' must be a non-nullable value type in
// order to use it as parameter 'T' in the generic type or method 'Number<T>'
<Assembly: CLSCompliant(True)>

Public Class Number(Of T As Structure)


' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double

Public Sub New(value As T)


Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub

Public Function Add(value As T) As T


Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function

Public Function Subtract(value As T) As T


Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class

Public Class FloatingPoint(Of T) : Inherits Number(Of T)


Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class
' The attempt to comple the example displays the following output:
' error BC32105: Type argument 'T' does not satisfy the 'Structure'
' constraint for type parameter 'T'.
'
' Public Class FloatingPoint(Of T) : Inherits Number(Of T)
' ~

L'exemple se compile correctement si la contrainte est ajoutée à la classe FloatingPoint<T> .


using System;

[assembly:CLSCompliant(true)]

public class Number<T> where T : struct


{
// use Double as the underlying type, since its range is a superset of
// the ranges of all numeric types except BigInteger.
protected double number;

public Number(T value)


{
try {
this.number = Convert.ToDouble(value);
}
catch (OverflowException e) {
throw new ArgumentException("value is too large.", e);
}
catch (InvalidCastException e) {
throw new ArgumentException("The value parameter is not numeric.", e);
}
}

public T Add(T value)


{
return (T) Convert.ChangeType(number + Convert.ToDouble(value), typeof(T));
}

public T Subtract(T value)


{
return (T) Convert.ChangeType(number - Convert.ToDouble(value), typeof(T));
}
}

public class FloatingPoint<T> : Number<T> where T : struct


{
public FloatingPoint(T number) : base(number)
{
if (typeof(float) == number.GetType() ||
typeof(double) == number.GetType() ||
typeof(decimal) == number.GetType())
this.number = Convert.ToDouble(number);
else
throw new ArgumentException("The number parameter is not a floating-point number.");
}
}
<Assembly: CLSCompliant(True)>

Public Class Number(Of T As Structure)


' Use Double as the underlying type, since its range is a superset of
' the ranges of all numeric types except BigInteger.
Protected number As Double

Public Sub New(value As T)


Try
Me.number = Convert.ToDouble(value)
Catch e As OverflowException
Throw New ArgumentException("value is too large.", e)
Catch e As InvalidCastException
Throw New ArgumentException("The value parameter is not numeric.", e)
End Try
End Sub

Public Function Add(value As T) As T


Return CType(Convert.ChangeType(number + Convert.ToDouble(value), GetType(T)), T)
End Function

Public Function Subtract(value As T) As T


Return CType(Convert.ChangeType(number - Convert.ToDouble(value), GetType(T)), T)
End Function
End Class

Public Class FloatingPoint(Of T As Structure) : Inherits Number(Of T)


Public Sub New(number As T)
MyBase.New(number)
If TypeOf number Is Single Or
TypeOf number Is Double Or
TypeOf number Is Decimal Then
Me.number = Convert.ToDouble(number)
Else
throw new ArgumentException("The number parameter is not a floating-point number.")
End If
End Sub
End Class

La spécification CLS (Common Language Specification) impose un modèle conservateur par instanciation pour les
types imbriqués et les membres protégés. Les types génériques ouverts ne peuvent pas exposer de champs ou de
membres avec des signatures qui contiennent une instanciation spécifique d'un type générique imbriqué et
protégé. Les types non génériques qui étendent une instanciation spécifique d'une classe de base ou une interface
générique ne peuvent pas exposer de champs ou de membres avec des signatures qui contiennent une
instanciation différente d'un type générique imbriqué et protégé.
L'exemple suivant définit un type générique, C1<T> (ou C1(Of T) en Visual Basic) et une classe protégée,
C1<T>.N (ou C1(Of T).N en Visual Basic). C1<T> a deux méthodes : M1 et M2 . Toutefois, M1 n’est pas conforme
CLS, car il tente de retourner un C1<int>.N objet (ou C1(Of Integer).N ) à partir de C1 <T> (ou C1(Of T) ). Une
deuxième classe, C2 , est dérivée de C1<long> (ou de C1(Of Long) ). Elle a deux méthodes, M3 et M4 . M3 n'est
pas conforme à CLS parce qu'elle essaie de retourner un objet C1<int>.N (ou C1(Of Integer).N ) d'une sous-
classe de C1<long> . Notez que les compilateurs de langage peuvent être encore plus restrictifs. Dans cet exemple,
Visual Basic affiche une erreur lorsqu'il tente de compiler M4 .
using System;

[assembly:CLSCompliant(true)]

public class C1<T>


{
protected class N { }

protected void M1(C1<int>.N n) { } // Not CLS-compliant - C1<int>.N not


// accessible from within C1<T> in all
// languages
protected void M2(C1<T>.N n) { } // CLS-compliant – C1<T>.N accessible
// inside C1<T>
}

public class C2 : C1<long>


{
protected void M3(C1<int>.N n) { } // Not CLS-compliant – C1<int>.N is not
// accessible in C2 (extends C1<long>)

protected void M4(C1<long>.N n) { } // CLS-compliant, C1<long>.N is


// accessible in C2 (extends C1<long>)
}
// Attempting to compile the example displays output like the following:
// Generics4.cs(9,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant
// Generics4.cs(18,22): warning CS3001: Argument type 'C1<int>.N' is not CLS-compliant
<Assembly: CLSCompliant(True)>

Public Class C1(Of T)


Protected Class N
End Class

Protected Sub M1(n As C1(Of Integer).N) ' Not CLS-compliant - C1<int>.N not
' accessible from within C1(Of T) in all
End Sub ' languages

Protected Sub M2(n As C1(Of T).N) ' CLS-compliant – C1(Of T).N accessible
End Sub ' inside C1(Of T)
End Class

Public Class C2 : Inherits C1(Of Long)


Protected Sub M3(n As C1(Of Integer).N) ' Not CLS-compliant – C1(Of Integer).N is not
End Sub ' accessible in C2 (extends C1(Of Long))

Protected Sub M4(n As C1(Of Long).N)


End Sub
End Class
' Attempting to compile the example displays output like the following:
' error BC30508: 'n' cannot expose type 'C1(Of Integer).N' in namespace
' '<Default>' through class 'C1'.
'
' Protected Sub M1(n As C1(Of Integer).N) ' Not CLS-compliant - C1<int>.N not
' ~~~~~~~~~~~~~~~~
' error BC30389: 'C1(Of T).N' is not accessible in this context because
' it is 'Protected'.
'
' Protected Sub M3(n As C1(Of Integer).N) ' Not CLS-compliant - C1(Of Integer).N is not
'
' ~~~~~~~~~~~~~~~~
'
' error BC30389: 'C1(Of T).N' is not accessible in this context because it is 'Protected'.
'
' Protected Sub M4(n As C1(Of Long).N)
' ~~~~~~~~~~~~~

Constructeurs
Les constructeurs des classes et structures conformes à CLS doivent suivre ces règles :
Un constructeur d'une classe dérivée doit appeler le constructeur d'instance de sa classe de base avant
d'accéder aux données d'instance héritées. Cette spécification est due au fait que les constructeurs de
classes de base ne sont pas hérités par leurs classes dérivées. Cette règle ne s'applique pas aux structures,
qui ne prennent pas en charge l'héritage direct.
En général, les compilateurs appliquent cette règle indépendamment de la conformité CLS, comme indiqué
dans l'exemple suivant. Il crée une classe Doctor dérivée d'une classe Person , mais la classe Doctor ne
parvient pas à appeler le constructeur de classe Person pour initialiser les champs d'instance hérités.
using System;

[assembly: CLSCompliant(true)]

public class Person


{
private string fName, lName, _id;

public Person(string firstName, string lastName, string id)


{
if (String.IsNullOrEmpty(firstName + lastName))
throw new ArgumentNullException("Either a first name or a last name must be provided.");

fName = firstName;
lName = lastName;
_id = id;
}

public string FirstName


{
get { return fName; }
}

public string LastName


{
get { return lName; }
}

public string Id
{
get { return _id; }
}

public override string ToString()


{
return String.Format("{0}{1}{2}", fName,
String.IsNullOrEmpty(fName) ? "" : " ",
lName);
}
}

public class Doctor : Person


{
public Doctor(string firstName, string lastName, string id)
{
}

public override string ToString()


{
return "Dr. " + base.ToString();
}
}
// Attempting to compile the example displays output like the following:
// ctor1.cs(45,11): error CS1729: 'Person' does not contain a constructor that takes 0
// arguments
// ctor1.cs(10,11): (Location of symbol related to previous error)
<Assembly: CLSCompliant(True)>

Public Class Person


Private fName, lName, _id As String

Public Sub New(firstName As String, lastName As String, id As String)


If String.IsNullOrEmpty(firstName + lastName) Then
Throw New ArgumentNullException("Either a first name or a last name must be provided.")
End If

fName = firstName
lName = lastName
_id = id
End Sub

Public ReadOnly Property FirstName As String


Get
Return fName
End Get
End Property

Public ReadOnly Property LastName As String


Get
Return lName
End Get
End Property

Public ReadOnly Property Id As String


Get
Return _id
End Get
End Property

Public Overrides Function ToString() As String


Return String.Format("{0}{1}{2}", fName,
If(String.IsNullOrEmpty(fName), "", " "),
lName)
End Function
End Class

Public Class Doctor : Inherits Person


Public Sub New(firstName As String, lastName As String, id As String)
End Sub

Public Overrides Function ToString() As String


Return "Dr. " + MyBase.ToString()
End Function
End Class
' Attempting to compile the example displays output like the following:
' Ctor1.vb(46) : error BC30148: First statement of this 'Sub New' must be a call
' to 'MyBase.New' or 'MyClass.New' because base class 'Person' of 'Doctor' does
' not have an accessible 'Sub New' that can be called with no arguments.
'
' Public Sub New()
' ~~~

Un constructeur d'objet ne peut être appelé que pour créer un objet. En outre, un objet ne peut pas être
initialisé deux fois. Par exemple, cela signifie que Object.MemberwiseClone et les méthodes de
désérialisation telles que BinaryFormatter.Deserialize ne doivent pas appeler de constructeurs.
Propriétés
Les propriétés dans les types conformes à CLS doivent suivre ces règles :
Une propriété doit posséder une méthode setter, getter ou les deux. Dans un assembly, celles-ci sont
implémentées comme des méthodes spéciales, ce qui signifie qu’elles apparaîtront comme des méthodes
distinctes (la méthode getter est nommée get_ propertyname et la méthode setter est nommée set_
propertyname) marquées comme SpecialName dans les métadonnées de l’assembly. Les compilateurs C#
et Visual Basic appliquent automatiquement cette règle sans qu'il soit nécessaire d'appliquer l'attribut
CLSCompliantAttribute.
Le type d’une propriété correspond au type de retour de la méthode getter de la propriété et du dernier
argument de la méthode setter. Ces types doivent être conformes à CLS et les arguments ne peuvent pas
être assignés à la propriété par référence (autrement dit, ils ne peuvent pas être des pointeurs managés).
Si une propriété possède une méthode getter et une méthode setter, elles doivent être toutes les deux
virtuelles, statiques ou être toutes les deux des instances. Les compilateurs C# et Visual Basic appliquent
automatiquement cette règle via leur syntaxe de définition de propriété.
Événements
Un événement est défini par son nom et son type. Le type d'événement est un délégué utilisé pour indiquer
l'événement. Par exemple, l'événement AppDomain.AssemblyResolve est de type ResolveEventHandler. Outre
l'événement lui-même, trois méthodes avec des noms basés sur le nom de l'événement fournissent une
implémentation de l'événement et sont marquées comme SpecialName dans les métadonnées de l'assembly :
Une méthode pour ajouter un gestionnaire d’événements, appelée add_ EventName. Par exemple, la
méthode d'abonnement aux événements pour l'événement AppDomain.AssemblyResolve est nommée
add_AssemblyResolve .

Une méthode pour supprimer un gestionnaire d’événements, appelée remove_ EventName. Par exemple, la
méthode de suppression pour l'événement AppDomain.AssemblyResolve est nommée
remove_AssemblyResolve .

Une méthode pour indiquer que l’événement s’est produit, nommée raise_ NomÉvénement.

NOTE
La plupart des règles de la spécification CLS concernant les événements sont implémentées par les compilateurs de langage
et sont transparentes aux développeurs de composants.

L'accessibilité des méthodes permettant d'ajouter, de supprimer et de déclencher un événement doit être
identique. Les méthodes doivent également toutes être statiques, instanciées ou virtuelles. Les méthodes d'ajout
et de suppression d'un événement ont un paramètre dont le type est le type du délégué d'événement. Les
méthodes d'ajout et de suppression doivent être toutes les deux présentes ou absentes.
L'exemple suivant définit une classe conforme à CLS nommée Temperature qui déclenche un événement
TemperatureChanged si le changement de température entre deux lectures est égal ou supérieur à une valeur seuil.
La classe Temperature définit explicitement une méthode raise_TemperatureChanged afin de pouvoir exécuter de
manière sélective les gestionnaires d'événements.

using System;
using System.Collections;
using System.Collections.Generic;

[assembly: CLSCompliant(true)]

public class TemperatureChangedEventArgs : EventArgs


{
private Decimal originalTemp;
private Decimal newTemp;
private DateTimeOffset when;

public TemperatureChangedEventArgs(Decimal original, Decimal @new, DateTimeOffset time)


{
{
originalTemp = original;
newTemp = @new;
when = time;
}

public Decimal OldTemperature


{
get { return originalTemp; }
}

public Decimal CurrentTemperature


{
get { return newTemp; }
}

public DateTimeOffset Time


{
get { return when; }
}
}

public delegate void TemperatureChanged(Object sender, TemperatureChangedEventArgs e);

public class Temperature


{
private struct TemperatureInfo
{
public Decimal Temperature;
public DateTimeOffset Recorded;
}

public event TemperatureChanged TemperatureChanged;

private Decimal previous;


private Decimal current;
private Decimal tolerance;
private List<TemperatureInfo> tis = new List<TemperatureInfo>();

public Temperature(Decimal temperature, Decimal tolerance)


{
current = temperature;
TemperatureInfo ti = new TemperatureInfo();
ti.Temperature = temperature;
tis.Add(ti);
ti.Recorded = DateTimeOffset.UtcNow;
this.tolerance = tolerance;
}

public Decimal CurrentTemperature


{
get { return current; }
set {
TemperatureInfo ti = new TemperatureInfo();
ti.Temperature = value;
ti.Recorded = DateTimeOffset.UtcNow;
previous = current;
current = value;
if (Math.Abs(current - previous) >= tolerance)
raise_TemperatureChanged(new TemperatureChangedEventArgs(previous, current, ti.Recorded));
}
}

public void raise_TemperatureChanged(TemperatureChangedEventArgs eventArgs)


{
if (TemperatureChanged == null)
return;

foreach (TemperatureChanged d in TemperatureChanged.GetInvocationList()) {


if (d.Method.Name.Contains("Duplicate"))
if (d.Method.Name.Contains("Duplicate"))
Console.WriteLine("Duplicate event handler; event handler not executed.");
else
d.Invoke(this, eventArgs);
}
}
}

public class Example


{
public Temperature temp;

public static void Main()


{
Example ex = new Example();
}

public Example()
{
temp = new Temperature(65, 3);
temp.TemperatureChanged += this.TemperatureNotification;
RecordTemperatures();
Example ex = new Example(temp);
ex.RecordTemperatures();
}

public Example(Temperature t)
{
temp = t;
RecordTemperatures();
}

public void RecordTemperatures()


{
temp.TemperatureChanged += this.DuplicateTemperatureNotification;
temp.CurrentTemperature = 66;
temp.CurrentTemperature = 63;
}

internal void TemperatureNotification(Object sender, TemperatureChangedEventArgs e)


{
Console.WriteLine("Notification 1: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature);
}

public void DuplicateTemperatureNotification(Object sender, TemperatureChangedEventArgs e)


{
Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature);
}
}

Imports System.Collections
Imports System.Collections.Generic

<Assembly: CLSCompliant(True)>

Public Class TemperatureChangedEventArgs : Inherits EventArgs


Private originalTemp As Decimal
Private newTemp As Decimal
Private [when] As DateTimeOffset

Public Sub New(original As Decimal, [new] As Decimal, [time] As DateTimeOffset)


originalTemp = original
newTemp = [new]
[when] = [time]
End Sub
Public ReadOnly Property OldTemperature As Decimal
Get
Return originalTemp
End Get
End Property

Public ReadOnly Property CurrentTemperature As Decimal


Get
Return newTemp
End Get
End Property

Public ReadOnly Property [Time] As DateTimeOffset


Get
Return [when]
End Get
End Property
End Class

Public Delegate Sub TemperatureChanged(sender As Object, e As TemperatureChangedEventArgs)

Public Class Temperature


Private Structure TemperatureInfo
Dim Temperature As Decimal
Dim Recorded As DateTimeOffset
End Structure

Public Event TemperatureChanged As TemperatureChanged

Private previous As Decimal


Private current As Decimal
Private tolerance As Decimal
Private tis As New List(Of TemperatureInfo)

Public Sub New(temperature As Decimal, tolerance As Decimal)


current = temperature
Dim ti As New TemperatureInfo()
ti.Temperature = temperature
ti.Recorded = DateTimeOffset.UtcNow
tis.Add(ti)
Me.tolerance = tolerance
End Sub

Public Property CurrentTemperature As Decimal


Get
Return current
End Get
Set
Dim ti As New TemperatureInfo
ti.Temperature = value
ti.Recorded = DateTimeOffset.UtcNow
previous = current
current = value
If Math.Abs(current - previous) >= tolerance Then
raise_TemperatureChanged(New TemperatureChangedEventArgs(previous, current, ti.Recorded))
End If
End Set
End Property

Public Sub raise_TemperatureChanged(eventArgs As TemperatureChangedEventArgs)


If TemperatureChangedEvent Is Nothing Then Exit Sub

Dim ListenerList() As System.Delegate = TemperatureChangedEvent.GetInvocationList()


For Each d As TemperatureChanged In TemperatureChangedEvent.GetInvocationList()
If d.Method.Name.Contains("Duplicate") Then
Console.WriteLine("Duplicate event handler; event handler not executed.")
Else
d.Invoke(Me, eventArgs)
End If
End If
Next
End Sub
End Class

Public Class Example


Public WithEvents temp As Temperature

Public Shared Sub Main()


Dim ex As New Example()
End Sub

Public Sub New()


temp = New Temperature(65, 3)
RecordTemperatures()
Dim ex As New Example(temp)
ex.RecordTemperatures()
End Sub

Public Sub New(t As Temperature)


temp = t
RecordTemperatures()
End Sub

Public Sub RecordTemperatures()


temp.CurrentTemperature = 66
temp.CurrentTemperature = 63

End Sub

Friend Shared Sub TemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _


Handles temp.TemperatureChanged
Console.WriteLine("Notification 1: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature)
End Sub

Friend Shared Sub DuplicateTemperatureNotification(sender As Object, e As TemperatureChangedEventArgs) _


Handles temp.TemperatureChanged
Console.WriteLine("Notification 2: The temperature changed from {0} to {1}", e.OldTemperature,
e.CurrentTemperature)
End Sub
End Class

Surcharges
La spécification CLS impose les conditions suivantes aux membres surchargés :
Les membres peuvent être surchargés selon le nombre de paramètres et le type d'un paramètre. La
convention d’appel, le type de retour, les modificateurs personnalisés appliqués à la méthode ou à son
paramètre et le fait que les paramètres soient transmis par valeur ou par référence ne sont pas pris en
considération lors de la différenciation des surcharges. Pour obtenir un exemple, consultez le code de
l’exigence indiquant que les noms doivent être uniques dans une portée, dans la section Conventions
d’affectation de noms.
Seules les propriétés et les méthodes peuvent être surchargées. Les champs et les événements ne peuvent
pas être surchargés.
Les méthodes génériques peuvent être surchargées selon le nombre de leurs paramètres génériques.

NOTE
Les opérateurs op_Explicit et op_Implicit sont des exceptions à la règle indiquant que la valeur de retour n'est pas
considérée comme faisant partie d'une signature de méthode pour la résolution de la surcharge. Ces deux opérateurs
peuvent être surchargés selon leurs paramètres et leur valeur de retour.
Exceptions
Les objets d'exception doivent dériver de System.Exception ou d'un autre type dérivé de System.Exception.
L'exemple suivant illustre l'erreur de compilateur obtenue lorsqu'une classe personnalisée nommée ErrorClass
est utilisée pour la gestion des exceptions.

using System;

[assembly: CLSCompliant(true)]

public class ErrorClass


{
string msg;

public ErrorClass(string errorMessage)


{
msg = errorMessage;
}

public string Message


{
get { return msg; }
}
}

public static class StringUtilities


{
public static string[] SplitString(this string value, int index)
{
if (index < 0 | index > value.Length) {
ErrorClass badIndex = new ErrorClass("The index is not within the string.");
throw badIndex;
}
string[] retVal = { value.Substring(0, index - 1),
value.Substring(index) };
return retVal;
}
}
// Compilation produces a compiler error like the following:
// Exceptions1.cs(26,16): error CS0155: The type caught or thrown must be derived from
// System.Exception
Imports System.Runtime.CompilerServices

<Assembly: CLSCompliant(True)>

Public Class ErrorClass


Dim msg As String

Public Sub New(errorMessage As String)


msg = errorMessage
End Sub

Public ReadOnly Property Message As String


Get
Return msg
End Get
End Property
End Class

Public Module StringUtilities


<Extension()> Public Function SplitString(value As String, index As Integer) As String()
If index < 0 Or index > value.Length Then
Dim BadIndex As New ErrorClass("The index is not within the string.")
Throw BadIndex
End If
Dim retVal() As String = {value.Substring(0, index - 1),
value.Substring(index)}
Return retVal
End Function
End Module
' Compilation produces a compiler error like the following:
' Exceptions1.vb(27) : error BC30665: 'Throw' operand must derive from 'System.Exception'.
'
' Throw BadIndex
' ~~~~~~~~~~~~~~

Pour corriger cette erreur, la classe ErrorClass doit hériter de System.Exception. En outre, la propriété Message
doit être remplacée. L'exemple suivant corrige ces erreurs pour définir une classe ErrorClass conforme à CLS.
using System;

[assembly: CLSCompliant(true)]

public class ErrorClass : Exception


{
string msg;

public ErrorClass(string errorMessage)


{
msg = errorMessage;
}

public override string Message


{
get { return msg; }
}
}

public static class StringUtilities


{
public static string[] SplitString(this string value, int index)
{
if (index < 0 | index > value.Length) {
ErrorClass badIndex = new ErrorClass("The index is not within the string.");
throw badIndex;
}
string[] retVal = { value.Substring(0, index - 1),
value.Substring(index) };
return retVal;
}
}

Imports System.Runtime.CompilerServices

<Assembly: CLSCompliant(True)>

Public Class ErrorClass : Inherits Exception


Dim msg As String

Public Sub New(errorMessage As String)


msg = errorMessage
End Sub

Public Overrides ReadOnly Property Message As String


Get
Return msg
End Get
End Property
End Class

Public Module StringUtilities


<Extension()> Public Function SplitString(value As String, index As Integer) As String()
If index < 0 Or index > value.Length Then
Dim BadIndex As New ErrorClass("The index is not within the string.")
Throw BadIndex
End If
Dim retVal() As String = {value.Substring(0, index - 1),
value.Substring(index)}
Return retVal
End Function
End Module

Attributs
Dans les assemblys .NET Framework, les attributs personnalisés fournissent un mécanisme extensible pour
stocker des attributs personnalisés et récupérer les métadonnées concernant la programmation des objets, tels
que les assemblys, les types, les membres et les paramètres de méthode. Les attributs personnalisés doivent
dériver de System.Attribute ou d'un type dérivé de System.Attribute.
L'exemple suivant enfreint cette règle. Il définit une classe NumericAttribute qui ne dérive pas de
System.Attribute. Notez qu'une erreur du compilateur se produit uniquement lorsque l'attribut non conforme à
CLS est appliqué, pas lorsque la classe est définie.

using System;

[assembly: CLSCompliant(true)]

[AttributeUsageAttribute(AttributeTargets.Class | AttributeTargets.Struct)]
public class NumericAttribute
{
private bool _isNumeric;

public NumericAttribute(bool isNumeric)


{
_isNumeric = isNumeric;
}

public bool IsNumeric


{
get { return _isNumeric; }
}
}

[Numeric(true)] public struct UDouble


{
double Value;
}
// Compilation produces a compiler error like the following:
// Attribute1.cs(22,2): error CS0616: 'NumericAttribute' is not an attribute class
// Attribute1.cs(7,14): (Location of symbol related to previous error)

<Assembly: CLSCompliant(True)>

<AttributeUsageAttribute(AttributeTargets.Class Or AttributeTargets.Struct)> _
Public Class NumericAttribute
Private _isNumeric As Boolean

Public Sub New(isNumeric As Boolean)


_isNumeric = isNumeric
End Sub

Public ReadOnly Property IsNumeric As Boolean


Get
Return _isNumeric
End Get
End Property
End Class

<Numeric(True)> Public Structure UDouble


Dim Value As Double
End Structure
' Compilation produces a compiler error like the following:
' error BC31504: 'NumericAttribute' cannot be used as an attribute because it
' does not inherit from 'System.Attribute'.
'
' <Numeric(True)> Public Structure UDouble
' ~~~~~~~~~~~~~
Le constructeur ou les propriétés d'un attribut conforme à CLS peuvent exposer uniquement les types suivants :
Boolean
Byte
Char
Double
Int16
Int32
Int64
Single
String
Type
Tout type d'énumération dont le type sous-jacent est Byte, Int16, Int32 ou Int64.
L'exemple suivant définit une classe DescriptionAttribute qui dérive de Attribute. Le constructeur de classe a un
paramètre de type Descriptor , la classe n'est donc pas conforme à CLS. Notez que le compilateur C# émet un
avertissement mais compile correctement, alors que le compilateur Visual Basic n'émet ni avertissement ni erreur.

using System;

[assembly:CLSCompliantAttribute(true)]

public enum DescriptorType { type, member };

public class Descriptor


{
public DescriptorType Type;
public String Description;
}

[AttributeUsage(AttributeTargets.All)]
public class DescriptionAttribute : Attribute
{
private Descriptor desc;

public DescriptionAttribute(Descriptor d)
{
desc = d;
}

public Descriptor Descriptor


{ get { return desc; } }
}
// Attempting to compile the example displays output like the following:
// warning CS3015: 'DescriptionAttribute' has no accessible
// constructors which use only CLS-compliant types
<Assembly: CLSCompliantAttribute(True)>

Public Enum DescriptorType As Integer


Type = 0
Member = 1
End Enum

Public Class Descriptor


Public Type As DescriptorType
Public Description As String
End Class

<AttributeUsage(AttributeTargets.All)> _
Public Class DescriptionAttribute : Inherits Attribute
Private desc As Descriptor

Public Sub New(d As Descriptor)


desc = d
End Sub

Public ReadOnly Property Descriptor As Descriptor


Get
Return desc
End Get
End Property
End Class

Attribut CLSCompliantAttribute
L'attribut CLSCompliantAttribute permet d'indiquer si un élément de programme est conforme à la spécification
CLS (Common Language Specification). Le constructeur CLSCompliantAttribute(Boolean) inclut un seul paramètre
obligatoire, isCompliant , qui indique si l'élément de programme est conforme à CLS.
Au moment de la compilation, le compilateur détecte les éléments non conformes qui sont présumés conformes à
CLS et émet alors un avertissement. Le compilateur n'émet pas d'avertissements pour les types ou les membres
qui sont explicitement déclarés comme étant non conformes.
Les développeurs de composants peuvent utiliser l'attribut CLSCompliantAttribute de deux façons :
Pour définir les parties de l'interface publique exposées par un composant qui sont conformes à CLS et
celles qui ne sont pas conformes à CLS. Lorsque l'attribut est utilisé pour marquer des éléments de
programme particuliers comme étant conformes à CLS, son utilisation garantit que ces éléments sont
accessibles à partir de tous les langages et outils qui ciblent le .NET Framework.
Pour vérifier que l'interface publique de la bibliothèque de composants expose uniquement les éléments de
programme conformes à CLS. Si les éléments ne sont pas conformes à CLS, les compilateurs publieront
généralement un avertissement.

WARNING
Dans certains cas, les compilateurs de langages imposent les règles conformes à CLS que l'attribut CLSCompliantAttribute
soit utilisé ou non. Par exemple, définir un membre statique dans une interface viole une règle CLS. À cet égard, si vous
définissez un membre static (en C#) ou Shared (en Visual Basic) dans une interface, les compilateurs C# et Visual Basic
affichent un message d'erreur et ne parviennent pas à compiler l'application.

L'attribut CLSCompliantAttribute est marqué avec un attribut AttributeUsageAttribute qui a la valeur


AttributeTargets.All. Cette valeur permet d'appliquer l'attribut CLSCompliantAttribute à un élément de
programme, notamment aux assemblys, modules, types (classes, structures, énumérations, interfaces et délégués),
membres de types (constructeurs, méthodes, propriétés, champs et événements), paramètres, paramètres
génériques et valeurs de retour. Toutefois, dans la pratique, vous devez appliquer l'attribut uniquement aux
assemblys, aux types et aux membres de types. Sinon, les compilateurs ignorent l'attribut et continuent à générer
des avertissements de compilateur lorsqu'ils rencontrent un paramètre non conforme, un paramètre générique
ou une valeur de retour dans l'interface publique de votre bibliothèque.
La valeur de l'attribut CLSCompliantAttribute est héritée par les éléments de programme contenus. Par exemple, si
un assembly est marqué comme étant conforme à CLS, ses types sont également conformes à CLS. Si un type est
marqué comme étant conforme à CLS, ses membres et types imbriqués seront également conformes à CLS.
Vous pouvez remplacer explicitement la conformité héritée en appliquant l'attribut CLSCompliantAttribute à un
élément de programme contenu. Par exemple, vous pouvez utiliser l'attribut CLSCompliantAttribute avec une
valeur isCompliant``false pour définir un type non conforme dans un assembly conforme et vous pouvez utiliser
l'attribut avec une valeur isCompliant``true pour définir un type conforme dans un assembly non conforme.
Vous pouvez également définir des membres non conformes dans un type conforme. Toutefois, un type non
conforme ne peut pas avoir de membres conformes, vous ne pouvez pas utiliser l'attribut avec une valeur
isCompliant``true pour remplacer l'héritage d'un type non conforme.

Lorsque vous développez des composants, vous devez toujours utiliser l'attribut CLSCompliantAttribute pour
indiquer si votre assembly, ses types et ses membres sont conformes à CLS.
Pour créer des composants conformes à CLS :
1. Utilisez CLSCompliantAttribute pour marquer votre assembly comme étant conforme à CLS.
2. Marquez les types exposés publiquement de l'assembly qui ne sont pas conformes à CLS comme non
conformes.
3. Marquez tous les membres exposés publiquement dans des types conformes à CLS comme non
conformes.
4. Fournissez une alternative conforme à CLS pour les membres non conformes.
Si vous avez correctement marqué tous vos types et membres non conformes, le compilateur ne doit émettre
aucun avertissement de non-conformité. Toutefois, vous devez indiquer quels membres ne sont pas conformes à
CLS et répertorier leurs alternatives conformes à CLS dans votre documentation produit.
L'exemple suivant utilise l'attribut CLSCompliantAttribute pour définir un assembly conforme à CLS et un type,
CharacterUtilities , qui a deux membres non conformes à CLS. Les deux membres étant référencés avec l'attribut
CLSCompliant(false) , le compilateur ne génère aucun avertissement. La classe fournit également une alternative
conforme à CLS pour les deux méthodes. Normalement, nous ajouterions seulement deux surcharges à la
méthode ToUTF16 pour fournir des alternatives conformes à CLS. Toutefois, les méthodes ne pouvant pas être
surchargées selon la valeur de retour, les noms des méthodes conformes à CLS sont différents des noms des
méthodes non conformes.
using System;
using System.Text;

[assembly:CLSCompliant(true)]

public class CharacterUtilities


{
[CLSCompliant(false)] public static ushort ToUTF16(String s)
{
s = s.Normalize(NormalizationForm.FormC);
return Convert.ToUInt16(s[0]);
}

[CLSCompliant(false)] public static ushort ToUTF16(Char ch)


{
return Convert.ToUInt16(ch);
}

// CLS-compliant alternative for ToUTF16(String).


public static int ToUTF16CodeUnit(String s)
{
s = s.Normalize(NormalizationForm.FormC);
return (int) Convert.ToUInt16(s[0]);
}

// CLS-compliant alternative for ToUTF16(Char).


public static int ToUTF16CodeUnit(Char ch)
{
return Convert.ToInt32(ch);
}

public bool HasMultipleRepresentations(String s)


{
String s1 = s.Normalize(NormalizationForm.FormC);
return s.Equals(s1);
}

public int GetUnicodeCodePoint(Char ch)


{
if (Char.IsSurrogate(ch))
throw new ArgumentException("ch cannot be a high or low surrogate.");

return Char.ConvertToUtf32(ch.ToString(), 0);


}

public int GetUnicodeCodePoint(Char[] chars)


{
if (chars.Length > 2)
throw new ArgumentException("The array has too many characters.");

if (chars.Length == 2) {
if (! Char.IsSurrogatePair(chars[0], chars[1]))
throw new ArgumentException("The array must contain a low and a high surrogate.");
else
return Char.ConvertToUtf32(chars[0], chars[1]);
}
else {
return Char.ConvertToUtf32(chars.ToString(), 0);
}
}
}
Imports System.Text

<Assembly: CLSCompliant(True)>

Public Class CharacterUtilities


<CLSCompliant(False)> Public Shared Function ToUTF16(s As String) As UShort
s = s.Normalize(NormalizationForm.FormC)
Return Convert.ToUInt16(s(0))
End Function

<CLSCompliant(False)> Public Shared Function ToUTF16(ch As Char) As UShort


Return Convert.ToUInt16(ch)
End Function

' CLS-compliant alternative for ToUTF16(String).


Public Shared Function ToUTF16CodeUnit(s As String) As Integer
s = s.Normalize(NormalizationForm.FormC)
Return CInt(Convert.ToInt16(s(0)))
End Function

' CLS-compliant alternative for ToUTF16(Char).


Public Shared Function ToUTF16CodeUnit(ch As Char) As Integer
Return Convert.ToInt32(ch)
End Function

Public Function HasMultipleRepresentations(s As String) As Boolean


Dim s1 As String = s.Normalize(NormalizationForm.FormC)
Return s.Equals(s1)
End Function

Public Function GetUnicodeCodePoint(ch As Char) As Integer


If Char.IsSurrogate(ch) Then
Throw New ArgumentException("ch cannot be a high or low surrogate.")
End If
Return Char.ConvertToUtf32(ch.ToString(), 0)
End Function

Public Function GetUnicodeCodePoint(chars() As Char) As Integer


If chars.Length > 2 Then
Throw New ArgumentException("The array has too many characters.")
End If
If chars.Length = 2 Then
If Not Char.IsSurrogatePair(chars(0), chars(1)) Then
Throw New ArgumentException("The array must contain a low and a high surrogate.")
Else
Return Char.ConvertToUtf32(chars(0), chars(1))
End If
Else
Return Char.ConvertToUtf32(chars.ToString(), 0)
End If
End Function
End Class

Si vous développez une application plutôt qu'une bibliothèque (autrement dit, si vous n'exposez pas des types ou
des membres qui peuvent être utilisés par d'autres développeurs d'applications), la conformité CLS des éléments
de programme que votre application consomme n'a d'intérêt que si votre langage ne les prend pas en charge.
Dans ce cas, votre compilateur de langage générera une erreur lorsque vous essaierez d'utiliser un élément non
conforme à CLS.

Interopérabilité interlangage
L'indépendance du langage a plusieurs significations possibles. Une des significations, qui est décrite dans l’article
Indépendance du langage et composants indépendants du langage, implique la consommation en toute
transparence des types écrits dans un langage à partir d’une application écrite dans un autre langage. Une
deuxième signification, qui est le sujet de cet article, consiste à combiner du code écrit dans plusieurs langages
dans un même assembly .NET Framework.
L'exemple suivant illustre l'interopérabilité interlangage en créant une bibliothèque de classes nommée Utilities.dll
qui comprend deux classes, NumericLib et StringLib . La classe NumericLib est écrite en C# et la classe
StringLib est écrite en Visual Basic. Voici le code source de StringUtil.vb, qui comprend un seul membre,
ToTitleCase , dans sa StringLib .

Imports System.Collections.Generic
Imports System.Runtime.CompilerServices

Public Module StringLib


Private exclusions As List(Of String)

Sub New()
Dim words() As String = {"a", "an", "and", "of", "the"}
exclusions = New List(Of String)
exclusions.AddRange(words)
End Sub

<Extension()> _
Public Function ToTitleCase(title As String) As String
Dim words() As String = title.Split()
Dim result As String = String.Empty

For ctr As Integer = 0 To words.Length - 1


Dim word As String = words(ctr)
If ctr = 0 OrElse Not exclusions.Contains(word.ToLower()) Then
result += word.Substring(0, 1).ToUpper() + _
word.Substring(1).ToLower()
Else
result += word.ToLower()
End If
If ctr <= words.Length - 1 Then
result += " "
End If
Next
Return result
End Function
End Module

Voici le code source de NumberUtil.cs, qui définit une classe NumericLib qui a deux membres, IsEven et
NearZero .
using System;

public static class NumericLib


{
public static bool IsEven(this IConvertible number)
{
if (number is Byte ||
number is SByte ||
number is Int16 ||
number is UInt16 ||
number is Int32 ||
number is UInt32 ||
number is Int64)
return Convert.ToInt64(number) % 2 == 0;
else if (number is UInt64)
return ((ulong) number) % 2 == 0;
else
throw new NotSupportedException("IsEven called for a non-integer value.");
}

public static bool NearZero(double number)


{
return Math.Abs(number) < .00001;
}
}

Pour placer les deux classes dans un même assembly, vous devez les compiler dans des modules. Pour compiler le
fichier de code source Visual Basic dans un module, utilisez cette commande :

vbc /t:module StringUtil.vb

Pour plus d’informations sur la syntaxe de la ligne de commande du compilateur Visual Basic, consultez
Génération à partir de la ligne de commande.
Pour compiler le fichier de code source C# dans un module, utilisez cette commande :

csc /t:module NumberUtil.cs

Pour plus d’informations sur la syntaxe de la ligne de commande du compilateur C#, consultez Génération à partir
de la ligne de commande avec csc.exe.
Vous utilisez ensuite les options de l’éditeur de liens pour compiler les deux modules en un assembly :

link numberutil.netmodule stringutil.netmodule /out:UtilityLib.dll /dll

L'exemple suivant appelle ensuite les méthodes NumericLib.NearZero et StringLib.ToTitleCase . Notez que le code
Visual Basic et le code C# peuvent accéder aux méthodes des deux classes.
using System;

public class Example


{
public static void Main()
{
Double dbl = 0.0 - Double.Epsilon;
Console.WriteLine(NumericLib.NearZero(dbl));

string s = "war and peace";


Console.WriteLine(s.ToTitleCase());
}
}
// The example displays the following output:
// True
// War and Peace

Module Example
Public Sub Main()
Dim dbl As Double = 0.0 - Double.Epsilon
Console.WriteLine(NumericLib.NearZero(dbl))

Dim s As String = "war and peace"


Console.WriteLine(s.ToTitleCase())
End Sub
End Module
' The example displays the following output:
' True
' War and Peace

Pour compiler le code Visual Basic, utilisez cette commande :

vbc example.vb /r:UtilityLib.dll

Pour compiler avec C#, changez le nom du compilateur vbc en CSC et changez l’extension de fichier. vb en. cs :

csc example.cs /r:UtilityLib.dll

Voir aussi
CLSCompliantAttribute
Bibliothèques de framework
18/03/2020 • 6 minutes to read • Edit Online

.NET offre un grand ensemble standard de bibliothèques de classes, appelées bibliothèques de classes de base
(ensemble principal) ou bibliothèques de classes de framework (ensemble complet). Ces bibliothèques fournissent
des implémentations pour de nombreux types généraux et spécifiques d’une application, algorithmes et
fonctionnalités d’utilitaire. Les bibliothèques commerciales et de la Communauté étant créées à partir des
bibliothèques de classes de framework, elles sont tout de suite faciles à utiliser pour des tâches de calcul très
diverses.
Une partie de ces bibliothèques est fournie avec chaque implémentation .NET. Les API de bibliothèque de classes
de base doivent être incluses dans toutes les implémentations .NET parce que les développeurs le souhaitent et
parce que les bibliothèques courantes en ont besoin pour s’exécuter. Les bibliothèques spécifiques aux applications
en plus de la bibliothèque de classes de base, comme ASP.NET, ne sont pas disponibles dans toutes les
implémentations .NET.

Bibliothèques de classes de base


Les bibliothèques de classes de base fournissent les types les plus fondamentaux ainsi que les fonctionnalités
d’utilitaire et constituent la base de toutes les autres bibliothèques de classes .NET. Elles ont pour objectif de fournir
des implémentations très générales sans préférer une charge de travail plutôt qu’une autre. Les performances sont
toujours un facteur important, car les applications peuvent préférer une stratégie en particulier, comme la faible
latence par rapport au haut débit ou une mémoire insuffisante par rapport à une faible utilisation du processeur.
Ces bibliothèques sont généralement destinées à être très performantes et adoptent une approche intermédiaire
en fonction des différents besoins en performances. Pour la plupart des applications, cette approche fonctionne
plutôt bien.

Types primitifs
.NET inclut un ensemble de types primitifs qui sont utilisés (à des degrés divers) dans tous les programmes. Ces
types contiennent des données, comme des nombres, des chaînes, des octets et des objets arbitraires. Le langage
C# comprend des mots clés pour ces types. Un exemple d’ensemble de ces types est indiqué ci-dessous, avec les
mots clés C# correspondants.
System.Object (object) : Classe de base par excellence dans le système de type CRL. Elle constitue la racine de la
hiérarchie des types.
System.Int16 (short) : Type entier signé 16 bits. L’entier non signé UInt16 existe également.
System.Int32 (int) : Type entier signée 32 bits. L’entier non signé UInt32 existe également.
System.Single (float) : Type virgule flottante 32 bits.
System.Decimal (decimal) : Type décimal 128 bits.
System.Byte (byte) : Entier non signé 8 bits qui représente un octet de mémoire.
System.Boolean(bool) - Un true type false Boolean qui représente ou .
System.Char (char) : Type numérique 16 bits qui représente un caractère Unicode.
System.String (string) : Représente une série de caractères. Différent de char[] , mais permet l’indexation dans
chaque char individuel de string .

Structures de données
.NET inclut un ensemble de structures de données qui sont les bêtes de somme de presque toutes les applications
.NET. Il s’agit essentiellement de collections, mais on retrouve également d’autres types.
Array : Représente un tableau d’objets fortement typés accessibles par index. Sa taille est fixe, par sa
construction.
List<T> : Représente une liste fortement typée d’objets accessibles par index. Est automatiquement
redimensionnée en fonction des besoins.
Dictionary<TKey,TValue> : Représente une collection de valeurs indexées par une clé. Les valeurs sont
accessibles par une clé. Est automatiquement redimensionnée en fonction des besoins.
Uri : Fournit une représentation objet d’un URI (Uniform Resource Identifier) et un accès simplifié aux parties de
l’identificateur.
DateTime : Représente un instant, généralement exprimé sous la forme d’une date et d’une heure.

API d’utilitaire
.NET inclut un ensemble d’API d’utilitaire qui fournissent des fonctionnalités pour de nombreuses tâches
importantes.
HttpClient : API qui permet d’envoyer des requêtes HTTP et de recevoir des réponses HTTP d’une ressource
identifiée par un URI.
XDocument : API qui permet de charger et d’interroger des documents XML avec LINQ.
StreamReader : API qui permet de lire des fichiers.
StreamWriter : API qui permet d’écrire des fichiers.

API de modèle d’application


Il existe de nombreux modèles d’application qui peuvent être utilisés avec .NET, fournis par plusieurs sociétés.
ASP.NET : Fournit un framework web pour créer des sites et des services web. Pris en charge sur Windows,
Linux et Mac OS (dépend de la version d’ASP.NET).
Présentation des bibliothèques de classes .NET
18/07/2020 • 9 minutes to read • Edit Online

Les implémentations .NET incluent des classes, des interfaces, des délégués et des types valeur qui permettent
d'accélérer et d'optimiser le processus de développement et de fournir l'accès aux fonctions du système. Pour
faciliter l'interopérabilité entre les langages, la plupart des types .NET sont conformes CLS (Common Language
Specification) et peuvent, par conséquent, être utilisés à partir de n'importe quel langage de programmation dont
le compilateur est conforme CLS.
Les types .NET sont le fondement sur lequel les applications, composants et contrôles .NET sont construits. Les
implémentations .NET comprennent des types qui effectuent les fonctions suivantes :
Représenter les types de données de base et les exceptions.
Encapsuler les structures de données.
Effectuer les E/S.
Accéder aux informations concernant les types chargés.
Appeler les contrôles de sécurité .NET Framework.
Fournir l'accès aux données, l'interface GUI riche côté client et l'interface GUI côté client contrôlée par le
serveur.
.NET fournit un ensemble complet d'interfaces, ainsi que des classes abstraites et concrètes (non abstraites). Vous
pouvez utiliser les classes concrètes telles quelles ou, dans de nombreux cas, en dériver vos propres classes. Pour
utiliser les fonctionnalités d’une interface, vous pouvez créer une classe qui implémente l’interface ou dériver une
classe de l’une des classes .NET qui implémentent l’interface.

Conventions d’affectation de noms


Les types .NET utilisent un schéma de nommage dans lequel les points indiquent une hiérarchie. Cette technique
regroupe les types associés en espaces de noms de sorte qu'ils peuvent être recherchés et référencés plus
facilement. La première partie du nom complet ( jusqu'au point le plus à droite) constitue le nom de l'espace de
noms. La dernière partie du nom est le nom du type. Par exemple, System.Collections.Generic.List<T> représente
le type List<T> , qui appartient à l’espace de noms System.Collections.Generic . Les types dans
System.Collections.Generic peuvent être utilisés pour manipuler des collections génériques.
Pour les développeurs de bibliothèques qui étendent le .NET Framework, ce schéma de nommage facilite la
création de groupes hiérarchiques de types et l’attribution d’un nom cohérent et descriptif. Il permet également
d'identifier clairement les types par leur nom complet (autrement dit, par leur espace de noms et nom de type) et
d'empêcher les collisions de nom de type. Les développeurs de bibliothèques sont censés utiliser la convention
suivante lors de l'affectation de noms aux nouveaux espaces de noms :
NomSociété.NomTechnologie
Par exemple, l’espace de noms Microsoft.Word est conforme à cette indication.
L'utilisation de modèles d'affectation de noms pour regrouper des types associés en espaces de noms est très utile
pour générer et documenter les bibliothèques de classes. Cependant, ce schéma d’affectation de noms n’a pas
d’effet sur la visibilité, l’accès aux membres, l’héritage, la sécurité ou la liaison. Un espace de noms peut être
partitionné en plusieurs assemblys et un seul assembly peut contenir des types provenant de plusieurs espaces de
noms. L'assembly fournit la structure formelle pour le versioning, le déploiement, la sécurité, le chargement et la
visibilité dans le Common Language Runtime.
Pour plus d’informations sur les espaces de noms et les noms des types, consultez Système de type commun (CTS,
Common Type System).

System (espace de noms)


L'espace de noms System est l'espace de noms racine pour les types fondamentaux dans .NET. Cet espace de noms
comprend les classes qui représentent les types de données de base utilisés par toutes les applications : Object
(racine de la hiérarchie d'héritage), Byte, Char, Array, Int32, String, et ainsi de suite. Nombre de ces types
correspondent aux types de données primitifs que votre langage de programmation utilise. Lorsque vous écrivez
du code à l'aide des types .NET Framework, vous pouvez utiliser le mot clé correspondant de votre langage
lorsqu'un type de données de base .NET Framework est prévu.
Le tableau suivant énumère les types de base fournis par .NET, décrit brièvement chaque type et indique le type
correspondant en Visual Basic, C#, C++ et F#.

T Y P E DE T Y P E DE
N O M DE DO N N ÉES T Y P E DE DO N N ÉES C + T Y P E DE
C AT EGO RY C L A SSE DESC RIP T IO N VISUA L B A SIC DO N N ÉES C # +/ C L I DO N N ÉES F #

Integer Byte Entier non Poids byte unsigned byte


signé 8 bits. char

SByte Entier signé 8 SByte sbyte char sbyte


bits. -ou-
signed char
Non conforme
CLS.

Int16 Entier signé Cour t Résumé Résumé int16


16 bits.

Int32 Entier signé Integer int int int


de 32 bits.
-ou-

long

Int64 Entier signé Long long __int64 int64


de 64 bits.

UInt16 Entier non UShor t ushor t unsigned UInt16


signé 16 bits. shor t

Non conforme
CLS.

UInt32 Entier non UInteger uint nombre uint32


signé 32 bits. entier non
signé
Non conforme -ou-
CLS. unsigned
long
T Y P E DE T Y P E DE
N O M DE DO N N ÉES T Y P E DE DO N N ÉES C + T Y P E DE
C AT EGO RY C L A SSE DESC RIP T IO N VISUA L B A SIC DO N N ÉES C # +/ C L I DO N N ÉES F #

UInt64 Entier non Correspond corresponda unsigned uint64


signé 64 bits. ante nte __int64

Non conforme
CLS.

Virgule Single Nombre à Unique float float float32


flottante virgule or
flottante single
(32 bits)
simple
précision.

Double Nombre à Double double double float


virgule or
flottante double
(64 bits)
double
précision.

Logical Boolean Valeur Booléen bool bool bool


booléenne
(true ou false).

Autres Char Caractère Char char wchar_t char


Unicode
(16 bits).

Decimal Valeur Décimal decimal Décimal decimal


décimale
(128 bits).

IntPtr Entier signé IntPtr IntPtr IntPtr unativeint


dont la taille
dépend de la Pas de type Pas de type Pas de type
plateforme intégré. intégré. intégré.
sous-jacente
(valeur 32 bits
sur une
plateforme
32 bits et
valeur 64 bits
sur une
plateforme
64 bits).
T Y P E DE T Y P E DE
N O M DE DO N N ÉES T Y P E DE DO N N ÉES C + T Y P E DE
C AT EGO RY C L A SSE DESC RIP T IO N VISUA L B A SIC DO N N ÉES C # +/ C L I DO N N ÉES F #

UIntPtr Entier non UIntPtr UIntPtr UIntPtr unativeint


signé dont la
taille dépend Pas de type Pas de type Pas de type
de la intégré. intégré. intégré.
plateforme
sous-jacente
(valeur 32 bits
sur une
plateforme
32 bits et
valeur 64 bits
sur une
plateforme
64 bits).

Non conforme
CLS.

Object Racine de la Object object Objet ^ obj


hiérarchie
d'objet.

String Chaîne Chaîne string Chaîne ^ string


immuable à
longueur fixe
de caractères
Unicode.

En plus des types de données de base, l'espace de noms System contient plus de 100 classes, de celles qui gèrent
les exceptions à celles qui traitent des principaux concepts relatifs au runtime, tels que les domaines d'application et
le « garbage collector ». L'espace de noms System contient également de nombreux espaces de noms de deuxième
niveau.
Pour plus d’informations sur les espaces de noms, utilisez le Navigateur d’API .NET pour parcourir la bibliothèque
de classes .NET. La documentation de référence sur les API fournit des informations sur chaque espace de noms, ses
types et chacun de ses membres.

Voir aussi
Système de type commun
Navigateur de l’API .NET
Vue d'ensemble
Système de type commun
18/07/2020 • 46 minutes to read • Edit Online

Le système de type commun (CTS, Common Type System) définit la façon dont les types sont déclarés, utilisés et
managés dans le Common Language Runtime ; il constitue également une partie importante de la prise en
charge, par le runtime, de l'intégration interlangage. Le système de type commun met en œuvre les fonctions
suivantes :
Il met en place une infrastructure permettant l'intégration interlangage, la cohérence des types et
l'exécution de code hautement performant.
Il fournit un modèle orienté objet qui prend en charge l'implémentation complète de nombreux langages
de programmation.
Il définit des règles que les langages doivent respecter, ce qui permet de garantir l'interaction des objets
écrits dans différents langages.
Il fournit une bibliothèque contenant les types de données primitifs (tels que Boolean, Byte, Char, Int32 et
UInt64) utilisés dans le développement d'applications.

Types dans .NET


Tous les types dans .NET sont soit des types valeur, soit des types référence.
Les types valeur sont des types de données dont les objets sont représentés par la valeur réelle de l'objet. Si une
instance d'un type valeur est assignée à une variable, cette variable reçoit une copie actualisée de la valeur.
Les types référence sont des types de données dont les objets sont représentés par une référence (identique à
un pointeur) à la valeur réelle de l'objet. Si un type référence est assigné à une variable, celle-ci référence (ou
pointe vers) la valeur d'origine. Aucune copie n'est effectuée.
Le système de type commun (CTS, Common Type System) dans .NET prend en charge les cinq catégories de
types suivantes :
Classes
Structures
Énumérations
Interfaces
Délégués
Classes
Une classe est un type référence qui peut être dérivé directement d'une autre classe et qui est implicitement
dérivé de System.Object. Une classe définit les opérations qu'un objet (qui est une instance de la classe) peut
effectuer (méthodes, événements ou propriétés) et les données que l'objet contient (champs). Bien qu'une classe
inclue généralement la définition et l'implémentation (contrairement aux interfaces, par exemple, qui
contiennent uniquement la définition sans l'implémentation), elle peut comporter un ou plusieurs membres
dépourvus d'implémentation.
Le tableau suivant décrit certaines des caractéristiques qu'une classe peut avoir. Chaque langage prenant en
charge le runtime fournit un moyen d'indiquer qu'une classe ou qu'un membre d'une classe a une ou plusieurs
de ces caractéristiques. Cependant, il se peut que des langages de programmation individuels qui ciblent .NET ne
mettent pas toutes ces caractéristiques à disposition.

C A RA C T ÉRIST IQ UE DESC RIP T IO N

sealed Spécifie qu'une autre classe ne peut pas être dérivée de ce


type.

implémente Indique que la classe utilise une ou plusieurs interfaces en


fournissant des implémentations des membres d'interface.

abstract Indique que la classe ne peut pas être instanciée. Pour


l'utiliser, vous devez dériver une autre classe de celle-ci.

hérite Indique que des instances de la classe peuvent être utilisées


partout où la classe de base est spécifiée. Une classe dérivée
qui hérite d'une classe de base peut utiliser l'implémentation
de n'importe quel membre public fourni par la classe de
base, ou la classe dérivée peut remplacer l'implémentation
des membres publics par sa propre implémentation.

exported ou not exported Indique si une classe est visible à l'extérieur de l'assembly
dans lequel elle est définie. Cette caractéristique s'applique
uniquement aux classes de niveau supérieur, et pas aux
classes imbriquées.

NOTE
Une classe peut également être imbriquée dans une structure ou une classe parente. Les classes imbriquées ont
également des caractéristiques de membre. Pour plus d’informations, consultez types imbriqués.

Les membres de classe sans implémentation sont des membres abstraits. Une classe qui possède un ou
plusieurs membres abstraits est elle-même abstraite ; il n'est pas possible d'en créer de nouvelles instances.
Certains langages qui ciblent le runtime vous permettent de marquer une classe comme abstraite même si
aucun de ses membres n'est abstrait. Vous pouvez utiliser une classe abstraite lorsque vous voulez encapsuler
un ensemble de fonctionnalités de base dont des classes dérivées peuvent hériter ou qu'elles peuvent substituer
lorsque cela est approprié. Les classes qui ne sont pas abstraites sont qualifiées de classes concrètes.
Une classe peut implémenter n'importe quel nombre d'interfaces, mais elle ne peut hériter que d'une seule
classe de base en plus de la classe System.Object, de laquelle toutes les classes héritent implicitement. Toutes les
classes doivent avoir au moins un constructeur qui initialise de nouvelles instances de la classe. Si vous ne
définissez pas explicitement un constructeur, la plupart des compilateurs fournissent automatiquement un
constructeur sans paramètre.
Structures
Une structure est un type valeur qui dérive implicitement de System.ValueType qui, à son tour, est dérivé de
System.Object. Une structure est utile pour représenter des valeurs dont les besoins en mémoire sont faibles et
pour passer des valeurs en tant que paramètres par valeur à des méthodes qui ont des paramètres fortement
typés. Dans .NET, tous les types de données primitifs (Boolean, Byte, Char, DateTime, Decimal, Double, Int16,
Int32, Int64, SByte, Single, UInt16, UInt32 et UInt64) sont définis en tant que structures.
Comme les classes, les structures définissent à la fois les données (les champs de la structure) et les opérations
qui peuvent être exécutées sur ces données (les méthodes de la structure). Cela signifie que vous pouvez appeler
des méthodes sur des structures, notamment les méthodes virtuelles définies sur les classes System.Object et
System.ValueType, ainsi que toute méthode définie sur le type valeur lui-même. En d'autres termes, les
structures peuvent comporter des champs, des propriétés et des événements, ainsi que des méthodes statiques
et non statiques. Vous pouvez créer des instances de structures, les passer en tant que paramètres, les stocker en
tant que variables locales ou les stocker dans un champ d'un autre type valeur ou type référence. Les structures
peuvent aussi implémenter des interfaces.
Les types valeur diffèrent également des classes par plusieurs aspects. Tout d'abord, bien qu'ils héritent
implicitement de System.ValueType, ils ne peuvent pas hériter directement de n'importe quel type. De même,
tous les types valeur sont sealed, ce qui signifie qu'aucun autre type ne peut en être dérivé. De plus, ils ne
nécessitent pas de constructeurs.
Pour chaque type valeur, le Common Language Runtime fournit un type boxed correspondant qui est une classe
ayant le même état et le même comportement que le type valeur. Une instance d'un type valeur est boxed
lorsqu'elle est passée à une méthode qui accepte un paramètre de type System.Object. Elle est unboxed (c'est-à-
dire reconvertie d'une instance d'une classe en instance d'un type valeur) lorsque le contrôle retourne d'un
appel de méthode qui accepte un type valeur comme paramètre par référence. Certains langages imposent
l'utilisation d'une syntaxe spéciale lorsque le type boxed est requis ; d'autres utilisent automatiquement le type
boxed lorsqu'il est nécessaire. Lorsque vous définissez un type valeur, vous définissez le type boxed et le type
unboxed.
Énumérations
Une énumération est un type valeur qui hérite directement de System.Enum et qui fournit des noms de
remplacement pour les valeurs d’un type primitif sous-jacent. Un type énumération a un nom, un type sous-
jacent qui doit être l'un des types d'entiers signés ou non signés intégrés (tels que Byte, Int32 ou UInt64) et un
ensemble de champs. Les champs sont des champs statiques de type Literal, chacun représentant une constante.
La même valeur peut être assignée à plusieurs champs. Dans ce cas, vous devez marquer l'une des valeurs
comme valeur d'énumération primaire pour la réflexion et la conversion de chaînes.
Vous pouvez assigner une valeur du type sous-jacent à une énumération et vice-versa (aucun cast n'est requis
par le runtime). Vous pouvez créer une instance d'une énumération et appeler les méthodes de System.Enum,
ainsi que toute méthode définie sur le type sous-jacent de l'énumération. Cependant, il est possible que certains
langages ne vous permettent pas de passer une énumération en tant que paramètre lorsqu'une instance du type
sous-jacent est requise (ou vice versa).
Les restrictions supplémentaires suivantes s'appliquent aux énumérations :
Elles ne peuvent pas définir leurs propres méthodes.
Elles ne peuvent pas implémenter d'interfaces.
Elles ne peuvent pas définir des propriétés ou des événements.
Elles ne peuvent pas être génériques, à moins qu'elles le soient uniquement parce qu'elles sont
imbriquées dans un type générique. Par conséquent, une énumération ne peut pas avoir de paramètres
de type propres.

NOTE
Les types imbriqués (y compris les énumérations) créés avec Visual Basic, C# et C++ incluent les paramètres de
type de tous les types génériques englobants, et sont donc génériques, même s’ils n’ont pas de paramètres de
type propres. Pour plus d'informations, consultez « Types imbriqués » de la rubrique de référence relative à
Type.MakeGenericType.

L'attribut FlagsAttribute désigne un genre particulier d'énumération appelé « champ de bits ». Le runtime lui-
même ne fait pas de distinction entre les énumérations traditionnelles et les champs de bits, mais votre langage
pourrait le faire. Lorsque cette distinction est effectuée, les opérateurs binaires peuvent être utilisés sur les
champs de bits, mais pas sur les énumérations, pour générer des valeurs sans nom. Les énumérations sont
généralement utilisées pour des listes d'éléments uniques, tels que les jours de la semaine, des noms de pays ou
de région, etc. Les champs de bits sont généralement utilisés pour des listes de qualités ou de quantités pouvant
être utilisées en combinaison, telle que Red And Big And Fast .
L'exemple suivant montre comment utiliser les champs de bits et les énumérations traditionnelles.

using System;
using System.Collections.Generic;

// A traditional enumeration of some root vegetables.


public enum SomeRootVegetables
{
HorseRadish,
Radish,
Turnip
}

// A bit field or flag enumeration of harvesting seasons.


[Flags]
public enum Seasons
{
None = 0,
Summer = 1,
Autumn = 2,
Winter = 4,
Spring = 8,
All = Summer | Autumn | Winter | Spring
}

public class Example


{
public static void Main()
{
// Hash table of when vegetables are available.
Dictionary<SomeRootVegetables, Seasons> AvailableIn = new Dictionary<SomeRootVegetables, Seasons>();

AvailableIn[SomeRootVegetables.HorseRadish] = Seasons.All;
AvailableIn[SomeRootVegetables.Radish] = Seasons.Spring;
AvailableIn[SomeRootVegetables.Turnip] = Seasons.Spring |
Seasons.Autumn;

// Array of the seasons, using the enumeration.


Seasons[] theSeasons = new Seasons[] { Seasons.Summer, Seasons.Autumn,
Seasons.Winter, Seasons.Spring };

// Print information of what vegetables are available each season.


foreach (Seasons season in theSeasons)
{
Console.Write(String.Format(
"The following root vegetables are harvested in {0}:\n",
season.ToString("G")));
foreach (KeyValuePair<SomeRootVegetables, Seasons> item in AvailableIn)
{
// A bitwise comparison.
if (((Seasons)item.Value & season) > 0)
Console.Write(String.Format(" {0:G}\n",
(SomeRootVegetables)item.Key));
}
}
}
}
// The example displays the following output:
// The following root vegetables are harvested in Summer:
// HorseRadish
// The following root vegetables are harvested in Autumn:
// Turnip
// HorseRadish
// The following root vegetables are harvested in Winter:
// HorseRadish
// HorseRadish
// The following root vegetables are harvested in Spring:
// Turnip
// Radish
// HorseRadish
Imports System.Collections.Generic

' A traditional enumeration of some root vegetables.


Public Enum SomeRootVegetables
HorseRadish
Radish
Turnip
End Enum

' A bit field or flag enumeration of harvesting seasons.


<Flags()> Public Enum Seasons
None = 0
Summer = 1
Autumn = 2
Winter = 4
Spring = 8
All = Summer Or Autumn Or Winter Or Spring
End Enum

' Entry point.


Public Class Example
Public Shared Sub Main()
' Hash table of when vegetables are available.
Dim AvailableIn As New Dictionary(Of SomeRootVegetables, Seasons)()

AvailableIn(SomeRootVegetables.HorseRadish) = Seasons.All
AvailableIn(SomeRootVegetables.Radish) = Seasons.Spring
AvailableIn(SomeRootVegetables.Turnip) = Seasons.Spring Or _
Seasons.Autumn

' Array of the seasons, using the enumeration.


Dim theSeasons() As Seasons = {Seasons.Summer, Seasons.Autumn, _
Seasons.Winter, Seasons.Spring}

' Print information of what vegetables are available each season.


For Each season As Seasons In theSeasons
Console.WriteLine(String.Format( _
"The following root vegetables are harvested in {0}:", _
season.ToString("G")))
For Each item As KeyValuePair(Of SomeRootVegetables, Seasons) In AvailableIn
' A bitwise comparison.
If (CType(item.Value, Seasons) And season) > 0 Then
Console.WriteLine(" " + _
CType(item.Key, SomeRootVegetables).ToString("G"))
End If
Next
Next
End Sub
End Class
' The example displays the following output:
' The following root vegetables are harvested in Summer:
' HorseRadish
' The following root vegetables are harvested in Autumn:
' Turnip
' HorseRadish
' The following root vegetables are harvested in Winter:
' HorseRadish
' The following root vegetables are harvested in Spring:
' Turnip
' Radish
' HorseRadish

Interfaces
Une interface définit un contrat qui spécifie une relation « peut faire » ou « a un ». Les interfaces permettent
souvent d'implémenter des fonctionnalités, comme la comparaison et le tri (interfaces IComparable et
IComparable<T>), le test pour l'égalité (interface IEquatable<T>) ou l'énumération d'éléments dans une
collection (interfaces IEnumerable et IEnumerable<T>). Les interfaces peuvent avoir des propriétés, des
méthodes et des événements qui sont tous des membres abstraits, c'est-à-dire que bien que l'interface définisse
les membres et leurs signatures, elle laisse le type qui implémente l'interface définir la fonctionnalité de chaque
membre d'interface. En d'autres termes, toute classe ou structure qui implémente une interface doit fournir des
définitions pour les membres abstraits déclarés dans l'interface. Une interface peut imposer à une classe ou à
une structure d'implémentation d'implémenter une ou plusieurs autres interfaces.
Les restrictions suivantes s'appliquent aux interfaces :
Une interface peut être déclarée avec n'importe quelle accessibilité, mais les membres d'interface doivent
tous avoir une accessibilité publique.
Les interfaces ne peuvent pas définir de constructeurs.
Les interfaces ne peuvent pas définir de champs.
Les interfaces peuvent définir uniquement des membres d'instance. Elles ne peuvent pas définir des
membres statiques.
Chaque langage doit fournir des règles de mappage d'une implémentation à l'interface qui nécessite le membre,
car plusieurs interfaces peuvent déclarer un membre avec la même signature, et ces membres peuvent avoir des
implémentations séparées.
Délégués
Les délégués sont des types référence qui jouent un rôle similaire à celui des pointeurs fonction en C++. Ils sont
utilisés pour les gestionnaires d’événements et les fonctions de rappel dans .NET. Contrairement aux pointeurs
fonction, les délégués sont de type sécurisé, sûr et vérifiable. Un type délégué peut représenter n'importe quelle
méthode d'instance ou méthode statique ayant une signature compatible.
Le paramètre d'un délégué est compatible avec le paramètre correspondant d'une méthode si le type de
paramètre du délégué est plus restrictif que le type de paramètre de la méthode. En effet, cela garantit qu'un
argument transmis au délégué peut être transmis à la méthode en toute sécurité.
De même, le type de retour d’un délégué est compatible avec le type de retour d’une méthode si le type de
retour de la méthode est plus restrictif que le type de retour du délégué, car cela garantit que le cast de la valeur
de retour de la méthode peut être effectué sans risque au type de retour du délégué.
Par exemple, un délégué ayant un paramètre de type IEnumerable et un type de retour Object peut représenter
une méthode ayant un paramètre de type Object et une valeur de retour de type IEnumerable. Pour obtenir des
informations supplémentaires ainsi qu'un code d'exemple, consultez Delegate.CreateDelegate(Type, Object,
MethodInfo).
On dit qu'un délégué est lié à la méthode qu'il représente. Outre cette liaison, un délégué peut être lié à un objet.
L'objet représente le premier paramètre de la méthode, et est passé à cette dernière chaque fois que le délégué
est appelé. Si la méthode est une méthode d'instance, l'objet dépendant est passé en tant que paramètre this
implicite ( Me en Visual Basic) ; si la méthode est statique, l'objet est passé en tant que premier paramètre formel
de la méthode, et la signature du délégué doit correspondre aux paramètres restants. Pour obtenir des
informations supplémentaires ainsi qu'un code d'exemple, consultez System.Delegate.
Tous les délégués héritent de System.MulticastDelegate, qui hérite de System.Delegate. Les langages C#, Visual
Basic et C++ n'autorisent pas l'héritage à partir de ces types. Ils fournissent à la place des mots clés pour la
déclaration des délégués.
Étant donné que les délégués héritent de MulticastDelegate, un délégué dispose d'une liste d'appel, liste des
méthodes représentées par le délégué et exécutées lorsque celui-ci est appelé. Toutes les méthodes de la liste
reçoivent les arguments fournis lorsque le délégué est appelé.
NOTE
La valeur de retour n'est pas définie pour un délégué dont la liste d'appel comprend plusieurs méthodes, même si le
délégué dispose d'un type de retour.

Dans la plupart des cas, comme avec les méthodes de rappel, un délégué représente une seule méthode, et les
mesures que vous devez prendre se limitent à la création et l'appel du délégué.
Pour les délégués qui représentent plusieurs méthodes, .NET fournit les méthodes des classes déléguées
Delegate et MulticastDelegate pour prendre en charge des opérations telles que l'ajout d'une méthode à la liste
d'appel d'un délégué (méthode Delegate.Combine), la suppression d'une méthode (méthode Delegate.Remove)
et l'obtention de la liste d'appel (méthode Delegate.GetInvocationList).

NOTE
Il n'est pas nécessaire d'utiliser ces méthodes pour les délégués de gestionnaires d'événements en C#, C++ et Visual Basic,
car ces langages fournissent une syntaxe pour l'ajout et la suppression des gestionnaires d'événements.

Définitions de type
Une définition de type inclut les éléments suivants :
les attributs définis sur le type ;
L'accessibilité du type (visibilité).
le nom du type ;
le type de base du type ;
les interfaces implémentées par le type ;
les définitions de chacun des membres du type.
Attributs
Les attributs fournissent des métadonnées définies par l'utilisateur supplémentaires. La plupart du temps, ils
sont utilisés pour stocker les informations supplémentaires relatives à un type de l’assembly, ou pour modifier le
comportement d’un membre de type dans l’environnement au moment du design ou dans l’environnement
d’exécution.
Les attributs sont des classes qui héritent de System.Attribute. Les langages qui prennent en charge l'utilisation
d'attributs ont chacun leur propre syntaxe pour l'application d'attributs à un élément du langage. Les attributs
peuvent être appliqués à presque n’importe quel élément de langage ; les éléments spécifiques auxquels un
attribut peut être appliqué sont définis par l’AttributeUsageAttribute qui est appliqué à cette classe d’attributs.
Accessibilité des types
Tous les types ont un modificateur qui régit leur accessibilité à partir d'autres types. Le tableau suivant décrit les
accessibilités de type prises en charge par le runtime.

A C C ESSIB IL IT É DESC RIP T IO N

public Le type est accessible par tous les assemblys.

assembly Le type est accessible uniquement à partir de cet assembly.


L'accessibilité d'un type imbriqué dépend de son domaine d'accessibilité, qui est déterminé par l'accessibilité
déclarée du membre et le domaine d'accessibilité du type conteneur immédiat. Toutefois, le domaine
d'accessibilité d'un type imbriqué ne peut pas dépasser celui du type conteneur.
Le domaine d'accessibilité d'un membre imbriqué M déclaré dans un type T à l'intérieur d'un programme P
est défini de la manière suivante (en notant que M pourrait lui-même être un type) :
Si l'accessibilité déclarée de M est public , le domaine d'accessibilité de M correspond à celui de T .
Si l'accessibilité déclarée de M est protected internal , le domaine d'accessibilité de M correspond à
l'intersection du domaine d'accessibilité de T avec le texte de programme de P et le texte de
programme de n'importe quel type dérivé de T déclaré en dehors de P .
Si l'accessibilité déclarée de M est protected , le domaine d'accessibilité de M correspond à
l'intersection du domaine d'accessibilité de T avec le texte de programme de T et n'importe quel type
dérivé de T .
Si l'accessibilité déclarée de M est internal , le domaine d'accessibilité de M correspond à l'intersection
du domaine d'accessibilité de T avec le texte de programme de P .
Si l'accessibilité déclarée de M est private , le domaine d'accessibilité de M correspond au texte de
programme de T .
Noms de types
Le système de type commun (CTS, Common Type System) impose uniquement deux restrictions sur les noms :
Tous les noms sont encodés comme des chaînes de caractères Unicode (16 bits).
Les noms ne peuvent pas incorporer la valeur (16 bits) 0x0000.
Toutefois, la plupart des langages imposent des restrictions supplémentaires sur les noms de types. Toutes les
comparaisons sont effectuées octet par octet ; elles respectent donc la casse et sont indépendantes des
paramètres régionaux.
Bien qu’un type puisse référencer des types d’autres modules et assemblys, il doit être entièrement défini à
l’intérieur d’un seul module .NET. (En fonction de la prise en charge du compilateur, toutefois, il peut être divisé
en plusieurs fichiers de code source.) Les noms de types doivent être uniques uniquement dans un espace de
noms. Pour identifier complètement un type, le nom du type doit être qualifié par l'espace de noms qui contient
l'implémentation du type.
Types de base et interfaces
Un type peut hériter des valeurs et des comportements d'un autre type. Le système de type commun (CTS,
Common Type System) ne permet pas à des types d'hériter de plusieurs types de base.
Un type peut implémenter n'importe quel nombre d'interfaces. Pour implémenter une interface, un type doit
implémenter tous les membres virtuels de cette interface. Une méthode virtuelle peut être implémentée par un
type dérivé et peut être appelée de manière statique ou dynamique.

Membres de type
Le runtime vous permet de définir les membres de votre type, ce qui spécifie le comportement et l'état d'un
type. Les membres de type incluent les éléments suivants :
Fields
Propriétés
Méthodes
Constructeurs
Événements
Types imbriqués
Champs
Un champ décrit et contient une partie de l'état du type. Les champs peuvent correspondre à n'importe quel
type pris en charge par le runtime. La plupart du temps, les champs sont soit private , soit protected , de sorte
qu'ils sont accessibles uniquement à partir de la classe ou d'une classe dérivée. Si la valeur d'un champ peut être
modifiée en dehors de son type, un accesseur set de propriété est en général utilisé. Les champs exposés
publiquement sont généralement en lecture seule et peuvent être de deux types :
Constantes, dont la valeur est assignée au moment du design. Ce sont des membres statiques d'une
classe, bien qu'ils ne soient pas définis à l'aide du mot clé static ( Shared en Visual Basic).
Des variables en lecture seule, dont les valeurs peuvent être assignées dans le constructeur de classe.
L'exemple ci-dessous illustre ces deux utilisations de champs en lecture seule.

using System;

public class Constants


{
public const double Pi = 3.1416;
public readonly DateTime BirthDate;

public Constants(DateTime birthDate)


{
this.BirthDate = birthDate;
}
}

public class Example


{
public static void Main()
{
Constants con = new Constants(new DateTime(1974, 8, 18));
Console.Write(Constants.Pi + "\n");
Console.Write(con.BirthDate.ToString("d") + "\n");
}
}
// The example displays the following output if run on a system whose current
// culture is en-US:
// 3.1416
// 8/18/1974
Public Class Constants
Public Const Pi As Double = 3.1416
Public ReadOnly BirthDate As Date

Public Sub New(birthDate As Date)


Me.BirthDate = birthDate
End Sub
End Class

Public Module Example


Public Sub Main()
Dim con As New Constants(#8/18/1974#)
Console.WriteLine(Constants.Pi.ToString())
Console.WriteLine(con.BirthDate.ToString("d"))
End Sub
End Module
' The example displays the following output if run on a system whose current
' culture is en-US:
' 3.1416
' 8/18/1974

Propriétés
Une propriété nomme une valeur ou un état du type et définit des méthodes pour obtenir ou définir la valeur de
la propriété. Les propriétés peuvent être des types primitifs, des collections de types primitifs, des types définis
par l'utilisateur ou des collections de types définis par l'utilisateur. Les propriétés sont souvent utilisées pour
maintenir l'interface publique d'un type indépendante de la représentation réelle du type. Cela permet aux
propriétés de refléter des valeurs qui ne sont pas directement stockées dans la classe (par exemple, lorsqu'une
propriété retourne une valeur calculée) ou d'effectuer une validation avant que des valeurs soient assignées à
des champs privés. L’exemple suivant illustre ce dernier modèle.

using System;

public class Person


{
private int m_Age;

public int Age


{
get { return m_Age; }
set {
if (value < 0 || value > 125)
{
throw new ArgumentOutOfRangeException("The value of the Age property must be between 0 and
125.");
}
else
{
m_Age = value;
}
}
}
}
Public Class Person
Private m_Age As Integer

Public Property Age As Integer


Get
Return m_Age
End Get
Set
If value < 0 Or value > 125 Then
Throw New ArgumentOutOfRangeException("The value of the Age property must be between 0 and
125.")
Else
m_Age = value
End If
End Set
End Property
End Class

En plus d’inclure la propriété elle-même, le langage MSIL (Microsoft Intermediate Language) pour un type qui
contient une propriété lisible inclut une get_ méthode PropertyName , et le MSIL pour un type qui contient une
propriété accessible en écriture inclut une set_ méthode PropertyName .
Méthodes
Une méthode décrit les opérations qui sont disponibles sur le type. La signature d'une méthode spécifie les
types autorisés de tous ses paramètres et de sa valeur de retour.
Bien que la plupart des méthodes définissent le nombre précis de paramètres requis pour les appels de
méthode, certaines méthodes acceptent un nombre variable de paramètres. Le dernier paramètre déclaré de ces
méthodes est marqué avec l'attribut ParamArrayAttribute. Les compilateurs de langage fournissent
généralement un mot clé, tel que params en C# et ParamArray en Visual Basic, qui rendent l'utilisation explicite
de ParamArrayAttribute inutile.
Constructeurs
Un constructeur est un genre de méthode particulier qui crée de nouvelles instances d'une classe ou d'une
structure. Comme n'importe quelle autre méthode, un constructeur peut inclure des paramètres ; toutefois, les
constructeurs n'ont pas de valeur de retour (en d'autres termes, ils retournent void ).
Si le code source d’une classe ne définit pas explicitement un constructeur, le compilateur inclut un constructeur
sans paramètre. Toutefois, si le code source d'une classe définit uniquement des constructeurs paramétrables,
les compilateurs Visual Basic et C# ne génèrent pas de constructeur sans paramètre.
Si le code source d’une structure définit des constructeurs, ceux-ci doivent être paramétrés ; une structure ne
peut pas définir un constructeur sans paramètre et les compilateurs ne génèrent pas de constructeur sans
paramètre pour les structures ou autres types valeur. Tous les types valeurs disposent d’un constructeur sans
paramètre implicite. Ce constructeur est implémenté par le Common Language Runtime et initialise tous les
champs de la structure avec leurs valeurs par défaut.
Événements
Un événement définit un incident auquel il est possible de répondre, et définit les méthodes permettant de
s'abonner à l'événement, d'annuler cet abonnement et de déclencher l'événement. Les événements sont souvent
utilisés pour signaler d'autres types de changements d'état. Pour plus d’informations, consultez Événements.
Types imbriqués
Un type imbriqué est un type qui est un membre d'un autre type. Un type imbriqué doit être fortement couplé
avec le type qui le contient et ne doit pas être utilisé en tant que type à usage général. Ceux-ci sont utiles lorsque
le type de déclaration utilise et crée des instances du type imbriqué et que l'utilisation du type imbriqué n'est pas
exposée dans des membres publics.
Certains développeurs éprouvent des difficultés à appréhender correctement les types imbriqués. Il faut savoir
qu'ils ne doivent pas être publiquement visibles sauf s'il existe une bonne raison à cela. Dans une bibliothèque
bien conçue, les développeurs doivent rarement avoir à utiliser des types imbriqués pour instancier des objets
ou déclarer des variables.

Caractéristiques des membres de type


Le système de type commun (CTS, Common Type System) permet aux membres de type d'avoir diverses
caractéristiques ; toutefois, les langages ne doivent pas obligatoirement prendre en charge toutes ces
caractéristiques. Le tableau suivant décrit les caractéristiques des membres.

C A RA C T ÉRIST IQ UE A P P L IC A B L E À DESC RIP T IO N

abstract Méthodes, propriétés et événements Le type n'assure pas l'implémentation


de la méthode. Les types qui héritent
de méthodes abstraites ou qui en
implémentent doivent fournir une
implémentation pour la méthode. Une
exception : le type dérivé est lui-même
un type abstrait. Toutes les méthodes
abstraites sont virtuelles.

private, family, assembly, family et Tous Définit l'accessibilité du membre :


assembly, family ou assembly, ou
public private
Accessible uniquement à partir du
même type que le membre ou à
l'intérieur d'un type imbriqué.

famille
Accessible à partir du même type que
le membre et à partir des types dérivés
qui en héritent.

assembly
Accessible uniquement dans l'assembly
dans lequel le type est défini.

family et assembly
Accessible uniquement à partir des
types qui se qualifient pour un accès
family et assembly.

family ou assembly
Accessible uniquement à partir des
types qui se qualifient pour un accès
family ou assembly.

public
Accessible à partir de n'importe quel
type.

final Méthodes, propriétés et événements La méthode virtuelle ne peut pas être


substituée dans un type dérivé.

initialize-only Champs La valeur peut seulement être


initialisée et ne peut pas être écrite
après initialisation.
C A RA C T ÉRIST IQ UE A P P L IC A B L E À DESC RIP T IO N

instance Champs, méthodes, propriétés et Si un membre n'est pas marqué


événements comme static (C# et C++),
Shared (Visual Basic), virtual (C#
et C++) ou Overridable (Visual
Basic), il est membre d'instance (il n'y a
pas de mot clé d'instance). La mémoire
comptera autant de copies de tels
membres que d'objets qui les utilisent.

littéral Champs La valeur assignée au champ est une


valeur fixe, connue au moment de la
compilation, d'un type valeur intégré.
Les champs de type Literal sont parfois
qualifiés de constantes.

newslot ou override Tous Définit la façon dont le membre


interagit avec des membres hérités
ayant la même signature :

newslot
Masque les membres hérités ayant la
même signature.

override
Remplace la définition d'une méthode
virtuelle héritée.

La valeur par défaut est newslot.

static Champs, méthodes, propriétés et Le membre appartient au type sur


événements lequel il est défini, et non à une
instance particulière du type ; le
membre existe même si une instance
du type n'est pas créée, et il est
partagé entre toutes les instances du
type.

virtual Méthodes, propriétés et événements La méthode peut être implémentée


par un type dérivé et peut être appelée
de manière statique ou dynamique. Si
un appel dynamique est utilisé, le type
de l'instance qui effectue l'appel au
moment de l'exécution détermine
quelle implémentation de la méthode
est appelée (plutôt que le type connu
au moment de la compilation). Pour
appeler une méthode virtuelle de
manière statique, un cast en un type
qui utilise la version désirée de la
méthode devra éventuellement être
effectué sur la variable.

Surcharge
Chaque membre de type a une signature unique. Les signatures de méthode sont composées du nom de la
méthode, d'une liste de paramètres (l'ordre et les types des arguments de la méthode). Plusieurs méthodes du
même nom peuvent être définies dans un type à condition que leurs signatures diffèrent. Lorsque plusieurs
méthodes du même nom sont définies, la méthode est dite surchargée. Par exemple, dans System.Char, la
méthode IsDigit est surchargée. Une méthode prend un Char. L'autre méthode prend un String et un Int32.
NOTE
Le type de retour n’est pas considéré comme une partie de la signature d’une méthode. En d'autres termes, les méthodes
ne peuvent pas être surchargées si seul le type de retour les différencie.

Hériter, substituer et masquer les membres


Un type dérivé hérite de tous les membres de son type de base ; c'est-à-dire que ces membres sont définis sur le
type dérivé et disponibles pour celui-ci. Le comportement ou les qualités de membres hérités peuvent être
modifiés de deux manières :
Un type dérivé peut masquer un membre hérité en définissant un nouveau membre avec la même
signature. Cela permet notamment de rendre privé un membre précédemment public ou de définir un
nouveau comportement pour une méthode héritée marquée comme final .
Un type dérivé peut substituer une méthode virtuelle héritée. La méthode de substitution fournit une
nouvelle définition de la méthode qui sera appelée en fonction du type de la valeur au moment de
l'exécution et non du type de la variable connue au moment de la compilation. Une méthode peut
substituer une méthode virtuelle uniquement si la méthode virtuelle n'est pas marquée comme final et
si la nouvelle méthode est au moins aussi accessible que la méthode virtuelle.

Voir aussi
Explorateur d’API .NET
Common Language Runtime
Conversion de type dans .NET
Conversion de type dans le .NET Framework
18/07/2020 • 42 minutes to read • Edit Online

Chaque valeur a un type associé qui définit des attributs, tels la quantité d'espace allouée à la valeur, la plage de
valeurs possibles et les membres qu'il rend disponibles. De nombreuses valeurs peuvent être exprimées sous
forme de plusieurs types. Par exemple, la valeur 4 peut être exprimée sous forme d'un entier ou d'une valeur à
virgule flottante. La conversion de type crée une valeur dans un nouveau type qui est équivalente à la valeur d'un
ancien type, mais ne préserve pas nécessairement l'identité (ou la valeur exacte) de l'objet d'origine.
Le .NET Framework prend automatiquement en charge les conversions suivantes :
Conversion d’une classe dérivée vers une classe de base. Cela signifie, par exemple, qu’une instance d’une
classe ou d’une structure peut être convertie en instance Object. Cette conversion ne nécessite pas
d’opérateur de conversion ou de cast.
Reconversion d’une classe de base vers la classe dérivée d’origine. En C#, cette conversion nécessite un
opérateur de cast. En Visual Basic, elle nécessite l’opérateur CType si Option Strict est activée.
Conversion d’un type qui implémente une interface vers un objet d’interface qui représente cette interface.
Cette conversion ne nécessite pas d’opérateur de conversion ou de cast.
Reconversion d’un objet d’interface vers le type d’origine qui implémente cette interface. En C#, cette
conversion nécessite un opérateur de cast. En Visual Basic, elle nécessite l’opérateur CType si
Option Strict est activée.

En plus de ces conversions automatiques, .NET Framework fournit plusieurs fonctionnalités qui prennent en
charge la conversion personnalisée. Ces options en question sont les suivantes :
L'opérateur Implicit , qui définit les conversions étendues disponibles entre des types. Pour plus
d’informations, consultez la section conversion implicite avec l’opérateur Implicit .
L'opérateur Explicit , qui définit les conversions restrictives entre des types. Pour plus d’informations,
consultez la section conversion explicite avec l’opérateur Explicit .
L'interface IConvertible, qui définit des conversions vers chacun des types de données .NET Framework de
base. Pour plus d’informations, consultez la section Interface IConvertible.
La classe Convert, qui fournit un ensemble de méthodes qui implémentent les méthodes dans l'interface
IConvertible. Pour plus d’informations, consultez la section Classe Convert.
La classe TypeConverter, qui est une classe de base qui peut être étendue pour prendre en charge la
conversion d'un type spécifié vers un autre type. Pour plus d’informations, consultez la section Classe
TypeConverter.

Conversion implicite avec l'opérateur Implicit


Les conversions étendues impliquent la création d'une valeur à partir de la valeur d'un type existant dont la plage
est plus restrictive ou qui contient une liste de membres plus restreinte que le type cible. Les conversions étendues
ne peuvent pas entraîner de perte de données (bien qu'elles puissent produire un résultat de moindre précision).
Étant donné qu'aucune donnée ne peut être perdue, les compilateurs peuvent gérer la conversion de façon
implicite ou transparente, sans exiger l'utilisation d'une méthode de conversion explicite ou d'un opérateur de
cast.
NOTE
Même si le code qui exécute une conversion implicite peut appeler une méthode de conversion ou utiliser un opérateur de
cast, son utilisation n'est pas requise par les compilateurs qui prennent en charge les conversions implicites.

Par exemple, le type Decimal prend en charge les conversions implicites à partir des valeurs Byte, Char, Int16,
Int32, Int64, SByte, UInt16, UInt32 et UInt64. L'exemple suivant illustre certaines de ces conversions implicites en
assignant des valeurs à une variable Decimal.

byte byteValue = 16;


short shortValue = -1024;
int intValue = -1034000;
long longValue = 1152921504606846976;
ulong ulongValue = UInt64.MaxValue;

decimal decimalValue;

decimalValue = byteValue;
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
byteValue.GetType().Name, decimalValue);

decimalValue = shortValue;
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
shortValue.GetType().Name, decimalValue);

decimalValue = intValue;
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
intValue.GetType().Name, decimalValue);

decimalValue = longValue;
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
longValue.GetType().Name, decimalValue);

decimalValue = ulongValue;
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
longValue.GetType().Name, decimalValue);
// The example displays the following output:
// After assigning a Byte value, the Decimal value is 16.
// After assigning a Int16 value, the Decimal value is -1024.
// After assigning a Int32 value, the Decimal value is -1034000.
// After assigning a Int64 value, the Decimal value is 1152921504606846976.
// After assigning a Int64 value, the Decimal value is 18446744073709551615.
Dim byteValue As Byte = 16
Dim shortValue As Short = -1024
Dim intValue As Integer = -1034000
Dim longValue As Long = CLng(1024 ^ 6)
Dim ulongValue As ULong = ULong.MaxValue

Dim decimalValue As Decimal

decimalValue = byteValue
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
byteValue.GetType().Name, decimalValue)

decimalValue = shortValue
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
shortValue.GetType().Name, decimalValue)

decimalValue = intValue
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
intValue.GetType().Name, decimalValue)

decimalValue = longValue
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
longValue.GetType().Name, decimalValue)

decimalValue = ulongValue
Console.WriteLine("After assigning a {0} value, the Decimal value is {1}.",
longValue.GetType().Name, decimalValue)
' The example displays the following output:
' After assigning a Byte value, the Decimal value is 16.
' After assigning a Int16 value, the Decimal value is -1024.
' After assigning a Int32 value, the Decimal value is -1034000.
' After assigning a Int64 value, the Decimal value is 1152921504606846976.
' After assigning a Int64 value, the Decimal value is 18446744073709551615.

Si un compilateur de langage particulier prend en charge des opérateurs personnalisés, vous pouvez également
définir des conversions implicites dans vos propres types personnalisés. L'exemple suivant fournit une
implémentation partielle d'un type de données d'octets signés nommé ByteWithSign qui utilise la représentation
« signe et magnitude ». Il prend en charge la conversion implicite des valeurs Byte et SByte en valeurs
ByteWithSign .
public struct ByteWithSign
{
private SByte signValue;
private Byte value;

public static implicit operator ByteWithSign(SByte value)


{
ByteWithSign newValue;
newValue.signValue = (SByte) Math.Sign(value);
newValue.value = (byte) Math.Abs(value);
return newValue;
}

public static implicit operator ByteWithSign(Byte value)


{
ByteWithSign newValue;
newValue.signValue = 1;
newValue.value = value;
return newValue;
}

public override string ToString()


{
return (signValue * value).ToString();
}
}

Public Structure ByteWithSign


Private signValue As SByte
Private value As Byte

Public Overloads Shared Widening Operator CType(value As SByte) As ByteWithSign


Dim newValue As ByteWithSign
newValue.signValue = CSByte(Math.Sign(value))
newValue.value = CByte(Math.Abs(value))
Return newValue
End Operator

Public Overloads Shared Widening Operator CType(value As Byte) As ByteWithSign


Dim NewValue As ByteWithSign
newValue.signValue = 1
newValue.value = value
Return newValue
End Operator

Public Overrides Function ToString() As String


Return (signValue * value).ToString()
End Function
End Structure

Le code client peut ensuite déclarer une variable ByteWithSign et lui assigner les valeurs Byte et SByte sans
exécuter de conversion explicite ni utiliser d'opérateur de cast, comme indiqué dans l'exemple suivant.

SByte sbyteValue = -120;


ByteWithSign value = sbyteValue;
Console.WriteLine(value);
value = Byte.MaxValue;
Console.WriteLine(value);
// The example displays the following output:
// -120
// 255
Dim sbyteValue As SByte = -120
Dim value As ByteWithSign = sbyteValue
Console.WriteLine(value.ToString())
value = Byte.MaxValue
Console.WriteLine(value.ToString())
' The example displays the following output:
' -120
' 255

Conversion explicite avec l'opérateur Explicit


Les conversions restrictives impliquent la création d'une valeur à partir de la valeur d'un type existant dont la
plage est plus étendue ou qui contient une liste de membres plus étendue que le type cible. Une conversion
restrictive pouvant entraîner une perte de données, les compilateurs exigent souvent que la conversion soit
effectuée de façon implicite via un appel à une méthode de conversion ou à un opérateur de cast. En d'autres
termes, la conversion doit être gérée de manière explicite dans le code du développeur.

NOTE
Exiger une méthode de conversion ou un opérateur de cast pour les conversions restrictives a pour principal objectif que le
développeur soit conscient des risques de perte de données ou d'un OverflowException et puisse les gérer dans le code.
Toutefois, certains compilateurs peuvent assouplir cette exigence. Par exemple, en Visual Basic, si Option Strict est
désactivé (paramètre par défaut), le compilateur Visual Basic essaie d'exécuter des conversions restrictives de manière
implicite.

Par exemple, les types de données UInt32, Int64 et UInt64 ont tous des plages qui dépassent celle du type de
données Int32, comme indiqué dans le tableau suivant.

TYPE C O M PA RA ISO N AVEC L A P L A GE DE IN T 32

Int64 Int64.MaxValue est supérieur à Int32.MaxValue et


Int64.MinValue est inférieur à (a une plage négative
supérieure à) Int32.MinValue.

UInt32 UInt32.MaxValue est supérieur à Int32.MaxValue.

UInt64 UInt64.MaxValue est supérieur à Int32.MaxValue.

Pour gérer ces conversions restrictives, .NET Framework permet aux types de définir un opérateur Explicit .
Chaque compilateur de langage peut ensuite implémenter cet opérateur à l'aide de sa propre syntaxe, ou un
membre de la classe Convert peut être appelé pour effectuer la conversion. (Pour plus d’informations sur la
Convert classe, consultez la classe Convert plus loin dans cette rubrique.) L’exemple suivant illustre l’utilisation de
fonctionnalités de langage pour gérer la conversion explicite de ces valeurs entières potentiellement hors limites
en Int32 valeurs.
long number1 = int.MaxValue + 20L;
uint number2 = int.MaxValue - 1000;
ulong number3 = int.MaxValue;

int intNumber;

try {
intNumber = checked((int) number1);
Console.WriteLine("After assigning a {0} value, the Integer value is {1}.",
number1.GetType().Name, intNumber);
}
catch (OverflowException) {
if (number1 > int.MaxValue)
Console.WriteLine("Conversion failed: {0} exceeds {1}.",
number1, int.MaxValue);
else
Console.WriteLine("Conversion failed: {0} is less than {1}.",
number1, int.MinValue);
}

try {
intNumber = checked((int) number2);
Console.WriteLine("After assigning a {0} value, the Integer value is {1}.",
number2.GetType().Name, intNumber);
}
catch (OverflowException) {
Console.WriteLine("Conversion failed: {0} exceeds {1}.",
number2, int.MaxValue);
}

try {
intNumber = checked((int) number3);
Console.WriteLine("After assigning a {0} value, the Integer value is {1}.",
number3.GetType().Name, intNumber);
}
catch (OverflowException) {
Console.WriteLine("Conversion failed: {0} exceeds {1}.",
number1, int.MaxValue);
}

// The example displays the following output:


// Conversion failed: 2147483667 exceeds 2147483647.
// After assigning a UInt32 value, the Integer value is 2147482647.
// After assigning a UInt64 value, the Integer value is 2147483647.
Dim number1 As Long = Integer.MaxValue + 20L
Dim number2 As UInteger = Integer.MaxValue - 1000
Dim number3 As ULong = Integer.MaxValue

Dim intNumber As Integer

Try
intNumber = CInt(number1)
Console.WriteLine("After assigning a {0} value, the Integer value is {1}.",
number1.GetType().Name, intNumber)
Catch e As OverflowException
If number1 > Integer.MaxValue Then
Console.WriteLine("Conversion failed: {0} exceeds {1}.",
number1, Integer.MaxValue)
Else
Console.WriteLine("Conversion failed: {0} is less than {1}.\n",
number1, Integer.MinValue)
End If
End Try

Try
intNumber = CInt(number2)
Console.WriteLine("After assigning a {0} value, the Integer value is {1}.",
number2.GetType().Name, intNumber)
Catch e As OverflowException
Console.WriteLine("Conversion failed: {0} exceeds {1}.",
number2, Integer.MaxValue)
End Try

Try
intNumber = CInt(number3)
Console.WriteLine("After assigning a {0} value, the Integer value is {1}.",
number3.GetType().Name, intNumber)
Catch e As OverflowException
Console.WriteLine("Conversion failed: {0} exceeds {1}.",
number1, Integer.MaxValue)
End Try
' The example displays the following output:
' Conversion failed: 2147483667 exceeds 2147483647.
' After assigning a UInt32 value, the Integer value is 2147482647.
' After assigning a UInt64 value, the Integer value is 2147483647.

Les conversions explicites peuvent produire des résultats différents selon les langages, et ces résultats peuvent
différer de la valeur retournée par la méthode Convert correspondante. Par exemple, si la valeur Double 12.63251
est convertie en Int32, la méthode CInt Visual Basic et la méthode Convert.ToInt32(Double) .NET Framework
arrondissent toutes les deux Double pour retourner la valeur 13, mais l'opérateur (int) C# tronque Double pour
retourner la valeur 12. De la même façon, l'opérateur (int) C# ne prend pas en charge la conversion de données
booléennes en entier, mais la méthode CInt Visual Basic convertit la valeur true en -1. En revanche, la méthode
Convert.ToInt32(Boolean) convertit la valeur true en 1.
La plupart des compilateurs autorisent les conversions explicites contrôlées ou non contrôlées. Lorsqu'une
conversion contrôlée est effectuée, un OverflowException est levé lorsque la valeur du type à convertir se situe
hors de la plage du type cible. Lorsqu'une conversion non contrôlée est effectuée dans les mêmes circonstances, la
conversion n'entraîne pas nécessairement la levée d'une exception, mais le comportement exact devient indéfini et
la valeur obtenue peut être incorrecte.
NOTE
En C#, vous pouvez effectuer des conversions contrôlées à l'aide du mot clé checked et d'un opérateur de cast, ou en
spécifiant l'option de compilateur /checked+ . Vous pouvez également effectuer des conversions non contrôlées à l'aide du
mot clé unchecked et de l'opérateur de cast ou en spécifiant l'option de compilateur /checked- . Par défaut, les
conversions explicites ne sont pas contrôlées. Dans Visual Basic, vous pouvez effectuer des conversions contrôlées en
désactivant la case à cocher Supprimer les contrôles de dépassement sur les entiers dans la boîte de dialogue
Paramètres avancés du compilateur du projet ou en spécifiant l’option de compilateur /removeintchecks- . En
revanche, pour effectuer des conversions non contrôlées, activez la case à cocher Supprimer les contrôles de
dépassement sur les entiers dans la boîte de dialogue Paramètres avancés du compilateur du projet, ou spécifiez
l’option de compilateur /removeintchecks+ . Par défaut, les conversions explicites sont contrôlées.

L'exemple C# suivant utilise les mots clés checked et unchecked pour illustrer la différence de comportement
lorsqu'une valeur en dehors de la plage d'un Byte est convertie en Byte. La conversion contrôlée lève une
exception, mais la conversion non contrôlée assigne Byte.MaxValue à la variable Byte.

int largeValue = Int32.MaxValue;


byte newValue;

try {
newValue = unchecked((byte) largeValue);
Console.WriteLine("Converted the {0} value {1} to the {2} value {3}.",
largeValue.GetType().Name, largeValue,
newValue.GetType().Name, newValue);
}
catch (OverflowException) {
Console.WriteLine("{0} is outside the range of the Byte data type.",
largeValue);
}

try {
newValue = checked((byte) largeValue);
Console.WriteLine("Converted the {0} value {1} to the {2} value {3}.",
largeValue.GetType().Name, largeValue,
newValue.GetType().Name, newValue);
}
catch (OverflowException) {
Console.WriteLine("{0} is outside the range of the Byte data type.",
largeValue);
}
// The example displays the following output:
// Converted the Int32 value 2147483647 to the Byte value 255.
// 2147483647 is outside the range of the Byte data type.

Si un compilateur de langage particulier prend en charge des opérateurs surchargés personnalisés, vous pouvez
également définir des conversions explicites dans vos propres types personnalisés. L'exemple suivant fournit une
implémentation partielle d'un type de données d'octets signés nommé ByteWithSign qui utilise la représentation
« signe et magnitude ». Il prend en charge la conversion explicite des valeurs Int32 et UInt32 en valeurs
ByteWithSign .
public struct ByteWithSign
{
private SByte signValue;
private Byte value;

private const byte MaxValue = byte.MaxValue;


private const int MinValue = -1 * byte.MaxValue;

public static explicit operator ByteWithSign(int value)


{
// Check for overflow.
if (value > ByteWithSign.MaxValue || value < ByteWithSign.MinValue)
throw new OverflowException(String.Format("'{0}' is out of range of the ByteWithSign data type.",
value));

ByteWithSign newValue;
newValue.signValue = (SByte) Math.Sign(value);
newValue.value = (byte) Math.Abs(value);
return newValue;
}

public static explicit operator ByteWithSign(uint value)


{
if (value > ByteWithSign.MaxValue)
throw new OverflowException(String.Format("'{0}' is out of range of the ByteWithSign data type.",
value));

ByteWithSign newValue;
newValue.signValue = 1;
newValue.value = (byte) value;
return newValue;
}

public override string ToString()


{
return (signValue * value).ToString();
}
}
Public Structure ByteWithSign
Private signValue As SByte
Private value As Byte

Private Const MaxValue As Byte = Byte.MaxValue


Private Const MinValue As Integer = -1 * Byte.MaxValue

Public Overloads Shared Narrowing Operator CType(value As Integer) As ByteWithSign


' Check for overflow.
If value > ByteWithSign.MaxValue Or value < ByteWithSign.MinValue Then
Throw New OverflowException(String.Format("'{0}' is out of range of the ByteWithSign data type.",
value))
End If

Dim newValue As ByteWithSign

newValue.signValue = CSByte(Math.Sign(value))
newValue.value = CByte(Math.Abs(value))
Return newValue
End Operator

Public Overloads Shared Narrowing Operator CType(value As UInteger) As ByteWithSign


If value > ByteWithSign.MaxValue Then
Throw New OverflowException(String.Format("'{0}' is out of range of the ByteWithSign data type.",
value))
End If

Dim NewValue As ByteWithSign

newValue.signValue = 1
newValue.value = CByte(value)
Return newValue
End Operator

Public Overrides Function ToString() As String


Return (signValue * value).ToString()
End Function
End Structure

Le code client peut ensuite déclarer une variable ByteWithSign et lui assigner les valeurs Int32 et UInt32 si les
assignations incluent un opérateur de cast ou une méthode de conversion, comme indiqué dans l'exemple suivant.

ByteWithSign value;

try {
int intValue = -120;
value = (ByteWithSign) intValue;
Console.WriteLine(value);
}
catch (OverflowException e) {
Console.WriteLine(e.Message);
}

try {
uint uintValue = 1024;
value = (ByteWithSign) uintValue;
Console.WriteLine(value);
}
catch (OverflowException e) {
Console.WriteLine(e.Message);
}
// The example displays the following output:
// -120
// '1024' is out of range of the ByteWithSign data type.
Dim value As ByteWithSign

Try
Dim intValue As Integer = -120
value = CType(intValue, ByteWithSign)
Console.WriteLine(value)
Catch e As OverflowException
Console.WriteLine(e.Message)
End Try

Try
Dim uintValue As UInteger = 1024
value = CType(uintValue, ByteWithSign)
Console.WriteLine(value)
Catch e As OverflowException
Console.WriteLine(e.Message)
End Try
' The example displays the following output:
' -120
' '1024' is out of range of the ByteWithSign data type.

Interface IConvertible
Pour prendre en charge la conversion de n’importe quel type vers un type de base du Common Language
Runtime (CLR), .NET Framework fournit l’interface IConvertible. Le type d'implémentation est requis pour fournir
les éléments suivants :
une méthode qui retourne le TypeCode du type d'implémentation ;
des méthodes pour convertir le type d'implémentation vers chacun des types de base Common Language
Runtime (Boolean, Byte, DateTime, Decimal, Double, etc.) ;
une méthode de conversion généralisée pour convertir une instance du type d'implémentation dans un
autre type spécifié. Les conversions qui ne sont pas prises en charge doivent lever un InvalidCastException.
Chacun des types de base Common Language Runtime (c'est-à-dire, Boolean, Byte, Char, DateTime, Decimal,
Double, Int16, Int32, Int64, SByte, Single, String, UInt16, UInt32 et UInt64), ainsi que les types DBNull et Enum
implémentent l'interface IConvertible. Il s'agit toutefois d'implémentations d'interface explicites. La méthode de
conversion ne peut être appelée que via une variable d'interface IConvertible, comme le montre l'exemple suivant.
Cet exemple convertit une valeur Int32 en sa valeur Char équivalente.

int codePoint = 1067;


IConvertible iConv = codePoint;
char ch = iConv.ToChar(null);
Console.WriteLine("Converted {0} to {1}.", codePoint, ch);

Dim codePoint As Integer = 1067


Dim iConv As IConvertible = codePoint
Dim ch As Char = iConv.ToChar(Nothing)
Console.WriteLine("Converted {0} to {1}.", codePoint, ch)

L’exigence visant à appeler la méthode de conversion sur son interface plutôt que sur le type d’implémentation
rend les implémentations d’interface explicites relativement coûteuse. En lieu et place, nous vous recommandons
d'appeler le membre approprié de la classe Convert pour effectuer des conversions entre des types de base
Common Language Runtime. Pour plus d’informations, consultez la section suivante, classe Convert.
NOTE
Outre l’interface IConvertible et la classe Convert fournies par .NET Framework, chaque langage peut également offrir des
moyens d’effectuer des conversions. Par exemple, C# utilise des opérateurs de casting et Visual Basic fait appel à des
fonctions de conversion implémentées par compilateur, telles que CType , CInt et DirectCast .

L’interface IConvertible est en grande partie conçue pour prendre en charge la conversion entre les types de base
dans .NET Framework. Toutefois, l'interface peut également être implémentée par un type personnalisé pour
prendre en charge la conversion de ce type vers d'autres types personnalisés. Pour plus d’informations, consultez
la section conversions personnalisées avec la méthode ChangeType , plus loin dans cette rubrique.

Classe Convert
L'implémentation de l'interface IConvertible de chaque type de base peut être appelée pour effectuer une
conversion de type. Toutefois, l'appel des méthodes de la classe System.Convert est recommandé pour effectuer
une conversion d'un type de base vers un autre, car il est indépendant du langage. Par ailleurs, la méthode
Convert.ChangeType(Object, Type, IFormatProvider) peut être utilisée pour convertir un type personnalisé spécifié
vers un autre type.
Conversions entre types de base
La classe Convert constitue une façon indépendante du langage d'effectuer des conversions entre des types de
base, et est disponible pour tous les langages qui ciblent le Common Language Runtime (CLR). Elle fournit un
ensemble complet de méthodes pour les conversions étendues et restrictives, et lève un InvalidCastException pour
les conversions qui ne sont pas prises en charge (telles que la conversion d'une valeur DateTime en valeur
entière). Les conversions restrictives sont effectuées dans un contexte vérifié (checked), et un OverflowException
est levé en cas d'échec de la conversion.

IMPORTANT
Étant donné que la classe Convert inclut des méthodes permettant d'effectuer des conversions pour chaque type de base,
elle évite de devoir appeler l'implémentation d'interface explicite IConvertible de chaque type de base.

L'exemple suivant illustre l'utilisation de la classe System.Convert pour effectuer plusieurs conversions étendues
et restrictives entre des types de base du .NET Framework.
// Convert an Int32 value to a Decimal (a widening conversion).
int integralValue = 12534;
decimal decimalValue = Convert.ToDecimal(integralValue);
Console.WriteLine("Converted the {0} value {1} to " +
"the {2} value {3:N2}.",
integralValue.GetType().Name,
integralValue,
decimalValue.GetType().Name,
decimalValue);
// Convert a Byte value to an Int32 value (a widening conversion).
byte byteValue = Byte.MaxValue;
int integralValue2 = Convert.ToInt32(byteValue);
Console.WriteLine("Converted the {0} value {1} to " +
"the {2} value {3:G}.",
byteValue.GetType().Name,
byteValue,
integralValue2.GetType().Name,
integralValue2);

// Convert a Double value to an Int32 value (a narrowing conversion).


double doubleValue = 16.32513e12;
try {
long longValue = Convert.ToInt64(doubleValue);
Console.WriteLine("Converted the {0} value {1:E} to " +
"the {2} value {3:N0}.",
doubleValue.GetType().Name,
doubleValue,
longValue.GetType().Name,
longValue);
}
catch (OverflowException) {
Console.WriteLine("Unable to convert the {0:E} value {1}.",
doubleValue.GetType().Name, doubleValue);
}

// Convert a signed byte to a byte (a narrowing conversion).


sbyte sbyteValue = -16;
try {
byte byteValue2 = Convert.ToByte(sbyteValue);
Console.WriteLine("Converted the {0} value {1} to " +
"the {2} value {3:G}.",
sbyteValue.GetType().Name,
sbyteValue,
byteValue2.GetType().Name,
byteValue2);
}
catch (OverflowException) {
Console.WriteLine("Unable to convert the {0} value {1}.",
sbyteValue.GetType().Name, sbyteValue);
}
// The example displays the following output:
// Converted the Int32 value 12534 to the Decimal value 12,534.00.
// Converted the Byte value 255 to the Int32 value 255.
// Converted the Double value 1.632513E+013 to the Int64 value 16,325,130,000,000.
// Unable to convert the SByte value -16.
' Convert an Int32 value to a Decimal (a widening conversion).
Dim integralValue As Integer = 12534
Dim decimalValue As Decimal = Convert.ToDecimal(integralValue)
Console.WriteLine("Converted the {0} value {1} to the {2} value {3:N2}.",
integralValue.GetType().Name,
integralValue,
decimalValue.GetType().Name,
decimalValue)

' Convert a Byte value to an Int32 value (a widening conversion).


Dim byteValue As Byte = Byte.MaxValue
Dim integralValue2 As Integer = Convert.ToInt32(byteValue)
Console.WriteLine("Converted the {0} value {1} to " +
"the {2} value {3:G}.",
byteValue.GetType().Name,
byteValue,
integralValue2.GetType().Name,
integralValue2)

' Convert a Double value to an Int32 value (a narrowing conversion).


Dim doubleValue As Double = 16.32513e12
Try
Dim longValue As Long = Convert.ToInt64(doubleValue)
Console.WriteLine("Converted the {0} value {1:E} to " +
"the {2} value {3:N0}.",
doubleValue.GetType().Name,
doubleValue,
longValue.GetType().Name,
longValue)
Catch e As OverflowException
Console.WriteLine("Unable to convert the {0:E} value {1}.",
doubleValue.GetType().Name, doubleValue)
End Try

' Convert a signed byte to a byte (a narrowing conversion).


Dim sbyteValue As SByte = -16
Try
Dim byteValue2 As Byte = Convert.ToByte(sbyteValue)
Console.WriteLine("Converted the {0} value {1} to " +
"the {2} value {3:G}.",
sbyteValue.GetType().Name,
sbyteValue,
byteValue2.GetType().Name,
byteValue2)
Catch e As OverflowException
Console.WriteLine("Unable to convert the {0} value {1}.",
sbyteValue.GetType().Name, sbyteValue)
End Try
' The example displays the following output:
' Converted the Int32 value 12534 to the Decimal value 12,534.00.
' Converted the Byte value 255 to the Int32 value 255.
' Converted the Double value 1.632513E+013 to the Int64 value 16,325,130,000,000.
' Unable to convert the SByte value -16.

Dans certains cas, particulièrement lors de la conversion entre des valeurs à virgule flottante, une conversion peut
impliquer une perte de précision, bien qu'elle ne lève pas d'OverflowException. L'exemple suivant illustre cette
perte de précision. Dans le premier cas, une valeur Decimal a moins de précision (moins de chiffres significatifs)
lorsqu'elle est convertie en Double. Dans le second cas, une valeur Double est arrondie de 42,72 à 43 afin de
terminer la conversion.
double doubleValue;

// Convert a Double to a Decimal.


decimal decimalValue = 13956810.96702888123451471211m;
doubleValue = Convert.ToDouble(decimalValue);
Console.WriteLine("{0} converted to {1}.", decimalValue, doubleValue);

doubleValue = 42.72;
try {
int integerValue = Convert.ToInt32(doubleValue);
Console.WriteLine("{0} converted to {1}.",
doubleValue, integerValue);
}
catch (OverflowException) {
Console.WriteLine("Unable to convert {0} to an integer.",
doubleValue);
}
// The example displays the following output:
// 13956810.96702888123451471211 converted to 13956810.9670289.
// 42.72 converted to 43.

Dim doubleValue As Double

' Convert a Double to a Decimal.


Dim decimalValue As Decimal = 13956810.96702888123451471211d
doubleValue = Convert.ToDouble(decimalValue)
Console.WriteLine("{0} converted to {1}.", decimalValue, doubleValue)

doubleValue = 42.72
Try
Dim integerValue As Integer = Convert.ToInt32(doubleValue)
Console.WriteLine("{0} converted to {1}.",
doubleValue, integerValue)
Catch e As OverflowException
Console.WriteLine("Unable to convert {0} to an integer.",
doubleValue)
End Try
' The example displays the following output:
' 13956810.96702888123451471211 converted to 13956810.9670289.
' 42.72 converted to 43.

Pour obtenir un tableau qui répertorie les conversions étendues et restrictives prises en charge par la classe
Convert, consultez Tableaux de conversion de types.
Conversions personnalisées avec la méthode ChangeType
Outre la prise en charge des conversions vers chacun des types de base, la classe Convert peut être utilisée pour
convertir un type personnalisé vers un ou plusieurs types prédéfinis. Cette conversion est effectuée par la
méthode Convert.ChangeType(Object, Type, IFormatProvider), qui inclut ensuite dans un wrapper un appel à la
méthode IConvertible.ToType du paramètre value . Ainsi, l'objet représenté par le paramètre value doit fournir
une implémentation de l'interface IConvertible.

NOTE
Étant donné que les méthodes Convert.ChangeType(Object, Type) et Convert.ChangeType(Object, Type, IFormatProvider)
utilisent un objet Type pour spécifier le type de cible vers lequel la value est convertie, elles peuvent être utilisées pour
réaliser une conversion dynamique vers un objet dont le type n'est pas connu au moment de la compilation. Cependant,
notez que l'implémentation de IConvertible de la value doit toujours prendre en charge cette conversion.

L'exemple suivant illustre une implémentation possible de l'interface IConvertible qui permet de convertir un
objet TemperatureCelsiusen objet TemperatureFahrenheit , et inversement. Cet exemple définit une classe de base,
Temperature , qui implémente l'interface IConvertible et substitue la méthode Object.ToString. Les classes dérivées
TemperatureCelsius et TemperatureFahrenheit substituent chacune les méthodes ToType et ToString de la classe
de base.

using System;

public abstract class Temperature : IConvertible


{
protected decimal temp;

public Temperature(decimal temperature)


{
this.temp = temperature;
}

public decimal Value


{
get { return this.temp; }
set { this.temp = value; }
}

public override string ToString()


{
return temp.ToString(null as IFormatProvider) + "º";
}

// IConvertible implementations.
public TypeCode GetTypeCode() {
return TypeCode.Object;
}

public bool ToBoolean(IFormatProvider provider) {


throw new InvalidCastException(String.Format("Temperature-to-Boolean conversion is not supported."));
}

public byte ToByte(IFormatProvider provider) {


if (temp < Byte.MinValue || temp > Byte.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the Byte data type.", temp));
else
return (byte) temp;
}

public char ToChar(IFormatProvider provider) {


throw new InvalidCastException("Temperature-to-Char conversion is not supported.");
}

public DateTime ToDateTime(IFormatProvider provider) {


throw new InvalidCastException("Temperature-to-DateTime conversion is not supported.");
}

public decimal ToDecimal(IFormatProvider provider) {


return temp;
}

public double ToDouble(IFormatProvider provider) {


return (double) temp;
}

public short ToInt16(IFormatProvider provider) {


if (temp < Int16.MinValue || temp > Int16.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the Int16 data type.", temp));
else
return (short) Math.Round(temp);
}

public int ToInt32(IFormatProvider provider) {


public int ToInt32(IFormatProvider provider) {
if (temp < Int32.MinValue || temp > Int32.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the Int32 data type.", temp));
else
return (int) Math.Round(temp);
}

public long ToInt64(IFormatProvider provider) {


if (temp < Int64.MinValue || temp > Int64.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the Int64 data type.", temp));
else
return (long) Math.Round(temp);
}

public sbyte ToSByte(IFormatProvider provider) {


if (temp < SByte.MinValue || temp > SByte.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the SByte data type.", temp));
else
return (sbyte) temp;
}

public float ToSingle(IFormatProvider provider) {


return (float) temp;
}

public virtual string ToString(IFormatProvider provider) {


return temp.ToString(provider) + "°";
}

// If conversionType is implemented by another IConvertible method, call it.


public virtual object ToType(Type conversionType, IFormatProvider provider) {
switch (Type.GetTypeCode(conversionType))
{
case TypeCode.Boolean:
return this.ToBoolean(provider);
case TypeCode.Byte:
return this.ToByte(provider);
case TypeCode.Char:
return this.ToChar(provider);
case TypeCode.DateTime:
return this.ToDateTime(provider);
case TypeCode.Decimal:
return this.ToDecimal(provider);
case TypeCode.Double:
return this.ToDouble(provider);
case TypeCode.Empty:
throw new NullReferenceException("The target type is null.");
case TypeCode.Int16:
return this.ToInt16(provider);
case TypeCode.Int32:
return this.ToInt32(provider);
case TypeCode.Int64:
return this.ToInt64(provider);
case TypeCode.Object:
// Leave conversion of non-base types to derived classes.
throw new InvalidCastException(String.Format("Cannot convert from Temperature to {0}.",
conversionType.Name));
case TypeCode.SByte:
return this.ToSByte(provider);
case TypeCode.Single:
return this.ToSingle(provider);
case TypeCode.String:
IConvertible iconv = this;
return iconv.ToString(provider);
case TypeCode.UInt16:
return this.ToUInt16(provider);
case TypeCode.UInt32:
return this.ToUInt32(provider);
case TypeCode.UInt64:
return this.ToUInt64(provider);
return this.ToUInt64(provider);
default:
throw new InvalidCastException("Conversion not supported.");
}
}

public ushort ToUInt16(IFormatProvider provider) {


if (temp < UInt16.MinValue || temp > UInt16.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the UInt16 data type.", temp));
else
return (ushort) Math.Round(temp);
}

public uint ToUInt32(IFormatProvider provider) {


if (temp < UInt32.MinValue || temp > UInt32.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the UInt32 data type.", temp));
else
return (uint) Math.Round(temp);
}

public ulong ToUInt64(IFormatProvider provider) {


if (temp < UInt64.MinValue || temp > UInt64.MaxValue)
throw new OverflowException(String.Format("{0} is out of range of the UInt64 data type.", temp));
else
return (ulong) Math.Round(temp);
}
}

public class TemperatureCelsius : Temperature, IConvertible


{
public TemperatureCelsius(decimal value) : base(value)
{
}

// Override ToString methods.


public override string ToString()
{
return this.ToString(null);
}

public override string ToString(IFormatProvider provider)


{
return temp.ToString(provider) + "°C";
}

// If conversionType is a implemented by another IConvertible method, call it.


public override object ToType(Type conversionType, IFormatProvider provider) {
// For non-objects, call base method.
if (Type.GetTypeCode(conversionType) != TypeCode.Object) {
return base.ToType(conversionType, provider);
}
else
{
if (conversionType.Equals(typeof(TemperatureCelsius)))
return this;
else if (conversionType.Equals(typeof(TemperatureFahrenheit)))
return new TemperatureFahrenheit((decimal) this.temp * 9 / 5 + 32);
else
throw new InvalidCastException(String.Format("Cannot convert from Temperature to {0}.",
conversionType.Name));
}
}
}

public class TemperatureFahrenheit : Temperature, IConvertible


{
public TemperatureFahrenheit(decimal value) : base(value)
{
}
// Override ToString methods.
public override string ToString()
{
return this.ToString(null);
}

public override string ToString(IFormatProvider provider)


{
return temp.ToString(provider) + "°F";
}

public override object ToType(Type conversionType, IFormatProvider provider)


{
// For non-objects, call base methood.
if (Type.GetTypeCode(conversionType) != TypeCode.Object) {
return base.ToType(conversionType, provider);
}
else
{
// Handle conversion between derived classes.
if (conversionType.Equals(typeof(TemperatureFahrenheit)))
return this;
else if (conversionType.Equals(typeof(TemperatureCelsius)))
return new TemperatureCelsius((decimal) (this.temp - 32) * 5 / 9);
// Unspecified object type: throw an InvalidCastException.
else
throw new InvalidCastException(String.Format("Cannot convert from Temperature to {0}.",
conversionType.Name));
}
}
}

Public MustInherit Class Temperature


Implements IConvertible

Protected temp As Decimal

Public Sub New(temperature As Decimal)


Me.temp = temperature
End Sub

Public Property Value As Decimal


Get
Return Me.temp
End Get
Set
Me.temp = Value
End Set
End Property

Public Overrides Function ToString() As String


Return temp.ToString() & "º"
End Function

' IConvertible implementations.


Public Function GetTypeCode() As TypeCode Implements IConvertible.GetTypeCode
Return TypeCode.Object
End Function

Public Function ToBoolean(provider As IFormatProvider) As Boolean Implements IConvertible.ToBoolean


Throw New InvalidCastException(String.Format("Temperature-to-Boolean conversion is not supported."))
End Function

Public Function ToByte(provider As IFormatProvider) As Byte Implements IConvertible.ToByte


If temp < Byte.MinValue Or temp > Byte.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the Byte data type.", temp))
Else
Else
Return CByte(temp)
End If
End Function

Public Function ToChar(provider As IFormatProvider) As Char Implements IConvertible.ToChar


Throw New InvalidCastException("Temperature-to-Char conversion is not supported.")
End Function

Public Function ToDateTime(provider As IFormatProvider) As DateTime Implements IConvertible.ToDateTime


Throw New InvalidCastException("Temperature-to-DateTime conversion is not supported.")
End Function

Public Function ToDecimal(provider As IFormatProvider) As Decimal Implements IConvertible.ToDecimal


Return temp
End Function

Public Function ToDouble(provider As IFormatProvider) As Double Implements IConvertible.ToDouble


Return CDbl(temp)
End Function

Public Function ToInt16(provider As IFormatProvider) As Int16 Implements IConvertible.ToInt16


If temp < Int16.MinValue Or temp > Int16.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the Int16 data type.", temp))
End If
Return CShort(Math.Round(temp))
End Function

Public Function ToInt32(provider As IFormatProvider) As Int32 Implements IConvertible.ToInt32


If temp < Int32.MinValue Or temp > Int32.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the Int32 data type.", temp))
End If
Return CInt(Math.Round(temp))
End Function

Public Function ToInt64(provider As IFormatProvider) As Int64 Implements IConvertible.ToInt64


If temp < Int64.MinValue Or temp > Int64.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the Int64 data type.", temp))
End If
Return CLng(Math.Round(temp))
End Function

Public Function ToSByte(provider As IFormatProvider) As SByte Implements IConvertible.ToSByte


If temp < SByte.MinValue Or temp > SByte.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the SByte data type.", temp))
Else
Return CSByte(temp)
End If
End Function

Public Function ToSingle(provider As IFormatProvider) As Single Implements IConvertible.ToSingle


Return CSng(temp)
End Function

Public Overridable Overloads Function ToString(provider As IFormatProvider) As String Implements


IConvertible.ToString
Return temp.ToString(provider) & " °C"
End Function

' If conversionType is a implemented by another IConvertible method, call it.


Public Overridable Function ToType(conversionType As Type, provider As IFormatProvider) As Object
Implements IConvertible.ToType
Select Case Type.GetTypeCode(conversionType)
Case TypeCode.Boolean
Return Me.ToBoolean(provider)
Case TypeCode.Byte
Return Me.ToByte(provider)
Case TypeCode.Char
Return Me.ToChar(provider)
Case TypeCode.DateTime
Case TypeCode.DateTime
Return Me.ToDateTime(provider)
Case TypeCode.Decimal
Return Me.ToDecimal(provider)
Case TypeCode.Double
Return Me.ToDouble(provider)
Case TypeCode.Empty
Throw New NullReferenceException("The target type is null.")
Case TypeCode.Int16
Return Me.ToInt16(provider)
Case TypeCode.Int32
Return Me.ToInt32(provider)
Case TypeCode.Int64
Return Me.ToInt64(provider)
Case TypeCode.Object
' Leave conversion of non-base types to derived classes.
Throw New InvalidCastException(String.Format("Cannot convert from Temperature to {0}.", _
conversionType.Name))
Case TypeCode.SByte
Return Me.ToSByte(provider)
Case TypeCode.Single
Return Me.ToSingle(provider)
Case TypeCode.String
Return Me.ToString(provider)
Case TypeCode.UInt16
Return Me.ToUInt16(provider)
Case TypeCode.UInt32
Return Me.ToUInt32(provider)
Case TypeCode.UInt64
Return Me.ToUInt64(provider)
Case Else
Throw New InvalidCastException("Conversion not supported.")
End Select
End Function

Public Function ToUInt16(provider As IFormatProvider) As UInt16 Implements IConvertible.ToUInt16


If temp < UInt16.MinValue Or temp > UInt16.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the UInt16 data type.", temp))
End If
Return CUShort(Math.Round(temp))
End Function

Public Function ToUInt32(provider As IFormatProvider) As UInt32 Implements IConvertible.ToUInt32


If temp < UInt32.MinValue Or temp > UInt32.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the UInt32 data type.", temp))
End If
Return CUInt(Math.Round(temp))
End Function

Public Function ToUInt64(provider As IFormatProvider) As UInt64 Implements IConvertible.ToUInt64


If temp < UInt64.MinValue Or temp > UInt64.MaxValue Then
Throw New OverflowException(String.Format("{0} is out of range of the UInt64 data type.", temp))
End If
Return CULng(Math.Round(temp))
End Function
End Class

Public Class TemperatureCelsius : Inherits Temperature : Implements IConvertible


Public Sub New(value As Decimal)
MyBase.New(value)
End Sub

' Override ToString methods.


Public Overrides Function ToString() As String
Return Me.ToString(Nothing)
End Function

Public Overrides Function ToString(provider As IFormatProvider) As String


Return temp.ToString(provider) + "°C"
End Function
End Function

' If conversionType is a implemented by another IConvertible method, call it.


Public Overrides Function ToType(conversionType As Type, provider As IFormatProvider) As Object
' For non-objects, call base method.
If Type.GetTypeCode(conversionType) <> TypeCode.Object Then
Return MyBase.ToType(conversionType, provider)
Else
If conversionType.Equals(GetType(TemperatureCelsius)) Then
Return Me
ElseIf conversionType.Equals(GetType(TemperatureFahrenheit))
Return New TemperatureFahrenheit(CDec(Me.temp * 9 / 5 + 32))
' Unspecified object type: throw an InvalidCastException.
Else
Throw New InvalidCastException(String.Format("Cannot convert from Temperature to {0}.", _
conversionType.Name))
End If
End If
End Function
End Class

Public Class TemperatureFahrenheit : Inherits Temperature : Implements IConvertible


Public Sub New(value As Decimal)
MyBase.New(value)
End Sub

' Override ToString methods.


Public Overrides Function ToString() As String
Return Me.ToString(Nothing)
End Function

Public Overrides Function ToString(provider As IFormatProvider) As String


Return temp.ToString(provider) + "°F"
End Function

Public Overrides Function ToType(conversionType As Type, provider As IFormatProvider) As Object


' For non-objects, call base methood.
If Type.GetTypeCode(conversionType) <> TypeCode.Object Then
Return MyBase.ToType(conversionType, provider)
Else
' Handle conversion between derived classes.
If conversionType.Equals(GetType(TemperatureFahrenheit)) Then
Return Me
ElseIf conversionType.Equals(GetType(TemperatureCelsius))
Return New TemperatureCelsius(CDec((MyBase.temp - 32) * 5 / 9))
' Unspecified object type: throw an InvalidCastException.
Else
Throw New InvalidCastException(String.Format("Cannot convert from Temperature to {0}.", _
conversionType.Name))
End If
End If
End Function
End Class

L'exemple suivant illustre plusieurs appels à ces implémentations de IConvertible pour convertir des objets
TemperatureCelsius en objets TemperatureFahrenheit , et inversement.
TemperatureCelsius tempC1 = new TemperatureCelsius(0);
TemperatureFahrenheit tempF1 = (TemperatureFahrenheit) Convert.ChangeType(tempC1,
typeof(TemperatureFahrenheit), null);
Console.WriteLine("{0} equals {1}.", tempC1, tempF1);
TemperatureCelsius tempC2 = (TemperatureCelsius) Convert.ChangeType(tempC1, typeof(TemperatureCelsius), null);
Console.WriteLine("{0} equals {1}.", tempC1, tempC2);
TemperatureFahrenheit tempF2 = new TemperatureFahrenheit(212);
TemperatureCelsius tempC3 = (TemperatureCelsius) Convert.ChangeType(tempF2, typeof(TemperatureCelsius), null);
Console.WriteLine("{0} equals {1}.", tempF2, tempC3);
TemperatureFahrenheit tempF3 = (TemperatureFahrenheit) Convert.ChangeType(tempF2,
typeof(TemperatureFahrenheit), null);
Console.WriteLine("{0} equals {1}.", tempF2, tempF3);
// The example displays the following output:
// 0°C equals 32°F.
// 0°C equals 0°C.
// 212°F equals 100°C.
// 212°F equals 212°F.

Dim tempC1 As New TemperatureCelsius(0)


Dim tempF1 As TemperatureFahrenheit = CType(Convert.ChangeType(tempC1, GetType(TemperatureFahrenheit),
Nothing), TemperatureFahrenheit)
Console.WriteLine("{0} equals {1}.", tempC1, tempF1)
Dim tempC2 As TemperatureCelsius = CType(Convert.ChangeType(tempC1, GetType(TemperatureCelsius), Nothing),
TemperatureCelsius)
Console.WriteLine("{0} equals {1}.", tempC1, tempC2)
Dim tempF2 As New TemperatureFahrenheit(212)
Dim tempC3 As TEmperatureCelsius = CType(Convert.ChangeType(tempF2, GEtType(TemperatureCelsius), Nothing),
TemperatureCelsius)
Console.WriteLine("{0} equals {1}.", tempF2, tempC3)
Dim tempF3 As TemperatureFahrenheit = CType(Convert.ChangeType(tempF2, GetType(TemperatureFahrenheit),
Nothing), TemperatureFahrenheit)
Console.WriteLine("{0} equals {1}.", tempF2, tempF3)
' The example displays the following output:
' 0°C equals 32°F.
' 0°C equals 0°C.
' 212°F equals 100°C.
' 212°F equals 212°F.

Classe TypeConverter
Grâce au .NET Framework, vous pouvez également définir un convertisseur de type pour un type personnalisé en
étendant la classe System.ComponentModel.TypeConverter et en associant le convertisseur de type au type grâce
à un attribut System.ComponentModel.TypeConverterAttribute. Le tableau suivant met en évidence les différences
entre cette approche et l'implémentation de l'interface IConvertible pour un type personnalisé.

NOTE
La prise en charge au moment du design ne peut être fournie pour un type personnalisé que si un convertisseur de type est
défini pour ce dernier.

C O N VERSIO N À L 'A IDE DE T Y P EC O N VERT ER C O N VERSIO N À L 'A IDE DE IC O N VERT IB L E

Est implémentée pour un type personnalisé en dérivant une Est implémentée par un type personnalisé pour effectuer la
classe distincte de TypeConverter. Cette classe dérivée est conversion. Un utilisateur du type appelle une méthode de
associée au type personnalisé en appliquant un attribut conversion IConvertible sur le type.
TypeConverterAttribute.
C O N VERSIO N À L 'A IDE DE T Y P EC O N VERT ER C O N VERSIO N À L 'A IDE DE IC O N VERT IB L E

Peut être utilisée à la fois au moment du design et au Ne peut être utilisée qu'au moment de l'exécution.
moment de l'exécution.

Utilise la réflexion, et est donc plus lente que la conversion à N'utilise pas la réflexion.
l'aide de IConvertible.

Permet les conversions de type bilatérales du type Permet la conversion d'un type personnalisé en d'autres types
personnalisé en d'autres types de données, et d'autres types de données, mais pas d'autres types de données en type
de données en type personnalisé. Par exemple, un personnalisé.
TypeConverter défini pour MyType permet d'effectuer des
conversions de MyType en String, et de String en MyType .

Pour plus d'informations sur l'utilisation de convertisseurs de type pour effectuer des conversions, consultez
System.ComponentModel.TypeConverter.

Voir aussi
System.Convert
IConvertible
Tables de conversion de type
Table de conversion de type dans .NET
18/07/2020 • 4 minutes to read • Edit Online

Une conversion étendue se produit quand une valeur d’un type est convertie en un autre type de taille égale ou
supérieure. Une conversion restrictive se produit quand une valeur d’un type est convertie en une valeur d’un
autre type de taille inférieure. Les tableaux de cette rubrique illustrent les comportements propres aux deux types
de conversion.

conversions étendues
Le tableau suivant décrit les conversions étendues qui peuvent être effectuées sans perte d’informations.

TYPE P EUT ÊT RE C O N VERT I SA N S P ERT E DE DO N N ÉES EN

Byte UInt16, Int16, UInt32, Int32, UInt64, Int64, Single, Double,


Decimal

SByte Int16, Int32, Int64, Single, Double, Decimal

Int16 Int32, Int64, Single, Double, Decimal

UInt16 UInt32, Int32, UInt64, Int64, Single, Double, Decimal

Char UInt16, UInt32, Int32, UInt64, Int64, Single, Double, Decimal

Int32 Int64, Double, Decimal

UInt32 Int64, UInt64, Double, Decimal

Int64 Decimal

UInt64 Decimal

Single Double

Certaines conversions étendues à Single ou Double peuvent entraîner une perte de précision. Le tableau suivant
décrit les conversions étendues qui entraînent parfois une perte d’informations.

TYPE P EUT ÊT RE C O N VERT I EN

Int32 Single

UInt32 Single

Int64 Single, Double

UInt64 Single, Double

Decimal Single, Double


conversions restrictives
Une conversion restrictive en Single ou Double peut entraîner une perte d’informations. Si le type cible ne peut
pas exprimer correctement la grandeur de la source, le type résultant est défini sur la constante PositiveInfinity
ou NegativeInfinity . PositiveInfinity est obtenu en divisant un nombre positif par zéro et est également
retourné quand la valeur d’un Single ou d’un Double dépasse la valeur du champ MaxValue . NegativeInfinity est
obtenu en divisant un nombre négatif par zéro et est également retourné quand la valeur d’un Single ou d’un
Double descend en dessous de la valeur du champ MinValue . Une conversion d’un Double en Single peut avoir
pour résultat PositiveInfinity ou NegativeInfinity .
Une conversion restrictive peut également entraîner une perte d’informations pour d’autres types de données.
Toutefois, une OverflowExceptionest levée si la valeur d’un type en cours de conversion se situe en dehors de la
plage spécifiée par les champs MaxValue et MinValue du type cible, et la conversion est vérifiée par le runtime
pour garantir que la valeur du type cible ne dépasse pas sa valeur MaxValue ou MinValue . Les conversions
effectuées avec la classe System.Convert sont toujours vérifiées de cette façon.
Le tableau suivant répertorie les conversions qui lèvent une OverflowException à l’aide de System.Convert ou de
toute conversion contrôlée si la valeur du type converti se situe en dehors de la plage définie du type résultant.

TYPE P EUT ÊT RE C O N VERT I EN

Byte SByte

SByte Byte, UInt16, UInt32, UInt64

Int16 Byte, SByte, UInt16

UInt16 Byte, SByte, Int16

Int32 Byte, SByte, Int16, UInt16,UInt32

UInt32 Byte, SByte, Int16, UInt16, Int32

Int64 Byte, SByte, Int16, UInt16, Int32,UInt32,UInt64

UInt64 Byte, SByte, Int16, UInt16, Int32, UInt32, Int64

Decimal Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64

Single Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64

Double Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64

Voir aussi
System.Convert
Conversion de type dans .NET
Choix entre les types de tuples et anonymes
18/07/2020 • 7 minutes to read • Edit Online

Le choix du type approprié implique la prise en compte de l’utilisation, des performances et des compromis par
rapport aux autres types. Les types anonymes sont disponibles depuis C# 3,0, tandis que System.Tuple<T1,T2> les
types génériques ont été introduits avec .NET Framework 4,0. Dans la mesure où de nouvelles options ont été
introduites avec la prise en charge du niveau de langage, comme, System.ValueTuple<T1,T2> qui, comme son nom
l’indique, fournit un type de valeur avec la flexibilité des types anonymes. Dans cet article, vous découvrirez quand
il est approprié de choisir un type par rapport à l’autre.

Convivialité et fonctionnalités
Les types anonymes ont été introduits dans C# 3,0 avec des expressions LINQ (Language-Integrated Query). Avec
LINQ, les développeurs projetent souvent les résultats de requêtes dans des types anonymes qui contiennent
quelques propriétés Select des objets avec lesquels ils travaillent. Prenons l’exemple suivant, qui instancie un
tableau d' DateTime objets, et itère au sein de ce projet dans un type anonyme avec deux propriétés.

var dates = new[]


{
DateTime.UtcNow.AddHours(-1),
DateTime.UtcNow,
DateTime.UtcNow.AddHours(1),
};

foreach (var anonymous in


dates.Select(
date => new { Formatted = $"{date:MMM dd, yyyy hh:mm zzz}", date.Ticks }))
{
Console.WriteLine($"Ticks: {anonymous.Ticks}, formatted: {anonymous.Formatted}");
}

Les types anonymes sont instanciés à l’aide de l' new opérateur, et les noms et types de propriétés sont déduits de
la déclaration. Si au moins deux initialiseurs d’objets anonymes dans le même assembly spécifient une séquence de
propriétés qui sont dans le même ordre et qui ont les mêmes noms et types, le compilateur traite les objets comme
des instances du même type. Elles partagent les mêmes informations de type générées par le compilateur.
L’extrait de code C# précédent projette un type anonyme avec deux propriétés, de la même façon que la classe C#
générée par le compilateur suivante :

internal sealed class f__AnonymousType0


{
public string Formatted { get; }
public long Ticks { get; }

public f__AnonymousType0(string formatted, long ticks)


{
Formatted = formatted;
Ticks = ticks;
}
}

Pour plus d’informations, consultez types anonymes. Les mêmes fonctionnalités existent avec les tuples lors de la
projection dans des requêtes LINQ, vous pouvez sélectionner des propriétés dans des tuples. Ces tuples passent par
la requête, tout comme les types anonymes. À présent, considérez l’exemple suivant à l’aide de
System.Tuple<string, long> .

var dates = new[]


{
DateTime.UtcNow.AddHours(-1),
DateTime.UtcNow,
DateTime.UtcNow.AddHours(1),
};

foreach (var tuple in


dates.Select(
date => new Tuple<string, long>($"{date:MMM dd, yyyy hh:mm zzz}", date.Ticks)))
{
Console.WriteLine($"Ticks: {tuple.Item2}, formatted: {tuple.Item1}");
}

Avec System.Tuple<T1,T2> , l’instance expose des propriétés d’élément numérotées, telles que Item1 et Item2 .
Ces noms de propriété peuvent compliquer la compréhension de l’objectif des valeurs de propriété, car le nom de
propriété fournit uniquement l’ordinal. En outre, les System.Tuple types sont des class types référence.
System.ValueTuple<T1,T2>Toutefois, est un type valeur struct . L’extrait de code C# suivant utilise
ValueTuple<string, long> pour projeter dans. Dans ce cas, elle est assignée à l’aide d’une syntaxe littérale.

var dates = new[]


{
DateTime.UtcNow.AddHours(-1),
DateTime.UtcNow,
DateTime.UtcNow.AddHours(1),
};

foreach (var (formatted, ticks) in


dates.Select(
date => (Formatted: $"{date:MMM dd, yyyy at hh:mm zzz}", date.Ticks)))
{
Console.WriteLine($"Ticks: {ticks}, formatted: {formatted}");
}

Pour plus d’informations sur les tuples, consultez types de tuples (référence C#) ou tuples (Visual Basic).
Les exemples précédents sont tous des équivalents fonctionnels, toutefois, leur utilisation et leurs implémentations
sous-jacentes présentent de légères différences.

Compromis
Vous souhaiterez peut-être toujours utiliser ValueTuple Tuple les types plus ou anonymes, mais il existe des
compromis à prendre en compte. Les ValueTuple types sont mutables, alors que Tuple ceux-ci sont en lecture seule.
Les types anonymes peuvent être utilisés dans les arborescences d’expressions, contrairement aux tuples. Le
tableau suivant est une vue d’ensemble de certaines des principales différences.
Différences clés
P RISE EN P RISE EN
N O M DU C H A RGE DE L A C H A RGE DE
M O DIF IC AT EUR M EM B RE DÉC O N ST RUC T IO L’A RB O RESC EN C E
NOM D’A C C ÈS TYPE P ERSO N N A L ISÉ N D’EXP RESSIO N S

Types anonymes internal class ️


✔ ❌ ️

Tuple public class ❌ ❌ ️



P RISE EN P RISE EN
N O M DU C H A RGE DE L A C H A RGE DE
M O DIF IC AT EUR M EM B RE DÉC O N ST RUC T IO L’A RB O RESC EN C E
NOM D’A C C ÈS TYPE P ERSO N N A L ISÉ N D’EXP RESSIO N S

ValueTuple public struct ️


✔ ️
✔ ❌

Sérialisation
Un point important à prendre en compte lors du choix d’un type est qu’il doit ou non être sérialisé. La sérialisation
correspond au processus de conversion de l'état d'un objet en un formulaire persistant ou transportable. Pour plus
d’informations, consultez sérialisation. Lorsque la sérialisation est importante, la création d’un ou d’un class
struct est préférable sur les types anonymes ou les types de tuples.

Performances
Les performances entre ces types dépendent du scénario. L’impact majeur concerne le compromis entre les
allocations et la copie. Dans la plupart des scénarios, l’impact est faible. Lorsque des impacts majeurs peuvent
survenir, les mesures doivent être prises pour informer la décision.

Conclusion
En tant que développeur qui choisit entre les tuples et les types anonymes, plusieurs facteurs sont à prendre en
compte. En règle générale, si vous ne travaillez pas avec les arborescences d’expressions, et que vous êtes
familiarisé avec la syntaxe des tuples, choisissez alors ValueTuple qu’elles fournissent un type de valeur avec la
flexibilité nécessaire pour nommer les propriétés. Si vous utilisez des arborescences d’expressions et que vous
préférez nommer des propriétés, choisissez des types anonymes. Sinon, utilisez Tuple.

Voir aussi
Types anonymes
Arborescences de l’expression
Types de tuples (référence C#)
Tuples (Visual Basic)
Instructions de conception de types
Types de format dans .NET
18/07/2020 • 64 minutes to read • Edit Online

La mise en forme est le processus de conversion d'une instance d'une classe, d'une structure ou d'une valeur
d'énumération en représentation sous forme de chaîne, généralement pour exposer la chaîne obtenue aux
utilisateurs ou pour qu'elle soit désérialisée afin de restaurer le type de données d'origine. Cette conversion
peut présenter plusieurs difficultés :
La manière dont les valeurs sont stockées en interne ne reflète pas nécessairement celle dont les
utilisateurs souhaitent les voir. Par exemple, un numéro de téléphone peut être stocké sous la forme
8009999999, ce qui n'est pas convivial. Il devrait plutôt être affiché sous la forme 800-999-9999.
Consultez la section Chaînes de format personnalisé pour obtenir un exemple d'une telle mise en forme
d'un nombre.
La conversion d'un objet en sa représentation sous forme de chaîne n'est pas toujours intuitive. Par
exemple, il n'est pas évident de savoir comment doit s'afficher la représentation sous forme de chaîne
d'un objet Temperature ou Person. Pour obtenir un exemple illustrant la mise en forme d'un objet
Temperature selon différentes manières, consultez la section Chaînes de format standard .
Les valeurs requièrent souvent une mise en forme qui tient compte de la culture. Par exemple, dans une
application qui utilise des nombres pour refléter des valeurs monétaires, les chaînes numériques doivent
inclure le symbole monétaire de la culture actuelle, le séparateur de groupes (qui, dans la plupart des
cultures, est le séparateur des milliers) et le symbole décimal. Pour obtenir un exemple, consultez la
section mise en forme dépendante de la culture avec les fournisseurs de format .
Une application peut avoir à afficher la même valeur de différentes manières. Par exemple, une
application peut représenter un membre d'énumération en affichant une représentation sous forme de
chaîne de son nom ou en affichant sa valeur sous-jacente. Pour obtenir un exemple illustrant la mise en
forme d'un membre de l'énumération DayOfWeek selon différentes manières, consultez la section
Chaînes de format standard .

NOTE
La mise en forme convertit la valeur d'un type en une représentation sous forme de chaîne. L'analyse est l'opération
inverse de la mise en forme. Une opération d'analyse crée une instance d'un type de données à partir de sa
représentation sous forme de chaîne. Pour plus d’informations sur la conversion de chaînes en d’autres types de données,
consultez Parsing Strings.

.NET assure une prise en charge évoluée de la mise en forme qui permet aux développeurs de surmonter ces
difficultés.

Mise en forme dans .NET


Le mécanisme de base de la mise en forme est l'implémentation par défaut de la méthode Object.ToString,
décrite dans la section Mise en forme par défaut à l'aide de la méthode ToString, plus loin dans cette rubrique.
Toutefois, .NET propose différentes manières de modifier et d’étendre sa prise en charge par défaut de la mise
en forme. Ces options en question sont les suivantes :
Substitution de la Object.ToString méthode pour définir une représentation sous forme de chaîne
personnalisée de la valeur d’un objet. Pour plus d’informations, consultez la section substitution de la
méthode ToString , plus loin dans cette rubrique.
Définition de spécificateurs de format qui permettent à la représentation sous forme de chaîne de la
valeur d’un objet de prendre plusieurs formes. Par exemple, dans l'instruction suivante, le spécificateur
de format "X" convertit un entier en la représentation sous forme de chaîne d'une valeur hexadécimale.

int integerValue = 60312;


Console.WriteLine(integerValue.ToString("X")); // Displays EB98.

Dim integerValue As Integer = 60312


Console.WriteLine(integerValue.ToString("X")) ' Displays EB98.

Pour plus d'informations sur les spécificateurs de format, consultez la section Méthode ToString et
chaînes de format .
Utilisation de fournisseurs de format pour tirer parti des conventions de mise en forme d'une culture
spécifique. Par exemple, l'instruction suivante affiche une valeur monétaire en utilisant les conventions de
mise en forme de la culture en-US.

double cost = 1632.54;


Console.WriteLine(cost.ToString("C",
new System.Globalization.CultureInfo("en-US")));
// The example displays the following output:
// $1,632.54

Dim cost As Double = 1632.54


Console.WriteLine(cost.ToString("C", New System.Globalization.CultureInfo("en-US")))
' The example displays the following output:
' $1,632.54

Pour plus d’informations sur la mise en forme avec les fournisseurs de format, consultez la section
fournisseurs de format .
Implémentation de l'interface IFormattable pour prendre en charge la conversion de chaînes avec la
classe Convert et la mise en forme composite. Pour plus d'informations, consultez la section Interface
IFormattable .
Utilisation de la mise en forme composite pour incorporer la représentation sous forme de chaîne d'une
valeur dans une chaîne plus grande. Pour plus d'informations, consultez la section Mise en forme
composite .
Implémentation d' ICustomFormatter et d' IFormatProvider pour fournir une solution de mise en forme
personnalisée et complète. Pour plus d'informations, consultez la section Mise en forme personnalisée
avec ICustomFormatter .
Les sections suivantes étudient ces méthodes de conversion d'un objet en sa représentation sous forme de
chaîne.

Mise en forme par défaut à l’aide de la méthode ToString


Chaque type qui est dérivé d' System.Object hérite automatiquement d'une méthode ToString sans paramètre,
laquelle retourne le nom du type par défaut. L'exemple suivant illustre la méthode ToString par défaut. Il
définit une classe nommée Automobile qui n'a pas d'implémentation. Lorsque cette classe est instanciée et que
sa méthode ToString est appelée, elle affiche son nom de type. Notez que la méthode ToString n'est pas
appelée explicitement dans cet exemple. La méthode Console.WriteLine(Object) appelle implicitement la
méthode ToString de l'objet qui lui est passé comme argument.
using System;

public class Automobile


{
// No implementation. All members are inherited from Object.
}

public class Example


{
public static void Main()
{
Automobile firstAuto = new Automobile();
Console.WriteLine(firstAuto);
}
}
// The example displays the following output:
// Automobile

Public Class Automobile


' No implementation. All members are inherited from Object.
End Class

Module Example
Public Sub Main()
Dim firstAuto As New Automobile()
Console.WriteLine(firstAuto)
End Sub
End Module
' The example displays the following output:
' Automobile

WARNING
À partir de Windows 8.1, le Windows Runtime inclut une IStringable interface avec une méthode unique, IStringable.
ToString, qui fournit la prise en charge de la mise en forme par défaut. Toutefois, nous recommandons que les types
managés n'implémentent pas l'interface IStringable . Pour plus d’informations, consultez la section « Windows
Runtime et interface IStringable » dans la page de référence de Object.ToString.

Étant donné que tous les types autres que les interfaces sont dérivés de Object, ces fonctionnalités sont fournies
automatiquement à vos classes ou structures personnalisées. Toutefois, les fonctionnalités offertes par la
méthode ToString par défaut sont limitées : Bien qu'elle identifie le type, elle ne fournit aucune information
relative à une instance du type. Pour fournir une représentation sous forme de chaîne d'un objet qui donne des
informations sur cet objet, vous devez substituer la méthode ToString .

NOTE
Les structures héritent de ValueType, qui, à son tour, est dérivé d' Object. Bien que ValueType substitue Object.ToString,
son implémentation est identique.

Substituer la méthode ToString


L'utilité de l'affichage du nom d'un type est souvent limitée et ne permet pas aux consommateurs de vos types
de différencier une instance d'une autre. Toutefois, vous pouvez substituer la ToString méthode pour fournir
une représentation plus utile de la valeur d’un objet. L'exemple suivant définit un objet Temperature et substitue
sa méthode ToString pour afficher la température en degrés Celsius.
using System;

public class Temperature


{
private decimal temp;

public Temperature(decimal temperature)


{
this.temp = temperature;
}

public override string ToString()


{
return this.temp.ToString("N1") + "°C";
}
}

public class Example


{
public static void Main()
{
Temperature currentTemperature = new Temperature(23.6m);
Console.WriteLine("The current temperature is " +
currentTemperature.ToString());
}
}
// The example displays the following output:
// The current temperature is 23.6°C.

Public Class Temperature


Private temp As Decimal

Public Sub New(temperature As Decimal)


Me.temp = temperature
End Sub

Public Overrides Function ToString() As String


Return Me.temp.ToString("N1") + "°C"
End Function
End Class

Module Example
Public Sub Main()
Dim currentTemperature As New Temperature(23.6d)
Console.WriteLine("The current temperature is " +
currentTemperature.ToString())
End Sub
End Module
' The example displays the following output:
' The current temperature is 23.6°C.

Dans .NET, la ToString méthode de chaque type valeur primitif a été substituée de façon à afficher la valeur de
l’objet au lieu de son nom. Le tableau suivant montre la substitution pour chaque type primitif. Notez que la
plupart des méthodes substituées appellent une autre surcharge de la méthode ToString et lui passent le
spécificateur de format "G", qui définit le format général pour son type, ainsi qu'un objet IFormatProvider qui
représente la culture actuelle.

TYPE SUB ST IT UT IO N DE TO ST RIN G

Boolean Retourne Boolean.TrueString ou Boolean.FalseString.


TYPE SUB ST IT UT IO N DE TO ST RIN G

Byte Appelle
Byte.ToString("G", NumberFormatInfo.CurrentInfo)
afin de mettre en forme la valeur Byte pour la culture
actuelle.

Char Retourne le caractère sous forme de chaîne.

DateTime Appelle
DateTime.ToString("G",
DatetimeFormatInfo.CurrentInfo)
afin de mettre en forme la valeur de date et d'heure pour la
culture actuelle.

Decimal Appelle
Decimal.ToString("G", NumberFormatInfo.CurrentInfo)
afin de mettre en forme la valeur Decimal pour la culture
actuelle.

Double Appelle
Double.ToString("G", NumberFormatInfo.CurrentInfo)
afin de mettre en forme la valeur Double pour la culture
actuelle.

Int16 Appelle
Int16.ToString("G", NumberFormatInfo.CurrentInfo)
afin de mettre en forme la valeur Int16 pour la culture
actuelle.

Int32 Appelle
Int32.ToString("G", NumberFormatInfo.CurrentInfo)
afin de mettre en forme la valeur Int32 pour la culture
actuelle.

Int64 Appelle
Int64.ToString("G", NumberFormatInfo.CurrentInfo)
afin de mettre en forme la valeur Int64 pour la culture
actuelle.

SByte Appelle
SByte.ToString("G", NumberFormatInfo.CurrentInfo)
afin de mettre en forme la valeur SByte pour la culture
actuelle.

Single Appelle
Single.ToString("G", NumberFormatInfo.CurrentInfo)
afin de mettre en forme la valeur Single pour la culture
actuelle.

UInt16 Appelle
UInt16.ToString("G", NumberFormatInfo.CurrentInfo)
afin de mettre en forme la valeur UInt16 pour la culture
actuelle.
TYPE SUB ST IT UT IO N DE TO ST RIN G

UInt32 Appelle
UInt32.ToString("G", NumberFormatInfo.CurrentInfo)
afin de mettre en forme la valeur UInt32 pour la culture
actuelle.

UInt64 Appelle
UInt64.ToString("G", NumberFormatInfo.CurrentInfo)
afin de mettre en forme la valeur UInt64 pour la culture
actuelle.

Méthode ToString et chaînes de format


Le recours à la méthode ToString ou la substitution de ToString ne valent que lorsqu'un objet a une seule
représentation sous forme de chaîne possible. Toutefois, la valeur d'un objet a souvent plusieurs
représentations. Par exemple, une température peut être exprimée en degrés Fahrenheit, Celsius ou Kelvin. De
même, la valeur entière 10 peut être représentée de plusieurs façons, dont 10, 10,0, 1,0e01 ou $10,00.
Pour permettre à une même valeur d’avoir plusieurs représentations sous forme de chaîne, .NET utilise des
chaînes de format. Une chaîne de format est une chaîne qui contient un ou plusieurs spécificateurs de format
prédéfinis, constitués d'un ou de plusieurs caractères servant à définir la manière dont la méthode ToString
doit mettre en forme sa sortie. La chaîne de format est ensuite passée en tant que paramètre à la méthode
ToString de l'objet et détermine la manière dont la représentation sous forme de chaîne de la valeur de cet
objet doit apparaître.
Dans .NET, tous les types numériques, types de date et d’heure et types énumération prennent en charge un jeu
prédéfini de spécificateurs de format. Vous pouvez aussi utiliser des chaînes de format pour définir plusieurs
représentations sous forme de chaîne de vos types de données définis par l'application.
Chaînes de format standard
Une chaîne de format standard comprend un spécificateur de format unique, qui est un caractère alphabétique
définissant la représentation sous forme de chaîne de l'objet auquel il s'applique, ainsi qu'un spécificateur de
précision facultatif qui affecte le nombre de chiffres affichés dans la chaîne de résultat. Si le spécificateur de
précision est omis ou n'est pas pris en charge, un spécificateur de format standard équivaut à une chaîne de
format standard.
.NET définit un jeu de spécificateurs de format standard pour tous les types numériques, types de date et
d’heure et types énumération. Par exemple, chacune de ces catégories prend en charge un spécificateur de
format standard "G", lequel définit une représentation sous forme de chaîne générale d'une valeur de ce type.
Les chaînes de format standard pour les types énumération contrôlent directement la représentation sous
forme de chaîne d'une valeur. Les chaînes de format passées à la méthode d’une valeur d’énumération
ToString déterminent si la valeur est affichée à l’aide de son nom de chaîne (spécificateurs de format "G" et
"F"), sa valeur intégrale sous-jacente (spécificateur de format "D") ou sa valeur hexadécimale (spécificateur de
format "X"). L'exemple suivant illustre l'utilisation de chaînes de format standard pour mettre en forme une
valeur d'énumération DayOfWeek .
DayOfWeek thisDay = DayOfWeek.Monday;
string[] formatStrings = {"G", "F", "D", "X"};

foreach (string formatString in formatStrings)


Console.WriteLine(thisDay.ToString(formatString));
// The example displays the following output:
// Monday
// Monday
// 1
// 00000001

Dim thisDay As DayOfWeek = DayOfWeek.Monday


Dim formatStrings() As String = {"G", "F", "D", "X"}

For Each formatString As String In formatStrings


Console.WriteLine(thisDay.ToString(formatString))
Next
' The example displays the following output:
' Monday
' Monday
' 1
' 00000001

Pour plus d’informations sur les chaînes de format d’énumération, consultez Enumeration Format Strings.
Les chaînes de format standard pour les types numériques définissent généralement une chaîne de résultat
dont l'apparence précise est contrôlée par une ou plusieurs valeurs de propriété. Par exemple, le spécificateur de
format "C" met en forme un nombre en tant que valeur monétaire. Quand vous appelez la ToString méthode
avec le spécificateur de format "C" comme seul paramètre, les valeurs de propriétés suivantes de l’objet de la
culture actuelle NumberFormatInfo sont utilisées pour définir la représentation sous forme de chaîne de la
valeur numérique :
La CurrencySymbol propriété, qui spécifie le symbole monétaire de la culture actuelle.
Propriété CurrencyNegativePattern ou CurrencyPositivePattern , qui retourne un entier déterminant :
la position du symbole monétaire ;
si les valeurs négatives sont indiquées par un signe négatif devant, par un signe négatif derrière
ou par des parenthèses ;
si un espace apparaît entre la valeur numérique et le symbole monétaire.
Propriété CurrencyDecimalDigits , qui définit le nombre de chiffres fractionnaires dans la chaîne de
résultat.
Propriété CurrencyDecimalSeparator , qui définit le symbole de séparateur décimal dans la chaîne de
résultat.
Propriété CurrencyGroupSeparator , qui définit le symbole du séparateur de groupes.
Propriété CurrencyGroupSizes , qui définit le nombre de chiffres de chaque groupe situé à gauche du
séparateur décimal.
Propriété NegativeSign , qui détermine le signe négatif utilisé dans la chaîne de résultat si les
parenthèses ne sont pas utilisées pour indiquer des valeurs négatives.
De plus, les chaînes de format numériques peuvent inclure un spécificateur de précision. La signification de ce
spécificateur dépend de la chaîne de format avec laquelle il est utilisé, mais il indique généralement le nombre
total de chiffres ou le nombre de chiffres fractionnaires qui doivent s'afficher dans la chaîne de résultat. Par
exemple, le code suivant utilise la chaîne numérique standard "X4" et un spécificateur de précision pour créer
une valeur de chaîne qui comprend quatre chiffres hexadécimaux.

byte[] byteValues = { 12, 163, 255 };


foreach (byte byteValue in byteValues)
Console.WriteLine(byteValue.ToString("X4"));
// The example displays the following output:
// 000C
// 00A3
// 00FF

Dim byteValues() As Byte = {12, 163, 255}


For Each byteValue As Byte In byteValues
Console.WriteLine(byteValue.ToString("X4"))
Next
' The example displays the following output:
' 000C
' 00A3
' 00FF

Pour plus d’informations sur les chaînes de format numériques standard, consultez Standard Numeric Format
Strings.
Les chaînes de format standard pour les valeurs de date et d'heure sont des alias de chaînes de format
personnalisées stockées par une propriété DateTimeFormatInfo particulière. Par exemple, l’appel ToString de la
méthode d’une valeur de date et d’heure avec le spécificateur de format "D" affiche la date et l’heure à l’aide de
la chaîne de format personnalisée stockée dans la propriété de la culture actuelle
DateTimeFormatInfo.LongDatePattern . (Pour plus d’informations sur les chaînes de format personnalisées,
consultez la section suivante.) L’exemple suivant illustre cette relation.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
DateTime date1 = new DateTime(2009, 6, 30);
Console.WriteLine("D Format Specifier: {0:D}", date1);
string longPattern = CultureInfo.CurrentCulture.DateTimeFormat.LongDatePattern;
Console.WriteLine("'{0}' custom format string: {1}",
longPattern, date1.ToString(longPattern));
}
}
// The example displays the following output when run on a system whose
// current culture is en-US:
// D Format Specifier: Tuesday, June 30, 2009
// 'dddd, MMMM dd, yyyy' custom format string: Tuesday, June 30, 2009
Imports System.Globalization

Module Example
Public Sub Main()
Dim date1 As Date = #6/30/2009#
Console.WriteLine("D Format Specifier: {0:D}", date1)
Dim longPattern As String = CultureInfo.CurrentCulture.DateTimeFormat.LongDatePattern
Console.WriteLine("'{0}' custom format string: {1}", _
longPattern, date1.ToString(longPattern))
End Sub
End Module
' The example displays the following output when run on a system whose
' current culture is en-US:
' D Format Specifier: Tuesday, June 30, 2009
' 'dddd, MMMM dd, yyyy' custom format string: Tuesday, June 30, 2009

Pour plus d’informations sur les chaînes de format de date et heure standard, consultez Standard Date and Time
Format Strings.
Vous pouvez également utiliser des chaînes de format standard pour définir la représentation sous forme de
chaîne d’un objet défini par l’application qui est produit par la méthode de l’objet ToString(String) . Vous
pouvez définir les spécificateurs de format standard spécifiques que votre objet prend en charge, et déterminer
s'ils respectent la casse. Votre implémentation de la méthode ToString(String) doit prendre en charge les
éléments suivants :
Spécificateur de format "G" qui représente un format habituel ou commun de l'objet. La surcharge sans
paramètre de la méthode ToString de votre objet doit appeler sa surcharge ToString(String) et lui
passer la chaîne de format standard "G".
Prise en charge d'un spécificateur de format qui est égal à une référence null ( Nothing en Visual Basic).
Un spécificateur de format qui est égal à une référence null doit être considéré comme équivalent au
spécificateur de format "G".
Par exemple, une classe Temperature peut stocker en interne la température en degrés Celsius et utiliser des
spécificateurs de format pour représenter la valeur de l'objet Temperature en degrés Celsius, Fahrenheit et
Kelvin. L'exemple suivant en est l'illustration.

using System;

public class Temperature


{
private decimal m_Temp;

public Temperature(decimal temperature)


{
this.m_Temp = temperature;
}

public decimal Celsius


{
get { return this.m_Temp; }
}

public decimal Kelvin


{
get { return this.m_Temp + 273.15m; }
}

public decimal Fahrenheit


{
get { return Math.Round(((decimal) (this.m_Temp * 9 / 5 + 32)), 2); }
}
public override string ToString()
{
return this.ToString("C");
}

public string ToString(string format)


{
// Handle null or empty string.
if (String.IsNullOrEmpty(format)) format = "C";
// Remove spaces and convert to uppercase.
format = format.Trim().ToUpperInvariant();

// Convert temperature to Fahrenheit and return string.


switch (format)
{
// Convert temperature to Fahrenheit and return string.
case "F":
return this.Fahrenheit.ToString("N2") + " °F";
// Convert temperature to Kelvin and return string.
case "K":
return this.Kelvin.ToString("N2") + " K";
// return temperature in Celsius.
case "G":
case "C":
return this.Celsius.ToString("N2") + " °C";
default:
throw new FormatException(String.Format("The '{0}' format string is not supported.", format));
}
}
}

public class Example


{
public static void Main()
{
Temperature temp1 = new Temperature(0m);
Console.WriteLine(temp1.ToString());
Console.WriteLine(temp1.ToString("G"));
Console.WriteLine(temp1.ToString("C"));
Console.WriteLine(temp1.ToString("F"));
Console.WriteLine(temp1.ToString("K"));

Temperature temp2 = new Temperature(-40m);


Console.WriteLine(temp2.ToString());
Console.WriteLine(temp2.ToString("G"));
Console.WriteLine(temp2.ToString("C"));
Console.WriteLine(temp2.ToString("F"));
Console.WriteLine(temp2.ToString("K"));

Temperature temp3 = new Temperature(16m);


Console.WriteLine(temp3.ToString());
Console.WriteLine(temp3.ToString("G"));
Console.WriteLine(temp3.ToString("C"));
Console.WriteLine(temp3.ToString("F"));
Console.WriteLine(temp3.ToString("K"));

Console.WriteLine(String.Format("The temperature is now {0:F}.", temp3));


}
}
// The example displays the following output:
// 0.00 °C
// 0.00 °C
// 0.00 °C
// 32.00 °F
// 273.15 K
// -40.00 °C
// -40.00 °C
// -40.00 °C
// -40.00 °C
// -40.00 °F
// 233.15 K
// 16.00 °C
// 16.00 °C
// 16.00 °C
// 60.80 °F
// 289.15 K
// The temperature is now 16.00 °C.

Public Class Temperature


Private m_Temp As Decimal

Public Sub New(temperature As Decimal)


Me.m_Temp = temperature
End Sub

Public ReadOnly Property Celsius() As Decimal


Get
Return Me.m_Temp
End Get
End Property

Public ReadOnly Property Kelvin() As Decimal


Get
Return Me.m_Temp + 273.15d
End Get
End Property

Public ReadOnly Property Fahrenheit() As Decimal


Get
Return Math.Round(CDec(Me.m_Temp * 9 / 5 + 32), 2)
End Get
End Property

Public Overrides Function ToString() As String


Return Me.ToString("C")
End Function

Public Overloads Function ToString(format As String) As String


' Handle null or empty string.
If String.IsNullOrEmpty(format) Then format = "C"
' Remove spaces and convert to uppercase.
format = format.Trim().ToUpperInvariant()

Select Case format


Case "F"
' Convert temperature to Fahrenheit and return string.
Return Me.Fahrenheit.ToString("N2") & " °F"
Case "K"
' Convert temperature to Kelvin and return string.
Return Me.Kelvin.ToString("N2") & " K"
Case "C", "G"
' Return temperature in Celsius.
Return Me.Celsius.ToString("N2") & " °C"
Case Else
Throw New FormatException(String.Format("The '{0}' format string is not supported.",
format))
End Select
End Function
End Class

Public Module Example


Public Sub Main()
Dim temp1 As New Temperature(0d)
Console.WriteLine(temp1.ToString())
Console.WriteLine(temp1.ToString("G"))
Console.WriteLine(temp1.ToString("C"))
Console.WriteLine(temp1.ToString("C"))
Console.WriteLine(temp1.ToString("F"))
Console.WriteLine(temp1.ToString("K"))

Dim temp2 As New Temperature(-40d)


Console.WriteLine(temp2.ToString())
Console.WriteLine(temp2.ToString("G"))
Console.WriteLine(temp2.ToString("C"))
Console.WriteLine(temp2.ToString("F"))
Console.WriteLine(temp2.ToString("K"))

Dim temp3 As New Temperature(16d)


Console.WriteLine(temp3.ToString())
Console.WriteLine(temp3.ToString("G"))
Console.WriteLine(temp3.ToString("C"))
Console.WriteLine(temp3.ToString("F"))
Console.WriteLine(temp3.ToString("K"))

Console.WriteLine(String.Format("The temperature is now {0:F}.", temp3))


End Sub
End Module
' The example displays the following output:
' 0.00 °C
' 0.00 °C
' 0.00 °C
' 32.00 °F
' 273.15 K
' -40.00 °C
' -40.00 °C
' -40.00 °C
' -40.00 °F
' 233.15 K
' 16.00 °C
' 16.00 °C
' 16.00 °C
' 60.80 °F
' 289.15 K
' The temperature is now 16.00 °C.

Chaînes de format personnalisées


Outre les chaînes de format standard, .NET définit des chaînes de format personnalisées pour les valeurs
numériques et les valeurs de date et d’heure. Une chaîne de format personnalisée se compose d'un ou de
plusieurs spécificateurs de format personnalisés qui définissent la représentation sous forme de chaîne d'une
valeur. Par exemple, la chaîne de format de date et d'heure personnalisée "yyyy\mm\dd hh:mm:ffff t zzz"
convertit une date en sa représentation sous forme de chaîne "2008/11/15 07:45:00.0000 P -08:00" pour la
culture en-US. De même, la chaîne de format personnalisée "0000" convertit la valeur entière 12 en "0012".
Pour obtenir la liste complète des chaînes de format personnalisées, consultez Custom Date and Time Format
Strings et Custom Numeric Format Strings.
Si une chaîne de format se compose d'un seul spécificateur de format personnalisé, le spécificateur de format
doit être précédé du symbole de pourcentage (%) pour éviter toute confusion avec un spécificateur de format
standard. L'exemple suivant utilise le spécificateur de format personnalisé "M" pour afficher un nombre à un
chiffre ou à deux chiffres du mois d'une date particulière.

DateTime date1 = new DateTime(2009, 9, 8);


Console.WriteLine(date1.ToString("%M")); // Displays 9

Dim date1 As Date = #09/08/2009#


Console.WriteLine(date1.ToString("%M")) ' Displays 9

De nombreuses chaînes de format standard pour les valeurs de date et d'heure sont des alias de chaînes de
format personnalisées qui sont définies par les propriétés de l'objet DateTimeFormatInfo . Les chaînes de
format personnalisées offrent également une souplesse considérable en matière de mise en forme définie par
l'application pour les valeurs numériques ou les valeurs de date et d'heure. Vous pouvez définir vos propres
chaînes de résultat personnalisées à la fois pour les valeurs numériques et pour les valeurs de date et d'heure
en combinant plusieurs spécificateurs de format personnalisés dans une chaîne de format personnalisée
unique. L'exemple suivant définit une chaîne de format personnalisée qui affiche le jour de la semaine entre
parenthèses après le nom du mois, le jour et l'année.

string customFormat = "MMMM dd, yyyy (dddd)";


DateTime date1 = new DateTime(2009, 8, 28);
Console.WriteLine(date1.ToString(customFormat));
// The example displays the following output if run on a system
// whose language is English:
// August 28, 2009 (Friday)

Dim customFormat As String = "MMMM dd, yyyy (dddd)"


Dim date1 As Date = #8/28/2009#
Console.WriteLine(date1.ToString(customFormat))
' The example displays the following output if run on a system
' whose language is English:
' August 28, 2009 (Friday)

L’exemple ci-dessous définit une chaîne de format personnalisée qui affiche une valeur Int64 sous la forme d’un
numéro de téléphone américain standard à sept chiffres avec son indicatif régional.

using System;

public class Example


{
public static void Main()
{
long number = 8009999999;
string fmt = "000-000-0000";
Console.WriteLine(number.ToString(fmt));
}
}
// The example displays the following output:
// 800-999-9999

Module Example
Public Sub Main()
Dim number As Long = 8009999999
Dim fmt As String = "000-000-0000"
Console.WriteLine(number.ToString(fmt))
End Sub
End Module
' The example displays the following output:

' The example displays the following output:


' 800-999-9999

Bien que les chaînes de format standard puissent généralement gérer la plupart des besoins de mise en forme
pour vos types définis par l'application, vous pouvez également définir des spécificateurs de format
personnalisés pour mettre en forme vos types.
Chaînes de format et types .NET
Tous les types numériques (c’est-à-dire, les types Byte, Decimal, Double, Int16, Int32, Int64, SByte, Single, UInt16,
UInt32, UInt64 et BigInteger), ainsi que les types DateTime, DateTimeOffset, TimeSpan et Guid, et tous les types
énumération, prennent en charge la mise en forme avec des chaînes de format. Pour plus d’informations sur les
chaînes de format spécifiques prises en charge par chaque type, consultez les rubriques suivantes :

T IT RE DÉF IN IT IO N

Chaînes de format numériques standard Décrit des chaînes de format standard qui créent des
représentations sous forme de chaîne couramment utilisées
de valeurs numériques.

Chaînes de format numériques personnalisées Décrit des chaînes de format personnalisées qui créent des
formats spécifiques à l'application pour les valeurs
numériques.

Chaînes de format de date et d’heure standard Décrit des chaînes de format standard qui créent des
représentations courantes de valeurs DateTime et
DateTimeOffset sous forme de chaînes.

Chaînes de format de date et d’heure personnalisées Décrit des chaînes de format personnalisées qui créent des
formats propres à l'application pour les valeurs DateTime et
DateTimeOffset.

Chaînes de format TimeSpan standard Décrit des chaînes de format standard qui créent des
représentations sous forme de chaîne couramment utilisées
d'intervalles de temps.

Chaînes de format TimeSpan personnalisées Décrit des chaînes de format personnalisées qui créent des
formats spécifiques à l'application pour les intervalles de
temps.

Chaînes de format d’énumération Décrit les chaînes de format standard qui sont utilisées pour
créer des représentations sous forme de chaîne de valeurs
d'énumération.

Guid.ToString(String) Décrit les chaînes de format standard pour les valeurs Guid .

Mise en forme dépendante de la culture avec les fournisseurs de


format
Si les spécificateurs de format vous permettent de personnaliser la mise en forme d'objets, la production, pour
ces derniers, d'une représentation sous forme de chaîne explicite requiert souvent des informations de mise en
forme supplémentaires. Par exemple, la mise en forme d'un nombre en tant que valeur monétaire en utilisant la
chaîne de format standard "C" ou une chaîne de format personnalisée telle que "$ #, #.00" requiert au minimum
l'existence d'informations à inclure dans la chaîne mise en forme concernant le symbole monétaire, le
séparateur de groupes et le séparateur décimal appropriés. Dans .NET, ces informations de mise en forme
supplémentaires sont disponibles sur l’interface IFormatProvider, laquelle est fournie comme paramètre d’une
ou plusieurs surcharges de la méthode ToString de types numériques et de types de date et d’heure. Des
implémentations de IFormatProvider sont utilisées dans .NET pour prendre en charge la mise en forme propre à
la culture. L'exemple suivant montre comment la représentation d'un objet sous forme de chaîne évolue
lorsqu'il est mis en forme avec trois objets IFormatProvider représentant des cultures différentes.
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
decimal value = 1603.42m;
Console.WriteLine(value.ToString("C3", new CultureInfo("en-US")));
Console.WriteLine(value.ToString("C3", new CultureInfo("fr-FR")));
Console.WriteLine(value.ToString("C3", new CultureInfo("de-DE")));
}
}
// The example displays the following output:
// $1,603.420
// 1 603,420 €
// 1.603,420 €

Imports System.Globalization

Public Module Example


Public Sub Main()
Dim value As Decimal = 1603.42d
Console.WriteLine(value.ToString("C3", New CultureInfo("en-US")))
Console.WriteLine(value.ToString("C3", New CultureInfo("fr-FR")))
Console.WriteLine(value.ToString("C3", New CultureInfo("de-DE")))
End Sub
End Module
' The example displays the following output:
' $1,603.420
' 1 603,420 €
' 1.603,420 €

L'interface IFormatProvider inclut une méthode, GetFormat(Type), qui a un seul paramètre spécifiant le type
d'objet qui fournit les informations de mise en forme. Si la méthode peut fournir un objet de ce type, elle le
retourne. Sinon, elle retourne une référence null ( Nothing en Visual Basic).
IFormatProvider.GetFormat est une méthode de rappel. Lorsque vous appelez une surcharge de méthode
ToString qui inclut un paramètre IFormatProvider , elle appelle la méthode GetFormat de cet objet
IFormatProvider . La méthode GetFormat est chargée de retourner les informations de mise en forme requises,
spécifiées par son paramètre formatType , à la méthode ToString .
Certaines méthodes de mise en forme ou de conversion de chaînes incluent un paramètre de type
IFormatProvider, mais la valeur de ce paramètre est souvent ignorée lorsque la méthode est appelée. Le tableau
suivant répertorie certaines des méthodes de mise en forme qui utilisent le paramètre et le type de l'objet Type
qu'elles passent à la méthode IFormatProvider.GetFormat .

M ÉT H O DE T Y P E DE PA RA M ÈT RE FORMATTYPE

Méthode ToString de types numériques System.Globalization.NumberFormatInfo

Méthode ToString de types de date et d'heure System.Globalization.DateTimeFormatInfo

String.Format System.ICustomFormatter

StringBuilder.AppendFormat System.ICustomFormatter
NOTE
Les méthodes ToString des types numériques et des types de date et d'heure sont surchargées, et seules certaines des
surcharges incluent un paramètre IFormatProvider . Si une méthode n'a pas de paramètre de type IFormatProvider,
l'objet retourné par la propriété CultureInfo.CurrentCulture est passé à la place. Par exemple, un appel à la méthode
Int32.ToString() par défaut a, pour résultat, un appel de méthode semblable au suivant :
Int32.ToString("G", System.Globalization.CultureInfo.CurrentCulture) .

.NET propose trois classes qui implémentent IFormatProvider :


DateTimeFormatInfo, une classe qui fournit des informations de mise en forme pour les valeurs de date
et d'heure pour une culture spécifique. Son implémentation de IFormatProvider.GetFormat retourne une
instance d'elle-même.
NumberFormatInfo, une classe qui fournit des informations de mise en forme des nombres pour une
culture spécifique. Son implémentation de IFormatProvider.GetFormat retourne une instance d'elle-
même.
CultureInfo. Son implémentation de IFormatProvider.GetFormat peut retourner un objet
NumberFormatInfo pour fournir des informations de mise en forme des nombres ou un objet
DateTimeFormatInfo pour fournir des informations de mise en forme des valeurs de date et d'heure.
Vous pouvez aussi implémenter votre propre fournisseur de format en remplacement de l'une de ces classes.
Toutefois, la méthode de votre implémentation GetFormat doit retourner un objet du type listé dans le tableau
précédent s’il doit fournir des informations de mise en forme à la ToString méthode.
Mise en forme dépendante de la culture des valeurs numériques
Par défaut, la mise en forme des valeurs numériques est dépendante de la culture. Si vous ne spécifiez pas de
culture lorsque vous appelez une méthode de mise en forme, les conventions de mise en forme de la culture
actuelle du thread sont utilisées. Ceci est illustré dans l'exemple ci-dessous où la culture actuelle du thread est
changée quatre fois avant que la méthode Decimal.ToString(String) soit appelée. Dans chaque cas, la chaîne
obtenue reflète les conventions de mise en forme de la culture actuelle. Ceci tient au fait que les méthodes
ToString et ToString(String) encapsulent les appels à la méthode ToString(String, IFormatProvider) de
chaque type numérique.
using System;
using System.Globalization;
using System.Threading;

public class Example


{
public static void Main()
{
string[] cultureNames = { "en-US", "fr-FR", "es-MX", "de-DE" };
Decimal value = 1043.17m;

foreach (var cultureName in cultureNames) {


// Change the current thread culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName);
Console.WriteLine("The current culture is {0}",
Thread.CurrentThread.CurrentCulture.Name);
Console.WriteLine(value.ToString("C2"));
Console.WriteLine();
}
}
}
// The example displays the following output:
// The current culture is en-US
// $1,043.17
//
// The current culture is fr-FR
// 1 043,17 €
//
// The current culture is es-MX
// $1,043.17
//
// The current culture is de-DE
// 1.043,17 €

Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
Dim cultureNames() As String = {"en-US", "fr-FR", "es-MX", "de-DE"}
Dim value As Decimal = 1043.17d

For Each cultureName In cultureNames


' Change the current thread culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName)
Console.WriteLine("The current culture is {0}",
Thread.CurrentThread.CurrentCulture.Name)
Console.WriteLine(value.ToString("C2"))
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' The current culture is en-US
' $1,043.17
'
' The current culture is fr-FR
' 1 043,17 €
'
' The current culture is es-MX
' $1,043.17
'
' The current culture is de-DE
' 1.043,17 €
Vous pouvez également mettre en forme une valeur numérique pour une culture spécifique en appelant une
surcharge ToString dotée d'un paramètre provider et en lui passant l'un ou l'autre des éléments suivants :
Un objet CultureInfo représentant la culture dont les conventions de mise en forme doivent être utilisées.
Sa méthode CultureInfo.GetFormat retourne la valeur de la propriété CultureInfo.NumberFormat , qui est
l'objet NumberFormatInfo qui fournit des informations de mise en forme propres à la culture pour les
valeurs numériques.
Un objet NumberFormatInfo définissant les conventions de mise en forme propres à la culture qui
doivent être utilisées. Sa méthode GetFormat retourne une instance d'elle-même.
L'exemple suivant utilise des objets NumberFormatInfo qui représentent les cultures Anglais (États-Unis) et
Anglais (Royaume-Uni), ainsi que les cultures neutres Français et Russe pour mettre en forme un nombre à
virgule flottante.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
Double value = 1043.62957;
string[] cultureNames = { "en-US", "en-GB", "ru", "fr" };

foreach (var name in cultureNames) {


NumberFormatInfo nfi = CultureInfo.CreateSpecificCulture(name).NumberFormat;
Console.WriteLine("{0,-6} {1}", name + ":", value.ToString("N3", nfi));
}
}
}
// The example displays the following output:
// en-US: 1,043.630
// en-GB: 1,043.630
// ru: 1 043,630
// fr: 1 043,630

Imports System.Globalization

Module Example
Public Sub Main()
Dim value As Double = 1043.62957
Dim cultureNames() As String = {"en-US", "en-GB", "ru", "fr"}

For Each name In cultureNames


Dim nfi As NumberFormatInfo = CultureInfo.CreateSpecificCulture(name).NumberFormat
Console.WriteLine("{0,-6} {1}", name + ":", value.ToString("N3", nfi))
Next
End Sub
End Module
' The example displays the following output:
' en-US: 1,043.630
' en-GB: 1,043.630
' ru: 1 043,630
' fr: 1 043,630

Mise en forme dépendante de la culture des valeurs de date et d’heure


Par défaut, la mise en forme des valeurs de date et d'heure est dépendante de la culture. Si vous ne spécifiez pas
de culture lorsque vous appelez une méthode de mise en forme, les conventions de mise en forme de la culture
actuelle du thread sont utilisées. Ceci est illustré dans l'exemple ci-dessous où la culture actuelle du thread est
changée quatre fois avant que la méthode DateTime.ToString(String) soit appelée. Dans chaque cas, la chaîne
obtenue reflète les conventions de mise en forme de la culture actuelle. Ceci tient au fait que les méthodes
DateTime.ToString(), DateTime.ToString(String), DateTimeOffset.ToString()et DateTimeOffset.ToString(String)
encapsulent les appels aux méthodes DateTime.ToString(String, IFormatProvider) et
DateTimeOffset.ToString(String, IFormatProvider) .

using System;
using System.Globalization;
using System.Threading;

public class Example


{
public static void Main()
{
string[] cultureNames = { "en-US", "fr-FR", "es-MX", "de-DE" };
DateTime dateToFormat = new DateTime(2012, 5, 28, 11, 30, 0);

foreach (var cultureName in cultureNames) {


// Change the current thread culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName);
Console.WriteLine("The current culture is {0}",
Thread.CurrentThread.CurrentCulture.Name);
Console.WriteLine(dateToFormat.ToString("F"));
Console.WriteLine();
}
}
}
// The example displays the following output:
// The current culture is en-US
// Monday, May 28, 2012 11:30:00 AM
//
// The current culture is fr-FR
// lundi 28 mai 2012 11:30:00
//
// The current culture is es-MX
// lunes, 28 de mayo de 2012 11:30:00 a.m.
//
// The current culture is de-DE
// Montag, 28. Mai 2012 11:30:00
Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
Dim cultureNames() As String = {"en-US", "fr-FR", "es-MX", "de-DE"}
Dim dateToFormat As Date = #5/28/2012 11:30AM#

For Each cultureName In cultureNames


' Change the current thread culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName)
Console.WriteLine("The current culture is {0}",
Thread.CurrentThread.CurrentCulture.Name)
Console.WriteLine(dateToFormat.ToString("F"))
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' The current culture is en-US
' Monday, May 28, 2012 11:30:00 AM
'
' The current culture is fr-FR
' lundi 28 mai 2012 11:30:00
'
' The current culture is es-MX
' lunes, 28 de mayo de 2012 11:30:00 a.m.
'
' The current culture is de-DE
' Montag, 28. Mai 2012 11:30:00

Vous pouvez également mettre en forme une valeur de date et d'heure pour une culture spécifique en appelant
une surcharge DateTime.ToString ou DateTimeOffset.ToString dotée d'un paramètre provider et en lui passant
l'un ou l'autre des éléments suivants :
Un objet CultureInfo représentant la culture dont les conventions de mise en forme doivent être utilisées.
Sa méthode CultureInfo.GetFormat retourne la valeur de la propriété CultureInfo.DateTimeFormat , qui
est l'objet DateTimeFormatInfo qui fournit des informations de mise en forme propres à la culture pour
les valeurs de date et d'heure.
Un objet DateTimeFormatInfo définissant les conventions de mise en forme propres à la culture qui
doivent être utilisées. Sa méthode GetFormat retourne une instance d'elle-même.
L'exemple suivant utilise des objets DateTimeFormatInfo qui représentent les cultures Anglais (États-Unis) et
Anglais (Royaume-Uni), ainsi que les cultures neutres Français et Russe pour mettre en forme une date.
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
DateTime dat1 = new DateTime(2012, 5, 28, 11, 30, 0);
string[] cultureNames = { "en-US", "en-GB", "ru", "fr" };

foreach (var name in cultureNames) {


DateTimeFormatInfo dtfi = CultureInfo.CreateSpecificCulture(name).DateTimeFormat;
Console.WriteLine("{0}: {1}", name, dat1.ToString(dtfi));
}
}
}
// The example displays the following output:
// en-US: 5/28/2012 11:30:00 AM
// en-GB: 28/05/2012 11:30:00
// ru: 28.05.2012 11:30:00
// fr: 28/05/2012 11:30:00

Imports System.Globalization

Module Example
Public Sub Main()
Dim dat1 As Date = #5/28/2012 11:30AM#
Dim cultureNames() As String = {"en-US", "en-GB", "ru", "fr"}

For Each name In cultureNames


Dim dtfi As DateTimeFormatInfo = CultureInfo.CreateSpecificCulture(name).DateTimeFormat
Console.WriteLine("{0}: {1}", name, dat1.ToString(dtfi))
Next
End Sub
End Module
' The example displays the following output:
' en-US: 5/28/2012 11:30:00 AM
' en-GB: 28/05/2012 11:30:00
' ru: 28.05.2012 11:30:00
' fr: 28/05/2012 11:30:00

Interface IFormattable
En règle générale, les types qui surchargent la méthode ToString avec une chaîne de format et un paramètre
IFormatProvider implémentent également l'interface IFormattable . Cette interface comprend un seul membre,
IFormattable.ToString(String, IFormatProvider), qui inclut comme paramètres une chaîne de format et un
fournisseur de format.
L'implémentation de l'interface IFormattable pour votre classe définie par l'application présente deux avantages
:
Prise en charge de la conversion de chaînes par la classe Convert . Les appels aux méthodes
Convert.ToString(Object) et Convert.ToString(Object, IFormatProvider) appellent automatiquement votre
implémentation d' IFormattable .
Prise en charge de la mise en forme composite. Si un élément de mise en forme qui inclut une chaîne de
format est utilisé pour mettre en forme votre type personnalisé, le Common Language Runtime appelle
automatiquement votre implémentation d' IFormattable et lui passe la chaîne de format. Pour plus
d’informations sur la mise en forme composite avec des méthodes telles que String.Format ou
Console.WriteLine , consultez la section mise en forme composite .
L'exemple suivant définit une classe Temperature qui implémente l'interface IFormattable . Il prend en charge
les spécificateurs de format "C" ou "G" pour afficher la température en Celsius, le spécificateur de format "F"
pour afficher la température en Fahrenheit et le spécificateur de format "K" pour afficher la température en
Kelvin.
using System;
using System.Globalization;

public class Temperature : IFormattable


{
private decimal m_Temp;

public Temperature(decimal temperature)


{
this.m_Temp = temperature;
}

public decimal Celsius


{
get { return this.m_Temp; }
}

public decimal Kelvin


{
get { return this.m_Temp + 273.15m; }
}

public decimal Fahrenheit


{
get { return Math.Round((decimal) this.m_Temp * 9 / 5 + 32, 2); }
}

public override string ToString()


{
return this.ToString("G", null);
}

public string ToString(string format)


{
return this.ToString(format, null);
}

public string ToString(string format, IFormatProvider provider)


{
// Handle null or empty arguments.
if (String.IsNullOrEmpty(format))
format = "G";
// Remove any white space and covert to uppercase.
format = format.Trim().ToUpperInvariant();

if (provider == null)
provider = NumberFormatInfo.CurrentInfo;

switch (format)
{
// Convert temperature to Fahrenheit and return string.
case "F":
return this.Fahrenheit.ToString("N2", provider) + "°F";
// Convert temperature to Kelvin and return string.
case "K":
return this.Kelvin.ToString("N2", provider) + "K";
// Return temperature in Celsius.
case "C":
case "G":
return this.Celsius.ToString("N2", provider) + "°C";
default:
throw new FormatException(String.Format("The '{0}' format string is not supported.", format));
}
}
}
Imports System.Globalization

Public Class Temperature : Implements IFormattable


Private m_Temp As Decimal

Public Sub New(temperature As Decimal)


Me.m_Temp = temperature
End Sub

Public ReadOnly Property Celsius() As Decimal


Get
Return Me.m_Temp
End Get
End Property

Public ReadOnly Property Kelvin() As Decimal


Get
Return Me.m_Temp + 273.15d
End Get
End Property

Public ReadOnly Property Fahrenheit() As Decimal


Get
Return Math.Round(CDec(Me.m_Temp * 9 / 5 + 32), 2)
End Get
End Property

Public Overrides Function ToString() As String


Return Me.ToString("G", Nothing)
End Function

Public Overloads Function ToString(format As String) As String


Return Me.ToString(format, Nothing)
End Function

Public Overloads Function ToString(format As String, provider As IFormatProvider) As String _


Implements IFormattable.ToString

' Handle null or empty arguments.


If String.IsNullOrEmpty(format) Then format = "G"
' Remove any white space and convert to uppercase.
format = format.Trim().ToUpperInvariant()

If provider Is Nothing Then provider = NumberFormatInfo.CurrentInfo

Select Case format


' Convert temperature to Fahrenheit and return string.
Case "F"
Return Me.Fahrenheit.ToString("N2", provider) & "°F"
' Convert temperature to Kelvin and return string.
Case "K"
Return Me.Kelvin.ToString("N2", provider) & "K"
' Return temperature in Celsius.
Case "C", "G"
Return Me.Celsius.ToString("N2", provider) & "°C"
Case Else
Throw New FormatException(String.Format("The '{0}' format string is not supported.",
format))
End Select
End Function
End Class

L'exemple suivant instancie un objet Temperature . Il appelle ensuite la méthode ToString et utilise plusieurs
chaînes de format composites pour obtenir des représentations sous forme de chaîne différentes d'un objet
Temperature . Chacun de ces appels de méthode appelle, à son tour, l'implémentation d' IFormattable de la
classe Temperature .

public class Example


{
public static void Main()
{
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
Temperature temp = new Temperature(22m);
Console.WriteLine(Convert.ToString(temp, new CultureInfo("ja-JP")));
Console.WriteLine("Temperature: {0:K}", temp);
Console.WriteLine("Temperature: {0:F}", temp);
Console.WriteLine(String.Format(new CultureInfo("fr-FR"), "Temperature: {0:F}", temp));
}
}
// The example displays the following output:
// 22.00°C
// Temperature: 295.15K
// Temperature: 71.60°F
// Temperature: 71,60°F

Public Module Example


Public Sub Main()
Dim temp As New Temperature(22d)
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US")
Console.WriteLine(Convert.ToString(temp1, New CultureInfo("ja-JP")))
Console.WriteLine("Temperature: {0:K}", temp)
Console.WriteLine("Temperature: {0:F}", temp)
Console.WriteLine(String.Format(New CultureInfo("fr-FR"), "Temperature: {0:F}", temp))
End Sub
End Module
' The example displays the following output:
' 22.00°C
' Temperature: 295.15K
' Temperature: 71.60°F
' Temperature: 71,60°F

Mise en forme composite


Certaines méthodes, telles que String.Format et StringBuilder.AppendFormat, prennent en charge la mise en
forme composite. Une chaîne de format composite est un genre de modèle retournant une seule chaîne qui
incorpore la représentation sous forme de chaîne de zéro, un ou plusieurs objets. Chaque objet est représenté
dans la chaîne de format composite par un élément de mise en forme indexé. L'index de l'élément de mise en
forme correspond à la position de l'objet qu'il représente dans la liste de paramètres de la méthode. Les index
sont de base zéro. Par exemple, dans l'appel suivant à la méthode String.Format, le premier élément de mise en
forme, {0:D} , est remplacé par la représentation sous forme de chaîne de thatDate ; le deuxième élément de
mise en forme, {1} , est remplacé par la représentation sous forme de chaîne item1 ; le troisième élément de
mise en forme, {2:C2} , est remplacé par la représentation sous forme de chaîne de item1.Value .

result = String.Format("On {0:d}, the inventory of {1} was worth {2:C2}.",


thatDate, item1, item1.Value);
Console.WriteLine(result);
// The example displays output like the following if run on a system
// whose current culture is en-US:
// On 5/1/2009, the inventory of WidgetA was worth $107.44.
result = String.Format("On {0:d}, the inventory of {1} was worth {2:C2}.", _
thatDate, item1, item1.Value)
Console.WriteLine(result)
' The example displays output like the following if run on a system
' whose current culture is en-US:
' On 5/1/2009, the inventory of WidgetA was worth $107.44.

En plus de remplacer un élément de format par la représentation sous forme de chaîne de l'objet
correspondant, les éléments de format vous permettent également de contrôler les éléments suivants :
La façon spécifique dont un objet est représenté sous forme de chaîne, si l'objet implémente l'interface
IFormattable et prend en charge les chaînes de format. Ceci se fait en faisant suivre l'index de l'élément
de format d'un : (deux-points), suivi d'une chaîne de format valide. L'exemple précédent a fait cela en
mettant en forme une valeur de date avec la chaîne de format "d" (modèle de date courte) (par exemple
{0:d} ) et en mettant en forme une valeur numérique avec la chaîne de format "C2" (par exemple
{2:C2} pour représenter le nombre comme valeur monétaire avec deux décimales.

La largeur du champ qui contient la représentation sous forme de chaîne de l'objet, et l'alignement de la
représentation sous forme de chaîne de ce champ. Ceci se fait en faisant suivre l'index de l'élément de
format d'une , (virgule), suivie de la largeur du champ. La chaîne est alignée à droite dans le champ si
la largeur du champ est une valeur positive, et elle est alignée à gauche si la largeur du champ est une
valeur négative. L'exemple suivant aligne à gauche des valeurs de date dans un champ de 20 caractères,
et aligne à droite des valeurs décimales avec une décimale dans un champ de 11 caractères.

DateTime startDate = new DateTime(2015, 8, 28, 6, 0, 0);


decimal[] temps = { 73.452m, 68.98m, 72.6m, 69.24563m,
74.1m, 72.156m, 72.228m };
Console.WriteLine("{0,-20} {1,11}\n", "Date", "Temperature");
for (int ctr = 0; ctr < temps.Length; ctr++)
Console.WriteLine("{0,-20:g} {1,11:N1}", startDate.AddDays(ctr), temps[ctr]);

// The example displays the following output:


// Date Temperature
//
// 8/28/2015 6:00 AM 73.5
// 8/29/2015 6:00 AM 69.0
// 8/30/2015 6:00 AM 72.6
// 8/31/2015 6:00 AM 69.2
// 9/1/2015 6:00 AM 74.1
// 9/2/2015 6:00 AM 72.2
// 9/3/2015 6:00 AM 72.2
Dim startDate As New Date(2015, 8, 28, 6, 0, 0)
Dim temps() As Decimal = {73.452, 68.98, 72.6, 69.24563,
74.1, 72.156, 72.228}
Console.WriteLine("{0,-20} {1,11}", "Date", "Temperature")
Console.WriteLine()
For ctr As Integer = 0 To temps.Length - 1
Console.WriteLine("{0,-20:g} {1,11:N1}", startDate.AddDays(ctr), temps(ctr))
Next
' The example displays the following output:
' Date Temperature
'
' 8/28/2015 6:00 AM 73.5
' 8/29/2015 6:00 AM 69.0
' 8/30/2015 6:00 AM 72.6
' 8/31/2015 6:00 AM 69.2
' 9/1/2015 6:00 AM 74.1
' 9/2/2015 6:00 AM 72.2
' 9/3/2015 6:00 AM 72.2

Notez que si le composant de chaîne d'alignement et le composant de chaîne de format sont présents, le
premier a priorité sur le deuxième (par exemple {0,-20:g} .
Pour plus d’informations sur la mise en forme composite, consultez Composite Formatting.

Mise en forme personnalisée avec ICustomFormatter


Deux méthodes de mise en forme composites, String.Format(IFormatProvider, String, Object[]) et
StringBuilder.AppendFormat(IFormatProvider, String, Object[]), incluent également un paramètre de fournisseur
de format qui prend en charge la mise en forme personnalisée. Quand l’une de ces méthodes de mise en forme
est appelée, elle passe un Type objet qui représente une ICustomFormatter interface à la méthode du
fournisseur de format GetFormat . La méthode GetFormat est alors chargée de retourner l'implémentation d'
ICustomFormatter qui fournit la mise en forme personnalisée.
L'interface ICustomFormatter a une méthode unique, Format(String, Object, IFormatProvider), qui est appelée
automatiquement par une méthode de mise en forme composite, une fois pour chaque élément de mise en
forme dans une chaîne de format composite. La méthode Format(String, Object, IFormatProvider) a trois
paramètres : une chaîne de format, qui représente l'argument formatString dans un élément de mise en forme,
un objet à mettre en forme et un objet IFormatProvider qui fournit des services de mise en forme. En général, la
classe qui implémente ICustomFormatter implémente également IFormatProvider; ce dernier paramètre est
donc une référence à la classe de mise en forme personnalisée elle-même. La méthode retourne une
représentation sous forme de chaîne mise en forme personnalisée de l'objet à mettre en forme. Si la méthode
ne peut pas mettre en forme l'objet, elle doit retourner une référence null ( Nothing en Visual Basic).
L'exemple suivant fournit une implémentation d' ICustomFormatter nommée ByteByByteFormatter qui affiche
des valeurs entières sous la forme d'une séquence de valeurs hexadécimales à deux chiffres suivie d'un espace.
public class ByteByByteFormatter : IFormatProvider, ICustomFormatter
{
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
return this;
else
return null;
}

public string Format(string format, object arg,


IFormatProvider formatProvider)
{
if (! formatProvider.Equals(this)) return null;

// Handle only hexadecimal format string.


if (! format.StartsWith("X")) return null;

byte[] bytes;
string output = null;

// Handle only integral types.


if (arg is Byte)
bytes = BitConverter.GetBytes((Byte) arg);
else if (arg is Int16)
bytes = BitConverter.GetBytes((Int16) arg);
else if (arg is Int32)
bytes = BitConverter.GetBytes((Int32) arg);
else if (arg is Int64)
bytes = BitConverter.GetBytes((Int64) arg);
else if (arg is SByte)
bytes = BitConverter.GetBytes((SByte) arg);
else if (arg is UInt16)
bytes = BitConverter.GetBytes((UInt16) arg);
else if (arg is UInt32)
bytes = BitConverter.GetBytes((UInt32) arg);
else if (arg is UInt64)
bytes = BitConverter.GetBytes((UInt64) arg);
else
return null;

for (int ctr = bytes.Length - 1; ctr >= 0; ctr--)


output += String.Format("{0:X2} ", bytes[ctr]);

return output.Trim();
}
}
Public Class ByteByByteFormatter : Implements IFormatProvider, ICustomFormatter
Public Function GetFormat(formatType As Type) As Object _
Implements IFormatProvider.GetFormat
If formatType Is GetType(ICustomFormatter) Then
Return Me
Else
Return Nothing
End If
End Function

Public Function Format(fmt As String, arg As Object,


formatProvider As IFormatProvider) As String _
Implements ICustomFormatter.Format

If Not formatProvider.Equals(Me) Then Return Nothing

' Handle only hexadecimal format string.


If Not fmt.StartsWith("X") Then
Return Nothing
End If

' Handle only integral types.


If Not typeof arg Is Byte AndAlso
Not typeof arg Is Int16 AndAlso
Not typeof arg Is Int32 AndAlso
Not typeof arg Is Int64 AndAlso
Not typeof arg Is SByte AndAlso
Not typeof arg Is UInt16 AndAlso
Not typeof arg Is UInt32 AndAlso
Not typeof arg Is UInt64 Then _
Return Nothing

Dim bytes() As Byte = BitConverter.GetBytes(arg)


Dim output As String = Nothing

For ctr As Integer = bytes.Length - 1 To 0 Step -1


output += String.Format("{0:X2} ", bytes(ctr))
Next

Return output.Trim()
End Function
End Class

L'exemple suivant utilise la classe ByteByByteFormatter pour mettre en forme des valeurs entières. Notez que la
méthode ICustomFormatter.Format est appelée plusieurs fois dans le deuxième appel de méthode
String.Format(IFormatProvider, String, Object[]), et que le fournisseur NumberFormatInfo par défaut est utilisé
dans le troisième appel de méthode, car la méthode . ByteByByteFormatter.Format ne reconnaît pas la chaîne de
format "N0" et retourne une référence null ( Nothing en Visual Basic).
public class Example
{
public static void Main()
{
long value = 3210662321;
byte value1 = 214;
byte value2 = 19;

Console.WriteLine(String.Format(new ByteByByteFormatter(), "{0:X}", value));


Console.WriteLine(String.Format(new ByteByByteFormatter(), "{0:X} And {1:X} = {2:X} ({2:000})",
value1, value2, value1 & value2));
Console.WriteLine(String.Format(new ByteByByteFormatter(), "{0,10:N0}", value));
}
}
// The example displays the following output:
// 00 00 00 00 BF 5E D1 B1
// 00 D6 And 00 13 = 00 12 (018)
// 3,210,662,321

Public Module Example


Public Sub Main()
Dim value As Long = 3210662321
Dim value1 As Byte = 214
Dim value2 As Byte = 19

Console.WriteLine((String.Format(New ByteByByteFormatter(), "{0:X}", value)))


Console.WriteLine((String.Format(New ByteByByteFormatter(), "{0:X} And {1:X} = {2:X} ({2:000})",
value1, value2, value1 And value2)))
Console.WriteLine(String.Format(New ByteByByteFormatter(), "{0,10:N0}", value))
End Sub
End Module
' The example displays the following output:
' 00 00 00 00 BF 5E D1 B1
' 00 D6 And 00 13 = 00 12 (018)
' 3,210,662,321

Rubriques connexes
T IT RE DÉF IN IT IO N

Chaînes de format numériques standard Décrit des chaînes de format standard qui créent des
représentations sous forme de chaîne couramment utilisées
de valeurs numériques.

Chaînes de format numériques personnalisées Décrit des chaînes de format personnalisées qui créent des
formats spécifiques à l'application pour les valeurs
numériques.

Chaînes de format de date et d’heure standard Décrit des chaînes de format standard qui créent des
représentations sous forme de chaîne couramment utilisées
de valeurs DateTime .

Chaînes de format de date et d’heure personnalisées Décrit des chaînes de format personnalisées qui créent des
formats spécifiques à l'application pour les valeurs DateTime
.

Chaînes de format TimeSpan standard Décrit des chaînes de format standard qui créent des
représentations sous forme de chaîne couramment utilisées
d'intervalles de temps.
T IT RE DÉF IN IT IO N

Chaînes de format TimeSpan personnalisées Décrit des chaînes de format personnalisées qui créent des
formats spécifiques à l'application pour les intervalles de
temps.

Chaînes de format d’énumération Décrit les chaînes de format standard qui sont utilisées pour
créer des représentations sous forme de chaîne de valeurs
d'énumération.

Mise en forme composite Explique comment incorporer une ou plusieurs valeurs mises
en forme dans une chaîne. La chaîne peut ensuite être
affichée dans la console ou écrite dans un flux.

Analyse de chaînes Décrit comment initialiser des objets aux valeurs décrites par
des représentations sous forme de chaîne de ces objets.
L'analyse est l'opération inverse de la mise en forme.

Référence
System.IFormattable
System.IFormatProvider
System.ICustomFormatter
Chaînes de format numériques standard
18/07/2020 • 49 minutes to read • Edit Online

Les chaînes de format numériques standard sont utilisées pour mettre en forme des types numériques courants.
Une chaîne de format numérique standard se présente sous la forme Axx , où :
A est un caractère alphabétique unique appelé spécificateur de format. Toute chaîne de format
numérique contenant plusieurs caractères alphabétiques, y compris un espace blanc, est interprétée
comme une chaîne de format numérique personnalisée. Pour plus d’informations, consultez Chaînes de
format numériques personnalisées.
xx est un entier facultatif appelé spécificateur de précision. Le spécificateur de précision est compris
entre 0 et 99 ; il affecte le nombre de chiffres dans le résultat. Notez que le spécificateur de précision
contrôle le nombre de chiffres dans la représentation sous forme de chaîne d'un nombre. Il n'arrondit pas
le nombre lui-même. Pour exécuter une opération d'arrondi, utilisez la méthode Math.Ceiling, Math.Floor
ou Math.Round.
Quand le spécificateur de précision contrôle le nombre de chiffres fractionnaires dans la chaîne de
résultat, celle-ci reflète un nombre qui est arrondi à un résultat représentable le plus proche du résultat
précis à l’infini. S’il existe deux résultats représentables aussi proches l’un que l’autre :
Sur le .NET Framework et .NET Core jusqu’à .NET Core 2.0 , le runtime sélectionne le résultat
ayant le chiffre le moins significatif le plus élevé (autrement dit, à l’aide de
MidpointRounding.AwayFromZero).
Sur .NET Core 2.1 et versions ultérieures , le runtime sélectionne le résultat ayant un chiffre
encore moins significatif (autrement dit, à l’aide de MidpointRounding.ToEven).

NOTE
Le spécificateur de précision détermine le nombre de chiffres dans la chaîne de résultat. Pour remplir une chaîne de
résultat avec des espaces de début ou de fin, utilisez la fonctionnalité de mise en forme composite et définissez un
composant d’alignement dans l’élément de mise en forme.

Les chaînes de format numériques standard sont prises en charge par :


Certaines surcharges de la méthode ToString de tous les types numériques. Par exemple, vous pouvez
fournir une chaîne de format numérique aux méthodes Int32.ToString(String) et Int32.ToString(String,
IFormatProvider).
La fonctionnalité de mise en forme composite .NET, utilisée par certaines méthodes Write et WriteLine
des classes Console et StreamWriter, la méthode String.Format et la méthode
StringBuilder.AppendFormat. La fonctionnalité de mise en forme composite vous permet d’inclure la
représentation sous forme de chaîne de plusieurs éléments de données dans une même chaîne, de
spécifier la largeur d’un champ et d’aligner les nombres dans un champ. Pour plus d’informations,
consultez Mise en forme composite.
Les chaînes interpolées en C# et Visual Basic, qui fournissent une syntaxe simplifiée par rapport aux de
chaînes de format composite.
TIP
Vous pouvez télécharger l’utilitaire de mise en forme , application .NET Core Windows Forms qui vous permet
d’appliquer des chaînes de mise en forme à des valeurs numériques ou à des valeurs de date et d’heure, et d’afficher la
chaîne de résultat. Le code source est disponible pour C# et Visual Basic.

Le tableau suivant décrit les spécificateurs de format numériques standard et affiche un exemple de sortie
produite par chaque spécificateur de format. Consultez la section Remarques pour plus d’informations sur
l’utilisation de chaînes de format numériques standard, et la section Exemple pour obtenir une illustration
complète de leur utilisation.

SP ÉC IF IC AT EUR DE F O RM AT NOM DESC RIP T IO N EXEM P L ES

"C" ou "c" Devise Résultat : une valeur 123,456 ("C", en-US)-> \


monétaire. $123,46

Pris en charge par : tous les 123.456 ("C", fr-FR) ->


types numériques. 123,46 €

Spécificateur de précision : 123.456 ("C", ja-JP) ->


nombre de chiffres ¥123
décimaux.
-123,456 ("C3", en-US)-> (
Spécificateur de précision \ $123,456)
par défaut : défini par
NumberFormatInfo.Currenc -123.456 ("C3", fr-FR) -> -
yDecimalDigits. 123,456 €

Informations -123.456 ("C3", ja-JP) ->


supplémentaires : -¥123.456
Spécificateur de format
monétaire ("C").

"D" ou "d" Decimal Résultat : chiffres entiers 1234 ("D") -> 1234
avec un signe négatif
facultatif. -1234 ("D6") -> -001234

Pris en charge par : les


types intégraux
uniquement.

Spécificateur de précision :
nombre minimal de chiffres.

Spécificateur de précision
par défaut : nombre
minimal de chiffres requis.

Informations
supplémentaires :
Spécificateur de format
décimal ("D").
SP ÉC IF IC AT EUR DE F O RM AT NOM DESC RIP T IO N EXEM P L ES

"E" ou "e" Exponentiel (scientifique) Résultat : notation 1052.0329112756 ("E", en-


exponentielle. US) -> 1.052033E+003

Pris en charge par : tous les 1052.0329112756 ("e", fr-


types numériques. FR) -> 1,052033e+003

Spécificateur de précision : -1052.0329112756 ("e2",


nombre de chiffres en-US) -> -1.05e+003
décimaux.
-1052.0329112756 ("E2",
Spécificateur de précision fr-FR) -> -1,05E+003
par défaut : 6.

Informations
supplémentaires :
Spécificateur de format
exponentiel ("E").

"F" ou "f" Virgule fixe Résultat : chiffres intégraux 1234.567 ("F", en-US) ->
et décimaux avec un signe 1234.57
négatif facultatif.
1234.567 ("F", de-DE) ->
Pris en charge par : tous les 1234,57
types numériques.
1234 ("F1", en-US) ->
Spécificateur de précision : 1234.0
nombre de chiffres
décimaux. 1234 ("F1", de-DE) ->
1234,0
Spécificateur de précision
par défaut : défini par -1234.56 ("F4", en-US) -> -
NumberFormatInfo.Number 1234.5600
DecimalDigits.
-1234.56 ("F4", de-DE) -> -
Informations 1234,5600
supplémentaires :
Spécificateur de format à
virgule fixe ("F").

"G" ou "g" Général Résultat : format le plus -123.456 ("G", en-US) -> -
compact (notation à virgule 123.456
fixe ou scientifique).
-123.456 ("G", sv-SE) -> -
Pris en charge par : tous les 123,456
types numériques.
123.4546 ("G4", en-US) ->
Spécificateur de précision : 123.5
nombre de chiffres
significatifs. 123.4546 ("G4", sv-SE) ->
123,5
Spécificateur de précision
par défaut : dépend du type -1.234567890e-25 ("G",
numérique. en-US) -> -1.23456789E-
25
Informations
supplémentaires : -1.234567890e-25 ("G", sv-
Spécificateur de format SE) -> -1,23456789E-25
standard ("G").
SP ÉC IF IC AT EUR DE F O RM AT NOM DESC RIP T IO N EXEM P L ES

"N" ou "n" Nombre Résultat : chiffres intégraux 1234.567 ("N", en-US) ->
et décimaux, séparateurs de 1,234.57
groupes et séparateur
décimal avec un signe 1234.567 ("N", ru-RU) -> 1
négatif facultatif. 234,57

Pris en charge par : tous les 1234 ("N1", en-US) ->


types numériques. 1,234.0

Spécificateur de précision : 1234 ("N1", ru-RU) -> 1


nombre souhaité de 234,0
décimales.
-1234.56 ("N3", en-US) -> -
Spécificateur de précision 1,234.560
par défaut : défini par
NumberFormatInfo.Number -1234.56 ("N3", ru-RU) -> -
DecimalDigits. 1 234,560

Informations
supplémentaires :
Spécificateur de format
numérique ("N").

"P" ou "p" Pourcentage Résultat : nombre multiplié 1 ("P", en-US) -> 100.00 %
par 100 et affiché avec un
symbole de pourcentage. 1 ("P", fr-FR) -> 100,00 %

Pris en charge par : tous les -0.39678 ("P1", en-US) -> -


types numériques. 39.7 %

Spécificateur de précision : -0.39678 ("P1", fr-FR) -> -


nombre souhaité de 39,7 %
décimales.

Spécificateur de précision
par défaut : défini par
NumberFormatInfo.Percent
DecimalDigits.

Informations
supplémentaires :
Spécificateur de format
pourcentage ("P").
SP ÉC IF IC AT EUR DE F O RM AT NOM DESC RIP T IO N EXEM P L ES

"R" ou "r" Aller-retour Résultat : chaîne qui peut 123456789.12345678 ("R")


effectuer un aller-retour -> 123456789.12345678
vers un nombre identique.
-1234567890.12345678
Pris en charge par : Single, ("R") -> -
Double et BigInteger. 1234567890.1234567

Remarque : recommandé
pour le type BigInteger
uniquement. Pour les types
Double, utilisez "G17" ; pour
les types Single, utilisez
"G9".
Spécificateur de précision :
ignoré.

Informations
supplémentaires :
Spécificateur de format
aller-retour ("R").

"X" ou "x" Valeur hexadécimale Résultat : chaîne 255 ("X") -> FF


hexadécimale.
-1 ("x") -> ff
Pris en charge par : les
types intégraux 255 ("x4") -> 00ff
uniquement.
-1 ("X4") -> 00FF
Spécificateur de précision :
nombre de chiffres dans la
chaîne de résultat.

Informations
supplémentaires :
Spécificateur de format
hexadécimal ("X").

N'importe quel caractère Spécificateur inconnu Résultat : lève un


FormatException au
moment de l'exécution.

Utilisation de chaînes de format numériques standard


NOTE
Les exemples C# de cet article s’exécutent dans l’exécuteur et le terrain de jeu du code inline Try.NET. Sélectionnez le
bouton Exécuter pour exécuter un exemple dans une fenêtre interactive. Une fois que vous avez exécuté le code, vous
pouvez le modifier et exécuter le code modifié en resélectionnant Exécuter . La code modifié s’exécute dans la fenêtre
interactive ou, si la compilation échoue, la fenêtre interactive affiche tous les messages d’erreur du compilateur C#.

Une chaîne de format numérique standard peut être utilisée pour définir la mise en forme d'une valeur
numérique de l'une des deux manières suivantes :
Elle peut être passée à une surcharge de la méthode ToString qui a un paramètre format . L'exemple
suivant met en forme une valeur numérique en tant que chaîne monétaire dans la culture actuelle (dans le
cas présent, la culture en-US).
Decimal value = static_cast<Decimal>(123.456);
Console::WriteLine(value.ToString("C2"));
// Displays $123.46

decimal value = 123.456m;


Console.WriteLine(value.ToString("C2"));
// Displays $123.46

Dim value As Decimal = 123.456d


Console.WriteLine(value.ToString("C2"))
' Displays $123.46

Elle peut être fournie comme argument formatString dans un élément de mise en forme utilisé avec des
méthodes telles que String.Format, Console.WriteLine et StringBuilder.AppendFormat. Pour plus
d’informations, consultez Mise en forme composite. L'exemple suivant utilise un élément de mise en
forme pour insérer une valeur monétaire dans une chaîne.

Decimal value = static_cast<Decimal>(123.456);


Console::WriteLine("Your account balance is {0:C2}.", value);
// Displays "Your account balance is $123.46."

decimal value = 123.456m;


Console.WriteLine("Your account balance is {0:C2}.", value);
// Displays "Your account balance is $123.46."

Dim value As Decimal = 123.456d


Console.WriteLine("Your account balance is {0:C2}.", value)
' Displays "Your account balance is $123.46."

Vous pouvez éventuellement fournir un argument alignment pour spécifier la largeur du champ
numérique et si sa valeur est alignée à droite ou à gauche. L’exemple suivant aligne à gauche une valeur
monétaire dans un champ de 28 caractères et aligne à droite une valeur monétaire dans un champ de
14 caractères.

array<Decimal>^ amounts = { static_cast<Decimal>(16305.32),


static_cast<Decimal>(18794.16) };
Console::WriteLine(" Beginning Balance Ending Balance");
Console::WriteLine(" {0,-28:C2}{1,14:C2}", amounts[0], amounts[1]);
// Displays:
// Beginning Balance Ending Balance
// $16,305.32 $18,794.16

decimal[] amounts = { 16305.32m, 18794.16m };


Console.WriteLine(" Beginning Balance Ending Balance");
Console.WriteLine(" {0,-28:C2}{1,14:C2}", amounts[0], amounts[1]);
// Displays:
// Beginning Balance Ending Balance
// $16,305.32 $18,794.16
Dim amounts() As Decimal = {16305.32d, 18794.16d}
Console.WriteLine(" Beginning Balance Ending Balance")
Console.WriteLine(" {0,-28:C2}{1,14:C2}", amounts(0), amounts(1))
' Displays:
' Beginning Balance Ending Balance
' $16,305.32 $18,794.16

Il peut être fourni en tant qu’argument formatString dans un élément d’expression interpolée d’une
chaîne interpolée. Pour plus d’informations, consultez la rubrique Interpolation de chaîne dans les
informations de référence sur C# ou la rubrique Chaînes interpolées dans les informations de référence
sur Visual Basic.
Les sections suivantes fournissent des informations détaillées sur chacune des chaînes de format numériques
standard.

Spécificateur de format monétaire ("C")


Le spécificateur de format "C" (ou monétaire) convertit un nombre en une chaîne qui représente un montant en
devise. Le spécificateur de précision indique le nombre souhaité de décimales dans la chaîne de résultat. Si le
spécificateur de précision est omis, la précision par défaut est définie par la propriété
NumberFormatInfo.CurrencyDecimalDigits.
Si la valeur à mettre en forme contient un nombre de décimales supérieur au nombre spécifié ou au nombre par
défaut, la valeur fractionnaire est arrondie dans la chaîne de résultat. Si la valeur à droite du nombre de
décimales est égale à 5 ou supérieure, le dernier chiffre dans la chaîne de résultat est arrondi à une valeur
différente de zéro.
Les informations de mise en forme de l'objet NumberFormatInfo actif affectent la chaîne de résultat. Le tableau
suivant répertorie les propriétés de NumberFormatInfo qui contrôlent la mise en forme de la chaîne retournée.

P RO P RIÉT É DE N UM B ERF O RM AT IN F O DESC RIP T IO N

CurrencyPositivePattern Définit la position du symbole monétaire pour les valeurs


positives.

CurrencyNegativePattern Définit la position du symbole monétaire pour les valeurs


négatives, et spécifie si le signe négatif est représenté par des
parenthèses ou par la propriété NegativeSign.

NegativeSign Définit le signe négatif utilisé si CurrencyNegativePattern


indique que les parenthèses ne sont pas utilisées.

CurrencySymbol Définit le symbole monétaire.

CurrencyDecimalDigits Définit le nombre par défaut de chiffres décimaux dans une


valeur monétaire. Cette valeur peut être substituée à l'aide
du spécificateur de précision.

CurrencyDecimalSeparator Définit la chaîne qui sépare les chiffres de la partie entière


des chiffres de la partie décimale.

CurrencyGroupSeparator Définit la chaîne qui sépare les groupes de nombres de la


partie entière.
P RO P RIÉT É DE N UM B ERF O RM AT IN F O DESC RIP T IO N

CurrencyGroupSizes Définit le nombre de chiffres entiers qui s'affichent dans un


groupe.

L’exemple suivant met en forme une Double valeur avec le spécificateur de format monétaire :

double value = 12345.6789;


Console::WriteLine(value.ToString("C", CultureInfo::CurrentCulture));

Console::WriteLine(value.ToString("C3", CultureInfo::CurrentCulture));

Console::WriteLine(value.ToString("C3",
CultureInfo::CreateSpecificCulture("da-DK")));
// The example displays the following output on a system whose
// current culture is English (United States):
// $12,345.68
// $12,345.679
// kr 12.345,679

double value = 12345.6789;


Console.WriteLine(value.ToString("C", CultureInfo.CurrentCulture));

Console.WriteLine(value.ToString("C3", CultureInfo.CurrentCulture));

Console.WriteLine(value.ToString("C3",
CultureInfo.CreateSpecificCulture("da-DK")));
// The example displays the following output on a system whose
// current culture is English (United States):
// $12,345.68
// $12,345.679
// 12.345,679 kr

Dim value As Double = 12345.6789


Console.WriteLine(value.ToString("C", CultureInfo.CurrentCulture))

Console.WriteLine(value.ToString("C3", CultureInfo.CurrentCulture))

Console.WriteLine(value.ToString("C3", _
CultureInfo.CreateSpecificCulture("da-DK")))
' The example displays the following output on a system whose
' current culture is English (United States):
' $12,345.68
' $12,345.679
' kr 12.345,679

Retour au tableau

Spécificateur de format décimal ("D")


Le spécificateur de format "D" (ou décimal) convertit un nombre en une chaîne de chiffres décimaux (0-9),
préfixée par un signe moins si le nombre est négatif. Ce format est pris en charge par les types intégraux
uniquement.
Le spécificateur de précision indique le nombre minimal de chiffres voulu dans la chaîne résultante. Le cas
échéant, des zéros sont ajoutés à la gauche du nombre afin de fournir le nombre de chiffres déterminé par le
spécificateur de précision. Si aucun spécificateur de précision n'est spécifié, la valeur par défaut est la valeur
minimale requise pour représenter l'entier sans zéros non significatifs.
Les informations de mise en forme de l'objet NumberFormatInfo actif affectent la chaîne de résultat. Comme le
montre le tableau suivant, une seule propriété affecte la mise en forme de la chaîne de résultat.

P RO P RIÉT É DE N UM B ERF O RM AT IN F O DESC RIP T IO N

NegativeSign Définit la chaîne qui indique qu'un nombre est négatif.

L'exemple suivant met en forme une valeur Int32 avec le spécificateur de format décimal.

int value;

value = 12345;
Console::WriteLine(value.ToString("D"));
// Displays 12345
Console::WriteLine(value.ToString("D8"));
// Displays 00012345

value = -12345;
Console::WriteLine(value.ToString("D"));
// Displays -12345
Console::WriteLine(value.ToString("D8"));
// Displays -00012345

int value;

value = 12345;
Console.WriteLine(value.ToString("D"));
// Displays 12345
Console.WriteLine(value.ToString("D8"));
// Displays 00012345

value = -12345;
Console.WriteLine(value.ToString("D"));
// Displays -12345
Console.WriteLine(value.ToString("D8"));
// Displays -00012345

Dim value As Integer

value = 12345
Console.WriteLine(value.ToString("D"))
' Displays 12345
Console.WriteLine(value.ToString("D8"))
' Displays 00012345

value = -12345
Console.WriteLine(value.ToString("D"))
' Displays -12345
Console.WriteLine(value.ToString("D8"))
' Displays -00012345

Retour au tableau

Spécificateur de format exponentiel ("E")


Le spécificateur de format exponentiel ("E") convertit un nombre en une chaîne au format "-d.ddd…E+ddd" ou "-
d.ddd…e+ddd", où chaque "d" correspond à un chiffre (0-9). La chaîne commence par un signe moins si le
nombre est négatif. Un chiffre exactement précède toujours la virgule décimale.
Le spécificateur de précision indique le nombre de chiffres voulu après la virgule décimale. Si vous avez omis le
spécificateur de précision, une précision par défaut de six chiffres après la virgule décimale est utilisée.
La casse du spécificateur de format indique si le préfixe "E" ou "e" doit être ajouté à l'exposant. L'exposant est
toujours constitué d'un signe plus ou moins et d'un minimum de trois chiffres. Le cas échéant, des zéros sont
ajoutés à l'exposant pour respecter ce minimum.
Les informations de mise en forme de l'objet NumberFormatInfo actif affectent la chaîne de résultat. Le tableau
suivant répertorie les propriétés de NumberFormatInfo qui contrôlent la mise en forme de la chaîne retournée.

P RO P RIÉT É DE N UM B ERF O RM AT IN F O DESC RIP T IO N

NegativeSign Définit la chaîne qui indique qu'un nombre est négatif à la


fois pour le coefficient et pour l'exposant.

NumberDecimalSeparator Définit la chaîne qui sépare le chiffre intégral des chiffres


décimaux dans le coefficient.

PositiveSign Définit la chaîne qui indique qu'un exposant est positif.

L’exemple suivant met en forme une Double valeur avec le spécificateur de format exponentiel :

double value = 12345.6789;


Console::WriteLine(value.ToString("E", CultureInfo::InvariantCulture));
// Displays 1.234568E+004

Console::WriteLine(value.ToString("E10", CultureInfo::InvariantCulture));
// Displays 1.2345678900E+004

Console::WriteLine(value.ToString("e4", CultureInfo::InvariantCulture));
// Displays 1.2346e+004

Console::WriteLine(value.ToString("E",
CultureInfo::CreateSpecificCulture("fr-FR")));
// Displays 1,234568E+004

double value = 12345.6789;


Console.WriteLine(value.ToString("E", CultureInfo.InvariantCulture));
// Displays 1.234568E+004

Console.WriteLine(value.ToString("E10", CultureInfo.InvariantCulture));
// Displays 1.2345678900E+004

Console.WriteLine(value.ToString("e4", CultureInfo.InvariantCulture));
// Displays 1.2346e+004

Console.WriteLine(value.ToString("E",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays 1,234568E+004
Dim value As Double = 12345.6789
Console.WriteLine(value.ToString("E", CultureInfo.InvariantCulture))
' Displays 1.234568E+004

Console.WriteLine(value.ToString("E10", CultureInfo.InvariantCulture))
' Displays 1.2345678900E+004

Console.WriteLine(value.ToString("e4", CultureInfo.InvariantCulture))
' Displays 1.2346e+004

Console.WriteLine(value.ToString("E", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays 1,234568E+004

Retour au tableau

Spécificateur de format à virgule fixe ("F")


Le spécificateur de format à virgule fixe ("F") convertit un nombre en chaîne au format "-ddd.ddd…" où chaque
"d" correspond à un chiffre (0-9). La chaîne commence par un signe moins si le nombre est négatif.
Le spécificateur de précision indique le nombre de décimales voulu. Si le spécificateur de précision est omis, la
propriété NumberFormatInfo.NumberDecimalDigits actuelle fournit la précision numérique.
Les informations de mise en forme de l'objet NumberFormatInfo actif affectent la chaîne de résultat. Le tableau
suivant répertorie les propriétés de l'objet NumberFormatInfo qui peuvent contrôler la mise en forme de la
chaîne de résultat.

P RO P RIÉT É DE N UM B ERF O RM AT IN F O DESC RIP T IO N

NegativeSign Définit la chaîne qui indique qu'un nombre est négatif.

NumberDecimalSeparator Définit la chaîne qui sépare les chiffres de la partie entière


des chiffres de la partie décimale.

NumberDecimalDigits Définit le nombre par défaut de chiffres décimaux. Cette


valeur peut être substituée à l'aide du spécificateur de
précision.

L’exemple suivant met en forme une Double valeur et une Int32 valeur avec le spécificateur de format à virgule
fixe :
int integerNumber;
integerNumber = 17843;
Console::WriteLine(integerNumber.ToString("F",
CultureInfo::InvariantCulture));
// Displays 17843.00

integerNumber = -29541;
Console::WriteLine(integerNumber.ToString("F3",
CultureInfo::InvariantCulture));
// Displays -29541.000

double doubleNumber;
doubleNumber = 18934.1879;
Console::WriteLine(doubleNumber.ToString("F", CultureInfo::InvariantCulture));
// Displays 18934.19

Console::WriteLine(doubleNumber.ToString("F0", CultureInfo::InvariantCulture));
// Displays 18934

doubleNumber = -1898300.1987;
Console::WriteLine(doubleNumber.ToString("F1", CultureInfo::InvariantCulture));
// Displays -1898300.2

Console::WriteLine(doubleNumber.ToString("F3",
CultureInfo::CreateSpecificCulture("es-ES")));
// Displays -1898300,199

int integerNumber;
integerNumber = 17843;
Console.WriteLine(integerNumber.ToString("F",
CultureInfo.InvariantCulture));
// Displays 17843.00

integerNumber = -29541;
Console.WriteLine(integerNumber.ToString("F3",
CultureInfo.InvariantCulture));
// Displays -29541.000

double doubleNumber;
doubleNumber = 18934.1879;
Console.WriteLine(doubleNumber.ToString("F", CultureInfo.InvariantCulture));
// Displays 18934.19

Console.WriteLine(doubleNumber.ToString("F0", CultureInfo.InvariantCulture));
// Displays 18934

doubleNumber = -1898300.1987;
Console.WriteLine(doubleNumber.ToString("F1", CultureInfo.InvariantCulture));
// Displays -1898300.2

Console.WriteLine(doubleNumber.ToString("F3",
CultureInfo.CreateSpecificCulture("es-ES")));
// Displays -1898300,199
Dim integerNumber As Integer
integerNumber = 17843
Console.WriteLine(integerNumber.ToString("F", CultureInfo.InvariantCulture))
' Displays 17843.00

integerNumber = -29541
Console.WriteLine(integerNumber.ToString("F3", CultureInfo.InvariantCulture))
' Displays -29541.000

Dim doubleNumber As Double


doubleNumber = 18934.1879
Console.WriteLine(doubleNumber.ToString("F", CultureInfo.InvariantCulture))
' Displays 18934.19

Console.WriteLine(doubleNumber.ToString("F0", CultureInfo.InvariantCulture))
' Displays 18934

doubleNumber = -1898300.1987
Console.WriteLine(doubleNumber.ToString("F1", CultureInfo.InvariantCulture))
' Displays -1898300.2

Console.WriteLine(doubleNumber.ToString("F3", _
CultureInfo.CreateSpecificCulture("es-ES")))
' Displays -1898300,199

Retour au tableau

Spécificateur de format général ("G")


Le spécificateur de format général (« G ») convertit un nombre dans le format le plus compact (notation à virgule
fixe ou notation scientifique), en fonction du type du nombre et de la présence d'un spécificateur de précision. Le
spécificateur de précision définit le nombre maximal des chiffres significatifs qui peuvent s'afficher dans la
chaîne de résultat. Si le spécificateur de précision est omis ou est égal à zéro, le type du nombre détermine la
précision par défaut, comme indiqué dans le tableau suivant.

T Y P E N UM ÉRIQ UE P RÉC ISIO N PA R DÉFA UT

Byte ou SByte 3 chiffres

Int16 ou UInt16 5 chiffres

Int32 ou UInt32 10 chiffres

Int64 19 chiffres

UInt64 20 chiffres

BigInteger Illimité (identique à « R »)

Single 7 chiffres

Double 15 chiffres

Decimal 29 chiffres

La notation à virgule fixe est utilisée si l'exposant résultant de l'expression du nombre en notation scientifique
est supérieur à -5 et inférieur au spécificateur de précision ; dans le cas contraire, la notation scientifique est
utilisée. Le résultat contient un séparateur décimal si nécessaire et les zéros non significatifs situés après le
séparateur décimal sont omis. Si le spécificateur de précision est présent et que le nombre de chiffres
significatifs dans le résultat est supérieur à la précision indiquée, les chiffres de fin en trop sont supprimés par
arrondi.
Toutefois, si le nombre est un Decimal et que le spécificateur de précision est omis, la notation à virgule fixe est
toujours utilisée et les zéros de fin sont conservés.
Si la notation scientifique est utilisée, l'exposant du résultat est précédé de "E" si le spécificateur de format
est "G", ou de "e" si le spécificateur de format est "g". L'exposant contient au minimum deux chiffres. Cela diffère
du format utilisé pour la notation scientifique produite par le spécificateur de format exponentiel, lequel inclut un
minimum de trois chiffres dans l'exposant.
Notez que, lorsqu’il est utilisé avec une valeur Double, le spécificateur de format "G17" garantit les allers-retours
de la valeur Double originale. En effet, Double est un nombre à virgule flottante double précision ( binary64 ) et
conforme à la norme IEEE 754-2008, qui offre jusqu’à 17 chiffres de précision. Nous vous recommandons de
l’utiliser à la place du spécificateur de format "R" car, dans certains cas, "R" ne parvient pas à effectuer un aller-
retour avec des valeurs à virgule flottante double précision. L'exemple suivant illustre cette situation.

double original = 0.84551240822557006;


var rSpecifier = original.ToString("R");
var g17Specifier = original.ToString("G17");

var rValue = Double.Parse(rSpecifier);


var g17Value = Double.Parse(g17Specifier);

Console.WriteLine($"{original:G17} = {rSpecifier} (R): {original.Equals(rValue)}");


Console.WriteLine($"{original:G17} = {g17Specifier} (G17): {original.Equals(g17Value)}");
// The example displays the following output:
// 0.84551240822557006 = 0.84551240822557: False
// 0.84551240822557006 = 0.84551240822557006: True

Module Example
Public Sub Main()
Dim original As Double = 0.84551240822557006
Dim rSpecifier = original.ToString("R")
Dim g17Specifier = original.ToString("G17")

Dim rValue = Double.Parse(rSpecifier)


Dim g17Value = Double.Parse(g17Specifier)

Console.WriteLine($"{original:G17} = {rSpecifier} (R): {original.Equals(rValue)}")


Console.WriteLine($"{original:G17} = {g17Specifier} (G17): {original.Equals(g17Value)}")
End Sub
End Module
' The example displays the following output:
' 0.84551240822557006 = 0.84551240822557 (R): False
' 0.84551240822557006 = 0.84551240822557006 (G17): True

Lorsqu’il est utilisé avec une valeur Single, le spécificateur de format "G9" garantit les allers-retours de la valeur
Single originale. En effet, Single est un nombre à virgule flottante simple précision ( binary32 ) et conforme à la
norme IEEE 754-2008, qui offre jusqu’à neuf chiffres de précision. Pour des raisons de performances, nous vous
recommandons de l’utiliser à la place du spécificateur de format "R".
Les informations de mise en forme de l'objet NumberFormatInfo actif affectent la chaîne de résultat. Le tableau
suivant répertorie les propriétés de NumberFormatInfo qui contrôlent la mise en forme de la chaîne de résultat.
P RO P RIÉT É DE N UM B ERF O RM AT IN F O DESC RIP T IO N

NegativeSign Définit la chaîne qui indique qu'un nombre est négatif.

NumberDecimalSeparator Définit la chaîne qui sépare les chiffres de la partie entière


des chiffres de la partie décimale.

PositiveSign Définit la chaîne qui indique qu'un exposant est positif.

L’exemple suivant met en forme les valeurs à virgule flottante assorties avec le spécificateur de format général :

double number;

number = 12345.6789;
Console::WriteLine(number.ToString("G", CultureInfo::InvariantCulture));
// Displays 12345.6789
Console::WriteLine(number.ToString("G",
CultureInfo::CreateSpecificCulture("fr-FR")));
// Displays 12345,6789

Console::WriteLine(number.ToString("G7", CultureInfo::InvariantCulture));
// Displays 12345.68

number = .0000023;
Console::WriteLine(number.ToString("G", CultureInfo::InvariantCulture));
// Displays 2.3E-06
Console::WriteLine(number.ToString("G",
CultureInfo::CreateSpecificCulture("fr-FR")));
// Displays 2,3E-06

number = .0023;
Console::WriteLine(number.ToString("G", CultureInfo::InvariantCulture));
// Displays 0.0023

number = 1234;
Console::WriteLine(number.ToString("G2", CultureInfo::InvariantCulture));
// Displays 1.2E+03

number = Math::PI;
Console::WriteLine(number.ToString("G5", CultureInfo::InvariantCulture));
// Displays 3.1416
double number;

number = 12345.6789;
Console.WriteLine(number.ToString("G", CultureInfo.InvariantCulture));
// Displays 12345.6789
Console.WriteLine(number.ToString("G",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays 12345,6789

Console.WriteLine(number.ToString("G7", CultureInfo.InvariantCulture));
// Displays 12345.68

number = .0000023;
Console.WriteLine(number.ToString("G", CultureInfo.InvariantCulture));
// Displays 2.3E-06
Console.WriteLine(number.ToString("G",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays 2,3E-06

number = .0023;
Console.WriteLine(number.ToString("G", CultureInfo.InvariantCulture));
// Displays 0.0023

number = 1234;
Console.WriteLine(number.ToString("G2", CultureInfo.InvariantCulture));
// Displays 1.2E+03

number = Math.PI;
Console.WriteLine(number.ToString("G5", CultureInfo.InvariantCulture));
// Displays 3.1416

Dim number As Double

number = 12345.6789
Console.WriteLine(number.ToString("G", CultureInfo.InvariantCulture))
' Displays 12345.6789
Console.WriteLine(number.ToString("G", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays 12345,6789

Console.WriteLine(number.ToString("G7", CultureInfo.InvariantCulture))
' Displays 12345.68

number = .0000023
Console.WriteLine(number.ToString("G", CultureInfo.InvariantCulture))
' Displays 2.3E-06
Console.WriteLine(number.ToString("G", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays 2,3E-06

number = .0023
Console.WriteLine(number.ToString("G", CultureInfo.InvariantCulture))
' Displays 0.0023

number = 1234
Console.WriteLine(number.ToString("G2", CultureInfo.InvariantCulture))
' Displays 1.2E+03

number = Math.Pi
Console.WriteLine(number.ToString("G5", CultureInfo.InvariantCulture))
' Displays 3.1416

Retour au tableau
Spécificateur de format numérique ("N")
Le spécificateur de format numérique ("N") convertit un nombre en une chaîne au format "-d,ddd,ddd.ddd…", où
"-" correspond, le cas échéant, à un symbole de nombre négatif, "d" indique un chiffre (0-9), "," indique un
séparateur de groupes et "." correspond à une virgule décimale. Le spécificateur de précision indique le nombre
de chiffres voulu après la virgule décimale. Si le spécificateur de précision est omis, le nombre de décimales est
défini par la propriété NumberFormatInfo.NumberDecimalDigits actuelle.
Les informations de mise en forme de l'objet NumberFormatInfo actif affectent la chaîne de résultat. Le tableau
suivant répertorie les propriétés de NumberFormatInfo qui contrôlent la mise en forme de la chaîne de résultat.

P RO P RIÉT É DE N UM B ERF O RM AT IN F O DESC RIP T IO N

NegativeSign Définit la chaîne qui indique qu'un nombre est négatif.

NumberNegativePattern Définit le format de valeurs négatives, et spécifie si le signe


négatif est représenté par des parenthèses ou par la
propriété NegativeSign.

NumberGroupSizes Définit le nombre de chiffres intégraux qui s'affichent entre


les séparateurs de groupes.

NumberGroupSeparator Définit la chaîne qui sépare les groupes de nombres de la


partie entière.

NumberDecimalSeparator Définit la chaîne qui sépare les chiffres de la partie entière


des chiffres de la partie décimale.

NumberDecimalDigits Définit le nombre par défaut de chiffres décimaux. Cette


valeur peut être substituée à l'aide d'un spécificateur de
précision.

L’exemple suivant met en forme les valeurs à virgule flottante assorties avec le spécificateur de format de
nombre :

double dblValue = -12445.6789;


Console::WriteLine(dblValue.ToString("N", CultureInfo::InvariantCulture));
// Displays -12,445.68
Console::WriteLine(dblValue.ToString("N1",
CultureInfo::CreateSpecificCulture("sv-SE")));
// Displays -12 445,7

int intValue = 123456789;


Console::WriteLine(intValue.ToString("N1", CultureInfo::InvariantCulture));
// Displays 123,456,789.0

double dblValue = -12445.6789;


Console.WriteLine(dblValue.ToString("N", CultureInfo.InvariantCulture));
// Displays -12,445.68
Console.WriteLine(dblValue.ToString("N1",
CultureInfo.CreateSpecificCulture("sv-SE")));
// Displays -12 445,7

int intValue = 123456789;


Console.WriteLine(intValue.ToString("N1", CultureInfo.InvariantCulture));
// Displays 123,456,789.0
Dim dblValue As Double = -12445.6789
Console.WriteLine(dblValue.ToString("N", CultureInfo.InvariantCulture))
' Displays -12,445.68
Console.WriteLine(dblValue.ToString("N1", _
CultureInfo.CreateSpecificCulture("sv-SE")))
' Displays -12 445,7

Dim intValue As Integer = 123456789


Console.WriteLine(intValue.ToString("N1", CultureInfo.InvariantCulture))
' Displays 123,456,789.0

Retour au tableau

Spécificateur de format pourcentage ("P")


Le spécificateur de format pourcentage ("P") multiplie un nombre par 100 et le convertit en une chaîne qui
représente un pourcentage. Le spécificateur de précision indique le nombre de décimales voulu. Si le
spécificateur de précision est omis, la précision numérique par défaut fournie par la propriété
PercentDecimalDigits actuelle est utilisée.
Le tableau suivant répertorie les propriétés de NumberFormatInfo qui contrôlent la mise en forme de la chaîne
retournée.

P RO P RIÉT É DE N UM B ERF O RM AT IN F O DESC RIP T IO N

PercentPositivePattern Définit la position du symbole de pourcentage pour les


valeurs positives.

PercentNegativePattern Définit la position du symbole de pourcentage et le symbole


négatif pour les valeurs négatives.

NegativeSign Définit la chaîne qui indique qu'un nombre est négatif.

PercentSymbol Définit le symbole de pourcentage.

PercentDecimalDigits Définit le nombre par défaut de chiffres décimaux dans une


valeur de pourcentage. Cette valeur peut être substituée à
l'aide du spécificateur de précision.

PercentDecimalSeparator Définit la chaîne qui sépare les chiffres de la partie entière


des chiffres de la partie décimale.

PercentGroupSeparator Définit la chaîne qui sépare les groupes de nombres de la


partie entière.

PercentGroupSizes Définit le nombre de chiffres entiers qui s'affichent dans un


groupe.

L’exemple suivant met en forme des valeurs à virgule flottante avec le spécificateur de format pourcentage :
double number = .2468013;
Console::WriteLine(number.ToString("P", CultureInfo::InvariantCulture));
// Displays 24.68 %
Console::WriteLine(number.ToString("P",
CultureInfo::CreateSpecificCulture("hr-HR")));
// Displays 24,68%
Console::WriteLine(number.ToString("P1", CultureInfo::InvariantCulture));
// Displays 24.7 %

double number = .2468013;


Console.WriteLine(number.ToString("P", CultureInfo.InvariantCulture));
// Displays 24.68 %
Console.WriteLine(number.ToString("P",
CultureInfo.CreateSpecificCulture("hr-HR")));
// Displays 24,68%
Console.WriteLine(number.ToString("P1", CultureInfo.InvariantCulture));
// Displays 24.7 %

Dim number As Double = .2468013


Console.WriteLine(number.ToString("P", CultureInfo.InvariantCulture))
' Displays 24.68 %
Console.WriteLine(number.ToString("P", _
CultureInfo.CreateSpecificCulture("hr-HR")))
' Displays 24,68%
Console.WriteLine(number.ToString("P1", CultureInfo.InvariantCulture))
' Displays 24.7 %

Retour au tableau

Spécificateur de format aller-retour ("R")


Le spécificateur de format aller-retour ("R") tente de garantir qu'une valeur numérique qui est convertie en
chaîne est de nouveau analysée en aboutissant à la même valeur numérique. Ce format est pris en charge
uniquement pour les types Single, Double et BigInteger.
Pour les valeurs Double, le spécificateur de format "R" ne parvient pas, dans certains cas, à effectuer un aller-
retour de la valeur originale. Pour les valeurs Double et Single, il offre également des performances relativement
faibles. Nous vous recommandons plutôt d’utiliser le spécificateur de format "G17" pour les valeurs Double, et le
spécificateur de format "G9" pour garantir l’aller-retour des valeurs Single.
Lorsqu'une valeur BigInteger est mise en forme à l'aide de ce spécificateur, sa représentation sous forme de
chaîne contient tous les chiffres significatifs dans la valeur BigInteger.
Bien que vous puissiez inclure un spécificateur de précision, il est ignoré. Les allers-retours prévalent sur la
précision lorsque vous utilisez ce spécificateur. Les informations de mise en forme de l'objet NumberFormatInfo
actif affectent la chaîne de résultat. Le tableau suivant répertorie les propriétés de NumberFormatInfo qui
contrôlent la mise en forme de la chaîne de résultat.

P RO P RIÉT É DE N UM B ERF O RM AT IN F O DESC RIP T IO N

NegativeSign Définit la chaîne qui indique qu'un nombre est négatif.

NumberDecimalSeparator Définit la chaîne qui sépare les chiffres de la partie entière


des chiffres de la partie décimale.

PositiveSign Définit la chaîne qui indique qu'un exposant est positif.


L'exemple suivant met en forme une valeur BigInteger avec le spécificateur de format aller-retour.

#using <System.Numerics.dll>

using namespace System;


using namespace System::Numerics;

void main()
{
BigInteger value = BigInteger::Pow(Int64::MaxValue, 2);
Console::WriteLine(value.ToString("R"));
}
// The example displays the following output:
// 85070591730234615847396907784232501249

using System;
using System.Numerics;

public class Example


{
public static void Main()
{
var value = BigInteger.Pow(Int64.MaxValue, 2);
Console.WriteLine(value.ToString("R"));
}
}
// The example displays the following output:
// 85070591730234615847396907784232501249

Imports System.Numerics

Module Example
Public Sub Main()
Dim value = BigInteger.Pow(Int64.MaxValue, 2)
Console.WriteLine(value.ToString("R"))
End Sub
End Module
' The example displays the following output:
' 85070591730234615847396907784232501249

IMPORTANT
Dans certains cas, les valeurs Double mises en forme avec la chaîne de format numérique standard "R" ne font pas un
aller-retour correct si elles sont compilées avec les commutateurs /platform:x64 ou /platform:anycpu , et exécutées
sur des systèmes 64 bits. Pour plus d'informations, consultez les paragraphes suivants :

Pour contourner le problème des valeurs Double mises en forme avec la chaîne de format numérique standard
« R » et pour lesquelles l'aller-retour ne fonctionne pas correctement dans le cas d'une compilation avec les
indicateurs /platform:x64 ou /platform:anycpu et d'une exécution sur des systèmes 64 bits, vous pouvez
mettre en forme ces valeurs Double en utilisant la chaîne de format numérique standard « G17 ». L’exemple
suivant utilise la chaîne de format "R" avec une valeur qui ne parvient pas à effectuer un aller Double -retour, et
utilise également la chaîne de format "G17" pour effectuer un aller-retour correct de la valeur d’origine :
Console.WriteLine("Attempting to round-trip a Double with 'R':");
double initialValue = 0.6822871999174;
string valueString = initialValue.ToString("R",
CultureInfo.InvariantCulture);
double roundTripped = double.Parse(valueString,
CultureInfo.InvariantCulture);
Console.WriteLine("{0:R} = {1:R}: {2}\n",
initialValue, roundTripped, initialValue.Equals(roundTripped));

Console.WriteLine("Attempting to round-trip a Double with 'G17':");


string valueString17 = initialValue.ToString("G17",
CultureInfo.InvariantCulture);
double roundTripped17 = double.Parse(valueString17,
CultureInfo.InvariantCulture);
Console.WriteLine("{0:R} = {1:R}: {2}\n",
initialValue, roundTripped17, initialValue.Equals(roundTripped17));
// If compiled to an application that targets anycpu or x64 and run on an x64 system,
// the example displays the following output:
// Attempting to round-trip a Double with 'R':
// 0.6822871999174 = 0.68228719991740006: False
//
// Attempting to round-trip a Double with 'G17':
// 0.6822871999174 = 0.6822871999174: True

Imports System.Globalization

Module Example
Public Sub Main()
Console.WriteLine("Attempting to round-trip a Double with 'R':")
Dim initialValue As Double = 0.6822871999174
Dim valueString As String = initialValue.ToString("R",
CultureInfo.InvariantCulture)
Dim roundTripped As Double = Double.Parse(valueString,
CultureInfo.InvariantCulture)
Console.WriteLine("{0:R} = {1:R}: {2}",
initialValue, roundTripped, initialValue.Equals(roundTripped))
Console.WriteLine()

Console.WriteLine("Attempting to round-trip a Double with 'G17':")


Dim valueString17 As String = initialValue.ToString("G17",
CultureInfo.InvariantCulture)
Dim roundTripped17 As Double = double.Parse(valueString17,
CultureInfo.InvariantCulture)
Console.WriteLine("{0:R} = {1:R}: {2}",
initialValue, roundTripped17, initialValue.Equals(roundTripped17))
End Sub
End Module
' If compiled to an application that targets anycpu or x64 and run on an x64 system,
' the example displays the following output:
' Attempting to round-trip a Double with 'R':
' 0.6822871999174 = 0.68228719991740006: False
'
' Attempting to round-trip a Double with 'G17':
' 0.6822871999174 = 0.6822871999174: True

Retour au tableau

Spécificateur de format hexadécimal ("X")


Le spécificateur de format hexadécimal ("X") convertit un nombre en une chaîne de chiffres hexadécimaux. La
casse du spécificateur de format indique s'il convient d'utiliser des caractères majuscules ou minuscules pour les
chiffres hexadécimaux supérieurs à 9. Par exemple, utilisez "X" pour produire "ABCDEF" et "x" pour produire
"abcdef". Ce format est pris en charge par les types intégraux uniquement.
Le spécificateur de précision indique le nombre minimal de chiffres voulu dans la chaîne résultante. Le cas
échéant, des zéros sont ajoutés à la gauche du nombre afin de fournir le nombre de chiffres déterminé par le
spécificateur de précision.
Les informations de mise en forme de l'objet NumberFormatInfo actif n'affectent pas la chaîne de résultat.
L'exemple suivant met en forme des valeurs Int32 avec le spécificateur de format hexadécimal.

int value;

value = 0x2045e;
Console::WriteLine(value.ToString("x"));
// Displays 2045e
Console::WriteLine(value.ToString("X"));
// Displays 2045E
Console::WriteLine(value.ToString("X8"));
// Displays 0002045E

value = 123456789;
Console::WriteLine(value.ToString("X"));
// Displays 75BCD15
Console::WriteLine(value.ToString("X2"));
// Displays 75BCD15

int value;

value = 0x2045e;
Console.WriteLine(value.ToString("x"));
// Displays 2045e
Console.WriteLine(value.ToString("X"));
// Displays 2045E
Console.WriteLine(value.ToString("X8"));
// Displays 0002045E

value = 123456789;
Console.WriteLine(value.ToString("X"));
// Displays 75BCD15
Console.WriteLine(value.ToString("X2"));
// Displays 75BCD15

Dim value As Integer

value = &h2045e
Console.WriteLine(value.ToString("x"))
' Displays 2045e
Console.WriteLine(value.ToString("X"))
' Displays 2045E
Console.WriteLine(value.ToString("X8"))
' Displays 0002045E

value = 123456789
Console.WriteLine(value.ToString("X"))
' Displays 75BCD15
Console.WriteLine(value.ToString("X2"))
' Displays 75BCD15

Retour au tableau

Notes
Paramètres du panneau de configuration
Les paramètres de l'élément Options régionales et linguistiques du Panneau de configuration influencent la
chaîne résultante produite par une opération de mise en forme. Ces paramètres sont utilisés pour initialiser
l'objet NumberFormatInfo associé à la culture du thread actuel, laquelle fournit les valeurs utilisées pour
déterminer la mise en forme. Les ordinateurs qui utilisent des paramètres différents génèrent des chaînes de
résultat différentes.
De plus, si le constructeur CultureInfo(String) est utilisé pour instancier un nouvel objet CultureInfo qui
représente la même culture que la culture système en cours, toutes les personnalisations établies par l’élément
Options régionales et linguistiques dans le Panneau de configuration seront appliquées au nouvel objet
CultureInfo. Vous pouvez utiliser le constructeur CultureInfo(String, Boolean) pour créer un objet CultureInfo qui
ne reflète pas les personnalisations d'un système.
Propriétés NumberFormatInfo
La mise en forme dépend des propriétés de l'objet NumberFormatInfo actuel, qui est fourni implicitement par la
culture actuelle du thread ou explicitement par le paramètre IFormatProvider de la méthode qui appelle la mise
en forme. Spécifiez un objet NumberFormatInfo ou CultureInfo pour ce paramètre.

NOTE
Pour plus d'informations sur la personnalisation des modèles ou des chaînes utilisés dans la mise en forme des valeurs
numériques, consultez la rubrique de la classe NumberFormatInfo.

Types numériques intégraux et à virgule flottante


Certaines descriptions de spécificateurs de format numériques standard font référence à des types numériques
intégraux ou à virgule flottante. Les types numériques intégraux sont Byte, SByte, Int16, Int32, Int64, UInt16,
UInt32, UInt64 et BigInteger. Les types numériques à virgule flottante sont Decimal, Single et Double.
Infinis à virgule flottante et NaN
Quelle que soit la chaîne de format, si la valeur d'un type à virgule flottante Single ou Double est l'infini positif,
l'infini négatif ou une valeur non numérique (NaN), la chaîne mise en forme est la valeur de la propriété
PositiveInfinitySymbol, NegativeInfinitySymbol ou NaNSymbol qui est spécifiée par l'objet NumberFormatInfo
actuellement applicable.

Exemple
NOTE
Certains exemples C# de cet article s’exécutent dans l’exécuteur et le terrain de jeu du code inline Try.NET. Sélectionnez le
bouton Exécuter pour exécuter un exemple dans une fenêtre interactive. Une fois que vous avez exécuté le code, vous
pouvez le modifier et exécuter le code modifié en resélectionnant Exécuter . La code modifié s’exécute dans la fenêtre
interactive ou, si la compilation échoue, la fenêtre interactive affiche tous les messages d’erreur du compilateur C#.

L'exemple suivant met en forme une valeur numérique intégrale et à virgule flottante en utilisant la culture en-
US et tous les spécificateurs de format numériques standard. Cet exemple utilise deux types numériques
particuliers (Double et Int32), mais produirait des résultats similaires pour n'importe lequel des autres types
numériques de base (Byte, SByte, Int16, Int32, Int64, UInt16, UInt32, UInt64, BigInteger, Decimal et Single).
// Display string representations of numbers for en-us culture
CultureInfo ci = new CultureInfo("en-us");

// Output floating point values


double floating = 10761.937554;
Console.WriteLine("C: {0}",
floating.ToString("C", ci)); // Displays "C: $10,761.94"
Console.WriteLine("E: {0}",
floating.ToString("E03", ci)); // Displays "E: 1.076E+004"
Console.WriteLine("F: {0}",
floating.ToString("F04", ci)); // Displays "F: 10761.9376"
Console.WriteLine("G: {0}",
floating.ToString("G", ci)); // Displays "G: 10761.937554"
Console.WriteLine("N: {0}",
floating.ToString("N03", ci)); // Displays "N: 10,761.938"
Console.WriteLine("P: {0}",
(floating/10000).ToString("P02", ci)); // Displays "P: 107.62 %"
Console.WriteLine("R: {0}",
floating.ToString("R", ci)); // Displays "R: 10761.937554"
Console.WriteLine();

// Output integral values


int integral = 8395;
Console.WriteLine("C: {0}",
integral.ToString("C", ci)); // Displays "C: $8,395.00"
Console.WriteLine("D: {0}",
integral.ToString("D6", ci)); // Displays "D: 008395"
Console.WriteLine("E: {0}",
integral.ToString("E03", ci)); // Displays "E: 8.395E+003"
Console.WriteLine("F: {0}",
integral.ToString("F01", ci)); // Displays "F: 8395.0"
Console.WriteLine("G: {0}",
integral.ToString("G", ci)); // Displays "G: 8395"
Console.WriteLine("N: {0}",
integral.ToString("N01", ci)); // Displays "N: 8,395.0"
Console.WriteLine("P: {0}",
(integral/10000.0).ToString("P02", ci)); // Displays "P: 83.95 %"
Console.WriteLine("X: 0x{0}",
integral.ToString("X", ci)); // Displays "X: 0x20CB"
Console.WriteLine();
Option Strict On

Imports System.Globalization
Imports System.Threading

Module NumericFormats
Public Sub Main()
' Display string representations of numbers for en-us culture
Dim ci As New CultureInfo("en-us")

' Output floating point values


Dim floating As Double = 10761.937554
Console.WriteLine("C: {0}", _
floating.ToString("C", ci)) ' Displays "C: $10,761.94"
Console.WriteLine("E: {0}", _
floating.ToString("E03", ci)) ' Displays "E: 1.076E+004"
Console.WriteLine("F: {0}", _
floating.ToString("F04", ci)) ' Displays "F: 10761.9376"
Console.WriteLine("G: {0}", _
floating.ToString("G", ci)) ' Displays "G: 10761.937554"
Console.WriteLine("N: {0}", _
floating.ToString("N03", ci)) ' Displays "N: 10,761.938"
Console.WriteLine("P: {0}", _
(floating / 10000).ToString("P02", ci)) ' Displays "P: 107.62 %"
Console.WriteLine("R: {0}", _
floating.ToString("R", ci)) ' Displays "R: 10761.937554"
Console.WriteLine()

' Output integral values


Dim integral As Integer = 8395
Console.WriteLine("C: {0}", _
integral.ToString("C", ci)) ' Displays "C: $8,395.00"
Console.WriteLine("D: {0}", _
integral.ToString("D6")) ' Displays "D: 008395"
Console.WriteLine("E: {0}", _
integral.ToString("E03", ci)) ' Displays "E: 8.395E+003"
Console.WriteLine("F: {0}", _
integral.ToString("F01", ci)) ' Displays "F: 8395.0"
Console.WriteLine("G: {0}", _
integral.ToString("G", ci)) ' Displays "G: 8395"
Console.WriteLine("N: {0}", _
integral.ToString("N01", ci)) ' Displays "N: 8,395.0"
Console.WriteLine("P: {0}", _
(integral / 10000).ToString("P02", ci)) ' Displays "P: 83.95 %"
Console.WriteLine("X: 0x{0}", _
integral.ToString("X", ci)) ' Displays "X: 0x20CB"
Console.WriteLine()
End Sub
End Module

Voir aussi
NumberFormatInfo
Chaînes de format numériques personnalisées
Mise en forme des types
Procédure : remplir un nombre avec des zéros non significatifs
Mise en forme composite
Exemple : utilitaire de mise en forme .NET Core WinForms (C#)
Exemple : utilitaire de mise en forme .NET Core WinForms (Visual Basic)
Chaînes de format numériques personnalisées
18/07/2020 • 38 minutes to read • Edit Online

Vous pouvez créer une chaîne de format numérique personnalisée, qui est composée d'un ou de plusieurs
spécificateurs de format numériques personnalisés, pour définir la mise en forme des données numériques. Une
chaîne de format numérique personnalisée est toute chaîne autre qu'une chaîne de format numérique standard.
Les chaînes de format numérique personnalisées sont prises en charge par certaines surcharges de la méthode
ToString de tous les types numériques. Par exemple, vous pouvez fournir une chaîne de format numérique aux
méthodes ToString(String) et ToString(String, IFormatProvider) du type Int32 . Les chaînes de format numérique
personnalisées sont également prises en charge par la fonctionnalité de mise en forme composite .NET, utilisée
par certaines méthodes Write et WriteLine des classes Console et StreamWriter, la méthode String.Format et
la méthode StringBuilder.AppendFormat. La fonctionnalité Interpolation de chaîne prend également en charge
les chaînes de format numérique personnalisées.

TIP
Vous pouvez télécharger l’utilitaire de mise en forme , application .NET Core Windows Forms qui vous permet
d’appliquer des chaînes de mise en forme à des valeurs numériques ou à des valeurs de date et d’heure, et d’afficher la
chaîne de résultat. Le code source est disponible pour C# et Visual Basic.

Le tableau suivant décrit les spécificateurs de format numériques personnalisés et affiche un exemple de sortie
produite par chaque spécificateur de format. Consultez la section Remarques pour plus d'informations sur
l'utilisation de chaînes de format numériques personnalisées, et la section Exemple pour obtenir une illustration
complète de leur utilisation.

SP ÉC IF IC AT EUR DE F O RM AT NOM DESC RIP T IO N EXEM P L ES

"0" Espace réservé du zéro Remplace le zéro par le 1234.5678 ("00000") ->
chiffre correspondant, le cas 01235
échéant ; sinon, le zéro
s'affiche dans la chaîne de 0.45678 ("0.00", en-US) ->
résultat. 0.46

Informations 0.45678 ("0.00", fr-FR) ->


supplémentaires : 0,46
Spécificateur personnalisé «
0 ».
SP ÉC IF IC AT EUR DE F O RM AT NOM DESC RIP T IO N EXEM P L ES

"#" Espace réservé de chiffre Remplace le symbole « # » 1234.5678 ("#####") ->


par le chiffre correspondant, 1235
le cas échéant ; sinon,
aucun chiffre ne s'affiche 0.45678 ("#.##", en-US) ->
dans la chaîne de résultat. .46

Notez qu’aucun chiffre 0.45678 ("#.##", fr-FR) ->


n’apparaît dans la chaîne de ,46
résultat si le chiffre
correspondant dans la
chaîne d’entrée est un 0
non significatif. Exemple :
0003 ("####") -> 3.

Informations
supplémentaires :
Spécificateur personnalisé «
# ».

"." Virgule décimale Détermine l'emplacement 0.45678 ("0.00", en-US) ->


du séparateur décimal dans 0.46
la chaîne de résultat.
0.45678 ("0.00", fr-FR) ->
Informations 0,46
supplémentaires : « . »
Spécificateur personnalisé.

"," Séparateur de groupes et Sert à la fois de séparateur Spécificateur de séparateur


mise à l'échelle des de groupes et de de groupes :
nombres spécificateur de mise à
l'échelle des nombres. En 2147483647 ("##,#", en-
tant que séparateur de US) -> 2,147,483,647
groupes, il insère un
caractère de séparation des 2147483647 ("##,#", es-ES)
groupes localisé entre -> 2.147.483.647
chaque groupe. En tant que
spécificateur de mise à Spécificateur de mise à
l'échelle des nombres, il l'échelle :
divise un nombre par 1000
pour chaque virgule 2147483647 ("#,#,,", en-US)
spécifiée. -> 2,147

Informations 2147483647 ("#,#,,", es-ES)


supplémentaires : -> 2.147
spécificateur personnalisé
« , ».

"%" Espace réservé de Multiplie un nombre par 0.3697 ("%#0.00", en-US) -


pourcentage 100 et insère un symbole > %36.97
de pourcentage localisé
dans la chaîne de résultat. 0.3697 ("%#0.00", el-GR) ->
%36,97
Informations
supplémentaires : 0.3697 ("##.0 %", en-US) ->
spécificateur personnalisé 37.0 %
"%".
0.3697 ("##.0 %", el-GR) ->
37,0 %
SP ÉC IF IC AT EUR DE F O RM AT NOM DESC RIP T IO N EXEM P L ES

"‰" Espace réservé « pour mille Multiplie un nombre par 0.03697 ("#0.00‰", en-US)
» 1000 et insère un symbole -> 36.97‰
« pour mille » localisé dans
la chaîne de résultat. 0.03697 ("#0.00‰", ru-RU)
-> 36,97‰
Informations
supplémentaires :
spécificateur personnalisé
"‰".

"E0" Notation exponentielle Si le spécificateur est suivi 987654 ("#0.0e0") ->


d'au moins un zéro (0), met 98.8e4
"E+0" en forme le résultat à l'aide
de la notation exponentielle. 1503.92311 ("0.0##e+00")
"E-0" La casse de « E » ou « e » -> 1.504e+03
indique la casse du symbole
"E0" d'exposant dans la chaîne 1.8901385E-16 ("0.0e+00")
de résultat. Le nombre des -> 1.9e-16
"E+0" zéros qui suivent le
caractère « E » ou « e »
"E-0" détermine le nombre
minimal de chiffres dans
l'exposant. Un signe plus
(+) indique qu'un caractère
de signe précède toujours
l'exposant. Un signe moins
(-) indique qu'un caractère
de signe précède
uniquement les exposants
négatifs.

Informations
supplémentaires :
Spécificateurs personnalisés
« E » et « e ».

"\" Caractère d’échappement Entraîne l'interprétation du 987654 ("\###00\#") ->


caractère suivant comme un #987654#
littéral plutôt que comme
un spécificateur de format
personnalisé.

Informations
supplémentaires : Caractère
d'échappement « \ ».

'chaîne' Délimiteur de chaîne Indique que les caractères 68 ("# 'degrees'") -> 68
littérale encadrés doivent être degrees
«chaîne» copiés inchangés dans la
chaîne de résultat. 68 ("#' degrees'") -> 68
degrees
Plus d’informations :
Littéraux de caractère.
SP ÉC IF IC AT EUR DE F O RM AT NOM DESC RIP T IO N EXEM P L ES

; Séparateur de section Définit des sections avec 12.345 ("#0.0#;(#0.0#);-\0-


des chaînes de format ") -> 12.35
distinctes pour les nombres
positifs, négatifs et nuls. 0 ("#0.0#;(#0.0#);-\0-") -> -
0-
Informations
supplémentaires : -12.345 ("#0.0#;(#0.0#);-\0-
Séparateur de section « ; ». ") -> (12.35)

12.345 ("#0.0#;(#0.0#)") ->


12.35

0 ("#0.0#;(#0.0#)") -> 0.0

-12.345 ("#0.0#;(#0.0#)") ->


(12.35)

Autre Tous les autres caractères Le caractère est copié 68 ("# °") -> 68 °
inchangé dans la chaîne de
résultat.

Plus d’informations :
Littéraux de caractère.

Les sections suivantes fournissent des informations détaillées sur chacun des spécificateurs de format
numériques personnalisés.

NOTE
Certains exemples C# de cet article s’exécutent dans l’exécuteur et le terrain de jeu du code inline Try.NET. Sélectionnez le
bouton Exécuter pour exécuter un exemple dans une fenêtre interactive. Une fois que vous avez exécuté le code, vous
pouvez le modifier et exécuter le code modifié en resélectionnant Exécuter . La code modifié s’exécute dans la fenêtre
interactive ou, si la compilation échoue, la fenêtre interactive affiche tous les messages d’erreur du compilateur C#.

Spécificateur personnalisé « 0 »
Le spécificateur de format personnalisé "0" sert de symbole d'espace réservé du zéro. Si la valeur qui est mise
en forme comprend un chiffre à l'emplacement le zéro apparaît dans la chaîne de format, ce chiffre est copié
dans la chaîne de résultant ; sinon, un zéro apparaît dans la chaîne de résultat. L'emplacement du zéro situé à
l'extrême gauche avant le séparateur décimal et du zéro situé à l'extrême droite après le séparateur décimal
détermine la plage des chiffres qui sont toujours présents dans la chaîne de résultat.
Le spécificateur « 00 » provoque l'arrondissement de la valeur au chiffre le plus proche précédant la virgule ;
l'arrondissement à une valeur différente de zéro est toujours utilisé. Par exemple, la mise en forme de 34,5 avec
« 00 » produit la valeur 35.
L'exemple suivant affiche plusieurs valeurs qui sont mises en forme à l'aide de chaînes de format personnalisées
incluant des espaces réservés du zéro.
double value;

value = 123;
Console::WriteLine(value.ToString("00000"));
Console::WriteLine(String::Format("{0:00000}", value));
// Displays 00123

value = 1.2;
Console::WriteLine(value.ToString("0.00", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.00}", value));
// Displays 1.20

Console::WriteLine(value.ToString("00.00", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:00.00}", value));
// Displays 01.20

CultureInfo^ daDK = CultureInfo::CreateSpecificCulture("da-DK");


Console::WriteLine(value.ToString("00.00", daDK));
Console::WriteLine(String::Format(daDK, "{0:00.00}", value));
// Displays 01,20

value = .56;
Console::WriteLine(value.ToString("0.0", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.0}", value));
// Displays 0.6

value = 1234567890;
Console::WriteLine(value.ToString("0,0", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0,0}", value));
// Displays 1,234,567,890

CultureInfo^ elGR = CultureInfo::CreateSpecificCulture("el-GR");


Console::WriteLine(value.ToString("0,0", elGR));
Console::WriteLine(String::Format(elGR, "{0:0,0}", value));
// Displays 1.234.567.890

value = 1234567890.123456;
Console::WriteLine(value.ToString("0,0.0", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0,0.0}", value));
// Displays 1,234,567,890.1

value = 1234.567890;
Console::WriteLine(value.ToString("0,0.00", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0,0.00}", value));
// Displays 1,234.57
double value;

value = 123;
Console.WriteLine(value.ToString("00000"));
Console.WriteLine(String.Format("{0:00000}", value));
// Displays 00123

value = 1.2;
Console.WriteLine(value.ToString("0.00", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.00}", value));
// Displays 1.20

Console.WriteLine(value.ToString("00.00", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:00.00}", value));
// Displays 01.20

CultureInfo daDK = CultureInfo.CreateSpecificCulture("da-DK");


Console.WriteLine(value.ToString("00.00", daDK));
Console.WriteLine(String.Format(daDK, "{0:00.00}", value));
// Displays 01,20

value = .56;
Console.WriteLine(value.ToString("0.0", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.0}", value));
// Displays 0.6

value = 1234567890;
Console.WriteLine(value.ToString("0,0", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0,0}", value));
// Displays 1,234,567,890

CultureInfo elGR = CultureInfo.CreateSpecificCulture("el-GR");


Console.WriteLine(value.ToString("0,0", elGR));
Console.WriteLine(String.Format(elGR, "{0:0,0}", value));
// Displays 1.234.567.890

value = 1234567890.123456;
Console.WriteLine(value.ToString("0,0.0", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0,0.0}", value));
// Displays 1,234,567,890.1

value = 1234.567890;
Console.WriteLine(value.ToString("0,0.00", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0,0.00}", value));
// Displays 1,234.57
Dim value As Double

value = 123
Console.WriteLine(value.ToString("00000"))
Console.WriteLine(String.Format("{0:00000}", value))
' Displays 00123

value = 1.2
Console.Writeline(value.ToString("0.00", CultureInfo.InvariantCulture))
Console.Writeline(String.Format(CultureInfo.InvariantCulture,
"{0:0.00}", value))
' Displays 1.20
Console.WriteLine(value.ToString("00.00", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:00.00}", value))
' Displays 01.20
Dim daDK As CultureInfo = CultureInfo.CreateSpecificCulture("da-DK")
Console.WriteLine(value.ToString("00.00", daDK))
Console.WriteLine(String.Format(daDK, "{0:00.00}", value))
' Displays 01,20

value = .56
Console.WriteLine(value.ToString("0.0", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.0}", value))
' Displays 0.6

value = 1234567890
Console.WriteLine(value.ToString("0,0", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0,0}", value))
' Displays 1,234,567,890
Dim elGR As CultureInfo = CultureInfo.CreateSpecificCulture("el-GR")
Console.WriteLine(value.ToString("0,0", elGR))
Console.WriteLine(String.Format(elGR, "{0:0,0}", value))
' Displays 1.234.567.890

value = 1234567890.123456
Console.WriteLine(value.ToString("0,0.0", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0,0.0}", value))
' Displays 1,234,567,890.1

value = 1234.567890
Console.WriteLine(value.ToString("0,0.00", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0,0.00}", value))
' Displays 1,234.57

Retour au tableau

Spécificateur personnalisé « # »
Le spécificateur de format personnalisé "#" sert de symbole d'espace réservé des chiffres. Si la valeur qui est
mise en forme comprend un chiffre à l'emplacement où le symbole « # » apparaît dans la chaîne de format, ce
chiffre est copié dans la chaîne de résultat. Sinon, rien n'est stocké à cet emplacement dans la chaîne résultante.
Notez que ce spécificateur n'affiche jamais un zéro qui n'est pas un chiffre significatif, même si le zéro est le seul
chiffre de la chaîne. Il affichera zéro uniquement s'il s'agit d'un chiffre significatif dans le nombre affiché.
La chaîne de format « ## » provoque l'arrondissement de la valeur au chiffre le plus proche précédant la virgule
; l'arrondissement à une valeur différente de zéro est toujours utilisé. Par exemple, la mise en forme de 34,5 avec
« ## » produit la valeur 35.
L'exemple suivant affiche plusieurs valeurs qui sont mises en forme à l'aide de chaînes de format personnalisées
incluant des espaces réservés de chiffres.

double value;

value = 1.2;
Console::WriteLine(value.ToString("#.##", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#.##}", value));
// Displays 1.2

value = 123;
Console::WriteLine(value.ToString("#####"));
Console::WriteLine(String::Format("{0:#####}", value));
// Displays 123

value = 123456;
Console::WriteLine(value.ToString("[##-##-##]"));
Console::WriteLine(String::Format("{0:[##-##-##]}", value));
// Displays [12-34-56]

value = 1234567890;
Console::WriteLine(value.ToString("#"));
Console::WriteLine(String::Format("{0:#}", value));
// Displays 1234567890

Console::WriteLine(value.ToString("(###) ###-####"));
Console::WriteLine(String::Format("{0:(###) ###-####}", value));
// Displays (123) 456-7890

double value;

value = 1.2;
Console.WriteLine(value.ToString("#.##", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#.##}", value));
// Displays 1.2

value = 123;
Console.WriteLine(value.ToString("#####"));
Console.WriteLine(String.Format("{0:#####}", value));
// Displays 123

value = 123456;
Console.WriteLine(value.ToString("[##-##-##]"));
Console.WriteLine(String.Format("{0:[##-##-##]}", value));
// Displays [12-34-56]

value = 1234567890;
Console.WriteLine(value.ToString("#"));
Console.WriteLine(String.Format("{0:#}", value));
// Displays 1234567890

Console.WriteLine(value.ToString("(###) ###-####"));
Console.WriteLine(String.Format("{0:(###) ###-####}", value));
// Displays (123) 456-7890
Dim value As Double

value = 1.2
Console.WriteLine(value.ToString("#.##", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#.##}", value))
' Displays 1.2

value = 123
Console.WriteLine(value.ToString("#####"))
Console.WriteLine(String.Format("{0:#####}", value))
' Displays 123

value = 123456
Console.WriteLine(value.ToString("[##-##-##]"))
Console.WriteLine(String.Format("{0:[##-##-##]}", value))
' Displays [12-34-56]

value = 1234567890
Console.WriteLine(value.ToString("#"))
Console.WriteLine(String.Format("{0:#}", value))
' Displays 1234567890

Console.WriteLine(value.ToString("(###) ###-####"))
Console.WriteLine(String.Format("{0:(###) ###-####}", value))
' Displays (123) 456-7890

Pour retourner une chaîne de résultat dans laquelle les chiffres absents ou les zéros non significatifs sont
remplacés par des espaces, utilisez la fonctionnalité de mise en forme composite et spécifiez une largeur de
champ, comme l’illustre l’exemple suivant.

using namespace System;

void main()
{
Double value = .324;
Console::WriteLine("The value is: '{0,5:#.###}'", value);
}
// The example displays the following output if the current culture
// is en-US:
// The value is: ' .324'

using System;

public class Example


{
public static void Main()
{
Double value = .324;
Console.WriteLine("The value is: '{0,5:#.###}'", value);
}
}
// The example displays the following output if the current culture
// is en-US:
// The value is: ' .324'
Module Example
Public Sub Main()
Dim value As Double = .324
Console.WriteLine("The value is: '{0,5:#.###}'", value)
End Sub
End Module
' The example displays the following output if the current culture
' is en-US:
' The value is: ' .324'

Retour au tableau

Spécificateur personnalisé « . »
Le spécificateur de format personnalisé "." insère un séparateur décimal localisé dans la chaîne de résultat. Le
premier point de la chaîne de format détermine l'emplacement du séparateur décimal dans la valeur mise en
forme. Tout autre point est ignoré.
Le caractère qui est utilisé comme séparateur décimal dans la chaîne de résultat n'est pas toujours un point ; il
est déterminé par la propriété NumberDecimalSeparator de l'objet NumberFormatInfo qui contrôle la mise en
forme.
L'exemple suivant utilise le spécificateur de format "." pour définir l'emplacement de la virgule décimale dans
plusieurs chaînes de résultat.

double value;

value = 1.2;
Console::WriteLine(value.ToString("0.00", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.00}", value));
// Displays 1.20

Console::WriteLine(value.ToString("00.00", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:00.00}", value));
// Displays 01.20

Console::WriteLine(value.ToString("00.00",
CultureInfo::CreateSpecificCulture("da-DK")));
Console::WriteLine(String::Format(CultureInfo::CreateSpecificCulture("da-DK"),
"{0:00.00}", value));
// Displays 01,20

value = .086;
Console::WriteLine(value.ToString("#0.##%", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#0.##%}", value));
// Displays 8.6%

value = 86000;
Console::WriteLine(value.ToString("0.###E+0", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.###E+0}", value));
// Displays 8.6E+4
double value;

value = 1.2;
Console.WriteLine(value.ToString("0.00", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.00}", value));
// Displays 1.20

Console.WriteLine(value.ToString("00.00", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:00.00}", value));
// Displays 01.20

Console.WriteLine(value.ToString("00.00",
CultureInfo.CreateSpecificCulture("da-DK")));
Console.WriteLine(String.Format(CultureInfo.CreateSpecificCulture("da-DK"),
"{0:00.00}", value));
// Displays 01,20

value = .086;
Console.WriteLine(value.ToString("#0.##%", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#0.##%}", value));
// Displays 8.6%

value = 86000;
Console.WriteLine(value.ToString("0.###E+0", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E+0}", value));
// Displays 8.6E+4

Dim value As Double

value = 1.2
Console.Writeline(value.ToString("0.00", CultureInfo.InvariantCulture))
Console.Writeline(String.Format(CultureInfo.InvariantCulture,
"{0:0.00}", value))
' Displays 1.20

Console.WriteLine(value.ToString("00.00", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:00.00}", value))
' Displays 01.20

Console.WriteLine(value.ToString("00.00", _
CultureInfo.CreateSpecificCulture("da-DK")))
Console.WriteLine(String.Format(CultureInfo.CreateSpecificCulture("da-DK"),
"{0:00.00}", value))
' Displays 01,20

value = .086
Console.WriteLine(value.ToString("#0.##%", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#0.##%}", value))
' Displays 8.6%

value = 86000
Console.WriteLine(value.ToString("0.###E+0", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E+0}", value))
' Displays 8.6E+4

Retour au tableau
Spécificateur personnalisé « , »
Le caractère "," sert à la fois de séparateur de groupes et de spécificateur de mise à l'échelle des nombres.
Séparateur de groupes : si une ou plusieurs virgules sont spécifiées entre deux espaces réservés de
chiffres (0 ou #) qui mettent en forme les chiffres intégraux d'un nombre, un caractère de séparation des
groupes est inséré entre chaque groupe de nombres dans la partie intégrale de la sortie.
Les propriétés NumberGroupSeparator et NumberGroupSizes de l'objet NumberFormatInfo en cours
déterminent le caractère utilisé comme séparateur de groupes de nombres et la taille de chaque groupe
de nombres. Par exemple, si la chaîne « #,# » et la culture indifférente sont utilisées pour mettre en forme
le nombre 1000, la sortie est « 1,000 ».
Spécificateur de mise à l'échelle des nombres : si une ou plusieurs virgules sont spécifiées
immédiatement à gauche du séparateur décimal explicite ou implicite, le nombre à mettre en forme est
divisé par 1000 pour chaque virgule. Par exemple, si la chaîne « 0,, » est utilisée pour mettre en forme le
nombre 100 millions, la sortie est « 100 ».
Vous pouvez utiliser des spécificateurs de séparateur de groupes et de mise à l'échelle des nombres dans la
même chaîne de format. Par exemple, si la chaîne « #,0,, » et la culture indifférente sont utilisées pour mettre en
forme le nombre 1 milliard, la sortie est « 1,000 ».
L'exemple suivant illustre l'utilisation de la virgule comme séparateur de groupes.

double value = 1234567890;


Console::WriteLine(value.ToString("#,#", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#,#}", value));
// Displays 1,234,567,890

Console::WriteLine(value.ToString("#,##0,,", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#,##0,,}", value));
// Displays 1,235

double value = 1234567890;


Console.WriteLine(value.ToString("#,#", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,#}", value));
// Displays 1,234,567,890

Console.WriteLine(value.ToString("#,##0,,", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,##0,,}", value));
// Displays 1,235

Dim value As Double = 1234567890


Console.WriteLine(value.ToString("#,#", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,#}", value))
' Displays 1,234,567,890

Console.WriteLine(value.ToString("#,##0,,", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,##0,,}", value))
' Displays 1,235

L'exemple suivant illustre l'utilisation de la virgule comme spécificateur pour la mise à l'échelle des nombres.
double value = 1234567890;
Console::WriteLine(value.ToString("#,,", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#,,}", value));
// Displays 1235

Console::WriteLine(value.ToString("#,,,", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#,,,}", value));
// Displays 1

Console::WriteLine(value.ToString("#,##0,,", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#,##0,,}", value));
// Displays 1,235

double value = 1234567890;


Console.WriteLine(value.ToString("#,,", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,,}", value));
// Displays 1235

Console.WriteLine(value.ToString("#,,,", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,,,}", value));
// Displays 1

Console.WriteLine(value.ToString("#,##0,,", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,##0,,}", value));
// Displays 1,235

Dim value As Double = 1234567890


Console.WriteLine(value.ToString("#,,", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0:#,,}", value))
' Displays 1235

Console.WriteLine(value.ToString("#,,,", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,,,}", value))
' Displays 1

Console.WriteLine(value.ToString("#,##0,,", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#,##0,,}", value))
' Displays 1,235

Retour au tableau

Spécificateur personnalisé « % »
Un signe de pourcentage (%) dans une chaîne de format entraîne la multiplication d'un nombre par 100 avant
sa mise en forme. Le symbole de pourcentage localisé est inséré dans le nombre à l'emplacement où le
caractère % apparaît dans la chaîne de format. Le caractère de pourcentage utilisé est défini par la propriété
PercentSymbol de l'objet NumberFormatInfo actif.
L'exemple suivant définit plusieurs chaînes de format personnalisées qui incluent le spécificateur personnalisé
"%".
double value = .086;
Console::WriteLine(value.ToString("#0.##%", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:#0.##%}", value));
// Displays 8.6%

double value = .086;


Console.WriteLine(value.ToString("#0.##%", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#0.##%}", value));
// Displays 8.6%

Dim value As Double = .086


Console.WriteLine(value.ToString("#0.##%", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:#0.##%}", value))
' Displays 8.6%

Retour au tableau

Spécificateur personnalisé « ‰ »
Un caractère « pour mille » (‰ ou \u2030) dans une chaîne de format entraîne la multiplication d'un nombre
par 1 000 avant sa mise en forme. Le symbole « pour mille » approprié est inséré dans la chaîne retournée, à
l'emplacement où le symbole ‰ apparaît dans la chaîne de format. Le caractère « pour mille » utilisé est défini
par la propriété NumberFormatInfo.PerMilleSymbol de l'objet qui fournit les informations de mise en forme
spécifique à la culture.
L'exemple suivant définit une chaîne de format personnalisée qui inclut le spécificateur personnalisé "‰".

double value = .00354;


String^ perMilleFmt = "#0.## " + '\u2030';
Console::WriteLine(value.ToString(perMilleFmt, CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:" + perMilleFmt + "}", value));
// Displays 3.54‰

double value = .00354;


string perMilleFmt = "#0.## " + '\u2030';
Console.WriteLine(value.ToString(perMilleFmt, CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:" + perMilleFmt + "}", value));
// Displays 3.54‰

Dim value As Double = .00354


Dim perMilleFmt As String = "#0.## " & ChrW(&h2030)
Console.WriteLine(value.ToString(perMilleFmt, CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:" + perMilleFmt + "}", value))
' Displays 3.54 ‰

Retour au tableau

Spécificateurs personnalisés « E » et « e »
Si l'une des chaînes "E", "E+", "E-", "e", "e+" ou "e-" est présente dans la chaîne de format et immédiatement
suivie d'au moins un zéro, le nombre est mis en forme à l'aide de la notation scientifique, avec un "E" ou un "e"
inséré entre le nombre et l'exposant. Le nombre de zéros qui suivent l'indicateur de notation scientifique
détermine le nombre minimal de chiffres à afficher pour l'exposant. Les formats "E+" et "e+" indiquent qu'un
signe plus ou un signe moins doit toujours précéder l'exposant. Les formats "E", "E-", "e" ou "e-" indiquent qu'un
signe ne doit précéder que les exposants négatifs.
L'exemple suivant met en forme plusieurs valeurs numériques à l'aide des spécificateurs pour la notation
scientifique.

double value = 86000;


Console::WriteLine(value.ToString("0.###E+0", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.###E+0}", value));
// Displays 8.6E+4

Console::WriteLine(value.ToString("0.###E+000", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.###E+000}", value));
// Displays 8.6E+004

Console::WriteLine(value.ToString("0.###E-000", CultureInfo::InvariantCulture));
Console::WriteLine(String::Format(CultureInfo::InvariantCulture,
"{0:0.###E-000}", value));
// Displays 8.6E004

double value = 86000;


Console.WriteLine(value.ToString("0.###E+0", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E+0}", value));
// Displays 8.6E+4

Console.WriteLine(value.ToString("0.###E+000", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E+000}", value));
// Displays 8.6E+004

Console.WriteLine(value.ToString("0.###E-000", CultureInfo.InvariantCulture));
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E-000}", value));
// Displays 8.6E004

Dim value As Double = 86000


Console.WriteLine(value.ToString("0.###E+0", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E+0}", value))
' Displays 8.6E+4

Console.WriteLine(value.ToString("0.###E+000", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E+000}", value))
' Displays 8.6E+004

Console.WriteLine(value.ToString("0.###E-000", CultureInfo.InvariantCulture))
Console.WriteLine(String.Format(CultureInfo.InvariantCulture,
"{0:0.###E-000}", value))
' Displays 8.6E004

Retour au tableau
Caractère d’échappement « \ »
Les symboles "#", "0", ".", ",", "%" et "‰" dans une chaîne de format sont interprétés comme des spécificateurs
de format plutôt que comme des caractères littéraux. En fonction de leur position dans une chaîne de format
personnalisée, les "E" majuscules et minuscules ainsi que les symboles + et - peuvent également être interprétés
comme des spécificateurs de format.
Pour éviter qu'un caractère soit interprété comme un spécificateur de format, vous pouvez le faire précéder
d'une barre oblique inverse, qui est le caractère d'échappement. Le caractère d'échappement signifie que le
caractère suivant est un caractère littéral qui doit être inclus inchangé dans la chaîne de résultat.
Pour inclure une barre oblique inverse dans une chaîne de résultat, vous devez la placer dans une séquence
d'échappement avec une autre barre oblique inverse ( \\ ).

NOTE
Certains compilateurs, tels que les compilateurs C++ et C#, peuvent également interpréter une barre oblique inverse
unique comme un caractère d'échappement. Pour garantir l'interprétation correcte d'une chaîne lors de la mise en forme,
vous pouvez utiliser le caractère littéral de chaîne textuel(le caractère @) avant la chaîne en C#, ou ajouter une autre barre
oblique inverse avant chaque barre oblique inverse en C# et C++. L'exemple C# suivant illustre ces deux approches.

L’exemple suivant utilise le caractère d’échappement pour empêcher l’opération de mise en forme d’interpréter
les caractères « # », « 0 » et « \ » comme des caractères d’échappement ou des spécificateurs de format.
L'exemple C# utilise une barre oblique inverse supplémentaire pour garantir qu'une barre oblique inverse est
interprétée comme un caractère littéral.

int value = 123;


Console::WriteLine(value.ToString("\\#\\#\\# ##0 dollars and \\0\\0 cents \\#\\#\\#"));
Console::WriteLine(String::Format("{0:\\#\\#\\# ##0 dollars and \\0\\0 cents \\#\\#\\#}",
value));
// Displays ### 123 dollars and 00 cents ###

Console::WriteLine(value.ToString("\\#\\#\\# ##0 dollars and \0\0 cents \\#\\#\\#"));


Console::WriteLine(String::Format("{0:\\#\\#\\# ##0 dollars and \0\0 cents \\#\\#\\#}",
value));
// Displays ### 123 dollars and 00 cents ###

Console::WriteLine(value.ToString("\\\\\\\\\\\\ ##0 dollars and \\0\\0 cents \\\\\\\\\\\\"));


Console::WriteLine(String::Format("{0:\\\\\\\\\\\\ ##0 dollars and \\0\\0 cents \\\\\\\\\\\\}",
value));
// Displays \\\ 123 dollars and 00 cents \\\

Console::WriteLine(value.ToString("\\\\\\ ##0 dollars and \0\0 cents \\\\\\"));


Console::WriteLine(String::Format("{0:\\\\\\ ##0 dollars and \0\0 cents \\\\\\}",
value));
// Displays \\\ 123 dollars and 00 cents \\\
int value = 123;
Console.WriteLine(value.ToString("\\#\\#\\# ##0 dollars and \\0\\0 cents \\#\\#\\#"));
Console.WriteLine(String.Format("{0:\\#\\#\\# ##0 dollars and \\0\\0 cents \\#\\#\\#}",
value));
// Displays ### 123 dollars and 00 cents ###

Console.WriteLine(value.ToString(@"\#\#\# ##0 dollars and \0\0 cents \#\#\#"));


Console.WriteLine(String.Format(@"{0:\#\#\# ##0 dollars and \0\0 cents \#\#\#}",
value));
// Displays ### 123 dollars and 00 cents ###

Console.WriteLine(value.ToString("\\\\\\\\\\\\ ##0 dollars and \\0\\0 cents \\\\\\\\\\\\"));


Console.WriteLine(String.Format("{0:\\\\\\\\\\\\ ##0 dollars and \\0\\0 cents \\\\\\\\\\\\}",
value));
// Displays \\\ 123 dollars and 00 cents \\\

Console.WriteLine(value.ToString(@"\\\\\\ ##0 dollars and \0\0 cents \\\\\\"));


Console.WriteLine(String.Format(@"{0:\\\\\\ ##0 dollars and \0\0 cents \\\\\\}",
value));
// Displays \\\ 123 dollars and 00 cents \\\

Dim value As Integer = 123


Console.WriteLine(value.ToString("\#\#\# ##0 dollars and \0\0 cents \#\#\#"))
Console.WriteLine(String.Format("{0:\#\#\# ##0 dollars and \0\0 cents \#\#\#}",
value))
' Displays ### 123 dollars and 00 cents ###

Console.WriteLine(value.ToString("\\\\\\ ##0 dollars and \0\0 cents \\\\\\"))


Console.WriteLine(String.Format("{0:\\\\\\ ##0 dollars and \0\0 cents \\\\\\}",
value))
' Displays \\\ 123 dollars and 00 cents \\\

Retour au tableau

Séparateur de section « ; »
Le point-virgule (;) est un spécificateur de format conditionnel qui applique une mise en forme différente à un
nombre suivant que sa valeur est positive, négative ou nulle. Pour entraîner ce comportement, une chaîne de
format personnalisée peut contenir jusqu'à trois sections séparées par des points-virgules. Ces sections sont
décrites dans le tableau suivant.

N O M B RE DE SEC T IO N S DESC RIP T IO N

Une section La chaîne de format s'applique à toutes les valeurs.

Deux sections La première section s'applique aux valeurs positives et aux


zéros, et la deuxième section s'applique aux valeurs
négatives.

Si le nombre à mettre en forme est négatif, mais devient nul


après l'arrondissement au format de la deuxième section, le
zéro résultant est mis en forme en fonction de la première
section.
N O M B RE DE SEC T IO N S DESC RIP T IO N

Trois sections La première section s'applique aux valeurs positives, la


deuxième section s'applique aux valeurs négatives et la
troisième section s'applique aux zéros.

La deuxième section peut rester vide (en n'insérant aucune


valeur entre les points-virgules), auquel cas la première
section s'applique à toutes les valeurs différentes de zéro.

Si le nombre à mettre en forme est différent de zéro, mais


devient nul après l'arrondissement au format de la première
ou deuxième section, le zéro résultant est mis en forme en
fonction de la troisième section.

Les séparateurs de section ignorent toute mise en forme préexistante associée à un nombre lorsque la valeur
finale est mise en forme. Par exemple, les valeurs négatives sont toujours affichées sans signe moins lorsque des
séparateurs de section sont utilisés. Si vous souhaitez que la valeur mise en forme finale soit précédée d'un
signe moins, vous devez inclure ce signe explicitement comme élément du spécificateur de format personnalisé.
L'exemple suivant utilise le spécificateur de format ";" pour mettre en forme différemment les nombres positifs,
négatifs et nuls.

double posValue = 1234;


double negValue = -1234;
double zeroValue = 0;

String^ fmt2 = "##;(##)";


String^ fmt3 = "##;(##);**Zero**";

Console::WriteLine(posValue.ToString(fmt2));
Console::WriteLine(String::Format("{0:" + fmt2 + "}", posValue));
// Displays 1234

Console::WriteLine(negValue.ToString(fmt2));
Console::WriteLine(String::Format("{0:" + fmt2 + "}", negValue));
// Displays (1234)

Console::WriteLine(zeroValue.ToString(fmt3));
Console::WriteLine(String::Format("{0:" + fmt3 + "}", zeroValue));
// Displays **Zero**

double posValue = 1234;


double negValue = -1234;
double zeroValue = 0;

string fmt2 = "##;(##)";


string fmt3 = "##;(##);**Zero**";

Console.WriteLine(posValue.ToString(fmt2));
Console.WriteLine(String.Format("{0:" + fmt2 + "}", posValue));
// Displays 1234

Console.WriteLine(negValue.ToString(fmt2));
Console.WriteLine(String.Format("{0:" + fmt2 + "}", negValue));
// Displays (1234)

Console.WriteLine(zeroValue.ToString(fmt3));
Console.WriteLine(String.Format("{0:" + fmt3 + "}", zeroValue));
// Displays **Zero**
Dim posValue As Double = 1234
Dim negValue As Double = -1234
Dim zeroValue As Double = 0

Dim fmt2 As String = "##;(##)"


Dim fmt3 As String = "##;(##);**Zero**"

Console.WriteLine(posValue.ToString(fmt2))
Console.WriteLine(String.Format("{0:" + fmt2 + "}", posValue))
' Displays 1234

Console.WriteLine(negValue.ToString(fmt2))
Console.WriteLine(String.Format("{0:" + fmt2 + "}", negValue))
' Displays (1234)

Console.WriteLine(zeroValue.ToString(fmt3))
Console.WriteLine(String.Format("{0:" + fmt3 + "}", zeroValue))
' Displays **Zero**

Retour au tableau

Littéraux de caractère
Les spécificateurs de format qui apparaissent dans une chaîne de format numérique personnalisée sont toujours
interprétés comme des caractères de mise en forme, jamais comme des caractères littéraux. Il s’agit notamment
des caractères suivants :
0
#
%

'
\
.
,
E ou e, en fonction de sa position dans la chaîne de format.
Tous les autres caractères sont toujours interprétés comme des caractères littéraux et, dans une opération de
mise en forme, sont inclus inchangés dans la chaîne de résultat. Dans une opération d’analyse, ils doivent
correspondre exactement aux caractères présents dans la chaîne d’entrée. La comparaison respecte la casse.
L’exemple suivant illustre un usage courant des unités de caractères littéraux (dans ce cas, les milliers) :

double n = 123.8;
Console.WriteLine($"{n:#,##0.0K}");
// The example displays the following output:
// 123.8K

Dim n As Double = 123.8


Console.WriteLine($"{n:#,##0.0K}")
' The example displays the following output:
' 123.8K

Il existe deux façons d’indiquer que les caractères doivent être interprétés comme des caractères littéraux, et non
comme des caractères de mise en forme, pour qu’ils puissent être inclus dans une chaîne de résultat ou analysés
correctement dans une chaîne d’entrée :
En échappant un caractère de mise en forme. Pour plus d’informations, consultez Caractère
d’échappement « \ ».
En plaçant la totalité de la chaîne littérale entre apostrophes.
L’exemple suivant utilise les deux approches pour inclure des caractères réservés dans une chaîne de format
numérique personnalisée.

double n = 9.3;
Console.WriteLine($@"{n:##.0\%}");
Console.WriteLine($@"{n:\'##\'}");
Console.WriteLine($@"{n:\\##\\}");
Console.WriteLine();
Console.WriteLine($"{n:##.0'%'}");
Console.WriteLine($@"{n:'\'##'\'}");
// The example displays the following output:
// 9.3%
// '9'
// \9\
//
// 9.3%
// \9\

Dim n As Double = 9.3


Console.WriteLine($"{n:##.0\%}")
Console.WriteLine($"{n:\'##\'}")
Console.WriteLine($"{n:\\##\\}")
Console.WriteLine()
Console.WriteLine($"{n:##.0'%'}")
Console.WriteLine($"{n:'\'##'\'}")
' The example displays the following output:
' 9.3%
' '9'
' \9\
'
' 9.3%
' \9\

Notes
Infinis à virgule flottante et NaN
Quelle que soit la chaîne de format, si la valeur d'un type à virgule flottante Single ou Double est l'infini positif,
l'infini négatif ou une valeur non numérique (NaN), la chaîne mise en forme est la valeur de la propriété
PositiveInfinitySymbol, NegativeInfinitySymbolou NaNSymbol spécifiée par l'objet NumberFormatInfo
actuellement applicable.
Paramètres du panneau de configuration
Les paramètres de l'élément Options régionales et linguistiques du Panneau de configuration influencent la
chaîne résultante produite par une opération de mise en forme. Ces paramètres sont utilisés pour initialiser
l'objet NumberFormatInfo associé à la culture du thread en cours et la culture du thread en cours fournit des
valeurs utilisées pour indiquer la mise en forme. Les ordinateurs qui utilisent des paramètres différents génèrent
des chaînes de résultat différentes.
De plus, si vous utilisez le constructeur CultureInfo(String) pour instancier un nouvel objet CultureInfo qui
représente la même culture que la culture système en cours, toutes les personnalisations établies par l'élément
Options régionales et linguistiques du Panneau de configuration seront appliquées au nouvel objet
CultureInfo . Vous pouvez utiliser le constructeur CultureInfo(String, Boolean) pour créer un objet CultureInfo qui
ne reflète pas les personnalisations d'un système.
Arrondi et chaînes de format à virgule fixe
Pour les chaînes de format à virgule fixe (c'est-à-dire les chaînes de format ne contenant pas de caractères de
format de notation scientifique), les nombres sont arrondis au même nombre de décimales que d'espaces
réservés de chiffres à droite du séparateur décimal. Si la chaîne de format ne contient pas de virgule décimale, le
nombre est arrondi à l'entier le plus proche. Si le nombre possède plus de chiffres que d'espaces réservés de
chiffres à gauche de la virgule décimale, les chiffres supplémentaires sont copiés dans la chaîne résultante
immédiatement avant le premier espace réservé de chiffre.
Retour au tableau

Exemple
L'exemple suivant présente deux chaînes de format numériques personnalisées. Dans les deux cas, l'espace
réservé de chiffre ( # ) affiche les données numériques, et tous les autres caractères sont copiés dans la chaîne
de résultat.

double number1 = 1234567890;


String^ value1 = number1.ToString("(###) ###-####");
Console::WriteLine(value1);

int number2 = 42;


String^ value2 = number2.ToString("My Number = #");
Console::WriteLine(value2);
// The example displays the following output:
// (123) 456-7890
// My Number = 42

double number1 = 1234567890;


string value1 = number1.ToString("(###) ###-####");
Console.WriteLine(value1);

int number2 = 42;


string value2 = number2.ToString("My Number = #");
Console.WriteLine(value2);
// The example displays the following output:
// (123) 456-7890
// My Number = 42

Dim number1 As Double = 1234567890


Dim value1 As String = number1.ToString("(###) ###-####")
Console.WriteLine(value1)

Dim number2 As Integer = 42


Dim value2 As String = number2.ToString("My Number = #")
Console.WriteLine(value2)
' The example displays the following output:
' (123) 456-7890
' My Number = 42

Retour au tableau

Voir aussi
System.Globalization.NumberFormatInfo
Mise en forme des types
Chaînes de format numériques standard
Procédure : remplir un nombre avec des zéros non significatifs
Exemple : utilitaire de mise en forme .NET Core WinForms (C#)
Exemple : utilitaire de mise en forme .NET Core WinForms (Visual Basic)
Chaînes de format de date et d’heure standard
18/07/2020 • 52 minutes to read • Edit Online

Une chaîne de format de date et d'heure standard utilise un spécificateur de format unique pour définir la
représentation textuelle d'une valeur de date et d'heure. Toute chaîne de format de date et d’heure contenant
plusieurs caractères, y compris un espace blanc, est interprétée comme une chaîne de format de date et d’heure
personnalisée. Pour plus d’informations, consultez chaînes de format de date et d’heure personnalisées. Une
chaîne de format standard ou personnalisée peut être utilisée de deux façons :
Pour définir la chaîne qui résulte d'une opération de mise en forme.
Pour définir la représentation textuelle d'une valeur de date et d'heure pouvant être convertie en valeur
DateTime ou en valeur DateTimeOffset lors d'une opération d'analyse.

TIP
Vous pouvez télécharger l’utilitaire de mise en forme , application .NET Core Windows Forms qui vous permet
d’appliquer des chaînes de mise en forme à des valeurs numériques ou à des valeurs de date et d’heure, et d’afficher la
chaîne de résultat. Le code source est disponible pour C# et Visual Basic.

Les chaînes de format de date et d'heure standard peuvent être utilisées avec les valeurs DateTime et
DateTimeOffset.

NOTE
Certains exemples C# de cet article s’exécutent dans l’exécuteur et le terrain de jeu du code inline Try.NET. Sélectionnez le
bouton Exécuter pour exécuter un exemple dans une fenêtre interactive. Une fois que vous avez exécuté le code, vous
pouvez le modifier et exécuter le code modifié en resélectionnant Exécuter . La code modifié s’exécute dans la fenêtre
interactive ou, si la compilation échoue, la fenêtre interactive affiche tous les messages d’erreur du compilateur C#.
Le fuseau horaire local de l’exécuteur de code en ligne et du terrain de jeu inline Try.NET est le temps universel coordonné,
ou UTC. Cela peut affecter le comportement et la sortie d’exemples qui illustrent les types DateTime, DateTimeOffset et
TimeZoneInfo ainsi que leurs membres.

Le tableau suivant décrit les spécificateurs de format de date et d’heure standard. Sauf indication contraire, un
spécificateur de format de date et d'heure standard particulier produit une représentation sous forme de chaîne
identique, qu'il soit utilisé avec une valeur DateTime ou DateTimeOffset. Pour plus d’informations sur l’utilisation
de chaînes de format de date et d’heure standard, consultez la section Remarques.

SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L ES

"d" Modèle de date courte. 2009-06-15T13:45:30 -> 6/15/2009


(en-US)
Informations
supplémentaires :Spécificateur de 2009-06-15T13:45:30 -> 15/06/2009
format de date courte ("d"). (fr-FR)

2009-06-15T13:45:30 -> 2009/06/15


(ja-JP)
SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L ES

«D» Modèle de date longue. 2009-06-15T13:45:30 -> Monday,


June 15, 2009 (en-US)
Informations
supplémentaires :Spécificateur de 2009-06-15T13:45:30 -> 15 июня
format de date longue ("D"). 2009 г. (ru-RU)

2009-06-15T13:45:30 -> Montag, 15.


Juni 2009 (de-DE)

"f" Modèle de date/heure complet (heure 2009-06-15T13:45:30 -> Monday,


courte). June 15, 2009 1:45 PM (en-US)

Informations supplémentaires : 2009-06-15T13:45:30 -> den 15 juni


Spécificateur de format de date 2009 13:45 (sv-SE)
complet et d’heure courte ("f").
2009-06-15T13:45:30 -> Δευτέρα, 15
Ιουνίου 2009 1:45 μμ (el-GR)

"F" Modèle de date/heure complet (heure 2009-06-15T13:45:30 -> Monday,


longue). June 15, 2009 1:45:30 PM (en-US)

Informations supplémentaires : 2009-06-15T13:45:30 -> den 15 juni


Spécificateur de format de date 2009 13:45:30 (sv-SE)
complet et d’heure longue ("F").
2009-06-15T13:45:30 -> Δευτέρα, 15
Ιουνίου 2009 1:45:30 μμ (el-GR)

"g" Modèle de date/heure général (heure 2009-06-15T13:45:30 -> 6/15/2009


courte). 1:45 PM (en-US)

Informations supplémentaires : 2009-06-15T13:45:30 -> 15/06/2009


Spécificateur de format de date général 13:45 (es-ES)
et d'heure courte ("g").
2009-06-15T13:45:30 -> 2009/6/15
13:45 (zh-CN)

"G" Modèle de date/heure général (heure 2009-06-15T13:45:30 -> 6/15/2009


longue). 1:45:30 PM (en-US)

Informations supplémentaires : 2009-06-15T13:45:30 -> 15/06/2009


Spécificateur de format de date général 13:45:30 (es-ES)
et d’heure longue ("G").
2009-06-15T13:45:30 -> 2009/6/15
13:45:30 (zh-CN)

"M", "m" Modèle de mois/jour. 2009-06-15T13:45:30 -> June 15 (en-


US)
Informations supplémentaires :
Spécificateur de format de mois ("M", 2009-06-15T13:45:30 -> 15. juni (da-
"m). DK)

2009-06-15T13:45:30 -> 15 Juni (id-


ID)
SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L ES

"O", "o" Modèle de date/heure aller-retour. Valeurs DateTime :

Informations supplémentaires : 2009-06-15T13:45:30


Spécificateur de format aller- (DateTimeKind.Local) --> 2009-06-
retour ("O", "o"»). 15T13:45:30.0000000-07:00

2009-06-15T13:45:30
(DateTimeKind.Utc) --> 2009-06-
15T13:45:30.0000000Z

2009-06-15T13:45:30
(DateTimeKind.Unspecified) --> 2009-
06-15T13:45:30.0000000

Valeurs DateTimeOffset :

2009-06-15T13:45:30-07:00 -->
2009-06-15T13:45:30.0000000-07:00

"R", "r" Modèle RFC1123. 2009-06-15T13:45:30 -> Mon, 15 Jun


2009 20:45:30 GMT
Informations supplémentaires :
Spécificateur de format RFC1123 ("R",
"r").

"s" Modèle de date/heure pouvant être 2009-06-15T13:45:30


trié. (DateTimeKind.Local) -> 2009-06-
15T13:45:30
Informations supplémentaires :
Spécificateur de format pouvant être 2009-06-15T13:45:30
trié ("s"). (DateTimeKind.Utc) -> 2009-06-
15T13:45:30

"t" Modèle d’heure courte. 2009-06-15T13:45:30 -> 1:45 PM


(en-US)
Informations supplémentaires :
Spécificateur de format d’heure 2009-06-15T13:45:30 -> 13:45 (hr-
courte ("t"). HR)

2009-06-15T13:45:30 -> 01:45 ‫( م‬ar-


EG)

"T" Modèle d’heure longue. 2009-06-15T13:45:30 -> 1:45:30 PM


(en-US)
Informations supplémentaires :
Spécificateur de format d’heure 2009-06-15T13:45:30 -> 13:45:30
longue ("T"). (hr-HR)

2009-06-15T13:45:30 -> 01:45:30 ‫م‬


(ar-EG)

"u" Modèle de date/heure universel Avec une valeur DateTime : 2009-06-


pouvant être trié. 15T13:45:30 -> 2009-06-15
13:45:30Z
Informations supplémentaires :
Spécificateur de format universel Avec une valeur DateTimeOffset :
pouvant être trié ("u"). 2009-06-15T13:45:30 -> 2009-06-15
20:45:30Z
SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L ES

"U" Modèle de date/heure complet 2009-06-15T13:45:30 -> Monday,


universel. June 15, 2009 8:45:30 PM (en-US)

Informations supplémentaires : 2009-06-15T13:45:30 -> den 15 juni


Spécificateur de format complet 2009 20:45:30 (sv-SE)
universel ("U").
2009-06-15T13:45:30 -> Δευτέρα, 15
Ιουνίου 2009 8:45:30 μμ (el-GR)

"Y", "y" Modèle d’année/mois. 2009-06-15T13:45:30-> juin 2009


(en-US)
Informations supplémentaires :
Spécificateur de format Année 2009-06-15T13:45:30 -> juni 2009
Mois ("Y"). (da-DK)

2009-06-15T13:45:30 -> Juni 2009


(id-ID)

N'importe quel caractère Spécificateur inconnu. Lève un FormatException runtime.

Fonctionnement des chaînes de format standard


Dans une opération de mise en forme, une chaîne de format standard constitue simplement un alias d'une
chaîne de format personnalisée. L'utilisation d'un alias pour faire référence à une chaîne de format personnalisée
présente l'avantage suivant : bien que l'alias reste invariant, la chaîne de format personnalisée peut varier. Ce
point est important car les représentations sous forme de chaîne de valeurs de date et d'heure varient
généralement selon la culture. Par exemple, la chaîne de format standard "d" indique qu'une valeur de date et
d'heure sera affichée à l'aide d'un modèle de date courte. Pour la culture dite indifférente, ce modèle est
"MM/dd/yyyy". Pour la culture fr-FR, il s'agit de "dd/MM/yyyy". Pour la culture ja-JP, il s'agit de "yyyy/MM/dd".
Si, dans une opération de mise en forme, une chaîne de format standard est mappée à une chaîne de format
personnalisée d'une culture particulière, votre application peut définir la culture spécifique dont les chaînes de
format personnalisées sont utilisées de l'une des manières suivantes :
Vous pouvez utiliser la culture par défaut (ou actuelle). L'exemple suivant affiche une date à l'aide du
format de date courte de la culture actuelle. Dans ce cas, la culture actuelle est en-US.

// Display using current (en-us) culture's short date format


DateTime thisDate = new DateTime(2008, 3, 15);
Console.WriteLine(thisDate.ToString("d")); // Displays 3/15/2008

' Display using current (en-us) culture's short date format


Dim thisDate As Date = #03/15/2008#
Console.WriteLine(thisDate.ToString("d")) ' Displays 3/15/2008

Vous pouvez passer un objet CultureInfo représentant la culture dont la mise en forme sera utilisée à une
méthode qui a un paramètre IFormatProvider. L'exemple suivant affiche une date à l'aide du format de
date courte de la culture pt-BR.

// Display using pt-BR culture's short date format


DateTime thisDate = new DateTime(2008, 3, 15);
CultureInfo culture = new CultureInfo("pt-BR");
Console.WriteLine(thisDate.ToString("d", culture)); // Displays 15/3/2008
' Display using pt-BR culture's short date format
Dim thisDate As Date = #03/15/2008#
Dim culture As New CultureInfo("pt-BR")
Console.WriteLine(thisDate.ToString("d", culture)) ' Displays 15/3/2008

Vous pouvez passer un objet DateTimeFormatInfo qui fournit des informations de mise en forme à une
méthode qui a un paramètre IFormatProvider. L'exemple suivant affiche une date à l'aide du format de
date courte d'un objet DateTimeFormatInfo pour la culture hr-HR.

// Display using date format information from hr-HR culture


DateTime thisDate = new DateTime(2008, 3, 15);
DateTimeFormatInfo fmt = (new CultureInfo("hr-HR")).DateTimeFormat;
Console.WriteLine(thisDate.ToString("d", fmt)); // Displays 15.3.2008

' Display using date format information from hr-HR culture


Dim thisDate As Date = #03/15/2008#
Dim fmt As DateTimeFormatInfo = (New CultureInfo("hr-HR")).DateTimeFormat
Console.WriteLine(thisDate.ToString("d", fmt)) ' Displays 15.3.2008

NOTE
Pour plus d'informations sur la personnalisation des chaînes ou des modèles utilisés dans la mise en forme des valeurs
numériques, consultez la rubrique de la classe NumberFormatInfo.

Dans certains cas, il est pratique d'utiliser la chaîne de format standard comme abréviation pour les chaînes de
format personnalisées plus longues et indifférentes. Quatre chaînes de format standard appartiennent à cette
catégorie : "O" (ou "o"), "R" (ou "r"), "s" et "u". Ces chaînes correspondent aux chaînes de format personnalisées
définies par la culture dite indifférente. Elles produisent des représentations sous forme de chaîne de valeurs de
date et d'heure destinées à être identiques dans toutes les cultures. Le tableau suivant fournit des informations
sur ces quatre chaînes de format de date et d'heure standard.

DÉF IN I PA R L A P RO P RIÉT É
DAT ET IM EF O RM AT IN F O. IN VA RIA N T IN F
C H A ÎN E DE F O RM AT STA N DA RD O C H A ÎN E DE F O RM AT P ERSO N N A L ISÉE

"O" ou "o" Aucune yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffzz

"R" ou "r" RFC1123Pattern ddd, dd MMM yyyy HH':'mm':'ss 'GMT'

"s" SortableDateTimePattern yyyy'-'MM'-'dd'T'HH':'mm':'ss

"u" UniversalSortableDateTimePattern yyyy'-'MM'-'dd HH':'mm':'ss'Z'

Les chaînes de format standard peuvent également utilisées dans les opérations d'analyses avec les méthodes
DateTime.ParseExact ou DateTimeOffset.ParseExact. Pour que l'opération d'analyse aboutisse, ces méthodes
requièrent qu'une chaîne d'entrée se conforme exactement à un modèle particulier. De nombreuses chaînes de
format standard peuvent être mappées à plusieurs chaînes de format personnalisées, afin qu'une valeur de date
et d'heure puisse être représentée dans divers formats et que l'opération d'analyse puisse continuer à aboutir.
Vous pouvez déterminer les chaînes de format personnalisées qui correspondent à une chaîne de format
standard en appelant la méthode DateTimeFormatInfo.GetAllDateTimePatterns(Char). L’exemple suivant affiche
les chaînes de format personnalisées qui sont mappées à la chaîne de format standard « d » (modèle de date
courte).
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
Console.WriteLine("'d' standard format string:");
foreach (var customString in DateTimeFormatInfo.CurrentInfo.GetAllDateTimePatterns('d'))
Console.WriteLine(" {0}", customString);
}
}
// The example displays the following output:
// 'd' standard format string:
// M/d/yyyy
// M/d/yy
// MM/dd/yy
// MM/dd/yyyy
// yy/MM/dd
// yyyy-MM-dd
// dd-MMM-yy

Imports System.Globalization

Module Example
Public Sub Main()
Console.WriteLine("'d' standard format string:")
For Each customString In DateTimeFormatInfo.CurrentInfo.GetAllDateTimePatterns("d"c)
Console.WriteLine(" {0}", customString)
Next
End Sub
End Module
' The example displays the following output:
' 'd' standard format string:
' M/d/yyyy
' M/d/yy
' MM/dd/yy
' MM/dd/yyyy
' yy/MM/dd
' yyyy-MM-dd
' dd-MMM-yy

Les sections suivantes décrivent les spécificateurs de format standard pour les valeurs DateTime et
DateTimeOffset.

Spécificateur de format de date courte ("d")


Le spécificateur de format standard "d" représente une chaîne de format de date et d'heure personnalisée définie
par la propriété DateTimeFormatInfo.ShortDatePattern d'une culture spécifique. Par exemple, la chaîne de format
personnalisée qui est retournée par la propriété ShortDatePattern de la culture dite indifférente est
"MM/dd/yyyy".
Le tableau suivant répertorie les propriétés de l'objet DateTimeFormatInfo qui contrôlent la mise en forme de la
chaîne retournée.

P RO P RIÉT É DESC RIP T IO N

ShortDatePattern Définit le format global de la chaîne de résultat.


P RO P RIÉT É DESC RIP T IO N

DateSeparator Définit la chaîne qui sépare les composants « année »,


« mois » et « jour » d'une date.

L'exemple suivant utilise le spécificateur de format "d" pour afficher une valeur de date et d'heure.

DateTime date1 = new DateTime(2008,4, 10);


Console.WriteLine(date1.ToString("d", DateTimeFormatInfo.InvariantInfo));
// Displays 04/10/2008
Console.WriteLine(date1.ToString("d",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays 4/10/2008
Console.WriteLine(date1.ToString("d",
CultureInfo.CreateSpecificCulture("en-NZ")));
// Displays 10/04/2008
Console.WriteLine(date1.ToString("d",
CultureInfo.CreateSpecificCulture("de-DE")));
// Displays 10.04.2008

Dim date1 As Date = #4/10/2008#


Console.WriteLine(date1.ToString("d", DateTimeFormatInfo.InvariantInfo))
' Displays 04/10/2008
Console.WriteLine(date1.ToString("d", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays 4/10/2008
Console.WriteLine(date1.ToString("d", _
CultureInfo.CreateSpecificCulture("en-NZ")))
' Displays 10/04/2008
Console.WriteLine(date1.ToString("d", _
CultureInfo.CreateSpecificCulture("de-DE")))
' Displays 10.04.2008

Retour au tableau

Spécificateur de format de date longue ("D")


Le spécificateur de format standard "D" représente une chaîne de format de date et d'heure personnalisée
définie par la propriété DateTimeFormatInfo.LongDatePattern actuelle. Par exemple, la chaîne de format
personnalisée pour la culture dite indifférente est "dddd, dd MMMM yyyy".
Le tableau suivant répertorie les propriétés de l'objet DateTimeFormatInfo qui contrôlent la mise en forme de la
chaîne retournée.

P RO P RIÉT É DESC RIP T IO N

LongDatePattern Définit le format global de la chaîne de résultat.

DayNames Définit les noms de jours localisés qui peuvent apparaître


dans la chaîne de résultat.

MonthNames Définit les noms de mois localisés qui peuvent apparaître


dans la chaîne de résultat.

L'exemple suivant utilise le spécificateur de format "D" pour afficher une valeur de date et d'heure.
DateTime date1 = new DateTime(2008, 4, 10);
Console.WriteLine(date1.ToString("D",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Thursday, April 10, 2008
Console.WriteLine(date1.ToString("D",
CultureInfo.CreateSpecificCulture("pt-BR")));
// Displays quinta-feira, 10 de abril de 2008
Console.WriteLine(date1.ToString("D",
CultureInfo.CreateSpecificCulture("es-MX")));
// Displays jueves, 10 de abril de 2008

Dim date1 As Date = #4/10/2008#


Console.WriteLine(date1.ToString("D", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Thursday, April 10, 2008
Console.WriteLine(date1.ToString("D", _
CultureInfo.CreateSpecificCulture("pt-BR")))
' Displays quinta-feira, 10 de abril de 2008
Console.WriteLine(date1.ToString("D", _
CultureInfo.CreateSpecificCulture("es-MX")))
' Displays jueves, 10 de abril de 2008

Retour au tableau

Spécificateur de format de date complet et d'heure courte ("f")


Le spécificateur de format standard "f" représente une combinaison des modèles de date longue ("D") et d’heure
courte ("t"), séparés par un espace.
Les informations de mise en forme d'un objet DateTimeFormatInfo spécifique affectent la chaîne de résultat. Le
tableau suivant répertorie les propriétés de l'objet DateTimeFormatInfo qui peuvent contrôler la mise en forme
de la chaîne retournée. Le spécificateur de format personnalisé retourné par les propriétés
DateTimeFormatInfo.LongDatePattern et DateTimeFormatInfo.ShortTimePattern de certaines cultures ne peut
pas utiliser toutes les propriétés.

P RO P RIÉT É DESC RIP T IO N

LongDatePattern Définit le format du composant « date » de la chaîne de


résultat.

ShortTimePattern Définit le format du composant « heure » de la chaîne de


résultat.

DayNames Définit les noms de jours localisés qui peuvent apparaître


dans la chaîne de résultat.

MonthNames Définit les noms de mois localisés qui peuvent apparaître


dans la chaîne de résultat.

TimeSeparator Définit la chaîne qui sépare les composants « heure »,


« minute » et « seconde » d'une heure.

AMDesignator Définit la chaîne qui indique les heures de minuit à avant


midi sur une horloge au format 12 heures.
P RO P RIÉT É DESC RIP T IO N

PMDesignator Définit la chaîne qui indique les heures de midi à avant


minuit sur une horloge au format 12 heures.

L'exemple suivant utilise le spécificateur de format "f" pour afficher une valeur de date et d'heure.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToString("f",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Thursday, April 10, 2008 6:30 AM
Console.WriteLine(date1.ToString("f",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays jeudi 10 avril 2008 06:30

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("f", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Thursday, April 10, 2008 6:30 AM
Console.WriteLine(date1.ToString("f", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays jeudi 10 avril 2008 06:30

Retour au tableau

Spécificateur de format de date complet et d'heure longue ("F")


Le spécificateur de format standard "F" représente une chaîne de format de date et d'heure personnalisée définie
par la propriété DateTimeFormatInfo.FullDateTimePattern actuelle. Par exemple, la chaîne de format
personnalisée pour la culture dite indifférente est "dddd, dd MMMM yyyy HH:mm:ss".
Le tableau suivant répertorie les propriétés de l'objet DateTimeFormatInfo qui peuvent contrôler la mise en
forme de la chaîne retournée. Le spécificateur de format personnalisé qui est retourné par la propriété
FullDateTimePattern de certaines cultures ne peut pas utiliser toutes les propriétés.

P RO P RIÉT É DESC RIP T IO N

FullDateTimePattern Définit le format global de la chaîne de résultat.

DayNames Définit les noms de jours localisés qui peuvent apparaître


dans la chaîne de résultat.

MonthNames Définit les noms de mois localisés qui peuvent apparaître


dans la chaîne de résultat.

TimeSeparator Définit la chaîne qui sépare les composants « heure »,


« minute » et « seconde » d'une heure.

AMDesignator Définit la chaîne qui indique les heures de minuit à avant


midi sur une horloge au format 12 heures.

PMDesignator Définit la chaîne qui indique les heures de midi à avant


minuit sur une horloge au format 12 heures.

L'exemple suivant utilise le spécificateur de format "F" pour afficher une valeur de date et d'heure.
DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);
Console.WriteLine(date1.ToString("F",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Thursday, April 10, 2008 6:30:00 AM
Console.WriteLine(date1.ToString("F",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays jeudi 10 avril 2008 06:30:00

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("F", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Thursday, April 10, 2008 6:30:00 AM
Console.WriteLine(date1.ToString("F", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays jeudi 10 avril 2008 06:30:00

Retour au tableau

Spécificateur de format de date général et d'heure courte ("g")


Le spécificateur de format standard "g" représente une combinaison des modèles de date courte ("d") et d’heure
courte ("t"), séparés par un espace.
Les informations de mise en forme d'un objet DateTimeFormatInfo spécifique affectent la chaîne de résultat. Le
tableau suivant répertorie les propriétés de l'objet DateTimeFormatInfo qui peuvent contrôler la mise en forme
de la chaîne retournée. Le spécificateur de format personnalisé qui est retourné par les propriétés
DateTimeFormatInfo.ShortDatePattern et DateTimeFormatInfo.ShortTimePattern de certaines cultures ne peut
pas utiliser toutes les propriétés.

P RO P RIÉT É DESC RIP T IO N

ShortDatePattern Définit le format du composant « date » de la chaîne de


résultat.

ShortTimePattern Définit le format du composant « heure » de la chaîne de


résultat.

DateSeparator Définit la chaîne qui sépare les composants « année »,


« mois » et « jour » d'une date.

TimeSeparator Définit la chaîne qui sépare les composants « heure »,


« minute » et « seconde » d'une heure.

AMDesignator Définit la chaîne qui indique les heures de minuit à avant


midi sur une horloge au format 12 heures.

PMDesignator Définit la chaîne qui indique les heures de midi à avant


minuit sur une horloge au format 12 heures.

L'exemple suivant utilise le spécificateur de format "g" pour afficher une valeur de date et d'heure.
DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);
Console.WriteLine(date1.ToString("g",
DateTimeFormatInfo.InvariantInfo));
// Displays 04/10/2008 06:30
Console.WriteLine(date1.ToString("g",
CultureInfo.CreateSpecificCulture("en-us")));
// Displays 4/10/2008 6:30 AM
Console.WriteLine(date1.ToString("g",
CultureInfo.CreateSpecificCulture("fr-BE")));
// Displays 10/04/2008 6:30

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("g", _
DateTimeFormatInfo.InvariantInfo))
' Displays 04/10/2008 06:30
Console.WriteLine(date1.ToString("g", _
CultureInfo.CreateSpecificCulture("en-us")))
' Displays 4/10/2008 6:30 AM
Console.WriteLine(date1.ToString("g", _
CultureInfo.CreateSpecificCulture("fr-BE")))
' Displays 10/04/2008 6:30

Retour au tableau

Spécificateur de format de date général et d'heure longue ("G")


Le spécificateur de format standard "G" représente une combinaison des modèles de date courte ("d") et d’heure
longue ("T"), séparés par un espace.
Les informations de mise en forme d'un objet DateTimeFormatInfo spécifique affectent la chaîne de résultat. Le
tableau suivant répertorie les propriétés de l'objet DateTimeFormatInfo qui peuvent contrôler la mise en forme
de la chaîne retournée. Le spécificateur de format personnalisé qui est retourné par les propriétés
DateTimeFormatInfo.ShortDatePattern et DateTimeFormatInfo.LongTimePattern de certaines cultures ne peut
pas utiliser toutes les propriétés.

P RO P RIÉT É DESC RIP T IO N

ShortDatePattern Définit le format du composant « date » de la chaîne de


résultat.

LongTimePattern Définit le format du composant « heure » de la chaîne de


résultat.

DateSeparator Définit la chaîne qui sépare les composants « année »,


« mois » et « jour » d'une date.

TimeSeparator Définit la chaîne qui sépare les composants « heure »,


« minute » et « seconde » d'une heure.

AMDesignator Définit la chaîne qui indique les heures de minuit à avant


midi sur une horloge au format 12 heures.

PMDesignator Définit la chaîne qui indique les heures de midi à avant


minuit sur une horloge au format 12 heures.

L'exemple suivant utilise le spécificateur de format "G" pour afficher une valeur de date et d'heure.
DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);
Console.WriteLine(date1.ToString("G",
DateTimeFormatInfo.InvariantInfo));
// Displays 04/10/2008 06:30:00
Console.WriteLine(date1.ToString("G",
CultureInfo.CreateSpecificCulture("en-us")));
// Displays 4/10/2008 6:30:00 AM
Console.WriteLine(date1.ToString("G",
CultureInfo.CreateSpecificCulture("nl-BE")));
// Displays 10/04/2008 6:30:00

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("G", _
DateTimeFormatInfo.InvariantInfo))
' Displays 04/10/2008 06:30:00
Console.WriteLine(date1.ToString("G", _
CultureInfo.CreateSpecificCulture("en-us")))
' Displays 4/10/2008 6:30:00 AM
Console.WriteLine(date1.ToString("G", _
CultureInfo.CreateSpecificCulture("nl-BE")))
' Displays 10/04/2008 6:30:00

Retour au tableau

Spécificateur de format de mois ("M", "m")


Le spécificateur de format standard "M" ou "m" représente une chaîne de format de date et d'heure
personnalisée définie par la propriété DateTimeFormatInfo.MonthDayPattern actuelle. Par exemple, la chaîne de
format personnalisée pour la culture dite indifférente est "MMMM dd".
Le tableau suivant répertorie les propriétés de l'objet DateTimeFormatInfo qui contrôlent la mise en forme de la
chaîne retournée.

P RO P RIÉT É DESC RIP T IO N

MonthDayPattern Définit le format global de la chaîne de résultat.

MonthNames Définit les noms de mois localisés qui peuvent apparaître


dans la chaîne de résultat.

L'exemple suivant utilise le spécificateur de format "m" pour afficher une valeur de date et d'heure.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToString("m",
CultureInfo.CreateSpecificCulture("en-us")));
// Displays April 10
Console.WriteLine(date1.ToString("m",
CultureInfo.CreateSpecificCulture("ms-MY")));
// Displays 10 April

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("m", _
CultureInfo.CreateSpecificCulture("en-us")))
' Displays April 10
Console.WriteLine(date1.ToString("m", _
CultureInfo.CreateSpecificCulture("ms-MY")))
' Displays 10 April
Retour au tableau

Spécificateur de format aller-retour ("O", "o")


Le spécificateur de format standard "O" ou "o" représente une chaîne de format de date et d’heure personnalisée
à l’aide d’un modèle qui conserve les informations de fuseau horaire et génère une chaîne de résultat conforme
à la norme ISO 8601. Pour les valeurs DateTime, ce spécificateur de format est conçu pour conserver des valeurs
de date et d'heure avec la propriété DateTime.Kind dans le texte. La chaîne mise en forme peut être de nouveau
analysée à l'aide de la méthode DateTime.Parse(String, IFormatProvider, DateTimeStyles) ou DateTime.ParseExact
si le paramètre styles a la valeur DateTimeStyles.RoundtripKind.
Le spécificateur de format standard "O" ou "o" correspond à la chaîne de format personnalisée "yyyy'-'MM'-
'dd'T'HH':'mm':'ss'.'fffffffK" pour les valeurs DateTime et à la chaîne de format personnalisée "yyyy'-'MM'-
'dd'T'HH':'mm':'ss'.'fffffffzzz" pour les valeurs DateTimeOffset. Dans cette chaîne, les paires de guillemets simples
qui délimitent des caractères individuels, comme les traits d'union, les deux-points et la lettre "T", indiquent que
le caractère individuel est un littéral qui ne peut pas être modifié. Les apostrophes n'apparaissent pas dans la
chaîne de sortie.
Le spécificateur de format standard "O" ou "o" et la chaîne de format personnalisée "yyyy'-'MM'-
'dd'T'HH':'mm':'ss'.'fffffffK" tirent parti des trois façons dont la norme ISO 8601 représente les informations de
fuseau horaire pour conserver la propriété Kind des valeurs DateTime :
Le composant de fuseau horaire des valeurs de date et d'heure DateTimeKind.Local est un décalage par
rapport à l'heure UTC (par exemple, +01:00, -07:00). Toutes les valeurs DateTimeOffset sont également
représentées dans ce format.
Le composant de fuseau horaire des valeurs de date et d'heure DateTimeKind.Utc utilise "Z" (soit zéro
décalage) pour représenter l'heure UTC.
Les valeurs de date et d'heure DateTimeKind.Unspecified ne possèdent aucune information de fuseau
horaire.
Comme le spécificateur de format standard « O » ou « o » est conforme à une norme internationale, l’opération
de mise en forme ou d’analyse qui recourt au spécificateur utilise toujours la culture dite indifférente et le
calendrier grégorien.
Les chaînes transmises aux méthodes Parse , TryParse , ParseExact , et TryParseExact de DateTime et
DateTimeOffset peuvent être analysées à l'aide du spécificateur de format "O" ou "o" si elles sont définies dans
l'un de ces formats. Dans le cas des objets DateTime, la surcharge d'analyse que vous appelez doit également
inclure un paramètre styles avec une valeur de DateTimeStyles.RoundtripKind. Notez que si vous appelez une
méthode d'analyse avec la chaîne de format personnalisée qui correspond au spécificateur de format "O" ou "o",
vous n'obtenez pas les mêmes résultats que "O" ou "o". En effet, les méthodes d'analyse qui utilisent une chaîne
de format personnalisée ne peuvent pas analyser la représentation sous forme de chaîne des valeurs de date et
d'heure auxquelles fait défaut un composant de fuseau horaire ou qui recourent à "Z" pour indiquer l'heure UTC.
L’exemple suivant utilise le spécificateur de format "o" pour afficher une série de DateTime valeurs et une
DateTimeOffset valeur sur un système situé dans le fuseau horaire Pacifique (États-Unis).
using System;

public class Example


{
public static void Main()
{
DateTime dat = new DateTime(2009, 6, 15, 13, 45, 30,
DateTimeKind.Unspecified);
Console.WriteLine("{0} ({1}) --> {0:O}", dat, dat.Kind);

DateTime uDat = new DateTime(2009, 6, 15, 13, 45, 30,


DateTimeKind.Utc);
Console.WriteLine("{0} ({1}) --> {0:O}", uDat, uDat.Kind);

DateTime lDat = new DateTime(2009, 6, 15, 13, 45, 30,


DateTimeKind.Local);
Console.WriteLine("{0} ({1}) --> {0:O}\n", lDat, lDat.Kind);

DateTimeOffset dto = new DateTimeOffset(lDat);


Console.WriteLine("{0} --> {0:O}", dto);
}
}
// The example displays the following output:
// 6/15/2009 1:45:30 PM (Unspecified) --> 2009-06-15T13:45:30.0000000
// 6/15/2009 1:45:30 PM (Utc) --> 2009-06-15T13:45:30.0000000Z
// 6/15/2009 1:45:30 PM (Local) --> 2009-06-15T13:45:30.0000000-07:00
//
// 6/15/2009 1:45:30 PM -07:00 --> 2009-06-15T13:45:30.0000000-07:00

Module Example
Public Sub Main()
Dim dat As New Date(2009, 6, 15, 13, 45, 30,
DateTimeKind.Unspecified)
Console.WriteLine("{0} ({1}) --> {0:O}", dat, dat.Kind)

Dim uDat As New Date(2009, 6, 15, 13, 45, 30, DateTimeKind.Utc)


Console.WriteLine("{0} ({1}) --> {0:O}", uDat, uDat.Kind)

Dim lDat As New Date(2009, 6, 15, 13, 45, 30, DateTimeKind.Local)


Console.WriteLine("{0} ({1}) --> {0:O}", lDat, lDat.Kind)
Console.WriteLine()

Dim dto As New DateTimeOffset(lDat)


Console.WriteLine("{0} --> {0:O}", dto)
End Sub
End Module
' The example displays the following output:
' 6/15/2009 1:45:30 PM (Unspecified) --> 2009-06-15T13:45:30.0000000
' 6/15/2009 1:45:30 PM (Utc) --> 2009-06-15T13:45:30.0000000Z
' 6/15/2009 1:45:30 PM (Local) --> 2009-06-15T13:45:30.0000000-07:00
'
' 6/15/2009 1:45:30 PM -07:00 --> 2009-06-15T13:45:30.0000000-07:00

L'exemple suivant utilise le spécificateur de format "o" pour créer une chaîne mise en forme, puis restaure la
valeur de date et d'heure d'origine en appelant une méthode Parse de date et d'heure.
// Round-trip DateTime values.
DateTime originalDate, newDate;
string dateString;
// Round-trip a local time.
originalDate = DateTime.SpecifyKind(new DateTime(2008, 4, 10, 6, 30, 0), DateTimeKind.Local);
dateString = originalDate.ToString("o");
newDate = DateTime.Parse(dateString, null, DateTimeStyles.RoundtripKind);
Console.WriteLine("Round-tripped {0} {1} to {2} {3}.", originalDate, originalDate.Kind,
newDate, newDate.Kind);
// Round-trip a UTC time.
originalDate = DateTime.SpecifyKind(new DateTime(2008, 4, 12, 9, 30, 0), DateTimeKind.Utc);
dateString = originalDate.ToString("o");
newDate = DateTime.Parse(dateString, null, DateTimeStyles.RoundtripKind);
Console.WriteLine("Round-tripped {0} {1} to {2} {3}.", originalDate, originalDate.Kind,
newDate, newDate.Kind);
// Round-trip time in an unspecified time zone.
originalDate = DateTime.SpecifyKind(new DateTime(2008, 4, 13, 12, 30, 0), DateTimeKind.Unspecified);
dateString = originalDate.ToString("o");
newDate = DateTime.Parse(dateString, null, DateTimeStyles.RoundtripKind);
Console.WriteLine("Round-tripped {0} {1} to {2} {3}.", originalDate, originalDate.Kind,
newDate, newDate.Kind);

// Round-trip a DateTimeOffset value.


DateTimeOffset originalDTO = new DateTimeOffset(2008, 4, 12, 9, 30, 0, new TimeSpan(-8, 0, 0));
dateString = originalDTO.ToString("o");
DateTimeOffset newDTO = DateTimeOffset.Parse(dateString, null, DateTimeStyles.RoundtripKind);
Console.WriteLine("Round-tripped {0} to {1}.", originalDTO, newDTO);
// The example displays the following output:
// Round-tripped 4/10/2008 6:30:00 AM Local to 4/10/2008 6:30:00 AM Local.
// Round-tripped 4/12/2008 9:30:00 AM Utc to 4/12/2008 9:30:00 AM Utc.
// Round-tripped 4/13/2008 12:30:00 PM Unspecified to 4/13/2008 12:30:00 PM Unspecified.
// Round-tripped 4/12/2008 9:30:00 AM -08:00 to 4/12/2008 9:30:00 AM -08:00.

' Round-trip DateTime values.


Dim originalDate, newDate As Date
Dim dateString As String
' Round-trip a local time.
originalDate = Date.SpecifyKind(#4/10/2008 6:30AM#, DateTimeKind.Local)
dateString = originalDate.ToString("o")
newDate = Date.Parse(dateString, Nothing, DateTimeStyles.RoundtripKind)
Console.WriteLine("Round-tripped {0} {1} to {2} {3}.", originalDate, originalDate.Kind, _
newDate, newDate.Kind)
' Round-trip a UTC time.
originalDate = Date.SpecifyKind(#4/12/2008 9:30AM#, DateTimeKind.Utc)
dateString = originalDate.ToString("o")
newDate = Date.Parse(dateString, Nothing, DateTimeStyles.RoundtripKind)
Console.WriteLine("Round-tripped {0} {1} to {2} {3}.", originalDate, originalDate.Kind, _
newDate, newDate.Kind)
' Round-trip time in an unspecified time zone.
originalDate = Date.SpecifyKind(#4/13/2008 12:30PM#, DateTimeKind.Unspecified)
dateString = originalDate.ToString("o")
newDate = Date.Parse(dateString, Nothing, DateTimeStyles.RoundtripKind)
Console.WriteLine("Round-tripped {0} {1} to {2} {3}.", originalDate, originalDate.Kind, _
newDate, newDate.Kind)

' Round-trip a DateTimeOffset value.


Dim originalDTO As New DateTimeOffset(#4/12/2008 9:30AM#, New TimeSpan(-8, 0, 0))
dateString = originalDTO.ToString("o")
Dim newDTO As DateTimeOffset = DateTimeOffset.Parse(dateString, Nothing, DateTimeStyles.RoundtripKind)
Console.WriteLine("Round-tripped {0} to {1}.", originalDTO, newDTO)
' The example displays the following output:
' Round-tripped 4/10/2008 6:30:00 AM Local to 4/10/2008 6:30:00 AM Local.
' Round-tripped 4/12/2008 9:30:00 AM Utc to 4/12/2008 9:30:00 AM Utc.
' Round-tripped 4/13/2008 12:30:00 PM Unspecified to 4/13/2008 12:30:00 PM Unspecified.
' Round-tripped 4/12/2008 9:30:00 AM -08:00 to 4/12/2008 9:30:00 AM -08:00.
Retour au tableau

Spécificateur de format RFC1123 ("R", "r")


Le spécificateur de format standard "R" ou "r" représente une chaîne de format de date et d'heure personnalisée
définie par la propriété DateTimeFormatInfo.RFC1123Pattern actuelle. Le modèle reflète une norme définie, et la
propriété est en lecture seule. Par conséquent, il s'agit toujours du même, quels que soient la culture utilisée ou
le fournisseur de format spécifié. La chaîne de format personnalisée est "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'".
Lorsque ce spécificateur de format standard est utilisé, l'opération de mise en forme ou d'analyse utilise toujours
la culture dite indifférente.
La chaîne de résultat est affectée par les propriétés suivantes de l'objet DateTimeFormatInfo retourné par la
propriété DateTimeFormatInfo.InvariantInfo qui représente la culture dite indifférente.

P RO P RIÉT É DESC RIP T IO N

RFC1123Pattern Définit le format de la chaîne de résultat.

AbbreviatedDayNames Définit les noms de jours abrégés qui peuvent s'afficher dans
la chaîne de résultat.

AbbreviatedMonthNames Définit les noms de mois abrégés qui peuvent s'afficher dans
la chaîne de résultat.

Bien que la norme RFC 1123 exprime une heure au format de temps universel coordonné (UTC, Coordinated
Universal Time), l'opération de mise en forme ne modifie pas la valeur de l'objet DateTime qui est mis en forme.
Par conséquent, vous devez convertir la valeur DateTime en temps UTC en appelant la méthode
DateTime.ToUniversalTime avant d'exécuter l'opération de mise en forme. En revanche, les valeurs
DateTimeOffset exécutent cette conversion automatiquement ; il n'est pas nécessaire d'appeler la méthode
DateTimeOffset.ToUniversalTime avant l'opération de mise en forme.
L'exemple suivant utilise le spécificateur de format "r" pour afficher une valeur DateTime et une valeur
DateTimeOffset sur un système situé dans le fuseau horaire Pacifique (États-Unis).

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


DateTimeOffset dateOffset = new DateTimeOffset(date1,
TimeZoneInfo.Local.GetUtcOffset(date1));
Console.WriteLine(date1.ToUniversalTime().ToString("r"));
// Displays Thu, 10 Apr 2008 13:30:00 GMT
Console.WriteLine(dateOffset.ToUniversalTime().ToString("r"));
// Displays Thu, 10 Apr 2008 13:30:00 GMT

Dim date1 As Date = #4/10/2008 6:30AM#


Dim dateOffset As New DateTimeOffset(date1, TimeZoneInfo.Local.GetUtcOFfset(date1))
Console.WriteLine(date1.ToUniversalTime.ToString("r"))
' Displays Thu, 10 Apr 2008 13:30:00 GMT
Console.WriteLine(dateOffset.ToUniversalTime.ToString("r"))
' Displays Thu, 10 Apr 2008 13:30:00 GMT

Retour au tableau

Spécificateur de format pouvant être trié ("s")


Le spécificateur de format standard "s" représente une chaîne de format de date et d'heure personnalisée définie
par la propriété DateTimeFormatInfo.SortableDateTimePattern actuelle. Le modèle reflète une norme définie
(ISO 8601), et la propriété est en lecture seule. Par conséquent, il s'agit toujours du même, quels que soient la
culture utilisée ou le fournisseur de format spécifié. La chaîne de format personnalisée est "yyyy'-'MM'-
'dd'T'HH':'mm':'ss".
L'objectif du spécificateur de format "s" est de produire des chaînes de résultats qui trient de façon cohérente par
ordre croissant ou décroissant en fonction des valeurs de date et d'heure. Par conséquent, bien que le
spécificateur de format standard "s" représente une valeur de date et d'heure dans un format cohérent,
l'opération de mise en forme ne modifie pas la valeur de l'objet de date et d'heure qui est mise en forme de
façon à refléter sa propriété DateTime.Kind ou sa valeur DateTimeOffset.Offset. Par exemple, les chaînes de
résultat produites par la mise en forme des valeurs de date et d'heure 2014-11-15T18:32:17 + 00:00 et 2014-
11-15T18:32:17 + 08:00 sont identiques.
Lorsque ce spécificateur de format standard est utilisé, l'opération de mise en forme ou d'analyse utilise toujours
la culture dite indifférente.
L'exemple suivant utilise le spécificateur de format "s" pour afficher une valeur DateTime et une valeur
DateTimeOffset sur un système situé dans le fuseau horaire Pacifique (États-Unis).

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToString("s"));
// Displays 2008-04-10T06:30:00

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("s"))
' Displays 2008-04-10T06:30:00

Retour au tableau

Spécificateur de format d'heure courte ("t")


Le spécificateur de format standard "t" représente une chaîne de format de date et d'heure personnalisée définie
par la propriété DateTimeFormatInfo.ShortTimePattern actuelle. Par exemple, la chaîne de format personnalisée
pour la culture dite indifférente est "HH:mm".
Les informations de mise en forme d'un objet DateTimeFormatInfo spécifique affectent la chaîne de résultat. Le
tableau suivant répertorie les propriétés de l'objet DateTimeFormatInfo qui peuvent contrôler la mise en forme
de la chaîne retournée. Le spécificateur de format personnalisé qui est retourné par la propriété
DateTimeFormatInfo.ShortTimePattern de certaines cultures ne peut pas utiliser toutes les propriétés.

P RO P RIÉT É DESC RIP T IO N

ShortTimePattern Définit le format du composant « heure » de la chaîne de


résultat.

TimeSeparator Définit la chaîne qui sépare les composants « heure »,


« minute » et « seconde » d'une heure.

AMDesignator Définit la chaîne qui indique les heures de minuit à avant


midi sur une horloge au format 12 heures.

PMDesignator Définit la chaîne qui indique les heures de midi à avant


minuit sur une horloge au format 12 heures.

L'exemple suivant utilise le spécificateur de format "t" pour afficher une valeur de date et d'heure.
DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);
Console.WriteLine(date1.ToString("t",
CultureInfo.CreateSpecificCulture("en-us")));
// Displays 6:30 AM
Console.WriteLine(date1.ToString("t",
CultureInfo.CreateSpecificCulture("es-ES")));
// Displays 6:30

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("t", _
CultureInfo.CreateSpecificCulture("en-us")))
' Displays 6:30 AM
Console.WriteLine(date1.ToString("t", _
CultureInfo.CreateSpecificCulture("es-ES")))
' Displays 6:30

Retour au tableau

Spécificateur de format d'heure longue ("T")


Le spécificateur de format standard "T" représente une chaîne de format de date et d'heure personnalisée définie
par la propriété DateTimeFormatInfo.LongTimePattern d'une culture spécifique. Par exemple, la chaîne de format
personnalisée pour la culture dite indifférente est "HH:mm:ss".
Le tableau suivant répertorie les propriétés de l'objet DateTimeFormatInfo qui peuvent contrôler la mise en
forme de la chaîne retournée. Le spécificateur de format personnalisé qui est retourné par la propriété
DateTimeFormatInfo.LongTimePattern de certaines cultures ne peut pas utiliser toutes les propriétés.

P RO P RIÉT É DESC RIP T IO N

LongTimePattern Définit le format du composant « heure » de la chaîne de


résultat.

TimeSeparator Définit la chaîne qui sépare les composants « heure »,


« minute » et « seconde » d'une heure.

AMDesignator Définit la chaîne qui indique les heures de minuit à avant


midi sur une horloge au format 12 heures.

PMDesignator Définit la chaîne qui indique les heures de midi à avant


minuit sur une horloge au format 12 heures.

L'exemple suivant utilise le spécificateur de format "T" pour afficher une valeur de date et d'heure.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToString("T",
CultureInfo.CreateSpecificCulture("en-us")));
// Displays 6:30:00 AM
Console.WriteLine(date1.ToString("T",
CultureInfo.CreateSpecificCulture("es-ES")));
// Displays 6:30:00
Dim date1 As Date = #4/10/2008 6:30AM#
Console.WriteLine(date1.ToString("T", _
CultureInfo.CreateSpecificCulture("en-us")))
' Displays 6:30:00 AM
Console.WriteLine(date1.ToString("T", _
CultureInfo.CreateSpecificCulture("es-ES")))
' Displays 6:30:00

Retour au tableau

Spécificateur de format universel pouvant être trié ("u")


Le spécificateur de format standard "u" représente une chaîne de format de date et d'heure personnalisée définie
par la propriété DateTimeFormatInfo.UniversalSortableDateTimePattern actuelle. Le modèle reflète une norme
définie, et la propriété est en lecture seule. Par conséquent, il s'agit toujours du même, quels que soient la culture
utilisée ou le fournisseur de format spécifié. La chaîne de format personnalisée est "yyyy'-'MM'-'dd
HH':'mm':'ss'Z'". Lorsque ce spécificateur de format standard est utilisé, l'opération de mise en forme ou
d'analyse utilise toujours la culture dite indifférente.
Bien que la chaîne de résultat doive exprimer une heure au format de temps universel coordonné (UTC,
Coordinated Universal Time), aucune conversion de la valeur DateTime d'origine n'est effectuée pendant
l'opération de mise en forme. Par conséquent, vous devez convertir une valeur DateTime en temps UTC en
appelant la méthode DateTime.ToUniversalTime avant de la mettre en forme. En revanche, les valeurs
DateTimeOffset exécutent cette conversion automatiquement ; il n'est pas nécessaire d'appeler la méthode
DateTimeOffset.ToUniversalTime avant l'opération de mise en forme.
L'exemple suivant utilise le spécificateur de format « u » pour afficher une valeur de date et d'heure.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToUniversalTime().ToString("u"));
// Displays 2008-04-10 13:30:00Z

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToUniversalTime.ToString("u"))
' Displays 2008-04-10 13:30:00Z

Retour au tableau

Spécificateur de format complet universel ("U")


Le spécificateur de format standard "U" représente une chaîne de format de date et d'heure personnalisée
définie par la propriété DateTimeFormatInfo.FullDateTimePattern d'une culture spécifiée. Le modèle est identique
au modèle "F". Toutefois, la valeur DateTime est automatiquement convertie en temps UTC avant d'être mise en
forme.
Le tableau suivant répertorie les propriétés de l'objet DateTimeFormatInfo qui peuvent contrôler la mise en
forme de la chaîne retournée. Le spécificateur de format personnalisé qui est retourné par la propriété
FullDateTimePattern de certaines cultures ne peut pas utiliser toutes les propriétés.

P RO P RIÉT É DESC RIP T IO N

FullDateTimePattern Définit le format global de la chaîne de résultat.


P RO P RIÉT É DESC RIP T IO N

DayNames Définit les noms de jours localisés qui peuvent apparaître


dans la chaîne de résultat.

MonthNames Définit les noms de mois localisés qui peuvent apparaître


dans la chaîne de résultat.

TimeSeparator Définit la chaîne qui sépare les composants « heure »,


« minute » et « seconde » d'une heure.

AMDesignator Définit la chaîne qui indique les heures de minuit à avant


midi sur une horloge au format 12 heures.

PMDesignator Définit la chaîne qui indique les heures de midi à avant


minuit sur une horloge au format 12 heures.

Le spécificateur de format "U" n'est pas pris en charge par le type DateTimeOffset et lève un FormatException s'il
est utilisé pour mettre en forme une valeur DateTimeOffset.
L'exemple suivant utilise le spécificateur de format "U" pour afficher une valeur de date et d'heure.

DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);


Console.WriteLine(date1.ToString("U",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Thursday, April 10, 2008 1:30:00 PM
Console.WriteLine(date1.ToString("U",
CultureInfo.CreateSpecificCulture("sv-FI")));
// Displays den 10 april 2008 13:30:00

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("U", CultureInfo.CreateSpecificCulture("en-US")))
' Displays Thursday, April 10, 2008 1:30:00 PM
Console.WriteLine(date1.ToString("U", CultureInfo.CreateSpecificCulture("sv-FI")))
' Displays den 10 april 2008 13:30:00

Retour au tableau

Spécificateur de format Année Mois ("Y")


Le spécificateur de format standard "Y" ou "y" représente une chaîne de format de date et d'heure personnalisée
définie par la propriété DateTimeFormatInfo.YearMonthPattern d'une culture spécifiée. Par exemple, la chaîne de
format personnalisée pour la culture dite indifférente est "yyyy MMMM".
Le tableau suivant répertorie les propriétés de l'objet DateTimeFormatInfo qui contrôlent la mise en forme de la
chaîne retournée.

P RO P RIÉT É DESC RIP T IO N

YearMonthPattern Définit le format global de la chaîne de résultat.

MonthNames Définit les noms de mois localisés qui peuvent apparaître


dans la chaîne de résultat.

L'exemple suivant utilise le spécificateur de format "y" pour afficher une valeur de date et d'heure.
DateTime date1 = new DateTime(2008, 4, 10, 6, 30, 0);
Console.WriteLine(date1.ToString("Y",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays April, 2008
Console.WriteLine(date1.ToString("y",
CultureInfo.CreateSpecificCulture("af-ZA")));
// Displays April 2008

Dim date1 As Date = #4/10/2008 6:30AM#


Console.WriteLine(date1.ToString("Y", CultureInfo.CreateSpecificCulture("en-US")))
' Displays April, 2008
Console.WriteLine(date1.ToString("y", CultureInfo.CreateSpecificCulture("af-ZA")))
' Displays April 2008

Retour au tableau

Notes
Paramètres du panneau de configuration
Les paramètres de l'élément Options régionales et linguistiques du Panneau de configuration influencent la
chaîne résultante produite par une opération de mise en forme. Ces paramètres sont utilisés pour initialiser
l'objet DateTimeFormatInfo associé à la culture du thread en cours qui fournit des valeurs utilisées pour indiquer
la mise en forme. Les ordinateurs qui utilisent des paramètres différents génèrent des chaînes de résultat
différentes.
De plus, si vous utilisez le constructeur CultureInfo(String) pour instancier un nouvel objet CultureInfo qui
représente la même culture que la culture système en cours, toutes les personnalisations établies par l'élément
Options régionales et linguistiques du Panneau de configuration seront appliquées au nouvel objet
CultureInfo . Vous pouvez utiliser le constructeur CultureInfo(String, Boolean) pour créer un objet CultureInfo qui
ne reflète pas les personnalisations d'un système.
Propriétés DateTimeFormatInfo
La mise en forme dépend des propriétés de l'objet DateTimeFormatInfo actif, qui est fourni implicitement par la
culture actuelle du thread ou explicitement par le paramètre IFormatProvider de la méthode qui appelle la mise
en forme. Pour le paramètre IFormatProvider, votre application doit spécifier un objet CultureInfo, qui représente
une culture, ou un objet DateTimeFormatInfo, qui représente les conventions de présentation de la date et de
l'heure d'une culture particulière. La plupart des spécificateurs de format de date et d’heure standard sont des
alias des modèles de mise en forme définis par les propriétés de l’objet DateTimeFormatInfo en cours. Votre
application peut modifier le résultat produit par certains spécificateurs de format de date et d'heure standard en
modifiant les modèles de format de date et d'heure correspondants de la propriété DateTimeFormatInfo
correspondante.

Voir aussi
System.DateTime
System.DateTimeOffset
Mise en forme des types
Chaînes de format de date et d’heure personnalisées
Exemple : utilitaire de mise en forme .NET Core WinForms (C#)
Exemple : utilitaire de mise en forme .NET Core WinForms (Visual Basic)
Chaînes de format de date et d’heure
personnalisées
18/07/2020 • 93 minutes to read • Edit Online

Une chaîne de format de date et d’heure définit la représentation textuelle d’une valeur DateTime ou
DateTimeOffset résultant d’une opération de mise en forme. Elle peut également définir la représentation d'une
valeur de date et d'heure qui est requise dans une opération d'analyse afin de convertir correctement la chaîne
sous forme de date et d'heure. Une chaîne de format personnalisée se compose d'un ou de plusieurs
spécificateurs de format de date et d'heure personnalisés. Toute chaîne autre qu’une chaîne de format de date et
d’heure standard est interprétée comme une chaîne de format de date et d’heure personnalisée.

TIP
Vous pouvez télécharger l’utilitaire de mise en forme , application .NET Core Windows Forms qui vous permet
d’appliquer des chaînes de mise en forme à des valeurs numériques ou à des valeurs de date et d’heure, et d’afficher la
chaîne de résultat. Le code source est disponible pour C# et Visual Basic.

Les chaînes de format de date et d'heure personnalisées peuvent être utilisées avec les valeurs DateTime et
DateTimeOffset.

NOTE
Certains exemples C# de cet article s’exécutent dans l’exécuteur et le terrain de jeu du code inline Try.NET. Sélectionnez le
bouton Exécuter pour exécuter un exemple dans une fenêtre interactive. Une fois que vous avez exécuté le code, vous
pouvez le modifier et exécuter le code modifié en resélectionnant Exécuter . La code modifié s’exécute dans la fenêtre
interactive ou, si la compilation échoue, la fenêtre interactive affiche tous les messages d’erreur du compilateur C#.
Le fuseau horaire local de l’exécuteur de code en ligne et du terrain de jeu inline Try.NET est le temps universel coordonné,
ou UTC. Cela peut affecter le comportement et la sortie d’exemples qui illustrent les types DateTime, DateTimeOffset et
TimeZoneInfo ainsi que leurs membres.

Dans les opérations de mise en forme, les chaînes de format de date et d'heure personnalisées peuvent être
utilisées avec la méthode ToString d'une instance de date et d'heure ou avec une méthode qui prend en charge
la mise en forme composite. L'exemple suivant illustre ces deux types d'utilisation.

DateTime thisDate1 = new DateTime(2011, 6, 10);


Console.WriteLine("Today is " + thisDate1.ToString("MMMM dd, yyyy") + ".");

DateTimeOffset thisDate2 = new DateTimeOffset(2011, 6, 10, 15, 24, 16,


TimeSpan.Zero);
Console.WriteLine("The current date and time: {0:MM/dd/yy H:mm:ss zzz}",
thisDate2);
// The example displays the following output:
// Today is June 10, 2011.
// The current date and time: 06/10/11 15:24:16 +00:00
Dim thisDate1 As Date = #6/10/2011#
Console.WriteLine("Today is " + thisDate1.ToString("MMMM dd, yyyy") + ".")

Dim thisDate2 As New DateTimeOffset(2011, 6, 10, 15, 24, 16, TimeSpan.Zero)


Console.WriteLine("The current date and time: {0:MM/dd/yy H:mm:ss zzz}",
thisDate2)
' The example displays the following output:
' Today is June 10, 2011.
' The current date and time: 06/10/11 15:24:16 +00:00

Dans les opérations d'analyse, les chaînes de format de date et d'heure personnalisées peuvent être utilisées
avec les méthodes DateTime.ParseExact, DateTime.TryParseExact, DateTimeOffset.ParseExact et
DateTimeOffset.TryParseExact. Pour que l’opération d’analyse aboutisse, il faut qu’une chaîne d’entrée soit
parfaitement conforme à un modèle particulier. L'exemple suivant illustre un appel à la méthode
DateTimeOffset.ParseExact(String, String, IFormatProvider) pour analyser une date qui doit comprendre un jour,
un mois et une année sur deux chiffres.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
string[] dateValues = { "30-12-2011", "12-30-2011",
"30-12-11", "12-30-11" };
string pattern = "MM-dd-yy";
DateTime parsedDate;

foreach (var dateValue in dateValues) {


if (DateTime.TryParseExact(dateValue, pattern, null,
DateTimeStyles.None, out parsedDate))
Console.WriteLine("Converted '{0}' to {1:d}.",
dateValue, parsedDate);
else
Console.WriteLine("Unable to convert '{0}' to a date and time.",
dateValue);
}
}
}
// The example displays the following output:
// Unable to convert '30-12-2011' to a date and time.
// Unable to convert '12-30-2011' to a date and time.
// Unable to convert '30-12-11' to a date and time.
// Converted '12-30-11' to 12/30/2011.
Imports System.Globalization

Module Example
Public Sub Main()
Dim dateValues() As String = {"30-12-2011", "12-30-2011",
"30-12-11", "12-30-11"}
Dim pattern As String = "MM-dd-yy"
Dim parsedDate As Date

For Each dateValue As String In dateValues


If DateTime.TryParseExact(dateValue, pattern, Nothing,
DateTimeStyles.None, parsedDate) Then
Console.WriteLine("Converted '{0}' to {1:d}.",
dateValue, parsedDate)
Else
Console.WriteLine("Unable to convert '{0}' to a date and time.",
dateValue)
End If
Next
End Sub
End Module
' The example displays the following output:
' Unable to convert '30-12-2011' to a date and time.
' Unable to convert '12-30-2011' to a date and time.
' Unable to convert '30-12-11' to a date and time.
' Converted '12-30-11' to 12/30/2011.

Le tableau suivant décrit les spécificateurs de format de date et d'heure personnalisés et affiche une chaîne de
résultat produite par chaque spécificateur de format. Par défaut, les chaînes de résultat reflètent les conventions
de mise en forme de la culture en-US. Si un spécificateur de format particulier produit une chaîne de résultat
localisée, l'exemple indique également la culture à laquelle la chaîne de résultat s'applique. Pour plus
d’informations sur les chaînes de format de date et d’heure personnalisées, voir la section Remarques.

SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L ES

"d" Jour du mois, de 1 à 31. 2009-06-01T13:45:30 -> 1

Informations supplémentaires : 2009-06-15T13:45:30 -> 15


Spécificateur de format personnalisé
"d".

"dd" Jour du mois, de 01 à 31. 2009-06-01T13:45:30 -> 01

Informations supplémentaires : 2009-06-15T13:45:30 -> 15


Spécificateur de format personnalisé
"dd".

"ddd" Nom abrégé du jour de la semaine. 2009-06-15T13:45:30 -> Mon (en-


US)
Informations supplémentaires :
Spécificateur de format personnalisé 2009-06-15T13:45:30 -> Пн (ru-RU)
"ddd".
2009-06-15T13:45:30 -> lun. (fr-FR)

"dddd" Nom complet du jour de la semaine. 2009-06-15T13:45:30 -> Monday


(en-US)
Informations supplémentaires :
Spécificateur de format personnalisé 2009-06-15T13:45:30 ->
"dddd". понедельник (ru-RU)

2009-06-15T13:45:30 -> lundi (fr-FR)


SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L ES

"f" Dixièmes de seconde dans une valeur 2009-06-15T13:45:30.6170000 -> 6


de date et d'heure.
2009-06-15T13:45:30.05 -> 0
Informations supplémentaires :
Spécificateur de format personnalisé
"f".

"ff" Centièmes de seconde dans une valeur 2009-06-15T13:45:30.6170000 -> 61


de date et d'heure.
2009-06-15T13:45:30.0050000 -> 00
Informations supplémentaires :
spécificateur de format personnalisé
"FF".

"fff" Millisecondes dans une valeur de date 6/15/2009 13:45:30.617 -> 617
et d'heure.
6/15/2009 13:45:30.0005 -> 000
Informations supplémentaires :
Spécificateur de format personnalisé
"fff".

"ffff" Dix millièmes de seconde dans une 2009-06-15T13:45:30.6175000 ->


valeur de date et d'heure. 6175

Informations supplémentaires : 2009-06-15T13:45:30.0000500 ->


spécificateur de format personnalisé 0000
"FFFF".

"fffff" Cent millièmes de seconde dans une 2009-06-15T13:45:30.6175400 ->


valeur de date et d'heure. 61754

Informations supplémentaires : 6/15/2009 13:45:30.000005 -> 00000


Spécificateur de format personnalisé
"fffff".

"ffffff" Millionièmes de seconde dans une 2009-06-15T13:45:30.6175420 ->


valeur de date et d'heure. 617542

Informations supplémentaires : 2009-06-15T13:45:30.0000005 ->


spécificateur de format personnalisé 000000
"FFFFFF".

"fffffff" Dix millionièmes de seconde dans une 2009-06-15T13:45:30.6175425 ->


valeur de date et d'heure. 6175425

Informations supplémentaires : 2009-06-15T13:45:30.0001150 ->


spécificateur de format personnalisé 0001150
"fffffff".

"F" Si la valeur est différente de zéro, elle 2009-06-15T13:45:30.6170000 -> 6


représente les dixièmes de seconde
dans une valeur de date et d'heure. 2009-06-15T13:45:30.0500000 ->
(pas de sortie)
Informations supplémentaires :
spécificateur de format personnalisé
"F".
SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L ES

"FF" Si la valeur est différente de zéro, elle 2009-06-15T13:45:30.6170000 -> 61


représente les centièmes de seconde
dans une valeur de date et d'heure. 2009-06-15T13:45:30.0050000 ->
(pas de sortie)
Informations supplémentaires :
Spécificateur de format personnalisé
"FF".

"FFF" Si la valeur est différente de zéro, elle 2009-06-15T13:45:30.6170000 ->


représente les millisecondes dans une 617
valeur de date et d'heure.
2009-06-15T13:45:30.0005000 ->
Informations supplémentaires : (pas de sortie)
spécificateur de format personnalisé
"fff".

"FFFF" Si la valeur est différente de zéro, elle 2009-06-15T13:45:30.5275000 ->


représente les dix millièmes de seconde 5275
dans une valeur de date et d'heure.
2009-06-15T13:45:30.0000500 ->
Informations supplémentaires : (pas de sortie)
Spécificateur de format personnalisé
"FFFF".

"FFFFF" Si la valeur est différente de zéro, elle 2009-06-15T13:45:30.6175400 ->


représente les cent millièmes de 61754
seconde dans une valeur de date et
d'heure. 2009-06-15T13:45:30.0000050 ->
(pas de sortie)
Informations supplémentaires :
spécificateur de format personnalisé
"fffff".

"FFFFFF" Si la valeur est différente de zéro, elle 2009-06-15T13:45:30.6175420 ->


représente les millionièmes de seconde 617542
dans une valeur de date et d'heure.
2009-06-15T13:45:30.0000005 ->
Informations supplémentaires : (pas de sortie)
Spécificateur de format personnalisé
"FFFFFF".

"FFFFFFF" Si la valeur est différente de zéro, elle 2009-06-15T13:45:30.6175425 ->


représente les dix millionièmes de 6175425
seconde dans une valeur de date et
d'heure. 2009-06-15T13:45:30.0001150 ->
000115
Informations supplémentaires :
Spécificateur de format personnalisé
"FFFFFFF".

"g", "gg" Période ou ère. 2009-06-15T13:45:30.6170000 ->


A.D.
Informations supplémentaires :
Spécificateur de format personnalisé
"g" ou "gg".
SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L ES

"h" Heure, au format de 12 heures, de 2009-06-15T01:45:30 -> 1


1 à 12.
2009-06-15T13:45:30 -> 1
Informations supplémentaires :
spécificateur de format personnalisé
"h".

"hh" Heure, au format de 12 heures, de 2009-06-15T01:45:30 -> 01


01 à 12.
2009-06-15T13:45:30 -> 01
Informations supplémentaires :
spécificateur de format personnalisé
"HH".

"H" Heure, au format de 24 heures, de 2009-06-15T01:45:30 -> 1


0 à 23.
2009-06-15T13:45:30 -> 13
Informations supplémentaires :
Spécificateur de format personnalisé
"H".

"HH" Heure, au format de 24 heures, de 2009-06-15T01:45:30 -> 01


00 à 23.
2009-06-15T13:45:30 -> 13
Informations supplémentaires :
Spécificateur de format personnalisé
"HH".

"K" Informations de fuseau horaire. Avec les valeurs DateTime :

Informations supplémentaires : 2009-06-15T13:45:30, Kind


Spécificateur de format personnalisé Unspecified ->
"K".
2009-06-15T13:45:30, Kind Utc -> Z

2009-06-15T13:45:30, Kind Local -> -


07:00 (dépend des paramètres de
l'ordinateur local)

Avec les valeurs DateTimeOffset :

2009-06-15T01:45:30-07:00 --> -
07:00

2009-06-15T08:45:30+00:00 -->
+00:00

"m" Minute, définie entre 0 et 59. 2009-06-15T01:09:30 -> 9

Informations supplémentaires : 2009-06-15T13:29:30 -> 29


spécificateur de format personnalisé
"m".

"mm" Minute, définie entre 00 et 59. 2009-06-15T01:09:30 -> 09

Informations supplémentaires : 2009-06-15T01:45:30 -> 45


Spécificateur de format personnalisé
"mm".
SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L ES

"M" Mois, de 1 à 12. 2009-06-15T13:45:30 -> 6

Informations supplémentaires :
Spécificateur de format personnalisé
"M".

"MM" Mois, de 01 à 12. 2009-06-15T13:45:30 -> 06

Informations supplémentaires :
spécificateur de format personnalisé
"mm".

"MMM" Nom abrégé du mois. 2009-06-15T13:45:30 -> Jun (en-US)

Informations supplémentaires : 2009-06-15T13:45:30 -> juin (fr-FR)


Spécificateur de format personnalisé
"MMM". 2009-06-15T13:45:30 -> Jun (zu-ZA)

"MMMM" Nom complet du mois. 2009-06-15T13:45:30 -> June (en-US)

Informations supplémentaires : 2009-06-15T13:45:30 -> juni (da-DK)


Spécificateur de format personnalisé
"MMMM". 2009-06-15T13:45:30 -> uJuni (zu-
ZA)

"s" Seconde, de 0 à 59. 2009-06-15T13:45:09 -> 9

Informations supplémentaires :
Spécificateur de format personnalisé
"s".

"ss" Seconde, de 00 à 59. 2009-06-15T13:45:09 -> 09

Informations supplémentaires :
Spécificateur de format personnalisé
"ss".

"t" Premier caractère de l'indicateur 2009-06-15T13:45:30 -> P (en-US)


AM/PM.
2009-06-15T13:45:30 -> 午 (ja-JP)
Informations supplémentaires :
Spécificateur de format personnalisé 2009-06-15T13:45:30 -> (fr-FR)
"t".

"tt" Indicateur AM/PM. 2009-06-15T13:45:30 -> PM (en-US)

Informations supplémentaires : 2009-06-15T13:45:30 -> 午後 (ja-JP)


Spécificateur de format personnalisé
"tt". 2009-06-15T13:45:30 -> (fr-FR)
SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L ES

"y" Année, de 0 à 99. 0001-01-01T00:00:00 -> 1

Informations supplémentaires : 0900-01-01T00:00:00 -> 0


Spécificateur de format personnalisé
"y". 1900-01-01T00:00:00 -> 0

2009-06-15T13:45:30 -> 9

2019-06-15T13:45:30 -> 19

"yy" Année, de 00 à 99. 0001-01-01T00:00:00 -> 01

Informations supplémentaires : 0900-01-01T00:00:00 -> 00


Spécificateur de format personnalisé
"yy". 1900-01-01T00:00:00 -> 00

2019-06-15T13:45:30 -> 19

"yyy" Année, avec au minimum trois chiffres. 0001-01-01T00:00:00 -> 001

Informations supplémentaires : 0900-01-01T00:00:00 -> 900


Spécificateur de format personnalisé
"yyy". 1900-01-01T00:00:00 -> 1900

2009-06-15T13:45:30 -> 2009

"yyyy" Année, en tant que nombre à quatre 0001-01-01T00:00:00 -> 0001


chiffres.
0900-01-01T00:00:00 -> 0900
Informations supplémentaires :
Spécificateur de format personnalisé 1900-01-01T00:00:00 -> 1900
"yyyy".
2009-06-15T13:45:30 -> 2009

"yyyyy" Année, en tant que nombre à cinq 0001-01-01T00:00:00 -> 00001


chiffres.
2009-06-15T13:45:30 -> 02009
Informations supplémentaires :
Spécificateur de format personnalisé
"yyyyy".

"z" Décalage horaire par rapport à l'heure 2009-06-15T13:45:30-07:00 -> -7


UTC, sans zéro non significatif.

Informations supplémentaires :
Spécificateur de format personnalisé
"z".

"zz" Décalage horaire par rapport à l'heure 2009-06-15T13:45:30-07:00 -> -07


UTC, avec un zéro non significatif pour
une valeur à un seul chiffre.

Informations supplémentaires :
Spécificateur de format personnalisé
"zz".
SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L ES

"zzz" Décalage horaire par rapport à l'heure 2009-06-15T13:45:30-07:00 -> -


UTC, en heures et minutes. 07:00

Informations supplémentaires :
Spécificateur de format personnalisé
"zzz".

":" Séparateur horaire. 2009-06-15T13:45:30 -> : (en-US)

Informations supplémentaires : 2009-06-15T13:45:30 -> . (it-IT)


Spécificateur de format
personnalisé ":". 2009-06-15T13:45:30 -> : (ja-JP)

"/" Séparateur de date. 2009-06-15T13:45:30 -> / (en-US)

Informations supplémentaires : 2009-06-15T13:45:30 -> - (ar-DZ)


spécificateur de format personnalisé
"/". 2009-06-15T13:45:30 -> . (tr-TR)

«chaîne» Délimiteur de chaîne littérale. 2009-06-15T13:45:30 ("arr:" h:m t) ->


arr: 1:45 P
'chaîne' Plus d’informations : Littéraux de
caractère. 2009-06-15T13:45:30 ('arr:' h:m t) ->
arr: 1:45 P

% Définit le caractère suivant comme 2009-06-15T13:45:30 (%h) -> 1


spécificateur de format personnalisé.

Informations supplémentaires :
Utilisation de spécificateurs de format
personnalisés uniques.

\ Caractère d'échappement. 2009-06-15T13:45:30 (h \h) -> 1 h

Plus d’informations : Littéraux de


caractère et Utilisation du caractère
d’échappement.

N'importe quel autre caractère Le caractère est copié inchangé dans la 2009-06-15T01:45:30 (arr hh:mm t) -
chaîne de résultat. > arr 01:45 A

Plus d’informations : Littéraux de


caractère.

Les sections suivantes fournissent des informations supplémentaires sur chaque spécificateur de format de date
et d'heure personnalisé. Sauf indication contraire, chaque spécificateur produit une représentation sous forme
de chaîne identique qu’il soit utilisé avec une valeur DateTime ou une valeur DateTimeOffset.

Le spécificateur de format personnalisé « d »


Le spécificateur de format personnalisé "d" représente le jour du mois sous la forme d'un nombre compris
entre 1 et 31. Un jour à un seul chiffre est mis en forme sans zéro non significatif.
Si le spécificateur de format « d » est utilisé sans autre spécificateur de format personnalisé, il est interprété
comme le spécificateur de format de date et d’heure standard « d ». Pour plus d’informations sur l’utilisation
d’un seul spécificateur de format, voir Utiliser des spécificateurs de format personnalisés uniques plus loin dans
cette rubrique.
L'exemple suivant inclut le spécificateur de format personnalisé "d" dans plusieurs chaînes de format.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15);

Console.WriteLine(date1.ToString("d, M",
CultureInfo.InvariantCulture));
// Displays 29, 8

Console.WriteLine(date1.ToString("d MMMM",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays 29 August
Console.WriteLine(date1.ToString("d MMMM",
CultureInfo.CreateSpecificCulture("es-MX")));
// Displays 29 agosto

Dim date1 As Date = #08/29/2008 7:27:15PM#

Console.WriteLine(date1.ToString("d, M", _
CultureInfo.InvariantCulture))
' Displays 29, 8

Console.WriteLine(date1.ToString("d MMMM", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays 29 August
Console.WriteLine(date1.ToString("d MMMM", _
CultureInfo.CreateSpecificCulture("es-MX")))
' Displays 29 agosto

Retour au tableau

Le spécificateur de format personnalisé « dd »


Le spécificateur de format personnalisé "dd" représente le jour du mois sous la forme d'un nombre compris
entre 01 et 31. Un jour à un seul chiffre est mis en forme avec un zéro non significatif.
L'exemple suivant inclut le spécificateur de format personnalisé "dd" dans une chaîne de format personnalisée.

DateTime date1 = new DateTime(2008, 1, 2, 6, 30, 15);

Console.WriteLine(date1.ToString("dd, MM",
CultureInfo.InvariantCulture));
// 02, 01

Dim date1 As Date = #1/2/2008 6:30:15AM#

Console.WriteLine(date1.ToString("dd, MM", _
CultureInfo.InvariantCulture))
' 02, 01

Retour au tableau

Le spécificateur de format personnalisé « ddd »


Le spécificateur de format personnalisé "ddd" représente le nom abrégé du jour de la semaine. Le nom abrégé
localisé du jour de la semaine est récupéré de la propriété DateTimeFormatInfo.AbbreviatedDayNames de la
culture actuelle ou spécifiée.
L'exemple suivant inclut le spécificateur de format personnalisé "ddd" dans une chaîne de format personnalisée.
DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15);

Console.WriteLine(date1.ToString("ddd d MMM",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Fri 29 Aug
Console.WriteLine(date1.ToString("ddd d MMM",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays ven. 29 août

Dim date1 As Date = #08/29/2008 7:27:15PM#

Console.WriteLine(date1.ToString("ddd d MMM", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Fri 29 Aug
Console.WriteLine(date1.ToString("ddd d MMM", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays ven. 29 août

Retour au tableau

Spécificateur de format personnalisé "dddd"


Le spécificateur de format personnalisé "dddd" (plus n'importe quel nombre de spécificateurs "d"
supplémentaires) représente le nom complet du jour de la semaine. Le nom localisé du jour de la semaine est
récupéré de la propriété DateTimeFormatInfo.DayNames de la culture actuelle ou spécifiée.
L'exemple suivant inclut le spécificateur de format personnalisé "dddd" dans une chaîne de format
personnalisée.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15);

Console.WriteLine(date1.ToString("dddd dd MMMM",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Friday 29 August
Console.WriteLine(date1.ToString("dddd dd MMMM",
CultureInfo.CreateSpecificCulture("it-IT")));
// Displays venerdì 29 agosto

Dim date1 As Date = #08/29/2008 7:27:15PM#

Console.WriteLine(date1.ToString("dddd dd MMMM", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Friday 29 August
Console.WriteLine(date1.ToString("dddd dd MMMM", _
CultureInfo.CreateSpecificCulture("it-IT")))
' Displays venerdì 29 agosto

Retour au tableau

Spécificateur de format personnalisé "f"


Le spécificateur de format personnalisé "f" représente le chiffre le plus significatif de la fraction de seconde ;
autrement dit, il représente les dixièmes de seconde dans une valeur de date et d'heure.
Si le spécificateur de format « f » est utilisé sans autre spécificateur de format, il est interprété comme le
spécificateur de format de date et d’heure standard « f ». Pour plus d’informations sur l’utilisation d’un seul
spécificateur de format, voir Utiliser des spécificateurs de format personnalisés uniques plus loin dans cette
rubrique.
Lorsque vous utilisez "f" dans le cadre d'une chaîne de format fournie à la méthode ParseExact, TryParseExact,
ParseExact ou TryParseExact, le nombre de spécificateurs de format "f" utilisés indique le nombre des chiffres les
plus significatifs de la fraction de seconde requis pour analyser correctement la chaîne.
L'exemple suivant inclut le spécificateur de format personnalisé "f" dans une chaîne de format personnalisée.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15, 18);


CultureInfo ci = CultureInfo.InvariantCulture;

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci));
// Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci));
// Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci));
// Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci));
// Displays 07:27:15.018

Dim date1 As New Date(2008, 8, 29, 19, 27, 15, 018)


Dim ci As CultureInfo = CultureInfo.InvariantCulture

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci))
' Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci))
' Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci))
' Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci))
' Displays 07:27:15.018

Retour au tableau

Spécificateur de format personnalisé "FF"


Le spécificateur de format personnalisé "ff" représente les deux chiffres les plus significatifs de la fraction de
seconde ; autrement dit, il représente les centièmes de seconde dans une valeur de date et d'heure.
L'exemple suivant inclut le spécificateur de format personnalisé "ff" dans une chaîne de format personnalisée.
DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15, 18);
CultureInfo ci = CultureInfo.InvariantCulture;

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci));
// Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci));
// Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci));
// Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci));
// Displays 07:27:15.018

Dim date1 As New Date(2008, 8, 29, 19, 27, 15, 018)


Dim ci As CultureInfo = CultureInfo.InvariantCulture

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci))
' Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci))
' Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci))
' Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci))
' Displays 07:27:15.018

Retour au tableau

Le spécificateur de format personnalisé « fff »


Le spécificateur de format personnalisé "fff" représente les trois chiffres les plus significatifs de la fraction de
seconde ; autrement dit, il représente les millisecondes dans une valeur de date et d'heure.
L'exemple suivant inclut le spécificateur de format personnalisé "fff" dans une chaîne de format personnalisée.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15, 18);


CultureInfo ci = CultureInfo.InvariantCulture;

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci));
// Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci));
// Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci));
// Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci));
// Displays 07:27:15.018
Dim date1 As New Date(2008, 8, 29, 19, 27, 15, 018)
Dim ci As CultureInfo = CultureInfo.InvariantCulture

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci))
' Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci))
' Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci))
' Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci))
' Displays 07:27:15.018

Retour au tableau

Spécificateur de format personnalisé "FFFF"


Le spécificateur de format personnalisé "ffff" représente les quatre chiffres les plus significatifs de la fraction de
seconde ; autrement dit, il représente les dix millièmes de seconde dans une valeur de date et d'heure.
Bien qu’il soit possible d’afficher les dix millièmes du composant « secondes » d’une valeur d’heure, cette valeur
n’est pas forcément significative. La précision des valeurs de date et d'heure dépend de la résolution de l'horloge
système. Sur les systèmes d’exploitation Windows NT version 3.5 (et ultérieures) et Windows Vista, la résolution
de l’horloge est d’environ 10-15 millisecondes.
Retour au tableau

Spécificateur de format personnalisé "fffff"


Le spécificateur de format personnalisé "fffff" représente les cinq chiffres les plus significatifs de la fraction de
seconde ; autrement dit, il représente les cent millièmes de seconde dans une valeur de date et d'heure.
Bien qu’il soit possible d’afficher les cent millièmes du composant « secondes » d’une valeur d’heure, cette
valeur n’est pas forcément significative. La précision des valeurs de date et d'heure dépend de la résolution de
l'horloge système. Sur les systèmes d’exploitation Windows NT 3.5 (et versions ultérieures) et Windows Vista, la
résolution de l’horloge est d’environ 10-15 millisecondes.
Retour au tableau

Spécificateur de format personnalisé "FFFFFF"


Le spécificateur de format personnalisé "ffffff" représente les six chiffres les plus significatifs de la fraction de
seconde ; autrement dit, il représente les millionièmes de seconde dans une valeur de date et d'heure.
Bien qu’il soit possible d’afficher les millionièmes du composant « secondes » d’une valeur d’heure, cette valeur
n’est pas forcément significative. La précision des valeurs de date et d'heure dépend de la résolution de l'horloge
système. Sur les systèmes d’exploitation Windows NT 3.5 (et versions ultérieures) et Windows Vista, la
résolution de l’horloge est d’environ 10-15 millisecondes.
Retour au tableau

Spécificateur de format personnalisé "fffffff"


Le spécificateur de format personnalisé "fffffff" représente les sept chiffres les plus significatifs de la fraction de
seconde ; autrement dit, il représente les dix millionièmes de seconde dans une valeur de date et d'heure.
Bien qu’il soit possible d’afficher les dix millionièmes du composant « secondes » d’une valeur d’heure, cette
valeur n’est pas forcément significative. La précision des valeurs de date et d'heure dépend de la résolution de
l'horloge système. Sur les systèmes d’exploitation Windows NT 3.5 (et versions ultérieures) et Windows Vista, la
résolution de l’horloge est d’environ 10-15 millisecondes.
Retour au tableau

Spécificateur de format personnalisé "F"


Le spécificateur de format personnalisé "F" représente le chiffre le plus significatif de la fraction de seconde ;
autrement dit, il représente les dixièmes de seconde dans une valeur de date et d'heure. Rien ne s'affiche si le
chiffre est zéro.
Si le spécificateur de format « F » est utilisé sans autre spécificateur de format, il est interprété comme le
spécificateur de format de date et d’heure standard « F ». Pour plus d’informations sur l’utilisation d’un seul
spécificateur de format, voir Utiliser des spécificateurs de format personnalisés uniques plus loin dans cette
rubrique.
Le nombre de spécificateurs de format "F" utilisés avec la méthode ParseExact, TryParseExact, ParseExact ou
TryParseExact indique le nombre maximum possible des chiffres les plus significatifs de la fraction de seconde
pour analyser correctement la chaîne.
L'exemple suivant inclut le spécificateur de format personnalisé "F" dans une chaîne de format personnalisée.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15, 18);


CultureInfo ci = CultureInfo.InvariantCulture;

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci));
// Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci));
// Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci));
// Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci));
// Displays 07:27:15.018

Dim date1 As New Date(2008, 8, 29, 19, 27, 15, 018)


Dim ci As CultureInfo = CultureInfo.InvariantCulture

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci))
' Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci))
' Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci))
' Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci))
' Displays 07:27:15.018

Retour au tableau

Spécificateur de format personnalisé "FF"


Le spécificateur de format personnalisé "FF" représente les deux chiffres les plus significatifs de la fraction de
seconde ; autrement dit, il représente les centièmes de seconde dans une valeur de date et d'heure. Toutefois, les
zéros de fin et doubles zéros ne sont pas affichés.
L'exemple suivant inclut le spécificateur de format personnalisé "FF" dans une chaîne de format personnalisée.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15, 18);


CultureInfo ci = CultureInfo.InvariantCulture;

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci));
// Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci));
// Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci));
// Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci));
// Displays 07:27:15.018

Dim date1 As New Date(2008, 8, 29, 19, 27, 15, 018)


Dim ci As CultureInfo = CultureInfo.InvariantCulture

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci))
' Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci))
' Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci))
' Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci))
' Displays 07:27:15.018

Retour au tableau

Spécificateur de format personnalisé "FFF"


Le spécificateur de format personnalisé "FFF" représente les trois chiffres les plus significatifs de la fraction de
seconde ; autrement dit, il représente les millisecondes dans une valeur de date et d'heure. Toutefois, les zéros de
fin et triples zéros ne sont pas affichés.
L'exemple suivant inclut le spécificateur de format personnalisé "FFF" dans une chaîne de format personnalisée.
DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15, 18);
CultureInfo ci = CultureInfo.InvariantCulture;

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci));
// Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci));
// Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci));
// Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci));
// Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci));
// Displays 07:27:15.018

Dim date1 As New Date(2008, 8, 29, 19, 27, 15, 018)


Dim ci As CultureInfo = CultureInfo.InvariantCulture

Console.WriteLine(date1.ToString("hh:mm:ss.f", ci))
' Displays 07:27:15.0
Console.WriteLine(date1.ToString("hh:mm:ss.F", ci))
' Displays 07:27:15
Console.WriteLine(date1.ToString("hh:mm:ss.ff", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.FF", ci))
' Displays 07:27:15.01
Console.WriteLine(date1.ToString("hh:mm:ss.fff", ci))
' Displays 07:27:15.018
Console.WriteLine(date1.ToString("hh:mm:ss.FFF", ci))
' Displays 07:27:15.018

Retour au tableau

Spécificateur de format personnalisé "FFFF"


Le spécificateur de format personnalisé "FFFF" représente les quatre chiffres les plus significatifs de la fraction
de seconde ; autrement dit, il représente les dix millièmes de seconde dans une valeur de date et d'heure.
Toutefois, les zéros de fin et quadruples zéros ne sont pas affichés.
Bien qu’il soit possible d’afficher les dix millièmes du composant « secondes » d’une valeur d’heure, cette valeur
n’est pas forcément significative. La précision des valeurs de date et d'heure dépend de la résolution de l'horloge
système. Sur les systèmes d’exploitation Windows NT 3.5 (et versions ultérieures) et Windows Vista, la
résolution de l’horloge est d’environ 10-15 millisecondes.
Retour au tableau

Spécificateur de format personnalisé "FFFFF"


Le spécificateur de format personnalisé "FFFFF" représente les cinq chiffres les plus significatifs de la fraction de
seconde ; autrement dit, il représente les cent millièmes de seconde dans une valeur de date et d'heure.
Toutefois, les zéros de fin et quintuples zéros ne sont pas affichés.
Bien qu’il soit possible d’afficher les cent millièmes du composant « secondes » d’une valeur d’heure, cette
valeur n’est pas forcément significative. La précision des valeurs de date et d'heure dépend de la résolution de
l'horloge système. Sur les systèmes d’exploitation Windows NT 3.5 (et versions ultérieures) et Windows Vista, la
résolution de l’horloge est d’environ 10-15 millisecondes.
Retour au tableau
Spécificateur de format personnalisé "FFFFFF"
Le spécificateur de format personnalisé "FFFFFF" représente les six chiffres les plus significatifs de la fraction de
seconde ; autrement dit, il représente les millionièmes de seconde dans une valeur de date et d'heure. Toutefois,
les zéros de fin et sextuples zéros ne sont pas affichés.
Bien qu’il soit possible d’afficher les millionièmes du composant « secondes » d’une valeur d’heure, cette valeur
n’est pas forcément significative. La précision des valeurs de date et d'heure dépend de la résolution de l'horloge
système. Sur les systèmes d’exploitation Windows NT 3.5 (et versions ultérieures) et Windows Vista, la
résolution de l’horloge est d’environ 10-15 millisecondes.
Retour au tableau

Spécificateur de format personnalisé "FFFFFFF"


Le spécificateur de format personnalisé "FFFFFFF" représente les sept chiffres les plus significatifs de la fraction
de seconde ; autrement dit, il représente les dix millionièmes de seconde dans une valeur de date et d'heure.
Toutefois, les zéros de fin et septuples zéros ne sont pas affichés.
Bien qu’il soit possible d’afficher les dix millionièmes du composant « secondes » d’une valeur d’heure, cette
valeur n’est pas forcément significative. La précision des valeurs de date et d'heure dépend de la résolution de
l'horloge système. Sur les systèmes d’exploitation Windows NT 3.5 (et versions ultérieures) et Windows Vista, la
résolution de l’horloge est d’environ 10-15 millisecondes.
Retour au tableau

Spécificateur de format personnalisé "g" ou "GG"


Les spécificateurs de format personnalisés "g" ou "gg" (plus n'importe quel nombre de spécificateurs "g"
supplémentaires) représentent la période ou l'ère, par exemple après J.-C. L’opération de mise en forme ignore
ce spécificateur si la date à mettre en forme n’est associée à aucune chaîne de période ou d’ère.
Si le spécificateur de format « g » est utilisé sans autre spécificateur de format personnalisé, il est interprété
comme le spécificateur de format de date et d’heure standard « g ». Pour plus d’informations sur l’utilisation
d’un seul spécificateur de format, voir Utiliser des spécificateurs de format personnalisés uniques plus loin dans
cette rubrique.
L'exemple suivant inclut le spécificateur de format personnalisé "g" dans une chaîne de format personnalisée.

DateTime date1 = new DateTime(70, 08, 04);

Console.WriteLine(date1.ToString("MM/dd/yyyy g",
CultureInfo.InvariantCulture));
// Displays 08/04/0070 A.D.
Console.WriteLine(date1.ToString("MM/dd/yyyy g",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays 08/04/0070 ap. J.-C.

Dim date1 As Date = #08/04/0070#

Console.WriteLine(date1.ToString("MM/dd/yyyy g", _
CultureInfo.InvariantCulture))
' Displays 08/04/0070 A.D.
Console.WriteLine(date1.ToString("MM/dd/yyyy g", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays 08/04/0070 ap. J.-C.
Retour au tableau

Spécificateur de format personnalisé "h"


Le spécificateur de format personnalisé "h" représente l'heure sous la forme d'un nombre compris entre 1
et 12 ; autrement dit, l'heure est représentée au format de 12 heures qui compte les heures entières depuis
minuit ou midi. Une heure après minuit se présente de la même manière que la même heure après midi. L'heure
n'est pas arrondie et une heure à un seul chiffre est mise en forme sans zéro non significatif. Par exemple, si on
considère l'heure 5:43 du matin ou de l'après-midi, ce spécificateur de format personnalisé affiche "5".
Si le spécificateur de format « h » est utilisé sans autre spécificateur de format personnalisé, il est interprété
comme un spécificateur de format de date et d’heure standard et lève une FormatException. Pour plus
d’informations sur l’utilisation d’un seul spécificateur de format, voir Utiliser des spécificateurs de format
personnalisés uniques plus loin dans cette rubrique.
L'exemple suivant inclut le spécificateur de format personnalisé "h" dans une chaîne de format personnalisée.

DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1 µ
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1.5 µ

Dim date1 As Date


date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1 µ
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1.5 µ

Retour au tableau

Spécificateur de format personnalisé "HH"


Le spécificateur de format personnalisé "hh" (plus n'importe quel nombre de spécificateurs "h" supplémentaires)
représente l'heure sous la forme d'un nombre compris entre 01 et 12 ; autrement dit, l'heure est représentée au
format de 12 heures qui compte les heures entières depuis minuit ou midi. Une heure après minuit se présente
de la même manière que la même heure après midi. L'heure n'est pas arrondie et une heure à un seul chiffre est
mise en forme avec un zéro non significatif. Par exemple, si on considère l'heure 5:43 du matin ou de l'après-
midi, ce spécificateur de format affiche "05".
L'exemple suivant inclut le spécificateur de format personnalisé "hh" dans une chaîne de format personnalisée.

DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01 du.
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01.50 du.

Dim date1 As Date


date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01 du.
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01.50 du.

Retour au tableau

Spécificateur de format personnalisé "H"


Le spécificateur de format personnalisé "H" représente l'heure sous la forme d'un nombre compris entre 0
et 23 ; autrement dit, l'heure est représentée au format de 24 heures de base zéro qui compte les heures depuis
minuit. Une heure à un seul chiffre est mise en forme sans zéro non significatif.
Si le spécificateur de format « H » est utilisé sans autre spécificateur de format personnalisé, il est interprété
comme un spécificateur de format de date et d’heure standard et lève une FormatException. Pour plus
d’informations sur l’utilisation d’un seul spécificateur de format, voir Utiliser des spécificateurs de format
personnalisés uniques plus loin dans cette rubrique.
L'exemple suivant inclut le spécificateur de format personnalisé "H" dans une chaîne de format personnalisée.

DateTime date1 = new DateTime(2008, 1, 1, 6, 9, 1);


Console.WriteLine(date1.ToString("H:mm:ss",
CultureInfo.InvariantCulture));
// Displays 6:09:01

Dim date1 As Date = #6:09:01AM#


Console.WriteLine(date1.ToString("H:mm:ss", _
CultureInfo.InvariantCulture))
' Displays 6:09:01
Retour au tableau

Spécificateur de format personnalisé "HH"


Le spécificateur de format personnalisé "HH" (plus n'importe quel nombre de spécificateurs "H"
supplémentaires) représente l'heure sous la forme d'un nombre compris entre 00 et 23 ; autrement dit, l'heure
est représentée au format de 24 heures de base zéro qui compte les heures depuis minuit. Une heure à un seul
chiffre est mise en forme avec un zéro non significatif.
L'exemple suivant inclut le spécificateur de format personnalisé "HH" dans une chaîne de format personnalisée.

DateTime date1 = new DateTime(2008, 1, 1, 6, 9, 1);


Console.WriteLine(date1.ToString("HH:mm:ss",
CultureInfo.InvariantCulture));
// Displays 06:09:01

Dim date1 As Date = #6:09:01AM#


Console.WriteLine(date1.ToString("HH:mm:ss", _
CultureInfo.InvariantCulture))
' Displays 06:09:01

Retour au tableau

Le spécificateur de format personnalisé « K »


Le spécificateur de format personnalisé "K" représente les informations de fuseau horaire d'une valeur de date
et d'heure. Lorsque ce spécificateur de format est utilisé avec les valeurs DateTime, la chaîne de résultat est
définie par la valeur de la propriété DateTime.Kind :
Pour le fuseau horaire local (une valeur de propriété DateTime.Kind de DateTimeKind.Local), ce
spécificateur est équivalent au spécificateur "zzz" et produit une chaîne de résultat contenant l'offset local
par rapport au temps universel coordonné (UTC, Universal Time Coordinated) ; par exemple, "-07:00".
Pour une heure UTC (une valeur de propriété DateTime.Kind de DateTimeKind.Utc), la chaîne de résultat
inclut un caractère "Z" pour représenter une date UTC.
Pour une heure d'un fuseau horaire non spécifié (une heure dont la propriété DateTime.Kind a la valeur
DateTimeKind.Unspecified), le résultat est équivalent à String.Empty.
Pour les valeurs DateTimeOffset, le spécificateur de format "K" est équivalent au spécificateur de format "zzz", et
produit une chaîne de résultat contenant l'offset de la valeur DateTimeOffset par rapport à l'heure UTC.
Si le spécificateur de format « K » est utilisé sans autre spécificateur de format personnalisé, il est interprété
comme un spécificateur de format de date et d’heure standard et lève une FormatException. Pour plus
d’informations sur l’utilisation d’un seul spécificateur de format, voir Utiliser des spécificateurs de format
personnalisés uniques plus loin dans cette rubrique.
L'exemple suivant affiche la chaîne qui résulte de l'utilisation du spécificateur de format personnalisé "K" avec
différentes valeurs DateTime et DateTimeOffset sur un système situé dans le fuseau horaire Pacifique (États-
Unis).
Console.WriteLine(DateTime.Now.ToString("%K"));
// Displays -07:00
Console.WriteLine(DateTime.UtcNow.ToString("%K"));
// Displays Z
Console.WriteLine("'{0}'",
DateTime.SpecifyKind(DateTime.Now,
DateTimeKind.Unspecified).ToString("%K"));
// Displays ''
Console.WriteLine(DateTimeOffset.Now.ToString("%K"));
// Displays -07:00
Console.WriteLine(DateTimeOffset.UtcNow.ToString("%K"));
// Displays +00:00
Console.WriteLine(new DateTimeOffset(2008, 5, 1, 6, 30, 0,
new TimeSpan(5, 0, 0)).ToString("%K"));
// Displays +05:00

Console.WriteLine(Date.Now.ToString("%K"))
' Displays -07:00
Console.WriteLine(Date.UtcNow.ToString("%K"))
' Displays Z
Console.WriteLine("'{0}'", _
Date.SpecifyKind(Date.Now, _
DateTimeKind.Unspecified). _
ToString("%K"))
' Displays ''
Console.WriteLine(DateTimeOffset.Now.ToString("%K"))
' Displays -07:00
Console.WriteLine(DateTimeOffset.UtcNow.ToString("%K"))
' Displays +00:00
Console.WriteLine(New DateTimeOffset(2008, 5, 1, 6, 30, 0, _
New TimeSpan(5, 0, 0)). _
ToString("%K"))
' Displays +05:00

Retour au tableau

Spécificateur de format personnalisé "m"


Le spécificateur de format personnalisé "m" représente la minute sous la forme d'un nombre compris entre 0
et 59. La minute représente les minutes entières qui se sont écoulées depuis la dernière heure. Une minute à un
seul chiffre est mise en forme sans zéro non significatif.
Si le spécificateur de format « m » est utilisé sans autre spécificateur de format personnalisé, il est interprété
comme le spécificateur de format de date et d’heure standard « m ». Pour plus d’informations sur l’utilisation
d’un seul spécificateur de format, voir Utiliser des spécificateurs de format personnalisés uniques plus loin dans
cette rubrique.
L'exemple suivant inclut le spécificateur de format personnalisé "m" dans une chaîne de format personnalisée.
DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1 µ
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1.5 µ

Dim date1 As Date


date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1 µ
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1.5 µ

Retour au tableau

Spécificateur de format personnalisé "mm"


Le spécificateur de format personnalisé "mm" (plus n'importe quel nombre de spécificateurs "m"
supplémentaires) représente la minute sous la forme d'un nombre compris entre 00 et 59. La minute représente
les minutes entières qui se sont écoulées depuis la dernière heure. Une minute à un seul chiffre est mise en
forme avec un zéro non significatif.
L'exemple suivant inclut le spécificateur de format personnalisé "mm" dans une chaîne de format personnalisée.

DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01 du.
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01.50 du.
Dim date1 As Date
date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01 du.
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01.50 du.

Retour au tableau

Spécificateur de format personnalisé "M"


Le spécificateur de format personnalisé "M" représente le mois comme un nombre compris entre 1 et 12 (ou
entre 1 et 13 pour les calendriers de 13 mois). Un mois à un seul chiffre est mis en forme sans zéro non
significatif.
Si le spécificateur de format « M » est utilisé sans autre spécificateur de format personnalisé, il est interprété
comme le spécificateur de format de date et d’heure standard « M ». Pour plus d’informations sur l’utilisation
d’un seul spécificateur de format, voir Utiliser des spécificateurs de format personnalisés uniques plus loin dans
cette rubrique.
L'exemple suivant inclut le spécificateur de format personnalisé "M" dans une chaîne de format personnalisée.

DateTime date1 = new DateTime(2008, 8, 18);


Console.WriteLine(date1.ToString("(M) MMM, MMMM",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays (8) Aug, August
Console.WriteLine(date1.ToString("(M) MMM, MMMM",
CultureInfo.CreateSpecificCulture("nl-NL")));
// Displays (8) aug, augustus
Console.WriteLine(date1.ToString("(M) MMM, MMMM",
CultureInfo.CreateSpecificCulture("lv-LV")));
// Displays (8) Aug, augusts

Dim date1 As Date = #8/18/2008#


Console.WriteLine(date1.ToString("(M) MMM, MMMM", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays (8) Aug, August
Console.WriteLine(date1.ToString("(M) MMM, MMMM", _
CultureInfo.CreateSpecificCulture("nl-NL")))
' Displays (8) aug, augustus
Console.WriteLine(date1.ToString("(M) MMM, MMMM", _
CultureInfo.CreateSpecificCulture("lv-LV")))
' Displays (8) Aug, augusts

Retour au tableau

Spécificateur de format personnalisé "MM"


Le spécificateur de format personnalisé "MM" représente le mois comme un nombre compris entre 01 et 12 (ou
entre 01 et 13 pour les calendriers de 13 mois). Un mois à un seul chiffre est mis en forme avec un zéro non
significatif.
L'exemple suivant inclut le spécificateur de format personnalisé "MM" dans une chaîne de format personnalisée.

DateTime date1 = new DateTime(2008, 1, 2, 6, 30, 15);

Console.WriteLine(date1.ToString("dd, MM",
CultureInfo.InvariantCulture));
// 02, 01

Dim date1 As Date = #1/2/2008 6:30:15AM#

Console.WriteLine(date1.ToString("dd, MM", _
CultureInfo.InvariantCulture))
' 02, 01

Retour au tableau

Spécificateur de format personnalisé "MMM"


Le spécificateur de format personnalisé "MMM" représente le nom abrégé du mois. Le nom abrégé localisé du
mois est récupéré de la propriété DateTimeFormatInfo.AbbreviatedMonthNames de la culture actuelle ou
spécifiée.
L'exemple suivant inclut le spécificateur de format personnalisé "MMM" dans une chaîne de format
personnalisée.

DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15);

Console.WriteLine(date1.ToString("ddd d MMM",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Fri 29 Aug
Console.WriteLine(date1.ToString("ddd d MMM",
CultureInfo.CreateSpecificCulture("fr-FR")));
// Displays ven. 29 août

Dim date1 As Date = #08/29/2008 7:27:15PM#

Console.WriteLine(date1.ToString("ddd d MMM", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Fri 29 Aug
Console.WriteLine(date1.ToString("ddd d MMM", _
CultureInfo.CreateSpecificCulture("fr-FR")))
' Displays ven. 29 août

Retour au tableau

Le spécificateur de format personnalisé « MMMM »


Le spécificateur de format personnalisé "MMMM" représente le nom complet du mois. Le nom localisé du mois
est récupéré de la propriété DateTimeFormatInfo.MonthNames de la culture actuelle ou spécifiée.
L'exemple suivant inclut le spécificateur de format personnalisé "MMMM" dans une chaîne de format
personnalisée.
DateTime date1 = new DateTime(2008, 8, 29, 19, 27, 15);

Console.WriteLine(date1.ToString("dddd dd MMMM",
CultureInfo.CreateSpecificCulture("en-US")));
// Displays Friday 29 August
Console.WriteLine(date1.ToString("dddd dd MMMM",
CultureInfo.CreateSpecificCulture("it-IT")));
// Displays venerdì 29 agosto

Dim date1 As Date = #08/29/2008 7:27:15PM#

Console.WriteLine(date1.ToString("dddd dd MMMM", _
CultureInfo.CreateSpecificCulture("en-US")))
' Displays Friday 29 August
Console.WriteLine(date1.ToString("dddd dd MMMM", _
CultureInfo.CreateSpecificCulture("it-IT")))
' Displays venerdì 29 agosto

Retour au tableau

Spécificateur de format personnalisé "s"


Le spécificateur de format personnalisé "s" représente les secondes sous la forme d'un nombre compris entre 0
et 59. Le résultat représente les secondes entières qui se sont écoulées depuis la dernière minute. Une seconde à
un seul chiffre est mise en forme sans zéro non significatif.
Si le spécificateur de format « s » est utilisé sans autre spécificateur de format personnalisé, il est interprété
comme le spécificateur de format de date et d’heure standard « s ». Pour plus d’informations sur l’utilisation
d’un seul spécificateur de format, voir Utiliser des spécificateurs de format personnalisés uniques plus loin dans
cette rubrique.
L'exemple suivant inclut le spécificateur de format personnalisé "s" dans une chaîne de format personnalisée.

DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1 µ
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1.5 µ
Dim date1 As Date
date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1 µ
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1.5 µ

Retour au tableau

Spécificateur de format personnalisé "SS"


Le spécificateur de format personnalisé "ss" (plus n'importe quel nombre de spécificateurs "s" supplémentaires)
représente les secondes sous la forme d'un nombre compris entre 00 et 59. Le résultat représente les secondes
entières qui se sont écoulées depuis la dernière minute. Une seconde à un seul chiffre est mise en forme avec un
zéro non significatif.
L'exemple suivant inclut le spécificateur de format personnalisé "ss" dans une chaîne de format personnalisée.

DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01 du.
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01.50 du.

Dim date1 As Date


date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01 du.
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01.50 du.

Retour au tableau
Spécificateur de format personnalisé "t"
Le spécificateur de format personnalisé "t" représente le premier caractère de l'indicateur AM/PM. L'indicateur
localisé approprié est récupéré de la propriété DateTimeFormatInfo.AMDesignator ou
DateTimeFormatInfo.PMDesignator de la culture actuelle ou spécifique. L'indicateur AM est utilisé pour toutes
les heures comprises entre 0:00:00 (minuit) et 11:59:59.999. L'indicateur PM est utilisé pour toutes les heures
comprises entre 12:00:00 (midi) et 23:59:59.999.
Si le spécificateur de format « t » est utilisé sans autre spécificateur de format personnalisé, il est interprété
comme le spécificateur de format de date et d’heure standard « t ». Pour plus d’informations sur l’utilisation
d’un seul spécificateur de format, voir Utiliser des spécificateurs de format personnalisés uniques plus loin dans
cette rubrique.
L'exemple suivant inclut le spécificateur de format personnalisé "t" dans une chaîne de format personnalisée.

DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1 µ
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.InvariantCulture));
// Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t",
CultureInfo.CreateSpecificCulture("el-GR")));
// Displays 6:9:1.5 µ

Dim date1 As Date


date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1 µ
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.InvariantCulture))
' Displays 6:9:1.5 P
Console.WriteLine(date1.ToString("h:m:s.F t", _
CultureInfo.CreateSpecificCulture("el-GR")))
' Displays 6:9:1.5 µ

Retour au tableau

Spécificateur de format personnalisé "TT"


Le spécificateur de format personnalisé "tt" (plus n'importe quel nombre de spécificateurs "t" supplémentaires)
représente l'intégralité de l'indicateur AM/PM. L'indicateur localisé approprié est récupéré de la propriété
DateTimeFormatInfo.AMDesignator ou DateTimeFormatInfo.PMDesignator de la culture actuelle ou spécifique.
L'indicateur AM est utilisé pour toutes les heures comprises entre 0:00:00 (minuit) et 11:59:59.999.
L'indicateur PM est utilisé pour toutes les heures comprises entre 12:00:00 (midi) et 23:59:59.999.
Veillez à utiliser le spécificateur « tt » dans les langues pour lesquelles il est nécessaire de maintenir la distinction
entre le matin (AM) et l’après-midi (PM). Un exemple est illustré par la langue japonaise, pour laquelle les
indicateurs AM/PM se distinguent dans le deuxième caractère au lieu du premier.
L'exemple suivant inclut le spécificateur de format personnalisé "tt" dans une chaîne de format personnalisée.

DateTime date1;
date1 = new DateTime(2008, 1, 1, 18, 9, 1);
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01 du.
date1 = new DateTime(2008, 1, 1, 18, 9, 1, 500);
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.InvariantCulture));
// Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt",
CultureInfo.CreateSpecificCulture("hu-HU")));
// Displays 06:09:01.50 du.

Dim date1 As Date


date1 = #6:09:01PM#
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01 PM
Console.WriteLine(date1.ToString("hh:mm:ss tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01 du.
date1 = New Date(2008, 1, 1, 18, 9, 1, 500)
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.InvariantCulture))
' Displays 06:09:01.50 PM
Console.WriteLine(date1.ToString("hh:mm:ss.ff tt", _
CultureInfo.CreateSpecificCulture("hu-HU")))
' Displays 06:09:01.50 du.

Retour au tableau

Spécificateur de format personnalisé "y"


Le spécificateur de format personnalisé "y" représente l'année sous la forme d'un nombre à un chiffre ou à deux
chiffres. Si l'année comporte plus de deux chiffres, seuls les deux chiffres de poids faible apparaissent dans le
résultat. Si le premier chiffre d'une année sur deux chiffres commence par un zéro (par exemple, 2008), le
nombre est mis en forme sans zéro non significatif.
Si le spécificateur de format « y » est utilisé sans autre spécificateur de format personnalisé, il est interprété
comme le spécificateur de format de date et d’heure standard « y ». Pour plus d’informations sur l’utilisation
d’un seul spécificateur de format, voir Utiliser des spécificateurs de format personnalisés uniques plus loin dans
cette rubrique.
L'exemple suivant inclut le spécificateur de format personnalisé "y" dans une chaîne de format personnalisée.
DateTime date1 = new DateTime(1, 12, 1);
DateTime date2 = new DateTime(2010, 1, 1);
Console.WriteLine(date1.ToString("%y"));
// Displays 1
Console.WriteLine(date1.ToString("yy"));
// Displays 01
Console.WriteLine(date1.ToString("yyy"));
// Displays 001
Console.WriteLine(date1.ToString("yyyy"));
// Displays 0001
Console.WriteLine(date1.ToString("yyyyy"));
// Displays 00001
Console.WriteLine(date2.ToString("%y"));
// Displays 10
Console.WriteLine(date2.ToString("yy"));
// Displays 10
Console.WriteLine(date2.ToString("yyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyyy"));
// Displays 02010

Dim date1 As Date = #12/1/0001#


Dim date2 As Date = #1/1/2010#
Console.WriteLine(date1.ToString("%y"))
' Displays 1
Console.WriteLine(date1.ToString("yy"))
' Displays 01
Console.WriteLine(date1.ToString("yyy"))
' Displays 001
Console.WriteLine(date1.ToString("yyyy"))
' Displays 0001
Console.WriteLine(date1.ToString("yyyyy"))
' Displays 00001
Console.WriteLine(date2.ToString("%y"))
' Displays 10
Console.WriteLine(date2.ToString("yy"))
' Displays 10
Console.WriteLine(date2.ToString("yyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyyy"))
' Displays 02010

Retour au tableau

Spécificateur de format personnalisé "yy"


Le spécificateur de format personnalisé "yy" représente l'année sous la forme d'un nombre à deux chiffres. Si
l'année comporte plus de deux chiffres, seuls les deux chiffres de poids faible apparaissent dans le résultat. Si
l'année à deux chiffres comporte moins de deux chiffres significatifs, le nombre est complété avec des zéros non
significatifs pour qu'il possède deux chiffres.
Dans une opération d'analyse, une année à deux chiffres qui est analysée avec le spécificateur de format
personnalisé "yy" est interprétée sur la base de la propriété Calendar.TwoDigitYearMax du calendrier actuel du
fournisseur de format. L'exemple suivant analyse la représentation sous forme de chaîne d'une date ayant une
année à deux chiffres, en utilisant le calendrier grégorien par défaut de la culture en-US, laquelle représente la
culture actuelle. Il remplace ensuite l'objet CultureInfo de la culture actuelle afin d'utiliser un objet
GregorianCalendar dont la propriété TwoDigitYearMax a été modifiée.
using System;
using System.Globalization;
using System.Threading;

public class Example


{
public static void Main()
{
string fmt = "dd-MMM-yy";
string value = "24-Jan-49";

Calendar cal = (Calendar) CultureInfo.CurrentCulture.Calendar.Clone();


Console.WriteLine("Two Digit Year Range: {0} - {1}",
cal.TwoDigitYearMax - 99, cal.TwoDigitYearMax);

Console.WriteLine("{0:d}", DateTime.ParseExact(value, fmt, null));


Console.WriteLine();

cal.TwoDigitYearMax = 2099;
CultureInfo culture = (CultureInfo) CultureInfo.CurrentCulture.Clone();
culture.DateTimeFormat.Calendar = cal;
Thread.CurrentThread.CurrentCulture = culture;

Console.WriteLine("Two Digit Year Range: {0} - {1}",


cal.TwoDigitYearMax - 99, cal.TwoDigitYearMax);
Console.WriteLine("{0:d}", DateTime.ParseExact(value, fmt, null));
}
}
// The example displays the following output:
// Two Digit Year Range: 1930 - 2029
// 1/24/1949
//
// Two Digit Year Range: 2000 - 2099
// 1/24/2049
Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
Dim fmt As String = "dd-MMM-yy"
Dim value As String = "24-Jan-49"

Dim cal As Calendar = CType(CultureInfo.CurrentCulture.Calendar.Clone(), Calendar)


Console.WriteLine("Two Digit Year Range: {0} - {1}",
cal.TwoDigitYearMax - 99, cal.TwoDigitYearMax)

Console.WriteLine("{0:d}", DateTime.ParseExact(value, fmt, Nothing))


Console.WriteLine()

cal.TwoDigitYearMax = 2099
Dim culture As CultureInfo = CType(CultureInfo.CurrentCulture.Clone(), CultureInfo)
culture.DateTimeFormat.Calendar = cal
Thread.CurrentThread.CurrentCulture = culture

Console.WriteLine("Two Digit Year Range: {0} - {1}",


cal.TwoDigitYearMax - 99, cal.TwoDigitYearMax)
Console.WriteLine("{0:d}", DateTime.ParseExact(value, fmt, Nothing))
End Sub
End Module
' The example displays the following output:
' Two Digit Year Range: 1930 - 2029
' 1/24/1949
'
' Two Digit Year Range: 2000 - 2099
' 1/24/2049

L'exemple suivant inclut le spécificateur de format personnalisé "yy" dans une chaîne de format personnalisé.

DateTime date1 = new DateTime(1, 12, 1);


DateTime date2 = new DateTime(2010, 1, 1);
Console.WriteLine(date1.ToString("%y"));
// Displays 1
Console.WriteLine(date1.ToString("yy"));
// Displays 01
Console.WriteLine(date1.ToString("yyy"));
// Displays 001
Console.WriteLine(date1.ToString("yyyy"));
// Displays 0001
Console.WriteLine(date1.ToString("yyyyy"));
// Displays 00001
Console.WriteLine(date2.ToString("%y"));
// Displays 10
Console.WriteLine(date2.ToString("yy"));
// Displays 10
Console.WriteLine(date2.ToString("yyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyyy"));
// Displays 02010
Dim date1 As Date = #12/1/0001#
Dim date2 As Date = #1/1/2010#
Console.WriteLine(date1.ToString("%y"))
' Displays 1
Console.WriteLine(date1.ToString("yy"))
' Displays 01
Console.WriteLine(date1.ToString("yyy"))
' Displays 001
Console.WriteLine(date1.ToString("yyyy"))
' Displays 0001
Console.WriteLine(date1.ToString("yyyyy"))
' Displays 00001
Console.WriteLine(date2.ToString("%y"))
' Displays 10
Console.WriteLine(date2.ToString("yy"))
' Displays 10
Console.WriteLine(date2.ToString("yyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyyy"))
' Displays 02010

Retour au tableau

Spécificateur de format personnalisé "yyy"


Le spécificateur de format personnalisé "yyy" représente l'année avec un minimum de trois chiffres. Si l'année
comporte plus de trois chiffres significatifs, ils sont inclus dans la chaîne résultante. Si l'année comporte moins
de trois chiffres, le nombre est rempli avec des zéros non significatifs pour produire trois chiffres.

NOTE
Pour le calendrier bouddhiste thaïlandais, qui peut comporter des années à cinq chiffres, ce spécificateur de format affiche
tous les chiffres significatifs.

L'exemple suivant inclut le spécificateur de format personnalisé "yyy" dans une chaîne de format personnalisée.

DateTime date1 = new DateTime(1, 12, 1);


DateTime date2 = new DateTime(2010, 1, 1);
Console.WriteLine(date1.ToString("%y"));
// Displays 1
Console.WriteLine(date1.ToString("yy"));
// Displays 01
Console.WriteLine(date1.ToString("yyy"));
// Displays 001
Console.WriteLine(date1.ToString("yyyy"));
// Displays 0001
Console.WriteLine(date1.ToString("yyyyy"));
// Displays 00001
Console.WriteLine(date2.ToString("%y"));
// Displays 10
Console.WriteLine(date2.ToString("yy"));
// Displays 10
Console.WriteLine(date2.ToString("yyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyyy"));
// Displays 02010
Dim date1 As Date = #12/1/0001#
Dim date2 As Date = #1/1/2010#
Console.WriteLine(date1.ToString("%y"))
' Displays 1
Console.WriteLine(date1.ToString("yy"))
' Displays 01
Console.WriteLine(date1.ToString("yyy"))
' Displays 001
Console.WriteLine(date1.ToString("yyyy"))
' Displays 0001
Console.WriteLine(date1.ToString("yyyyy"))
' Displays 00001
Console.WriteLine(date2.ToString("%y"))
' Displays 10
Console.WriteLine(date2.ToString("yy"))
' Displays 10
Console.WriteLine(date2.ToString("yyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyyy"))
' Displays 02010

Retour au tableau

Spécificateur de format personnalisé "yyyy"


Le spécificateur de format personnalisé "yyyy" représente l'année avec un minimum de quatre chiffres. Si
l'année comporte plus de quatre chiffres significatifs, ils sont inclus dans la chaîne résultante. Si l'année
comporte moins de quatre chiffres, le nombre est rempli à l'aide de zéros non significatifs pour produire quatre
chiffres.

NOTE
Pour le calendrier bouddhiste thaïlandais, qui peut comporter des années à cinq chiffres, ce spécificateur de format affiche
au minimum quatre chiffres.

L'exemple suivant inclut le spécificateur de format personnalisé "yyyy" dans une chaîne de format personnalisée.
DateTime date1 = new DateTime(1, 12, 1);
DateTime date2 = new DateTime(2010, 1, 1);
Console.WriteLine(date1.ToString("%y"));
// Displays 1
Console.WriteLine(date1.ToString("yy"));
// Displays 01
Console.WriteLine(date1.ToString("yyy"));
// Displays 001
Console.WriteLine(date1.ToString("yyyy"));
// Displays 0001
Console.WriteLine(date1.ToString("yyyyy"));
// Displays 00001
Console.WriteLine(date2.ToString("%y"));
// Displays 10
Console.WriteLine(date2.ToString("yy"));
// Displays 10
Console.WriteLine(date2.ToString("yyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyyy"));
// Displays 02010

Dim date1 As Date = #12/1/0001#


Dim date2 As Date = #1/1/2010#
Console.WriteLine(date1.ToString("%y"))
' Displays 1
Console.WriteLine(date1.ToString("yy"))
' Displays 01
Console.WriteLine(date1.ToString("yyy"))
' Displays 001
Console.WriteLine(date1.ToString("yyyy"))
' Displays 0001
Console.WriteLine(date1.ToString("yyyyy"))
' Displays 00001
Console.WriteLine(date2.ToString("%y"))
' Displays 10
Console.WriteLine(date2.ToString("yy"))
' Displays 10
Console.WriteLine(date2.ToString("yyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyyy"))
' Displays 02010

Retour au tableau

Spécificateur de format personnalisé "yyyyy"


Le spécificateur de format personnalisé "yyyyy" (plus tout nombre de spécificateurs "y" supplémentaires)
représente l'année avec au minimum cinq chiffres. Si l'année comporte plus de cinq chiffres significatifs, ils sont
inclus dans la chaîne résultante. Si l'année comporte moins de cinq chiffres, le nombre est rempli avec des zéros
non significatifs pour produire cinq chiffres.
S'il existe des spécificateurs "y" supplémentaires, le nombre est rempli avec autant de zéros non significatifs que
nécessaire pour produire le nombre de spécificateurs "y".
L'exemple suivant inclut le spécificateur de format personnalisé "yyyyy" dans une chaîne de format
personnalisée.
DateTime date1 = new DateTime(1, 12, 1);
DateTime date2 = new DateTime(2010, 1, 1);
Console.WriteLine(date1.ToString("%y"));
// Displays 1
Console.WriteLine(date1.ToString("yy"));
// Displays 01
Console.WriteLine(date1.ToString("yyy"));
// Displays 001
Console.WriteLine(date1.ToString("yyyy"));
// Displays 0001
Console.WriteLine(date1.ToString("yyyyy"));
// Displays 00001
Console.WriteLine(date2.ToString("%y"));
// Displays 10
Console.WriteLine(date2.ToString("yy"));
// Displays 10
Console.WriteLine(date2.ToString("yyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyy"));
// Displays 2010
Console.WriteLine(date2.ToString("yyyyy"));
// Displays 02010

Dim date1 As Date = #12/1/0001#


Dim date2 As Date = #1/1/2010#
Console.WriteLine(date1.ToString("%y"))
' Displays 1
Console.WriteLine(date1.ToString("yy"))
' Displays 01
Console.WriteLine(date1.ToString("yyy"))
' Displays 001
Console.WriteLine(date1.ToString("yyyy"))
' Displays 0001
Console.WriteLine(date1.ToString("yyyyy"))
' Displays 00001
Console.WriteLine(date2.ToString("%y"))
' Displays 10
Console.WriteLine(date2.ToString("yy"))
' Displays 10
Console.WriteLine(date2.ToString("yyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyy"))
' Displays 2010
Console.WriteLine(date2.ToString("yyyyy"))
' Displays 02010

Retour au tableau

Spécificateur de format personnalisé "z"


Avec les valeurs DateTime, le spécificateur de format personnalisé "z" représente l'offset signé du fuseau horaire
du système d'exploitation local par rapport au temps universel coordonné (UTC, Coordinated Universal Time),
mesuré en heures. Il ne reflète pas la valeur de la propriété DateTime.Kind d’une instance. C'est pourquoi il n'est
pas recommandé d'utiliser le spécificateur de format "z" avec les valeurs DateTime.
Avec les valeurs DateTimeOffset, ce spécificateur de format représente l'offset de la valeur DateTimeOffset par
rapport à l'heure UTC, en heures.
L'offset est toujours affiché avec un signe de début. Un signe plus (+) indique les heures avant l'heure UTC, et un
signe moins (-) indique les heures après l'heure UTC. Un offset à un seul chiffre est mis en forme sans zéro non
significatif.
Si le spécificateur de format « z » est utilisé sans autre spécificateur de format personnalisé, il est interprété
comme un spécificateur de format de date et d’heure standard et lève une FormatException. Pour plus
d’informations sur l’utilisation d’un seul spécificateur de format, voir Utiliser des spécificateurs de format
personnalisés uniques plus loin dans cette rubrique.
L'exemple suivant inclut le spécificateur de format personnalisé "z" dans une chaîne de format personnalisée.

DateTime date1 = DateTime.UtcNow;


Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}",
date1));
// Displays -7, -07, -07:00

DateTimeOffset date2 = new DateTimeOffset(2008, 8, 1, 0, 0, 0,


new TimeSpan(6, 0, 0));
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}",
date2));
// Displays +6, +06, +06:00

Dim date1 As Date = Date.UtcNow


Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", _
date1))
' Displays -7, -07, -07:00

Dim date2 As New DateTimeOffset(2008, 8, 1, 0, 0, 0, _


New Timespan(6, 0, 0))
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", _
date2))
' Displays +6, +06, +06:00

Retour au tableau

Spécificateur de format personnalisé "zz"


Avec les valeurs DateTime, le spécificateur de format personnalisé "zz" représente l'offset signé du fuseau
horaire du système d'exploitation local par rapport à l'heure UTC, mesuré en heures. Il ne reflète pas la valeur de
la propriété DateTime.Kind d’une instance. C'est pourquoi il n'est pas recommandé d'utiliser le spécificateur de
format "zz" avec les valeurs DateTime.
Avec les valeurs DateTimeOffset, ce spécificateur de format représente l'offset de la valeur DateTimeOffset par
rapport à l'heure UTC, en heures.
L'offset est toujours affiché avec un signe de début. Un signe plus (+) indique les heures avant l'heure UTC, et un
signe moins (-) indique les heures après l'heure UTC. Un offset à un seul chiffre est mis en forme avec un zéro
non significatif.
L'exemple suivant inclut le spécificateur de format personnalisé "zz" dans une chaîne de format personnalisée.

DateTime date1 = DateTime.UtcNow;


Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}",
date1));
// Displays -7, -07, -07:00

DateTimeOffset date2 = new DateTimeOffset(2008, 8, 1, 0, 0, 0,


new TimeSpan(6, 0, 0));
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}",
date2));
// Displays +6, +06, +06:00
Dim date1 As Date = Date.UtcNow
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", _
date1))
' Displays -7, -07, -07:00

Dim date2 As New DateTimeOffset(2008, 8, 1, 0, 0, 0, _


New Timespan(6, 0, 0))
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", _
date2))
' Displays +6, +06, +06:00

Retour au tableau

Spécificateur de format personnalisé "zzz"


Avec les valeurs DateTime, le spécificateur de format personnalisé "zzz" représente l'offset signé du fuseau
horaire du système d'exploitation local par rapport à l'heure UTC, mesuré en heures et en minutes. Il ne reflète
pas la valeur de la propriété DateTime.Kind d’une instance. C'est pourquoi il n'est pas recommandé d'utiliser le
spécificateur de format "zzz" avec les valeurs DateTime.
Avec les valeurs DateTimeOffset, ce spécificateur de format représente l'offset de la valeur DateTimeOffset par
rapport à l'heure UTC, en heures et en minutes.
L'offset est toujours affiché avec un signe de début. Un signe plus (+) indique les heures avant l'heure UTC, et un
signe moins (-) indique les heures après l'heure UTC. Un offset à un seul chiffre est mis en forme avec un zéro
non significatif.
L'exemple suivant inclut le spécificateur de format personnalisé "zzz" dans une chaîne de format personnalisée.

DateTime date1 = DateTime.UtcNow;


Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}",
date1));
// Displays -7, -07, -07:00

DateTimeOffset date2 = new DateTimeOffset(2008, 8, 1, 0, 0, 0,


new TimeSpan(6, 0, 0));
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}",
date2));
// Displays +6, +06, +06:00

Dim date1 As Date = Date.UtcNow


Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", _
date1))
' Displays -7, -07, -07:00

Dim date2 As New DateTimeOffset(2008, 8, 1, 0, 0, 0, _


New Timespan(6, 0, 0))
Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", _
date2))
' Displays +6, +06, +06:00

Retour au tableau

Spécificateur de format personnalisé " :"


Le spécificateur de format personnalisé ":" représente le séparateur d'heure, lequel est utilisé pour différencier
les heures, les minutes et les secondes. Le séparateur d'heure localisé approprié est récupéré de la propriété
DateTimeFormatInfo.TimeSeparator de la culture actuelle ou spécifiée.
NOTE
Pour modifier le séparateur horaire d'une chaîne de date et heure, spécifiez le caractère de séparation dans un délimiteur
de chaîne littérale. Par exemple, la chaîne de format personnalisée hh'_'dd'_'ss produit une chaîne de résultat dans
laquelle "_" (trait de soulignement) est toujours utilisé comme séparateur horaire. Pour modifier le séparateur horaire de
toutes les dates d'une culture, modifiez la valeur de la propriété DateTimeFormatInfo.TimeSeparator de la culture actuelle,
ou instanciez un objet DateTimeFormatInfo, affectez le caractère à sa propriété TimeSeparator, puis appelez une surcharge
de la méthode de mise en forme qui inclut un paramètre IFormatProvider.

Si le spécificateur de format « : » est utilisé sans autre spécificateur de format personnalisé, il est interprété
comme un spécificateur de format de date et d’heure standard et lève une FormatException. Pour plus
d’informations sur l’utilisation d’un seul spécificateur de format, voir Utiliser des spécificateurs de format
personnalisés uniques plus loin dans cette rubrique.
Retour au tableau

Spécificateur de format personnalisé "/"


Le spécificateur de format personnalisé "/" représente le séparateur de date, lequel est utilisé pour différencier
les années, les mois et les jours. Le séparateur de date localisé approprié est récupéré de la propriété
DateTimeFormatInfo.DateSeparator de la culture actuelle ou spécifiée.

NOTE
Pour modifier le séparateur de date d'une chaîne de date et heure, spécifiez le caractère de séparation dans un délimiteur
de chaîne littérale. Par exemple, la chaîne de format personnalisée mm'/'dd'/'yyyy produit une chaîne de résultat dans
laquelle "/" est toujours utilisé comme séparateur de date. Pour modifier le séparateur de date de toutes les dates d'une
culture, modifiez la valeur de la propriété DateTimeFormatInfo.DateSeparator de la culture actuelle, ou instanciez un objet
DateTimeFormatInfo, affectez le caractère à sa propriété DateSeparator, puis appelez une surcharge de la méthode de
mise en forme qui inclut un paramètre IFormatProvider.

Si le spécificateur de format « / » est utilisé sans autre spécificateur de format personnalisé, il est interprété
comme un spécificateur de format de date et d’heure standard et lève une FormatException. Pour plus
d’informations sur l’utilisation d’un seul spécificateur de format, voir Utiliser des spécificateurs de format
personnalisés uniques plus loin dans cette rubrique.
Retour au tableau

Littéraux de caractère
Les caractères suivants dans une chaîne de format de date et d’heure standard ou personnalisée sont réservés et
sont toujours interprétés comme des caractères de mise en forme ou, dans le cas de ", ', / et \ comme des
caractères spéciaux.

F H K M d

f g h m s

t y z % :

/ " ' \
Tous les autres caractères sont toujours interprétés comme des caractères littéraux et, dans une opération de
mise en forme, sont inclus inchangés dans la chaîne de résultat. Dans une opération d’analyse, ils doivent
correspondre exactement aux caractères présents dans la chaîne d’entrée. La comparaison respecte la casse.
L’exemple suivant inclut les caractères littéraux « PST » (pour Pacific Standard Time, heure normale du Pacifique)
et « PDT » (pour Pacific Daylight Time, heure d’été du Pacifique) pour représenter le fuseau horaire local dans
une chaîne de format. Notez que la chaîne est incluse dans la chaîne de résultat, et qu’une chaîne qui inclut la
chaîne du fuseau horaire local est également analysée avec succès.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
String[] formats = { "dd MMM yyyy hh:mm tt PST",
"dd MMM yyyy hh:mm tt PDT" };
var dat = new DateTime(2016, 8, 18, 16, 50, 0);
// Display the result string.
Console.WriteLine(dat.ToString(formats[1]));

// Parse a string.
String value = "25 Dec 2016 12:00 pm PST";
DateTime newDate;
if (DateTime.TryParseExact(value, formats, null,
DateTimeStyles.None, out newDate))
Console.WriteLine(newDate);
else
Console.WriteLine("Unable to parse '{0}'", value);
}
}
// The example displays the following output:
// 18 Aug 2016 04:50 PM PDT
// 12/25/2016 12:00:00 PM

Imports System.Globalization

Module Example
Public Sub Main()
Dim formats() As String = {"dd MMM yyyy hh:mm tt PST",
"dd MMM yyyy hh:mm tt PDT"}
Dim dat As New Date(2016, 8, 18, 16, 50, 0)
' Display the result string.
Console.WriteLine(dat.ToString(formats(1)))

' Parse a string.


Dim value As String = "25 Dec 2016 12:00 pm PST"
Dim newDate As Date
If Date.TryParseExact(value, formats, Nothing,
DateTimeStyles.None, newDate) Then
Console.WriteLine(newDate)
Else
Console.WriteLine("Unable to parse '{0}'", value)
End If
End Sub
End Module
' The example displays the following output:
' 18 Aug 2016 04:50 PM PDT
' 12/25/2016 12:00:00 PM

Il existe deux façons d’indiquer que les caractères doivent être interprétés comme des caractères littéraux, et non
comme des caractères réservés, pour qu’ils puissent être inclus dans une chaîne de résultat ou analysés
correctement dans une chaîne d’entrée :
En plaçant chaque caractère réservé dans une séquence d’échappement. Pour plus d’informations, consultez
utilisation du caractère d’échappement.
L’exemple suivant inclut les caractères littéraux « pst » (pour Pacific Standard Time, heure normale du Pacifique)
pour représenter le fuseau horaire local dans une chaîne de format. Étant donné que « s » et « t » sont des
chaînes de format personnalisées, les deux caractères doivent être placés dans des séquences d’échappement
pour être interprétés comme des littéraux de caractères.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
String format = "dd MMM yyyy hh:mm tt p\\s\\t";
var dat = new DateTime(2016, 8, 18, 16, 50, 0);
// Display the result string.
Console.WriteLine(dat.ToString(format));

// Parse a string.
String value = "25 Dec 2016 12:00 pm pst";
DateTime newDate;
if (DateTime.TryParseExact(value, format, null,
DateTimeStyles.None, out newDate))
Console.WriteLine(newDate);
else
Console.WriteLine("Unable to parse '{0}'", value);
}
}
// The example displays the following output:
// 18 Aug 2016 04:50 PM PDT
// 12/25/2016 12:00:00 PM

Imports System.Globalization

Module Example
Public Sub Main()
Dim fmt As String = "dd MMM yyyy hh:mm tt p\s\t"
Dim dat As New Date(2016, 8, 18, 16, 50, 0)
' Display the result string.
Console.WriteLine(dat.ToString(fmt))

' Parse a string.


Dim value As String = "25 Dec 2016 12:00 pm pst"
Dim newDate As Date
If Date.TryParseExact(value, fmt, Nothing,
DateTimeStyles.None, newDate) Then
Console.WriteLine(newDate)
Else
Console.WriteLine("Unable to parse '{0}'", value)
End If
End Sub
End Module
' The example displays the following output:
' 18 Aug 2016 04:50 PM pst
' 12/25/2016 12:00:00 PM

En plaçant la totalité de la chaîne littérale entre guillemets ou apostrophes. L’exemple suivant est semblable
au précédent, excepté que « pst » est placé entre guillemets pour indiquer que la totalité de la chaîne
délimitée doit être interprétée comme des littéraux de caractères.
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
String format = "dd MMM yyyy hh:mm tt \"pst\"";
var dat = new DateTime(2016, 8, 18, 16, 50, 0);
// Display the result string.
Console.WriteLine(dat.ToString(format));

// Parse a string.
String value = "25 Dec 2016 12:00 pm pst";
DateTime newDate;
if (DateTime.TryParseExact(value, format, null,
DateTimeStyles.None, out newDate))
Console.WriteLine(newDate);
else
Console.WriteLine("Unable to parse '{0}'", value);
}
}
// The example displays the following output:
// 18 Aug 2016 04:50 PM PDT
// 12/25/2016 12:00:00 PM

Imports System.Globalization

Module Example
Public Sub Main()
Dim fmt As String = "dd MMM yyyy hh:mm tt ""pst"""
Dim dat As New Date(2016, 8, 18, 16, 50, 0)
' Display the result string.
Console.WriteLine(dat.ToString(fmt))

' Parse a string.


Dim value As String = "25 Dec 2016 12:00 pm pst"
Dim newDate As Date
If Date.TryParseExact(value, fmt, Nothing,
DateTimeStyles.None, newDate) Then
Console.WriteLine(newDate)
Else
Console.WriteLine("Unable to parse '{0}'", value)
End If
End Sub
End Module
' The example displays the following output:
' 18 Aug 2016 04:50 PM pst
' 12/25/2016 12:00:00 PM

Notes
Utilisation de spécificateurs de format personnalisés uniques
Une chaîne de format de date et d'heure personnalisée se compose d'au moins deux caractères. Les méthodes
de mise en forme de la date et de l'heure interprètent toute chaîne à un caractère comme une chaîne de format
de date et d'heure standard. Si elles ne reconnaissent pas le caractère comme un spécificateur de format valide,
elles lèvent une FormatException. Par exemple, une chaîne de format qui se compose uniquement du
spécificateur "h" est interprétée comme une chaîne de format de date et d'heure standard. Cependant, dans ce
cas particulier, une exception est levée, car il n'existe pas spécificateur de format de date et d'heure "h" standard.
Pour utiliser tout spécificateur de format de date et d'heure personnalisé comme seul spécificateur dans une
chaîne de format (autrement dit, utiliser le spécificateur de format personnalisé "d", "f", "F", "g", "h", "H", "K", "m",
"M", "s", "t", "y", "z", ":" ou "/" tout seul), incluez un espace avant ou après le spécificateur, ou incluez un
spécificateur de format pourcentage ("%") avant le spécificateur de date et d'heure personnalisé unique.
Par exemple », " %h" " est interprété en tant que chaîne de format de date et d'heure personnalisée qui affiche
l'heure représentée par la valeur de date et d'heure actuelle. Vous pouvez également utiliser la chaîne de
format "h" ou "h", bien que cela inclue un espace avec l'heure dans la chaîne de résultat. L'exemple suivant
illustre ces trois chaînes de format.

DateTime dat1 = new DateTime(2009, 6, 15, 13, 45, 0);

Console.WriteLine("'{0:%h}'", dat1);
Console.WriteLine("'{0: h}'", dat1);
Console.WriteLine("'{0:h }'", dat1);
// The example displays the following output:
// '1'
// ' 1'
// '1 '

Dim dat1 As Date = #6/15/2009 1:45PM#

Console.WriteLine("'{0:%h}'", dat1)
Console.WriteLine("'{0: h}'", dat1)
Console.WriteLine("'{0:h }'", dat1)
' The example displays the following output:
' '1'
' ' 1'
' '1 '

Utiliser le caractère d’échappement


Les caractères "d", "f", "F", "g", "h", "H", "K", "m", "M", "s", "t", "y", "z", ":" ou "/" dans une chaîne de format sont
interprétés comme des spécificateurs de format personnalisés plutôt que comme des caractères littéraux. Pour
éviter qu’un caractère soit interprété comme un spécificateur de format, vous pouvez le faire précéder d’une
barre oblique inverse (\), qui est le caractère d’échappement. Le caractère d'échappement signifie que le
caractère suivant est un caractère littéral qui doit être inclus inchangé dans la chaîne de résultat.
Pour inclure une barre oblique inverse dans une chaîne de résultat, vous devez la placer dans une séquence
d'échappement avec une autre barre oblique inverse ( \\ ).

NOTE
Certains compilateurs, tels que les compilateurs C++ et C#, peuvent également interpréter une barre oblique inverse
unique comme un caractère d'échappement. Pour garantir l'interprétation correcte d'une chaîne lors de la mise en forme,
vous pouvez utiliser le caractère littéral de chaîne textuel(le caractère @) avant la chaîne en C#, ou ajouter une autre barre
oblique inverse avant chaque barre oblique inverse en C# et C++. L'exemple C# suivant illustre ces deux approches.

L'exemple suivant utilise le caractère d'échappement pour empêcher l'opération de mise en forme d'interpréter
les caractères "h" et "m" comme des spécificateurs de format.
DateTime date = new DateTime(2009, 06, 15, 13, 45, 30, 90);
string fmt1 = "h \\h m \\m";
string fmt2 = @"h \h m \m";

Console.WriteLine("{0} ({1}) -> {2}", date, fmt1, date.ToString(fmt1));


Console.WriteLine("{0} ({1}) -> {2}", date, fmt2, date.ToString(fmt2));
// The example displays the following output:
// 6/15/2009 1:45:30 PM (h \h m \m) -> 1 h 45 m
// 6/15/2009 1:45:30 PM (h \h m \m) -> 1 h 45 m

Dim date1 As Date = #6/15/2009 13:45#


Dim fmt As String = "h \h m \m"

Console.WriteLine("{0} ({1}) -> {2}", date1, fmt, date1.ToString(fmt))


' The example displays the following output:
' 6/15/2009 1:45:00 PM (h \h m \m) -> 1 h 45 m

Paramètres du panneau de configuration


Les paramètres Options régionales et linguistiques du Panneau de configuration influencent la chaîne de
résultat produite par une opération de mise en forme qui inclut une grande partie des spécificateurs de format
de date et d’heure personnalisés. Ces paramètres sont utilisés pour initialiser l'objet DateTimeFormatInfo
associé à la culture du thread en cours qui fournit des valeurs utilisées pour indiquer la mise en forme. Les
ordinateurs qui utilisent des paramètres différents génèrent des chaînes de résultat différentes.
De plus, si vous utilisez le constructeur CultureInfo(String) pour instancier un nouvel objet CultureInfo qui
représente la même culture que la culture système en cours, toutes les personnalisations établies par l'élément
Options régionales et linguistiques du Panneau de configuration seront appliquées au nouvel objet
CultureInfo . Vous pouvez utiliser le constructeur CultureInfo(String, Boolean) pour créer un objet CultureInfo qui
ne reflète pas les personnalisations d’un système.
Propriétés DateTimeFormatInfo
La mise en forme dépend des propriétés de l'objet DateTimeFormatInfo actif, qui est fourni implicitement par la
culture actuelle du thread ou explicitement par le paramètre IFormatProvider de la méthode qui appelle la mise
en forme. Pour le paramètre IFormatProvider, vous devez spécifier un objet CultureInfo qui représente une
culture, ou un objet DateTimeFormatInfo.
La chaîne de résultat produite par la plupart des spécificateurs de format de date et d'heure personnalisés
dépend également des propriétés de l'objet DateTimeFormatInfo actif. Votre application peut modifier le résultat
produit par certains spécificateurs de format de date et d'heure personnalisés en modifiant la propriété
DateTimeFormatInfo correspondante. Par exemple, le spécificateur de format "ddd" ajoute à la chaîne de résultat
le nom abrégé d'un jour de la semaine trouvé dans le tableau de chaînes AbbreviatedDayNames. De la même
façon, le spécificateur de format "MMMM" ajoute à la chaîne de résultat un nom de mois complet trouvé dans le
tableau de chaînes MonthNames.

Voir aussi
System.DateTime
System.IFormatProvider
Mise en forme des types
Chaînes de format de date et d’heure standard
Exemple : utilitaire de mise en forme .NET Core WinForms (C#)
Exemple : utilitaire de mise en forme .NET Core WinForms (Visual Basic)
Chaînes de format TimeSpan standard
18/07/2020 • 15 minutes to read • Edit Online

Une chaîne de format TimeSpan standard utilise un spécificateur de format pour définir la représentation textuelle
d'une valeur TimeSpan qui résulte d'une opération de mise en forme. Toute chaîne de format contenant plusieurs
caractères alphabétiques, y compris un espace blanc, est interprétée comme une chaîne de format TimeSpan
personnalisée. Pour plus d’informations, consultez chaînes de format TimeSpan personnalisées .
Les représentations sous forme de chaîne de valeurs TimeSpan sont produites par des appels aux surcharges de la
méthode TimeSpan.ToString, ainsi que par les méthodes qui prennent en charge la mise en forme composite,
telles que String.Format. Pour plus d’informations, consultez Mise en forme des types et Mise en forme composite.
L'exemple suivant illustre l'utilisation de chaînes de format standard dans des opérations de mise en forme.

using System;

public class Example


{
public static void Main()
{
TimeSpan duration = new TimeSpan(1, 12, 23, 62);
string output = "Time of Travel: " + duration.ToString("c");
Console.WriteLine(output);

Console.WriteLine("Time of Travel: {0:c}", duration);


}
}
// The example displays the following output:
// Time of Travel: 1.12:24:02
// Time of Travel: 1.12:24:02

Module Example
Public Sub Main()
Dim duration As New TimeSpan(1, 12, 23, 62)
Dim output As String = "Time of Travel: " + duration.ToString("c")
Console.WriteLine(output)

Console.WriteLine("Time of Travel: {0:c}", duration)


End Sub
End Module
' The example displays the following output:
' Time of Travel: 1.12:24:02
' Time of Travel: 1.12:24:02

Les chaînes de format TimeSpan standard sont également utilisées par les méthodes TimeSpan.ParseExact et
TimeSpan.TryParseExact pour définir le format requis des chaînes d'entrée pour les opérations d'analyse.
(L’analyse convertit la représentation sous forme de chaîne d’une valeur en cette valeur.) L’exemple suivant illustre
l’utilisation de chaînes de format standard dans les opérations d’analyse.
using System;

public class Example


{
public static void Main()
{
string value = "1.03:14:56.1667";
TimeSpan interval;
try {
interval = TimeSpan.ParseExact(value, "c", null);
Console.WriteLine("Converted '{0}' to {1}", value, interval);
}
catch (FormatException) {
Console.WriteLine("{0}: Bad Format", value);
}
catch (OverflowException) {
Console.WriteLine("{0}: Out of Range", value);
}

if (TimeSpan.TryParseExact(value, "c", null, out interval))


Console.WriteLine("Converted '{0}' to {1}", value, interval);
else
Console.WriteLine("Unable to convert {0} to a time interval.",
value);
}
}
// The example displays the following output:
// Converted '1.03:14:56.1667' to 1.03:14:56.1667000
// Converted '1.03:14:56.1667' to 1.03:14:56.1667000

Module Example
Public Sub Main()
Dim value As String = "1.03:14:56.1667"
Dim interval As TimeSpan
Try
interval = TimeSpan.ParseExact(value, "c", Nothing)
Console.WriteLine("Converted '{0}' to {1}", value, interval)
Catch e As FormatException
Console.WriteLine("{0}: Bad Format", value)
Catch e As OverflowException
Console.WriteLine("{0}: Out of Range", value)
End Try

If TimeSpan.TryParseExact(value, "c", Nothing, interval) Then


Console.WriteLine("Converted '{0}' to {1}", value, interval)
Else
Console.WriteLine("Unable to convert {0} to a time interval.",
value)
End If
End Sub
End Module
' The example displays the following output:
' Converted '1.03:14:56.1667' to 1.03:14:56.1667000
' Converted '1.03:14:56.1667' to 1.03:14:56.1667000

Le tableau suivant répertorie les spécificateurs de format d'intervalle de temps standard.

SP ÉC IF IC AT EUR DE F O RM AT NOM DESC RIP T IO N EXEM P L ES


SP ÉC IF IC AT EUR DE F O RM AT NOM DESC RIP T IO N EXEM P L ES

"c" Format constant (invariant) Ce spécificateur ne dépend TimeSpan.Zero ->


pas de la culture. Son format 00:00:00
est le suivant :
[-] New TimeSpan(0, 0, 30,
[d'.']hh':'mm':'ss['.'fffffff]0)
. -> 00:30:00

(les chaînes de format "t" et New TimeSpan(3, 17, 25,


"T" produisent les mêmes 30, 500)
résultats) -> 3.17:25:30.5000000

Pour plus d’informations,


consultez Spécificateur de
format constant ("c").

"g" Format court général Ce spécificateur retourne New TimeSpan(1, 3, 16,


uniquement ce qui est 50, 500)
nécessaire. Il dépend de la -> 1:3:16:50.5 (en-US)
culture et prend la forme
suivante : New TimeSpan(1, 3, 16,
50, 500)
[-]
[d':']h':'mm':'ss[.FFFFFFF] -> 1:3:16:50,5 (fr-FR)
.
New TimeSpan(1, 3, 16,
50, 599)
Pour plus d’informations,
consultez Spécificateur de -> 1:3:16:50.599 (en-US)
format court général ("g").
New TimeSpan(1, 3, 16,
50, 599)
-> 1:3:16:50,599 (fr-FR)

"G" Format long général Ce spécificateur retourne New TimeSpan(18, 30, 0)


toujours les jours et sept -> 0:18:30:00.0000000 (en-
chiffres fractionnaires. Il US)
dépend de la culture et
prend la forme suivante : New TimeSpan(18, 30, 0)
[- -> 0:18:30:00,0000000 (fr-
]d':'hh':'mm':'ss.fffffff
FR)
.

Pour plus d’informations,


consultez Spécificateur de
format long général ("G").

Spécificateur de format constant ("c")


Le spécificateur de format "c" retourne la représentation sous forme de chaîne d'une valeur TimeSpan au format
suivant :
[-][d.]hh:mm:ss[.fffffff]
Les éléments entre crochets ([ et ]) sont facultatifs. Le point (.) et les deux-points (:) sont des symboles littéraux. Le
tableau suivant décrit les éléments restants.

ÉL ÉM EN T DESC RIP T IO N

- Signe négatif facultatif, qui indique un intervalle de temps


négatif.
ÉL ÉM EN T DESC RIP T IO N

d Nombre facultatif de jours, sans zéros non significatifs.

hh Nombre d'heures, allant de "00" à "23".

mm Nombre de minutes, allant de "00" à "59".

sécurité Nombre de secondes, allant de "0" à "59".

fffffff Partie fractionnaire facultative d'une seconde. Sa valeur peut


varier de "0000001" (une graduation ou un dix-millionième
de seconde) à "9999999" (9 999 999 dix-millionièmes de
seconde ou une seconde moins une graduation).

Contrairement aux spécificateurs de format "g" et "G", le spécificateur de format "c" ne dépend pas de la culture. Il
produit la représentation sous forme de chaîne d’une valeur TimeSpan qui est invariante et commune à toutes les
versions du .NET Framework antérieures à .NET Framework 4. "c" est la chaîne de format TimeSpan par défaut. La
méthode TimeSpan.ToString() met en forme une valeur d'intervalle de temps à l'aide de la chaîne de format "c".

NOTE
TimeSpan prend également en charge les chaînes de format standard "t" et "T", dont le comportement est identique à celui
de la chaîne de format standard "c".

L'exemple suivant instancie deux objets TimeSpan, les utilise pour effectuer des opérations arithmétiques, puis
affiche le résultat. Dans chaque cas, il utilise la mise en forme composite pour afficher la valeur TimeSpan à l'aide
du spécificateur de format "c".

using System;

public class Example


{
public static void Main()
{
TimeSpan interval1, interval2;
interval1 = new TimeSpan(7, 45, 16);
interval2 = new TimeSpan(18, 12, 38);

Console.WriteLine("{0:c} - {1:c} = {2:c}", interval1,


interval2, interval1 - interval2);
Console.WriteLine("{0:c} + {1:c} = {2:c}", interval1,
interval2, interval1 + interval2);

interval1 = new TimeSpan(0, 0, 1, 14, 365);


interval2 = TimeSpan.FromTicks(2143756);
Console.WriteLine("{0:c} + {1:c} = {2:c}", interval1,
interval2, interval1 + interval2);
}
}
// The example displays the following output:
// 07:45:16 - 18:12:38 = -10:27:22
// 07:45:16 + 18:12:38 = 1.01:57:54
// 00:01:14.3650000 + 00:00:00.2143756 = 00:01:14.5793756
Module Example
Public Sub Main()
Dim interval1, interval2 As TimeSpan
interval1 = New TimeSpan(7, 45, 16)
interval2 = New TimeSpan(18, 12, 38)

Console.WriteLine("{0:c} - {1:c} = {2:c}", interval1,


interval2, interval1 - interval2)
Console.WriteLine("{0:c} + {1:c} = {2:c}", interval1,
interval2, interval1 + interval2)

interval1 = New TimeSpan(0, 0, 1, 14, 365)


interval2 = TimeSpan.FromTicks(2143756)
Console.WriteLine("{0:c} + {1:c} = {2:c}", interval1,
interval2, interval1 + interval2)
End Sub
End Module
' The example displays the following output:
' 07:45:16 - 18:12:38 = -10:27:22
' 07:45:16 + 18:12:38 = 1.01:57:54
' 00:01:14.3650000 + 00:00:00.2143756 = 00:01:14.5793756

Spécificateur de format court général ("g")


Le spécificateur de format "g" TimeSpan retourne la représentation sous forme de chaîne d'une valeur TimeSpan
dans un format compact, en incluant uniquement les éléments qui sont nécessaires. Elle se présente comme suit :
[-][d:]h:mm:ss[.FFFFFFF]
Les éléments entre crochets ([ et ]) sont facultatifs. Le signe deux-points (:) est un symbole littéral. Le tableau
suivant décrit les éléments restants.

ÉL ÉM EN T DESC RIP T IO N

- Signe négatif facultatif, qui indique un intervalle de temps


négatif.

d Nombre facultatif de jours, sans zéros non significatifs.

h Nombre d'heures, allant de "0" à "23", sans zéros non


significatifs.

mm Nombre de minutes, allant de "00" à "59".

sécurité Nombre de secondes, allant de "00" à "59".

. Séparateur des fractions de seconde. Il équivaut à la propriété


NumberDecimalSeparator de la culture spécifiée, sans
substitutions par l'utilisateur.

FFFFFFF Fractions de seconde. Le moins de chiffres possible sont


affichés.

Tout comme le spécificateur de format "G", le spécificateur de format "g" est localisé. Son séparateur de fraction de
seconde est basé sur la propriété NumberDecimalSeparator de la culture actuelle ou de la culture spécifiée.
L'exemple suivant instancie deux objets TimeSpan, les utilise pour effectuer des opérations arithmétiques, puis
affiche le résultat. Dans chaque cas, il utilise la mise en forme composite pour afficher la valeur TimeSpan à l'aide
du spécificateur de format "g". De plus, il met en forme la valeur TimeSpan à l'aide des conventions de format de
la culture système actuelle (dans le cas présent, Anglais - États-Unis ou en-US) et de la culture Français - France
(fr-FR).

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
TimeSpan interval1, interval2;
interval1 = new TimeSpan(7, 45, 16);
interval2 = new TimeSpan(18, 12, 38);

Console.WriteLine("{0:g} - {1:g} = {2:g}", interval1,


interval2, interval1 - interval2);
Console.WriteLine(String.Format(new CultureInfo("fr-FR"),
"{0:g} + {1:g} = {2:g}", interval1,
interval2, interval1 + interval2));

interval1 = new TimeSpan(0, 0, 1, 14, 36);


interval2 = TimeSpan.FromTicks(2143756);
Console.WriteLine("{0:g} + {1:g} = {2:g}", interval1,
interval2, interval1 + interval2);
}
}
// The example displays the following output:
// 7:45:16 - 18:12:38 = -10:27:22
// 7:45:16 + 18:12:38 = 1:1:57:54
// 0:01:14.036 + 0:00:00.2143756 = 0:01:14.2503756

Imports System.Globalization

Module Example
Public Sub Main()
Dim interval1, interval2 As TimeSpan
interval1 = New TimeSpan(7, 45, 16)
interval2 = New TimeSpan(18, 12, 38)

Console.WriteLine("{0:g} - {1:g} = {2:g}", interval1,


interval2, interval1 - interval2)
Console.WriteLine(String.Format(New CultureInfo("fr-FR"),
"{0:g} + {1:g} = {2:g}", interval1,
interval2, interval1 + interval2))

interval1 = New TimeSpan(0, 0, 1, 14, 36)


interval2 = TimeSpan.FromTicks(2143756)
Console.WriteLine("{0:g} + {1:g} = {2:g}", interval1,
interval2, interval1 + interval2)
End Sub
End Module
' The example displays the following output:
' 7:45:16 - 18:12:38 = -10:27:22
' 7:45:16 + 18:12:38 = 1:1:57:54
' 0:01:14.036 + 0:00:00.2143756 = 0:01:14.2503756

Spécificateur de format long général ("G")


Le spécificateur de format "G" TimeSpan retourne la représentation sous forme de chaîne d'une valeur TimeSpan
sous une forme longue comprenant toujours les jours et les fractions de secondes. La chaîne qui résulte du
spécificateur de format standard "G" est au format suivant :
[-] d:hh:mm:SS . fffffff
Les éléments entre crochets ([ et ]) sont facultatifs. Le signe deux-points (:) est un symbole littéral. Le tableau
suivant décrit les éléments restants.

ÉL ÉM EN T DESC RIP T IO N

- Signe négatif facultatif, qui indique un intervalle de temps


négatif.

d Nombre de jours, sans zéros non significatifs.

hh Nombre d'heures, allant de "00" à "23".

mm Nombre de minutes, allant de "00" à "59".

sécurité Nombre de secondes, allant de "00" à "59".

. Séparateur des fractions de seconde. Il équivaut à la propriété


NumberDecimalSeparator de la culture spécifiée, sans
substitutions par l'utilisateur.

fffffff Fractions de seconde.

Tout comme le spécificateur de format "G", le spécificateur de format "g" est localisé. Son séparateur de fraction de
seconde est basé sur la propriété NumberDecimalSeparator de la culture actuelle ou de la culture spécifiée.
L'exemple suivant instancie deux objets TimeSpan, les utilise pour effectuer des opérations arithmétiques, puis
affiche le résultat. Dans chaque cas, il utilise la mise en forme composite pour afficher la valeur TimeSpan à l'aide
du spécificateur de format "G". De plus, il met en forme la valeur TimeSpan à l'aide des conventions de format de
la culture système actuelle (dans le cas présent, Anglais - États-Unis ou en-US) et de la culture Français - France
(fr-FR).
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
TimeSpan interval1, interval2;
interval1 = new TimeSpan(7, 45, 16);
interval2 = new TimeSpan(18, 12, 38);

Console.WriteLine("{0:G} - {1:G} = {2:G}", interval1,


interval2, interval1 - interval2);
Console.WriteLine(String.Format(new CultureInfo("fr-FR"),
"{0:G} + {1:G} = {2:G}", interval1,
interval2, interval1 + interval2));

interval1 = new TimeSpan(0, 0, 1, 14, 36);


interval2 = TimeSpan.FromTicks(2143756);
Console.WriteLine("{0:G} + {1:G} = {2:G}", interval1,
interval2, interval1 + interval2);
}
}
// The example displays the following output:
// 0:07:45:16.0000000 - 0:18:12:38.0000000 = -0:10:27:22.0000000
// 0:07:45:16,0000000 + 0:18:12:38,0000000 = 1:01:57:54,0000000
// 0:00:01:14.0360000 + 0:00:00:00.2143756 = 0:00:01:14.2503756

Imports System.Globalization

Module Example
Public Sub Main()
Dim interval1, interval2 As TimeSpan
interval1 = New TimeSpan(7, 45, 16)
interval2 = New TimeSpan(18, 12, 38)

Console.WriteLine("{0:G} - {1:G} = {2:G}", interval1,


interval2, interval1 - interval2)
Console.WriteLine(String.Format(New CultureInfo("fr-FR"),
"{0:G} + {1:G} = {2:G}", interval1,
interval2, interval1 + interval2))

interval1 = New TimeSpan(0, 0, 1, 14, 36)


interval2 = TimeSpan.FromTicks(2143756)
Console.WriteLine("{0:G} + {1:G} = {2:G}", interval1,
interval2, interval1 + interval2)
End Sub
End Module
' The example displays the following output:
' 0:07:45:16.0000000 - 0:18:12:38.0000000 = -0:10:27:22.0000000
' 0:07:45:16,0000000 + 0:18:12:38,0000000 = 1:01:57:54,0000000
' 0:00:01:14.0360000 + 0:00:00:00.2143756 = 0:00:01:14.2503756

Voir aussi
Mise en forme des types
Chaînes de format TimeSpan personnalisées
Analyse de chaînes
Chaînes de format TimeSpan personnalisées
18/07/2020 • 63 minutes to read • Edit Online

Une chaîne de format TimeSpan définit la représentation sous forme de chaîne d’une valeur TimeSpan qui résulte
d’une opération de mise en forme. Une chaîne de format personnalisée se compose d’un ou de plusieurs
spécificateurs de format TimeSpan personnalisé et d’un nombre quelconque de caractères littéraux. Toute chaîne
qui n’est pas une chaîne de format TimeSpan standard est interprétée comme une TimeSpan chaîne de format
personnalisée.

IMPORTANT
Les spécificateurs de format TimeSpan personnalisés ne comportent pas de symboles de séparation de type espace réservé,
comme ceux qui séparent les jours des heures, les heures des minutes ou les secondes des fractions de seconde. Au lieu de
cela, ces symboles doivent figurer dans la chaîne de format personnalisée comme littéraux de chaîne. Par exemple,
"dd\.hh\:mm" définit un point (.) comme séparateur entre les jours et les heures et un signe deux-points (:) comme
séparateur entre les heures et les minutes.
Les spécificateurs de format TimeSpan personnalisés ne comportent pas non plus de symbole de signe permettant de faire
la distinction entre les intervalles de temps positifs et négatifs. Pour inclure un symbole de signe, vous devez construire une
chaîne de format à l’aide d’une logique conditionnelle. La section autres caractères contient un exemple.

Les représentations sous forme de chaîne de valeurs TimeSpan sont produites par des appels aux surcharges de
la méthode TimeSpan.ToString, ainsi que par les méthodes qui prennent en charge la mise en forme composite,
telles que String.Format. Pour plus d’informations, consultez Mise en forme des types et Mise en forme
composite. L'exemple suivant illustre l'utilisation de chaînes de format personnalisé dans des opérations de mise
en forme.

using System;

public class Example


{
public static void Main()
{
TimeSpan duration = new TimeSpan(1, 12, 23, 62);

string output = null;


output = "Time of Travel: " + duration.ToString("%d") + " days";
Console.WriteLine(output);
output = "Time of Travel: " + duration.ToString(@"dd\.hh\:mm\:ss");
Console.WriteLine(output);

Console.WriteLine("Time of Travel: {0:%d} day(s)", duration);


Console.WriteLine("Time of Travel: {0:dd\\.hh\\:mm\\:ss} days", duration);
}
}
// The example displays the following output:
// Time of Travel: 1 days
// Time of Travel: 01.12:24:02
// Time of Travel: 1 day(s)
// Time of Travel: 01.12:24:02 days
Module Example
Public Sub Main()
Dim duration As New TimeSpan(1, 12, 23, 62)

Dim output As String = Nothing


output = "Time of Travel: " + duration.ToString("%d") + " days"
Console.WriteLine(output)
output = "Time of Travel: " + duration.ToString("dd\.hh\:mm\:ss")
Console.WriteLine(output)

Console.WriteLine("Time of Travel: {0:%d} day(s)", duration)


Console.WriteLine("Time of Travel: {0:dd\.hh\:mm\:ss} days", duration)
End Sub
End Module
' The example displays the following output:
' Time of Travel: 1 days
' Time of Travel: 01.12:24:02
' Time of Travel: 1 day(s)
' Time of Travel: 01.12:24:02 days

Les chaînes de format TimeSpan standard sont également utilisées par les méthodes TimeSpan.ParseExact et
TimeSpan.TryParseExact pour définir le format requis des chaînes d'entrée pour les opérations d'analyse.
(L’analyse convertit la représentation sous forme de chaîne d’une valeur en cette valeur.) L’exemple suivant illustre
l’utilisation de chaînes de format standard dans les opérations d’analyse.

using System;

public class Example


{
public static void Main()
{
string value = null;
TimeSpan interval;

value = "6";
if (TimeSpan.TryParseExact(value, "%d", null, out interval))
Console.WriteLine("{0} --> {1}", value, interval.ToString("c"));
else
Console.WriteLine("Unable to parse '{0}'", value);

value = "16:32.05";
if (TimeSpan.TryParseExact(value, @"mm\:ss\.ff", null, out interval))
Console.WriteLine("{0} --> {1}", value, interval.ToString("c"));
else
Console.WriteLine("Unable to parse '{0}'", value);

value= "12.035";
if (TimeSpan.TryParseExact(value, "ss\\.fff", null, out interval))
Console.WriteLine("{0} --> {1}", value, interval.ToString("c"));
else
Console.WriteLine("Unable to parse '{0}'", value);
}
}
// The example displays the following output:
// 6 --> 6.00:00:00
// 16:32.05 --> 00:16:32.0500000
// 12.035 --> 00:00:12.0350000
Module Example
Public Sub Main()
Dim value As String = Nothing
Dim interval As TimeSpan

value = "6"
If TimeSpan.TryParseExact(value, "%d", Nothing, interval) Then
Console.WriteLine("{0} --> {1}", value, interval.ToString("c"))
Else
Console.WriteLine("Unable to parse '{0}'", value)
End If

value = "16:32.05"
If TimeSpan.TryParseExact(value, "mm\:ss\.ff", Nothing, interval) Then
Console.WriteLine("{0} --> {1}", value, interval.ToString("c"))
Else
Console.WriteLine("Unable to parse '{0}'", value)
End If

value = "12.035"
If TimeSpan.TryParseExact(value, "ss\.fff", Nothing, interval) Then
Console.WriteLine("{0} --> {1}", value, interval.ToString("c"))
Else
Console.WriteLine("Unable to parse '{0}'", value)
End If
End Sub
End Module
' The example displays the following output:
' 6 --> 6.00:00:00
' 16:32.05 --> 00:16:32.0500000
' 12.035 --> 00:00:12.0350000

Le tableau suivant décrit les spécificateurs de format de date et d'heure personnalisé.

SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L E

"d", "%d" Nombre de jours entiers dans new TimeSpan(6, 14, 32, 17, 685):
l’intervalle de temps.
%d --> "6"
Informations supplémentaires :
spécificateur de format personnalisé d\.hh\:mm --> "6.14:32"
"d".

"dd" à "dddddddd" Nombre de jours entiers dans new TimeSpan(6, 14, 32, 17, 685):
l’intervalle de temps, complété avec des
zéros non significatifs en fonction des ddd --> "006"
besoins.
dd\.hh\:mm --> "06.14:32"
Informations supplémentaires :
spécificateurs de format personnalisé
"dd"-"dddddddd".

"h", "%h" Nombre d’heures entières de new TimeSpan(6, 14, 32, 17, 685):
l’intervalle de temps non
comptabilisées dans des jours. Les %h --> "14"
heures à un chiffre n’ont pas de zéro
non significatif. hh\:mm --> "14:32"
Informations supplémentaires :
spécificateur de format personnalisé
"h".
SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L E

"hh" Nombre d’heures entières de new TimeSpan(6, 14, 32, 17, 685):
l’intervalle de temps non
comptabilisées dans des jours. Les hh --> "14"
heures à un chiffre ont un zéro non
significatif. new TimeSpan(6, 8, 32, 17, 685):

Informations supplémentaires : hh --> 08


spécificateur de format personnalisé
"HH".

"m", "%m" Nombre de minutes entières de new TimeSpan(6, 14, 8, 17, 685):
l’intervalle de temps non
comptabilisées dans des jours ou des %m --> "8"
heures. Les minutes à un chiffre n’ont
pas de zéro non significatif. h\:m --> "14:8"
Informations supplémentaires :
spécificateur de format personnalisé
"m".

"mm" Nombre de minutes entières de new TimeSpan(6, 14, 8, 17, 685):


l’intervalle de temps non
comptabilisées dans des jours ou des mm --> "08"
heures. Les minutes à un chiffre ont un
zéro non significatif. new TimeSpan(6, 8, 5, 17, 685):

Informations supplémentaires : d\.hh\:mm\:ss --> 6.08:05:17


spécificateur de format personnalisé
"mm".

"s", "%s" Nombre de secondes entières de TimeSpan.FromSeconds(12.965) :


l’intervalle de temps non
comptabilisées dans des jours, des %s --> 12
heures ou des minutes. Les secondes à
un chiffre n’ont pas de zéro non s\.fff --> 12.965
significatif.

Informations supplémentaires :
spécificateur de format personnalisé "s".

"ss" Nombre de secondes entières de TimeSpan.FromSeconds(6.965) :


l’intervalle de temps non
comptabilisées dans des jours, des ss --> 06
heures ou des minutes. Les secondes à
un chiffre ont un zéro non significatif. ss\.fff --> 06.965
Informations supplémentaires :
spécificateur de format personnalisé
"SS".

"f", "%f" Dixièmes de seconde dans un intervalle TimeSpan.FromSeconds(6.895) :


de temps.
f --> 8
Informations supplémentaires :
spécificateur de format personnalisé "f". ss\.f --> 06.8
SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L E

"ff" Centièmes de seconde dans un TimeSpan.FromSeconds(6.895) :


intervalle de temps.
ff --> 89
Informations supplémentaires :
spécificateur de format personnalisé ss\.ff --> 06.89
"FF".

"fff" Millisecondes dans un intervalle de TimeSpan.FromSeconds(6.895) :


temps.
fff --> 895
Informations supplémentaires :
spécificateur de format personnalisé ss\.fff --> 06.895
"fff".

"ffff" Dix millièmes de seconde dans un TimeSpan.Parse("0:0:6.8954321") :


intervalle de temps.
ffff --> 8954
Informations supplémentaires :
spécificateur de format personnalisé ss\.ffff --> 06.8954
"FFFF".

"fffff" Cent millièmes de seconde dans un TimeSpan.Parse("0:0:6.8954321") :


intervalle de temps.
fffff --> 89543
Informations supplémentaires :
spécificateur de format personnalisé ss\.fffff --> 06.89543
"fffff".

"ffffff" Millionièmes de seconde dans un TimeSpan.Parse("0:0:6.8954321") :


intervalle de temps.
ffffff --> 895432
Informations supplémentaires :
spécificateur de format personnalisé ss\.ffffff --> 06.895432
"FFFFFF".

"fffffff" Dix millionièmes de seconde (ou TimeSpan.Parse("0:0:6.8954321") :


nombre fractionnaire de graduations)
dans un intervalle de temps. fffffff --> 8954321
Informations supplémentaires : ss\.fffffff --> 06.8954321
spécificateur de format personnalisé
"fffffff".

"F", "%F" Dixièmes de seconde dans un intervalle TimeSpan.Parse("00:00:06.32") :


de temps. Rien ne s'affiche si le chiffre
est zéro. %F :3

Informations supplémentaires : TimeSpan.Parse("0:0:3.091") :


spécificateur de format personnalisé
"F". ss\.F : 03.
SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L E

"FF" Centièmes de seconde dans un TimeSpan.Parse("00:00:06.329") :


intervalle de temps. Les éventuels zéros
de fin fractionnaires et doubles zéros FF : 32
ne sont pas affichés.
TimeSpan.Parse("0:0:3.101") :
Informations supplémentaires :
spécificateur de format personnalisé ss\.FF : 03.1
"FF".

"FFF" Millisecondes dans un intervalle de TimeSpan.Parse("00:00:06.3291") :


temps. Les éventuels zéros de fin
fractionnaires ne sont pas affichés. FFF : 329

Plus d’informations : TimeSpan.Parse("0:0:3.1009") :

ss\.FFF : 03.1

"FFFF" Dix millièmes de seconde dans un TimeSpan.Parse("00:00:06.32917") :


intervalle de temps. Les éventuels zéros
de fin fractionnaires ne sont pas FFFFF : 3291
affichés.
TimeSpan.Parse("0:0:3.10009") :
Informations supplémentaires :
spécificateur de format personnalisé ss\.FFFF : 03.1
"FFFF".

"FFFFF" Cent millièmes de seconde dans un TimeSpan.Parse("00:00:06.329179")


intervalle de temps. Les éventuels zéros :
de fin fractionnaires ne sont pas
affichés. FFFFF : 32917

Informations supplémentaires : TimeSpan.Parse("0:0:3.100009") :


spécificateur de format personnalisé
"fffff". ss\.FFFFF : 03.1

"FFFFFF" Millionièmes de seconde dans un TimeSpan.Parse("00:00:06.3291791")


intervalle de temps. Les éventuels zéros :
de fin fractionnaires ne sont pas
affichés. FFFFFF : 329179

Informations supplémentaires : TimeSpan.Parse("0:0:3.1000009") :


spécificateur de format personnalisé
"FFFFFF". ss\.FFFFFF : 03.1

"FFFFFFF" Dix millionièmes de seconde dans un TimeSpan.Parse("00:00:06.3291791")


intervalle de temps. Les éventuels zéros :
de fin fractionnaires et septuples zéros
ne sont pas affichés. FFFFFF : 3291791

Informations supplémentaires : TimeSpan.Parse("0:0:3.1900000") :


spécificateur de format personnalisé
"fffffff". ss\.FFFFFF : 03.19

'chaîne' Délimiteur de chaîne littérale. new TimeSpan(14, 32, 17):

Informations supplémentaires : autres hh':'mm':'ss --> "14:32:17"


caractères.
SP ÉC IF IC AT EUR DE F O RM AT DESC RIP T IO N EXEM P L E

\ Caractère d'échappement. new TimeSpan(14, 32, 17):

Informations supplémentaires : autres hh\:mm\:ss --> "14:32:17"


caractères.

N'importe quel autre caractère Tout autre caractère sans séquence new TimeSpan(14, 32, 17):
d’échappement est interprété comme
un spécificateur de format personnalisé. hh\:mm\:ss --> "14:32:17"
Informations supplémentaires : autres
caractères.

Le spécificateur de format personnalisé « d »


Le spécificateur de format personnalisé "d" affiche la valeur de la propriété TimeSpan.Days, qui représente le
nombre de jours entiers dans l’intervalle de temps. Il affiche le nombre total de jours dans une valeur TimeSpan,
même si la valeur a plusieurs chiffres. Si la valeur de la propriété TimeSpan.Days est zéro, le spécificateur retourne
"0".
Si le spécificateur de format personnalisé « d » est utilisé seul, spécifiez « %d » afin qu’il ne soit pas interprété à
tort comme une chaîne de format standard. L'exemple suivant en est l'illustration.

TimeSpan ts1 = new TimeSpan(16, 4, 3, 17, 250);


Console.WriteLine(ts1.ToString("%d"));
// Displays 16

Dim ts As New TimeSpan(16, 4, 3, 17, 250)


Console.WriteLine(ts.ToString("%d"))
' Displays 16

L’exemple suivant illustre l’utilisation du spécificateur de format personnalisé "d".

TimeSpan ts2 = new TimeSpan(4, 3, 17);


Console.WriteLine(ts2.ToString(@"d\.hh\:mm\:ss"));

TimeSpan ts3 = new TimeSpan(3, 4, 3, 17);


Console.WriteLine(ts3.ToString(@"d\.hh\:mm\:ss"));
// The example displays the following output:
// 0.04:03:17
// 3.04:03:17

Dim ts2 As New TimeSpan(4, 3, 17)


Console.WriteLine(ts2.ToString("d\.hh\:mm\:ss"))

Dim ts3 As New TimeSpan(3, 4, 3, 17)


Console.WriteLine(ts3.ToString("d\.hh\:mm\:ss"))
' The example displays the following output:
' 0.04:03:17
' 3.04:03:17

Retour au tableau

Spécificateurs de format personnalisé "dd"-"dddddddd"


Les spécificateurs de format personnalisé "dd", "ddd", "dddd", "ddddd", "dddddd", "ddddddd" et "dddddddd"
affichent la valeur de la propriété TimeSpan.Days, qui représente le nombre de jours entiers dans l’intervalle de
temps.
La chaîne de sortie comporte un nombre minimal de chiffres spécifié par le nombre de caractères « d » dans le
spécificateur de format ; elle est complétée par des zéros non significatifs si nécessaire. Si les chiffres dans le
nombre de jours dépassent le nombre de caractères « d » dans le spécificateur de format, le nombre total de jours
est affiché dans la chaîne de résultat.
L’exemple suivant utilise ces spécificateurs de format pour afficher la représentation sous forme de chaîne de deux
valeurs TimeSpan. La valeur du composant « jours » du premier intervalle de temps est zéro ; la valeur du
composant « jours » du deuxième est 365.

TimeSpan ts1 = new TimeSpan(0, 23, 17, 47);


TimeSpan ts2 = new TimeSpan(365, 21, 19, 45);

for (int ctr = 2; ctr <= 8; ctr++)


{
string fmt = new String('d', ctr) + @"\.hh\:mm\:ss";
Console.WriteLine("{0} --> {1:" + fmt + "}", fmt, ts1);
Console.WriteLine("{0} --> {1:" + fmt + "}", fmt, ts2);
Console.WriteLine();
}
// The example displays the following output:
// dd\.hh\:mm\:ss --> 00.23:17:47
// dd\.hh\:mm\:ss --> 365.21:19:45
//
// ddd\.hh\:mm\:ss --> 000.23:17:47
// ddd\.hh\:mm\:ss --> 365.21:19:45
//
// dddd\.hh\:mm\:ss --> 0000.23:17:47
// dddd\.hh\:mm\:ss --> 0365.21:19:45
//
// ddddd\.hh\:mm\:ss --> 00000.23:17:47
// ddddd\.hh\:mm\:ss --> 00365.21:19:45
//
// dddddd\.hh\:mm\:ss --> 000000.23:17:47
// dddddd\.hh\:mm\:ss --> 000365.21:19:45
//
// ddddddd\.hh\:mm\:ss --> 0000000.23:17:47
// ddddddd\.hh\:mm\:ss --> 0000365.21:19:45
//
// dddddddd\.hh\:mm\:ss --> 00000000.23:17:47
// dddddddd\.hh\:mm\:ss --> 00000365.21:19:45
Dim ts1 As New TimeSpan(0, 23, 17, 47)
Dim ts2 As New TimeSpan(365, 21, 19, 45)

For ctr As Integer = 2 To 8


Dim fmt As String = New String("d"c, ctr) + "\.hh\:mm\:ss"
Console.WriteLine("{0} --> {1:" + fmt + "}", fmt, ts1)
Console.WriteLine("{0} --> {1:" + fmt + "}", fmt, ts2)
Console.WriteLine()
Next
' The example displays the following output:
' dd\.hh\:mm\:ss --> 00.23:17:47
' dd\.hh\:mm\:ss --> 365.21:19:45
'
' ddd\.hh\:mm\:ss --> 000.23:17:47
' ddd\.hh\:mm\:ss --> 365.21:19:45
'
' dddd\.hh\:mm\:ss --> 0000.23:17:47
' dddd\.hh\:mm\:ss --> 0365.21:19:45
'
' ddddd\.hh\:mm\:ss --> 00000.23:17:47
' ddddd\.hh\:mm\:ss --> 00365.21:19:45
'
' dddddd\.hh\:mm\:ss --> 000000.23:17:47
' dddddd\.hh\:mm\:ss --> 000365.21:19:45
'
' ddddddd\.hh\:mm\:ss --> 0000000.23:17:47
' ddddddd\.hh\:mm\:ss --> 0000365.21:19:45
'
' dddddddd\.hh\:mm\:ss --> 00000000.23:17:47
' dddddddd\.hh\:mm\:ss --> 00000365.21:19:45

Retour au tableau

Spécificateur de format personnalisé "h"


Le spécificateur de format personnalisé « h » donne la valeur de la propriété TimeSpan.Hours, qui représente le
nombre d’heures entières de l’intervalle de temps non comptabilisées dans son composant « jours ». Il retourne
une valeur de chaîne à un chiffre si la valeur de la propriété TimeSpan.Hours est comprise entre 0 et 9, ou une
valeur de chaîne à deux chiffres si la valeur de la propriété TimeSpan.Hours est comprise entre 10 et 23.
Si le spécificateur de format personnalisé « h » est utilisé seul, spécifiez « %h » afin qu’il ne soit pas interprété à
tort comme une chaîne de format standard. L'exemple suivant en est l'illustration.

TimeSpan ts = new TimeSpan(3, 42, 0);


Console.WriteLine("{0:%h} hours {0:%m} minutes", ts);
// The example displays the following output:
// 3 hours 42 minutes

Dim ts As New TimeSpan(3, 42, 0)


Console.WriteLine("{0:%h} hours {0:%m} minutes", ts)
' The example displays the following output:
' 3 hours 42 minutes

En règle générale, dans une opération d’analyse, une chaîne d’entrée qui ne contient qu’un seul nombre est
interprétée comme le nombre de jours. Vous pouvez utiliser le spécificateur de format personnalisé "%h" à la
place pour interpréter la chaîne numérique comme nombre d’heures. L'exemple suivant en est l'illustration.
string value = "8";
TimeSpan interval;
if (TimeSpan.TryParseExact(value, "%h", null, out interval))
Console.WriteLine(interval.ToString("c"));
else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value);
// The example displays the following output:
// 08:00:00

Dim value As String = "8"


Dim interval As TimeSpan
If TimeSpan.TryParseExact(value, "%h", Nothing, interval) Then
Console.WriteLine(interval.ToString("c"))
Else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value)
End If
' The example displays the following output:
' 08:00:00

L’exemple suivant illustre l’utilisation du spécificateur de format personnalisé "h".

TimeSpan ts1 = new TimeSpan(14, 3, 17);


Console.WriteLine(ts1.ToString(@"d\.h\:mm\:ss"));

TimeSpan ts2 = new TimeSpan(3, 4, 3, 17);


Console.WriteLine(ts2.ToString(@"d\.h\:mm\:ss"));
// The example displays the following output:
// 0.14:03:17
// 3.4:03:17

Dim ts1 As New TimeSpan(14, 3, 17)


Console.WriteLine(ts1.ToString("d\.h\:mm\:ss"))

Dim ts2 As New TimeSpan(3, 4, 3, 17)


Console.WriteLine(ts2.ToString("d\.h\:mm\:ss"))
' The example displays the following output:
' 0.14:03:17
' 3.4:03:17

Retour au tableau

Spécificateur de format personnalisé "HH"


Le spécificateur de format personnalisé « hh » donne la valeur de la propriété TimeSpan.Hours, qui représente le
nombre d’heures entières de l’intervalle de temps non comptabilisées dans son composant « jours ». Pour les
valeurs 0 à 9, la chaîne de sortie inclut un zéro non significatif.
En règle générale, dans une opération d’analyse, une chaîne d’entrée qui ne contient qu’un seul nombre est
interprétée comme le nombre de jours. Vous pouvez utiliser le spécificateur de format personnalisé "hh" à la place
pour interpréter la chaîne numérique comme nombre d’heures. L'exemple suivant en est l'illustration.
string value = "08";
TimeSpan interval;
if (TimeSpan.TryParseExact(value, "hh", null, out interval))
Console.WriteLine(interval.ToString("c"));
else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value);
// The example displays the following output:
// 08:00:00

Dim value As String = "08"


Dim interval As TimeSpan
If TimeSpan.TryParseExact(value, "hh", Nothing, interval) Then
Console.WriteLine(interval.ToString("c"))
Else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value)
End If
' The example displays the following output:
' 08:00:00

L’exemple suivant illustre l’utilisation du spécificateur de format personnalisé "hh".

TimeSpan ts1 = new TimeSpan(14, 3, 17);


Console.WriteLine(ts1.ToString(@"d\.hh\:mm\:ss"));

TimeSpan ts2 = new TimeSpan(3, 4, 3, 17);


Console.WriteLine(ts2.ToString(@"d\.hh\:mm\:ss"));
// The example displays the following output:
// 0.14:03:17
// 3.04:03:17

Dim ts1 As New TimeSpan(14, 3, 17)


Console.WriteLine(ts1.ToString("d\.hh\:mm\:ss"))

Dim ts2 As New TimeSpan(3, 4, 3, 17)


Console.WriteLine(ts2.ToString("d\.hh\:mm\:ss"))
' The example displays the following output:
' 0.14:03:17
' 3.04:03:17

Retour au tableau

Spécificateur de format personnalisé "m"


Le spécificateur de format personnalisé « mm » donne la valeur de la propriété TimeSpan.Minutes, qui représente
le nombre de minutes entières de l’intervalle de temps non comptabilisées dans son composant « jours ». Il
retourne une valeur de chaîne à un chiffre si la valeur de la propriété TimeSpan.Minutes est comprise entre 0 et 9,
ou une valeur de chaîne à deux chiffres si la valeur de la propriété TimeSpan.Minutes est comprise entre 10 et 59.
Si le spécificateur de format personnalisé « m » est utilisé seul, spécifiez « %m » afin qu’il ne soit pas interprété à
tort comme une chaîne de format standard. L'exemple suivant en est l'illustration.

TimeSpan ts = new TimeSpan(3, 42, 0);


Console.WriteLine("{0:%h} hours {0:%m} minutes", ts);
// The example displays the following output:
// 3 hours 42 minutes
Dim ts As New TimeSpan(3, 42, 0)
Console.WriteLine("{0:%h} hours {0:%m} minutes", ts)
' The example displays the following output:
' 3 hours 42 minutes

En règle générale, dans une opération d’analyse, une chaîne d’entrée qui ne contient qu’un seul nombre est
interprétée comme le nombre de jours. Vous pouvez utiliser le spécificateur de format personnalisé "%m" à la
place pour interpréter la chaîne numérique comme nombre de minutes. L'exemple suivant en est l'illustration.

string value = "3";


TimeSpan interval;
if (TimeSpan.TryParseExact(value, "%m", null, out interval))
Console.WriteLine(interval.ToString("c"));
else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value);
// The example displays the following output:
// 00:03:00

Dim value As String = "3"


Dim interval As TimeSpan
If TimeSpan.TryParseExact(value, "%m", Nothing, interval) Then
Console.WriteLine(interval.ToString("c"))
Else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value)
End If
' The example displays the following output:
' 00:03:00

L’exemple suivant illustre l’utilisation du spécificateur de format personnalisé "m".

TimeSpan ts1 = new TimeSpan(0, 6, 32);


Console.WriteLine("{0:m\\:ss} minutes", ts1);

TimeSpan ts2 = new TimeSpan(3, 4, 3, 17);


Console.WriteLine("Elapsed time: {0:m\\:ss}", ts2);
// The example displays the following output:
// 6:32 minutes
// Elapsed time: 18:44

Dim ts1 As New TimeSpan(0, 6, 32)


Console.WriteLine("{0:m\:ss} minutes", ts1)

Dim ts2 As New TimeSpan(0, 18, 44)


Console.WriteLine("Elapsed time: {0:m\:ss}", ts2)
' The example displays the following output:
' 6:32 minutes
' Elapsed time: 18:44

Retour au tableau

Spécificateur de format personnalisé "mm"


Le spécificateur de format personnalisé « mm » donne la valeur de la propriété TimeSpan.Minutes, qui représente
le nombre de minutes entières de l’intervalle de temps non comptabilisées dans son composant « jours » ou «
heures ». Pour les valeurs 0 à 9, la chaîne de sortie inclut un zéro non significatif.
En règle générale, dans une opération d’analyse, une chaîne d’entrée qui ne contient qu’un seul nombre est
interprétée comme le nombre de jours. Vous pouvez utiliser le spécificateur de format personnalisé "mm" à la
place pour interpréter la chaîne numérique comme nombre de minutes. L'exemple suivant en est l'illustration.

string value = "07";


TimeSpan interval;
if (TimeSpan.TryParseExact(value, "mm", null, out interval))
Console.WriteLine(interval.ToString("c"));
else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value);
// The example displays the following output:
// 00:07:00

Dim value As String = "05"


Dim interval As TimeSpan
If TimeSpan.TryParseExact(value, "mm", Nothing, interval) Then
Console.WriteLine(interval.ToString("c"))
Else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value)
End If
' The example displays the following output:
' 00:05:00

L’exemple suivant illustre l’utilisation du spécificateur de format personnalisé "mm".

TimeSpan departTime = new TimeSpan(11, 12, 00);


TimeSpan arriveTime = new TimeSpan(16, 28, 00);
Console.WriteLine("Travel time: {0:hh\\:mm}",
arriveTime - departTime);
// The example displays the following output:
// Travel time: 05:16

Dim departTime As New TimeSpan(11, 12, 00)


Dim arriveTime As New TimeSpan(16, 28, 00)
Console.WriteLine("Travel time: {0:hh\:mm}",
arriveTime - departTime)
' The example displays the following output:
' Travel time: 05:16

Retour au tableau

Spécificateur de format personnalisé "s"


Le spécificateur de format personnalisé « s » donne la valeur de la propriété TimeSpan.Seconds, qui représente le
nombre de secondes entières de l’intervalle de temps non comptabilisées dans son composant « jours », « heures
» ou « minutes ». Il retourne une valeur de chaîne à un chiffre si la valeur de la propriété TimeSpan.Seconds est
comprise entre 0 et 9, ou une valeur de chaîne à deux chiffres si la valeur de la propriété TimeSpan.Seconds est
comprise entre 10 et 59.
Si le spécificateur de format personnalisé « s » est utilisé seul, spécifiez « %s » afin qu’il ne soit pas interprété à
tort comme une chaîne de format standard. L'exemple suivant en est l'illustration.
TimeSpan ts = TimeSpan.FromSeconds(12.465);
Console.WriteLine(ts.ToString("%s"));
// The example displays the following output:
// 12

Dim ts As TimeSpan = TimeSpan.FromSeconds(12.465)


Console.WriteLine(ts.ToString("%s"))
' The example displays the following output:
' 12

En règle générale, dans une opération d’analyse, une chaîne d’entrée qui ne contient qu’un seul nombre est
interprétée comme le nombre de jours. Vous pouvez utiliser le spécificateur de format personnalisé "%s" à la
place pour interpréter la chaîne numérique comme nombre de secondes. L'exemple suivant en est l'illustration.

string value = "9";


TimeSpan interval;
if (TimeSpan.TryParseExact(value, "%s", null, out interval))
Console.WriteLine(interval.ToString("c"));
else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value);
// The example displays the following output:
// 00:00:09

Dim value As String = "9"


Dim interval As TimeSpan
If TimeSpan.TryParseExact(value, "%s", Nothing, interval) Then
Console.WriteLine(interval.ToString("c"))
Else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value)
End If
' The example displays the following output:
' 00:00:09

L’exemple suivant illustre l’utilisation du spécificateur de format personnalisé "s".

TimeSpan startTime = new TimeSpan(0, 12, 30, 15, 0);


TimeSpan endTime = new TimeSpan(0, 12, 30, 21, 3);
Console.WriteLine(@"Elapsed Time: {0:s\:fff} seconds",
endTime - startTime);
// The example displays the following output:
// Elapsed Time: 6:003 seconds

Dim startTime As New TimeSpan(0, 12, 30, 15, 0)


Dim endTime As New TimeSpan(0, 12, 30, 21, 3)
Console.WriteLine("Elapsed Time: {0:s\:fff} seconds",
endTime - startTime)
' The example displays the following output:
' Elapsed Time: 6:003 seconds

Retour au tableau

Spécificateur de format personnalisé "SS"


Le spécificateur de format personnalisé « ss » donne la valeur de la propriété TimeSpan.Seconds, qui représente le
nombre de secondes entières de l’intervalle de temps non comptabilisées dans son composant « jours », « heures
» ou « minutes ». Pour les valeurs 0 à 9, la chaîne de sortie inclut un zéro non significatif.
En règle générale, dans une opération d’analyse, une chaîne d’entrée qui ne contient qu’un seul nombre est
interprétée comme le nombre de jours. Vous pouvez utiliser le spécificateur de format personnalisé "ss" à la place
pour interpréter la chaîne numérique comme nombre de secondes. L'exemple suivant en est l'illustration.

string[] values = { "49", "9", "06" };


TimeSpan interval;
foreach (string value in values)
{
if (TimeSpan.TryParseExact(value, "ss", null, out interval))
Console.WriteLine(interval.ToString("c"));
else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value);
}
// The example displays the following output:
// 00:00:49
// Unable to convert '9' to a time interval
// 00:00:06

Dim values() As String = {"49", "9", "06"}


Dim interval As TimeSpan
For Each value As String In values
If TimeSpan.TryParseExact(value, "ss", Nothing, interval) Then
Console.WriteLine(interval.ToString("c"))
Else
Console.WriteLine("Unable to convert '{0}' to a time interval",
value)
End If
Next
' The example displays the following output:
' 00:00:49
' Unable to convert '9' to a time interval
' 00:00:06

L’exemple suivant illustre l’utilisation du spécificateur de format personnalisé "ss".

TimeSpan interval1 = TimeSpan.FromSeconds(12.60);


Console.WriteLine(interval1.ToString(@"ss\.fff"));

TimeSpan interval2 = TimeSpan.FromSeconds(6.485);


Console.WriteLine(interval2.ToString(@"ss\.fff"));
// The example displays the following output:
// 12.600
// 06.485

Dim interval1 As TimeSpan = TimeSpan.FromSeconds(12.60)


Console.WriteLine(interval1.ToString("ss\.fff"))
Dim interval2 As TimeSpan = TimeSpan.FromSeconds(6.485)
Console.WriteLine(interval2.ToString("ss\.fff"))
' The example displays the following output:
' 12.600
' 06.485

Retour au tableau

Spécificateur de format personnalisé "f"


Le spécificateur de format personnalisé "f" affiche les dixièmes de seconde dans un intervalle de temps. Dans une
opération de mise en forme, tous les chiffres fractionnaires restants sont tronqués. Dans une opération d’analyse
qui appelle la méthode TimeSpan.ParseExact ou TimeSpan.TryParseExact, la chaîne d’entrée doit contenir
exactement un chiffre fractionnaire.
Si le spécificateur de format personnalisé « f » est utilisé seul, spécifiez « %f » afin qu’il ne soit pas interprété à tort
comme une chaîne de format standard.
L’exemple suivant utilise le spécificateur de format personnalisé "f" pour afficher les dixièmes de seconde dans
une valeur TimeSpan. "f" est d’abord utilisé seul comme spécificateur de format, puis est combiné avec le
spécificateur "s" dans une chaîne de format personnalisée.

TimeSpan ts = new TimeSpan(1003498765432);


string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432
Dim ts As New TimeSpan(1003498765432)
Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432

Retour au tableau

Spécificateur de format personnalisé "FF"


Le spécificateur de format personnalisé "ff" affiche les centièmes de seconde dans un intervalle de temps. Dans
une opération de mise en forme, tous les chiffres fractionnaires restants sont tronqués. Dans une opération
d’analyse qui appelle la méthode TimeSpan.ParseExact ou TimeSpan.TryParseExact, la chaîne d’entrée doit contenir
exactement deux chiffres fractionnaires.
L’exemple suivant utilise le spécificateur de format personnalisé "ff" pour afficher les centièmes de seconde dans
une valeur TimeSpan. "ff" est d’abord utilisé seul comme spécificateur de format, puis est combiné avec le
spécificateur "s" dans une chaîne de format personnalisée.
TimeSpan ts = new TimeSpan(1003498765432);
string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432

Dim ts As New TimeSpan(1003498765432)


Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432
Retour au tableau

Le spécificateur de format personnalisé « fff »


Le spécificateur de format personnalisé "fff" (trois caractères « f ») affiche les millisecondes dans un intervalle de
temps. Dans une opération de mise en forme, tous les chiffres fractionnaires restants sont tronqués. Dans une
opération d’analyse qui appelle la méthode TimeSpan.ParseExact ou TimeSpan.TryParseExact, la chaîne d’entrée
doit contenir exactement trois chiffres fractionnaires.
L’exemple suivant utilise le spécificateur de format personnalisé "fff" pour afficher les millisecondes dans une
valeur TimeSpan. "fff" est d’abord utilisé seul comme spécificateur de format, puis est combiné avec le
spécificateur "s" dans une chaîne de format personnalisée.

TimeSpan ts = new TimeSpan(1003498765432);


string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432
Dim ts As New TimeSpan(1003498765432)
Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432

Retour au tableau

Spécificateur de format personnalisé "FFFF"


Le spécificateur de format personnalisé "ffff" (quatre caractères « f ») affiche les dix millièmes de seconde dans un
intervalle de temps. Dans une opération de mise en forme, tous les chiffres fractionnaires restants sont tronqués.
Dans une opération d’analyse qui appelle la méthode TimeSpan.ParseExact ou TimeSpan.TryParseExact, la chaîne
d’entrée doit contenir exactement quatre chiffres fractionnaires.
L’exemple suivant utilise le spécificateur de format personnalisé "ffff" pour afficher les dix millièmes de seconde
dans une valeur TimeSpan. "ffff" est d’abord utilisé seul comme spécificateur de format, puis est combiné avec le
spécificateur "s" dans une chaîne de format personnalisée.
TimeSpan ts = new TimeSpan(1003498765432);
string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432

Dim ts As New TimeSpan(1003498765432)


Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432
Retour au tableau

Spécificateur de format personnalisé "fffff"


Le spécificateur de format personnalisé "fffff" (cinq caractères « f ») affiche les cent millièmes de seconde dans un
intervalle de temps. Dans une opération de mise en forme, tous les chiffres fractionnaires restants sont tronqués.
Dans une opération d’analyse qui appelle la méthode TimeSpan.ParseExact ou TimeSpan.TryParseExact, la chaîne
d’entrée doit contenir exactement cinq chiffres fractionnaires.
L’exemple suivant utilise le spécificateur de format personnalisé "fffff" pour afficher les cent millièmes de seconde
dans une valeur TimeSpan. "fffff" est d’abord utilisé seul comme spécificateur de format, puis est combiné avec le
spécificateur "s" dans une chaîne de format personnalisée.

TimeSpan ts = new TimeSpan(1003498765432);


string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432
Dim ts As New TimeSpan(1003498765432)
Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432

Retour au tableau

Spécificateur de format personnalisé "FFFFFF"


Le spécificateur de format personnalisé "ffffff" (six caractères « f ») affiche les millionièmes de seconde dans un
intervalle de temps. Dans une opération de mise en forme, tous les chiffres fractionnaires restants sont tronqués.
Dans une opération d’analyse qui appelle la méthode TimeSpan.ParseExact ou TimeSpan.TryParseExact, la chaîne
d’entrée doit contenir exactement six chiffres fractionnaires.
L’exemple suivant utilise le spécificateur de format personnalisé "ffffff" pour afficher les millionièmes de seconde
dans une valeur TimeSpan. Il est d’abord utilisé seul comme spécificateur de format, puis est combiné avec le
spécificateur "s" dans une chaîne de format personnalisée.
TimeSpan ts = new TimeSpan(1003498765432);
string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432

Dim ts As New TimeSpan(1003498765432)


Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432
Retour au tableau

Spécificateur de format personnalisé "fffffff"


Le spécificateur de format personnalisé "fffffff" (sept caractères « f ») affiche les dix millionièmes de seconde (ou
le nombre fractionnaire de graduations) dans un intervalle de temps. Dans une opération d’analyse qui appelle la
méthode TimeSpan.ParseExact ou TimeSpan.TryParseExact, la chaîne d’entrée doit contenir exactement sept
chiffres fractionnaires.
L’exemple suivant utilise le spécificateur de format personnalisé "fffffff" pour afficher le nombre fractionnaire de
graduations dans une valeur TimeSpan. Il est d’abord utilisé seul comme spécificateur de format, puis est combiné
avec le spécificateur "s" dans une chaîne de format personnalisée.

TimeSpan ts = new TimeSpan(1003498765432);


string fmt;
Console.WriteLine(ts.ToString("c"));
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
if (fmt.Length == 1) fmt = "%" + fmt;
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts);
}
Console.WriteLine();

for (int ctr = 1; ctr <= 7; ctr++) {


fmt = new String('f', ctr);
Console.WriteLine("{0,10}: {1:s\\." + fmt + "}", "s\\." + fmt, ts);
}
// The example displays the following output:
// %f: 8
// ff: 87
// fff: 876
// ffff: 8765
// fffff: 87654
// ffffff: 876543
// fffffff: 8765432
//
// s\.f: 29.8
// s\.ff: 29.87
// s\.fff: 29.876
// s\.ffff: 29.8765
// s\.fffff: 29.87654
// s\.ffffff: 29.876543
// s\.fffffff: 29.8765432
Dim ts As New TimeSpan(1003498765432)
Dim fmt As String
Console.WriteLine(ts.ToString("c"))
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
If fmt.Length = 1 Then fmt = "%" + fmt
Console.WriteLine("{0,10}: {1:" + fmt + "}", fmt, ts)
Next
Console.WriteLine()

For ctr = 1 To 7
fmt = New String("f"c, ctr)
Console.WriteLine("{0,10}: {1:s\." + fmt + "}", "s\." + fmt, ts)
Next
' The example displays the following output:
' %f: 8
' ff: 87
' fff: 876
' ffff: 8765
' fffff: 87654
' ffffff: 876543
' fffffff: 8765432
'
' s\.f: 29.8
' s\.ff: 29.87
' s\.fff: 29.876
' s\.ffff: 29.8765
' s\.fffff: 29.87654
' s\.ffffff: 29.876543
' s\.fffffff: 29.8765432

Retour au tableau

Spécificateur de format personnalisé "F"


Le spécificateur de format personnalisé "F" affiche les dixièmes de seconde dans un intervalle de temps. Dans une
opération de mise en forme, tous les chiffres fractionnaires restants sont tronqués. Si la valeur des dixièmes de
seconde de l’intervalle de temps est égale à zéro, elle n’est pas incluse dans la chaîne de résultat. Dans une
opération d’analyse qui appelle la méthode TimeSpan.ParseExact ou TimeSpan.TryParseExact, la présence du
chiffre des dixièmes de seconde est facultative.
Si le spécificateur de format personnalisé « F » est utilisé seul, spécifiez « %F » afin qu’il ne soit pas interprété à
tort comme une chaîne de format standard.
L’exemple suivant utilise le spécificateur de format personnalisé "F" pour afficher les dixièmes de seconde dans
une valeur TimeSpan. Il utilise également ce spécificateur de format personnalisé dans une opération d’analyse.
Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.669");
Console.WriteLine("{0} ('%F') --> {0:%F}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.091");


Console.WriteLine("{0} ('ss\\.F') --> {0:ss\\.F}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.1", "0:0:03.12" };
string fmt = @"h\:m\:ss\.F";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6690000 ('%F') --> 6
// 00:00:03.0910000 ('ss\.F') --> 03.
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.F') --> 00:00:03
// 0:0:03.1 ('h\:m\:ss\.F') --> 00:00:03.1000000
// Cannot parse 0:0:03.12 with 'h\:m\:ss\.F'.

Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.669")
Console.WriteLine("{0} ('%F') --> {0:%F}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.091")


Console.WriteLine("{0} ('ss\.F') --> {0:ss\.F}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.1", "0:0:03.12"}
Dim fmt As String = "h\:m\:ss\.F"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6690000 ('%F') --> 6
' 00:00:03.0910000 ('ss\.F') --> 03.
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.F') --> 00:00:03
' 0:0:03.1 ('h\:m\:ss\.F') --> 00:00:03.1000000
' Cannot parse 0:0:03.12 with 'h\:m\:ss\.F'.

Retour au tableau
Spécificateur de format personnalisé "FF"
Le spécificateur de format personnalisé "FF" affiche les centièmes de seconde dans un intervalle de temps. Dans
une opération de mise en forme, tous les chiffres fractionnaires restants sont tronqués. Les éventuels zéros de fin
fractionnaires ne sont pas inclus dans la chaîne de résultat. Dans une opération d’analyse qui appelle la méthode
TimeSpan.ParseExact ou TimeSpan.TryParseExact, la présence du chiffre des dixièmes et des centièmes de seconde
est facultative.
L’exemple suivant utilise le spécificateur de format personnalisé "FF" pour afficher les centièmes de seconde dans
une valeur TimeSpan. Il utilise également ce spécificateur de format personnalisé dans une opération d’analyse.

Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.697");
Console.WriteLine("{0} ('FF') --> {0:FF}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.809");


Console.WriteLine("{0} ('ss\\.FF') --> {0:ss\\.FF}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.1", "0:0:03.127" };
string fmt = @"h\:m\:ss\.FF";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6970000 ('FF') --> 69
// 00:00:03.8090000 ('ss\.FF') --> 03.8
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.FF') --> 00:00:03
// 0:0:03.1 ('h\:m\:ss\.FF') --> 00:00:03.1000000
// Cannot parse 0:0:03.127 with 'h\:m\:ss\.FF'.
Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.697")
Console.WriteLine("{0} ('FF') --> {0:FF}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.809")


Console.WriteLine("{0} ('ss\.FF') --> {0:ss\.FF}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.1", "0:0:03.127"}
Dim fmt As String = "h\:m\:ss\.FF"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6970000 ('FF') --> 69
' 00:00:03.8090000 ('ss\.FF') --> 03.8
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.FF') --> 00:00:03
' 0:0:03.1 ('h\:m\:ss\.FF') --> 00:00:03.1000000
' Cannot parse 0:0:03.127 with 'h\:m\:ss\.FF'.

Retour au tableau

Spécificateur de format personnalisé "FFF"


Le spécificateur de format personnalisé "FFF" (trois caractères « F ») affiche les millisecondes dans un intervalle de
temps. Dans une opération de mise en forme, tous les chiffres fractionnaires restants sont tronqués. Les éventuels
zéros de fin fractionnaires ne sont pas inclus dans la chaîne de résultat. Dans une opération d’analyse qui appelle
la méthode TimeSpan.ParseExact ou TimeSpan.TryParseExact, la présence du chiffre des dixièmes, des centièmes
et des millièmes de seconde est facultative.
L’exemple suivant utilise le spécificateur de format personnalisé "FFF" pour afficher les millièmes de seconde dans
une valeur TimeSpan. Il utilise également ce spécificateur de format personnalisé dans une opération d’analyse.
Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.6974");
Console.WriteLine("{0} ('FFF') --> {0:FFF}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.8009");


Console.WriteLine("{0} ('ss\\.FFF') --> {0:ss\\.FFF}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.12", "0:0:03.1279" };
string fmt = @"h\:m\:ss\.FFF";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6974000 ('FFF') --> 697
// 00:00:03.8009000 ('ss\.FFF') --> 03.8
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.FFF') --> 00:00:03
// 0:0:03.12 ('h\:m\:ss\.FFF') --> 00:00:03.1200000
// Cannot parse 0:0:03.1279 with 'h\:m\:ss\.FFF'.

Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.6974")
Console.WriteLine("{0} ('FFF') --> {0:FFF}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.8009")


Console.WriteLine("{0} ('ss\.FFF') --> {0:ss\.FFF}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.12", "0:0:03.1279"}
Dim fmt As String = "h\:m\:ss\.FFF"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6974000 ('FFF') --> 697
' 00:00:03.8009000 ('ss\.FFF') --> 03.8
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.FFF') --> 00:00:03
' 0:0:03.12 ('h\:m\:ss\.FFF') --> 00:00:03.1200000
' Cannot parse 0:0:03.1279 with 'h\:m\:ss\.FFF'.

Retour au tableau
Spécificateur de format personnalisé "FFFF"
Le spécificateur de format personnalisé "FFFF" (quatre caractères « F ») affiche les dix millièmes de seconde dans
un intervalle de temps. Dans une opération de mise en forme, tous les chiffres fractionnaires restants sont
tronqués. Les éventuels zéros de fin fractionnaires ne sont pas inclus dans la chaîne de résultat. Dans une
opération d’analyse qui appelle la méthode TimeSpan.ParseExact ou TimeSpan.TryParseExact, la présence du
chiffre des dixièmes, des centièmes, des millièmes et des dix millièmes de seconde est facultative.
L’exemple suivant utilise le spécificateur de format personnalisé "FFFF" pour afficher les dix millièmes de seconde
dans une valeur TimeSpan. Il utilise également le spécificateur de format personnalisé "FFFF" dans une opération
d’analyse.

Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.69749");
Console.WriteLine("{0} ('FFFF') --> {0:FFFF}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.80009");


Console.WriteLine("{0} ('ss\\.FFFF') --> {0:ss\\.FFFF}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.12", "0:0:03.12795" };
string fmt = @"h\:m\:ss\.FFFF";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6974900 ('FFFF') --> 6974
// 00:00:03.8000900 ('ss\.FFFF') --> 03.8
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.FFFF') --> 00:00:03
// 0:0:03.12 ('h\:m\:ss\.FFFF') --> 00:00:03.1200000
// Cannot parse 0:0:03.12795 with 'h\:m\:ss\.FFFF'.
Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.69749")
Console.WriteLine("{0} ('FFFF') --> {0:FFFF}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.80009")


Console.WriteLine("{0} ('ss\.FFFF') --> {0:ss\.FFFF}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.12", "0:0:03.12795"}
Dim fmt As String = "h\:m\:ss\.FFFF"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6974900 ('FFFF') --> 6974
' 00:00:03.8000900 ('ss\.FFFF') --> 03.8
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.FFFF') --> 00:00:03
' 0:0:03.12 ('h\:m\:ss\.FFFF') --> 00:00:03.1200000
' Cannot parse 0:0:03.12795 with 'h\:m\:ss\.FFFF'.

Retour au tableau

Spécificateur de format personnalisé "FFFFF"


Le spécificateur de format personnalisé "FFFFF" (cinq caractères « F ») affiche les cent millièmes de seconde dans
un intervalle de temps. Dans une opération de mise en forme, tous les chiffres fractionnaires restants sont
tronqués. Les éventuels zéros de fin fractionnaires ne sont pas inclus dans la chaîne de résultat. Dans une
opération d’analyse qui appelle la méthode TimeSpan.ParseExact ou TimeSpan.TryParseExact, la présence du
chiffre des dixièmes, des centièmes, des millièmes, des dix millièmes et des cent millièmes de seconde est
facultative.
L’exemple suivant utilise le spécificateur de format personnalisé "FFFFF" pour afficher les cent millièmes de
seconde dans une valeur TimeSpan. Il utilise également le spécificateur de format personnalisé "FFFFF" dans une
opération d’analyse.
Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.697497");
Console.WriteLine("{0} ('FFFFF') --> {0:FFFFF}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.800009");


Console.WriteLine("{0} ('ss\\.FFFFF') --> {0:ss\\.FFFFF}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.12", "0:0:03.127956" };
string fmt = @"h\:m\:ss\.FFFFF";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6974970 ('FFFFF') --> 69749
// 00:00:03.8000090 ('ss\.FFFFF') --> 03.8
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.FFFF') --> 00:00:03
// 0:0:03.12 ('h\:m\:ss\.FFFF') --> 00:00:03.1200000
// Cannot parse 0:0:03.127956 with 'h\:m\:ss\.FFFF'.

Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.697497")
Console.WriteLine("{0} ('FFFFF') --> {0:FFFFF}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.800009")


Console.WriteLine("{0} ('ss\.FFFFF') --> {0:ss\.FFFFF}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.12", "0:0:03.127956"}
Dim fmt As String = "h\:m\:ss\.FFFFF"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6974970 ('FFFFF') --> 69749
' 00:00:03.8000090 ('ss\.FFFFF') --> 03.8
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.FFFF') --> 00:00:03
' 0:0:03.12 ('h\:m\:ss\.FFFF') --> 00:00:03.1200000
' Cannot parse 0:0:03.127956 with 'h\:m\:ss\.FFFF'.

Retour au tableau
Spécificateur de format personnalisé "FFFFFF"
Le spécificateur de format personnalisé "FFFFFF" (six caractères « F ») affiche les millionièmes de seconde dans un
intervalle de temps. Dans une opération de mise en forme, tous les chiffres fractionnaires restants sont tronqués.
Les éventuels zéros de fin fractionnaires ne sont pas inclus dans la chaîne de résultat. Dans une opération
d’analyse qui appelle la méthode TimeSpan.ParseExact ou TimeSpan.TryParseExact, la présence du chiffre des
dixièmes, des centièmes, des millièmes, des dix millièmes, des cent millièmes et des millionièmes de seconde est
facultative.
L’exemple suivant utilise le spécificateur de format personnalisé "FFFFFF" pour afficher les millionièmes de
seconde dans une valeur TimeSpan. Il utilise également ce spécificateur de format personnalisé dans une
opération d’analyse.

Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.6974974");
Console.WriteLine("{0} ('FFFFFF') --> {0:FFFFFF}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.8000009");


Console.WriteLine("{0} ('ss\\.FFFFFF') --> {0:ss\\.FFFFFF}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.12", "0:0:03.1279569" };
string fmt = @"h\:m\:ss\.FFFFFF";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6974974 ('FFFFFF') --> 697497
// 00:00:03.8000009 ('ss\.FFFFFF') --> 03.8
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.FFFFFF') --> 00:00:03
// 0:0:03.12 ('h\:m\:ss\.FFFFFF') --> 00:00:03.1200000
// Cannot parse 0:0:03.1279569 with 'h\:m\:ss\.FFFFFF'.
Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.6974974")
Console.WriteLine("{0} ('FFFFFF') --> {0:FFFFFF}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.8000009")


Console.WriteLine("{0} ('ss\.FFFFFF') --> {0:ss\.FFFFFF}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.12", "0:0:03.1279569"}
Dim fmt As String = "h\:m\:ss\.FFFFFF"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6974974 ('FFFFFF') --> 697497
' 00:00:03.8000009 ('ss\.FFFFFF') --> 03.8
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.FFFFFF') --> 00:00:03
' 0:0:03.12 ('h\:m\:ss\.FFFFFF') --> 00:00:03.1200000
' Cannot parse 0:0:03.1279569 with 'h\:m\:ss\.FFFFFF'.

Retour au tableau

Spécificateur de format personnalisé "FFFFFFF"


Le spécificateur de format personnalisé "FFFFFFF" (sept caractères « F ») affiche les dix millionièmes de seconde
(ou le nombre fractionnaire de graduations) dans un intervalle de temps. Les éventuels zéros de fin fractionnaires
ne sont pas inclus dans la chaîne de résultat. Dans une opération d’analyse qui appelle la méthode
TimeSpan.ParseExact ou TimeSpan.TryParseExact, la présence des sept chiffres fractionnaires dans la chaîne
d’entrée est facultative.
L’exemple suivant utilise le spécificateur de format personnalisé "FFFFFFF" pour afficher les parties fractionnaires
d’une seconde dans une valeur TimeSpan. Il utilise également ce spécificateur de format personnalisé dans une
opération d’analyse.
Console.WriteLine("Formatting:");
TimeSpan ts1 = TimeSpan.Parse("0:0:3.6974974");
Console.WriteLine("{0} ('FFFFFFF') --> {0:FFFFFFF}", ts1);

TimeSpan ts2 = TimeSpan.Parse("0:0:3.9500000");


Console.WriteLine("{0} ('ss\\.FFFFFFF') --> {0:ss\\.FFFFFFF}", ts2);
Console.WriteLine();

Console.WriteLine("Parsing:");
string[] inputs = { "0:0:03.", "0:0:03.12", "0:0:03.1279569" };
string fmt = @"h\:m\:ss\.FFFFFFF";
TimeSpan ts3;

foreach (string input in inputs) {


if (TimeSpan.TryParseExact(input, fmt, null, out ts3))
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3);
else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt);
}
// The example displays the following output:
// Formatting:
// 00:00:03.6974974 ('FFFFFFF') --> 6974974
// 00:00:03.9500000 ('ss\.FFFFFFF') --> 03.95
//
// Parsing:
// 0:0:03. ('h\:m\:ss\.FFFFFFF') --> 00:00:03
// 0:0:03.12 ('h\:m\:ss\.FFFFFFF') --> 00:00:03.1200000
// 0:0:03.1279569 ('h\:m\:ss\.FFFFFFF') --> 00:00:03.1279569

Console.WriteLine("Formatting:")
Dim ts1 As TimeSpan = TimeSpan.Parse("0:0:3.6974974")
Console.WriteLine("{0} ('FFFFFFF') --> {0:FFFFFFF}", ts1)

Dim ts2 As TimeSpan = TimeSpan.Parse("0:0:3.9500000")


Console.WriteLine("{0} ('ss\.FFFFFFF') --> {0:ss\.FFFFFFF}", ts2)
Console.WriteLine()

Console.WriteLine("Parsing:")
Dim inputs() As String = {"0:0:03.", "0:0:03.12", "0:0:03.1279569"}
Dim fmt As String = "h\:m\:ss\.FFFFFFF"
Dim ts3 As TimeSpan

For Each input As String In inputs


If TimeSpan.TryParseExact(input, fmt, Nothing, ts3)
Console.WriteLine("{0} ('{1}') --> {2}", input, fmt, ts3)
Else
Console.WriteLine("Cannot parse {0} with '{1}'.",
input, fmt)
End If
Next
' The example displays the following output:
' Formatting:
' 00:00:03.6974974 ('FFFFFFF') --> 6974974
' 00:00:03.9500000 ('ss\.FFFFFFF') --> 03.95
'
' Parsing:
' 0:0:03. ('h\:m\:ss\.FFFFFFF') --> 00:00:03
' 0:0:03.12 ('h\:m\:ss\.FFFFFFF') --> 00:00:03.1200000
' 0:0:03.1279569 ('h\:m\:ss\.FFFFFFF') --> 00:00:03.1279569

Retour au tableau
Les autres caractères
Tout autre caractère sans séquence d’échappement dans une chaîne de format, y compris un espace blanc, est
interprété comme un spécificateur de format personnalisé. Dans la plupart des cas, la présence de tout autre
caractère sans séquence d’échappement aboutit à une FormatException.
Vous pouvez inclure un caractère littéral dans une chaîne de format de deux façons :
Mettez-le entre guillemets simples (délimiteur de chaîne littérale).
Faites-le précéder d’une barre oblique inverse (« \ »), qui est interprétée comme un caractère
d’échappement. Cela signifie que, en C#, soit la chaîne de format doit être @-quoted, puis placée entre
guillemets, soit le caractère littéral doit être précédé d’une barre oblique inverse supplémentaire.
Dans certains cas, vous devrez peut-être utiliser une logique conditionnelle pour inclure un littéral avec
séquence d’échappement dans une chaîne de format. L’exemple suivant utilise une logique conditionnelle
pour inclure un symbole de signe pour les intervalles de temps négatifs.

using System;

public class Example


{
public static void Main()
{
TimeSpan result = new DateTime(2010, 01, 01) - DateTime.Now;
String fmt = (result < TimeSpan.Zero ? "\\-" : "") + "dd\\.hh\\:mm";

Console.WriteLine(result.ToString(fmt));
Console.WriteLine("Interval: {0:" + fmt + "}", result);
}
}
// The example displays output like the following:
// -1291.10:54
// Interval: -1291.10:54

Module Example
Public Sub Main()
Dim result As TimeSpan = New DateTime(2010, 01, 01) - Date.Now
Dim fmt As String = If(result < TimeSpan.Zero, "\-", "") + "dd\.hh\:mm"

Console.WriteLine(result.ToString(fmt))
Console.WriteLine("Interval: {0:" + fmt + "}", result)
End Sub
End Module
' The example displays output like the following:
' -1291.10:54
' Interval: -1291.10:54

.NET ne définit pas de grammaire pour les séparateurs des intervalles de temps. Cela signifie que les séparateurs
entre les jours et les heures, entre les heures et les minutes, entre les minutes et les secondes et entre les
secondes et les fractions de seconde doivent tous être traités comme des littéraux de caractère dans une chaîne
de format.
L’exemple suivant utilise le caractère d’échappement et le guillemet simple pour définir une chaîne de format
personnalisée qui inclut le mot « minutes » dans la chaîne de sortie.
TimeSpan interval = new TimeSpan(0, 32, 45);
// Escape literal characters in a format string.
string fmt = @"mm\:ss\ \m\i\n\u\t\e\s";
Console.WriteLine(interval.ToString(fmt));
// Delimit literal characters in a format string with the ' symbol.
fmt = "mm':'ss' minutes'";
Console.WriteLine(interval.ToString(fmt));
// The example displays the following output:
// 32:45 minutes
// 32:45 minutes

Dim interval As New TimeSpan(0, 32, 45)


' Escape literal characters in a format string.
Dim fmt As String = "mm\:ss\ \m\i\n\u\t\e\s"
Console.WriteLine(interval.ToString(fmt))
' Delimit literal characters in a format string with the ' symbol.
fmt = "mm':'ss' minutes'"
Console.WriteLine(interval.ToString(fmt))
' The example displays the following output:
' 32:45 minutes
' 32:45 minutes

Retour au tableau

Voir aussi
Mise en forme des types
Chaînes de format TimeSpan standard
Chaînes de format d’énumération
18/07/2020 • 5 minutes to read • Edit Online

Vous pouvez utiliser la méthode Enum.ToString pour créer un objet de chaîne qui représente la valeur numérique,
hexadécimale ou de chaîne d’un membre d’énumération. Cette méthode prend l’une des chaînes de mise en
forme d’énumération pour spécifier la valeur à retourner.
Les sections suivantes répertorient les chaînes de mise en forme d’énumération et les valeurs qu’elles retournent.
Ces spécificateurs de format ne respectent pas la casse.

G ou g
Affiche l’entrée d’énumération comme valeur de chaîne, dans la mesure du possible ; sinon, affiche la valeur
entière de l’instance actuelle. Si l’énumération est définie avec l’attribut Flags défini, les valeurs de chaîne de
chaque entrée valide sont concaténées et séparées par des virgules. Si l’attribut Flags n’est pas défini, une valeur
non valide s’affiche comme entrée numérique. L’exemple suivant illustre le spécificateur de format G.

Console.WriteLine(ConsoleColor.Red.ToString("G")); // Displays Red


FileAttributes attributes = FileAttributes.Hidden |
FileAttributes.Archive;
Console.WriteLine(attributes.ToString("G")); // Displays Hidden, Archive

Console.WriteLine(ConsoleColor.Red.ToString("G")) ' Displays Red


Dim attributes As FileAttributes = FileAttributes.Hidden Or _
FileAttributes.Archive
Console.WriteLine(attributes.ToString("G")) ' Displays Hidden, Archive

F ou f
Affiche l’entrée d’énumération comme valeur de chaîne, si possible. Si la valeur peut être complètement affichée
comme somme des entrées de l’énumération (même si l’attribut Flags n’est pas présent), les valeurs de chaîne de
chaque entrée valide sont concaténées ensemble, séparées par des virgules. Si la valeur ne peut pas être
complètement déterminée par les entrées de l’énumération, la valeur est mise en forme comme valeur entière.
L’exemple suivant illustre le spécificateur de format F.

Console.WriteLine(ConsoleColor.Blue.ToString("F")); // Displays Blue


FileAttributes attributes = FileAttributes.Hidden |
FileAttributes.Archive;
Console.WriteLine(attributes.ToString("F")); // Displays Hidden, Archive

Console.WriteLine(ConsoleColor.Blue.ToString("F")) ' Displays Blue


Dim attributes As FileAttributes = FileAttributes.Hidden Or _
FileAttributes.Archive
Console.WriteLine(attributes.ToString("F")) ' Displays Hidden, Archive

D ou d
Affiche l’entrée d’énumération comme valeur entière dans la représentation la plus courte possible. L’exemple
suivant illustre le spécificateur de format D.
Console.WriteLine(ConsoleColor.Cyan.ToString("D")); // Displays 11
FileAttributes attributes = FileAttributes.Hidden |
FileAttributes.Archive;
Console.WriteLine(attributes.ToString("D")); // Displays 34

Console.WriteLine(ConsoleColor.Cyan.ToString("D")) ' Displays 11


Dim attributes As FileAttributes = FileAttributes.Hidden Or _
FileAttributes.Archive
Console.WriteLine(attributes.ToString("D")) ' Displays 34

X ou x
Affiche l’entrée d’énumération comme valeur hexadécimale. La valeur est représentée avec des zéros interligne si
nécessaire, pour vérifier que la chaîne de résultat a deux caractères pour chaque octet dans le type numérique
sous-jacent du type d’énumération. L’exemple suivant illustre le spécificateur de format X. Dans l’exemple, le type
sous-jacent des deux ConsoleColor et FileAttributes est Int32 ou un entier de 32 bits (ou 4 octets), ce qui produit
une chaîne de résultat de 8 caractères.

Console.WriteLine(ConsoleColor.Cyan.ToString("X")); // Displays 0000000B


FileAttributes attributes = FileAttributes.Hidden |
FileAttributes.Archive;
Console.WriteLine(attributes.ToString("X")); // Displays 00000022

Console.WriteLine(ConsoleColor.Cyan.ToString("X")) ' Displays 0000000B


Dim attributes As FileAttributes = FileAttributes.Hidden Or _
FileAttributes.Archive
Console.WriteLine(attributes.ToString("X")) ' Displays 00000022

Exemple
L’exemple suivant définit une énumération appelée Colors qui se compose de trois entrées : Red , Blue et
Green .

public enum Color {Red = 1, Blue = 2, Green = 3}

Public Enum Color


Red = 1
Blue = 2
Green = 3
End Enum

Une fois que l’énumération est définie, une instance peut être déclarée de la manière suivante.

Color myColor = Color.Green;

Dim myColor As Color = Color.Green

La méthode Color.ToString(System.String) peut ensuite être utilisée pour afficher la valeur d’énumération de
différentes manières, selon le spécificateur de format qui lui est passé.
Console.WriteLine("The value of myColor is {0}.",
myColor.ToString("G"));
Console.WriteLine("The value of myColor is {0}.",
myColor.ToString("F"));
Console.WriteLine("The value of myColor is {0}.",
myColor.ToString("D"));
Console.WriteLine("The value of myColor is 0x{0}.",
myColor.ToString("X"));
// The example displays the following output to the console:
// The value of myColor is Green.
// The value of myColor is Green.
// The value of myColor is 3.
// The value of myColor is 0x00000003.

Console.WriteLine("The value of myColor is {0}.", _


myColor.ToString("G"))
Console.WriteLine("The value of myColor is {0}.", _
myColor.ToString("F"))
Console.WriteLine("The value of myColor is {0}.", _
myColor.ToString("D"))
Console.WriteLine("The value of myColor is 0x{0}.", _
myColor.ToString("X"))
' The example displays the following output to the console:
' The value of myColor is Green.
' The value of myColor is Green.
' The value of myColor is 3.
' The value of myColor is 0x00000003.

Voir aussi
Mise en forme des types
Mise en forme composite
18/07/2020 • 23 minutes to read • Edit Online

La fonctionnalité de mise en forme composite du .NET utilise une liste d’objets et une chaîne de format
composite comme entrée. Une chaîne de format composite se compose de texte fixe mélangé à des espaces
réservés indexés, appelés éléments de format, qui correspondent aux objets de la liste. L'opération de mise
en forme produit une chaîne résultante qui se compose du texte fixe d'origine mélangé à la représentation
sous forme de chaîne des objets de la liste.

IMPORTANT
Au lieu d’utiliser des chaînes de format composite, vous pouvez utiliser des chaînes interpolées si le langage et la
version du langage que vous utilisez les prennent en charge. Une chaîne interpolée est une chaîne contenant des
expressions interpolées. Chaque expression interpolée est résolue avec la valeur de l’expression et incluse dans la
chaîne du résultat quand la chaîne est affectée. Pour plus d’informations, consultez Interpolation de chaîne
(Informations de référence sur C#) et Chaînes interpolées (Informations de référence sur Visual Basic).

La fonctionnalité de mise en forme composite est prise en charge par les méthodes suivantes :
String.Format, qui retourne une chaîne de résultat mise en forme.
StringBuilder.AppendFormat, qui ajoute une chaîne de résultat mise en forme à un objet StringBuilder.
Certaines surcharges de la méthode Console.WriteLine, qui affichent une chaîne de résultat mise en
forme sur la console.
Certaines surcharges de la méthode TextWriter.WriteLine, qui écrivent la chaîne de résultat mise en
forme dans un flux ou un fichier. Les classes dérivées de TextWriter, telles que StreamWriter et
HtmlTextWriter, partagent également ces fonctionnalités.
Debug.WriteLine(String, Object[]), qui génère un message mis en forme pour les écouteurs Trace.
Les méthodes Trace.TraceError(String, Object[]), Trace.TraceInformation(String, Object[]) et
Trace.TraceWarning(String, Object[]) qui génèrent des messages mis en forme pour les écouteurs
Trace.
La méthode TraceSource.TraceInformation(String, Object[]), qui écrit une méthode à caractère
informatif pour les écouteurs Trace.

Chaîne de format composite


Une chaîne de format composite et une liste d'objets sont utilisées comme arguments des méthodes qui
prennent en charge la fonctionnalité de mise en forme composite. Une chaîne de format composite est
constituée de zéro, une ou plusieurs séquences de texte fixe mélangées à un ou plusieurs éléments de
format. Le texte fixe correspond à toute chaîne que vous choisissez, et chaque élément de format correspond
à un objet ou une structure boxed dans la liste. La fonctionnalité de mise en forme composite retourne une
nouvelle chaîne résultante, dans laquelle chaque élément de format est remplacé par la représentation sous
forme de chaîne de l’objet correspondant dans la liste.
Prenons le fragment de code Format suivant.
string name = "Fred";
String.Format("Name = {0}, hours = {1:hh}", name, DateTime.Now);

Dim name As String = "Fred"


String.Format("Name = {0}, hours = {1:hh}", name, DateTime.Now)

Le texte fixe est Name = et , hours = . Les éléments de format sont {0} , dont l’index est 0, ce qui
correspond à l’objet name , et {1:hh} , dont l’index est 1, ce qui correspond à l’objet DateTime.Now .

Syntaxe des éléments de format


Chaque élément de format prend la forme suivante et comprend les composants suivants :
{ index[ , alignement] [ : FormatString] }
Les accolades correspondantes (« { » et « } ») sont nécessaires.
Composant d'index
Le composant obligatoire index, également appelé « spécificateur de paramètre », est un nombre à partir
de 0 qui permet d’identifier un élément correspondant dans la liste des objets. En d'autres termes, l'élément
de format dont le spécificateur de format est 0 met en forme le premier objet de la liste, l'élément de format
dont le spécificateur de paramètres est 1 met en forme le deuxième objet de la liste, etc. L’exemple suivant
comprend quatre spécificateurs de paramètres, numérotés de 0 à 3, pour représenter les nombres premiers
inférieurs à 10 :

string primes;
primes = String.Format("Prime numbers less than 10: {0}, {1}, {2}, {3}",
2, 3, 5, 7 );
Console.WriteLine(primes);
// The example displays the following output:
// Prime numbers less than 10: 2, 3, 5, 7

Dim primes As String


primes = String.Format("Prime numbers less than 10: {0}, {1}, {2}, {3}",
2, 3, 5, 7)
Console.WriteLine(primes)
' The example displays the following output:
' Prime numbers less than 10: 2, 3, 5, 7

Plusieurs éléments de format peuvent faire référence au même élément de la liste d'objets en indiquant le
même spécificateur de paramètre. Par exemple, vous pouvez mettre en forme la même valeur numérique au
format hexadécimal, scientifique et numérique en spécifiant une chaîne de format composite telle que : « 0x
{0:X} {0:E} {0:N} », comme le montre l’exemple suivant.

string multiple = String.Format("0x{0:X} {0:E} {0:N}",


Int64.MaxValue);
Console.WriteLine(multiple);
// The example displays the following output:
// 0x7FFFFFFFFFFFFFFF 9.223372E+018 9,223,372,036,854,775,807.00
Dim multiple As String = String.Format("0x{0:X} {0:E} {0:N}",
Int64.MaxValue)
Console.WriteLine(multiple)
' The example displays the following output:
' 0x7FFFFFFFFFFFFFFF 9.223372E+018 9,223,372,036,854,775,807.00

Chaque élément de format peut faire référence à n'importe quel objet de la liste. Par exemple, s’il y a trois
objets, vous pouvez mettre en forme le deuxième, le premier et le troisième objet en spécifiant une chaîne de
format composite telle que : « {1} {0} {2} ». Un objet qui n'est pas référencé par un élément de format est
ignoré. Une FormatException est levée au moment de l’exécution si un spécificateur de paramètres désigne
un élément situé en dehors des limites de la liste d’objets.
Composant d'alignement
Le composant facultatif alignment est un entier signé indiquant la largeur préférée du champ mis en forme.
Si la valeur du composant alignment est inférieure à la longueur de la chaîne mise en forme, alignmentest
ignoré et la longueur de la chaîne mise en forme est utilisée comme largeur de champ. Les données mises en
forme dans le champ sont alignées à droite si alignment est positif et alignées à gauche si alignment est
négatif. Si un remplissage est nécessaire, des espaces blancs sont utilisés. La virgule est obligatoire si
alignment est spécifié.
L'exemple suivant définit deux tableaux, l'un contenant les noms des employés et l'autre contenant les
heures qu'ils ont prestées sur une période de deux semaines. La chaîne de format composite aligne les noms
à gauche dans un champ de 20 caractères et aligne leurs heures à droite dans un champ de 5 caractères.
Notez que la chaîne de format standard "N1" est également utilisée pour mettre les heures sous la forme
d'un nombre avec une décimale.

using System;

public class Example


{
public static void Main()
{
string[] names = { "Adam", "Bridgette", "Carla", "Daniel",
"Ebenezer", "Francine", "George" };
decimal[] hours = { 40, 6.667m, 40.39m, 82, 40.333m, 80,
16.75m };

Console.WriteLine("{0,-20} {1,5}\n", "Name", "Hours");


for (int ctr = 0; ctr < names.Length; ctr++)
Console.WriteLine("{0,-20} {1,5:N1}", names[ctr], hours[ctr]);
}
}
// The example displays the following output:
// Name Hours
//
// Adam 40.0
// Bridgette 6.7
// Carla 40.4
// Daniel 82.0
// Ebenezer 40.3
// Francine 80.0
// George 16.8
Module Example
Public Sub Main()
Dim names() As String = {"Adam", "Bridgette", "Carla", "Daniel",
"Ebenezer", "Francine", "George"}
Dim hours() As Decimal = {40, 6.667d, 40.39d, 82, 40.333d, 80,
16.75d}

Console.WriteLine("{0,-20} {1,5}", "Name", "Hours")


Console.WriteLine()
For ctr As Integer = 0 To names.Length - 1
Console.WriteLine("{0,-20} {1,5:N1}", names(ctr), hours(ctr))
Next
End Sub
End Module
' The example displays the following output:
' Name Hours
'
' Adam 40.0
' Bridgette 6.7
' Carla 40.4
' Daniel 82.0
' Ebenezer 40.3
' Francine 80.0
' George 16.8

Composant de chaîne de format


Le composant facultatif formatString est une chaîne de format appropriée pour le type d’objet mis en forme.
Spécifiez une chaîne de format numérique standard ou personnalisée si l'objet correspondant est une valeur
numérique, une chaîne de format de date et d'heure standard ou personnalisée si l'objet correspondant est
un objet DateTime, ou une chaîne de format d'énumération si l'objet correspondant est une valeur
d'énumération. Si formatString n’est pas spécifié, le spécificateur de format général (« G ») pour un type
numérique, de date et d’heure ou d’énumération est utilisé. Le point est obligatoire si formatString est
spécifié.
Le tableau suivant répertorie les types ou les catégories de types dans la bibliothèque de classes
.NET Framework qui prennent en charge un ensemble prédéfini de chaînes de format, et fournit des liens
vers les rubriques qui répertorient les chaînes de format prises en charge. Notez que la mise en forme de
chaînes est un mécanisme extensible qui permet de définir de nouvelles chaînes de format pour tous les
types existants et de définir un ensemble de chaînes de format pris en charge par un type défini par
l'application. Pour plus d'informations, consultez les rubriques sur l'interface IFormattable et
ICustomFormatter.

T Y P E O U C AT ÉGO RIE DE T Y P E C O N SULT EZ

Types de date et d'heure (DateTime, DateTimeOffset) Chaînes de format de date et d’heure standard

Chaînes de format de date et d’heure personnalisées

Types d'énumération (tous les types dérivés de Chaînes de format d’énumération


System.Enum)

Types numériques (BigInteger, Byte, Decimal, Double, Chaînes de format numériques standard
Int16, Int32, Int64, SByte, Single, UInt16, UInt32, UInt64)
Chaînes de format numériques personnalisées

Guid Guid.ToString(String)
T Y P E O U C AT ÉGO RIE DE T Y P E C O N SULT EZ

TimeSpan Chaînes de format TimeSpan standard

Chaînes de format TimeSpan personnalisées

Accolades d'échappement
Les accolades ouvrantes et fermantes sont interprétées comme le début et la fin d'un élément de format. Par
conséquent, vous devez utiliser une séquence d'échappement pour afficher une accolade ouvrante ou
fermante littérale. Spécifiez deux accolades ouvrantes (« {{ ») dans le texte fixe pour afficher une accolade
ouvrante (« { ») ou deux accolades fermantes (« }} ») pour afficher une accolade fermante (« } »). Les
accolades d'un élément de format sont interprétées séquentiellement dans l'ordre dans lequel elles sont
rencontrées. L'interprétation des accolades imbriquées n'est pas prise en charge.
La façon dont les accolades d'échappement sont interprétées peut générer des résultats inattendus. Par
exemple, considérez l'élément de format « {{{0:D}}} » destiné à afficher une accolade ouvrante, une valeur
numérique mise en forme en tant que nombre décimal et une accolade fermante. Toutefois, l'élément de
format est réellement interprété de la manière suivante :
1. Les deux premières accolades ouvrantes (« {{ ») font l'objet d'un échappement et produisent une
accolade ouvrante.
2. Les trois caractères suivants (« {0: ») sont interprétés comme le début d'un élément de format.
3. Le caractère suivant (« D ») devrait être interprété comme le spécificateur de format numérique
standard Decimal, mais les deux accolades d'échappement suivantes (« }} ») produisent une seule
accolade. Comme la chaîne résultante (« D} ») n'est pas un spécificateur de format numérique
standard, elle est interprétée comme une chaîne de mise en forme personnalisée qui sous-entend
l'affichage de la chaîne littérale « D} ».
4. La dernière accolade (« } ») est interprétée comme la fin de l'élément de format.
5. Le résultat final affiché est la chaîne littérale, « {D} ». La valeur numérique qui devait être mise en
forme n'est pas affichée.
Pour éviter une mauvaise interprétation des accolades d'échappement et des éléments de format, mettez en
forme séparément les accolades et l'élément de format. Autrement dit, dans la première opération de
formatage, affichez une accolade ouvrante littérale, dans l'opération suivante, affichez le résultat de l'élément
de format, puis dans la dernière opération, affichez une accolade fermante littérale. L'exemple suivant illustre
cette approche.

int value = 6324;


string output = string.Format("{0}{1:D}{2}",
"{", value, "}");
Console.WriteLine(output);
// The example displays the following output:
// {6324}

Dim value As Integer = 6324


Dim output As String = String.Format("{0}{1:D}{2}", _
"{", value, "}")
Console.WriteLine(output)
' The example displays the following output:
' {6324}

Ordre de traitement
Si l'appel à la méthode de mise en forme composite comprend un argument IFormatProvider dont la valeur
n'est pas null , le runtime appelle sa méthode IFormatProvider.GetFormat pour demander une
implémentation de ICustomFormatter. Si la méthode est en mesure de retourner une implémentation de
ICustomFormatter, elle est mise en cache durant l’appel de la méthode de mise en forme composite.
Chaque valeur de la liste de paramètres qui correspond à un élément de mise en forme est convertie en une
chaîne de la manière suivante :
1. Si la valeur à mettre en forme est null , une chaîne vide String.Empty est retournée.
2. Si une implémentation de ICustomFormatter est disponible, le runtime appelle sa méthode Format. Il
passe à la méthode la valeur formatString de l’élément de mise en forme, s’il en existe une, ou null si
ce n’est pas le cas, ainsi que l’implémentation de IFormatProvider. Si l’appel à la méthode
ICustomFormatter.Format retourne null , l’exécution se poursuit à l’étape suivante. Sinon, le résultat
de l’appel à ICustomFormatter.Format est retourné.
3. Si la valeur implémente l'interface IFormattable, la méthode de l'interface ToString(String,
IFormatProvider) est appelée. La valeur formatString, s’il en existe une dans l’élément de mise en
forme, est passée à la méthode, ou bien la valeur null si ce n’est pas le cas. L'argument
IFormatProvider est déterminé comme suit :
Pour une valeur numérique, si une méthode de mise en forme composite avec l’argument non
null IFormatProvider est appelée, le runtime demande un objet NumberFormatInfo de sa
méthode IFormatProvider.GetFormat. S’il ne peut pas en fournir un, si la valeur de l’argument
est null ou si la méthode de mise en forme composite n’a pas de paramètre IFormatProvider,
l’objet NumberFormatInfo de la culture actuelle du thread est utilisé.
Pour une valeur de date et d’heure, si une méthode de mise en forme composite avec
l’argument non null IFormatProvider est appelée, le runtime demande un objet
DateTimeFormatInfo de sa méthode IFormatProvider.GetFormat. S’il ne peut pas en fournir un,
si la valeur de l’argument est null ou si la méthode de mise en forme composite n’a pas de
paramètre IFormatProvider, l’objet DateTimeFormatInfo de la culture actuelle du thread est
utilisé.
Pour les objets d'autres types, si une méthode de mise en forme composite est appelée avec un
argument IFormatProvider, sa valeur est transmise directement à l'implémentation de
IFormattable.ToString. Dans le cas contraire, null est transmis à l’implémentation de
IFormattable.ToString.
4. La méthode ToString sans paramètre du type, qui remplace Object.ToString() ou hérite du
comportement de la classe de base, est appelée. Dans ce cas, la chaîne de format spécifiée par le
composant formatString dans l’élément de mise en forme, si elle est présente, est ignorée.
L'alignement est appliqué une fois les précédentes étapes effectuées.

Exemples de code
L'exemple suivant illustre une chaîne créée à l'aide de la mise en forme composite et une autre chaîne créée
à l'aide de la méthode ToString d'un objet. Les deux types de mise en forme produisent des résultats
équivalents.

string FormatString1 = String.Format("{0:dddd MMMM}", DateTime.Now);


string FormatString2 = DateTime.Now.ToString("dddd MMMM");
Dim FormatString1 As String = String.Format("{0:dddd MMMM}", DateTime.Now)
Dim FormatString2 As String = DateTime.Now.ToString("dddd MMMM")

En supposant que le jour actuel soit un jeudi du mois de mai, la valeur des deux chaînes de l'exemple
précédent est Thursday May dans la culture américaine.
Console.WriteLine présente les mêmes fonctionnalités que String.Format. La seule différence entre les deux
méthodes est que String.Format retourne son résultat sous la forme d'une chaîne, alors que
Console.WriteLine écrit le résultat dans le flux de sortie associé à l'objet Console. L'exemple suivant utilise la
méthode Console.WriteLine pour mettre en forme la valeur de MyInt en une valeur monétaire.

int MyInt = 100;


Console.WriteLine("{0:C}", MyInt);
// The example displays the following output
// if en-US is the current culture:
// $100.00

Dim MyInt As Integer = 100


Console.WriteLine("{0:C}", MyInt)
' The example displays the following output
' if en-US is the current culture:
' $100.00

L'exemple suivant illustre la mise en forme de plusieurs objets, y compris la mise en forme d'un objet de
deux manières différentes.

string myName = "Fred";


Console.WriteLine(String.Format("Name = {0}, hours = {1:hh}, minutes = {1:mm}",
myName, DateTime.Now));
// Depending on the current time, the example displays output like the following:
// Name = Fred, hours = 11, minutes = 30

Dim myName As String = "Fred"


Console.WriteLine(String.Format("Name = {0}, hours = {1:hh}, minutes = {1:mm}", _
myName, DateTime.Now))
' Depending on the current time, the example displays output like the following:
' Name = Fred, hours = 11, minutes = 30

L'exemple suivant illustre l'utilisation de l'alignement lors de la mise en forme. Les arguments mis en forme
sont placés entre des barres verticales (|) pour mettre en évidence l’alignement en résultant.
string myFName = "Fred";
string myLName = "Opals";
int myInt = 100;
string FormatFName = String.Format("First Name = |{0,10}|", myFName);
string FormatLName = String.Format("Last Name = |{0,10}|", myLName);
string FormatPrice = String.Format("Price = |{0,10:C}|", myInt);
Console.WriteLine(FormatFName);
Console.WriteLine(FormatLName);
Console.WriteLine(FormatPrice);
Console.WriteLine();

FormatFName = String.Format("First Name = |{0,-10}|", myFName);


FormatLName = String.Format("Last Name = |{0,-10}|", myLName);
FormatPrice = String.Format("Price = |{0,-10:C}|", myInt);
Console.WriteLine(FormatFName);
Console.WriteLine(FormatLName);
Console.WriteLine(FormatPrice);
// The example displays the following output on a system whose current
// culture is en-US:
// First Name = | Fred|
// Last Name = | Opals|
// Price = | $100.00|
//
// First Name = |Fred |
// Last Name = |Opals |
// Price = |$100.00 |

Dim myFName As String = "Fred"


Dim myLName As String = "Opals"

Dim myInt As Integer = 100


Dim FormatFName As String = String.Format("First Name = |{0,10}|", myFName)
Dim FormatLName As String = String.Format("Last Name = |{0,10}|", myLName)
Dim FormatPrice As String = String.Format("Price = |{0,10:C}|", myInt)
Console.WriteLine(FormatFName)
Console.WriteLine(FormatLName)
Console.WriteLine(FormatPrice)
Console.WriteLine()

FormatFName = String.Format("First Name = |{0,-10}|", myFName)


FormatLName = String.Format("Last Name = |{0,-10}|", myLName)
FormatPrice = String.Format("Price = |{0,-10:C}|", myInt)
Console.WriteLine(FormatFName)
Console.WriteLine(FormatLName)
Console.WriteLine(FormatPrice)
' The example displays the following output on a system whose current
' culture is en-US:
' First Name = | Fred|
' Last Name = | Opals|
' Price = | $100.00|
'
' First Name = |Fred |
' Last Name = |Opals |
' Price = |$100.00 |

Voir aussi
WriteLine
String.Format
Interpolation de chaîne (C#)
Interpolation de chaîne (Visual Basic)
Mise en forme des types
Chaînes de format numériques standard
Chaînes de format numériques personnalisées
Chaînes de format de date et d’heure standard
Chaînes de format de date et d’heure personnalisées
Chaînes de format TimeSpan standard
Chaînes de format TimeSpan personnalisées
Chaînes de format d’énumération
Procédure : remplir un nombre avec des zéros non
significatifs
18/07/2020 • 10 minutes to read • Edit Online

Vous pouvez ajouter des zéros non significatifs à un entier en utilisant la chaîne de format numérique standard
« D » avec un spécificateur de précision. Vous pouvez ajouter des zéros non significatifs aux nombres entiers et à
virgule flottante en utilisant une chaîne de format numérique personnalisée. Cet article montre comment utiliser
les deux méthodes pour remplir un nombre avec des zéros non significatifs.

Pour remplir un entier avec des zéros non significatifs dans la limite
d'une longueur spécifique
1. Déterminez le nombre minimal de chiffres que la valeur entière doit afficher. Incluez des chiffres non
significatifs dans ce nombre.
2. Déterminez si vous souhaitez afficher l'entier en tant que valeur décimale ou que valeur hexadécimale.
Pour afficher l’entier comme valeur décimale, appelez sa méthode ToString(String) , puis passez la
chaîne « Dn » comme valeur du paramètre format , où n représente la longueur minimale de la
chaîne.
Pour afficher l’entier comme valeur hexadécimale, appelez sa méthode ToString(String) , puis
transmettez la chaîne « Xn » comme valeur du paramètre de format, où n représente la longueur
minimale de la chaîne.
Vous pouvez également utiliser la chaîne de format dans une chaîne interpolée dans C# et Visual Basic, ou vous
pouvez appeler une méthode, telle que String.Format ou Console.WriteLine, qui utilise la mise en forme composite.
L'exemple suivant met en forme plusieurs valeurs entières avec des zéros non significatifs de manière à ce que la
longueur totale du nombre mis en forme soit au moins égale à huit caractères.
byte byteValue = 254;
short shortValue = 10342;
int intValue = 1023983;
long lngValue = 6985321;
ulong ulngValue = UInt64.MaxValue;

// Display integer values by calling the ToString method.


Console.WriteLine("{0,22} {1,22}", byteValue.ToString("D8"), byteValue.ToString("X8"));
Console.WriteLine("{0,22} {1,22}", shortValue.ToString("D8"), shortValue.ToString("X8"));
Console.WriteLine("{0,22} {1,22}", intValue.ToString("D8"), intValue.ToString("X8"));
Console.WriteLine("{0,22} {1,22}", lngValue.ToString("D8"), lngValue.ToString("X8"));
Console.WriteLine("{0,22} {1,22}", ulngValue.ToString("D8"), ulngValue.ToString("X8"));
Console.WriteLine();

// Display the same integer values by using composite formatting.


Console.WriteLine("{0,22:D8} {0,22:X8}", byteValue);
Console.WriteLine("{0,22:D8} {0,22:X8}", shortValue);
Console.WriteLine("{0,22:D8} {0,22:X8}", intValue);
Console.WriteLine("{0,22:D8} {0,22:X8}", lngValue);
Console.WriteLine("{0,22:D8} {0,22:X8}", ulngValue);
// The example displays the following output:
// 00000254 000000FE
// 00010342 00002866
// 01023983 000F9FEF
// 06985321 006A9669
// 18446744073709551615 FFFFFFFFFFFFFFFF
//
// 00000254 000000FE
// 00010342 00002866
// 01023983 000F9FEF
// 06985321 006A9669
// 18446744073709551615 FFFFFFFFFFFFFFFF
// 18446744073709551615 FFFFFFFFFFFFFFFF
Dim byteValue As Byte = 254
Dim shortValue As Short = 10342
Dim intValue As Integer = 1023983
Dim lngValue As Long = 6985321
Dim ulngValue As ULong = UInt64.MaxValue

' Display integer values by calling the ToString method.


Console.WriteLine("{0,22} {1,22}", byteValue.ToString("D8"), byteValue.ToString("X8"))
Console.WriteLine("{0,22} {1,22}", shortValue.ToString("D8"), shortValue.ToString("X8"))
Console.WriteLine("{0,22} {1,22}", intValue.ToString("D8"), intValue.ToString("X8"))
Console.WriteLine("{0,22} {1,22}", lngValue.ToString("D8"), lngValue.ToString("X8"))
Console.WriteLine("{0,22} {1,22}", ulngValue.ToString("D8"), ulngValue.ToString("X8"))
Console.WriteLine()

' Display the same integer values by using composite formatting.


Console.WriteLine("{0,22:D8} {0,22:X8}", byteValue)
Console.WriteLine("{0,22:D8} {0,22:X8}", shortValue)
Console.WriteLine("{0,22:D8} {0,22:X8}", intValue)
Console.WriteLine("{0,22:D8} {0,22:X8}", lngValue)
Console.WriteLine("{0,22:D8} {0,22:X8}", ulngValue)
' The example displays the following output:
' 00000254 000000FE
' 00010342 00002866
' 01023983 000F9FEF
' 06985321 006A9669
' 18446744073709551615 FFFFFFFFFFFFFFFF
'
' 00000254 000000FE
' 00010342 00002866
' 01023983 000F9FEF
' 06985321 006A9669
' 18446744073709551615 FFFFFFFFFFFFFFFF

Pour remplir un entier avec un nombre spécifique de zéros non


significatifs
1. Déterminez le nombre de zéros non significatifs que la valeur entière doit afficher.
2. Déterminez si vous souhaitez afficher l'entier en tant que valeur décimale ou que valeur hexadécimale.
Une mise en forme en tant que valeur décimale nécessite l’utilisation du spécificateur de format
standard « D ».
Une mise en forme en tant que valeur hexadécimale nécessite l’utilisation du spécificateur de format
standard « X ».
3. Déterminez la longueur de la chaîne numérique non remplie en appelant la méthode ToString("D").Length
ou ToString("X").Length de la valeur entière.
4. Ajoutez le nombre de zéros non significatifs à inclure dans la chaîne mise en forme à la longueur de la
chaîne numérique non remplie. L’ajout du nombre de zéros non significatifs définit la longueur totale de la
chaîne remplie.
5. Appelez la méthode ToString(String) de la valeur entière, puis transmettez la chaîne « Dn » pour les
chaînes décimales et la chaîne « Xn » pour les chaînes hexadécimales, où n représente la longueur totale de
la chaîne remplie. Vous pouvez également utiliser la chaîne de format « Dn » ou « Xn » dans une méthode
qui prend en charge la mise en forme composite.
L'exemple suivant remplit une valeur entière avec cinq zéros non significatifs.
int value = 160934;
int decimalLength = value.ToString("D").Length + 5;
int hexLength = value.ToString("X").Length + 5;
Console.WriteLine(value.ToString("D" + decimalLength.ToString()));
Console.WriteLine(value.ToString("X" + hexLength.ToString()));
// The example displays the following output:
// 00000160934
// 00000274A6

Dim value As Integer = 160934


Dim decimalLength As Integer = value.ToString("D").Length + 5
Dim hexLength As Integer = value.ToString("X").Length + 5
Console.WriteLine(value.ToString("D" + decimalLength.ToString()))
Console.WriteLine(value.ToString("X" + hexLength.ToString()))
' The example displays the following output:
' 00000160934
' 00000274A6

Pour remplir une valeur numérique avec des zéros non significatifs dans
la limite d'une longueur spécifique
1. Déterminez le nombre de chiffres à gauche du séparateur décimal qui doivent apparaître dans la
représentation du nombre sous forme de chaîne. Incluez des zéros non significatifs dans ce nombre total de
chiffres.
2. Définissez une chaîne de format numérique personnalisée qui utilise l’espace réservé du zéro « 0 » pour
représenter le nombre minimal de zéros.
3. Appelez la méthode ToString(String) du nombre et transmettez-lui la chaîne de format personnalisée.
Vous pouvez également utiliser la chaîne de format personnalisée avec l’interpolation de chaîne ou avec une
méthode qui prend en charge la mise en forme composite.
L’exemple suivant met en forme plusieurs valeurs numériques avec des zéros non significatifs. Par conséquent, la
longueur totale du nombre mis en forme est de huit chiffres minimum à gauche du séparateur décimal.
string fmt = "00000000.##";
int intValue = 1053240;
decimal decValue = 103932.52m;
float sngValue = 1549230.10873992f;
double dblValue = 9034521202.93217412;

// Display the numbers using the ToString method.


Console.WriteLine(intValue.ToString(fmt));
Console.WriteLine(decValue.ToString(fmt));
Console.WriteLine(sngValue.ToString(fmt));
Console.WriteLine(dblValue.ToString(fmt));
Console.WriteLine();

// Display the numbers using composite formatting.


string formatString = " {0,15:" + fmt + "}";
Console.WriteLine(formatString, intValue);
Console.WriteLine(formatString, decValue);
Console.WriteLine(formatString, sngValue);
Console.WriteLine(formatString, dblValue);
// The example displays the following output:
// 01053240
// 00103932.52
// 01549230
// 9034521202.93
//
// 01053240
// 00103932.52
// 01549230
// 9034521202.93

Dim fmt As String = "00000000.##"


Dim intValue As Integer = 1053240
Dim decValue As Decimal = 103932.52d
Dim sngValue As Single = 1549230.10873992
Dim dblValue As Double = 9034521202.93217412

' Display the numbers using the ToString method.


Console.WriteLine(intValue.ToString(fmt))
Console.WriteLine(decValue.ToString(fmt))
Console.WriteLine(sngValue.ToString(fmt))
Console.WriteLine(dblValue.ToString(fmt))
Console.WriteLine()

' Display the numbers using composite formatting.


Dim formatString As String = " {0,15:" + fmt + "}"
Console.WriteLine(formatString, intValue)
Console.WriteLine(formatString, decValue)
Console.WriteLine(formatString, sngValue)
Console.WriteLine(formatString, dblValue)
' The example displays the following output:
' 01053240
' 00103932.52
' 01549230
' 9034521202.93
'
' 01053240
' 00103932.52
' 01549230
' 9034521202.93

Pour remplir une valeur numérique avec un nombre spécifique de zéros


non significatifs
1. Déterminez le nombre de zéros non significatifs que la valeur numérique doit avoir.
2. Déterminez le nombre de chiffres à gauche du séparateur décimal dans la chaîne numérique non remplie :
a. Déterminez si la représentation sous forme de chaîne d'un nombre comprend un séparateur décimal.
b. Si tel est le cas, déterminez le nombre de caractères à gauche du séparateur décimal.
-ou-
Sinon, déterminez la longueur de la chaîne.
3. Créez une chaîne de format personnalisée qui utilise :
L’espace réservé du zéro « 0 » pour chaque zéro non significatif qui apparaît dans la chaîne.
L’espace réservé du zéro ou l’espace réservé de chiffre « # » pour représenter chaque chiffre dans la
chaîne par défaut.
4. Fournissez la chaîne de format personnalisée comme paramètre à la méthode ToString(String) du nombre
ou à une méthode qui prend en charge la mise en forme composite.
L'exemple suivant remplit deux valeurs Double avec cinq zéros non significatifs.

double[] dblValues = { 9034521202.93217412, 9034521202 };


foreach (double dblValue in dblValues)
{
string decSeparator = System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator;
string fmt, formatString;

if (dblValue.ToString().Contains(decSeparator))
{
int digits = dblValue.ToString().IndexOf(decSeparator);
fmt = new String('0', 5) + new String('#', digits) + ".##";
}
else
{
fmt = new String('0', dblValue.ToString().Length);
}
formatString = "{0,20:" + fmt + "}";

Console.WriteLine(dblValue.ToString(fmt));
Console.WriteLine(formatString, dblValue);
}
// The example displays the following output:
// 000009034521202.93
// 000009034521202.93
// 9034521202
// 9034521202
Dim dblValues() As Double = {9034521202.93217412, 9034521202}
For Each dblValue As Double In dblValues
Dim decSeparator As String = System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator
Dim fmt, formatString As String

If dblValue.ToString.Contains(decSeparator) Then
Dim digits As Integer = dblValue.ToString().IndexOf(decSeparator)
fmt = New String("0"c, 5) + New String("#"c, digits) + ".##"
Else
fmt = New String("0"c, dblValue.ToString.Length)
End If
formatString = "{0,20:" + fmt + "}"

Console.WriteLine(dblValue.ToString(fmt))
Console.WriteLine(formatString, dblValue)
Next
' The example displays the following output:
' 000009034521202.93
' 000009034521202.93
' 9034521202
' 9034521202

Voir aussi
Chaînes de format numériques personnalisées
Chaînes de format numériques standard
Mise en forme composite
Procédure : extraire le jour de la semaine d’une date
spécifique
18/07/2020 • 12 minutes to read • Edit Online

Le .NET Framework permet de déterminer facilement le jour ordinal de la semaine pour une date particulière, et
d'afficher le nom du jour de la semaine localisé pour une date particulière. Le jour de la semaine correspondant à
une date particulière est indiqué par une valeur énumérée contenue dans la propriété DayOfWeek ou DayOfWeek.
Par contre, la récupération du nom du jour de la semaine est une opération de mise en forme qui peut être
effectuée en appelant une méthode de mise en forme, comme la méthode ToString d'une valeur de date et
d'heure ou la méthode String.Format. Cette rubrique montre comment effectuer ces opérations de mise en forme.
Pour extraire un nombre indiquant le jour de la semaine d'une date spécifique
1. Si la date est représentée sous forme de chaîne, convertissez-la en une valeur DateTime ou DateTimeOffset
en utilisant la méthode DateTime.Parse ou DateTimeOffset.Parse statique.
2. Utilisez la propriété DateTime.DayOfWeek ou DateTimeOffset.DayOfWeek pour récupérer une valeur
DayOfWeek qui indique le jour de la semaine.
3. Au besoin, effectuez un cast (en C#) ou une conversion (en Visual Basic) de la valeur DayOfWeek en un
entier.
L'exemple suivant affiche un entier qui représente le jour de la semaine d'une date spécifique.

using System;

public class Example


{
public static void Main()
{
DateTime dateValue = new DateTime(2008, 6, 11);
Console.WriteLine((int) dateValue.DayOfWeek);
}
}
// The example displays the following output:
// 3

Module Example
Public Sub Main()
Dim dateValue As Date = #6/11/2008#
Console.WriteLine(dateValue.DayOfWeek)
End Sub
End Module
' The example displays the following output:
' 3

Pour extraire le nom abrégé du jour de la semaine d'une date spécifique


1. Si la date est représentée sous forme de chaîne, convertissez-la en une valeur DateTime ou DateTimeOffset
en utilisant la méthode DateTime.Parse ou DateTimeOffset.Parse statique.
2. Vous pouvez extraire le nom abrégé du jour de la semaine au format de la culture actuelle ou d'une culture
spécifique :
a. Pour extraire le nom abrégé du jour de la semaine au format de la culture actuelle, appelez la
méthode d'instance DateTime.ToString(String) ou DateTimeOffset.ToString(String) de la valeur de date
et d'heure, puis indiquez la chaîne « ddd » comme paramètre format . L'exemple suivant illustre
l'appel de la méthode ToString(String).

using System;

public class Example


{
public static void Main()
{
DateTime dateValue = new DateTime(2008, 6, 11);
Console.WriteLine(dateValue.ToString("ddd"));
}
}
// The example displays the following output:
// Wed

Module Example
Public Sub Main()
Dim dateValue As Date = #6/11/2008#
Console.WriteLine(dateValue.ToString("ddd"))
End Sub
End Module
' The example displays the following output:
' Wed

b. Pour extraire le nom abrégé du jour de la semaine pour une culture spécifique, appelez la méthode
d’instance ou de la valeur de date et d’heure DateTime.ToString(String, IFormatProvider)
DateTimeOffset.ToString(String, IFormatProvider) . Indiquez la chaîne « ddd » comme paramètre
format . Indiquez comme paramètre CultureInfo un objet DateTimeFormatInfo ou provider qui
représente la culture au format de laquelle vous souhaitez récupérer le nom du jour de la semaine. Le
code suivant illustre un appel de la méthode ToString(String, IFormatProvider) à l'aide d'un objet
CultureInfo qui représente la culture fr-FR.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
DateTime dateValue = new DateTime(2008, 6, 11);
Console.WriteLine(dateValue.ToString("ddd",
new CultureInfo("fr-FR")));
}
}
// The example displays the following output:
// mer.
Imports System.Globalization

Module Example
Public Sub Main()
Dim dateValue As Date = #6/11/2008#
Console.WriteLine(dateValue.ToString("ddd",
New CultureInfo("fr-FR")))
End Sub
End Module
' The example displays the following output:
' mer.

Pour extraire le nom complet du jour de la semaine d'une date spécifique


1. Si la date est représentée sous forme de chaîne, convertissez-la en une valeur DateTime ou DateTimeOffset
en utilisant la méthode DateTime.Parse ou DateTimeOffset.Parse statique.
2. Vous pouvez extraire le nom complet du jour de la semaine au format de la culture actuelle ou d'une culture
spécifique :
a. Pour extraire le nom du jour de la semaine pour la culture actuelle, appelez la méthode d’instance ou
de la valeur de date et d’heure DateTime.ToString(String) DateTimeOffset.ToString(String) , puis
transmettez la chaîne "dddd" comme format paramètre. L'exemple suivant illustre l'appel de la
méthode ToString(String).

using System;

public class Example


{
public static void Main()
{
DateTime dateValue = new DateTime(2008, 6, 11);
Console.WriteLine(dateValue.ToString("dddd"));
}
}
// The example displays the following output:
// Wednesday

Module Example
Public Sub Main()
Dim dateValue As Date = #6/11/2008#
Console.WriteLine(dateValue.ToString("dddd"))
End Sub
End Module
' The example displays the following output:
' Wednesday

b. Pour extraire le nom du jour de la semaine pour une culture spécifique, appelez la
DateTime.ToString(String, IFormatProvider) méthode d’instance ou de la valeur de date et d’heure
DateTimeOffset.ToString(String, IFormatProvider) . Indiquez la chaîne « dddd » comme paramètre
format . Indiquez comme paramètre CultureInfo un objet DateTimeFormatInfo ou provider qui
représente la culture au format de laquelle vous souhaitez récupérer le nom du jour de la semaine. Le
code suivant illustre un appel de la méthode ToString(String, IFormatProvider) à l'aide d'un objet
CultureInfo qui représente la culture es-ES.
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
DateTime dateValue = new DateTime(2008, 6, 11);
Console.WriteLine(dateValue.ToString("dddd",
new CultureInfo("es-ES")));
}
}
// The example displays the following output:
// miércoles.

Imports System.Globalization

Module Example
Public Sub Main()
Dim dateValue As Date = #6/11/2008#
Console.WriteLine(dateValue.ToString("dddd", _
New CultureInfo("es-ES")))
End Sub
End Module
' The example displays the following output:
' miércoles.

Exemple
L'exemple montre comment appeler les propriétés DateTime.DayOfWeek et DateTimeOffset.DayOfWeek et les
méthodes DateTime.ToString et DateTimeOffset.ToString pour récupérer le nombre qui représente le jour de la
semaine, le nom abrégé du jour de la semaine et le nom complet du jour de la semaine pour une date particulière.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
string dateString = "6/11/2007";
DateTime dateValue;
DateTimeOffset dateOffsetValue;

try
{
DateTimeFormatInfo dateTimeFormats;
// Convert date representation to a date value
dateValue = DateTime.Parse(dateString, CultureInfo.InvariantCulture);
dateOffsetValue = new DateTimeOffset(dateValue,
TimeZoneInfo.Local.GetUtcOffset(dateValue));

// Convert date representation to a number indicating the day of week


Console.WriteLine((int) dateValue.DayOfWeek);
Console.WriteLine((int) dateOffsetValue.DayOfWeek);

// Display abbreviated weekday name using current culture


Console.WriteLine(dateValue.ToString("ddd"));
Console.WriteLine(dateOffsetValue.ToString("ddd"));

// Display full weekday name using current culture


Console.WriteLine(dateValue.ToString("dddd"));
Console.WriteLine(dateOffsetValue.ToString("dddd"));
Console.WriteLine(dateOffsetValue.ToString("dddd"));

// Display abbreviated weekday name for de-DE culture


Console.WriteLine(dateValue.ToString("ddd", new CultureInfo("de-DE")));
Console.WriteLine(dateOffsetValue.ToString("ddd",
new CultureInfo("de-DE")));

// Display abbreviated weekday name with de-DE DateTimeFormatInfo object


dateTimeFormats = new CultureInfo("de-DE").DateTimeFormat;
Console.WriteLine(dateValue.ToString("ddd", dateTimeFormats));
Console.WriteLine(dateOffsetValue.ToString("ddd", dateTimeFormats));

// Display full weekday name for fr-FR culture


Console.WriteLine(dateValue.ToString("ddd", new CultureInfo("fr-FR")));
Console.WriteLine(dateOffsetValue.ToString("ddd",
new CultureInfo("fr-FR")));

// Display abbreviated weekday name with fr-FR DateTimeFormatInfo object


dateTimeFormats = new CultureInfo("fr-FR").DateTimeFormat;
Console.WriteLine(dateValue.ToString("dddd", dateTimeFormats));
Console.WriteLine(dateOffsetValue.ToString("dddd", dateTimeFormats));
}
catch (FormatException)
{
Console.WriteLine("Unable to convert {0} to a date.", dateString);
}
}
}
// The example displays the following output:
// 1
// 1
// Mon
// Mon
// Monday
// Monday
// Mo
// Mo
// Mo
// Mo
// lun.
// lun.
// lundi
// lundi
Imports System.Globalization

Module Example
Public Sub Main()
Dim dateString As String = "6/11/2007"
Dim dateValue As Date
Dim dateOffsetValue As DateTimeOffset

Try
Dim dateTimeFormats As DateTimeFormatInfo
' Convert date representation to a date value
dateValue = Date.Parse(dateString, CultureInfo.InvariantCulture)
dateOffsetValue = New DateTimeOffset(dateValue, _
TimeZoneInfo.Local.GetUtcOffset(dateValue))
' Convert date representation to a number indicating the day of week
Console.WriteLine(dateValue.DayOfWeek)
Console.WriteLine(dateOffsetValue.DayOfWeek)

' Display abbreviated weekday name using current culture


Console.WriteLine(dateValue.ToString("ddd"))
Console.WriteLine(dateOffsetValue.ToString("ddd"))

' Display full weekday name using current culture


Console.WriteLine(dateValue.ToString("dddd"))
Console.WriteLine(dateOffsetValue.ToString("dddd"))

' Display abbreviated weekday name for de-DE culture


Console.WriteLine(dateValue.ToString("ddd", New CultureInfo("de-DE")))
Console.WriteLine(dateOffsetValue.ToString("ddd", _
New CultureInfo("de-DE")))

' Display abbreviated weekday name with de-DE DateTimeFormatInfo object


dateTimeFormats = New CultureInfo("de-DE").DateTimeFormat
Console.WriteLine(dateValue.ToString("ddd", dateTimeFormats))
Console.WriteLine(dateOffsetValue.ToString("ddd", dateTimeFormats))

' Display full weekday name for fr-FR culture


Console.WriteLine(dateValue.ToString("ddd", New CultureInfo("fr-FR")))
Console.WriteLine(dateOffsetValue.ToString("ddd", _
New CultureInfo("fr-FR")))

' Display abbreviated weekday name with fr-FR DateTimeFormatInfo object


dateTimeFormats = New CultureInfo("fr-FR").DateTimeFormat
Console.WriteLine(dateValue.ToString("dddd", dateTimeFormats))
Console.WriteLine(dateOffsetValue.ToString("dddd", dateTimeFormats))
Catch e As FormatException
Console.WriteLine("Unable to convert {0} to a date.", dateString)
End Try
End Sub
End Module
' The example displays the following output to the console:
' 1
' 1
' Mon
' Mon
' Monday
' Monday
' Mo
' Mo
' Mo
' Mo
' lun.
' lun.
' lundi
' lundi
Des langages spécifiques peuvent fournir des fonctionnalités similaires aux fonctionnalités offertes par le .NET
Framework, ou complémentaires de celles-ci. Par exemple, Visual Basic comprend deux de ces fonctions :
Weekday , qui retourne un nombre qui indique le jour de la semaine d'une date particulière. Il considère que
la valeur ordinale du premier jour de la semaine est un, tandis que la propriété DateTime.DayOfWeek
considère que cette valeur est égale à zéro.
WeekdayName , qui retourne, dans la culture actuelle, le nom du jour de la semaine correspondant au nombre
d'un jour de la semaine.
L'exemple suivant illustre l'utilisation des fonctions Visual Basic Weekday et WeekdayName .

Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
Dim dateValue As Date = #6/11/2008#

' Get weekday number using Visual Basic Weekday function


Console.WriteLine(Weekday(dateValue)) ' Displays 4
' Compare with .NET DateTime.DayOfWeek property
Console.WriteLine(dateValue.DayOfWeek) ' Displays 3

' Get weekday name using Weekday and WeekdayName functions


Console.WriteLine(WeekdayName(Weekday(dateValue))) ' Displays Wednesday

' Change culture to de-DE


Dim originalCulture As CultureInfo = Thread.CurrentThread.CurrentCulture
Thread.CurrentThread.CurrentCulture = New CultureInfo("de-DE")
' Get weekday name using Weekday and WeekdayName functions
Console.WriteLine(WeekdayName(Weekday(dateValue))) ' Displays Donnerstag

' Restore original culture


Thread.CurrentThread.CurrentCulture = originalCulture
End Sub
End Module

Vous pouvez également recourir à la valeur retournée par la propriété DateTime.DayOfWeek pour récupérer le
nom du jour de la semaine d'une date particulière. Pour ce faire, vous avez uniquement besoin d'appeler la
méthode ToString sur la valeur DayOfWeek retournée par la propriété. Toutefois, cette technique ne génère pas un
nom de jour de la semaine localisé dans la culture actuelle, comme l'illustre l'exemple suivant.
using System;
using System.Globalization;
using System.Threading;

public class Example


{
public static void Main()
{
// Change current culture to fr-FR
CultureInfo originalCulture = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");

DateTime dateValue = new DateTime(2008, 6, 11);


// Display the DayOfWeek string representation
Console.WriteLine(dateValue.DayOfWeek.ToString());
// Restore original current culture
Thread.CurrentThread.CurrentCulture = originalCulture;
}
}
// The example displays the following output:
// Wednesday

Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
' Change current culture to fr-FR
Dim originalCulture As CultureInfo = Thread.CurrentThread.CurrentCulture
Thread.CurrentThread.CurrentCulture = New CultureInfo("fr-FR")

Dim dateValue As Date = #6/11/2008#


' Display the DayOfWeek string representation
Console.WriteLine(dateValue.DayOfWeek.ToString())
' Restore original current culture
Thread.CurrentThread.CurrentCulture = originalCulture
End Sub
End Module
' The example displays the following output:
' Wednesday

Voir aussi
Chaînes de format de date et d’heure standard
Chaînes de format de date et d’heure personnalisées
Procédure : définir et utiliser des fournisseurs de
formats numériques personnalisés
18/07/2020 • 12 minutes to read • Edit Online

Le .NET Framework vous donne un contrôle étendu sur la représentation sous forme de chaîne de valeurs
numériques. Il prend en charge les fonctionnalités suivantes pour personnaliser le format des valeurs numériques :
Chaînes de format numériques standard, qui fournissent un ensemble prédéfini de formats pour convertir
des nombres dans leur représentation sous forme de chaîne. Vous pouvez les utiliser avec toute méthode de
mise en forme numérique, telle que Decimal.ToString(String), qui est dotée d’un paramètre format . Pour
plus d’informations, consultez Chaînes de format numériques standard.
Chaînes de format numériques personnalisées, qui fournissent un ensemble de symboles pouvant être
combinés pour définir des spécificateurs de format numériques personnalisés. Elles peuvent également être
utilisées avec toute méthode de mise en forme numérique, telle que Decimal.ToString(String), qui est dotée
d’un paramètre format . Pour plus d’informations, consultez Chaînes de format numériques personnalisées.
Personnalisez les objets CultureInfo ou NumberFormatInfo, qui définissent les symboles et les modèles de
format utilisés pour afficher les représentations sous forme de chaîne des valeurs numériques. Vous pouvez
les utiliser avec toute méthode de mise en forme numérique, telle que ToString, qui est dotée d’un paramètre
provider . En règle générale, le paramètre provider est utilisé pour spécifier une mise en forme propre à la
culture.
Dans certains cas (par exemple, quand une application doit afficher un numéro de compte mis en forme, un
numéro d’identification ou un code postal), ces trois techniques sont inappropriées. Le .NET Framework vous
permet également de définir un objet de mise en forme qui n’est ni un objet CultureInfo ni un objet
NumberFormatInfo pour déterminer la mise en forme d’une valeur numérique. Cette rubrique fournit des
instructions détaillées pour l’implémentation de ce type d’objet et fournit un exemple qui met en forme les
numéros de téléphone.
Pour définir un fournisseur de format personnalisé
1. Définir une classe qui implémente les inferfaces IFormatProvider et ICustomFormatter.
2. Implémentez la méthode IFormatProvider.GetFormat. GetFormat est une méthode de rappel que la méthode
de mise en forme (telle que la méthode String.Format(IFormatProvider, String, Object[])) appelle pour
récupérer l’objet qui est réellement chargé d’effectuer la mise en forme personnalisée. Une implémentation
type de GetFormat effectue les opérations suivantes :
a. Elle détermine si l’objet Type passé comme paramètre de méthode représente une interface
ICustomFormatter.
b. Si le paramètre représente l’interface ICustomFormatter, GetFormat retourne un objet qui
implémente l’interface ICustomFormatter chargée de fournir la mise en forme personnalisée. En
général, l’objet de mise en forme personnalisée retourne lui-même.
c. Si le paramètre ne représente pas l’interface ICustomFormatter, GetFormat retourne null .
3. Implémentez la méthode Format. Cette méthode est appelée par la méthode String.Format(IFormatProvider,
String, Object[]) et est chargée de retourner la représentation sous forme de chaîne d’un nombre. En général,
l’implémentation de la méthode implique les étapes suivantes :
a. Le cas échéant, vérifiez que la méthode est légitimement destinée à fournir des services de mise en
forme en examinant le paramètre provider . Pour les objets de mise en forme qui implémentent à la
fois IFormatProvider et ICustomFormatter, vous devez déterminer si le paramètre provider
correspond à l’objet de mise en forme actuel.
b. Déterminez si l’objet de mise en forme doit prendre en charge les spécificateurs de format
personnalisés. (Par exemple, un spécificateur de format « N » peut indiquer qu’un numéro de
téléphone américain doit être généré au format NANP, et un « I » peut indiquer une sortie au format E.
123 de la recommandation ITU-T.) Si les spécificateurs de format sont utilisés, la méthode doit gérer le
spécificateur de format spécifique. Ill est passé à la méthode dans le paramètre format . Si aucun
spécificateur n’est présent, la valeur du paramètre format est String.Empty.
c. Récupérez la valeur numérique passée à la méthode comme paramètre arg . Effectuez toute
opération requise pour le convertir en sa représentation sous forme de chaîne.
d. Retournez la représentation sous forme de chaîne du paramètre arg .
Pour utiliser un objet de mise en forme numérique personnalisé
1. Créez une instance de la classe de mise en forme personnalisée.
2. Appelez la méthode de mise en forme String.Format(IFormatProvider, String, Object[]) en lui passant l’objet
de mise en forme personnalisée, le spécificateur de mise en forme (ou String.Empty, si aucun n’est utilisé) et
la valeur numérique à mettre en forme.

Exemple
L’exemple suivant définit un fournisseur de format numérique personnalisé nommé TelephoneFormatter qui
convertit un nombre représentant un numéro de téléphone aux États-Unis au format NANP ou E.123
correspondant. La méthode gère deux spécificateurs de format, « N » (qui génère le format NANP) et « I » (qui
génère le format E.123 international).

using System;
using System.Globalization;

public class TelephoneFormatter : IFormatProvider, ICustomFormatter


{
public object GetFormat(Type formatType)
{
if (formatType == typeof(ICustomFormatter))
return this;
else
return null;
}

public string Format(string format, object arg, IFormatProvider formatProvider)


{
// Check whether this is an appropriate callback
if (! this.Equals(formatProvider))
return null;

// Set default format specifier


if (string.IsNullOrEmpty(format))
format = "N";

string numericString = arg.ToString();

if (format == "N")
{
if (numericString.Length <= 4)
return numericString;
else if (numericString.Length == 7)
return numericString.Substring(0, 3) + "-" + numericString.Substring(3, 4);
else if (numericString.Length == 10)
else if (numericString.Length == 10)
return "(" + numericString.Substring(0, 3) + ") " +
numericString.Substring(3, 3) + "-" + numericString.Substring(6);
else
throw new FormatException(
string.Format("'{0}' cannot be used to format {1}.",
format, arg.ToString()));
}
else if (format == "I")
{
if (numericString.Length < 10)
throw new FormatException(string.Format("{0} does not have 10 digits.", arg.ToString()));
else
numericString = "+1 " + numericString.Substring(0, 3) + " " + numericString.Substring(3, 3) + " " +
numericString.Substring(6);
}
else
{
throw new FormatException(string.Format("The {0} format specifier is invalid.", format));
}
return numericString;
}
}

public class TestTelephoneFormatter


{
public static void Main()
{
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 0));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 911));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 8490216));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 4257884748));

Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 0));


Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 911));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 8490216));
Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 4257884748));

Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:I}", 4257884748));


}
}
Public Class TelephoneFormatter : Implements IFormatProvider, ICustomFormatter
Public Function GetFormat(formatType As Type) As Object _
Implements IFormatProvider.GetFormat
If formatType Is GetType(ICustomFormatter) Then
Return Me
Else
Return Nothing
End If
End Function

Public Function Format(fmt As String, arg As Object, _


formatProvider As IFormatProvider) As String _
Implements ICustomFormatter.Format
' Check whether this is an appropriate callback
If Not Me.Equals(formatProvider) Then Return Nothing

' Set default format specifier


If String.IsNullOrEmpty(fmt) Then fmt = "N"

Dim numericString As String = arg.ToString

If fmt = "N" Then


Select Case numericString.Length
Case <= 4
Return numericString
Case 7
Return Left(numericString, 3) & "-" & Mid(numericString, 4)
Case 10
Return "(" & Left(numericString, 3) & ") " & _
Mid(numericString, 4, 3) & "-" & Mid(numericString, 7)
Case Else
Throw New FormatException( _
String.Format("'{0}' cannot be used to format {1}.", _
fmt, arg.ToString()))
End Select
ElseIf fmt = "I" Then
If numericString.Length < 10 Then
Throw New FormatException(String.Format("{0} does not have 10 digits.", arg.ToString()))
Else
numericString = "+1 " & Left(numericString, 3) & " " & Mid(numericString, 4, 3) & " " &
Mid(numericString, 7)
End If
Else
Throw New FormatException(String.Format("The {0} format specifier is invalid.", fmt))
End If
Return numericString
End Function
End Class

Public Module TestTelephoneFormatter


Public Sub Main
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 0))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 911))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 8490216))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 4257884748))

Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 0))


Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 911))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 8490216))
Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 4257884748))

Console.WriteLine(String.Format(New TelephoneFormatter, "{0:I}", 4257884748))


End Sub
End Module

Le fournisseur de format numérique personnalisé peut être utilisé uniquement avec la méthode
String.Format(IFormatProvider, String, Object[]). Les autres surcharges des méthodes de mise en forme numérique
(telles que ToString ) dotées d’un paramètre de type IFormatProvider passent toutes à l’implémentation
IFormatProvider.GetFormat un objet Type qui représente le type NumberFormatInfo. La méthode doit normalement
leur retourner un objet NumberFormatInfo. Si elle ne le fait pas, le fournisseur de format numérique personnalisé
est ignoré et l’objet NumberFormatInfo pour la culture actuelle est utilisé à la place. Dans l’exemple, la méthode
TelephoneFormatter.GetFormat vérifie si la transmission à une méthode de mise en forme numérique est effectuée
de façon inappropriée ; elle examine le paramètre de méthode et retourne une valeur null si le type représenté
n’est pas ICustomFormatter.
Si un fournisseur de format numérique personnalisé prend en charge un jeu de spécificateurs de format, vérifiez
que vous fournissez un comportement par défaut si aucun spécificateur de format n’est fourni dans l’élément de
format utilisé dans l’appel de la méthode String.Format(IFormatProvider, String, Object[]). Dans l’exemple, « N » est
le spécificateur de format par défaut. Cela permet de convertir un nombre en un numéro de téléphone mis en
forme en fournissant un spécificateur de format explicite. L’exemple suivant illustre un appel de méthode de ce type.

Console.WriteLine(String.Format(new TelephoneFormatter(), "{0:N}", 4257884748));

Console.WriteLine(String.Format(New TelephoneFormatter, "{0:N}", 4257884748))

Mais il permet également la conversion si aucun spécificateur de format n’est présent. L’exemple suivant illustre un
appel de méthode de ce type.

Console.WriteLine(String.Format(new TelephoneFormatter(), "{0}", 4257884748));

Console.WriteLine(String.Format(New TelephoneFormatter, "{0}", 4257884748))

Si aucun spécificateur de format par défaut n’est défini, votre implémentation de la méthode
ICustomFormatter.Format doit inclure le code suivant afin que .NET puisse fournir la mise en forme que votre code
ne prend pas en charge.

if (arg is IFormattable)
s = ((IFormattable)arg).ToString(format, formatProvider);
else if (arg != null)
s = arg.ToString();

If TypeOf (arg) Is IFormattable Then


s = DirectCast(arg, IFormattable).ToString(fmt, formatProvider)
ElseIf arg IsNot Nothing Then
s = arg.ToString()
End If

Dans le cas de cet exemple, la méthode qui implémente ICustomFormatter.Format est destinée à servir de méthode
de rappel pour la méthode String.Format(IFormatProvider, String, Object[]). Ainsi, elle examine le paramètre
formatProvider pour déterminer s’il contient une référence à l’objet TelephoneFormatter actuel. Toutefois, la
méthode peut également être appelée directement à partir du code. Dans ce cas, vous pouvez utiliser le paramètre
formatProvider pour fournir un objet CultureInfo ou NumberFormatInfo qui fournit des informations de mise en
forme spécifiques à la culture.
Procédure : effectuer un aller-retour de valeurs de
date et d’heure
18/07/2020 • 12 minutes to read • Edit Online

Dans de nombreuses applications, une valeur de date et d’heure est destinée à identifier clairement un point unique
dans le temps. Cet article explique comment enregistrer et restaurer une valeur DateTime , une valeur
DateTimeOffset et une valeur de date et d’heure avec des informations de fuseau horaire afin que la valeur
restaurée identifie la même heure que la valeur enregistrée.

Aller-retour d’une valeur DateTime


1. Convertissez la valeur DateTime en sa représentation sous forme de chaîne en appelant la méthode
DateTime.ToString(String) avec le spécificateur de format « o ».
2. Enregistrez la représentation sous forme de chaîne de la valeur DateTime dans un fichier ou passez-la dans
un processus, un domaine d’application ou une limite d’ordinateur.
3. Récupérez la chaîne qui représente la valeur DateTime.
4. Appelez la méthode DateTime.Parse(String, IFormatProvider, DateTimeStyles) et passez
DateTimeStyles.RoundtripKind en tant que valeur du paramètre styles .
L’exemple suivant montre comment effectuer un aller-retour d’une valeur DateTime.

const string fileName = @".\DateFile.txt";

StreamWriter outFile = new StreamWriter(fileName);

// Save DateTime value.


DateTime dateToSave = DateTime.SpecifyKind(new DateTime(2008, 6, 12, 18, 45, 15),
DateTimeKind.Local);
string dateString = dateToSave.ToString("o");
Console.WriteLine("Converted {0} ({1}) to {2}.",
dateToSave.ToString(),
dateToSave.Kind.ToString(),
dateString);
outFile.WriteLine(dateString);
Console.WriteLine("Wrote {0} to {1}.", dateString, fileName);
outFile.Close();

// Restore DateTime value.


DateTime restoredDate;

StreamReader inFile = new StreamReader(fileName);


dateString = inFile.ReadLine();
inFile.Close();
restoredDate = DateTime.Parse(dateString, null, DateTimeStyles.RoundtripKind);
Console.WriteLine("Read {0} ({2}) from {1}.", restoredDate.ToString(),
fileName,
restoredDate.Kind.ToString());
// The example displays the following output:
// Converted 6/12/2008 6:45:15 PM (Local) to 2008-06-12T18:45:15.0000000-05:00.
// Wrote 2008-06-12T18:45:15.0000000-05:00 to .\DateFile.txt.
// Read 6/12/2008 6:45:15 PM (Local) from .\DateFile.txt.
Const fileName As String = ".\DateFile.txt"

Dim outFile As New StreamWriter(fileName)

' Save DateTime value.


Dim dateToSave As Date = DateTime.SpecifyKind(#06/12/2008 6:45:15 PM#, _
DateTimeKind.Local)
Dim dateString As String = dateToSave.ToString("o")
Console.WriteLine("Converted {0} ({1}) to {2}.", dateToSave.ToString(), _
dateToSave.Kind.ToString(), dateString)
outFile.WriteLine(dateString)
Console.WriteLine("Wrote {0} to {1}.", dateString, fileName)
outFile.Close()

' Restore DateTime value.


Dim restoredDate As Date

Dim inFile As New StreamReader(fileName)


dateString = inFile.ReadLine()
inFile.Close()
restoredDate = DateTime.Parse(dateString, Nothing, DateTimeStyles.RoundTripKind)
Console.WriteLine("Read {0} ({2}) from {1}.", restoredDate.ToString(), _
fileName, restoredDAte.Kind.ToString())
' The example displays the following output:
' Converted 6/12/2008 6:45:15 PM (Local) to 2008-06-12T18:45:15.0000000-05:00.
' Wrote 2008-06-12T18:45:15.0000000-05:00 to .\DateFile.txt.
' Read 6/12/2008 6:45:15 PM (Local) from .\DateFile.txt.

Durant l’aller-retour d’une valeur DateTime, cette technique permet de conserver correctement l’heure pour toutes
les heures locales et universelles. Par exemple, si une DateTime valeur locale est enregistrée sur un système situé
dans le fuseau horaire Pacifique (États-Unis) et qu’elle est restaurée sur un système situé dans le fuseau horaire du
Centre des États-Unis, la date et l’heure de restauration sont deux heures après l’heure d’origine, ce qui reflète la
différence de temps entre les deux fuseaux horaires. En revanche, cette technique n’est pas nécessairement exacte
pour les heures non spécifiées. Toutes les valeurs DateTime dont la propriété Kind est Unspecified sont traitées
comme s’il s’agissait d’heures locales. S’il ne s’agit pas d’une heure locale, le DateTime n’identifie pas correctement
le point correct dans le temps. La solution pour contourner cette limitation consiste à associer étroitement une
valeur de date et d’heure avec son fuseau horaire pour l’opération d’enregistrement et de restauration.

Aller-retour d’une valeur DateTimeOffset


1. Convertissez la valeur DateTimeOffset en sa représentation sous forme de chaîne en appelant la méthode
DateTimeOffset.ToString(String) avec le spécificateur de format « o ».
2. Enregistrez la représentation sous forme de chaîne de la valeur DateTimeOffset dans un fichier ou passez-la
dans un processus, un domaine d’application ou une limite d’ordinateur.
3. Récupérez la chaîne qui représente la valeur DateTimeOffset.
4. Appelez la méthode DateTimeOffset.Parse(String, IFormatProvider, DateTimeStyles) et passez
DateTimeStyles.RoundtripKind en tant que valeur du paramètre styles .

L’exemple suivant montre comment effectuer un aller-retour d’une valeur DateTimeOffset.


const string fileName = @".\DateOff.txt";

StreamWriter outFile = new StreamWriter(fileName);

// Save DateTime value.


DateTimeOffset dateToSave = new DateTimeOffset(2008, 6, 12, 18, 45, 15,
new TimeSpan(7, 0, 0));
string dateString = dateToSave.ToString("o");
Console.WriteLine("Converted {0} to {1}.", dateToSave.ToString(),
dateString);
outFile.WriteLine(dateString);
Console.WriteLine("Wrote {0} to {1}.", dateString, fileName);
outFile.Close();

// Restore DateTime value.


DateTimeOffset restoredDateOff;

StreamReader inFile = new StreamReader(fileName);


dateString = inFile.ReadLine();
inFile.Close();
restoredDateOff = DateTimeOffset.Parse(dateString, null,
DateTimeStyles.RoundtripKind);
Console.WriteLine("Read {0} from {1}.", restoredDateOff.ToString(),
fileName);
// The example displays the following output:
// Converted 6/12/2008 6:45:15 PM +07:00 to 2008-06-12T18:45:15.0000000+07:00.
// Wrote 2008-06-12T18:45:15.0000000+07:00 to .\DateOff.txt.
// Read 6/12/2008 6:45:15 PM +07:00 from .\DateOff.txt.

Const fileName As String = ".\DateOff.txt"

Dim outFile As New StreamWriter(fileName)

' Save DateTime value.


Dim dateToSave As New DateTimeOffset(2008, 6, 12, 18, 45, 15, _
New TimeSpan(7, 0, 0))
Dim dateString As String = dateToSave.ToString("o")
Console.WriteLine("Converted {0} to {1}.", dateToSave.ToString(), dateString)
outFile.WriteLine(dateString)
Console.WriteLine("Wrote {0} to {1}.", dateString, fileName)
outFile.Close()

' Restore DateTime value.


Dim restoredDateOff As DateTimeOffset

Dim inFile As New StreamReader(fileName)


dateString = inFile.ReadLine()
inFile.Close()
restoredDateOff = DateTimeOffset.Parse(dateString, Nothing, DateTimeStyles.RoundTripKind)
Console.WriteLine("Read {0} from {1}.", restoredDateOff.ToString(), fileName)
' The example displays the following output:
' Converted 6/12/2008 6:45:15 PM +07:00 to 2008-06-12T18:45:15.0000000+07:00.
' Wrote 2008-06-12T18:45:15.0000000+07:00 to .\DateOff.txt.
' Read 6/12/2008 6:45:15 PM +07:00 from .\DateOff.txt.

Cette technique identifie toujours clairement une valeur DateTimeOffset comme point unique dans le temps. La
valeur peut ensuite être convertie en temps universel coordonné (UTC) en appelant la méthode
DateTimeOffset.ToUniversalTime, ou convertie en heure dans un fuseau horaire particulier en appelant la méthode
DateTimeOffset.ToOffset ou TimeZoneInfo.ConvertTime(DateTimeOffset, TimeZoneInfo). La principale limitation de
cette technique est que les opérations arithmétiques de date et heure, effectuées sur une valeur DateTimeOffset qui
représente l’heure dans un fuseau horaire particulier, risquent de ne pas produire des résultats exacts pour ce
fuseau horaire. En effet, quand une valeur DateTimeOffset est instanciée, elle est dissociée de son fuseau horaire.
Ainsi, les règles d’ajustement de ce fuseau horaire ne sont plus applicables quand vous effectuez des calculs de date
et d’heure. Vous pouvez contourner ce problème en définissant un type personnalisé qui inclut à la fois une valeur
de date et heure et son fuseau horaire correspondant.

Aller-retour d’une valeur de date et d’heure avec son fuseau horaire


1. Définissez une classe ou une structure avec deux champs. Le premier champ est un objet DateTime ou
DateTimeOffset, et le second est un objet TimeZoneInfo. L’exemple suivant est une version simple de ce type.

[Serializable] public class DateInTimeZone


{
private TimeZoneInfo tz;
private DateTimeOffset thisDate;

public DateInTimeZone() {}

public DateInTimeZone(DateTimeOffset date, TimeZoneInfo timeZone)


{
if (timeZone == null)
throw new ArgumentNullException("The time zone cannot be null.");

this.thisDate = date;
this.tz = timeZone;
}

public DateTimeOffset DateAndTime


{
get {
return this.thisDate;
}
set {
if (value.Offset != this.tz.GetUtcOffset(value))
this.thisDate = TimeZoneInfo.ConvertTime(value, tz);
else
this.thisDate = value;
}
}

public TimeZoneInfo TimeZone


{
get {
return this.tz;
}
}
}
<Serializable> Public Class DateInTimeZone
Private tz As TimeZoneInfo
Private thisDate As DateTimeOffset

Public Sub New()


End Sub

Public Sub New(date1 As DateTimeOffset, timeZone As TimeZoneInfo)


If timeZone Is Nothing Then
Throw New ArgumentNullException("The time zone cannot be null.")
End If
Me.thisDate = date1
Me.tz = timeZone
End Sub

Public Property DateAndTime As DateTimeOffset


Get
Return Me.thisDate
End Get
Set
If Value.Offset <> Me.tz.GetUtcOffset(Value) Then
Me.thisDate = TimeZoneInfo.ConvertTime(Value, tz)
Else
Me.thisDate = Value
End If
End Set
End Property

Public ReadOnly Property TimeZone As TimeZoneInfo


Get
Return tz
End Get
End Property
End Class

2. Marquez la classe avec l’attribut SerializableAttribute.


3. Sérialisez l’objet à l’aide de la méthode BinaryFormatter.Serialize.
4. Restaurez l’objet à l’aide de la méthode Deserialize.
5. Castez (en C#) ou convertissez (en Visual Basic) l’objet désérialisé en objet du type approprié.
L’exemple suivant montre comment effectuer un aller-retour d’un objet qui stocke des informations de date et
d’heure dans un fuseau horaire.
const string fileName = @".\DateWithTz.dat";

DateTime tempDate = new DateTime(2008, 9, 3, 19, 0, 0);


TimeZoneInfo tempTz = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
DateInTimeZone dateWithTz = new DateInTimeZone(new DateTimeOffset(tempDate,
tempTz.GetUtcOffset(tempDate)),
tempTz);

// Store DateInTimeZone value to a file


FileStream outFile = new FileStream(fileName, FileMode.Create);
try
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(outFile, dateWithTz);
Console.WriteLine("Saving {0} {1} to {2}", dateWithTz.DateAndTime,
dateWithTz.TimeZone.IsDaylightSavingTime(dateWithTz.DateAndTime) ?
dateWithTz.TimeZone.DaylightName : dateWithTz.TimeZone.DaylightName,
fileName);
}
catch (SerializationException)
{
Console.WriteLine("Unable to serialize time data to {0}.", fileName);
}
finally
{
outFile.Close();
}

// Retrieve DateInTimeZone value


if (File.Exists(fileName))
{
FileStream inFile = new FileStream(fileName, FileMode.Open);
DateInTimeZone dateWithTz2 = new DateInTimeZone();
try
{
BinaryFormatter formatter = new BinaryFormatter();
dateWithTz2 = formatter.Deserialize(inFile) as DateInTimeZone;
Console.WriteLine("Restored {0} {1} from {2}", dateWithTz2.DateAndTime,
dateWithTz2.TimeZone.IsDaylightSavingTime(dateWithTz2.DateAndTime) ?
dateWithTz2.TimeZone.DaylightName : dateWithTz2.TimeZone.DaylightName,
fileName);
}
catch (SerializationException)
{
Console.WriteLine("Unable to retrieve date and time information from {0}",
fileName);
}
finally
{
inFile.Close();
}
}
// This example displays the following output to the console:
// Saving 9/3/2008 7:00:00 PM -05:00 Central Daylight Time to .\DateWithTz.dat
// Restored 9/3/2008 7:00:00 PM -05:00 Central Daylight Time from .\DateWithTz.dat
Const fileName As String = ".\DateWithTz.dat"

Dim tempDate As Date = #9/3/2008 7:00:00 PM#


Dim tempTz As TimeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time")
Dim dateWithTz As New DateInTimeZone(New DateTimeOffset(tempDate, _
tempTz.GetUtcOffset(tempDate)), _
tempTz)

' Store DateInTimeZone value to a file


Dim outFile As New FileStream(fileName, FileMode.Create)
Try
Dim formatter As New BinaryFormatter()
formatter.Serialize(outFile, dateWithTz)
Console.WriteLine("Saving {0} {1} to {2}", dateWithTz.DateAndTime, _
IIf(dateWithTz.TimeZone.IsDaylightSavingTime(dateWithTz.DateAndTime), _
dateWithTz.TimeZone.DaylightName, dateWithTz.TimeZone.DaylightName), _
fileName)
Catch e As SerializationException
Console.WriteLine("Unable to serialize time data to {0}.", fileName)
Finally
outFile.Close()
End Try

' Retrieve DateInTimeZone value


If File.Exists(fileName) Then
Dim inFile As New FileStream(fileName, FileMode.Open)
Dim dateWithTz2 As New DateInTimeZone()
Try
Dim formatter As New BinaryFormatter()
dateWithTz2 = DirectCast(formatter.Deserialize(inFile), DateInTimeZone)
Console.WriteLine("Restored {0} {1} from {2}", dateWithTz2.DateAndTime, _
IIf(dateWithTz2.TimeZone.IsDaylightSavingTime(dateWithTz2.DateAndTime), _
dateWithTz2.TimeZone.DaylightName, dateWithTz2.TimeZone.DaylightName), _
fileName)
Catch e As SerializationException
Console.WriteLine("Unable to retrieve date and time information from {0}", _
fileName)
Finally
inFile.Close
End Try
End If
' This example displays the following output to the console:
' Saving 9/3/2008 7:00:00 PM -05:00 Central Daylight Time to .\DateWithTz.dat
' Restored 9/3/2008 7:00:00 PM -05:00 Central Daylight Time from .\DateWithTz.dat

Cette technique devrait toujours refléter clairement le point dans le temps correct avant et après son
enregistrement et sa restauration, à condition que l’implémentation de l’objet combiné de date et d’heure et de
fuseau horaire ne permette pas à la valeur de date de se désynchroniser de la valeur de fuseau horaire.

Compiler le code
Ces exemples requièrent les éléments suivants :
Les espaces de noms suivants doivent être importés avec les using directives C# ou les Imports
instructions Visual Basic :
System(C# uniquement)
System.Globalization
System.IO
System.Runtime.Serialization
System.Runtime.Serialization.Formatters.Binary
Chaque exemple de code, autre que la DateInTimeZone classe, est inclus dans une classe ou un module
Visual Basic, encapsulé dans des méthodes et appelé à partir de la Main méthode.

Voir aussi
Choisir entre DateTime, DateTimeOffset, TimeSpan et TimeZoneInfo
Chaînes de format de date et d’heure standard
Procédure : afficher les millisecondes dans les valeurs
de date et d’heure
18/07/2020 • 7 minutes to read • Edit Online

Les méthodes de mise en forme de date et d’heure par défaut, telles que DateTime.ToString(), incluent les heures,
les minutes et les secondes d’une valeur d’heure, mais excluent son composant « millisecondes ». Cette rubrique
montre comment inclure le composant « millisecondes » d’une date et d’une heure dans des chaînes de date et
d’heure mises en forme.
Pour afficher le composant « millisecondes » d’une valeur DateTime
1. Si la date est représentée sous forme de chaîne, convertissez-la en une valeur DateTime ou DateTimeOffset
en utilisant la méthode DateTime.Parse(String) ou DateTimeOffset.Parse(String) statique.
2. Pour extraire la représentation sous forme de chaîne du composant « millisecondes » d’une heure, appelez la
méthode DateTime.ToString(String) ou ToString de la valeur de date et d’heure, puis transmettez le modèle
de format personnalisé fff ou FFF seul ou avec d’autres spécificateurs de format personnalisé comme
paramètre de format .

Exemple
L’exemple affiche le composant « milliseconde » d’une valeur DateTime et d’une valeur DateTimeOffset sur la
console, à la fois seul et inclus dans une chaîne de date et d’heure plus longue.
using System;
using System.Globalization;
using System.Text.RegularExpressions;

public class MillisecondDisplay


{
public static void Main()
{
string dateString = "7/16/2008 8:32:45.126 AM";

try
{
DateTime dateValue = DateTime.Parse(dateString);
DateTimeOffset dateOffsetValue = DateTimeOffset.Parse(dateString);

// Display Millisecond component alone.


Console.WriteLine("Millisecond component only: {0}",
dateValue.ToString("fff"));
Console.WriteLine("Millisecond component only: {0}",
dateOffsetValue.ToString("fff"));

// Display Millisecond component with full date and time.


Console.WriteLine("Date and Time with Milliseconds: {0}",
dateValue.ToString("MM/dd/yyyy hh:mm:ss.fff tt"));
Console.WriteLine("Date and Time with Milliseconds: {0}",
dateOffsetValue.ToString("MM/dd/yyyy hh:mm:ss.fff tt"));

// Append millisecond pattern to current culture's full date time pattern


string fullPattern = DateTimeFormatInfo.CurrentInfo.FullDateTimePattern;
fullPattern = Regex.Replace(fullPattern, "(:ss|:s)", "$1.fff");

// Display Millisecond component with modified full date and time pattern.
Console.WriteLine("Modified full date time pattern: {0}",
dateValue.ToString(fullPattern));
Console.WriteLine("Modified full date time pattern: {0}",
dateOffsetValue.ToString(fullPattern));
}
catch (FormatException)
{
Console.WriteLine("Unable to convert {0} to a date.", dateString);
}
}
}
// The example displays the following output if the current culture is en-US:
// Millisecond component only: 126
// Millisecond component only: 126
// Date and Time with Milliseconds: 07/16/2008 08:32:45.126 AM
// Date and Time with Milliseconds: 07/16/2008 08:32:45.126 AM
// Modified full date time pattern: Wednesday, July 16, 2008 8:32:45.126 AM
// Modified full date time pattern: Wednesday, July 16, 2008 8:32:45.126 AM
Imports System.Globalization
Imports System.Text.REgularExpressions

Module MillisecondDisplay
Public Sub Main()

Dim dateString As String = "7/16/2008 8:32:45.126 AM"

Try
Dim dateValue As Date = Date.Parse(dateString)
Dim dateOffsetValue As DateTimeOffset = DateTimeOffset.Parse(dateString)

' Display Millisecond component alone.


Console.WriteLine("Millisecond component only: {0}", _
dateValue.ToString("fff"))
Console.WriteLine("Millisecond component only: {0}", _
dateOffsetValue.ToString("fff"))

' Display Millisecond component with full date and time.


Console.WriteLine("Date and Time with Milliseconds: {0}", _
dateValue.ToString("MM/dd/yyyy hh:mm:ss.fff tt"))
Console.WriteLine("Date and Time with Milliseconds: {0}", _
dateOffsetValue.ToString("MM/dd/yyyy hh:mm:ss.fff tt"))

' Append millisecond pattern to current culture's full date time pattern
Dim fullPattern As String = DateTimeFormatInfo.CurrentInfo.FullDateTimePattern
fullPattern = Regex.Replace(fullPattern, "(:ss|:s)", "$1.fff")

' Display Millisecond component with modified full date and time pattern.
Console.WriteLine("Modified full date time pattern: {0}", _
dateValue.ToString(fullPattern))
Console.WriteLine("Modified full date time pattern: {0}", _
dateOffsetValue.ToString(fullPattern))
Catch e As FormatException
Console.WriteLine("Unable to convert {0} to a date.", dateString)
End Try
End Sub
End Module
' The example displays the following output if the current culture is en-US:
' Millisecond component only: 126
' Millisecond component only: 126
' Date and Time with Milliseconds: 07/16/2008 08:32:45.126 AM
' Date and Time with Milliseconds: 07/16/2008 08:32:45.126 AM
' Modified full date time pattern: Wednesday, July 16, 2008 8:32:45.126 AM
' Modified full date time pattern: Wednesday, July 16, 2008 8:32:45.126 AM

Le modèle de format fff inclut les zéros de fin éventuels dans la valeur en millisecondes. Le modèle de format
FFF les supprime. L’exemple suivant illustre cette différence.

DateTime dateValue = new DateTime(2008, 7, 16, 8, 32, 45, 180);


Console.WriteLine(dateValue.ToString("fff"));
Console.WriteLine(dateValue.ToString("FFF"));
// The example displays the following output to the console:
// 180
// 18

Dim dateValue As New Date(2008, 7, 16, 8, 32, 45, 180)


Console.WriteLIne(dateValue.ToString("fff"))
Console.WriteLine(dateValue.ToString("FFF"))
' The example displays the following output to the console:
' 180
' 18
Quand vous définissez un spécificateur de format personnalisé complet qui inclut le composant « millisecondes »
d’une date et d’une heure, vous pouvez être confronté au problème suivant : le format défini peut être un format
codé en dur qui ne correspond pas à la disposition des éléments d’heure dans la culture actuelle de l’application.
Une meilleure solution consiste à récupérer l’un des modèles d’affichage de la date et de l’heure définis par l’objet
DateTimeFormatInfo de la culture actuelle et à le modifier pour inclure les millisecondes. L’exemple illustre
également cette approche. Il récupère de la propriété DateTimeFormatInfo.FullDateTimePattern le modèle de date
et d’heure complet de la culture actuelle, puis insère le modèle personnalisé .ffff après son modèle des
secondes. Notez que l’exemple utilise une expression régulière pour effectuer cette opération dans un seul appel de
méthode.
Vous pouvez également utiliser un spécificateur de format personnalisé pour afficher une partie fractionnaire des
secondes autre que les millisecondes. Par exemple, le spécificateur de format personnalisé f ou F affiche les
dixièmes de seconde, le spécificateur de format personnalisé ff ou FF affiche les centièmes de seconde, tandis
que le spécificateur de format personnalisé ffff ou FFFF affiche les dix millièmes de seconde. La partie
fractionnaire d’une milliseconde est tronquée au lieu d’être arrondie dans la chaîne retournée. Ces spécificateurs de
format sont utilisés dans l’exemple suivant.

DateTime dateValue = new DateTime(2008, 7, 16, 8, 32, 45, 180);


Console.WriteLine("{0} seconds", dateValue.ToString("s.f"));
Console.WriteLine("{0} seconds", dateValue.ToString("s.ff"));
Console.WriteLine("{0} seconds", dateValue.ToString("s.ffff"));
// The example displays the following output to the console:
// 45.1 seconds
// 45.18 seconds
// 45.1800 seconds

Dim dateValue As New DateTime(2008, 7, 16, 8, 32, 45, 180)


Console.WriteLine("{0} seconds", dateValue.ToString("s.f"))
Console.WriteLine("{0} seconds", dateValue.ToString("s.ff"))
Console.WriteLine("{0} seconds", dateValue.ToString("s.ffff"))
' The example displays the following output to the console:
' 45.1 seconds
' 45.18 seconds
' 45.1800 seconds

NOTE
Il est possible d’afficher de très petites unités fractionnaires d’une seconde, telles que les dix millièmes de seconde ou les cent
millièmes de seconde. Toutefois, ces valeurs peuvent ne pas être significatives. La précision des valeurs de date et d'heure
dépend de la résolution de l'horloge système. Sur Windows NT 3,5 et versions ultérieures, ainsi que sur les systèmes
d’exploitation Windows Vista, la résolution de l’horloge est d’environ 10-15 millisecondes.

Voir aussi
DateTimeFormatInfo
Chaînes de format de date et d’heure personnalisées
Procédure : afficher des dates dans des calendriers
non grégoriens
01/04/2020 • 13 minutes to read • Edit Online

Les types DateTime et DateTimeOffset utilisent le calendrier grégorien comme calendrier par défaut. Cela signifie
que l’appel de la méthode ToString d’une valeur de date et d’heure affiche la représentation sous forme de chaîne
de la date et de l’heure dans le calendrier grégorien, même si ces date et heure ont été créées à l’aide d’un autre
calendrier. Cela est illustré dans l’exemple suivant, qui utilise deux méthodes différentes pour créer une valeur de
date et d’heure avec le calendrier persan, mais affiche toujours ces valeurs de date et d’heure dans le calendrier
grégorien quand il appelle la méthode ToString. Cet exemple reflète deux techniques couramment utilisées, mais
incorrectes, pour l’affichage de la date dans un calendrier particulier.

PersianCalendar persianCal = new PersianCalendar();

DateTime persianDate = persianCal.ToDateTime(1387, 3, 18, 12, 0, 0, 0);


Console.WriteLine(persianDate.ToString());

persianDate = new DateTime(1387, 3, 18, persianCal);


Console.WriteLine(persianDate.ToString());
// The example displays the following output to the console:
// 6/7/2008 12:00:00 PM
// 6/7/2008 12:00:00 AM

Dim persianCal As New PersianCalendar()

Dim persianDate As Date = persianCal.ToDateTime(1387, 3, 18, _


12, 0, 0, 0)
Console.WriteLine(persianDate.ToString())

persianDate = New DateTime(1387, 3, 18, persianCal)


Console.WriteLine(persianDate.ToString())
' The example displays the following output to the console:
' 6/7/2008 12:00:00 PM
' 6/7/2008 12:00:00 AM

Deux techniques différentes peuvent être utilisées pour afficher la date dans un calendrier particulier. La première
nécessite que le calendrier soit le calendrier par défaut pour une culture particulière. La seconde est utilisable avec
n’importe quel calendrier.
Pour afficher la date pour le calendrier par défaut d’une culture
1. Instanciez un objet de calendrier dérivé de la classe Calendar qui représente le calendrier à utiliser.
2. Instanciez un objet CultureInfo représentant la culture dont la mise en forme doit être utilisée pour afficher
la date.
3. Appelez la méthode Array.Exists pour déterminer si l’objet de calendrier est membre du tableau retourné par
la propriété CultureInfo.OptionalCalendars. Cela indique que le calendrier peut servir de calendrier par
défaut pour l’objet CultureInfo. S’il n’est pas membre du tableau, suivez les instructions de la section « Pour
afficher la date dans un calendrier ».
4. Attribuez l’objet de calendrier à la propriété Calendar de l'objet DateTimeFormatInfo retourné par la
propriété CultureInfo.DateTimeFormat.
NOTE
La classe CultureInfo a également une propriété Calendar. Toutefois, elle est en lecture seule et constante. Elle ne
change pas pour refléter le nouveau calendrier par défaut attribué à la propriété DateTimeFormatInfo.Calendar.

5. Appelez la méthode ToString ou ToString et passez-la à l’objet CultureInfo dont le calendrier par défaut a été
modifié à l’étape précédente.
Pour afficher la date dans un calendrier
1. Instanciez un objet de calendrier dérivé de la classe Calendar qui représente le calendrier à utiliser.
2. Déterminez les éléments de date et d’heure qui doivent apparaître dans la représentation sous forme de
chaîne de la valeur de date et d’heure.
3. Pour chaque élément de date et d’heure que vous souhaitez afficher, appelez la méthode Get ... de l’objet de
calendrier. . Les méthodes suivantes sont disponibles :
GetYear, pour afficher l’année dans le calendrier approprié.
GetMonth, pour afficher le mois dans le calendrier approprié.
GetDayOfMonth, pour afficher le numéro du jour du mois dans le calendrier approprié.
GetHour, pour afficher l’heure du jour dans le calendrier approprié.
GetMinute, pour afficher les minutes de l’heure dans le calendrier approprié.
GetSecond, pour afficher les secondes de la minute dans le calendrier approprié.
GetMilliseconds, pour afficher les millisecondes de la seconde dans le calendrier approprié.

Exemple
L’exemple affiche une date à l’aide de deux calendriers différents. Il affiche la date après avoir défini le calendrier
hégirien comme calendrier par défaut pour la culture ar-JO et affiche la date à l’aide du calendrier persan, qui n’est
pas pris en charge comme calendrier facultatif par la culture fa-IR.

using System;
using System.Globalization;

public class CalendarDates


{
public static void Main()
{
HijriCalendar hijriCal = new HijriCalendar();
CalendarUtility hijriUtil = new CalendarUtility(hijriCal);
DateTime dateValue1 = new DateTime(1429, 6, 29, hijriCal);
DateTimeOffset dateValue2 = new DateTimeOffset(dateValue1,
TimeZoneInfo.Local.GetUtcOffset(dateValue1));
CultureInfo jc = CultureInfo.CreateSpecificCulture("ar-JO");

// Display the date using the Gregorian calendar.


Console.WriteLine("Using the system default culture: {0}",
dateValue1.ToString("d"));
// Display the date using the ar-JO culture's original default calendar.
Console.WriteLine("Using the ar-JO culture's original default calendar: {0}",
dateValue1.ToString("d", jc));
// Display the date using the Hijri calendar.
Console.WriteLine("Using the ar-JO culture with Hijri as the default calendar:");
// Display a Date value.
Console.WriteLine(hijriUtil.DisplayDate(dateValue1, jc));
// Display a DateTimeOffset value.
// Display a DateTimeOffset value.
Console.WriteLine(hijriUtil.DisplayDate(dateValue2, jc));

Console.WriteLine();

PersianCalendar persianCal = new PersianCalendar();


CalendarUtility persianUtil = new CalendarUtility(persianCal);
CultureInfo ic = CultureInfo.CreateSpecificCulture("fa-IR");

// Display the date using the ir-FA culture's default calendar.


Console.WriteLine("Using the ir-FA culture's default calendar: {0}",
dateValue1.ToString("d", ic));
// Display a Date value.
Console.WriteLine(persianUtil.DisplayDate(dateValue1, ic));
// Display a DateTimeOffset value.
Console.WriteLine(persianUtil.DisplayDate(dateValue2, ic));
}
}

public class CalendarUtility


{
private Calendar thisCalendar;
private CultureInfo targetCulture;

public CalendarUtility(Calendar cal)


{
this.thisCalendar = cal;
}

private bool CalendarExists(CultureInfo culture)


{
this.targetCulture = culture;
return Array.Exists(this.targetCulture.OptionalCalendars,
this.HasSameName);
}

private bool HasSameName(Calendar cal)


{
if (cal.ToString() == thisCalendar.ToString())
return true;
else
return false;
}

public string DisplayDate(DateTime dateToDisplay, CultureInfo culture)


{
DateTimeOffset displayOffsetDate = dateToDisplay;
return DisplayDate(displayOffsetDate, culture);
}

public string DisplayDate(DateTimeOffset dateToDisplay,


CultureInfo culture)
{
string specifier = "yyyy/MM/dd";

if (this.CalendarExists(culture))
{
Console.WriteLine("Displaying date in supported {0} calendar...",
this.thisCalendar.GetType().Name);
culture.DateTimeFormat.Calendar = this.thisCalendar;
return dateToDisplay.ToString(specifier, culture);
}
else
{
Console.WriteLine("Displaying date in unsupported {0} calendar...",
thisCalendar.GetType().Name);

string separator = targetCulture.DateTimeFormat.DateSeparator;

return thisCalendar.GetYear(dateToDisplay.DateTime).ToString("0000") +
return thisCalendar.GetYear(dateToDisplay.DateTime).ToString("0000") +
separator +
thisCalendar.GetMonth(dateToDisplay.DateTime).ToString("00") +
separator +
thisCalendar.GetDayOfMonth(dateToDisplay.DateTime).ToString("00");
}
}
}
// The example displays the following output to the console:
// Using the system default culture: 7/3/2008
// Using the ar-JO culture's original default calendar: 03/07/2008
// Using the ar-JO culture with Hijri as the default calendar:
// Displaying date in supported HijriCalendar calendar...
// 1429/06/29
// Displaying date in supported HijriCalendar calendar...
// 1429/06/29
//
// Using the ir-FA culture's default calendar: 7/3/2008
// Displaying date in unsupported PersianCalendar calendar...
// 1387/04/13
// Displaying date in unsupported PersianCalendar calendar...
// 1387/04/13

Imports System.Globalization

Public Class CalendarDates


Public Shared Sub Main()
Dim hijriCal As New HijriCalendar()
Dim hijriUtil As New CalendarUtility(hijriCal)
Dim dateValue1 As Date = New Date(1429, 6, 29, hijriCal)
Dim dateValue2 As DateTimeOffset = New DateTimeOffset(dateValue1, _
TimeZoneInfo.Local.GetUtcOffset(dateValue1))
Dim jc As CultureInfo = CultureInfo.CreateSpecificCulture("ar-JO")

' Display the date using the Gregorian calendar.


Console.WriteLine("Using the system default culture: {0}", _
dateValue1.ToString("d"))
' Display the date using the ar-JO culture's original default calendar.
Console.WriteLine("Using the ar-JO culture's original default calendar: {0}", _
dateValue1.ToString("d", jc))
' Display the date using the Hijri calendar.
Console.WriteLine("Using the ar-JO culture with Hijri as the default calendar:")
' Display a Date value.
Console.WriteLine(hijriUtil.DisplayDate(dateValue1, jc))
' Display a DateTimeOffset value.
Console.WriteLine(hijriUtil.DisplayDate(dateValue2, jc))

Console.WriteLine()

Dim persianCal As New PersianCalendar()


Dim persianUtil As New CalendarUtility(persianCal)
Dim ic As CultureInfo = CultureInfo.CreateSpecificCulture("fa-IR")

' Display the date using the ir-FA culture's default calendar.
Console.WriteLine("Using the ir-FA culture's default calendar: {0}", _
dateValue1.ToString("d", ic))
' Display a Date value.
Console.WriteLine(persianUtil.DisplayDate(dateValue1, ic))
' Display a DateTimeOffset value.
Console.WriteLine(persianUtil.DisplayDate(dateValue2, ic))
End Sub
End Class

Public Class CalendarUtility


Private thisCalendar As Calendar
Private targetCulture As CultureInfo

Public Sub New(cal As Calendar)


Public Sub New(cal As Calendar)
Me.thisCalendar = cal
End Sub

Private Function CalendarExists(culture As CultureInfo) As Boolean


Me.targetCulture = culture
Return Array.Exists(Me.targetCulture.OptionalCalendars, _
AddressOf Me.HasSameName)
End Function

Private Function HasSameName(cal As Calendar) As Boolean


If cal.ToString() = thisCalendar.ToString() Then
Return True
Else
Return False
End If
End Function

Public Function DisplayDate(dateToDisplay As Date, _


culture As CultureInfo) As String
Dim displayOffsetDate As DateTimeOffset = dateToDisplay
Return DisplayDate(displayOffsetDate, culture)
End Function

Public Function DisplayDate(dateToDisplay As DateTimeOffset, _


culture As CultureInfo) As String
Dim specifier As String = "yyyy/MM/dd"

If Me.CalendarExists(culture) Then
Console.WriteLine("Displaying date in supported {0} calendar...", _
thisCalendar.GetType().Name)
culture.DateTimeFormat.Calendar = Me.thisCalendar
Return dateToDisplay.ToString(specifier, culture)
Else
Console.WriteLine("Displaying date in unsupported {0} calendar...", _
thisCalendar.GetType().Name)

Dim separator As String = targetCulture.DateTimeFormat.DateSeparator

Return thisCalendar.GetYear(dateToDisplay.DateTime).ToString("0000") & separator & _


thisCalendar.GetMonth(dateToDisplay.DateTime).ToString("00") & separator & _
thisCalendar.GetDayOfMonth(dateToDisplay.DateTime).ToString("00")
End If
End Function
End Class
' The example displays the following output to the console:
' Using the system default culture: 7/3/2008
' Using the ar-JO culture's original default calendar: 03/07/2008
' Using the ar-JO culture with Hijri as the default calendar:
' Displaying date in supported HijriCalendar calendar...
' 1429/06/29
' Displaying date in supported HijriCalendar calendar...
' 1429/06/29
'
' Using the ir-FA culture's default calendar: 7/3/2008
' Displaying date in unsupported PersianCalendar calendar...
' 1387/04/13
' Displaying date in unsupported PersianCalendar calendar...
' 1387/04/13

Chaque objet CultureInfo peut prendre en charge un ou plusieurs calendriers, indiqués par la propriété
OptionalCalendars. L’un d’eux est désigné comme calendrier par défaut de la culture et est retourné par la propriété
CultureInfo.Calendar en lecture seule. Un autre des calendriers facultatifs peut être désigné comme calendrier par
défaut en attribuant un objet Calendar qui représente ce calendrier à la propriété DateTimeFormatInfo.Calendar
retournée par la propriété CultureInfo.DateTimeFormat. Toutefois, certains calendriers, tels que le calendrier persan
représenté par la classe PersianCalendar, ne servent de calendriers facultatifs pour aucune culture.
L’exemple définit une classe utilitaire de calendrier réutilisable, CalendarUtility , pour gérer de nombreux détails de
la génération de la représentation sous forme de chaîne d’une date à l’aide d’un calendrier particulier. La classe
CalendarUtility possède les membres suivants :

Un constructeur paramétré dont le paramètre unique est un objet Calendar dans lequel la date doit être
représentée. Il est assigné à un champ privé de la classe.
CalendarExists , méthode privée qui retourne une valeur booléenne indiquant si le calendrier représenté par
l’objet CalendarUtility est pris en charge par l’objet CultureInfo qui est passé à la méthode comme
paramètre. La méthode encapsule un appel à la méthode Array.Exists, à laquelle est passé le tableau
CultureInfo.OptionalCalendars.
HasSameName , méthode privée assignée au délégué Predicate<T> qui est passé comme paramètre à la
méthode Array.Exists. Chaque membre du tableau est passé à la méthode jusqu’à ce qu’elle retourne true .
La méthode détermine si le nom d’un calendrier facultatif est identique à celui du calendrier représenté par
l’objet CalendarUtility .
DisplayDate , méthode publique surchargée qui reçoit deux paramètres : une valeur DateTime ou
DateTimeOffset à exprimer dans le calendrier représenté par l’objet CalendarUtility et la culture dont les
règles de mise en forme doivent être utilisées. La façon dont elle retourne la représentation sous forme de
chaîne d’une date varie selon que le calendrier cible est pris en charge ou non par la culture dont les règles
de mise en forme doivent être utilisées.
Quel que soit le calendrier utilisé pour créer une valeur DateTime ou DateTimeOffset dans cet exemple, la valeur est
généralement exprimée sous la forme d’une date du calendrier grégorien. En effet, les types DateTime et
DateTimeOffset ne conservent pas les informations de calendrier. En interne, elles sont représentées comme
nombre de graduations qui se sont écoulées depuis le 1er janvier 0001 à minuit. L’interprétation de ce nombre
dépend du calendrier. Pour la plupart des cultures, le calendrier par défaut est le calendrier grégorien.
Bonnes pratiques pour l’utilisation de chaînes dans
.NET
18/07/2020 • 59 minutes to read • Edit Online

.NET offre une prise en charge complète du développement d’applications localisées et globalisées, et facilite
l’application des conventions de la culture actuelle ou d’une culture spécifique lors de l’exécution d’opérations
courantes telles que le tri et l’affichage de chaînes. Toutefois, le tri ou la comparaison de chaînes n'est pas toujours
une opération dépendante de la culture. Par exemple, les chaînes utilisées en interne par une application doivent
généralement être gérées de la même manière dans toutes les cultures. Quand des données de type chaîne
culturellement indépendantes, telles que des balises XML, des balises HTML, des noms d'utilisateurs, des chemins
d'accès aux fichiers et des noms d'objets système, sont interprétées comme si elles étaient dépendantes de la
culture, le code d'application peut faire l'objet de bogues subtils, de performances médiocres et, dans certains cas,
de problèmes de sécurité.
Cette rubrique examine les méthodes de tri, de comparaison et d’application de la casse pour les chaînes dans .NET,
présente des recommandations pour sélectionner une méthode appropriée de gestion des chaînes et fournit des
informations supplémentaires sur les méthodes de gestion des chaînes. Elle décrit également comment les
données mises en forme, telles que les données numériques et les données de date et d'heure, sont traitées pour
l'affichage et pour le stockage.

Recommandations relatives à l’utilisation de chaînes


Dans le cadre du développement à l’aide de .NET, suivez les recommandations simples ci-après quand vous utilisez
des chaînes :
Utilisez des surcharges qui spécifient explicitement les règles de comparaison de chaînes pour les opérations de
chaînes. Cela implique généralement l'appel d'une surcharge de méthode avec un paramètre de type
StringComparison.
Utilisez StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase pour les comparaisons comme
valeur par défaut sécurisée pour la correspondance de chaînes de culture non spécifiée.
Pour obtenir de meilleures performances, utilisez des comparaisons avec StringComparison.Ordinal ou
StringComparison.OrdinalIgnoreCase .
Utilisez des opérations de chaînes basées sur StringComparison.CurrentCulture quand vous affichez la sortie à
l'utilisateur.
Utilisez les valeurs StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase non linguistiques plutôt
que des opérations de chaînes basées sur CultureInfo.InvariantCulture quand la comparaison est
linguistiquement non pertinente (symbolique, par exemple).
Utilisez la méthode String.ToUpperInvariant à la place de la méthode String.ToLowerInvariant quand vous
normalisez des chaînes pour la comparaison.
Utilisez une surcharge de la méthode String.Equals pour tester si deux chaînes sont égales.
Utilisez les méthodes String.Compare et String.CompareTo pour trier les chaînes, et non pour en vérifier
l'égalité.
Utilisez la mise en forme en fonction de la culture pour afficher des données non-chaînées, telles que les
nombres et les dates, dans une interface utilisateur. Utilisez la mise en forme avec la culture invariante pour
conserver les données qui ne sont pas des chaînes sous forme de chaîne.
Quand vous utilisez des chaînes, évitez les pratiques suivantes :
N'utilisez pas de surcharges qui ne spécifient pas explicitement ou implicitement les règles de comparaison de
chaînes pour les opérations de chaînes.
N'utilisez pas d'opérations de chaînes basées sur StringComparison.InvariantCulture dans la plupart des cas.
L'une des rares exceptions est quand vous rendez persistantes des données linguistiquement explicites, mais
dont la culture n'est pas spécifiée.
N'utilisez pas une surcharge de la méthode String.Compare ou CompareTo , et testez une valeur de retour de
zéro pour déterminer si deux chaînes sont égales.
N'utilisez pas la mise en forme qui tient compte de la culture pour rendre des données numériques ou les
données de date et d'heure sous forme de chaîne.

Spécification explicite de comparaisons de chaînes


La plupart des méthodes de manipulation de chaînes dans .NET sont surchargées. Une ou plusieurs surcharges
acceptent généralement des paramètres par défaut, contrairement à d'autres qui définissent avec précision la
façon dont les chaînes doivent être comparées ou manipulées. La plupart des méthodes qui ne s'appuient pas sur
des valeurs par défaut incluent un paramètre de type StringComparison, qui est une énumération spécifiant
explicitement des règles pour la comparaison de chaînes selon la culture et la casse. Le tableau suivant décrit les
membres de l'énumération StringComparison .

M EM B RE ST RIN GC O M PA RISO N DESC RIP T IO N

CurrentCulture Effectue une comparaison respectant la casse à l'aide de la


culture actuelle.

CurrentCultureIgnoreCase Effectue une comparaison ne respectant pas la casse à l'aide


de la culture actuelle.

InvariantCulture Effectue une comparaison respectant la casse à l'aide de la


culture dite indifférente.

InvariantCultureIgnoreCase Effectue une comparaison ne respectant pas la casse à l'aide


de la culture dite indifférente.

Ordinal Effectue une comparaison ordinale.

OrdinalIgnoreCase Effectue une comparaison ordinale ne respectant pas la casse.

Par exemple, la méthode IndexOf , qui retourne l'index d'une sous-chaîne contenue dans un objet String
correspondant à un caractère ou à une chaîne, a neuf surcharges :
IndexOf(Char), IndexOf(Char, Int32)et IndexOf(Char, Int32, Int32), qui effectuent par défaut une recherche
ordinale (respectant la casse et indépendante de la culture) d'un caractère dans la chaîne.
IndexOf(String), IndexOf(String, Int32)et IndexOf(String, Int32, Int32), qui effectuent par défaut une recherche
respectant la casse et dépendante de la culture d'une sous-chaîne dans la chaîne.
IndexOf(String, StringComparison), IndexOf(String, Int32, StringComparison)et IndexOf(String, Int32, Int32,
StringComparison), qui incluent un paramètre de type StringComparison permettant la spécification de la
forme de la comparaison.
Nous vous recommandons de sélectionner une surcharge qui n'utilise pas de valeurs par défaut, pour les raisons
suivantes :
Certaines surcharges ayant des paramètres par défaut (celles qui recherchent un Char dans l'instance de
chaîne) effectuent une comparaison ordinale, tandis que d'autres (celles qui recherchent une chaîne dans
l'instance de chaîne) sont dépendantes de la culture. Il est difficile de mémoriser quelle méthode utilise
quelle valeur par défaut, et les surcharges peuvent être facilement confondues.
L'objectif du code qui s'appuie sur des valeurs par défaut pour les appels de méthode n'est pas clair. Dans
l’exemple suivant, qui est basé sur des valeurs par défaut, il est difficile de savoir si le développeur voulait en
fait une comparaison ordinale ou linguistique de deux chaînes, ou si une différence de casse entre protocol
et "http" peut entraîner le retour de false .

string protocol = GetProtocol(url);


if (String.Equals(protocol, "http")) {
// ...Code to handle HTTP protocol.
}
else {
throw new InvalidOperationException();
}

Dim protocol As String = GetProtocol(url)


If String.Equals(protocol, "http") Then
' ...Code to handle HTTP protocol.
Else
Throw New InvalidOperationException()
End If

En général, nous vous recommandons d'appeler une méthode qui ne s'appuie pas sur des valeurs par défaut, car
elle rend l'objectif du code non équivoque. Cela rend ensuite le code plus lisible et plus facile à déboguer et à gérer.
L'exemple suivant aborde les questions soulevées à propos de l'exemple précédent. Il explique que la comparaison
ordinale est utilisée et que les différences de casse sont ignorées.

string protocol = GetProtocol(url);


if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
// ...Code to handle HTTP protocol.
}
else {
throw new InvalidOperationException();
}

Dim protocol As String = GetProtocol(url)


If String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase) Then
' ...Code to handle HTTP protocol.
Else
Throw New InvalidOperationException()
End If

Détails de la comparaison de chaînes


La comparaison de chaînes est le cœur de nombreuses opérations liées aux chaînes, en particulier le tri et le test
d'égalité. Les chaînes sont triées dans un ordre déterminé : si "my" s'affiche avant "string" dans une liste triée de
chaînes, "my" doit être considéré comme inférieur ou égal à "string". En outre, la comparaison définit implicitement
l'égalité. L'opération de comparaison retourne zéro pour les chaînes qu'il estime égales. Considérer qu'aucune
chaîne n'est inférieure à l'autre constitue une bonne interprétation. La plupart des opérations significatives
impliquant des chaînes incluent l'une des procédures suivantes, ou les deux : comparaison avec une autre chaîne et
exécution d'une opération de tri bien définie.
NOTE
Vous pouvez télécharger les Sorting Weight Tables, un ensemble de fichiers texte qui contiennent des informations sur les
poids des caractères utilisés dans les opérations de tri et de comparaison pour les systèmes d’exploitation Windows et la
Default Unicode Collation Element Table, la version la plus récente de la table de pondération de tri pour Linux et macOS. La
version spécifique de la table de pondération de tri sur Linux et macOS varie selon la version des bibliothèques International
Components for Unicode installées sur le système. Pour plus d’informations sur les versions ICU et les versions Unicode
qu’elles implémentent, consultez Téléchargement d’ICU.

Toutefois, l'évaluation de l'égalité ou de l'ordre de tri de deux chaînes ne produit pas un résultat correct unique ; le
résultat dépend des critères utilisés pour comparer les chaînes. En particulier, les comparaisons de chaînes qui sont
ordinales ou basées sur les conventions de casse et de tri de la culture actuelle ou la culture invariante (culture aux
paramètres régionaux non spécifiés basée sur la langue anglaise) peuvent produire des résultats différents.
En outre, les comparaisons de chaînes à l’aide de différentes versions de .NET ou à l’aide de .NET sur différents
systèmes d’exploitation ou des versions différentes de système d’exploitation peuvent retourner des résultats
différents. Pour plus d’informations, consultez Chaînes et norme Unicode.
Comparaisons de chaînes qui utilisent la culture actuelle
L'un des critères à prendre en compte est l'utilisation des conventions de la culture actuelle lors de la comparaison
de chaînes. Les comparaisons basées sur la culture actuelle utilisent la culture ou les paramètres régionaux actuels
du thread. Si la culture n'est pas définie par l'utilisateur, sa valeur par défaut est le paramètre défini dans la fenêtre
Options régionales du Panneau de configuration. Vous devez toujours utiliser des comparaisons basées sur la
culture actuelle quand les données sont linguistiquement pertinentes et quand elles reflètent l'intervention de
l'utilisateur dépendante de la culture.
Toutefois, le comportement de la comparaison et de la casse dans .NET change en fonction de la culture. Cela se
produit quand une application s'exécute sur un ordinateur dont la culture est différente de celle de l'ordinateur sur
lequel l'application a été développée, ou quand le thread en cours d'exécution change sa culture. Ce comportement
est intentionnel, mais il reste peu évident à de nombreux développeurs. L'exemple suivant illustre les différences
au niveau de l'ordre de tri entre les cultures Anglais (États-Unis) ("en-US") et Suédois ("sv-SE"). Notez que les mots
« ångström », « Windows » et « Visual Studio » s’affichent à des positions différentes dans les tableaux de chaînes
triées.
using System;
using System.Globalization;
using System.Threading;

public class Example


{
public static void Main()
{
string[] values= { "able", "ångström", "apple", "Æble",
"Windows", "Visual Studio" };
Array.Sort(values);
DisplayArray(values);

// Change culture to Swedish (Sweden).


string originalCulture = CultureInfo.CurrentCulture.Name;
Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE");
Array.Sort(values);
DisplayArray(values);

// Restore the original culture.


Thread.CurrentThread.CurrentCulture = new CultureInfo(originalCulture);
}

private static void DisplayArray(string[] values)


{
Console.WriteLine("Sorting using the {0} culture:",
CultureInfo.CurrentCulture.Name);
foreach (string value in values)
Console.WriteLine(" {0}", value);

Console.WriteLine();
}
}
// The example displays the following output:
// Sorting using the en-US culture:
// able
// Æble
// ångström
// apple
// Visual Studio
// Windows
//
// Sorting using the sv-SE culture:
// able
// Æble
// apple
// Windows
// Visual Studio
// ångström
Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
Dim values() As String = {"able", "ångström", "apple", _
"Æble", "Windows", "Visual Studio"}
Array.Sort(values)
DisplayArray(values)

' Change culture to Swedish (Sweden).


Dim originalCulture As String = CultureInfo.CurrentCulture.Name
Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
Array.Sort(values)
DisplayArray(values)

' Restore the original culture.


Thread.CurrentThread.CurrentCulture = New CultureInfo(originalCulture)
End Sub

Private Sub DisplayArray(values() As String)


Console.WRiteLine("Sorting using the {0} culture:", _
CultureInfo.CurrentCulture.Name)
For Each value As String In values
Console.WriteLine(" {0}", value)
Next
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' Sorting using the en-US culture:
' able
' Æble
' ångström
' apple
' Visual Studio
' Windows
'
' Sorting using the sv-SE culture:
' able
' Æble
' apple
' Windows
' Visual Studio
' ångström

Les comparaisons ne respectant pas la casse qui utilisent la culture actuelle sont identiques aux comparaisons
dépendantes de la culture, mais elles ignorent la casse, comme défini par la culture actuelle du thread. Ce
comportement peut également se manifester dans les ordres de tri.
Les comparaisons qui utilisent la sémantique de la culture actuelle sont la valeur par défaut pour les méthodes
suivantes :
les surchargesString.Compare qui n'incluent pas de paramètre StringComparison ;
les surchargesString.CompareTo ;
la méthode String.StartsWith(String) par défaut et la méthode String.StartsWith(String, Boolean, CultureInfo)
avec un paramètre null CultureInfo ;
la méthode String.EndsWith(String) par défaut et la méthode String.EndsWith(String, Boolean, CultureInfo) avec
un paramètre null CultureInfo ;
les surchargesString.IndexOf qui acceptent un String comme paramètre de recherche et qui n'ont pas de
paramètre StringComparison ;
les surchargesString.LastIndexOf qui acceptent un String comme paramètre de recherche et qui n'ont pas de
paramètre StringComparison ;
Dans tous les cas, nous vous recommandons d'appeler une surcharge qui a un paramètre StringComparison , afin
que l'objectif de l'appel de la méthode soit clair.
Des bogues, plus ou moins subtils, peuvent émerger quand des données de type chaîne non linguistiques sont
interprétées linguistiquement, ou quand des données de type chaîne d'une culture particulière sont interprétées à
l'aide des conventions d'une autre culture. L'exemple canonique est le problème du caractère I en turc.
Dans presque tous les alphabets latins, y compris l'anglais (États-Unis), le caractère "i" (\u0069) est la version en
minuscule du caractère "I" (\u0049). Cette règle de casse devient rapidement la valeur par défaut pour quelqu'un
qui effectue de la programmation dans une telle culture. Toutefois, l’alphabet turc ("tr-TR") inclut un caractère
"I avec un point", "İ" (\u0130), qui est la version en majuscule de "i". Le turc comprend également un caractère "i
sans point" minuscule, "ı" (\u0131), dont la majuscule est "I". Ce comportement se produit également dans la
culture azérie ("az").
Par conséquent, les suppositions faites sur la mise en majuscule du "i" ou de la mise en minuscule du "I" ne sont
pas valides dans toutes les cultures. Si vous utilisez les surcharges par défaut pour les routines de comparaison de
chaînes, elles varieront suivant les cultures. Si les données à comparer sont non linguistiques, l'utilisation de
surcharges par défaut peut produire des résultats indésirables, comme le montre la tentative suivante d'exécution
d'une comparaison ne respectant pas la casse des chaînes "file" et "FILE".

using System;
using System.Globalization;
using System.Threading;

public class Example


{
public static void Main()
{
string fileUrl = "file";
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine("Culture = {0}",
Thread.CurrentThread.CurrentCulture.DisplayName);
Console.WriteLine("(file == FILE) = {0}",
fileUrl.StartsWith("FILE", true, null));
Console.WriteLine();

Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");


Console.WriteLine("Culture = {0}",
Thread.CurrentThread.CurrentCulture.DisplayName);
Console.WriteLine("(file == FILE) = {0}",
fileUrl.StartsWith("FILE", true, null));
}
}
// The example displays the following output:
// Culture = English (United States)
// (file == FILE) = True
//
// Culture = Turkish (Turkey)
// (file == FILE) = False
Imports System.Globalization
Imports System.Threading

Module Example
Public Sub Main()
Dim fileUrl = "file"
Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
Console.WriteLine("Culture = {0}", _
Thread.CurrentThread.CurrentCulture.DisplayName)
Console.WriteLine("(file == FILE) = {0}", _
fileUrl.StartsWith("FILE", True, Nothing))
Console.WriteLine()

Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")


Console.WriteLine("Culture = {0}", _
Thread.CurrentThread.CurrentCulture.DisplayName)
Console.WriteLine("(file == FILE) = {0}", _
fileUrl.StartsWith("FILE", True, Nothing))
End Sub
End Module
' The example displays the following output:
' Culture = English (United States)
' (file == FILE) = True
'
' Culture = Turkish (Turkey)
' (file == FILE) = False

Cette comparaison peut provoquer de sérieux problèmes si la culture est utilisée par inadvertance dans des
paramètres de sécurité sensibles, comme dans l'exemple suivant. Un appel de méthode comme
IsFileURI("file:") retourne true si la culture actuelle est Anglais (États-Unis), mais false si la culture actuelle
est Turc. Par conséquent, sur les systèmes turcs, quelqu'un pourrait contourner les mesures de sécurité qui
bloquent l'accès aux URI ne respectant pas la casse qui commencent par "FILE:".

public static bool IsFileURI(String path)


{
return path.StartsWith("FILE:", true, null);
}

Public Shared Function IsFileURI(path As String) As Boolean


Return path.StartsWith("FILE:", True, Nothing)
End Function

Dans ce cas, étant donné que « file : » est destiné à être interprété comme un identificateur non linguistique et
indépendant de la culture, le code doit plutôt être écrit comme indiqué dans l’exemple suivant :

public static bool IsFileURI(string path)


{
return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
}

Public Shared Function IsFileURI(path As String) As Boolean


Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function

Opérations de chaînes ordinales


La spécification de la valeur StringComparison.Ordinal ou StringComparison.OrdinalIgnoreCase dans un appel de
méthode correspond à une comparaison non linguistique dans laquelle les fonctionnalités de langages naturels
sont ignorées. Les méthodes qui sont appelées avec ces valeurs StringComparison font reposer les décisions
d'opération de chaîne sur de simples comparaisons d'octets plutôt que sur la casse ou des tables d'équivalences
paramétrables par la culture. Dans la plupart des cas, cette approche correspond le mieux à l'interprétation de
chaînes prévue tout en rendant le code plus rapide et plus fiable.
Les comparaisons ordinales sont des comparaisons de chaînes dans lesquelles chaque octet de chaque chaîne est
comparé sans interprétation linguistique ; par exemple, "windows" ne correspond pas à "Windows". Il s’agit
fondamentalement d’un appel à la fonction strcmp du runtime C. Utilisez cette comparaison quand le contexte
indique que les chaînes doivent correspondre exactement ou demande une stratégie de correspondance classique.
En outre, la comparaison ordinale est l'opération de comparaison la plus rapide, car elle n'applique aucune règle
linguistique lors de la détermination d'un résultat.
Les chaînes dans .NET peuvent contenir des caractères Null incorporés. L'une des différences les plus évidentes
entre la comparaison ordinale et la comparaison dépendante de la culture (y compris les comparaisons qui
utilisent la culture dite indifférente) concerne la gestion des caractères Null incorporés dans une chaîne. Ces
caractères sont ignorés quand vous utilisez les méthodes String.Compare et String.Equals pour effectuer des
comparaisons dépendantes de la culture (notamment des comparaisons qui utilisent la culture dite indifférente).
Par conséquent, dans les comparaisons dépendantes de la culture, les chaînes qui contiennent des caractères Null
incorporés peuvent être considérées comme égales à des chaînes qui n'en contiennent pas.

IMPORTANT
Les méthodes de comparaison de chaînes ignorent les caractères Null incorporés, contrairement aux méthodes de recherche
de chaînes telles que String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOfet String.StartsWith .

L’exemple suivant effectue une comparaison dépendante de la culture de la chaîne "AA" avec une chaîne semblable
qui contient plusieurs caractères null incorporés entre "A" et "a", et montre comment les deux chaînes sont
considérées comme égales :
using System;

public class Example


{
public static void Main()
{
string str1 = "Aa";
string str2 = "A" + new String('\u0000', 3) + "a";
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):",
str1, ShowBytes(str1), str2, ShowBytes(str2));
Console.WriteLine(" With String.Compare:");
Console.WriteLine(" Current Culture: {0}",
String.Compare(str1, str2, StringComparison.CurrentCulture));
Console.WriteLine(" Invariant Culture: {0}",
String.Compare(str1, str2, StringComparison.InvariantCulture));

Console.WriteLine(" With String.Equals:");


Console.WriteLine(" Current Culture: {0}",
String.Equals(str1, str2, StringComparison.CurrentCulture));
Console.WriteLine(" Invariant Culture: {0}",
String.Equals(str1, str2, StringComparison.InvariantCulture));
}

private static string ShowBytes(string str)


{
string hexString = String.Empty;
for (int ctr = 0; ctr < str.Length; ctr++)
{
string result = String.Empty;
result = Convert.ToInt32(str[ctr]).ToString("X4");
result = " " + result.Substring(0,2) + " " + result.Substring(2, 2);
hexString += result;
}
return hexString.Trim();
}
}
// The example displays the following output:
// Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
// With String.Compare:
// Current Culture: 0
// Invariant Culture: 0
// With String.Equals:
// Current Culture: True
// Invariant Culture: True
Module Example
Public Sub Main()
Dim str1 As String = "Aa"
Dim str2 As String = "A" + New String(Convert.ToChar(0), 3) + "a"
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
str1, ShowBytes(str1), str2, ShowBytes(str2))
Console.WriteLine(" With String.Compare:")
Console.WriteLine(" Current Culture: {0}", _
String.Compare(str1, str2, StringComparison.CurrentCulture))
Console.WriteLine(" Invariant Culture: {0}", _
String.Compare(str1, str2, StringComparison.InvariantCulture))

Console.WriteLine(" With String.Equals:")


Console.WriteLine(" Current Culture: {0}", _
String.Equals(str1, str2, StringComparison.CurrentCulture))
Console.WriteLine(" Invariant Culture: {0}", _
String.Equals(str1, str2, StringComparison.InvariantCulture))
End Sub

Private Function ShowBytes(str As String) As String


Dim hexString As String = String.Empty
For ctr As Integer = 0 To str.Length - 1
Dim result As String = String.Empty
result = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
result = " " + result.Substring(0, 2) + " " + result.Substring(2, 2)
hexString += result
Next
Return hexString.Trim()
End Function
End Module

Toutefois, les chaînes ne sont pas considérées comme égales quand vous utilisez la comparaison ordinale, comme
le montre l’exemple suivant :

Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):",


str1, ShowBytes(str1), str2, ShowBytes(str2));
Console.WriteLine(" With String.Compare:");
Console.WriteLine(" Ordinal: {0}",
String.Compare(str1, str2, StringComparison.Ordinal));

Console.WriteLine(" With String.Equals:");


Console.WriteLine(" Ordinal: {0}",
String.Equals(str1, str2, StringComparison.Ordinal));
// The example displays the following output:
// Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
// With String.Compare:
// Ordinal: 97
// With String.Equals:
// Ordinal: False
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
str1, ShowBytes(str1), str2, ShowBytes(str2))
Console.WriteLine(" With String.Compare:")
Console.WriteLine(" Ordinal: {0}", _
String.Compare(str1, str2, StringComparison.Ordinal))

Console.WriteLine(" With String.Equals:")


Console.WriteLine(" Ordinal: {0}", _
String.Equals(str1, str2, StringComparison.Ordinal))
' The example displays the following output:
' Comparing 'Aa' (00 41 00 61) and 'A a' (00 41 00 00 00 00 00 00 00 61):
' With String.Compare:
' Ordinal: 97
' With String.Equals:
' Ordinal: False

Les comparaisons ordinales ne respectant pas la casse sont l'approche la plus conservatrice suivante. Ces
comparaisons ignorent la plus grande partie de la casse ; par exemple, "windows" correspond à "Windows". Lors
du traitement de caractères ASCII, cette stratégie est équivalente à StringComparison.Ordinal, excepté qu'elle
ignore la casse ASCII habituelle. Par conséquent, tout caractère de la plage [A, Z] (\u0041-\u005A) correspond au
caractère correspondant de la plage [a,z] (\u0061-\007A). La casse hors de la plage ASCII utilise les tables de la
culture dite indifférente. Par conséquent, la comparaison suivante :

String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);

String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)

est équivalente à la comparaison suivante (mais plus rapide) :

String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(),
StringComparison.Ordinal);

String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(),
StringComparison.Ordinal)

Ces comparaisons restent très rapides.

NOTE
StringComparison.OrdinalIgnoreCaseconstitue la meilleure représentation du comportement de chaîne du système de
fichiers, des clés de Registre et des valeurs, ainsi que des variables d'environnement.

StringComparison.Ordinal et StringComparison.OrdinalIgnoreCase utilisent tous les deux directement les valeurs


binaires et sont les plus adaptés à la mise en correspondance. Quand vous n'êtes pas sûr de vos paramètres de
comparaison, utilisez l'une de ces deux valeurs. Toutefois, étant donné qu'elles effectuent une comparaison octet
par octet, elles n'effectuent pas le tri selon un ordre de tri linguistique (comme un dictionnaire français), mais selon
un ordre de tri binaire. Les résultats peuvent sembler étranges dans la plupart des contextes s'ils sont affichés aux
utilisateurs.
La sémantique ordinale est la valeur par défaut pour les surcharges de String.Equals qui n'incluent pas d'argument
StringComparison (notamment l'opérateur d'égalité). Dans tous les cas, nous vous recommandons d'appeler une
surcharge ayant un paramètre StringComparison .
opérations de chaîne qui utilisent la culture dite indifférente
Les comparaisons avec la culture dite indifférente utilisent la propriété CompareInfo retournée par la propriété
CultureInfo.InvariantCulture statique. Ce comportement est le même sur tous les systèmes ; il traduit tous les
caractères en dehors de sa plage en ce qu'il suppose être des caractères invariants équivalents. Cette stratégie peut
être utile pour la gestion d'un jeu de comportements de chaîne dans toutes les cultures, mais elle donne souvent
des résultats inattendus.
Les comparaisons ne respectant pas la casse avec la culture dite indifférente utilisent également la propriété
CompareInfo statique retournée également par la propriété CultureInfo.InvariantCulture statique pour les
informations de comparaison. Toutes les différences de casse entre ces caractères traduits sont ignorées.
Les comparaisons qui utilisent StringComparison.InvariantCulture et StringComparison.Ordinal fonctionnent de la
même manière sur les chaînes ASCII. Toutefois, StringComparison.InvariantCulture prend des décisions
linguistiques qui peuvent ne pas être appropriées pour les chaînes qui doivent être interprétées comme un jeu
d'octets. L’objet CultureInfo.InvariantCulture.CompareInfo entraîne l’interprétation par la méthode Compare de
certains jeux de caractères comme étant équivalents. Par exemple, l'équivalence suivante est valide dans la culture
dite indifférente :
InvariantCulture: a + ̊ = å
Le caractère LETTRE MINUSCULE LATINE A "a" (\u0061), quand il se trouve à côté du caractère DIACRITIQUE
ROND EN CHEF " + " ̊" (\u030a), est interprété comme une LETTRE MINUSCULE LATINE A AVEC DIACRITIQUE
ROND EN CHEF "å" (\u00e5). Comme le montre l'exemple suivant, ce comportement diffère de la comparaison
ordinale.

string separated = "\u0061\u030a";


string combined = "\u00e5";

Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",


separated, combined,
String.Compare(separated, combined,
StringComparison.InvariantCulture) == 0);

Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",


separated, combined,
String.Compare(separated, combined,
StringComparison.Ordinal) == 0);
// The example displays the following output:
// Equal sort weight of a° and å using InvariantCulture: True
// Equal sort weight of a° and å using Ordinal: False

Dim separated As String = ChrW(&h61) + ChrW(&h30a)


Dim combined As String = ChrW(&he5)

Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}", _


separated, combined, _
String.Compare(separated, combined, _
StringComparison.InvariantCulture) = 0)

Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}", _


separated, combined, _
String.Compare(separated, combined, _
StringComparison.Ordinal) = 0)
' The example displays the following output:
' Equal sort weight of a° and å using InvariantCulture: True
' Equal sort weight of a° and å using Ordinal: False

Lors de l’interprétation de noms de fichiers, de cookies ou de tout autre élément dans lequel peut s’afficher une
combinaison telle que "å", les comparaisons ordinales offrent toujours le comportement le plus transparent et le
plus approprié.
En définitive, la culture dite indifférente a très peu de propriétés qui la rendent utile pour la comparaison. Elle
effectue la comparaison d'une manière linguistiquement pertinente, qui l'empêche de garantir une équivalence
symbolique complète, mais elle ne constitue pas le choix approprié pour l'affichage dans n'importe quelle culture.
L'une des rares raisons justifiant l'utilisation de StringComparison.InvariantCulture pour la comparaison est de
rendre persistantes des données classées pour un affichage identique dans toutes les cultures. Par exemple, si un
fichier de données volumineux qui contient une liste d'identificateurs triés à des fins d'affichage accompagne une
application, un ajout à cette liste nécessiterait une insertion avec un tri de style invariant.

Choix d’un membre StringComparison pour votre appel de méthode


Le tableau suivant présente le mappage du contexte de chaîne sémantique à un membre de l' StringComparison
énumération :

VA L EUR SY ST EM . ST RIN GC O M PA RISO N

DO N N ÉES C O M P O RT EM EN T VA L UE

Identificateurs internes respectant la Identificateur non linguistique, où les Ordinal


casse. octets correspondent exactement.

Identificateurs respectant la casse dans


des normes telles que XML et HTTP.

Paramètres liés à la sécurité respectant


la casse.

Identificateurs internes ne respectant Identificateur non linguistique, où la OrdinalIgnoreCase


pas la casse. casse n'est pas pertinente ; en
particulier, les données stockées dans la
Identificateurs ne respectant pas la plupart des services système Windows.
casse dans des normes telles que XML
et HTTP.

Chemins d'accès aux fichiers.

Clés et valeurs de Registre.

Variables d'environnement.

Identificateurs de ressource (par


exemple, noms de handles).

Paramètres liés à la sécurité ne


respectant pas la casse.

Certaines données rendues persistantes Données dont la culture n'est pas InvariantCulture
et linguistiquement pertinentes. spécifiée qui sont toutefois
linguistiquement pertinentes. -ou-
Affichage de données linguistiques qui
nécessitent un ordre de tri fixe. InvariantCultureIgnoreCase

Données affichées à l'utilisateur. Données qui nécessitent des usages CurrentCulture


linguistiques locaux.
La plupart des entrées d'utilisateur. -ou-

CurrentCultureIgnoreCase
Méthodes courantes de comparaison de chaînes dans .NET
Les sections suivantes décrivent les méthodes le plus souvent utilisées pour la comparaison de chaînes.
String.Compare
Interprétation par défaut : StringComparison.CurrentCulture.
En tant qu'opération la plus centrale de l'interprétation de chaînes, toutes les instances de ces appels de méthode
doivent être examinées pour déterminer si les chaînes doivent être interprétées d'après la culture actuelle ou être
dissociées de la culture (symboliquement). L'opération appropriée est, en général, la dernière, et une comparaison
StringComparison.Ordinal doit être utilisée à la place.
La classe System.Globalization.CompareInfo , retournée par la propriété CultureInfo.CompareInfo , inclut
également une méthode Compare qui fournit un grand nombre d'options de correspondance (ordinale, ignorance
des espaces blancs, ignorance du type Kana, etc.) au moyen de l'énumération d'indicateur CompareOptions .
String.CompareTo
Interprétation par défaut : StringComparison.CurrentCulture.
Cette méthode n'offre actuellement pas de surcharge spécifiant un type StringComparison . Il est généralement
possible de convertir cette méthode dans la forme String.Compare(String, String, StringComparison)
recommandée.
Les types qui implémentent les interfaces IComparable et IComparable<T> implémentent cette méthode. Étant
donné qu'elle n'offre pas la possibilité d'un paramètre StringComparison , les types d'implémentation permettent
souvent à l'utilisateur de spécifier un StringComparer dans leur constructeur. L'exemple suivant définit une classe
FileName dont le constructeur de classe inclut un paramètre StringComparer . Cet objet StringComparer est
ensuite utilisé dans la méthode FileName.CompareTo .
using System;

public class FileName : IComparable


{
string fname;
StringComparer comparer;

public FileName(string name, StringComparer comparer)


{
if (String.IsNullOrEmpty(name))
throw new ArgumentNullException("name");

this.fname = name;

if (comparer != null)
this.comparer = comparer;
else
this.comparer = StringComparer.OrdinalIgnoreCase;
}

public string Name


{
get { return fname; }
}

public int CompareTo(object obj)


{
if (obj == null) return 1;

if (! (obj is FileName))
return comparer.Compare(this.fname, obj.ToString());
else
return comparer.Compare(this.fname, ((FileName) obj).Name);
}
}
Public Class FileName : Implements IComparable
Dim fname As String
Dim comparer As StringComparer

Public Sub New(name As String, comparer As StringComparer)


If String.IsNullOrEmpty(name) Then
Throw New ArgumentNullException("name")
End If

Me.fname = name

If comparer IsNot Nothing Then


Me.comparer = comparer
Else
Me.comparer = StringComparer.OrdinalIgnoreCase
End If
End Sub

Public ReadOnly Property Name As String


Get
Return fname
End Get
End Property

Public Function CompareTo(obj As Object) As Integer _


Implements IComparable.CompareTo
If obj Is Nothing Then Return 1

If Not TypeOf obj Is FileName Then


obj = obj.ToString()
Else
obj = CType(obj, FileName).Name
End If
Return comparer.Compare(Me.fname, obj)
End Function
End Class

String.Equals
Interprétation par défaut : StringComparison.Ordinal.
La classe String vous permet de tester l'égalité en appelant les surcharges de méthode Equals statique ou
d'instance, ou en utilisant l'opérateur d'égalité statique. Par défaut, les surcharges et l'opérateur utilisent la
comparaison ordinale. Toutefois, nous vous recommandons quand même d'appeler une surcharge qui spécifie
explicitement le type StringComparison , même si vous voulez effectuer une comparaison ordinale ; cela le
simplifie la recherche d'une interprétation de chaîne particulière dans du code.
String.ToUpper et String.ToLower
Interprétation par défaut : StringComparison.CurrentCulture.
Vous devez faire preuve de prudence quand vous utilisez ces méthodes, car imposer une majuscule ou une
minuscule dans une chaîne est souvent utilisé comme une petite normalisation pour la comparaison de chaînes
indépendamment de la casse. Si tel est le cas, vous devez envisager d'utiliser une comparaison ne respectant pas la
casse.
Les méthodes String.ToUpperInvariant et String.ToLowerInvariant sont également disponibles. ToUpperInvariant est
le moyen standard de normaliser la casse. Les comparaisons faites à l'aide de
StringComparison.OrdinalIgnoreCase sont, sur le plan comportemental, la composition de deux appels : appel à
ToUpperInvariant sur les deux arguments de chaîne, et exécution d'une comparaison à l'aide de
StringComparison.Ordinal.
Des surcharges sont également disponibles pour la conversion en majuscules et en minuscules dans une culture
spécifique, en passant à la méthode un objet CultureInfo qui représente cette culture.
Char.ToUpper et Char.ToLower
Interprétation par défaut : StringComparison.CurrentCulture.
Ces méthodes fonctionnent de la même façon que les méthodes String.ToUpper et String.ToLower décrites dans la
section précédente.
String.StartsWith et String.EndsWith
Interprétation par défaut : StringComparison.CurrentCulture.
Par défaut, ces deux méthodes effectuent une comparaison dépendante de la culture.
String.IndexOf et String.LastIndexOf
Interprétation par défaut : StringComparison.CurrentCulture.
La façon dont les surcharges par défaut de ces méthodes effectuent les comparaisons n'est pas cohérente. Toutes
les méthodes String.IndexOf et String.LastIndexOf qui incluent un paramètre Char effectuent une comparaison
ordinale, mais les méthodes String.IndexOf et String.LastIndexOf par défaut qui incluent un paramètre String
effectuent une comparaison dépendante de la culture.
Si vous appelez la méthode String.IndexOf(String) ou String.LastIndexOf(String) et que vous lui passez une chaîne à
localiser dans l'instance actuelle, nous vous recommandons d'appeler une surcharge qui spécifie explicitement le
type StringComparison . Les surcharges qui incluent un argument Char ne vous permettent pas de spécifier un
type StringComparison .

Méthodes qui effectuent indirectement la comparaison de chaînes


Certaines méthodes autres que les méthodes de chaîne dont l'opération centrale est la comparaison de chaînes
utilisent le type StringComparer . La classe StringComparer inclut six propriétés statiques qui retournent des
instances de StringComparer dont les méthodes StringComparer.Compare effectuent les types de comparaisons de
chaînes suivants :
Comparaisons de chaînes dépendantes de la culture à l'aide de la culture actuelle. Cet objet StringComparer est
retourné par la propriété StringComparer.CurrentCulture .
Comparaisons ne respectant pas la casse à l'aide de la culture actuelle. Cet objet StringComparer est retourné
par la propriété StringComparer.CurrentCultureIgnoreCase .
Comparaisons indépendantes de la culture à l'aide des règles de comparaison de mots de la culture dite
indifférente. Cet objet StringComparer est retourné par la propriété StringComparer.InvariantCulture .
Comparaisons ne respectant pas la casse et indépendantes de la culture à l'aide des règles de comparaison des
mots de la culture dite indifférente. Cet objet StringComparer est retourné par la propriété
StringComparer.InvariantCultureIgnoreCase .
Comparaison ordinale. Cet objet StringComparer est retourné par la propriété StringComparer.Ordinal .
Comparaison ordinale ne respectant pas la casse. Cet objet StringComparer est retourné par la propriété
StringComparer.OrdinalIgnoreCase .
Array.Sort et Array.BinarySearch
Interprétation par défaut : StringComparison.CurrentCulture.
Quand vous stockez des données dans une collection ou quand vous lisez des données persistantes à partir d’un
fichier ou d’une base de données dans une collection, le changement de culture actuelle peut invalider les
invariants de la collection. La méthode Array.BinarySearch suppose que les éléments du tableau dans lequel
effectuer la recherche sont déjà triés. Pour trier tout élément de chaîne dans le tableau, la méthode Array.Sort
appelle la méthode String.Compare pour classer les éléments individuels. L'utilisation d'un comparateur
dépendant de la culture peut s'avérer dangereux si la culture change entre le moment où le tableau est trié et le
moment où son contenu fait l'objet d'une recherche. Par exemple, dans le code suivant, le stockage et la
récupération fonctionnent sur le comparateur fourni implicitement par la propriété
Thread.CurrentThread.CurrentCulture statique. Si la culture peut changer entre les appels à StoreNames et
DoesNameExist , et surtout si le contenu du tableau est rendu persistant à un moment donné entre les deux appels
de méthode, la recherche binaire peut échouer.

// Incorrect.
string []storedNames;

public void StoreNames(string [] names)


{
int index = 0;
storedNames = new string[names.Length];

foreach (string name in names)


{
this.storedNames[index++] = name;
}

Array.Sort(names); // Line A.
}

public bool DoesNameExist(string name)


{
return (Array.BinarySearch(this.storedNames, name) >= 0); // Line B.
}

' Incorrect.
Dim storedNames() As String

Public Sub StoreNames(names() As String)


Dim index As Integer = 0
ReDim storedNames(names.Length - 1)

For Each name As String In names


Me.storedNames(index) = name
index += 1
Next

Array.Sort(names) ' Line A.


End Sub

Public Function DoesNameExist(name As String) As Boolean


Return Array.BinarySearch(Me.storedNames, name) >= 0 ' Line B.
End Function

L'exemple suivant, qui utilise la même méthode de comparaison ordinale (indépendante de la culture) pour trier le
tableau et pour effectuer une recherche, présente une variation recommandée. Le code de modification est reflété
dans les lignes libellées Line A et Line B dans les deux exemples.
// Correct.
string []storedNames;

public void StoreNames(string [] names)


{
int index = 0;
storedNames = new string[names.Length];

foreach (string name in names)


{
this.storedNames[index++] = name;
}

Array.Sort(names, StringComparer.Ordinal); // Line A.


}

public bool DoesNameExist(string name)


{
return (Array.BinarySearch(this.storedNames, name, StringComparer.Ordinal) >= 0); // Line B.
}

' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)


Dim index As Integer = 0
ReDim storedNames(names.Length - 1)

For Each name As String In names


Me.storedNames(index) = name
index += 1
Next

Array.Sort(names, StringComparer.Ordinal) ' Line A.


End Sub

Public Function DoesNameExist(name As String) As Boolean


Return Array.BinarySearch(Me.storedNames, name, StringComparer.Ordinal) >= 0 ' Line B.
End Function

Si ces données sont rendues persistantes et déplacées dans toutes les cultures, et que le tri est utilisé pour
présenter ces données à l'utilisateur, vous pouvez envisager d'utiliser StringComparison.InvariantCulture, qui
fonctionne linguistiquement pour une meilleure sortie d'utilisateur mais n'est pas affecté par les modifications
apportées à la culture. L'exemple suivant modifie les deux exemples précédents pour utiliser la culture dite
indifférente pour le tri du tableau et la recherche dans celui-ci.
// Correct.
string []storedNames;

public void StoreNames(string [] names)


{
int index = 0;
storedNames = new string[names.Length];

foreach (string name in names)


{
this.storedNames[index++] = name;
}

Array.Sort(names, StringComparer.InvariantCulture); // Line A.


}

public bool DoesNameExist(string name)


{
return (Array.BinarySearch(this.storedNames, name, StringComparer.InvariantCulture) >= 0); // Line B.
}

' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)


Dim index As Integer = 0
ReDim storedNames(names.Length - 1)

For Each name As String In names


Me.storedNames(index) = name
index += 1
Next

Array.Sort(names, StringComparer.InvariantCulture) ' Line A.


End Sub

Public Function DoesNameExist(name As String) As Boolean


Return Array.BinarySearch(Me.storedNames, name, StringComparer.InvariantCulture) >= 0 ' Line B.
End Function

Exemple de collections : constructeur Hashtable


Le hachage de chaînes constitue un deuxième exemple d'opération qui est affectée par la façon dont des chaînes
sont comparées.
L'exemple suivant instancie un objet Hashtable en lui passant l'objet StringComparer qui est retourné par la
propriété StringComparer.OrdinalIgnoreCase . Étant donné qu'une classe StringComparer dérivée de
StringComparer implémente l'interface IEqualityComparer , sa méthode GetHashCode est utilisée pour calculer le
code de hachage de chaînes dans la table de hachage.
const int initialTableCapacity = 100;
Hashtable h;

public void PopulateFileTable(string directory)


{
h = new Hashtable(initialTableCapacity,
StringComparer.OrdinalIgnoreCase);

foreach (string file in Directory.GetFiles(directory))


h.Add(file, File.GetCreationTime(file));
}

public void PrintCreationTime(string targetFile)


{
Object dt = h[targetFile];
if (dt != null)
{
Console.WriteLine("File {0} was created at time {1}.",
targetFile,
(DateTime) dt);
}
else
{
Console.WriteLine("File {0} does not exist.", targetFile);
}
}

Const initialTableCapacity As Integer = 100


Dim h As Hashtable

Public Sub PopulateFileTable(dir As String)


h = New Hashtable(initialTableCapacity, _
StringComparer.OrdinalIgnoreCase)

For Each filename As String In Directory.GetFiles(dir)


h.Add(filename, File.GetCreationTime(filename))
Next
End Sub

Public Sub PrintCreationTime(targetFile As String)


Dim dt As Object = h(targetFile)
If dt IsNot Nothing Then
Console.WriteLine("File {0} was created at {1}.", _
targetFile, _
CDate(dt))
Else
Console.WriteLine("File {0} does not exist.", targetFile)
End If
End Sub

Affichage et persistance des données mises en forme


Lorsque vous affichez des données non-chaînées telles que les nombres et les dates et heures aux utilisateurs,
mettez-les en forme en utilisant les paramètres de la culture de l'utilisateur. Par défaut, tous les éléments suivants
utilisent la culture du thread actuelle dans les opérations de mise en forme :
Chaînes interpolées prises en charge par les compilateurs C# et Visual Basic.
Opérations de concaténation de chaîne qui utilisent les opérateurs de concaténation C# ou Visual Basic, ou qui
appellent directement la méthode String.Concat.
Méthode String.Format
Méthodes ToString des types numériques et des types de date et d’heure.
Pour spécifier explicitement qu’une chaîne doit être mise en forme en utilisant les conventions d’une culture
désignée ou de la culture invariante, vous pouvez procéder comme suit :
Quand vous utilisez les méthodes String.Format et ToString , appelez une surcharge qui a un paramètre
provider , tel que String.Format(IFormatProvider, String, Object[]) ou DateTime.ToString(IFormatProvider) et
passez-lui la propriété CultureInfo.CurrentCulture, une instance CultureInfo qui représente la culture
souhaitée, ou la propriété CultureInfo.InvariantCulture.
Pour la concaténation de chaîne, n’autorisez pas le compilateur à effectuer de conversions implicites. Au lieu
de cela, effectuez une conversion explicite en appelant une surcharge ToString ayant un paramètre
provider . Par exemple, le compilateur utilise implicitement la culture actuelle lors de la conversion Double
d’une valeur en une chaîne dans le code suivant :

string concat1 = "The amount is " + 126.03 + ".";


Console.WriteLine(concat1);

Dim concat1 As String = "The amount is " & 126.03 & "."
Console.WriteLine(concat1)

Au lieu de cela, vous pouvez spécifier explicitement la culture dont les conventions de mise en forme sont
utilisées lors de la conversion en appelant la Double.ToString(IFormatProvider) méthode, comme le montre
le code suivant :

string concat2 = "The amount is " + 126.03.ToString(CultureInfo.InvariantCulture) + ".";


Console.WriteLine(concat2);

Dim concat2 As String = "The amount is " & 126.03.ToString(CultureInfo.InvariantCulture) & "."
Console.WriteLine(concat2)

Pour l’interpolation de chaîne, plutôt que d’affecter une chaîne interpolée à une instance String, affectez-la à
un élément FormattableString. Vous pouvez ensuite appeler sa méthode FormattableString.ToString() pour
produire une chaîne de résultat qui reflète les conventions de la culture actuelle, ou vous pouvez appeler la
méthode FormattableString.ToString(IFormatProvider) pour produire une chaîne de résultat qui reflète les
conventions d’une culture spécifiée. Vous pouvez également transmettre la chaîne pouvant être mise en
forme à la méthode FormattableString.Invariant statique pour produire une chaîne de résultat qui reflète les
conventions de la culture invariante. L'exemple suivant illustre cette approche. (La sortie de l’exemple
correspond à la culture actuelle en-US.)
using System;
using System.Globalization;

class Program
{
static void Main()
{
Decimal value = 126.03m;
FormattableString amount = $"The amount is {value:C}";
Console.WriteLine(amount.ToString());
Console.WriteLine(amount.ToString(new CultureInfo("fr-FR")));
Console.WriteLine(FormattableString.Invariant(amount));
}
}
// The example displays the following output:
// The amount is $126.03
// The amount is 126,03 €
// The amount is ¤126.03

Imports System.Globalization

Module Program
Sub Main()
Dim value As Decimal = 126.03
Dim amount As FormattableString = $"The amount is {value:C}"
Console.WriteLine(amount.ToString())
Console.WriteLine(amount.ToString(new CultureInfo("fr-FR")))
Console.WriteLine(FormattableString.Invariant(amount))
End Sub
End Module
' The example displays the following output:
' The amount is $126.03
' The amount is 126,03 €
' The amount is ¤126.03

Vous pouvez rendre persistantes des données non-chaînées soit comme données binaires, soit comme données
mises en forme. Si vous choisissez de l'enregistrer en tant que données mises en forme, vous devez appeler une
surcharge de méthode de mise en forme qui inclut un paramètre provider et le passer à la propriété
CultureInfo.InvariantCulture . La culture dite indifférente fournit un format cohérent pour les données mises en
forme qui est indépendant de la culture et de l'ordinateur. En revanche, assurer la persistance de données mises en
forme à l'aide de cultures autres que la culture dite indifférente a plusieurs limites :
Les données seront vraisemblablement inutilisables si elles sont récupérées sur un système ayant une autre
culture, ou si l'utilisateur du système actuel change la culture actuelle et essaie de récupérer les données.
Les propriétés d'une culture sur un ordinateur spécifique peuvent différer des valeurs standard. À tout moment,
un utilisateur peut personnaliser les paramètres d'affichage selon la culture. De ce fait, les données mises en
forme sont stockées sur un système et peuvent ne pas être lisibles lorsque l'utilisateur personnalise les
paramètres de culture. La portabilité des données mises en forme sur différents ordinateurs peut être encore
plus limitée.
Des normes internationales, régionales ou nationales qui régissent la mise en forme des nombres ou des dates
et heures évoluent au fil du temps, et ces modifications sont intégrées dans les mises à jour du système
d'exploitation Windows. Quand les conventions de mise en forme changent, les données qui ont été mises en
forme en utilisant les conventions antérieures peuvent devenir illisibles.
L'exemple suivant illustre la portabilité limitée qui résulte de l'utilisation de la mise en forme qui tient compte de la
culture pour assurer la persistance des données. L'exemple enregistre un tableau de valeurs de date et d'heure
dans un fichier. Celles-ci sont mises en forme en utilisant les conventions culturelles de l'anglais (États-Unis). Une
fois que l'application change la culture du thread actuel pour appliquer le français (Suisse), elle essaie de lire les
valeurs enregistrées en utilisant les conventions de mise en forme de la culture actuelle. La tentative de lecture des
éléments de données par deux fois renvoie une exception FormatException , et le tableau de dates contient
maintenant deux éléments incorrects qui sont identiques à MinValue.

using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;

public class Example


{
private static string filename = @".\dates.dat";

public static void Main()


{
DateTime[] dates = { new DateTime(1758, 5, 6, 21, 26, 0),
new DateTime(1818, 5, 5, 7, 19, 0),
new DateTime(1870, 4, 22, 23, 54, 0),
new DateTime(1890, 9, 8, 6, 47, 0),
new DateTime(1905, 2, 18, 15, 12, 0) };
// Write the data to a file using the current culture.
WriteData(dates);
// Change the current culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH");
// Read the data using the current culture.
DateTime[] newDates = ReadData();
foreach (var newDate in newDates)
Console.WriteLine(newDate.ToString("g"));
}

private static void WriteData(DateTime[] dates)


{
StreamWriter sw = new StreamWriter(filename, false, Encoding.UTF8);
for (int ctr = 0; ctr < dates.Length; ctr++) {
sw.Write("{0}", dates[ctr].ToString("g", CultureInfo.CurrentCulture));
if (ctr < dates.Length - 1) sw.Write("|");
}
sw.Close();
}

private static DateTime[] ReadData()


{
bool exceptionOccurred = false;

// Read file contents as a single string, then split it.


StreamReader sr = new StreamReader(filename, Encoding.UTF8);
string output = sr.ReadToEnd();
sr.Close();

string[] values = output.Split( new char[] { '|' } );


DateTime[] newDates = new DateTime[values.Length];
for (int ctr = 0; ctr < values.Length; ctr++) {
try {
newDates[ctr] = DateTime.Parse(values[ctr], CultureInfo.CurrentCulture);
}
catch (FormatException) {
Console.WriteLine("Failed to parse {0}", values[ctr]);
exceptionOccurred = true;
}
}
if (exceptionOccurred) Console.WriteLine();
return newDates;
}
}
// The example displays the following output:
// Failed to parse 4/22/1870 11:54 PM
// Failed to parse 2/18/1905 3:12 PM
// Failed to parse 2/18/1905 3:12 PM
//
// 05.06.1758 21:26
// 05.05.1818 07:19
// 01.01.0001 00:00
// 09.08.1890 06:47
// 01.01.0001 00:00
// 01.01.0001 00:00
Imports System.Globalization
Imports System.IO
Imports System.Text
Imports System.Threading

Module Example
Private filename As String = ".\dates.dat"

Public Sub Main()


Dim dates() As Date = {#5/6/1758 9:26PM#, #5/5/1818 7:19AM#, _
#4/22/1870 11:54PM#, #9/8/1890 6:47AM#, _
#2/18/1905 3:12PM#}
' Write the data to a file using the current culture.
WriteData(dates)
' Change the current culture.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("fr-CH")
' Read the data using the current culture.
Dim newDates() As Date = ReadData()
For Each newDate In newDates
Console.WriteLine(newDate.ToString("g"))
Next
End Sub

Private Sub WriteData(dates() As Date)


Dim sw As New StreamWriter(filename, False, Encoding.Utf8)
For ctr As Integer = 0 To dates.Length - 1
sw.Write("{0}", dates(ctr).ToString("g", CultureInfo.CurrentCulture))
If ctr < dates.Length - 1 Then sw.Write("|")
Next
sw.Close()
End Sub

Private Function ReadData() As Date()


Dim exceptionOccurred As Boolean = False

' Read file contents as a single string, then split it.


Dim sr As New StreamReader(filename, Encoding.Utf8)
Dim output As String = sr.ReadToEnd()
sr.Close()

Dim values() As String = output.Split({"|"c})


Dim newDates(values.Length - 1) As Date
For ctr As Integer = 0 To values.Length - 1
Try
newDates(ctr) = DateTime.Parse(values(ctr), CultureInfo.CurrentCulture)
Catch e As FormatException
Console.WriteLine("Failed to parse {0}", values(ctr))
exceptionOccurred = True
End Try
Next
If exceptionOccurred Then Console.WriteLine()
Return newDates
End Function
End Module
' The example displays the following output:
' Failed to parse 4/22/1870 11:54 PM
' Failed to parse 2/18/1905 3:12 PM
'
' 05.06.1758 21:26
' 05.05.1818 07:19
' 01.01.0001 00:00
' 09.08.1890 06:47
' 01.01.0001 00:00
' 01.01.0001 00:00
'
Toutefois, si vous remplacez la CultureInfo.CurrentCulture propriété par CultureInfo.InvariantCulture dans les
appels à DateTime.ToString(String, IFormatProvider) et DateTime.Parse(String, IFormatProvider) , les données de
date et d’heure persistantes sont correctement restaurées, comme le montre la sortie suivante :

06.05.1758 21:26
05.05.1818 07:19
22.04.1870 23:54
08.09.1890 06:47
18.02.1905 15:12
Opérations de chaînes de base dans .NET
18/07/2020 • 2 minutes to read • Edit Online

Les applications répondent souvent aux utilisateurs en construisant des messages selon l’entrée d’utilisateur. Par
exemple, il n’est pas rare pour les sites Web de répondre à un utilisateur qui vient d’être connecté avec un
message d’accueil spécialisé qui comprend le nom de l’utilisateur.
Plusieurs méthodes dans les classes System.String et System.Text.StringBuilder vous permettent de construire de
façon dynamique des chaînes personnalisées à afficher dans votre interface utilisateur. Ces méthodes vous aident
également à effectuer plusieurs opérations de chaînes de base telles que la création de chaînes à partir de
tableaux d’octets, la comparaison des valeurs de chaînes et la modification des chaînes existantes.

Sections connexes
Conversion de type dans .NET
Explique comment convertir un type en un autre.
Mise en forme des types
Explique comment mettre en forme des chaînes à l’aide de spécificateurs de format.
Création de nouvelles chaînes dans .NET
18/07/2020 • 7 minutes to read • Edit Online

Le .NET Framework permet de créer des chaînes à l’aide d’une assignation simple, et surcharge un constructeur de
classe pour prendre en charge la création de chaînes à l’aide de plusieurs paramètres différents. Le .NET Framework
fournit également plusieurs méthodes dans la classe System.String qui créent des objets de chaînes en combinant
plusieurs chaînes, tableaux de chaînes ou objets.

Création de chaînes à l’aide d’une affectation


Le moyen le plus simple de créer un objet String consiste à assigner un littéral de chaîne à un objet String.

Création de chaînes à l’aide d’un constructeur de classe


Vous pouvez utiliser des surcharges du constructeur de classe String pour créer des chaînes à partir de tableaux de
caractères. Vous pouvez également créer une chaîne en dupliquant un caractère spécifique un nombre spécifié de
fois.

Méthodes retournant des chaînes


Le tableau suivant présente plusieurs méthodes utiles qui retournent de nouveaux objets String.

N O M DE L A M ÉT H O DE UT IL ISAT IO N

String.Format Génère une chaîne mise en forme à partir d’un ensemble


d’objets en entrée.

String.Concat Génère des chaînes à partir de deux ou de plusieurs chaînes.

String.Join Génère une nouvelle chaîne en combinant un tableau de


chaînes.

String.Insert Génère une nouvelle chaîne en insérant une chaîne dans


l’index spécifié d’une chaîne existante.

String.CopyTo Copie des caractères spécifiés dans une chaîne à une position
spécifiée dans un tableau de caractères.

Format
Vous pouvez utiliser la méthode String.Format pour créer des chaînes mises en forme et concaténer des chaînes
représentant plusieurs objets. Cette méthode convertit automatiquement tout objet passé en une chaîne. Par
exemple, si votre application doit afficher une valeur Int32 et une valeur DateTime à l’utilisateur, vous pouvez
aisément construire une chaîne représentant ces valeurs à l’aide de la méthode Format . Pour plus d’informations
sur les conventions de mise en forme utilisées avec cette méthode, consultez la section relative à la mise en forme
composite.
L’exemple suivant utilise la méthode Format pour créer une chaîne utilisant une variable de type integer.
int numberOfFleas = 12;
string miscInfo = String.Format("Your dog has {0} fleas. " +
"It is time to get a flea collar. " +
"The current universal date is: {1:u}.",
numberOfFleas, DateTime.Now);
Console.WriteLine(miscInfo);
// The example displays the following output:
// Your dog has 12 fleas. It is time to get a flea collar.
// The current universal date is: 2008-03-28 13:31:40Z.

Dim numberOfFleas As Integer = 12


Dim miscInfo As String = String.Format("Your dog has {0} fleas. " & _
"It is time to get a flea collar. " & _
"The current universal date is: {1:u}.", _
numberOfFleas, Date.Now)
Console.WriteLine(miscInfo)
' The example displays the following output:
' Your dog has 12 fleas. It is time to get a flea collar.
' The current universal date is: 2008-03-28 13:31:40Z.

Dans cet exemple, DateTime.Now affiche la date et l’heure actuelles de la manière spécifiée par la culture associée
au thread actuel.
Concat
La méthode String.Concat peut être utilisée pour créer facilement un objet chaîne à partir de deux ou de plusieurs
objets existants. Elle offre un moyen de concaténer des chaînes indépendamment du langage. Cette méthode
accepte toute classe qui dérive de System.Object . L’exemple suivant crée une chaîne à partir de deux objets String
existants et d’un caractère à utiliser comme séparateur.

string helloString1 = "Hello";


string helloString2 = "World!";
Console.WriteLine(String.Concat(helloString1, ' ', helloString2));
// The example displays the following output:
// Hello World!

Dim helloString1 As String = "Hello"


Dim helloString2 As String = "World!"
Console.WriteLine(String.Concat(helloString1, " "c, helloString2))
' The example displays the following output:
' Hello World!

Join
La méthode String.Join crée une chaîne à partir d’un tableau de chaînes et d’une chaîne de séparation. Cette
méthode est utile si vous voulez concaténer plusieurs chaînes et en faire une liste éventuellement séparée par une
virgule.
L’exemple suivant utilise un espace pour lier un tableau de chaînes.

string[] words = {"Hello", "and", "welcome", "to", "my" , "world!"};


Console.WriteLine(String.Join(" ", words));
// The example displays the following output:
// Hello and welcome to my world!
Dim words() As String = {"Hello", "and", "welcome", "to", "my", "world!"}
Console.WriteLine(String.Join(" ", words))
' The example displays the following output:
' Hello and welcome to my world!

Insérer
La méthode String.Inser t crée une chaîne en insérant une chaîne à une position spécifiée dans une autre chaîne.
Cette méthode utilise un index de base zéro. L’exemple suivant insère une chaîne à la cinquième position d’index de
MyString et crée une chaîne avec cette valeur.

string sentence = "Once a time.";


Console.WriteLine(sentence.Insert(4, " upon"));
// The example displays the following output:
// Once upon a time.

Dim sentence As String = "Once a time."


Console.WriteLine(sentence.Insert(4, " upon"))
' The example displays the following output:
' Once upon a time.

CopyTo
La méthode String.CopyTo copie des fragments d’une chaîne dans un tableau de caractères. Vous pouvez spécifier
l’index de début de la chaîne et le nombre de caractères à copier. Cette méthode utilise l’index source, un tableau de
caractères, l’index de destination et le nombre de caractères à copier. Tous les index sont de base zéro.
L’exemple suivant utilise la méthode CopyTo pour copier les caractères du mot « Hello » d’un objet String vers la
première position d’un tableau de caractères.

string greeting = "Hello World!";


char[] charArray = {'W','h','e','r','e'};
Console.WriteLine("The original character array: {0}", new string(charArray));
greeting.CopyTo(0, charArray,0 ,5);
Console.WriteLine("The new character array: {0}", new string(charArray));
// The example displays the following output:
// The original character array: Where
// The new character array: Hello

Dim greeting As String = "Hello World!"


Dim charArray() As Char = {"W"c, "h"c, "e"c, "r"c, "e"c}
Console.WriteLine("The original character array: {0}", New String(charArray))
greeting.CopyTo(0, charArray, 0, 5)
Console.WriteLine("The new character array: {0}", New String(charArray))
' The example displays the following output:
' The original character array: Where
' The new character array: Hello

Voir aussi
Opérations de chaînes de base
Mise en forme composite
Suppression d’espaces et de caractères dans .NET
18/07/2020 • 8 minutes to read • Edit Online

Si vous analysez une phrase en mots individuels, vous risquez d’obtenir des mots incluant des espaces vides
(également appelés espaces blancs) à chaque extrémité du mot. Dans ce cas, vous pouvez utiliser l’une des
méthodes de suppression de la classe System.String pour supprimer n’importe quel nombre d’espaces ou
d’autres caractères à partir d’une position spécifiée dans la chaîne. Le tableau suivant décrit les méthodes de
suppression disponibles.

N O M DE L A M ÉT H O DE UT IL ISAT IO N

String.Trim Supprime les espaces blancs ou caractères spécifiés dans un


tableau de caractères à partir du début et la fin d’une chaîne.

String.TrimEnd Supprime les caractères spécifiés dans un tableau de caractères


à partir de la fin d’une chaîne.

String.TrimStart Supprime les caractères spécifiés dans un tableau de caractères


à partir du début d’une chaîne.

String.Remove Supprime un nombre spécifié de caractères à partir de la


position d’index spécifiée dans une chaîne.

SupprEspace
Vous pouvez facilement supprimer les espaces blancs situés aux deux extrémités d’une chaîne à l’aide de la
méthode String.Trim, comme indiqué dans l’exemple suivant.

String^ MyString = " Big ";


Console::WriteLine("Hello{0}World!", MyString);
String^ TrimString = MyString->Trim();
Console::WriteLine("Hello{0}World!", TrimString);
// The example displays the following output:
// Hello Big World!
// HelloBigWorld!

string MyString = " Big ";


Console.WriteLine("Hello{0}World!", MyString);
string TrimString = MyString.Trim();
Console.WriteLine("Hello{0}World!", TrimString);
// The example displays the following output:
// Hello Big World!
// HelloBigWorld!

Dim MyString As String = " Big "


Console.WriteLine("Hello{0}World!", MyString)
Dim TrimString As String = MyString.Trim()
Console.WriteLine("Hello{0}World!", TrimString)
' The example displays the following output:
' Hello Big World!
' HelloBigWorld!
Vous pouvez également supprimer les caractères que vous spécifiez dans un tableau de caractères à partir du
début et de la fin d’une chaîne. L’exemple suivant permet de supprimer les espaces blancs, les points et les
astérisques.

using System;

public class Example


{
public static void Main()
{
String header = "* A Short String. *";
Console.WriteLine(header);
Console.WriteLine(header.Trim( new Char[] { ' ', '*', '.' } ));
}
}
// The example displays the following output:
// * A Short String. *
// A Short String

Module Example
Public Sub Main()
Dim header As String = "* A Short String. *"
Console.WriteLine(header)
Console.WriteLine(header.Trim({" "c, "*"c, "."c}))
End Sub
End Module
' The example displays the following output:
' * A Short String. *
' A Short String

TrimEnd
La méthode String.TrimEnd supprime les caractères à partir de la fin d’une chaîne, créant ainsi un objet String. Un
tableau de caractères est passé à cette méthode pour spécifier les caractères à supprimer. L’ordre des éléments dans
le tableau de caractères n’affecte pas l’opération de suppression. La suppression s’arrête lorsqu’un caractère non
spécifié dans le tableau est trouvé.
L’exemple suivant supprime les dernières lettres d’une chaîne à l’aide de la méthode TrimEnd . Dans cet exemple, la
position du caractère 'r' et du caractère 'W' est inversée pour illustrer que l’ordre des caractères dans le tableau
n’a pas d’importance. Remarquez que ce code supprime le dernier mot de MyString plus une partie du premier.

String^ MyString = "Hello World!";


array<Char>^ MyChar = {'r','o','W','l','d','!',' '};
String^ NewString = MyString->TrimEnd(MyChar);
Console::WriteLine(NewString);

string MyString = "Hello World!";


char[] MyChar = {'r','o','W','l','d','!',' '};
string NewString = MyString.TrimEnd(MyChar);
Console.WriteLine(NewString);

Dim MyString As String = "Hello World!"


Dim MyChar() As Char = {"r", "o", "W", "l", "d", "!", " "}
Dim NewString As String = MyString.TrimEnd(MyChar)
Console.WriteLine(NewString)
Ce code affiche He dans la console.
L’exemple suivant supprime le dernier mot d’une chaîne à l’aide de la méthode TrimEnd . Dans ce code, une virgule
suit le mot Hello et, étant donné que la virgule n’est pas spécifiée dans le tableau de caractères à supprimer, la
suppression s’arrête au niveau de la virgule.

String^ MyString = "Hello, World!";


array<Char>^ MyChar = {'r','o','W','l','d','!',' '};
String^ NewString = MyString->TrimEnd(MyChar);
Console::WriteLine(NewString);

string MyString = "Hello, World!";


char[] MyChar = {'r','o','W','l','d','!',' '};
string NewString = MyString.TrimEnd(MyChar);
Console.WriteLine(NewString);

Dim MyString As String = "Hello, World!"


Dim MyChar() As Char = {"r", "o", "W", "l", "d", "!", " "}
Dim NewString As String = MyString.TrimEnd(MyChar)
Console.WriteLine(NewString)

Ce code affiche Hello, dans la console.

TrimStart
La méthode String.TrimStar t est similaire à la méthode String.TrimEnd , si ce n’est qu’elle crée une chaîne en
supprimant les caractères à partir du début d’un objet string existant. Un tableau de caractères est passé à la
méthode TrimStar t pour spécifier les caractères à supprimer. Comme avec la méthode TrimEnd , l’ordre des
éléments dans le tableau de caractères n’affecte pas l’opération de suppression. La suppression s’arrête lorsqu’un
caractère non spécifié dans le tableau est trouvé.
L’exemple suivant supprime le premier mot d’une chaîne. Dans cet exemple, la position du caractère 'l' et du
caractère 'H' est inversée pour illustrer que l’ordre des caractères dans le tableau n’a pas d’importance.

String^ MyString = "Hello World!";


array<Char>^ MyChar = {'e', 'H','l','o',' ' };
String^ NewString = MyString->TrimStart(MyChar);
Console::WriteLine(NewString);

string MyString = "Hello World!";


char[] MyChar = {'e', 'H','l','o',' ' };
string NewString = MyString.TrimStart(MyChar);
Console.WriteLine(NewString);

Dim MyString As String = "Hello World!"


Dim MyChar() As Char = {"e", "H", "l", "o", " "}
Dim NewString As String = MyString.TrimStart(MyChar)
Console.WriteLine(NewString)

Ce code affiche World! dans la console.

Supprimer
La méthode String.Remove supprime un nombre spécifié de caractères en commençant à la position spécifiée dans
une chaîne existante. Cette méthode suppose un index de base zéro.
L’exemple suivant supprime dix caractères d’une chaîne en commençant à la position cinq d’un index de base zéro
de la chaîne.

String^ MyString = "Hello Beautiful World!";


Console::WriteLine(MyString->Remove(5,10));
// The example displays the following output:
// Hello World!

string MyString = "Hello Beautiful World!";


Console.WriteLine(MyString.Remove(5,10));
// The example displays the following output:
// Hello World!

Dim MyString As String = "Hello Beautiful World!"


Console.WriteLine(MyString.Remove(5, 10))
' The example displays the following output:
' Hello World!

Replace
Vous pouvez également supprimer un caractère spécifié ou une sous-chaîne spécifiée d’une chaîne en appelant la
méthode String.Replace(String, String) et en spécifiant une chaîne vide (String.Empty) comme valeur de
remplacement. L’exemple suivant supprime toutes les virgules d’une chaîne.

using System;

public class Example


{
public static void Main()
{
String phrase = "a cold, dark night";
Console.WriteLine("Before: {0}", phrase);
phrase = phrase.Replace(",", "");
Console.WriteLine("After: {0}", phrase);
}
}
// The example displays the following output:
// Before: a cold, dark night
// After: a cold dark night

Module Example
Public Sub Main()
Dim phrase As String = "a cold, dark night"
Console.WriteLine("Before: {0}", phrase)
phrase = phrase.Replace(",", "")
Console.WriteLine("After: {0}", phrase)
End Sub
End Module
' The example displays the following output:
' Before: a cold, dark night
' After: a cold dark night

Voir aussi
Opérations de chaînes de base
Remplissage de chaînes dans .NET
18/07/2020 • 3 minutes to read • Edit Online

Utilisez l’une des méthodes String suivantes pour créer une chaîne qui se compose d’une chaîne d’origine remplie à
l’aide de caractères de début ou de fin sur une longueur totale spécifiée. Le caractère de remplissage peut être un
espace ou un caractère spécifié. La chaîne résultante apparaît alignée à droite ou à gauche. Si la longueur de la
chaîne d’origine est déjà égale ou supérieure à la longueur totale souhaitée, les méthodes de remplissage
retournent la chaîne d’origine inchangée. Pour plus d’informations, consultez les sections Retours des deux
surcharges des méthodes String.PadLeft et String.PadRight.

N O M DE L A M ÉT H O DE UT IL ISAT IO N

String.PadLeft Remplit une chaîne à l’aide de caractères de début sur une


longueur totale spécifiée.

String.PadRight Remplit une chaîne à l’aide de caractères de fin sur une


longueur totale spécifiée.

PadLeft
La méthode String.PadLeft crée une chaîne en concaténant suffisamment de caractères de remplissage de début à
une chaîne d’origine pour atteindre une longueur totale spécifiée. La méthode String.PadLeft(Int32) utilise l’espace
blanc comme caractère de remplissage et la méthode String.PadLeft(Int32, Char) vous permet de spécifier votre
propre caractère de remplissage.
L’exemple de code suivant utilise la méthode PadLeft pour créer une chaîne longue de vingt caractères. L’exemple
affiche « --------Hello World! » sur la console.

String^ MyString = "Hello World!";


Console::WriteLine(MyString->PadLeft(20, '-'));

string MyString = "Hello World!";


Console.WriteLine(MyString.PadLeft(20, '-'));

Dim MyString As String = "Hello World!"


Console.WriteLine(MyString.PadLeft(20, "-"c))

PadRight
La méthode String.PadRight crée une chaîne en concaténant suffisamment de caractères de remplissage de fin à
une chaîne d’origine pour atteindre une longueur totale spécifiée. La méthode String.PadRight(Int32) utilise l’espace
blanc comme caractère de remplissage et la méthode String.PadRight(Int32, Char) vous permet de spécifier votre
propre caractère de remplissage.
L’exemple de code suivant utilise la méthode PadRight pour créer une chaîne longue de vingt caractères. L’exemple
affiche « Hello World!-------- » sur la console.
String^ MyString = "Hello World!";
Console::WriteLine(MyString->PadRight(20, '-'));

string MyString = "Hello World!";


Console.WriteLine(MyString.PadRight(20, '-'));

Dim MyString As String = "Hello World!"


Console.WriteLine(MyString.PadRight(20, "-"c))

Voir aussi
Opérations de chaînes de base
Comparer des chaînes dans . NET
18/07/2020 • 13 minutes to read • Edit Online

.NET fournit plusieurs méthodes permettant de comparer les valeurs de chaînes. Le tableau suivant répertorie et
décrit les méthodes de comparaison de valeurs.

N O M DE L A M ÉT H O DE UT IL ISAT IO N

String.Compare Compare les valeurs de deux chaînes. Retourne une valeur


entière.

String.CompareOrdinal Compare deux chaînes sans tenir compte de la culture locale.


Retourne une valeur entière.

String.CompareTo Compare l'objet chaîne actif à une autre chaîne. Retourne une
valeur entière.

String.StartsWith Détermine si une chaîne commence par la chaîne passée.


Retourne une valeur booléenne.

String.EndsWith Détermine si une chaîne se termine par la chaîne passée.


Retourne une valeur booléenne.

String.Equals Détermine si deux chaînes sont identiques. Retourne une


valeur booléenne.

String.IndexOf Retourne la position d'index d'un caractère ou d'une chaîne, en


commençant par le début de la chaîne que vous examinez.
Retourne une valeur entière.

String.LastIndexOf Retourne la position d'index d'un caractère ou d'une chaîne, en


commençant par la fin de la chaîne que vous examinez.
Retourne une valeur entière.

Comparer
La méthode statique String.Compare fournit un moyen de comparer deux chaînes de façon approfondie. Cette
méthode prend en compte la culture. Vous pouvez utiliser cette fonction pour comparer deux chaînes ou les sous-
chaînes de deux chaînes. En outre, des surcharges sont fournies, qui prennent ou non en compte les différences de
casse et de culture. Le tableau suivant montre les trois valeurs entières que cette méthode peut retourner.

VA L EUR DE RETO UR C O N DIT IO N

Entier négatif La première chaîne précède la seconde chaîne dans l'ordre de


tri.

-ou-

La première chaîne est null .


VA L EUR DE RETO UR C O N DIT IO N

0 La première chaîne et la seconde chaîne sont égales.

-ou-

Les deux chaînes sont null .

Entier positif La première chaîne suit la seconde chaîne dans l'ordre de tri.

-ou- -ou-

1 La seconde chaîne est null .

IMPORTANT
La méthode String.Compare est principalement destinée à être utilisée lors du classement ou du tri de chaînes. Vous ne devez
pas utiliser la méthode String.Compare pour tester l'égalité (c'est-à-dire rechercher explicitement une valeur de retour égale à
0 sans savoir si une chaîne est inférieure ou supérieure à l'autre). Pour déterminer si deux chaînes sont égales, utilisez à la
place la méthode String.Equals(String, String, StringComparison) .

L'exemple suivant utilise la méthode String.Compare pour déterminer les valeurs relatives de deux chaînes.

String^ string1 = "Hello World!";


Console::WriteLine(String::Compare(string1, "Hello World?"));

string string1 = "Hello World!";


Console.WriteLine(String.Compare(string1, "Hello World?"));

Dim string1 As String = "Hello World!"


Console.WriteLine(String.Compare(string1, "Hello World?"))

Cet exemple affiche -1 sur la console.


L'exemple précédent tient compte par défaut de la culture. Pour effectuer une comparaison de chaînes
indépendantes de la culture, utilisez une surcharge de la méthode String.Compare qui vous permet de spécifier la
culture à utiliser en fournissant un paramètre culture. Pour un exemple qui montre comment utiliser la méthode
String.Compare pour effectuer une comparaison indépendante de la culture, consultez Réalisation de comparaisons
de chaînes indépendantes de la culture.

CompareOrdinal
La méthode String.CompareOrdinal compare deux objets chaîne sans prendre en compte la culture locale. Les
valeurs de retour de cette méthode sont identiques aux valeurs retournées par la méthode Compare du tableau
précédent.

IMPORTANT
La méthode String.CompareOrdinal est principalement destinée à être utilisée lors du classement ou du tri de chaînes. Vous
ne devez pas utiliser la méthode String.CompareOrdinal pour tester l'égalité (c'est-à-dire rechercher explicitement une valeur
de retour égale à 0 sans savoir si une chaîne est inférieure ou supérieure à l'autre). Pour déterminer si deux chaînes sont
égales, utilisez à la place la méthode String.Equals(String, String, StringComparison) .
L'exemple suivant utilise la méthode CompareOrdinal pour comparer les valeurs de deux chaînes.

String^ string1 = "Hello World!";


Console::WriteLine(String::CompareOrdinal(string1, "hello world!"));

string string1 = "Hello World!";


Console.WriteLine(String.CompareOrdinal(string1, "hello world!"));

Dim string1 As String = "Hello World!"


Console.WriteLine(String.CompareOrdinal(string1, "hello world!"))

Cet exemple affiche -32 sur la console.

CompareTo
La méthode String.CompareTo compare la chaîne encapsulée par l'objet chaîne actif à une autre chaîne ou un autre
objet. Les valeurs de retour de cette méthode sont identiques aux valeurs retournées par la méthode
String.Compare du tableau précédent.

IMPORTANT
La méthode String.CompareTo est principalement destinée à être utilisée lors du classement ou du tri de chaînes. Vous ne
devez pas utiliser la méthode String.CompareTo pour tester l'égalité (c'est-à-dire rechercher explicitement une valeur de retour
égale à 0 sans savoir si une chaîne est inférieure ou supérieure à l'autre). Pour déterminer si deux chaînes sont égales, utilisez
à la place la méthode String.Equals(String, String, StringComparison) .

L’exemple suivant utilise la méthode String.CompareTo pour comparer l’objet string1 à l’objet string2 .

String^ string1 = "Hello World";


String^ string2 = "Hello World!";
int MyInt = string1->CompareTo(string2);
Console::WriteLine( MyInt );

string string1 = "Hello World";


string string2 = "Hello World!";
int MyInt = string1.CompareTo(string2);
Console.WriteLine( MyInt );

Dim string1 As String = "Hello World"


Dim string2 As String = "Hello World!"
Dim MyInt As Integer = string1.CompareTo(string2)
Console.WriteLine(MyInt)

Cet exemple affiche -1 sur la console.


Toutes les surcharges de la méthode String.CompareTo effectuent par défaut des comparaisons dépendantes de la
culture et qui respectent la casse. Aucune surcharge de cette méthode n'est fournie pour vous permettre d'effectuer
une comparaison indépendante de la culture. Pour la clarté du code, nous vous recommandons d’utiliser à la place
la méthode String.Compare , en spécifiant CultureInfo.CurrentCulture pour les opérations dépendantes de la
culture ou CultureInfo.InvariantCulture pour les opérations indépendantes de la culture. Pour un exemple montrant
comment utiliser la méthode String.Compare pour effectuer des comparaisons dépendantes et indépendantes de
la culture, consultez Réalisation de comparaisons de chaînes indépendantes de la culture.

Égal à
La méthode String.Equals peut facilement déterminer si deux chaînes sont identiques. Cette méthode respectant
la casse retourne une valeur booléenne true ou false . Elle peut être utilisée à partir d'une classe existante, comme
illustré dans l'exemple suivant. L'exemple suivant utilise la méthode Equals pour déterminer si un objet chaîne
contient la phrase "Hello World".

String^ string1 = "Hello World";


Console::WriteLine(string1->Equals("Hello World"));

string string1 = "Hello World";


Console.WriteLine(string1.Equals("Hello World"));

Dim string1 As String = "Hello World"


Console.WriteLine(string1.Equals("Hello World"))

Cet exemple affiche True sur la console.


Cette méthode peut également être utilisée comme une méthode statique. L'exemple suivant compare deux objets
chaîne à l'aide d'une méthode statique.

String^ string1 = "Hello World";


String^ string2 = "Hello World";
Console::WriteLine(String::Equals(string1, string2));

string string1 = "Hello World";


string string2 = "Hello World";
Console.WriteLine(String.Equals(string1, string2));

Dim string1 As String = "Hello World"


Dim string2 As String = "Hello World"
Console.WriteLine(String.Equals(string1, string2))

Cet exemple affiche True sur la console.

StartsWith et EndsWith
Vous pouvez utiliser la méthode String.Star tsWith pour déterminer si un objet chaîne commence par les mêmes
caractères que ceux qui constituent une autre chaîne. Cette méthode qui respecte la casse retourne true si l'objet
chaîne en cours commence par la chaîne passée, et false ce n'est pas le cas. L'exemple suivant utilise cette méthode
pour déterminer si un objet chaîne commence par "Hello".

String^ string1 = "Hello World";


Console::WriteLine(string1->StartsWith("Hello"));

string string1 = "Hello World";


Console.WriteLine(string1.StartsWith("Hello"));
Dim string1 As String = "Hello World!"
Console.WriteLine(string1.StartsWith("Hello"))

Cet exemple affiche True sur la console.


La méthode String.EndsWith compare une chaîne passée aux caractères qui se trouvent à la fin de l'objet chaîne
actif. Elle retourne également une valeur booléenne. L'exemple suivant teste la fin d'une chaîne à l'aide de la
méthode EndsWith .

String^ string1 = "Hello World";


Console::WriteLine(string1->EndsWith("Hello"));

string string1 = "Hello World";


Console.WriteLine(string1.EndsWith("Hello"));

Dim string1 As String = "Hello World!"


Console.WriteLine(string1.EndsWith("Hello"))

Cet exemple affiche False sur la console.

IndexOf et LastIndexOf
Vous pouvez utiliser la méthode String.IndexOf pour déterminer la position de la première occurrence d'un
caractère particulier dans une chaîne. Cette méthode qui respecte la casse commence à compter à partir du début
d'une chaîne et retourne la position d'un caractère passé en utilisant un index de base zéro. Si le caractère est
introuvable, la valeur -1 est retournée.
L'exemple suivant utilise la méthode IndexOf pour rechercher la première occurrence du caractère ' l ' dans une
chaîne.

String^ string1 = "Hello World";


Console::WriteLine(string1->IndexOf('l'));

string string1 = "Hello World";


Console.WriteLine(string1.IndexOf('l'));

Dim string1 As String = "Hello World!"


Console.WriteLine(string1.IndexOf("l"))

Cet exemple affiche 2 sur la console.


La méthode String.LastIndexOf est similaire à la méthode String.IndexOf , sauf qu'elle retourne la position de la
dernière occurrence d'un caractère particulier dans une chaîne. Elle respecte la casse et utilise un index de base
zéro.
L'exemple suivant utilise la méthode LastIndexOf pour rechercher la dernière occurrence du caractère ' l ' dans
une chaîne.

String^ string1 = "Hello World";


Console::WriteLine(string1->LastIndexOf('l'));
string string1 = "Hello World";
Console.WriteLine(string1.LastIndexOf('l'));

Dim string1 As String = "Hello World!"


Console.WriteLine(string1.LastIndexOf("l"))

Cet exemple affiche 9 sur la console.


Les deux méthodes sont utiles quand elles sont utilisées conjointement avec la méthode String.Remove . Vous
pouvez utiliser la méthode IndexOf ou la méthode LastIndexOf pour récupérer la position d'un caractère, puis
fournir cette position à la méthode Remove pour supprimer un caractère ou un mot commençant par ce caractère.

Voir aussi
Opérations de chaînes de base
Exécution d’opérations de chaînes indépendantes de la culture
Sorting Weight Tables (pour .NET sur Windows)
Default Unicode Collation Element Table (pour .NET Core sur Linux et macOS)
Modifier la casse dans .NET
18/07/2020 • 8 minutes to read • Edit Online

Si vous écrivez une application qui accepte l’entrée d’un utilisateur, vous ne pouvez jamais être sûr de la casse
(supérieure ou inférieure) qu’elle utilisera pour entrer les données. Souvent, vous voulez que les chaînes aient une
casse cohérente, en particulier si vous les affichez dans l'interface utilisateur. Le tableau suivant décrit trois
méthodes de changement de la casse : Les deux premières méthodes fournissent une surcharge qui accepte une
culture.

N O M DE L A M ÉT H O DE UT IL ISAT IO N

String.ToUpper Convertit tous les caractères d'une chaîne en majuscules.

String.ToLower Convertit tous les caractères d'une chaîne en minuscules.

TextInfo.ToTitleCase Convertit une chaîne en une casse avec la première lettre des
mots en majuscule.

WARNING
Notez que les méthodes String.ToUpper et String.ToLower ne doivent pas être utilisées pour convertir des chaînes pour les
comparer ou pour tester leur égalité. Pour plus d'informations, consultez la section Comparaison de chaînes de casse mixte.

Comparer des chaînes de casse mixte


Pour comparer des chaînes de casse mixte et déterminer leur classement, appelez une des surcharges de la
méthode String.CompareTo avec un paramètre comparisonType et spécifiez une valeur
StringComparison.CurrentCultureIgnoreCase, StringComparison.InvariantCultureIgnoreCase ou
StringComparison.OrdinalIgnoreCase pour l’argument comparisonType . Pour une comparaison en utilisant une
culture spécifique autre que la culture actuelle, appelez une surcharge de la méthode String.CompareTo avec les
paramètres culture et options , et spécifiez une valeur CompareOptions.IgnoreCase pour l'argument options .
Pour comparer des chaînes de casse mixte et déterminer si elles sont égales, appelez une des surcharges de la
méthode String.Equals avec un paramètre comparisonType et spécifiez une valeur
StringComparison.CurrentCultureIgnoreCase, StringComparison.InvariantCultureIgnoreCase ou
StringComparison.OrdinalIgnoreCase pour l’argument comparisonType .
Pour plus d’informations, consultez Bonnes pratiques l’utilisation de chaînes.

ToUpper
La méthode String.ToUpper change tous les caractères d'une chaîne en majuscules. L’exemple suivant convertit la
chaîne « Hello World! » d’une casse mixte en majuscules.

string properString = "Hello World!";


Console.WriteLine(properString.ToUpper());
// This example displays the following output:
// HELLO WORLD!
Dim MyString As String = "Hello World!"
Console.WriteLine(MyString.ToUpper())
' This example displays the following output:
' HELLO WORLD!

L'exemple précédent est par défaut dépendant de la culture. Il applique les conventions de casse de la culture
actuelle. Pour effectuer un changement de casse indépendant de la culture ou appliquer les conventions de casse
d'une culture particulière, utilisez la surcharge de la méthode String.ToUpper(CultureInfo) et spécifiez une valeur
CultureInfo.InvariantCulture ou un objet System.Globalization.CultureInfo qui représente la culture spécifiée pour le
paramètre culture. Pour un exemple montrant comment utiliser la méthode ToUpper pour effectuer un changement
de casse indépendant de la culture, consultez Réalisation de changements de casse indépendants de la culture.

ToLower
La méthode String.ToLower est similaire à la méthode précédente, mais elle convertit tous les caractères d'une
chaîne en minuscules. L’exemple suivant convertit la chaîne « Hello World! » en minuscules.

string properString = "Hello World!";


Console.WriteLine(properString.ToLower());
// This example displays the following output:
// hello world!

Dim MyString As String = "Hello World!"


Console.WriteLine(MyString.ToLower())
' This example displays the following output:
' hello world!

L'exemple précédent est par défaut dépendant de la culture. Il applique les conventions de casse de la culture
actuelle. Pour effectuer un changement de casse indépendant de la culture ou appliquer les conventions de casse
d'une culture particulière, utilisez la surcharge de la méthode String.ToLower(CultureInfo) et spécifiez une valeur
CultureInfo.InvariantCulture ou un objet System.Globalization.CultureInfo qui représente la culture spécifiée pour le
paramètre culture. Pour un exemple montrant comment utiliser la méthode ToLower(CultureInfo) pour effectuer un
changement de casse indépendant de la culture, consultez Réalisation de changements de casse indépendants de la
culture.

ToTitleCase
La méthode TextInfo.ToTitleCase convertit le premier caractère de chaque mot en majuscules et les autres caractères
en minuscules. Cependant, les mots qui sont entièrement en majuscules sont supposés être des acronymes et ne
sont pas convertis.
La méthode TextInfo.ToTitleCase est dépendante de la culture ; autrement dit, elle utilise les conventions de casse
d'une culture particulière. Pour pouvoir appeler la méthode, vous récupérez d'abord l'objet TextInfo qui représente
les conventions de casse de la culture spécifique auprès de la propriété CultureInfo.TextInfo d'une culture
particulière.
L'exemple suivant passe chaque chaîne d'un tableau à la méthode TextInfo.ToTitleCase. Les chaînes incluent des
chaînes de titre appropriées, ainsi que des acronymes. Les chaînes sont converties en casse avec la première lettre
des mots en majuscules en utilisant les conventions de casse de la culture Anglais (États-Unis).
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
string[] values = { "a tale of two cities", "gROWL to the rescue",
"inside the US government", "sports and MLB baseball",
"The Return of Sherlock Holmes", "UNICEF and children"};

TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
foreach (var value in values)
Console.WriteLine("{0} --> {1}", value, ti.ToTitleCase(value));
}
}
// The example displays the following output:
// a tale of two cities --> A Tale Of Two Cities
// gROWL to the rescue --> Growl To The Rescue
// inside the US government --> Inside The US Government
// sports and MLB baseball --> Sports And MLB Baseball
// The Return of Sherlock Holmes --> The Return Of Sherlock Holmes
// UNICEF and children --> UNICEF And Children

Imports System.Globalization

Module Example
Public Sub Main()
Dim values() As String = {"a tale of two cities", "gROWL to the rescue",
"inside the US government", "sports and MLB baseball",
"The Return of Sherlock Holmes", "UNICEF and children"}

Dim ti As TextInfo = CultureInfo.CurrentCulture.TextInfo


For Each value In values
Console.WriteLine("{0} --> {1}", value, ti.ToTitleCase(value))
Next
End Sub
End Module
' The example displays the following output:
' a tale of two cities --> A Tale Of Two Cities
' gROWL to the rescue --> Growl To The Rescue
' inside the US government --> Inside The US Government
' sports and MLB baseball --> Sports And MLB Baseball
' The Return of Sherlock Holmes --> The Return Of Sherlock Holmes
' UNICEF and children --> UNICEF And Children

Notez que bien qu'elle soit dépendante de la culture, la méthode TextInfo.ToTitleCase ne fournit pas de règles de
casse correctes d'un point de vue linguistique. Par exemple, dans l'exemple précédent, la méthode convertit "a tale
of two cities" en "A Tale Of Two Cities". Cependant, la casse linguistiquement correcte de ce titre pour la culture en-
US est "A Tale of Two Cities".

Voir aussi
Opérations de chaînes de base
Exécution d’opérations de chaînes indépendantes de la culture
Utilisation de la classe StringBuilder dans .NET
18/07/2020 • 12 minutes to read • Edit Online

L’objet String est immuable. Chaque fois que vous utilisez l’une des méthodes de la classe System.String, vous créez
un nouvel objet string en mémoire, ce qui nécessite une nouvelle allocation d’espace pour ce nouvel objet. Si vous
devez effectuer des modifications répétées sur une chaîne, la surcharge associée à la création d’un objet String peut
être coûteuse. Vous pouvez utiliser la classe System.Text.StringBuilder quand vous voulez modifier une chaîne sans
créer d’objet. Par exemple, la classe StringBuilder permet d’améliorer les performances quand il s’agit de concaténer
un grand nombre de chaînes dans une boucle.

Importation de l’espace de noms System.Text


La classe StringBuilder se trouve dans l’espace de noms System.Text. Pour éviter d’avoir à fournir un nom de type
complet dans votre code, vous pouvez importer l’espace de noms System.Text :

using namespace System;


using namespace System::Text;

using System;
using System.Text;

Imports System.Text

Instanciation d’un objet StringBuilder


Vous pouvez créer une nouvelle instance de la classe StringBuilder en initialisant votre variable avec l’une des
méthodes de constructeur surchargées, comme l’illustre l’exemple suivant.

StringBuilder^ myStringBuilder = gcnew StringBuilder("Hello World!");

StringBuilder myStringBuilder = new StringBuilder("Hello World!");

Dim myStringBuilder As New StringBuilder("Hello World!")

Définition de la capacité et de la longueur


Bien que StringBuilder soit un objet dynamique qui permet de développer le nombre de caractères contenus dans
la chaîne qu’il encapsule, vous pouvez spécifier une valeur pour le nombre maximal de caractères qu’elle peut
contenir. Cette valeur est appelée « capacité » de l’objet et ne doit pas être confondue avec la longueur de la chaîne
que l’instance actuelle de StringBuilder contient. Par exemple, vous pouvez créer une nouvelle instance de la classe
StringBuilder avec la chaîne « Hello », dont la longueur est de 5, et préciser que l’objet a une capacité maximale de
25. Quand vous modifiez l’instance de StringBuilder, elle ne se réalloue pas une taille tant que la capacité maximale
n’est pas atteinte. Quand cela se produit, le nouvel espace est alloué automatiquement et la capacité est doublée.
Vous pouvez spécifier la capacité de la classe StringBuilder en utilisant l’un des constructeurs surchargés. L’exemple
suivant spécifie que l’objet myStringBuilder peut être développé en 25 espaces, au maximum.

StringBuilder^ myStringBuilder = gcnew StringBuilder("Hello World!", 25);

StringBuilder myStringBuilder = new StringBuilder("Hello World!", 25);

Dim myStringBuilder As New StringBuilder("Hello World!", 25)

De plus, vous pouvez utiliser la propriété Capacity en lecture/écriture pour définir la longueur maximale de votre
objet. L’exemple suivant utilise la propriété Capacity pour définir la longueur maximale de l’objet.

myStringBuilder->Capacity = 25;

myStringBuilder.Capacity = 25;

myStringBuilder.Capacity = 25

La méthode EnsureCapacity peut être utilisée pour vérifier la capacité de l’instance actuelle de StringBuilder . Si la
capacité est supérieure à la valeur passée, aucune modification n’est apportée ; en revanche, si la capacité est
inférieure à la valeur passée, la capacité actuelle est modifiée pour correspondre à la valeur passée.
Vous pouvez aussi afficher ou définir la propriété Length. Si vous attribuez à la propriété Length une valeur
supérieure à celle de la propriété Capacity , la propriété Capacity prend automatiquement la valeur de la propriété
Length . Si vous attribuez à la propriété Length une valeur inférieure à la longueur de la chaîne dans l’instance
actuelle de StringBuilder , la chaîne se raccourcit.

Modification de la chaîne StringBuilder


Le tableau suivant répertorie les méthodes que vous pouvez utiliser pour modifier le contenu d’une instance de
StringBuilder .

N O M DE L A M ÉT H O DE UT IL ISAT IO N

StringBuilder.Append Ajoute des informations à la fin de l’instance actuelle de


StringBuilder .

StringBuilder.AppendFormat Remplace un spécificateur de format passé dans une chaîne


avec du texte mis en forme.

StringBuilder.Insert Insère une chaîne ou un objet dans l’index spécifié de


l’instance actuelle de StringBuilder .

StringBuilder.Remove Supprime un nombre de caractères spécifié de l’instance


actuelle de StringBuilder .

StringBuilder.Replace Remplace toutes les occurrences d’un caractère ou d’une


chaîne spécifiée dans l’objet StringBuilder actuel par une
autre chaîne ou un autre caractère spécifié.

Ajouter
La méthode Append permet d’ajouter du texte ou une représentation sous forme de chaîne d’un objet à la fin
d’une chaîne représentée par l’instance actuelle de StringBuilder . L’exemple suivant initialise une instance de
StringBuilder à « Hello World » avant d’ajouter du texte à la fin de l’objet. L’espace est alloué automatiquement en
fonction des besoins.

StringBuilder^ myStringBuilder = gcnew StringBuilder("Hello World!");


myStringBuilder->Append(" What a beautiful day.");
Console::WriteLine(myStringBuilder);
// The example displays the following output:
// Hello World! What a beautiful day.

StringBuilder myStringBuilder = new StringBuilder("Hello World!");


myStringBuilder.Append(" What a beautiful day.");
Console.WriteLine(myStringBuilder);
// The example displays the following output:
// Hello World! What a beautiful day.

Dim myStringBuilder As New StringBuilder("Hello World!")


myStringBuilder.Append(" What a beautiful day.")
Console.WriteLine(myStringBuilder)
' The example displays the following output:
' Hello World! What a beautiful day.

AppendFormat
La méthode StringBuilder.AppendFormat ajoute du texte à la fin de l’objet StringBuilder. Elle prend en charge la
fonctionnalité de mise en forme composite (pour plus d’informations, consultez Mise en forme composite) en
appelant l’implémentation IFormattable des objets à mettre en forme. Par conséquent, elle accepte les chaînes de
format standard pour les valeurs numériques, de date et d’heure et d’énumération, les chaînes de format
personnalisé pour les valeurs numériques et de date et d’heure, ainsi que les chaînes de format définies pour des
types personnalisés. (Pour plus d’informations sur la mise en forme, consultez mise en forme des types.) Vous
pouvez utiliser cette méthode pour personnaliser le format des variables et ajouter ces valeurs à un StringBuilder .
L’exemple suivant utilise la méthode AppendFormat pour placer une valeur entière mise en forme en tant que
valeur monétaire à la fin d’un objet StringBuilder.

int MyInt = 25;


StringBuilder^ myStringBuilder = gcnew StringBuilder("Your total is ");
myStringBuilder->AppendFormat("{0:C} ", MyInt);
Console::WriteLine(myStringBuilder);
// The example displays the following output:
// Your total is $25.00

int MyInt = 25;


StringBuilder myStringBuilder = new StringBuilder("Your total is ");
myStringBuilder.AppendFormat("{0:C} ", MyInt);
Console.WriteLine(myStringBuilder);
// The example displays the following output:
// Your total is $25.00
Dim MyInt As Integer = 25
Dim myStringBuilder As New StringBuilder("Your total is ")
myStringBuilder.AppendFormat("{0:C} ", MyInt)
Console.WriteLine(myStringBuilder)
' The example displays the following output:
' Your total is $25.00

Insérer
La méthode Insert ajoute une chaîne ou un objet à une position spécifiée dans l’objet StringBuilder actuel. L’exemple
suivant utilise cette méthode pour insérer un mot à la sixième position d’un objet StringBuilder.

StringBuilder^ myStringBuilder = gcnew StringBuilder("Hello World!");


myStringBuilder->Insert(6,"Beautiful ");
Console::WriteLine(myStringBuilder);
// The example displays the following output:
// Hello Beautiful World!

StringBuilder myStringBuilder = new StringBuilder("Hello World!");


myStringBuilder.Insert(6,"Beautiful ");
Console.WriteLine(myStringBuilder);
// The example displays the following output:
// Hello Beautiful World!

Dim myStringBuilder As New StringBuilder("Hello World!")


myStringBuilder.Insert(6, "Beautiful ")
Console.WriteLine(myStringBuilder)
' The example displays the following output:
' Hello Beautiful World!

Supprimer
Vous pouvez utiliser la méthode Remove pour supprimer un nombre spécifié de caractères dans l’objet
StringBuilder actuel, en partant d’un index de base zéro spécifié. L’exemple suivant utilise la méthode Remove pour
raccourcir un objet StringBuilder.

StringBuilder^ myStringBuilder = gcnew StringBuilder("Hello World!");


myStringBuilder->Remove(5,7);
Console::WriteLine(myStringBuilder);
// The example displays the following output:
// Hello

StringBuilder myStringBuilder = new StringBuilder("Hello World!");


myStringBuilder.Remove(5,7);
Console.WriteLine(myStringBuilder);
// The example displays the following output:
// Hello

Dim myStringBuilder As New StringBuilder("Hello World!")


myStringBuilder.Remove(5, 7)
Console.WriteLine(myStringBuilder)
' The example displays the following output:
' Hello

Replace
La méthode Replace permet de remplacer des caractères dans l’objet StringBuilder par un autre caractère spécifié.
L’exemple suivant utilise la méthode Replace pour rechercher toutes les instances du caractère point d’exclamation
(!) dans un objet StringBuilder et les remplace par le caractère point d’interrogation (?).

StringBuilder^ myStringBuilder = gcnew StringBuilder("Hello World!");


myStringBuilder->Replace('!', '?');
Console::WriteLine(myStringBuilder);
// The example displays the following output:
// Hello World?

StringBuilder myStringBuilder = new StringBuilder("Hello World!");


myStringBuilder.Replace('!', '?');
Console.WriteLine(myStringBuilder);
// The example displays the following output:
// Hello World?

Dim myStringBuilder As New StringBuilder("Hello World!")


myStringBuilder.Replace("!"c, "?"c)
Console.WriteLine(myStringBuilder)
' The example displays the following output:
' Hello World?

Conversion d’un objet StringBuilder en chaîne


Vous devez convertir l’objet StringBuilder en objet String pour pouvoir passer la chaîne représentée par l’objet
StringBuilder à une méthode qui a un paramètre String ou pour l’afficher dans l’interface utilisateur. Pour effectuer
cette conversion, vous devez appeler la méthode StringBuilder.ToString. L’exemple suivant appelle un certain
nombre de méthodes StringBuilder, puis appelle la méthode StringBuilder.ToString() pour afficher la chaîne.

using System;
using System.Text;

public class Example


{
public static void Main()
{
StringBuilder sb = new StringBuilder();
bool flag = true;
string[] spellings = { "recieve", "receeve", "receive" };
sb.AppendFormat("Which of the following spellings is {0}:", flag);
sb.AppendLine();
for (int ctr = 0; ctr <= spellings.GetUpperBound(0); ctr++) {
sb.AppendFormat(" {0}. {1}", ctr, spellings[ctr]);
sb.AppendLine();
}
sb.AppendLine();
Console.WriteLine(sb.ToString());
}
}
// The example displays the following output:
// Which of the following spellings is True:
// 0. recieve
// 1. receeve
// 2. receive
Imports System.Text

Module Example
Public Sub Main()
Dim sb As New StringBuilder()
Dim flag As Boolean = True
Dim spellings() As String = {"recieve", "receeve", "receive"}
sb.AppendFormat("Which of the following spellings is {0}:", flag)
sb.AppendLine()
For ctr As Integer = 0 To spellings.GetUpperBound(0)
sb.AppendFormat(" {0}. {1}", ctr, spellings(ctr))
sb.AppendLine()
Next
sb.AppendLine()
Console.WriteLine(sb.ToString())
End Sub
End Module
' The example displays the following output:
' Which of the following spellings is True:
' 0. recieve
' 1. receeve
' 2. receive

Voir aussi
System.Text.StringBuilder
Opérations de chaînes de base
Mise en forme des types
Guide pratique pour effectuer des manipulations de
chaînes de base dans .NET
18/07/2020 • 5 minutes to read • Edit Online

L’exemple suivant utilise certaines des méthodes décrites dans les rubriques Opérations de chaînes de base pour
construire une classe qui effectue des manipulations de chaînes éventuellement comme dans une application réelle.
La classe MailToData stocke le nom et l’adresse d’une personne dans des propriétés séparées et fournit un moyen
de combiner les champs City , State et Zip dans une seule chaîne à montrer à l’utilisateur. De plus, la classe
permet à l’utilisateur d’entrer la ville, l’état et le code postal dans une chaîne unique ; l’application analyse
automatiquement la chaîne unique et entre les informations appropriées dans la propriété correspondante.
Pour plus de simplicité, cet exemple utilise une application console avec une interface de ligne de commande.

Exemple
using System;

class MainClass
{
static void Main()
{
MailToData MyData = new MailToData();

Console.Write("Enter Your Name: ");


MyData.Name = Console.ReadLine();
Console.Write("Enter Your Address: ");
MyData.Address = Console.ReadLine();
Console.Write("Enter Your City, State, and ZIP Code separated by spaces: ");
MyData.CityStateZip = Console.ReadLine();
Console.WriteLine();

if (MyData.Validated) {
Console.WriteLine("Name: {0}", MyData.Name);
Console.WriteLine("Address: {0}", MyData.Address);
Console.WriteLine("City: {0}", MyData.City);
Console.WriteLine("State: {0}", MyData.State);
Console.WriteLine("Zip: {0}", MyData.Zip);

Console.WriteLine("\nThe following address will be used:");


Console.WriteLine(MyData.Address);
Console.WriteLine(MyData.CityStateZip);
}
}
}

public class MailToData


{
string name = "";
string address = "";
string citystatezip = "";
string city = "";
string state = "";
string zip = "";
bool parseSucceeded = false;

public string Name


{
get{return name;}
set{name = value;}
}

public string Address


{
get{return address;}
set{address = value;}
}

public string CityStateZip


{
get {
return String.Format("{0}, {1} {2}", city, state, zip);
}
set {
citystatezip = value.Trim();
ParseCityStateZip();
}
}

public string City


{
get{return city;}
set{city = value;}
}

public string State


{
get{return state;}
set{state = value;}
}

public string Zip


{
get{return zip;}
set{zip = value;}
}

public bool Validated


{
get { return parseSucceeded; }
}

private void ParseCityStateZip()


{
string msg = "";
const string msgEnd = "\nYou must enter spaces between city, state, and zip code.\n";

// Throw a FormatException if the user did not enter the necessary spaces
// between elements.
try
{
// City may consist of multiple words, so we'll have to parse the
// string from right to left starting with the zip code.
int zipIndex = citystatezip.LastIndexOf(" ");
if (zipIndex == -1) {
msg = "\nCannot identify a zip code." + msgEnd;
throw new FormatException(msg);
}
zip = citystatezip.Substring(zipIndex + 1);

int stateIndex = citystatezip.LastIndexOf(" ", zipIndex - 1);


if (stateIndex == -1) {
msg = "\nCannot identify a state." + msgEnd;
throw new FormatException(msg);
}
state = citystatezip.Substring(stateIndex + 1, zipIndex - stateIndex - 1);
state = state.ToUpper();
city = citystatezip.Substring(0, stateIndex);
if (city.Length == 0) {
msg = "\nCannot identify a city." + msgEnd;
throw new FormatException(msg);
}
parseSucceeded = true;
}
catch (FormatException ex)
{
Console.WriteLine(ex.Message);
}
}

private string ReturnCityStateZip()


{
// Make state uppercase.
state = state.ToUpper();

// Put the value of city, state, and zip together in the proper manner.
string MyCityStateZip = String.Concat(city, ", ", state, " ", zip);

return MyCityStateZip;
}
}

Class MainClass
Public Shared Sub Main()
Dim MyData As New MailToData()

Console.Write("Enter Your Name: ")


MyData.Name = Console.ReadLine()
Console.Write("Enter Your Address: ")
MyData.Address = Console.ReadLine()
Console.Write("Enter Your City, State, and ZIP Code separated by spaces: ")
MyData.CityStateZip = Console.ReadLine()
Console.WriteLine()

If MyData.Validated Then
Console.WriteLine("Name: {0}", MyData.Name)
Console.WriteLine("Address: {0}", MyData.Address)
Console.WriteLine("City: {0}", MyData.City)
Console.WriteLine("State: {0}", MyData.State)
Console.WriteLine("ZIP Code: {0}", MyData.Zip)

Console.WriteLine("The following address will be used:")


Console.WriteLine(MyData.Address)
Console.WriteLine(MyData.CityStateZip)
End If
End Sub
End Class

Public Class MailToData


Private strName As String = ""
Private strAddress As String = ""
Private strCityStateZip As String = ""
Private strCity As String = ""
Private strState As String = ""
Private strZip As String = ""
Private parseSucceeded As Boolean = False

Public Property Name() As String


Get
Return strName
End Get
Set
strName = value
End Set
End Property

Public Property Address() As String


Get
Return strAddress
End Get
Set
strAddress = value
End Set
End Property

Public Property CityStateZip() As String


Get
Return String.Format("{0}, {1} {2}", strCity, strState, strZip)
End Get
Set
strCityStateZip = value.Trim()
ParseCityStateZip()
End Set
End Property

Public Property City() As String


Get
Return strCity
End Get
Set
strCity = value
End Set
End Property

Public Property State() As String


Get
Return strState
End Get
Set
strState = value
End Set
End Property

Public Property Zip() As String


Get
Return strZip
End Get
Set
strZip = value
End Set
End Property

Public ReadOnly Property Validated As Boolean


Get
Return parseSucceeded
End Get
End Property

Private Sub ParseCityStateZip()


Dim msg As String = Nothing
Const msgEnd As String = vbCrLf +
"You must enter spaces between city, state, and zip code." +
vbCrLf

' Throw a FormatException if the user did not enter the necessary spaces
' between elements.
Try
' City may consist of multiple words, so we'll have to parse the
' string from right to left starting with the zip code.
Dim zipIndex As Integer = strCityStateZip.LastIndexOf(" ")
If zipIndex = -1 Then
msg = vbCrLf + "Cannot identify a zip code." + msgEnd
Throw New FormatException(msg)
End If
strZip = strCityStateZip.Substring(zipIndex + 1)

Dim stateIndex As Integer = strCityStateZip.LastIndexOf(" ", zipIndex - 1)


If stateIndex = -1 Then
msg = vbCrLf + "Cannot identify a state." + msgEnd
Throw New FormatException(msg)
End If
strState = strCityStateZip.Substring(stateIndex + 1, zipIndex - stateIndex - 1)
strState = strState.ToUpper()

strCity = strCityStateZip.Substring(0, stateIndex)


If strCity.Length = 0 Then
msg = vbCrLf + "Cannot identify a city." + msgEnd
Throw New FormatException(msg)
End If
parseSucceeded = True
Catch ex As FormatException
Console.WriteLine(ex.Message)
End Try
End Sub
End Class

Lorsque le code précédent est exécuté, l’utilisateur est invité à entrer son nom et son adresse. L’application place les
informations dans les propriétés appropriées et les montre à l’utilisateur, en créant une chaîne unique qui affiche la
ville, l’état et le code postal.

Voir aussi
Opérations de chaînes de base
Expressions régulières .NET
18/07/2020 • 17 minutes to read • Edit Online

Les expressions régulières permettent de traiter un texte de façon puissante, souple et efficace. La notation
étendue de critères spéciaux d’expressions régulières vous permet d’analyser rapidement de grandes quantités de
texte pour :
Rechercher des modèles de caractères spécifiques.
Validez le texte pour vous assurer qu’il correspond à un modèle prédéfini (par exemple, une adresse de
messagerie).
Extraire, modifier, remplacer ou supprimer des sous-chaînes de texte.
Ajoutez des chaînes extraites à une collection afin de générer un rapport.
Pour de nombreuses applications qui traitent des chaînes ou qui analysent de grands blocs de texte, les
expressions régulières constituent un outil indispensable.

Fonctionnement des expressions régulières


La pièce maîtresse du traitement d'un texte avec des expressions régulières est le moteur d'expression régulière,
représenté par l'objet System.Text.RegularExpressions.Regex dans .NET. Au minimum, vous devez fournir au
moteur d'expression régulière les deux éléments d'information suivants pour traiter un texte à l'aide
d'expressions régulières :
Le modèle d’expression régulière à identifier dans le texte.
Dans .NET, les modèles d’expression régulière sont définis par un langage ou une syntaxe spécifique, qui
est compatible avec les expressions régulières Perl 5 et ajoute certaines fonctionnalités comme la mise en
correspondance de la droite vers la gauche. Pour plus d’informations, consultez Langage des expressions
régulières - Aide-mémoire.
Le texte à analyser pour le modèle d’expression régulière.
Les méthodes de la classe Regex vous permettent d'effectuer les opérations suivantes :
Déterminer si le modèle d’expression régulière est présent dans le texte d’entrée en appelant la méthode
Regex.IsMatch. Pour obtenir un exemple d'utilisation de la méthode IsMatch pour valider un texte,
consultez Comment : vérifier que des chaînes sont dans un format d'adresse de messagerie valide.
Récupérer une occurrence, ou toutes les occurrences, de texte qui correspondent au modèle d’expression
régulière en appelant la méthode Regex.Match ou Regex.Matches. La première méthode retourne un objet
System.Text.RegularExpressions.Match qui fournit des informations sur le texte correspondant. La seconde
retourne un objet MatchCollection qui contient un objet System.Text.RegularExpressions.Match pour
chaque correspondance trouvée dans le texte analysé.
Remplacer le texte qui correspond au modèle d’expression régulière en appelant la méthode Regex.Replace.
Pour obtenir des exemples d'utilisation de la méthode Replace pour modifier des formats de date et
supprimer des caractères non valides d'une chaîne, consultez Comment : supprimer des caractères non
valides d'une chaîne et Exemple : modification des formats de date.
Pour obtenir une vue d’ensemble du modèle objet d’expression régulière, consultez Modèle objet d’expression
régulière.
Pour plus d'informations sur le langage d'expression régulière, consultez Langage des expressions régulières -
Aide-mémoire ou téléchargez et imprimez l'une des brochures suivantes :
Aide-mémoire au format Word (.docx)
Aide-mémoire au format PDF (.pdf)

Exemples d’expressions régulières


La classe String comprend de nombreuses méthodes de recherche et de remplacement de chaîne qui vous
permettent de trouver des chaînes littérales dans une chaîne plus grande. Les expressions régulières sont
particulièrement utiles quand vous souhaitez trouver une sous-chaîne spécifique dans une chaîne plus grande ou
identifier des modèles dans une chaîne, comme le montrent les exemples suivants.

WARNING
Lorsque System.Text.RegularExpressions vous utilisez pour traiter une entrée non fiable, transmettez un délai d’attente. Un
utilisateur malveillant peut fournir une entrée pour RegularExpressions provoquer une attaque par déni de service.
ASP.NET Core les API d’infrastructure qui utilisent RegularExpressions passent un délai d’expiration.

TIP
L’espace de noms System.Web.RegularExpressions contient un nombre d’objets d’expression régulière qui implémentent des
modèles d’expression régulière prédéfinis pour l’analyse de chaînes provenant de documents HTML, XML et ASP.NET. Par
exemple, la classe TagRegex identifie les balises de début d’une chaîne, tandis que la classe CommentRegex identifie les
commentaires ASP.NET d’une chaîne.

Exemple 1 : remplacer des sous-chaînes


Supposons qu'une liste de diffusion contient des noms complets, constitués d'un prénom, d'un nom et, parfois,
d'un titre (Mr, Mme ou Mlle). Si vous ne souhaitez pas inclure les titres quand vous générez les étiquettes des
enveloppes à partir de la liste, vous pouvez utiliser une expression régulière pour supprimer les titres, comme le
montre l'exemple suivant.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "(Mr\\.? |Mrs\\.? |Miss |Ms\\.? )";
string[] names = { "Mr. Henry Hunt", "Ms. Sara Samuels",
"Abraham Adams", "Ms. Nicole Norris" };
foreach (string name in names)
Console.WriteLine(Regex.Replace(name, pattern, String.Empty));
}
}
// The example displays the following output:
// Henry Hunt
// Sara Samuels
// Abraham Adams
// Nicole Norris
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(Mr\.? |Mrs\.? |Miss |Ms\.? )"
Dim names() As String = {"Mr. Henry Hunt", "Ms. Sara Samuels", _
"Abraham Adams", "Ms. Nicole Norris"}
For Each name As String In names
Console.WriteLine(Regex.Replace(name, pattern, String.Empty))
Next
End Sub
End Module
' The example displays the following output:
' Henry Hunt
' Sara Samuels
' Abraham Adams
' Nicole Norris

Le modèle d’expression régulière (Mr\.? |Mrs\.? |Miss |Ms\.? ) trouve toute occurrence de « Mr », « Mr. »,
« Mrs », « Mrs. », « Miss », « Ms » ou « Ms. ». L'appel de la méthode Regex.Replace remplace la chaîne mise en
correspondance par String.Empty ; en d'autres termes, il la supprime de la chaîne d'origine.
Exemple 2 : identifier les mots en double
La répétition d'un mot est une erreur de rédaction courante. Vous pouvez utiliser une expression régulière pour
identifier les mots en double, comme le montre l'exemple suivant.

using System;
using System.Text.RegularExpressions;

public class Class1


{
public static void Main()
{
string pattern = @"\b(\w+?)\s\1\b";
string input = "This this is a nice day. What about this? This tastes good. I saw a a dog.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("{0} (duplicates '{1}') at position {2}",
match.Value, match.Groups[1].Value, match.Index);
}
}
// The example displays the following output:
// This this (duplicates 'This') at position 0
// a a (duplicates 'a') at position 66

Imports System.Text.RegularExpressions

Module modMain
Public Sub Main()
Dim pattern As String = "\b(\w+?)\s\1\b"
Dim input As String = "This this is a nice day. What about this? This tastes good. I saw a a dog."
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine("{0} (duplicates '{1}') at position {2}", _
match.Value, match.Groups(1).Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' This this (duplicates 'This') at position 0
' a a (duplicates 'a') at position 66

Le modèle d'expression régulière \b(\w+?)\s\1\b peut être interprété comme suit :


M O DÈL E IN T ERP RÉTAT IO N

\b Commencer à la limite d'un mot.

(\w+?) Correspond à un ou plusieurs caractères alphabétiques, mais


le moins de caractères possible. Ensemble, ils forment un
groupe identifiable par \1 .

\s Mettre en correspondance un espace blanc.

\1 Mettre en correspondance la sous-chaîne égale au groupe


nommé \1 .

\b Mettre en correspondance la limite d'un mot.

La méthode Regex.Matches est appelée avec les options d'expression régulière définies sur
RegexOptions.IgnoreCase. Ainsi, l'opération de mise en correspondance ne fait pas la distinction entre minuscules
et majuscules, et l'exemple identifie la sous-chaîne « This this » comme étant une duplication.
La chaîne d’entrée comprend la sous-chaîne «This ? This ». Toutefois, en raison du signe de ponctuation
intermédiaire, cette sous-chaîne n'est pas identifiée comme étant une duplication.
Exemple 3 : générer dynamiquement une expression régulière dépendante de la culture
L’exemple suivant illustre la puissance des expressions régulières combinée à la souplesse offerte par les
fonctionnalités de globalisation de .NET. Il utilise l'objet NumberFormatInfo pour déterminer le format de la
devise dans la culture actuelle du système. Ensuite, à partir de cette information, il construit dynamiquement une
expression régulière qui extrait du texte les valeurs en devise. Pour chaque correspondance, il extrait le sous-
groupe qui contient uniquement la chaîne numérique, le convertit en une valeur Decimal, puis calcule un total
cumulé.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
// Define text to be parsed.
string input = "Office expenses on 2/13/2008:\n" +
"Paper (500 sheets) $3.95\n" +
"Pencils (box of 10) $1.00\n" +
"Pens (box of 10) $4.49\n" +
"Erasers $2.19\n" +
"Ink jet printer $69.95\n\n" +
"Total Expenses $ 81.58\n";

// Get current culture's NumberFormatInfo object.


NumberFormatInfo nfi = CultureInfo.CurrentCulture.NumberFormat;
// Assign needed property values to variables.
string currencySymbol = nfi.CurrencySymbol;
bool symbolPrecedesIfPositive = nfi.CurrencyPositivePattern % 2 == 0;
string groupSeparator = nfi.CurrencyGroupSeparator;
string decimalSeparator = nfi.CurrencyDecimalSeparator;

// Form regular expression pattern.


string pattern = Regex.Escape( symbolPrecedesIfPositive ? currencySymbol : "") +
@"\s*[-+]?" + "([0-9]{0,3}(" + groupSeparator + "[0-9]{3})*(" +
Regex.Escape(decimalSeparator) + "[0-9]+)?)" +
(! symbolPrecedesIfPositive ? currencySymbol : "");
Console.WriteLine( "The regular expression pattern is:");
Console.WriteLine(" " + pattern);

// Get text that matches regular expression pattern.


MatchCollection matches = Regex.Matches(input, pattern,
RegexOptions.IgnorePatternWhitespace);
Console.WriteLine("Found {0} matches.", matches.Count);

// Get numeric string, convert it to a value, and add it to List object.


List<decimal> expenses = new List<Decimal>();

foreach (Match match in matches)


expenses.Add(Decimal.Parse(match.Groups[1].Value));

// Determine whether total is present and if present, whether it is correct.


decimal total = 0;
foreach (decimal value in expenses)
total += value;

if (total / 2 == expenses[expenses.Count - 1])


Console.WriteLine("The expenses total {0:C2}.", expenses[expenses.Count - 1]);
else
Console.WriteLine("The expenses total {0:C2}.", total);
}
}
// The example displays the following output:
// The regular expression pattern is:
// \$\s*[-+]?([0-9]{0,3}(,[0-9]{3})*(\.[0-9]+)?)
// Found 6 matches.
// The expenses total $81.58.
Imports System.Collections.Generic
Imports System.Globalization
Imports System.Text.RegularExpressions

Public Module Example


Public Sub Main()
' Define text to be parsed.
Dim input As String = "Office expenses on 2/13/2008:" + vbCrLf + _
"Paper (500 sheets) $3.95" + vbCrLf + _
"Pencils (box of 10) $1.00" + vbCrLf + _
"Pens (box of 10) $4.49" + vbCrLf + _
"Erasers $2.19" + vbCrLf + _
"Ink jet printer $69.95" + vbCrLf + vbCrLf + _
"Total Expenses $ 81.58" + vbCrLf
' Get current culture's NumberFormatInfo object.
Dim nfi As NumberFormatInfo = CultureInfo.CurrentCulture.NumberFormat
' Assign needed property values to variables.
Dim currencySymbol As String = nfi.CurrencySymbol
Dim symbolPrecedesIfPositive As Boolean = CBool(nfi.CurrencyPositivePattern Mod 2 = 0)
Dim groupSeparator As String = nfi.CurrencyGroupSeparator
Dim decimalSeparator As String = nfi.CurrencyDecimalSeparator

' Form regular expression pattern.


Dim pattern As String = Regex.Escape(CStr(IIf(symbolPrecedesIfPositive, currencySymbol, ""))) + _
"\s*[-+]?" + "([0-9]{0,3}(" + groupSeparator + "[0-9]{3})*(" + _
Regex.Escape(decimalSeparator) + "[0-9]+)?)" + _
CStr(IIf(Not symbolPrecedesIfPositive, currencySymbol, ""))
Console.WriteLine("The regular expression pattern is: ")
Console.WriteLine(" " + pattern)

' Get text that matches regular expression pattern.


Dim matches As MatchCollection = Regex.Matches(input, pattern, RegexOptions.IgnorePatternWhitespace)
Console.WriteLine("Found {0} matches. ", matches.Count)

' Get numeric string, convert it to a value, and add it to List object.
Dim expenses As New List(Of Decimal)

For Each match As Match In matches


expenses.Add(Decimal.Parse(match.Groups.Item(1).Value))
Next

' Determine whether total is present and if present, whether it is correct.


Dim total As Decimal
For Each value As Decimal In expenses
total += value
Next

If total / 2 = expenses(expenses.Count - 1) Then


Console.WriteLine("The expenses total {0:C2}.", expenses(expenses.Count - 1))
Else
Console.WriteLine("The expenses total {0:C2}.", total)
End If
End Sub
End Module
' The example displays the following output:
' The regular expression pattern is:
' \$\s*[-+]?([0-9]{0,3}(,[0-9]{3})*(\.[0-9]+)?)
' Found 6 matches.
' The expenses total $81.58.

Sur un ordinateur dont la culture actuelle est anglais-États-Unis (en-US), l'exemple crée dynamiquement
l'expression régulière \$\s*[-+]?([0-9]{0,3}(,[0-9]{3})*(\.[0-9]+)?) . Ce modèle d’expression régulière peut être
interprété comme suit :
M O DÈL E IN T ERP RÉTAT IO N

\$ Rechercher une occurrence unique du symbole dollar ( $ )


dans la chaîne d’entrée. La chaîne du modèle d'expression
régulière comprend une barre oblique inverse pour indiquer
que le symbole dollar doit être interprété littéralement et non
comme une ancre d'expression régulière. (Le $ symbole
peut uniquement indiquer que le moteur des expressions
régulières doit essayer de commencer sa correspondance à la
fin d’une chaîne.) Pour faire en sorte que le symbole
monétaire de la culture actuelle ne soit pas interprété à tort
comme un symbole d’expression régulière, l’exemple appelle
la Regex.Escape méthode pour échapper le caractère.

\s* Rechercher zéro occurrence, ou plus, d'un espace blanc.

[-+]? Rechercher zéro ou une occurrence d'un signe positif ou d'un


signe négatif.

([0-9]{0,3}(,[0-9]{3})*(\.[0-9]+)?) Les parenthèses externes de cette expression définissent


celle-ci en tant que groupe de capture ou que sous-
expression. Si une correspondance est trouvée, les
informations sur cette partie de la chaîne correspondante
peuvent être récupérées du deuxième objet Group dans
l'objet GroupCollection retourné par la propriété
Match.Groups. (Le premier élément de la collection
représente la correspondance entière.)

[0-9]{0,3} Rechercher entre zéro et trois occurrences des chiffres


décimaux allant de 0 à 9.

(,[0-9]{3})* Rechercher zéro occurrence, ou plus, d'un séparateur de


groupes suivi de trois chiffres décimaux.

\. Rechercher une occurrence unique du séparateur décimal.

[0-9]+ Rechercher un ou plusieurs chiffres décimaux.

(\.[0-9]+)? Rechercher zéro ou une occurrence du séparateur décimal


suivie d'au moins un chiffre décimal.

Si chacun de ces sous-modèles est trouvé dans la chaîne d'entrée, la recherche de correspondance réussit, et un
objet Match contenant des informations sur la correspondance est ajouté à l'objet MatchCollection.

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Langage des expressions régulières - Aide-mémoire Fournit des informations sur le jeu de caractères, d'opérateurs
et de constructions permettant de définir des expressions
régulières.

Modèle objet d'expression régulière Fournit des informations et des exemples de code illustrant
l'utilisation des classes d'expression régulière.
IN T IT UL É DESC RIP T IO N

Comportement détaillé des expressions régulières Fournit des informations sur les fonctionnalités et le
comportement des expressions régulières .NET.

Utiliser des expressions régulières dans Visual Studio

Informations de référence
System.Text.RegularExpressions
System.Text.RegularExpressions.Regex
Expressions régulières - Aide-mémoire (téléchargement au format Word)
Expressions régulières - Aide-mémoire (téléchargement au format PDF)
Langage des expressions régulières - Aide-mémoire
18/07/2020 • 22 minutes to read • Edit Online

Une expression régulière est un modèle que le moteur des expressions régulières tente de faire correspondre
dans le texte d’entrée. Un modèle se compose d’un ou de plusieurs littéraux de caractère, opérateurs ou
constructions. Pour obtenir une brève présentation, consultez Expressions régulières .NET.
Chaque section dans cette référence rapide répertorie une catégorie particulière de caractères, d’opérateurs et
de constructions que vous pouvez utiliser pour définir des expressions régulières.
Nous avons également fourni ces informations dans deux formats que vous pouvez télécharger et imprimer
pour une référence facile :
Télécharger au format Word (.docx)
Télécharger au format PDF (.pdf)

Caractères d’échappement
La barre oblique inverse (\) dans une expression régulière indique que le caractère qui le suit est un caractère
spécial (comme indiqué dans le tableau suivant), ou qu’il doit être interprété littéralement. Pour plus
d’informations, consultez Caractères d’échappement.

C A RA C T ÈRE
D’ÉC H A P P EM EN T DESC RIP T IO N M O DÈL E C O RRESP O N D À

\a Correspond à un caractère \a "\u0007" dans


de cloche, \u0007. "Error!" + '\u0007'

\b Dans une classe de [\b]{3,} "\b\b\b\b" dans


caractères, correspond à un "\b\b\b\b"
retour arrière, \u0008.

\t Correspond à une (\w+)\t "item1\t" , "item2\t"


tabulation, \u0009. dans "item1\titem2\t"

\r Correspond à un retour \r\n(\w+) "\r\nThese" dans


chariot, \u000D. ( \r n’est "\r\nThese are\ntwo
pas équivalent au caractère lines."
de saut de ligne, \n .)

\v Correspond à une [\v]{2,} "\v\v\v" dans


tabulation verticale, \u000B. "\v\v\v"

\f Correspond à un saut de [\f]{2,} "\f\f\f" dans


page, \u000C. "\f\f\f"

\n Correspond à une nouvelle \r\n(\w+) "\r\nThese" dans


ligne, \u000A. "\r\nThese are\ntwo
lines."

\e Correspond à un caractère \e "\x001B" dans


d’échappement, \u001B. "\x001B"
C A RA C T ÈRE
D’ÉC H A P P EM EN T DESC RIP T IO N M O DÈL E C O RRESP O N D À

\ nnn Utilise la représentation \w\040\w "a b" , "c d" dans


octale pour spécifier un "a bc d"
caractère (nnn se compose
de deux ou trois chiffres).

\x nn Utilise une représentation \w\x20\w "a b" , "c d" dans


hexadécimale pour spécifier "a bc d"
un caractère (nn se
compose de deux chiffres
exactement).

\c X Correspond au caractère de \cC "\x0003" dans


contrôle ASCII spécifié par X "\x0003" (Ctrl-C)
\c x ou x, où X ou x représente
la lettre du caractère de
contrôle.

\u nnnn Correspond à un caractère \w\u0020\w "a b" , "c d" dans


Unicode en utilisant la "a bc d"
représentation
hexadécimale
(quatre chiffres exactement,
représentés par nnnn).

\ Lorsque ce caractère \d+[\+-x\*]\d+ "2+2" et "3*9" dans


d'échappement est suivi "(2+2) * 3*9"
d'un caractère non identifié
comme caractère
d'échappement, correspond
au caractère lui-même. Par
exemple, \* est identique
à \x2A et \. est
identique à \x2E . Cela
permet au moteur des
expressions régulières de
lever l’ambiguïté d’éléments
de langage (tels que * ou ?)
et de caractères littéraux
(représentés par \* ou
\? ).

Classes de caractères
Une classe de caractères fait correspondre tout caractère d’un jeu de caractères. Les classes de caractères
incluent les éléments de langage répertoriés dans le tableau suivant. Pour plus d’informations, consultez classes
de caractères.

C L A SSE DE C A RA C T ÈRES DESC RIP T IO N M O DÈL E C O RRESP O N D À

[ character_group ] Correspond à n’importe [ae] "a" dans "gray"


quel caractère unique dans
character_group. Par défaut, "a" , "e" dans "lane"
la correspondance respecte
la casse.
C L A SSE DE C A RA C T ÈRES DESC RIP T IO N M O DÈL E C O RRESP O N D À

[^ character_group ] Négation : correspond à [^aei] "r" , "g" , "n" dans


n'importe quel caractère "reign"
unique qui ne se trouve pas
dans groupe_caractères. Par
défaut, les caractères de
groupe_caractères
respectent la casse.

[ premier - last ] Plage de caractères : [A-Z] "A" , "B" dans "AB123"


correspond à n'importe
quel caractère unique dans
la plage comprise entre
premier et dernier.

. Caractère générique : a.e "ave" dans "nave"


correspond à tout caractère
à l'exception de \n. "ate" dans "water"

Pour faire correspondre un


caractère littéral « point » (.
ou \u002E ), vous devez le
faire précéder du caractère
d'échappement ( \. ).

\p{ nom } Correspond à n’importe \p{Lu} "C" , "L" dans


quel caractère unique de la "City Lights"
catégorie générale Unicode \p{IsCyrillic}
ou du bloc nommé spécifié "Д" , "Ж" dans "ДЖem"
par name.

\P{ nom } Correspond à n'importe \P{Lu} "i" , "t" , "y" dans


quel caractère unique qui "City"
ne se trouve pas dans la \P{IsCyrillic}
catégorie générale Unicode "e" , "m" dans "ДЖem"
ou le bloc nommé spécifié
par name.

\w Correspond à n’importe \w "I" , "D" , "A" , "1" ,


quel caractère alphabétique. "3" dans "ID A1.3"

\W Correspond à tout caractère \W " " , "." dans


autre qu’un caractère de "ID A1.3"
mot.

\s Correspond à tout caractère \w\s "D " dans "ID A1.3"


espace blanc.

\S Correspond à tout caractère \s\S " _" dans "int __ctr"


autre qu’un espace blanc.

\d Correspond à n’importe \d "4" dans "4 = IV"


quel chiffre décimal.

\D Correspond à n’importe \D " " , "=" , " " , "I" ,


quel caractère autre qu’un "V" dans "4 = IV"
chiffre décimal.
Ancres
Les ancres, ou assertions de largeur nulle atomiques, entraînent le succès ou l’échec d’une correspondance en
fonction de la position actuelle dans la chaîne, mais elles n’entraînent pas l’avancement du moteur à travers la
chaîne ou la consommation de caractères. Les métacaractères répertoriés dans le tableau suivant sont des
ancres. Pour plus d’informations, consultez Ancres.

A SSERT IO N DESC RIP T IO N M O DÈL E C O RRESP O N D À

^ Par défaut, la ^\d{3} "901" dans "901-333-"


correspondance doit
commencer au début de la
chaîne ; en mode multiligne,
elle doit commencer au
début de la ligne.

$ Par défaut, la -\d{3}$ "-333" dans


correspondance doit se "-901-333"
produire à la fin de la chaîne
ou avant \n à la fin de la
chaîne ; en mode multiligne,
elle doit se produire avant
la fin de la ligne ou avant
\n à la fin de la ligne.

\A La correspondance doit se \A\d{3} "901" dans "901-333-"


produire au début de la
chaîne.

\Z La correspondance doit se -\d{3}\Z "-333" dans


produire à la fin de la chaîne "-901-333"
ou avant \n à la fin de la
chaîne.

\z La correspondance doit se -\d{3}\z "-333" dans


produire à la fin de la "-901-333"
chaîne.

\G La correspondance doit se \G\(\d\) "(1)" , "(3)" , "(5)"


produire à l’emplacement dans "(1)(3)(5)[7](9)"
où la correspondance
précédente s’est terminée.

\b La correspondance doit se \b\w+\s\w+\b "them theme" ,


produire sur une limite "them them" dans
entre un caractère \w "them theme them them"
(alphanumérique) et un
caractère \W (non
alphanumériques).

\B La correspondance ne doit \Bend\w*\b "ends" , "ender" dans


pas se produire sur une "end sends endure
limite \b . lender"

Constructions de regroupement
Les constructions de regroupement délimitent les sous-expressions d’une expression régulière et capturent
généralement les sous-chaînes d’une chaîne d’entrée. Les constructions de regroupement incluent les éléments
de langage répertoriés dans le tableau suivant. Pour plus d’informations, consultez Constructions de
regroupement.

C O N ST RUC T IO N DE
REGRO UP EM EN T DESC RIP T IO N M O DÈL E C O RRESP O N D À

( sous- expression ) Capture la sous-expression (\w)\1 "ee" dans "deep"


mise en correspondance et
lui assigne un nombre
ordinal de base un.

(?< name > sous- Capture la sous-expression (? "ee" dans "deep"


mise en correspondance <double>\w)\k<double>
expression )
dans un groupe nommé.

(?< nom1 - nom2 > Définit un équilibre de (((?'Open'\()[^\ "((1-3)*(3-1))" dans


sous-expression ) définition de groupe. Pour (\)]*)+((?'Close- "3+2^((1-3)*(3-1))"
Open'\))[^\(\)]*)+)*(?
plus d’informations, (Open)(?!))$
consultez la section «
Équilibre de définition de
groupe » dans
Constructions de
regroupement.

(?: sous- expression ) Définit un groupe sans Write(?:Line)? "WriteLine" dans


capture. "Console.WriteLine()"

"Write" dans
"Console.Write(value)"

(?imnsx-imnsx: sous- Active ou désactive les A\d{2}(?i:\w+)\b "A12xl" , "A12XL" dans


expression ) options spécifiées dans "A12xl A12XL a12xl"
sous-expression. Pour plus
d’informations, consultez
Options des expressions
régulières.

(?= sous- expression ) Assertion de préanalyse \w+(?=\.) "is" , "ran" et "out"


positive de largeur nulle. dans
"He is. The dog ran.
The sun is out."

(?! sous- expression ) Assertion de préanalyse \b(?!un)\w+\b "sure" , "used" dans


négative de largeur nulle. "unsure sure unity
used"

(?<= sous- expression ) Assertion de postanalyse (?<=19)\d{2}\b "99" , "50" , "05" dans
positive de largeur nulle. "1851 1999 1950 1905
2003"

(?<! sous- expression ) Assertion de postanalyse (?<!19)\d{2}\b "51" , "03" dans


négative de largeur nulle. "1851 1999 1950 1905
2003"

(?> sous- expression ) Groupe atomique. [13579](?>A+B+) "1ABB" , "3ABB" et


"5AB" dans
"1ABB 3ABBC 5AB 5AC"
Quantificateurs
Un quantificateur indique combien d’instances de l’élément précédent (qui peut être un caractère, un groupe ou
une classe de caractères) doivent être présentes dans la chaîne d’entrée pour qu’il y ait correspondance. Les
quantificateurs incluent les éléments de langage répertoriés dans le tableau suivant. Pour plus d’informations,
consultez Quantificateurs.

Q UA N T IF IC AT EUR DESC RIP T IO N M O DÈL E C O RRESP O N D À

* Correspond zéro, une ou \d*\.\d ".0" , "19.9" , "219.9"


plusieurs fois à l’élément
précédent.

+ Correspond une ou "be+" "bee" dans "been" ,


plusieurs fois à l’élément "be" dans "bent"
précédent.

? Correspond zéro ou une "rai?n" "ran" , "rain"


fois à l’élément précédent.

{ n } Correspond à l’élément ",\d{3}" ",043" dans "1,043.6" ,


précédent exactement n ",876" , ",543" et
fois. ",210" dans
"9,876,543,210"

{ n ,} Correspond à l’élément "\d{2,}" "166" , "29" , "1930"


précédent au moins n fois.

{ n , m} Correspond à l'élément "\d{3,5}" "166" , "17668"


précédent au moins n fois,
mais pas plus de m fois. "19302" dans "193024"

*? Correspond zéro fois ou \d*?\.\d ".0" , "19.9" , "219.9"


plus à l’élément précédent,
mais le moins de fois
possible.

+? Correspond une ou "be+?" "be" dans "been" ,


plusieurs fois à l’élément "be" dans "bent"
précédent, mais le moins de
fois possible.

?? Correspond zéro ou une "rai??n" "ran" , "rain"


fois à l’élément précédent,
mais le moins de fois
possible.

{ n }? Correspond exactement n ",\d{3}?" ",043" dans "1,043.6" ,


fois à l’élément précédent. ",876" , ",543" et
",210" dans
"9,876,543,210"

{ n ,}? Correspond au moins n fois "\d{2,}?" "166" , "29" , "1930"


à l’élément précédent, mais
le moins de fois possible.
Q UA N T IF IC AT EUR DESC RIP T IO N M O DÈL E C O RRESP O N D À

{ n , m }? Correspond entre n et m "\d{3,5}?" "166" , "17668"


fois à l'élément précédent,
mais le moins de fois "193" , "024" dans
possible. "193024"

Constructions de référence arrière


Une backreference permet qu’une sous-expression précédemment mise en correspondance soit ensuite
identifiée dans la même expression régulière. Le tableau suivant répertorie les constructions de backreference
prises en charge par les expressions régulières dans .NET. Pour plus d'informations, consultez Backreference
Constructs.

C O N ST RUC T IO N DE
B A C K REF EREN C E DESC RIP T IO N M O DÈL E C O RRESP O N D À

\ nombre Backreference. Correspond (\w)\1 "ee" dans "seek"


à la valeur d’une sous-
expression numérotée.

\k< nom > Backreference nommée. (?<char>\w)\k<char> "ee" dans "seek"


Correspond à la valeur
d’une expression nommée.

Constructions d’alternative
Les constructions d’alternative modifient une expression régulière pour permettre la correspondance de type
inclusif/exclusif. Ces constructions incluent les éléments de langage répertoriés dans le tableau suivant. Pour
plus d’informations, consultez Constructions d’alternative.

C O N ST RUC T IO N
D’A LT ERN AT IVE DESC RIP T IO N M O DÈL E C O RRESP O N D À

| Correspond à tout élément th(e|is|at) "the" , "this" dans


séparé par le caractère "this is the day."
barre verticale ( | ).

(?( expression ) Oui | Correspond à oui si le (? "A10" , "910" dans


non ) modèle d’expression (A)A\d{2}\b|\b\d{3}\b) "A10 C103 910"
régulière indiqué par
expression correspond ;
sinon, correspond à no
(facultatif). expression est
interprétée comme une
assertion de largeur nulle.

(?( name ) oui | non Correspond à oui si nom, (?<quoted>")?(? "Dogs.jpg " ,
) un groupe de capture (quoted).+?"|\S+\s) "\"Yiska
nommé ou numéroté, a une playing.jpg\""
correspondance ; sinon, dans
correspond à non "Dogs.jpg \"Yiska
(facultatif). playing.jpg\""

Substitutions
Les substitutions sont des éléments de langage d’expression régulière pris en charge dans les modèles de
remplacement. Pour plus d’informations, consultez Substitutions. Les métacaractères répertoriés dans le tableau
suivant sont des assertions de largeur nulle atomiques.

M O DÈL E DE C H A ÎN E C H A ÎN E DE
C A RA C T ÈRE DESC RIP T IO N M O DÈL E REM P L A C EM EN T D’EN T RÉE RÉSULTAT

$ nombre Remplace la \b(\w+)(\s) $3$2$1 "one two" "two one"


sous-chaîne mise (\w+)\b
en
correspondance
par le groupe
nombre.

${ nom } Remplace la \b(? ${word2} "one two" "two one"


sous-chaîne mise <word1>\w+) ${word1}
(\s)(?
en <word2>\w+)\b
correspondance
par le groupe
nommé nom.

$$ Remplace un "$" \b(\d+)\s?USD $$$1 "103 USD" "$103"


littéral.

$& Remplace une \$?\d*\.?\d+ **$&** "$1.30" "**$1.30**"


copie de la
totalité de la
correspondance.

$` Remplace tout le B+ $` "AABBCC" "AAAACC"


texte de la
chaîne d'entrée
avant la
correspondance.

$' Remplace tout le B+ $' "AABBCC" "AACCCC"


texte de la
chaîne d’entrée
après la
correspondance.

$+ Remplace le B+(C+) $+ "AABBCCDD" "AACCDD"


dernier groupe
qui a été
capturé.

$_ Remplace la B+ $_ "AABBCC" "AAAABBCCCC"


chaîne d’entrée
entière.

Options des expressions régulières


Vous pouvez définir des options qui contrôlent comment le moteur des expressions régulières interprète un
modèle d’expression régulière. Bon nombre de ces options peuvent être spécifiées inline (dans le modèle
d’expression régulière) ou sous la forme d’une ou de plusieurs constantes RegexOptions. Cette référence rapide
répertorie uniquement les options inline. Pour plus d’informations sur les options inline et RegexOptions,
consultez l’article Options des expressions régulières.
Vous pouvez spécifier une option inline de deux façons :
À l’aide d’une construction diverse (?imnsx-imnsx) , où un signe moins (-) devant une option ou un jeu
d’options désactive ces options. Par exemple, (?i-mn) active la correspondance qui ne respecte pas la casse (
i ), désactive le mode multiligne ( m ) et désactive les captures de groupe sans nom ( n ). L’option s’applique
au modèle d’expression régulière depuis le point au niveau duquel l’option est définie et est effective jusqu’à
la fin du modèle ou jusqu’au point au niveau duquel une autre construction inverse l’option.
En utilisant la construction de regroupementsous- (?imnsx-imnsx: expression ) , qui définit des options
pour le groupe spécifié uniquement.
Le moteur d’expression régulière .NET prend en charge les options inline suivantes :

O P T IO N DESC RIP T IO N M O DÈL E C O RRESP O N D À

i Utilise la correspondance \b(?i)a(?-i)a\w+\b "aardvark" , "aaaAuto"


qui ne respecte pas la casse. dans
"aardvark AAAuto
aaaAuto Adam
breakfast"

m Utilise le mode multiligne. Pour obtenir un exemple,


^ et $ correspondent au consultez la section « Mode
début et à la fin d’une ligne, multiligne » dans Options
au lieu du début et de la fin des expressions régulières.
d’une chaîne.

n Ne capture aucun groupe Pour obtenir un exemple,


sans nom. consultez la section «
Captures explicites
uniquement » dans Options
des expressions régulières.

s Utilise le mode à ligne Pour obtenir un exemple,


simple. consultez la section « Mode
à ligne simple » dans
Options des expressions
régulières.

x Ignore l’espace blanc sans \b(?x) \d+ \s \w+ "1 aardvark" , "2 cats"
séquence d’échappement dans
dans le modèle d’expression "1 aardvark 2 cats IV
régulière. centurions"

Constructions diverses
Diverses constructions modifient un modèle d’expression régulière ou fournissent des informations le
concernant. Le tableau suivant répertorie les diverses constructions prises en charge par .NET. Pour plus
d'informations, consultez Miscellaneous Constructs.

C O N ST RUC T IO N DÉF IN IT IO N EXEM P L E

(?imnsx-imnsx) Active ou désactive des options telles \bA(?i)b\w+\b correspond à "ABA"


que le non-respect de la casse au , "Able" dans "ABA Able Act"
milieu d’un modèle. Pour plus
d’informations, consultez Options des
expressions régulières.
C O N ST RUC T IO N DÉF IN IT IO N EXEM P L E

(?# Commentaire ) Commentaire inline. Le commentaire \bA(?#Matches words starting


se termine à la première parenthèse with A)\w+\b
fermante.

# [to end of line] Commentaire en mode X. Le (?x)\bA\w+\b#Matches words


commentaire commence au caractère starting with A
# sans séquence d'échappement et
se poursuit jusqu'à la fin de la ligne.

Voir aussi
System.Text.RegularExpressions
System.Text.RegularExpressions.Regex
Expressions régulières
Classes d’expressions régulières
Expressions régulières - Aide-mémoire (téléchargement au format Word)
Expressions régulières - Aide-mémoire (téléchargement au format PDF)
Caractères d'échappement dans les expressions
régulières
18/07/2020 • 8 minutes to read • Edit Online

La barre oblique inverse (\) dans une expression régulière indique une des possibilités suivantes :
Le caractère qui la suit est un caractère spécial, comme indiqué dans le tableau de la section suivante. Par
exemple, \b est une ancre qui indique qu'une correspondance d'expression régulière doit commencer sur
une limite de mot, \t représente une tabulation et \x020 représente un espace.
Un caractère qui doit être interprété littéralement, et qui sans cela serait interprété comme une construction
du langage sans séquence d'échappement. Par exemple, une accolade ( { ) commence la définition d'un
quantificateur, mais une barre oblique inverse suivie d'une accolade ( \{ ) indique que le moteur
d'expressions régulières doit rechercher une correspondance avec l'accolade. De la même façon, une seule
barre oblique inverse marque le début d'une construction du langage avec échappement, mais deux barres
obliques inverses ( \\ ) indiquent que le moteur d'expressions régulières doit chercher une correspondance
avec la barre oblique inverse.

NOTE
Les séquences d'échappement des caractères sont reconnues dans les modèles d'expressions régulières, mais pas dans les
modèles de remplacement.

Caractères d’échappement dans .NET


Le tableau suivant répertorie les séquences d’échappement des caractères prises en charge par les expressions
régulières dans .NET.

C A RA C T ÈRE O U SÉQ UEN C E DESC RIP T IO N

Tous les caractères à l'exception des suivants : Les caractères autres que ceux répertoriés dans la colonne
Caractère ou séquence n’ont pas de signification spéciale
.$^{[(|)*+?\ dans les expressions régulières ; ils ne correspondent qu’à
eux-mêmes.

Les caractères inclus dans la colonne Caractère ou


séquence sont des éléments spéciaux du langage des
expressions régulières. Pour les faire correspondre dans une
expression régulière, ils doivent être placés dans une séquence
d’échappement ou inclus dans un groupe de caractères
positif. Par exemple, l'expression régulière \$\d+ ou [$]\d+
est en correspondance avec "$1200".

\a Correspond à un caractère représentant une cloche (alarme),


\u0007 .

\b Dans une [ character_group ] classe de caractères


character_group, correspond à un retour arrière, \u0008 .
(Consultez classes de caractères.) En dehors d’une classe de
caractères, \b est une ancre qui correspond à une limite de
mot. (Voir Ancres.)
C A RA C T ÈRE O U SÉQ UEN C E DESC RIP T IO N

\t Correspond à une tabulation, \u0009 .

\r Correspond à un retour chariot, \u000D . Notez que \r


n'est pas équivalent au caractère de nouvelle ligne, \n .

\v Correspond à une tabulation verticale, \u000B .

\f Correspond à un saut de page, \u000C .

\n Correspond à une nouvelle ligne, \u000A .

\e Correspond à un caractère d'échappement, \u001B .

\ nnn Correspond à un caractère ASCII, où nnn se compose de deux


ou trois chiffres qui représentent le code de caractère octal.
Par exemple, \040 représente un espace. Cette construction
est interprétée comme une référence arrière si elle a un seul
chiffre (par exemple \2 ) ou si elle correspond au nombre
d'un groupe de capture. (Voir Constructions de référence
arrière.)

\x nn Correspond à un caractère ASCII, où nn est un code


hexadécimal à deux chiffres d’un caractère.

\c X Correspond à un caractère de contrôle ASCII, où X est la lettre


du caractère de contrôle. Par exemple, \cC est Ctrl-C.

\u nnnn Correspond à une unité de code UTF-16 dont la valeur est


nnnn en hexadécimal. Remarque : La séquence
d’échappement des caractères de Perl 5 utilisée pour spécifier
Unicode n’est pas prise en charge par .NET. Le caractère
d’échappement de Perl 5 a la forme \x{ #### …} , où
#### … est une série de chiffres hexadécimaux. Utilisez à la
place \u nnnn.

\ Quand ce caractère d'échappement est suivi d'un caractère


non reconnu comme caractère d'échappement, correspond au
caractère lui-même. Par exemple, \* correspond à un
astérisque (*) et est identique à \x2A .

Exemple
L'exemple suivant montre l'utilisation de caractères d'échappement dans une expression régulière. Il analyse une
chaîne qui contient les noms des plus grandes villes du monde et leur population en 2009. Chaque nom de ville
est séparé de sa population par une tabulation ( \t ) ou par une barre verticale (| ou \u007c ). Les villes
individuelles et leur population sont séparées les unes des autres par un retour chariot et un saut de ligne.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string delimited = @"\G(.+)[\t\u007c](.+)\r?\n";
string input = "Mumbai, India|13,922,125\t\n" +
"Shanghai, China\t13,831,900\n" +
"Karachi, Pakistan|12,991,000\n" +
"Delhi, India\t12,259,230\n" +
"Istanbul, Turkey|11,372,613\n";
Console.WriteLine("Population of the World's Largest Cities, 2009");
Console.WriteLine();
Console.WriteLine("{0,-20} {1,10}", "City", "Population");
Console.WriteLine();
foreach (Match match in Regex.Matches(input, delimited))
Console.WriteLine("{0,-20} {1,10}", match.Groups[1].Value,
match.Groups[2].Value);
}
}
// The example displays the following output:
// Population of the World's Largest Cities, 2009
//
// City Population
//
// Mumbai, India 13,922,125
// Shanghai, China 13,831,900
// Karachi, Pakistan 12,991,000
// Delhi, India 12,259,230
// Istanbul, Turkey 11,372,613

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim delimited As String = "\G(.+)[\t\u007c](.+)\r?\n"
Dim input As String = "Mumbai, India|13,922,125" + vbCrLf + _
"Shanghai, China" + vbTab + "13,831,900" + vbCrLf + _
"Karachi, Pakistan|12,991,000" + vbCrLf + _
"Delhi, India" + vbTab + "12,259,230" + vbCrLf + _
"Istanbul, Turkey|11,372,613" + vbCrLf
Console.WriteLine("Population of the World's Largest Cities, 2009")
Console.WriteLine()
Console.WriteLine("{0,-20} {1,10}", "City", "Population")
Console.WriteLine()
For Each match As Match In Regex.Matches(input, delimited)
Console.WriteLine("{0,-20} {1,10}", match.Groups(1).Value, _
match.Groups(2).Value)
Next
End Sub
End Module
' The example displays the following output:
' Population of the World's Largest Cities, 2009
'
' City Population
'
' Mumbai, India 13,922,125
' Shanghai, China 13,831,900
' Karachi, Pakistan 12,991,000
' Delhi, India 12,259,230
' Istanbul, Turkey 11,372,613

L'expression régulière \G(.+)[\t|\u007c](.+)\r?\n est interprétée comme indiqué dans le tableau suivant.
M O DÈL E DESC RIP T IO N

\G Commencer la correspondance là où la dernière


correspondance s'est terminée.

(.+) Faire correspondre à n'importe quel caractère une ou


plusieurs fois. Il s'agit du premier groupe de capture.

[\t\u007c] Faire correspondre à une tabulation ( \t ) ou à une barre


verticale (|).

(.+) Faire correspondre à n'importe quel caractère une ou


plusieurs fois. Il s'agit du deuxième groupe de capture.

\r?\n Faire correspondre à zéro ou à une occurrence d'un retour


chariot suivi d'une nouvelle ligne.

Voir aussi
Langage des expressions régulières - Aide-mémoire
Classes de caractères dans les expressions régulières
18/07/2020 • 57 minutes to read • Edit Online

Une classe de caractères définit un jeu de caractères, chacun d'entre eux pouvant apparaître dans une chaîne
d'entrée pour aboutir à une correspondance. Le langage d’expression régulière dans .NET prend en charge les
classes de caractères suivantes :
Groupes de caractères positifs. Un caractère de la chaîne d'entrée doit correspondre à l'un des jeux de
caractères spécifié. Pour plus d’informations, consultez groupe de caractères positif.
Groupes de caractères négatifs. Un caractère de la chaîne d'entrée ne doit pas correspondre à l'un des jeux
de caractères spécifié. Pour plus d’informations, consultez groupe de caractères négatif.
Tout caractère. Le caractère . (point) dans une expression régulière est un caractère générique qui
correspond à n'importe quel caractère sauf \n . Pour plus d’informations, consultez tout caractère.
Catégorie Unicode générale ou bloc nommé. Un caractère de la chaîne d'entrée doit être membre d'une
catégorie Unicode particulière ou doit figurer dans une plage contiguë de caractères Unicode pour aboutir
à une correspondance. Pour plus d’informations, consultez la rubrique catégorie Unicode ou bloc Unicode.
Catégorie Unicode générale négative ou bloc nommé. Un caractère de la chaîne d'entrée ne doit pas être
membre d'une catégorie Unicode particulière ou ne doit pas figurer dans une plage contiguë de
caractères Unicode pour aboutir à une correspondance. Pour plus d’informations, consultez catégorie
Unicode négative ou bloc Unicode.
Caractère de mot. Un caractère de la chaîne d'entrée peut appartenir à l'une des catégories Unicode
appropriées aux caractères contenus dans les mots. Pour plus d’informations, consultez caractère de mot.
Caractère autre qu'un caractère de mot. Un caractère de la chaîne d'entrée peut appartenir à n'importe
quelle catégorie Unicode qui n'est pas un caractère de mot. Pour plus d’informations, consultez caractère
autre qu’un mot.
Espace blanc. Un caractère de la chaîne d'entrée peut être n'importe quel caractère Unicode de séparation,
ainsi que n'importe quel nombre de caractères de contrôle. Pour plus d’informations, consultez espace
blanc.
Caractère autre qu'un espace blanc. Un caractère de la chaîne d'entrée peut être n'importe quel caractère
qui n'est pas un espace blanc. Pour plus d’informations, consultez caractère autre qu’un espace blanc.
Chiffre décimal. Un caractère de la chaîne d'entrée peut être n'importe quel nombre de caractères
classifiés en tant que chiffres décimaux Unicode. Pour plus d’informations, consultez caractère décimal
digit.
Chiffre non décimal. Un caractère de la chaîne d'entrée peut correspondre à autre chose qu'un chiffre
décimal Unicode. Pour plus d’informations, consultez caractère décimal digit.
.NET prend en charge les expressions de soustraction de classe de caractères, ce qui vous permet de définir un
jeu de caractères comme résultat de l’exclusion d’une classe de caractères d’une autre classe de caractères. Pour
plus d’informations, consultez soustraction de classe de caractères.
NOTE
Les classes de caractères qui font correspondre les caractères par catégorie, comme \w pour faire correspondre les
caractères alphabétiques, ou \p{} pour les faire correspondre à une catégorie Unicode, s’appuient sur la classe
CharUnicodeInfo pour fournir des informations sur les catégories de caractères. À compter de .NET Framework 4.6.2, les
catégories de caractères sont basées sur la norme Unicode version 8.0.0. De .NET Framework 4 à .NET Framework 4.6.1,
elles sont basées sur la norme Unicode version 6.3.0.

Groupe de caractères positif : [ ]


Un groupe de caractères positif spécifie une liste de caractères, chacun d'entre eux pouvant apparaître dans une
chaîne d'entrée pour produire une correspondance. Cette liste de caractères peut être spécifiée individuellement,
comme une plage, ou les deux à la fois.
La syntaxe de la spécification d'une liste de différents caractères est comme suit :
[*character_group*]

où groupe_caractères est la liste des différents caractères pouvant apparaître dans la chaîne d’entrée pour qu’une
correspondance soit établie. character_group peut être constitué de n’importe quelle combinaison d’un ou
plusieurs caractères littéraux, de caractères d’échappementou de classes de caractères.
La syntaxe de la spécification d'une plage de caractères est comme suit :
[firstCharacter-lastCharacter]

où premiercaractère est le caractère qui commence la plage et derniercaractère est le caractère qui termine la
plage. Une plage de caractères est une série contiguë de caractères définie par la spécification du premier
caractère de la série, d'un trait d'union (-), puis du dernier caractère de la série. Deux caractères sont contigus s'ils
présentent des points de code Unicode adjacents. premierCaractère doit être le caractère avec le code de
caractère le plus faible et dernierCaractère doit être le caractère avec le code de caractère le plus élevé.

NOTE
Étant donné qu’un groupe de caractères positif peut inclure à la fois un jeu de caractères et une plage de caractères, un
trait d’union ( - ) est toujours interprété comme le séparateur de plage, sauf s’il s’agit du premier ou dernier caractère du
groupe.

Quelques modèles d'expressions régulières courants qui contiennent des classes de caractères positifs
apparaissent dans le tableau suivant.

M O DÈL E DESC RIP T IO N

[aeiou] Mettre en correspondance toutes les voyelles.

[\p{P}\d] Mettre en correspondance tous les signes de ponctuation et


chiffres décimaux.

[\s\p{P}] Mettre en correspondance tous les espaces blancs et signes


de ponctuation.

L'exemple suivant définit un groupe de caractères positif qui contient les caractères « a » et « e » afin que la
chaîne d'entrée contienne les mots « grey » ou « gray » suivis par un autre mot pour produire une
correspondance.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"gr[ae]y\s\S+?[\s\p{P}]";
string input = "The gray wolf jumped over the grey wall.";
MatchCollection matches = Regex.Matches(input, pattern);
foreach (Match match in matches)
Console.WriteLine($"'{match.Value}'");
}
}
// The example displays the following output:
// 'gray wolf '
// 'grey wall.'

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "gr[ae]y\s\S+?[\s\p{P}]"
Dim input As String = "The gray wolf jumped over the grey wall."
Dim matches As MatchCollection = Regex.Matches(input, pattern)
For Each match As Match In matches
Console.WriteLine($"'{match.Value}'")
Next
End Sub
End Module
' The example displays the following output:
' 'gray wolf '
' 'grey wall.'

L'expression régulière gr[ae]y\s\S+?[\s|\p{P}] est définie comme suit :

M O DÈL E DESC RIP T IO N

gr Mettre en correspondance les caractères littéraux « gr ».

[ae] Mettre en correspondance un « a » ou un « e ».

y\s Mettre en correspondance le caractère littéral « y » suivi d'un


espace blanc.

\S+? Mettre en correspondance un ou plusieurs caractères autres


que des espaces blancs, mais le moins possible.

[\s\p{P}] Mettre en correspondance un espace blanc ou un signe de


ponctuation.

L'exemple suivant correspond aux mots qui commencent par une majuscule. Il utilise la sous-expression [A-Z]
pour représenter la plage de majuscules de A à Z.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b[A-Z]\w*\b";
string input = "A city Albany Zulu maritime Marseilles";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// A
// Albany
// Zulu
// Marseilles

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b[A-Z]\w*\b"
Dim input As String = "A city Albany Zulu maritime Marseilles"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
End Sub
End Module

L'expression régulière \b[A-Z]\w*\b est définie comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

[A-Z] Mettre en correspondance une majuscule de A à Z.

\w* Mettre en correspondance zéro, un ou plusieurs caractères


alphabétiques.

\b Mettre en correspondance la limite d'un mot.

Groupe de caractères négatif : [^]


Un groupe de caractères négatifs spécifie une liste de caractères qui ne doivent pas s'afficher dans une chaîne
d'entrée pour produire une correspondance. La liste de caractères peut être spécifiée individuellement, comme
une plage, ou les deux à la fois.
La syntaxe de la spécification d'une liste de différents caractères est comme suit :
[*^character_group*]

où groupe_caractères est la liste des différents caractères qui ne peuvent pas apparaître dans la chaîne d’entrée
pour qu’une correspondance soit établie. character_group peut être constitué de n’importe quelle combinaison
d’un ou plusieurs caractères littéraux, de caractères d’échappementou de classes de caractères.
La syntaxe de la spécification d'une plage de caractères est comme suit :
[^*firstCharacter*-*lastCharacter*]

où premiercaractère est le caractère qui commence la plage et derniercaractère est le caractère qui termine la
plage. Une plage de caractères est une série contiguë de caractères définie par la spécification du premier
caractère de la série, d'un trait d'union (-), puis du dernier caractère de la série. Deux caractères sont contigus s'ils
présentent des points de code Unicode adjacents. premierCaractère doit être le caractère avec le code de
caractère le plus faible et dernierCaractère doit être le caractère avec le code de caractère le plus élevé.

NOTE
Étant donné qu’un groupe de caractères négatif peut inclure à la fois un jeu de caractères et une plage de caractères, un
trait d’union ( - ) est toujours interprété comme le séparateur de plage, sauf s’il s’agit du premier ou dernier caractère du
groupe.

Deux ou plusieurs plages de caractères peuvent être concaténées. Par exemple, pour spécifier la plage de chiffres
décimaux de « 0 » à « 9 », la plage de lettres minuscules de « a » à « f » et la plage de lettres majuscules de « A »
à « F », utilisez [0-9a-fA-F] .
Le caractère de signe insertion de début ( ^ ) dans un groupe de caractères négatif est obligatoire et indique
que le groupe de caractères est un groupe de caractères négatif et non un groupe de caractères positif.

IMPORTANT
Un groupe de caractères négatifs d'un plus grand modèle d'expressions régulières n'est pas une assertion de largeur nulle.
Autrement dit, après avoir évalué le groupe de caractères négatif, le moteur des expressions régulières avance d'un
caractère dans la chaîne d'entrée.

Quelques modèles d'expressions régulières courants qui contiennent des groupes de caractères négatifs
apparaissent dans le tableau suivant.

M O DÈL E DESC RIP T IO N

[^aeiou] Mettre en correspondance tous les caractères, à l'exception


des voyelles.

[^\p{P}\d] Mettre en correspondance tous les caractères, à l'exception


des signes de ponctuation et des chiffres décimaux.

L'exemple suivant correspond à un mot commençant par les caractères « th » et n'étant pas suivi d'un « o ».
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\bth[^o]\w+\b";
string input = "thought thing though them through thus thorough this";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// thing
// them
// through
// thus
// this

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\bth[^o]\w+\b"
Dim input As String = "thought thing though them through thus " + _
"thorough this"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' thing
' them
' through
' thus
' this

L'expression régulière \bth[^o]\w+\b est définie comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

th Mettre en correspondance les caractères littéraux « th ».

[^o] Mettre en correspondance n'importe quel caractère autre


que « o ».

\w+ Mettre en correspondance un ou plusieurs caractères


alphabétiques.

\b Terminer à une limite de mot.

N’importe quel caractère : .


Le point (.) correspond à n'importe quel caractère à l'exception de \n (caractère de saut de ligne, \u000A), avec
les deux qualifications suivantes :
Si un modèle d'expression régulière est modifié par l'option RegexOptions.Singleline ou si la partie du
modèle qui contient la classe de caractères . est modifiée par l'option s , . correspond à n'importe
quel caractère. Pour plus d’informations, consultez Options des expressions régulières.
L'exemple suivant illustre le comportement différent de la classe de caractères . par défaut et avec
l'option RegexOptions.Singleline. L'expression régulière ^.+ commence au début de la chaîne et
correspond à tous les caractères. Par défaut, la correspondance se termine à la fin de la première ligne ; le
modèle d'expression régulière correspond au retour chariot, à \r ou à \u000D, mais il ne correspond pas
à \n . Étant donné que l'option RegexOptions.Singleline interprète la chaîne d'entrée entière comme une
ligne unique, il correspond à chaque caractère de la chaîne d'entrée, notamment \n .

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "^.+";
string input = "This is one line and" + Environment.NewLine + "this is the second.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(Regex.Escape(match.Value));

Console.WriteLine();
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.Singleline))
Console.WriteLine(Regex.Escape(match.Value));
}
}
// The example displays the following output:
// This\ is\ one\ line\ and\r
//
// This\ is\ one\ line\ and\r\nthis\ is\ the\ second\.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^.+"
Dim input As String = "This is one line and" + vbCrLf + "this is the second."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(Regex.Escape(match.Value))
Next
Console.WriteLine()
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.SingleLine)
Console.WriteLine(Regex.Escape(match.Value))
Next
End Sub
End Module
' The example displays the following output:
' This\ is\ one\ line\ and\r
'
' This\ is\ one\ line\ and\r\nthis\ is\ the\ second\.

NOTE
Étant donné qu'elle correspond à n'importe quel caractère sauf \n , la classe de caractères . correspond également à
\r (retour chariot, \u000D).

Dans un groupe de caractères négatif ou positif, un point est traité comme un caractère littéral de point, et
non pas comme une classe de caractères. Pour plus d'informations, consultez Groupe de caractères positif
et Groupe de caractères négatif plus haut dans cette rubrique. L'exemple suivant en propose une
illustration. Il définit une expression régulière qui inclut le point ( . ) à la fois en tant que classe de
caractères et en tant que membre d'un groupe de caractères positif. L’expression régulière
\b.*[.?!;:](\s|\z) commence à une limite de mot, correspond à n’importe quel caractère tant qu’elle ne
rencontre pas l’un des cinq signes de ponctuation, y compris le point, puis correspond à un espace blanc
ou à la fin de la chaîne.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b.*[.?!;:](\s|\z)";
string input = "this. what: is? go, thing.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// this. what: is? go, thing.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As STring = "\b.*[.?!;:](\s|\z)"
Dim input As String = "this. what: is? go, thing."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' this. what: is? go, thing.

NOTE
Étant donné qu'il correspond à n'importe quel caractère, l'élément de langage . est souvent utilisé avec un quantificateur
limitatif si un modèle d'expression régulière essaie de correspondre plusieurs fois à n'importe quel caractère. Pour plus
d’informations, consultez Quantificateurs.

Catégorie Unicode ou bloc Unicode : \p{}


La norme Unicode assigne une catégorie générale à chaque caractère. Par exemple, un caractère particulier peut
être une lettre majuscule (représenté par la catégorie Lu ), un chiffre décimal (catégorie Nd ), un symbole
mathématique (catégorie Sm ) ou un séparateur de paragraphe (catégorie Zl ). Les jeux de caractères
spécifiques de la norme Unicode occupent également une plage spécifique ou un bloc de points de code
consécutifs. Par exemple, l'alphabet latin de base se trouve de \u0000 à \u007F, alors que le jeu de caractères
arabe se trouve de \u0600 à \u06FF.
La construction d'expression régulière
\p{ nom }
correspond à n’importe quel caractère qui appartient à une catégorie Unicode générale ou à un bloc nommé, où
nom est l’abréviation de la catégorie ou le nom du bloc nommé. Pour obtenir la liste des abréviations des
catégories, consultez la section catégories générales Unicode prises en charge plus loin dans cette rubrique. Pour
obtenir la liste des blocs nommés, consultez la section blocs nommés pris en charge plus loin dans cette
rubrique.
L’exemple suivant utilise la \p{ name } construction de nom pour faire correspondre à la fois une catégorie
Unicode générale (dans ce cas, la Pd catégorie, ou la ponctuation, la catégorie de tirets) et un bloc nommé (les
IsGreek IsBasicLatin blocs nommés et).

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\p{IsGreek}+(\s)?)+\p{Pd}\s(\p{IsBasicLatin}+(\s)?)+";
string input = "Κατα Μαθθαίον - The Gospel of Matthew";

Console.WriteLine(Regex.IsMatch(input, pattern)); // Displays True.


}
}

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\p{IsGreek}+(\s)?)+\p{Pd}\s(\p{IsBasicLatin}+(\s)?)+"
Dim input As String = "Κατα Μαθθαίον - The Gospel of Matthew"

Console.WriteLine(Regex.IsMatch(input, pattern)) ' Displays True.


End Sub
End Module

L'expression régulière \b(\p{IsGreek}+(\s)?)+\p{Pd}\s(\p{IsBasicLatin}+(\s)?)+ est définie comme indiqué dans


le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

\p{IsGreek}+ Mettre en correspondance un ou plusieurs caractères grecs.

(\s)? Mettre en correspondance zéro ou un espace blanc.

(\p{IsGreek}+(\s)?)+ Mettre en correspondance le modèle d’un ou plusieurs


caractères grecs, suivis de zéro ou d’un espace blanc, une ou
plusieurs fois.

\p{Pd} Mettre en correspondance un signe de ponctuation tiret.

\s Mettre en correspondance un espace blanc.

\p{IsBasicLatin}+ Mettre en correspondance un ou plusieurs caractères latins


de base.
M O DÈL E DESC RIP T IO N

(\s)? Mettre en correspondance zéro ou un espace blanc.

(\p{IsBasicLatin}+(\s)?)+ Mettre en correspondance un ou plusieurs caractères latins


de base suivis de zéro ou d’un espace blanc, une ou plusieurs
fois.

Catégorie Unicode négative ou bloc Unicode : \P{}


La norme Unicode assigne une catégorie générale à chaque caractère. Par exemple, un caractère particulier peut
être une lettre majuscule (représenté par la catégorie Lu ), un chiffre décimal (catégorie Nd ), un symbole
mathématique (catégorie Sm ) ou un séparateur de paragraphe (catégorie Zl ). Les jeux de caractères
spécifiques de la norme Unicode occupent également une plage spécifique ou un bloc de points de code
consécutifs. Par exemple, l'alphabet latin de base se trouve de \u0000 à \u007F, alors que le jeu de caractères
arabe se trouve de \u0600 à \u06FF.
La construction d'expression régulière
\P{ nom }
correspond à n'importe quel caractère qui n'appartient pas à une catégorie générale Unicode ou à un bloc
nommé, où nom est l'abréviation de la catégorie ou le nom du bloc nommé. Pour obtenir la liste des abréviations
des catégories, consultez la section catégories générales Unicode prises en charge plus loin dans cette rubrique.
Pour obtenir la liste des blocs nommés, consultez la section blocs nommés pris en charge plus loin dans cette
rubrique.
L’exemple suivant utilise la \P{ name } construction de nom pour supprimer tous les symboles monétaires
(dans ce cas, le Sc symbole, ou, la catégorie Currency) des chaînes numériques.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(\P{Sc})+";

string[] values = { "$164,091.78", "£1,073,142.68", "73¢", "€120" };


foreach (string value in values)
Console.WriteLine(Regex.Match(value, pattern).Value);
}
}
// The example displays the following output:
// 164,091.78
// 1,073,142.68
// 73
// 120
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(\P{Sc})+"

Dim values() As String = {"$164,091.78", "£1,073,142.68", "73¢", "€120"}


For Each value As String In values
Console.WriteLine(Regex.Match(value, pattern).Value)
Next
End Sub
End Module
' The example displays the following output:
' 164,091.78
' 1,073,142.68
' 73
' 120

Le modèle d'expression régulière (\P{Sc})+ correspond à un ou plusieurs caractères qui ne sont pas des
symboles monétaires ; il supprime efficacement tout symbole monétaire de la chaîne du résultat.

Caractère de mot : \w
\w correspond à n'importe quel caractère alphabétique. Un caractère de mot est un membre d'une des
catégories Unicode répertoriées dans le tableau suivant.

C AT EGO RY DESC RIP T IO N

Ll Letter, Lowercase

Lu Letter, Uppercase

Lt Letter, Titlecase

Lo Letter, Other

Lm Letter, Modifier

Mn Mark, Nonspacing

Nd Number, Decimal Digit

Pc Punctuation, Connector. Cette catégorie inclut dix caractères


dont le plus souvent utilisé est le caractère LOWLINE (),
u+005F.

Si un comportement conforme à ECMAScript est spécifié, \w est équivalent à [a-zA-Z_0-9] . Pour plus
d’informations sur les expressions régulières ECMAScript, consultez la section « comportement de
correspondance ECMAScript » dans Options des expressions régulières.

NOTE
Étant donné qu'il correspond à n'importe quel caractère alphabétique, l'élément de langage \w est souvent utilisé avec un
quantificateur limitatif si un modèle d'expression régulière essaie de correspondre plusieurs fois à n'importe quel caractère
alphabétique, suivi par un caractère alphabétique spécifique. Pour plus d’informations, consultez Quantificateurs.
L'exemple suivant utilise l'élément de langage \w pour le faire corresponde à des caractères en double dans un
mot. L'exemple définit un modèle d'expression régulière, (\w)\1 , qui peut être interprété comme suit.

ÉL ÉM EN T DESC RIP T IO N

(\w) Mettre en correspondance un caractère de mot. Il s'agit du


premier groupe de capture.

\1 Mettre en correspondance la valeur de la première capture.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(\w)\1";
string[] words = { "trellis", "seer", "latter", "summer",
"hoarse", "lesser", "aardvark", "stunned" };
foreach (string word in words)
{
Match match = Regex.Match(word, pattern);
if (match.Success)
Console.WriteLine("'{0}' found in '{1}' at position {2}.",
match.Value, word, match.Index);
else
Console.WriteLine("No double characters in '{0}'.", word);
}
}
}
// The example displays the following output:
// 'll' found in 'trellis' at position 3.
// 'ee' found in 'seer' at position 1.
// 'tt' found in 'latter' at position 2.
// 'mm' found in 'summer' at position 2.
// No double characters in 'hoarse'.
// 'ss' found in 'lesser' at position 2.
// 'aa' found in 'aardvark' at position 0.
// 'nn' found in 'stunned' at position 3.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(\w)\1"
Dim words() As String = {"trellis", "seer", "latter", "summer", _
"hoarse", "lesser", "aardvark", "stunned"}
For Each word As String In words
Dim match As Match = Regex.Match(word, pattern)
If match.Success Then
Console.WriteLine("'{0}' found in '{1}' at position {2}.", _
match.Value, word, match.Index)
Else
Console.WriteLine("No double characters in '{0}'.", word)
End If
Next
End Sub
End Module
' The example displays the following output:
' 'll' found in 'trellis' at position 3.
' 'ee' found in 'seer' at position 1.
' 'tt' found in 'latter' at position 2.
' 'mm' found in 'summer' at position 2.
' No double characters in 'hoarse'.
' 'ss' found in 'lesser' at position 2.
' 'aa' found in 'aardvark' at position 0.
' 'nn' found in 'stunned' at position 3.

Caractère autre qu’un mot : \W


\W correspond à tout caractère autre qu'un caractère alphabétique. L'élément de langage \W est équivalent à la
classe de caractères suivante :
[^\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Pc}\p{Lm}]

En d’autres termes, il correspond à n’importe quel caractère à l’exception de ceux inclus dans les catégories
Unicode répertoriées dans le tableau suivant.

C AT EGO RY DESC RIP T IO N

Ll Letter, Lowercase

Lu Letter, Uppercase

Lt Letter, Titlecase

Lo Letter, Other

Lm Letter, Modifier

Mn Mark, Nonspacing

Nd Number, Decimal Digit

Pc Punctuation, Connector. Cette catégorie inclut dix caractères


dont le plus souvent utilisé est le caractère LOWLINE (),
u+005F.

Si un comportement conforme à ECMAScript est spécifié, \W est équivalent à [^a-zA-Z_0-9] . Pour plus
d’informations sur les expressions régulières ECMAScript, consultez la section « comportement de
correspondance ECMAScript » dans Options des expressions régulières.

NOTE
Étant donné qu'il correspond à n'importe quel caractère non alphabétique, l'élément de langage \W est souvent utilisé
avec un quantificateur limitatif si un modèle d'expression régulière essaie de correspondre plusieurs fois à n'importe quel
caractère non alphabétique suivi d'un caractère non alphabétique spécifique. Pour plus d’informations, consultez
Quantificateurs.

L'exemple suivant illustre la classe de caractères \W . Il définit un modèle d'expression régulière,


\b(\w+)(\W){1,2} , qui correspond à un mot suivi d'un ou deux caractères non alphabétiques, comme un espace
ou un signe de ponctuation. L'expression régulière est interprétée comme indiqué dans le tableau suivant.

ÉL ÉM EN T DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

(\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques. Il s'agit du premier groupe de capture.

(\W){1,2} Mettre en correspondance un caractère autre qu'un mot une


ou deux fois. Il s'agit du deuxième groupe de capture.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\w+)(\W){1,2}";
string input = "The old, grey mare slowly walked across the narrow, green pasture.";
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine(match.Value);
Console.Write(" Non-word character(s):");
CaptureCollection captures = match.Groups[2].Captures;
for (int ctr = 0; ctr < captures.Count; ctr++)
Console.Write(@"'{0}' (\u{1}){2}", captures[ctr].Value,
Convert.ToUInt16(captures[ctr].Value[0]).ToString("X4"),
ctr < captures.Count - 1 ? ", " : "");
Console.WriteLine();
}
}
}
// The example displays the following output:
// The
// Non-word character(s):' ' (\u0020)
// old,
// Non-word character(s):',' (\u002C), ' ' (\u0020)
// grey
// Non-word character(s):' ' (\u0020)
// mare
// Non-word character(s):' ' (\u0020)
// slowly
// Non-word character(s):' ' (\u0020)
// walked
// Non-word character(s):' ' (\u0020)
// across
// Non-word character(s):' ' (\u0020)
// the
// Non-word character(s):' ' (\u0020)
// narrow,
// Non-word character(s):',' (\u002C), ' ' (\u0020)
// green
// Non-word character(s):' ' (\u0020)
// pasture.
// Non-word character(s):'.' (\u002E)
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\w+)(\W){1,2}"
Dim input As String = "The old, grey mare slowly walked across the narrow, green pasture."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Console.Write(" Non-word character(s):")
Dim captures As CaptureCollection = match.Groups(2).Captures
For ctr As Integer = 0 To captures.Count - 1
Console.Write("'{0}' (\u{1}){2}", captures(ctr).Value, _
Convert.ToUInt16(captures(ctr).Value.Chars(0)).ToString("X4"), _
If(ctr < captures.Count - 1, ", ", ""))
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' The
' Non-word character(s):' ' (\u0020)
' old,
' Non-word character(s):',' (\u002C), ' ' (\u0020)
' grey
' Non-word character(s):' ' (\u0020)
' mare
' Non-word character(s):' ' (\u0020)
' slowly
' Non-word character(s):' ' (\u0020)
' walked
' Non-word character(s):' ' (\u0020)
' across
' Non-word character(s):' ' (\u0020)
' the
' Non-word character(s):' ' (\u0020)
' narrow,
' Non-word character(s):',' (\u002C), ' ' (\u0020)
' green
' Non-word character(s):' ' (\u0020)
' pasture.
' Non-word character(s):'.' (\u002E)

Étant donné que l'objet Group du deuxième groupe de capture ne contient qu'un seul caractère autre qu'un mot
capturé, l'exemple extrait tous les caractères autres que des mots capturés de l'objet CaptureCollection retourné
par la propriété Group.Captures.

Espace : \s
\s correspond à n'importe quel espace. Il est équivalent aux séquences d'échappement et catégories Unicode
répertoriées dans le tableau suivant.

C AT EGO RY DESC RIP T IO N

\f Saut de page, \u000C.

\n Saut de ligne, \u000A.

\r Retour chariot, \u000D.

\t Tabulation, \u0009.
C AT EGO RY DESC RIP T IO N

\v Tabulation verticale, \u000B.

\x85 Points de suspension ou caractère À LA LIGNE (NEL) (…),


\u0085.

\p{Z} Correspond à n'importe quel caractère de séparation.

Si un comportement conforme à ECMAScript est spécifié, \s est équivalent à [ \f\n\r\t\v] . Pour plus
d’informations sur les expressions régulières ECMAScript, consultez la section « comportement de
correspondance ECMAScript » dans Options des expressions régulières.
L'exemple suivant illustre la classe de caractères \s . Il définit un modèle d'expression régulière,
\b\w+(e)?s(\s|$) , qui correspond à un mot se terminant par "s" ou par "es", suivi d'un espace ou de la fin de la
chaîne d'entrée. L'expression régulière est interprétée comme indiqué dans le tableau suivant.

ÉL ÉM EN T DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

\w+ Mettre en correspondance un ou plusieurs caractères


alphabétiques.

(e)? Mettre en correspondance un « e » zéro ou une fois.

s Mettre en correspondance un « s ».

(\s|$) Mettre en correspondance un espace blanc ou la fin de la


chaîne d’entrée.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b\w+(e)?s(\s|$)";
string input = "matches stores stops leave leaves";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// matches
// stores
// stops
// leaves
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b\w+(e)?s(\s|$)"
Dim input As String = "matches stores stops leave leaves"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' matches
' stores
' stops
' leaves

Caractère autre qu’un espace : \S


\S correspond à tout caractère autre qu'un espace. Il est équivalent au modèle d'expression régulière
[^\f\n\r\t\v\x85\p{Z}] ou à l'opposé du modèle d'expression régulière qui est équivalent à \s , qui met en
correspondance des espaces. Pour plus d'informations, consultez Espace blanc : \s.
Si un comportement conforme à ECMAScript est spécifié, \S est équivalent à [^ \f\n\r\t\v] . Pour plus
d’informations sur les expressions régulières ECMAScript, consultez la section « comportement de
correspondance ECMAScript » dans Options des expressions régulières.
L'exemple suivant illustre l'élément de langage \S . Le modèle d'expression régulière \b(\S+)\s? met en
correspondance des chaînes délimitées par des espaces blancs. Le deuxième élément de l'objet GroupCollection
de la correspondance contient la chaîne correspondante. L'expression régulière peut être interprétée comme
indiqué dans le tableau suivant.

ÉL ÉM EN T DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

(\S+) Mettre en correspondance un ou plusieurs caractères autres


que des espaces blancs. Il s'agit du premier groupe de
capture.

\s? Mettre en correspondance zéro ou un espace blanc.


using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\S+)\s?";
string input = "This is the first sentence of the first paragraph. " +
"This is the second sentence.\n" +
"This is the only sentence of the second paragraph.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Groups[1]);
}
}
// The example displays the following output:
// This
// is
// the
// first
// sentence
// of
// the
// first
// paragraph.
// This
// is
// the
// second
// sentence.
// This
// is
// the
// only
// sentence
// of
// the
// second
// paragraph.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\S+)\s?"
Dim input As String = "This is the first sentence of the first paragraph. " + _
"This is the second sentence." + vbCrLf + _
"This is the only sentence of the second paragraph."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Groups(1))
Next
End Sub
End Module
' The example displays the following output:
' This
' is
' the
' first
' sentence
' of
' the
' first
' paragraph.
' This
' is
' the
' second
' sentence.
' This
' is
' the
' only
' sentence
' of
' the
' second
' paragraph.

Caractère numérique décimal : \d


\d correspond à n'importe quel chiffre décimal. Il est équivalent au modèle d'expression régulière \p{Nd} , qui
inclut les chiffres décimaux standard de 0 à 9, ainsi que les chiffres décimaux de plusieurs autres jeux de
caractères.
Si un comportement conforme à ECMAScript est spécifié, \d est équivalent à [0-9] . Pour plus d’informations
sur les expressions régulières ECMAScript, consultez la section « comportement de correspondance
ECMAScript » dans Options des expressions régulières.
L'exemple suivant illustre l'élément de langage \d . Il teste si une chaîne d'entrée représente un numéro de
téléphone valide aux États-Unis et au Canada. Le modèle d'expression régulière
^(\(?\d{3}\)?[\s-])?\d{3}-\d{4}$ est défini comme indiqué dans le tableau suivant.

ÉL ÉM EN T DESC RIP T IO N

^ Commencer la correspondance au début de la chaîne


d'entrée.

\(? Mettre en correspondance zéro ou un caractère littéral « ( ».

\d{3} Mettre en correspondance trois chiffres décimaux.


ÉL ÉM EN T DESC RIP T IO N

\)? Mettre en correspondance zéro ou un caractère littéral « ) ».

[\s-] Mettre en correspondance un trait d'union ou un espace


blanc.

(\(?\d{3}\)?[\s-])? Mettre en correspondance une parenthèse ouvrante


facultative suivie de trois chiffres décimaux, d'une parenthèse
fermante facultative et d'un espace blanc ou d'un tiret, zéro
ou une fois. Il s'agit du premier groupe de capture.

\d{3}-\d{4} Mettre en correspondance trois chiffres décimaux suivis d'un


trait d'union et de quatre chiffres décimaux supplémentaires.

$ Mettre en correspondance la fin de la chaîne d'entrée.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"^(\(?\d{3}\)?[\s-])?\d{3}-\d{4}$";
string[] inputs = { "111 111-1111", "222-2222", "222 333-444",
"(212) 111-1111", "111-AB1-1111",
"212-111-1111", "01 999-9999" };

foreach (string input in inputs)


{
if (Regex.IsMatch(input, pattern))
Console.WriteLine(input + ": matched");
else
Console.WriteLine(input + ": match failed");
}
}
}
// The example displays the following output:
// 111 111-1111: matched
// 222-2222: matched
// 222 333-444: match failed
// (212) 111-1111: matched
// 111-AB1-1111: match failed
// 212-111-1111: matched
// 01 999-9999: match failed
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^(\(?\d{3}\)?[\s-])?\d{3}-\d{4}$"
Dim inputs() As String = {"111 111-1111", "222-2222", "222 333-444", _
"(212) 111-1111", "111-AB1-1111", _
"212-111-1111", "01 999-9999"}

For Each input As String In inputs


If Regex.IsMatch(input, pattern) Then
Console.WriteLine(input + ": matched")
Else
Console.WriteLine(input + ": match failed")
End If
Next
End Sub
End Module
' The example displays the following output:
' 111 111-1111: matched
' 222-2222: matched
' 222 333-444: match failed
' (212) 111-1111: matched
' 111-AB1-1111: match failed
' 212-111-1111: matched
' 01 999-9999: match failed

Caractère autre qu’un chiffre : \D


\D correspond à n'importe quel caractère autre qu'un chiffre. Il est équivalent au modèle d'expression régulière
\P{Nd} .

Si un comportement conforme à ECMAScript est spécifié, \D est équivalent à [^0-9] . Pour plus d’informations
sur les expressions régulières ECMAScript, consultez la section « comportement de correspondance
ECMAScript » dans Options des expressions régulières.
L'exemple suivant illustre l'élément de langage \D. Il teste si une chaîne telle qu'un numéro de référence se
compose de la combinaison appropriée de caractères décimaux et autres que des décimaux. Le modèle
d'expression régulière ^\D\d{1,5}\D*$ est défini comme indiqué dans le tableau suivant.

ÉL ÉM EN T DESC RIP T IO N

^ Commencer la correspondance au début de la chaîne


d'entrée.

\D Mettre en correspondance un caractère autre qu'un chiffre.

\d{1,5} Mettre en correspondance de un à cinq chiffres décimaux.

\D* Mettre en correspondance zéro, un ou plusieurs caractères


non décimaux.

$ Mettre en correspondance la fin de la chaîne d'entrée.


using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"^\D\d{1,5}\D*$";
string[] inputs = { "A1039C", "AA0001", "C18A", "Y938518" };

foreach (string input in inputs)


{
if (Regex.IsMatch(input, pattern))
Console.WriteLine(input + ": matched");
else
Console.WriteLine(input + ": match failed");
}
}
}
// The example displays the following output:
// A1039C: matched
// AA0001: match failed
// C18A: matched
// Y938518: match failed

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^\D\d{1,5}\D*$"
Dim inputs() As String = {"A1039C", "AA0001", "C18A", "Y938518"}

For Each input As String In inputs


If Regex.IsMatch(input, pattern) Then
Console.WriteLine(input + ": matched")
Else
Console.WriteLine(input + ": match failed")
End If
Next
End Sub
End Module
' The example displays the following output:

Catégories générales Unicode prises en charge


La norme Unicode définit les catégories générales répertoriées dans le tableau suivant. Pour plus d’informations,
consultez les sous-rubriques « Format de fichier UCD » et « Valeurs des catégories générales » dans la Base de
données de caractères Unicode.

C AT EGO RY DESC RIP T IO N

Lu Letter, Uppercase

Ll Letter, Lowercase

Lt Letter, Titlecase

Lm Letter, Modifier
C AT EGO RY DESC RIP T IO N

Lo Letter, Other

L Toutes les lettres. Cela inclut les caractères Lu , Ll , Lt ,


Lm et Lo .

Mn Mark, Nonspacing

Mc Mark, Spacing Combining

Me Mark, Enclosing

M Toutes les signes diacritiques. Cela inclut les catégories Mn ,


Mc et Me .

Nd Number, Decimal Digit

Nl Number, Letter

No Number, Other

N Tous les nombres. Cela inclut les catégories Nd , Nl et No .

Pc Punctuation, Connector

Pd Punctuation, Dash

Ps Punctuation, Open

Pe Punctuation, Close

Pi Punctuation, Initial quote (peut se comporter comme Ps ou


Pe selon l'utilisation)

Pf Punctuation, Final quote (peut se comporter comme Ps ou


Pe selon l'utilisation)

Po Punctuation, Other

P Tous les caractères de ponctuation. Cela inclut les catégories


Pc , Pd , Ps , Pe , Pi , Pf et Po .

Sm Symbol, Math

Sc Symbol, Currency

Sk Symbol, Modifier

So Symbol, Other
C AT EGO RY DESC RIP T IO N

S Tous les symboles. Cela inclut les catégories Sm , Sc , Sk


et So .

Zs Separator, Space

Zl Separator, Line

Zp Separator, Paragraph

Z Tous les caractères de séparation. Cela inclut les catégories


Zs , Zl et Zp .

Cc Other, Control

Cf Other, Format

Cs Other, Surrogate

Co Other, Private Use

Cn Other, Not Assigned (aucun caractère n'a cette propriété)

C Tous les caractères de contrôle. Cela inclut les catégories Cc ,


Cf , Cs , Co et Cn .

Vous pouvez déterminer la catégorie Unicode de n'importe quel caractère particulier en passant ce caractère à la
méthode GetUnicodeCategory. L'exemple suivant utilise la méthode GetUnicodeCategory pour déterminer la
catégorie de chaque élément dans un tableau qui contient des caractères latins sélectionnés.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
char[] chars = { 'a', 'X', '8', ',', ' ', '\u0009', '!' };

foreach (char ch in chars)


Console.WriteLine("'{0}': {1}", Regex.Escape(ch.ToString()),
Char.GetUnicodeCategory(ch));
}
}
// The example displays the following output:
// 'a': LowercaseLetter
// 'X': UppercaseLetter
// '8': DecimalDigitNumber
// ',': OtherPunctuation
// '\ ': SpaceSeparator
// '\t': Control
// '!': OtherPunctuation
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim chars() As Char = {"a"c, "X"c, "8"c, ","c, " "c, ChrW(9), "!"c}

For Each ch As Char In chars


Console.WriteLine("'{0}': {1}", Regex.Escape(ch.ToString()), _
Char.GetUnicodeCategory(ch))
Next
End Sub
End Module
' The example displays the following output:
' 'a': LowercaseLetter
' 'X': UppercaseLetter
' '8': DecimalDigitNumber
' ',': OtherPunctuation
' '\ ': SpaceSeparator
' '\t': Control
' '!': OtherPunctuation

Blocs nommés pris en charge


.NET fournit les blocs nommés répertoriés dans le tableau suivant. Le jeu de blocs nommés pris en charge est
basé sur Unicode 4.0 et Perl 5.6. Pour une expression régulière qui utilise des blocs nommés, consultez la section
Catégorie Unicode ou bloc Unicode : \p{}.

P L A GE DE P O IN T S DE C O DE N O M DU B LO C

0000 - 007F IsBasicLatin

0080 - 00FF IsLatin-1Supplement

0100 - 017F IsLatinExtended-A

0180 - 024F IsLatinExtended-B

0250 - 02AF IsIPAExtensions

02B0 - 02FF IsSpacingModifierLetters

0300 - 036F IsCombiningDiacriticalMarks

0370 - 03FF IsGreek

-ou-

IsGreekandCoptic

0400 - 04FF IsCyrillic

0500 - 052F IsCyrillicSupplement

0530 - 058F IsArmenian


P L A GE DE P O IN T S DE C O DE N O M DU B LO C

0590 - 05FF IsHebrew

0600 - 06FF IsArabic

0700 - 074F IsSyriac

0780 - 07BF IsThaana

0900 - 097F IsDevanagari

0980 - 09FF IsBengali

0A00 - 0A7F IsGurmukhi

0A80 - 0AFF IsGujarati

0B00 - 0B7F IsOriya

0B80 - 0BFF IsTamil

0C00 - 0C7F IsTelugu

0C80 - 0CFF IsKannada

0D00 - 0D7F IsMalayalam

0D80 - 0DFF IsSinhala

0E00 - 0E7F IsThai

0E80 - 0EFF IsLao

0F00 - 0FFF IsTibetan

1000 - 109F IsMyanmar

10A0 - 10FF IsGeorgian

1100 - 11FF IsHangulJamo

1200 - 137F IsEthiopic

13A0 - 13FF IsCherokee

1400 - 167F IsUnifiedCanadianAboriginalSyllabics

1680 - 169F IsOgham


P L A GE DE P O IN T S DE C O DE N O M DU B LO C

16A0 - 16FF IsRunic

1700 - 171F IsTagalog

1720 - 173F IsHanunoo

1740 - 175F IsBuhid

1760 - 177F IsTagbanwa

1780 - 17FF IsKhmer

1800 - 18AF IsMongolian

1900 - 194F IsLimbu

1950 - 197F IsTaiLe

19E0 - 19FF IsKhmerSymbols

1D00 - 1D7F IsPhoneticExtensions

1E00 - 1EFF IsLatinExtendedAdditional

1F00 - 1FFF IsGreekExtended

2000 - 206F IsGeneralPunctuation

2070 - 209F IsSuperscriptsandSubscripts

20A0 - 20CF IsCurrencySymbols

20D0 - 20FF IsCombiningDiacriticalMarksforSymbols

-ou-

IsCombiningMarksforSymbols

2100 - 214F IsLetterlikeSymbols

2150 - 218F IsNumberForms

2190 - 21FF IsArrows

2200 - 22FF IsMathematicalOperators

2300 - 23FF IsMiscellaneousTechnical

2400 - 243F IsControlPictures


P L A GE DE P O IN T S DE C O DE N O M DU B LO C

2440 - 245F IsOpticalCharacterRecognition

2460 - 24FF IsEnclosedAlphanumerics

2500 - 257F IsBoxDrawing

2580 - 259F IsBlockElements

25A0 - 25FF IsGeometricShapes

2600 - 26FF IsMiscellaneousSymbols

2700 - 27BF IsDingbats

27C0 - 27EF IsMiscellaneousMathematicalSymbols-A

27F0 - 27FF IsSupplementalArrows-A

2800 - 28FF IsBraillePatterns

2900 - 297F IsSupplementalArrows-B

2980 - 29FF IsMiscellaneousMathematicalSymbols-B

2A00 - 2AFF IsSupplementalMathematicalOperators

2B00 - 2BFF IsMiscellaneousSymbolsandArrows

2E80 - 2EFF IsCJKRadicalsSupplement

2F00 - 2FDF IsKangxiRadicals

2FF0 - 2FFF IsIdeographicDescriptionCharacters

3000 - 303F IsCJKSymbolsandPunctuation

3040 - 309F IsHiragana

30A0 - 30FF IsKatakana

3100 - 312F IsBopomofo

3130 - 318F IsHangulCompatibilityJamo

3190 - 319F IsKanbun

31A0 - 31BF IsBopomofoExtended


P L A GE DE P O IN T S DE C O DE N O M DU B LO C

31F0 - 31FF IsKatakanaPhoneticExtensions

3200 - 32FF IsEnclosedCJKLettersandMonths

3300 - 33FF IsCJKCompatibility

3400 - 4DBF IsCJKUnifiedIdeographsExtensionA

4DC0 - 4DFF IsYijingHexagramSymbols

4E00 - 9FFF IsCJKUnifiedIdeographs

A000 - A48F IsYiSyllables

A490 - A4CF IsYiRadicals

AC00 - D7AF IsHangulSyllables

D800 - DB7F IsHighSurrogates

DB80 - DBFF IsHighPrivateUseSurrogates

DC00 - DFFF IsLowSurrogates

E000 - F8FF IsPrivateUse ou IsPrivateUseArea

F900 - FAFF IsCJKCompatibilityIdeographs

FB00 - FB4F IsAlphabeticPresentationForms

FB50 - FDFF IsArabicPresentationForms-A

FE00 - FE0F IsVariationSelectors

FE20 - FE2F IsCombiningHalfMarks

FE30 - FE4F IsCJKCompatibilityForms

FE50 - FE6F IsSmallFormVariants

FE70 - FEFF IsArabicPresentationForms-B

FF00 - FFEF IsHalfwidthandFullwidthForms

FFF0 - FFFF IsSpecials

Soustraction de classe de caractères : [groupe_base - [groupe_exclu]]


Une classe de caractères définit un jeu de caractères. La soustraction de classe de caractères produit un jeu de
caractères qui est le résultat de l'exclusion des caractères d'une classe de caractères d'une autre classe de
caractères.
Une expression de soustraction de classe de caractères a la forme suivante :
[ base_group -[ excluded_group ]]
Les crochets ( [] ) et le trait d'union ( - ) sont obligatoires. Le base_group est un groupe de caractères positif ou
un groupe de caractères négatif. Le composant groupe_exclu est un autre groupe de caractères positif ou négatif,
ou une autre expression de soustraction de classe de caractères (autrement dit, vous pouvez imbriquer des
expressions de soustraction de classe de caractères).
Par exemple, supposons que vous disposiez d'un groupe de base composé de la plage de caractères « a » à « z ».
Pour définir le jeu de caractères composé du groupe de base, à l'exception du caractère « m », utilisez [a-z-[m]] .
Pour définir le jeu de caractères composé du groupe de base, à l'exception du jeu de caractères « d », « j » et
« p », utilisez [a-z-[djp]] . Pour définir le jeu de caractères composé du groupe de base, à l'exception de la plage
de caractères allant de « m » à « p », utilisez [a-z-[m-p]] .
Prenons l'exemple de l'expression de soustraction de classe de caractères imbriquée, [a-z-[d-w-[m-o]]] .
L'expression est évaluée à partir de la plage de caractères la plus profonde, vers l'extérieur. Tout d'abord, la plage
de caractères « m » à « o » est soustraite de la plage de caractères « d » à « w », ce qui génère le jeu de caractères
« d » à « l » et « p » à « w ». Ce jeu est ensuite soustrait de la plage de caractères allant de « a » à « z », ce qui
produit le jeu de caractères [abcmnoxyz] .
Vous pouvez utiliser n'importe quelle classe de caractères avec la soustraction de classe de caractères. Pour
définir le jeu de caractères qui se compose de tous les caractères Unicode de \u0000 à \uFFFF, à l'exception des
espaces ( \s ), les caractères de la catégorie générale de ponctuation ( \p{P} ), les caractères du bloc nommé
IsGreek ( \p{IsGreek} ) et le caractère de contrôle Unicode LIGNE SUIVANTE (\x85), utilisez
[\u0000-\uFFFF-[\s\p{P}\p{IsGreek}\x85]] .

Choisissez des classes de caractères pour une expression de soustraction de classe de caractères qui produira
des résultats utiles. Évitez toute expression produisant un jeu de caractères vide, n'ayant aucune correspondance
ou toute expression équivalant au groupe de base d'origine. Par exemple, le jeu vide résulte de l'expression
[\p{IsBasicLatin}-[\x00-\x7F]] , qui soustrait tous les caractères compris dans la plage de caractères
IsBasicLatin de la catégorie générale IsBasicLatin . De même, le groupe de base d'origine résulte de
l'expression [a-z-[0-9]] . Cela est dû au fait que le groupe de base, c'est-à-dire la plage de caractères des lettres
de « a » à « z », ne contient aucun caractère dans le groupe exclu, c'est-à-dire la plage de caractères des chiffres
décimaux de « 0 » à « 9 ».
L'exemple suivant définit une expression régulière, ^[0-9-[2468]]+$ , qui correspond à zéro et aux nombres
impairs d'une chaîne d'entrée. L'expression régulière est interprétée comme indiqué dans le tableau suivant.

ÉL ÉM EN T DESC RIP T IO N

^ Commencer la correspondance au démarrage de la chaîne


d'entrée.

[0-9-[2468]]+ Mettre en correspondance une ou plusieurs occurrences de


n'importe quel caractère de 0 à 9 à l'exception de 2, 4, 6 et 8.
En d'autres termes, mettre en correspondance une ou
plusieurs occurrences de zéro ou d'un chiffre impair.

$ Terminer la correspondance à la fin de la chaîne d'entrée.


using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "123", "13579753", "3557798", "335599901" };
string pattern = @"^[0-9-[2468]]+$";

foreach (string input in inputs)


{
Match match = Regex.Match(input, pattern);
if (match.Success)
Console.WriteLine(match.Value);
}
}
}
// The example displays the following output:
// 13579753
// 335599901

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"123", "13579753", "3557798", "335599901"}
Dim pattern As String = "^[0-9-[2468]]+$"

For Each input As String In inputs


Dim match As Match = Regex.Match(input, pattern)
If match.Success Then Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' 13579753
' 335599901

Voir aussi
GetUnicodeCategory
Langage des expressions régulières - Aide-mémoire
Options des expressions régulières
Ancres dans les expressions régulières
18/07/2020 • 30 minutes to read • Edit Online

Les ancres, ou assertions atomiques de largeur nulle, spécifient une position dans la chaîne où une
correspondance doit se produire. Quand vous utilisez une ancre dans votre expression de recherche, le moteur
des expressions régulières n'avance pas dans la chaîne ou ne consomme pas de caractères ; il recherche
uniquement une correspondance à la position spécifiée. Par exemple, ^ spécifie que la correspondance doit
commencer au début d'une ligne ou d'une chaîne. Par conséquent, l'expression régulière ^http: correspond
uniquement à « http: » quand elle se produit au début d'une ligne. Le tableau suivant répertorie les ancres prises
en charge par les expressions régulières dans .NET.

A N C RE DESC RIP T IO N

^ Par défaut, la correspondance doit se produire au début de la


chaîne ; en mode multiligne, elle doit se produire au début de
la ligne. Pour plus d'informations, consultez Début de chaîne
ou de ligne.

$ Par défaut, la correspondance doit se produire à la fin de la


chaîne ou avant \n à la fin de la chaîne ; en mode multiligne,
elle doit se produire à la fin de la ligne ou avant \n à la fin
de la ligne. Pour plus d'informations, consultez Fin de chaîne
ou de ligne.

\A La correspondance doit se produire au début de la chaîne


uniquement (aucun support multiligne). Pour plus
d'informations, consultez Début de chaîne uniquement.

\Z La correspondance doit se produire à la fin de la chaîne, ou


avant \n à la fin de la chaîne. Pour plus d'informations,
consultez Fin de chaîne ou avant un saut de ligne final.

\z La correspondance doit se produire uniquement à la fin de la


chaîne. Pour plus d'informations, consultez Fin de chaîne
uniquement.

\G La correspondance doit démarrer à la position où la


correspondance précédente s'est terminée. Pour plus
d'informations, consultez Correspondances contiguës.

\b La correspondance doit se produire à la limite d'un mot. Pour


plus d'informations, consultez Limite de mot.

\B La correspondance ne doit pas se produire à la limite d'un


mot. Pour plus d'informations, consultez Limite n'appartenant
pas à un mot.

Début de chaîne ou de ligne : ^


Par défaut, l’ancre ^ spécifie que le modèle suivant doit commencer à la première position de caractère de la
chaîne. Si vous utilisez ^ avec l'option RegexOptions.Multiline (consultez Options des expressions régulières), la
correspondance doit se trouver au début de chaque ligne.
L'exemple suivant utilise l'ancre ^ dans une expression régulière qui extrait des informations à propos des
années pendant lesquelles certaines équipes de base-ball professionnelles ont existé. L'exemple appelle deux
surcharges de la méthode Regex.Matches :
L'appel à la surcharge Matches(String, String) recherche uniquement la première sous-chaîne dans la
chaîne d'entrée qui correspond au modèle d'expression régulière.
L'appel à la surcharge Matches(String, String, RegexOptions) avec le paramètre options défini avec la
valeur RegexOptions.Multiline trouve les cinq sous-chaînes.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957\n" +
"Chicago Cubs, National League, 1903-present\n" +
"Detroit Tigers, American League, 1901-present\n" +
"New York Giants, National League, 1885-1957\n" +
"Washington Senators, American League, 1901-1960\n";
string pattern = @"^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+";
Match match;

match = Regex.Match(input, pattern);


while (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);

Console.WriteLine(".");
match = match.NextMatch();
}
Console.WriteLine();

match = Regex.Match(input, pattern, RegexOptions.Multiline);


while (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);

Console.WriteLine(".");
match = match.NextMatch();
}
Console.WriteLine();
}
}
// The example displays the following output:
// The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
//
// The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
// The Chicago Cubs played in the National League in 1903-present.
// The Detroit Tigers played in the American League in 1901-present.
// The New York Giants played in the National League in 1885-1957.
// The Washington Senators played in the American League in 1901-1960.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957" + vbCrLf +
"Chicago Cubs, National League, 1903-present" + vbCrLf +
"Detroit Tigers, American League, 1901-present" + vbCrLf +
"New York Giants, National League, 1885-1957" + vbCrLf +
"Washington Senators, American League, 1901-1960" + vbCrLf

Dim pattern As String = "^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+"


Dim match As Match

match = Regex.Match(input, pattern)


Do While match.Success
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")
match = match.NextMatch()
Loop
Console.WriteLine()

match = Regex.Match(input, pattern, RegexOptions.Multiline)


Do While match.Success
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")
match = match.NextMatch()
Loop
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
'
' The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
' The Chicago Cubs played in the National League in 1903-present.
' The Detroit Tigers played in the American League in 1901-present.
' The New York Giants played in the National League in 1885-1957.
' The Washington Senators played in the American League in 1901-1960.

Le modèle d'expression régulière ^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+ est défini


comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

^ Commencer la correspondance au début de la chaîne d'entrée


(ou au début de la ligne si la méthode est appelée avec
l'option RegexOptions.Multiline ).

((\w+(\s?)){2,} Mettre en correspondance un ou plusieurs caractères de mot


suivis de zéro ou d’un espace au moins deux fois. Il s'agit du
premier groupe de capture. Cette expression définit
également un deuxième et un troisième groupe de capture :
le deuxième se compose du mot capturé, et le troisième est
constitué de l’espace blanc capturé.
M O DÈL E DESC RIP T IO N

,\s Mettre en correspondance une virgule suivie d'un caractère


d'espace blanc.

(\w+\s\w+) Mettre en correspondance un ou plusieurs caractères de mot


suivis d'un espace, suivi d'un ou plusieurs caractères de mot. Il
s'agit du quatrième groupe de capture.

, Mettre en correspondance une virgule.

\s\d{4} Mettre en correspondance un espace suivi de quatre chiffres


décimaux.

(-(\d{4}|present))? Mettre en correspondance zéro ou une occurrence d'un trait


d'union suivie de quatre chiffres décimaux ou de la chaîne
« present ». Il s'agit du sixième groupe de capture. Il inclut
également un septième groupe de capture.

,? Mettre en correspondance zéro ou une occurrence d'une


virgule.

(\s\d{4}(-(\d{4}|present))?,?)+ Mettre en correspondance une ou plusieurs occurrences des


éléments suivants : un espace, quatre chiffres décimaux, zéro
ou une occurrence d'un trait d'union suivie de quatre chiffres
décimaux ou de la chaîne « present » et zéro ou une virgule. Il
s'agit du cinquième groupe de capture.

Fin de chaîne ou de ligne : $


L'ancre $ spécifie que le modèle précédent doit se produire à la fin de la chaîne d'entrée ou avant \n à la fin de
la chaîne d'entrée.
Si vous utilisez $ avec l'option RegexOptions.Multiline , la correspondance peut également se trouver à la fin
d'une ligne. Notez que $ correspond à \n , mais ne correspond pas à \r\n (combinaison de caractères de
retour chariot et de saut de ligne ou CR/LF). Pour établir une correspondance avec la combinaison de caractères
CR/LF, incluez \r?$ dans le modèle d'expression régulière.
L'exemple suivant ajoute l'ancre $ au modèle d'expression régulière utilisé dans l'exemple dans la section Début
de chaîne ou de ligne . En cas d'utilisation avec la chaîne d'entrée d'origine, qui inclut cinq lignes de texte, la
méthode Regex.Matches(String, String) ne peut pas trouver de correspondance, parce que la fin de la première
ligne ne correspond pas au modèle $ . Quand la chaîne d'entrée d'origine est fractionnée dans un tableau de
chaînes, la méthode Regex.Matches(String, String) réussit à faire correspondre chacune des cinq lignes. Quand la
méthode Regex.Matches(String, String, RegexOptions) est appelée avec le paramètre options défini avec la valeur
RegexOptions.Multiline, aucune correspondance n'est trouvée parce que le modèle d'expression régulière ne
représente pas l'élément de retour chariot (\u+000D). Toutefois, quand le modèle d'expression régulière est
modifié par le remplacement de $ par \r?$ , l'appel de la méthode Regex.Matches(String, String, RegexOptions)
avec le paramètre options défini avec la valeur RegexOptions.Multiline trouve encore cinq correspondances.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string cr = Environment.NewLine;
string cr = Environment.NewLine;
string input = "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957" + cr +
"Chicago Cubs, National League, 1903-present" + cr +
"Detroit Tigers, American League, 1901-present" + cr +
"New York Giants, National League, 1885-1957" + cr +
"Washington Senators, American League, 1901-1960" + cr;
Match match;

string basePattern = @"^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+";


string pattern = basePattern + "$";
Console.WriteLine("Attempting to match the entire input string:");
match = Regex.Match(input, pattern);
while (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);

Console.WriteLine(".");
match = match.NextMatch();
}
Console.WriteLine();

string[] teams = input.Split(new String[] { cr }, StringSplitOptions.RemoveEmptyEntries);


Console.WriteLine("Attempting to match each element in a string array:");
foreach (string team in teams)
{
match = Regex.Match(team, pattern);
if (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);
Console.WriteLine(".");
}
}
Console.WriteLine();

Console.WriteLine("Attempting to match each line of an input string with '$':");


match = Regex.Match(input, pattern, RegexOptions.Multiline);
while (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);

Console.WriteLine(".");
match = match.NextMatch();
}
Console.WriteLine();

pattern = basePattern + "\r?$";


Console.WriteLine(@"Attempting to match each line of an input string with '\r?$':");
match = Regex.Match(input, pattern, RegexOptions.Multiline);
while (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);

Console.WriteLine(".");
match = match.NextMatch();
}
Console.WriteLine();
}
}
// The example displays the following output:
// Attempting to match the entire input string:
//
// Attempting to match each element in a string array:
// The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
// The Chicago Cubs played in the National League in 1903-present.
// The Detroit Tigers played in the American League in 1901-present.
// The New York Giants played in the National League in 1885-1957.
// The Washington Senators played in the American League in 1901-1960.
//
// Attempting to match each line of an input string with '$':
//
// Attempting to match each line of an input string with '\r?$':
// The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
// The Chicago Cubs played in the National League in 1903-present.
// The Detroit Tigers played in the American League in 1901-present.
// The New York Giants played in the National League in 1885-1957.
// The Washington Senators played in the American League in 1901-1960.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957" + vbCrLf +
"Chicago Cubs, National League, 1903-present" + vbCrLf +
"Detroit Tigers, American League, 1901-present" + vbCrLf +
"New York Giants, National League, 1885-1957" + vbCrLf +
"Washington Senators, American League, 1901-1960" + vbCrLf

Dim basePattern As String = "^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+"


Dim match As Match

Dim pattern As String = basePattern + "$"


Console.WriteLine("Attempting to match the entire input string:")
match = Regex.Match(input, pattern)
Do While match.Success
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")
match = match.NextMatch()
Loop
Console.WriteLine()

Dim teams() As String = input.Split(New String() {vbCrLf}, StringSplitOptions.RemoveEmptyEntries)


Console.WriteLine("Attempting to match each element in a string array:")
For Each team As String In teams
match = Regex.Match(team, pattern)
If match.Success Then
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")
End If
Next
Console.WriteLine()

Console.WriteLine("Attempting to match each line of an input string with '$':")


match = Regex.Match(input, pattern, RegexOptions.Multiline)
Do While match.Success
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")
match = match.NextMatch()
Loop
Console.WriteLine()

pattern = basePattern + "\r?$"


Console.WriteLine("Attempting to match each line of an input string with '\r?$':")
match = Regex.Match(input, pattern, RegexOptions.Multiline)
Do While match.Success
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")

match = match.NextMatch()
Loop
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' Attempting to match the entire input string:
'
' Attempting to match each element in a string array:
' The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
' The Chicago Cubs played in the National League in 1903-present.
' The Detroit Tigers played in the American League in 1901-present.
' The New York Giants played in the National League in 1885-1957.
' The Washington Senators played in the American League in 1901-1960.
'
' Attempting to match each line of an input string with '$':
'
' Attempting to match each line of an input string with '\r?$':
' The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.
' The Chicago Cubs played in the National League in 1903-present.
' The Detroit Tigers played in the American League in 1901-present.
' The New York Giants played in the National League in 1885-1957.
' The Washington Senators played in the American League in 1901-1960.

Début de chaîne uniquement : \A


L'ancre \A spécifie qu'une correspondance doit se produire au début de la chaîne d'entrée. Elle est identique à
l'ancre ^ , à la différence près que l'ancre \A ignore l'option RegexOptions.Multiline . Par conséquent, elle peut
correspondre uniquement au début de la première ligne dans une chaîne d'entrée multiligne.
L'exemple suivant est semblable aux exemples des ancres ^ et $ . Il utilise l'ancre \A dans une expression
régulière qui extrait des informations sur les années où jouaient certaines équipes de base-ball professionnelles.
La chaîne d'entrée inclut cinq lignes. L'appel à la méthode Regex.Matches(String, String, RegexOptions) recherche
uniquement la première sous-chaîne dans la chaîne d'entrée qui correspond au modèle d'expression régulière.
Comme le montre l'exemple, l'option Multiline n'a aucun effet.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957\n" +
"Chicago Cubs, National League, 1903-present\n" +
"Detroit Tigers, American League, 1901-present\n" +
"New York Giants, National League, 1885-1957\n" +
"Washington Senators, American League, 1901-1960\n";

string pattern = @"\A((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+";

Match match = Regex.Match(input, pattern, RegexOptions.Multiline);


while (match.Success)
{
Console.Write("The {0} played in the {1} in",
match.Groups[1].Value, match.Groups[4].Value);
foreach (Capture capture in match.Groups[5].Captures)
Console.Write(capture.Value);

Console.WriteLine(".");
match = match.NextMatch();
}
Console.WriteLine();
}
}
// The example displays the following output:
// The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957" + vbCrLf +
"Chicago Cubs, National League, 1903-present" + vbCrLf +
"Detroit Tigers, American League, 1901-present" + vbCrLf +
"New York Giants, National League, 1885-1957" + vbCrLf +
"Washington Senators, American League, 1901-1960" + vbCrLf

Dim pattern As String = "\A((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+"

Dim match As Match = Regex.Match(input, pattern, RegexOptions.Multiline)


Do While match.Success
Console.Write("The {0} played in the {1} in",
match.Groups(1).Value, match.Groups(4).Value)
For Each capture As Capture In match.Groups(5).Captures
Console.Write(capture.Value)
Next
Console.WriteLine(".")
match = match.NextMatch()
Loop
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' The Brooklyn Dodgers played in the National League in 1911, 1912, 1932-1957.

Fin de chaîne ou avant un saut de ligne final : \Z


L'ancre \Z spécifie qu'une correspondance doit se produire à la fin de la chaîne d'entrée ou avant \n à la fin de
la chaîne d'entrée. Elle est identique à l'ancre $ , à la différence près que l'ancre \Z ignore l'option
RegexOptions.Multiline . Par conséquent, dans une chaîne multiligne, elle peut correspondre uniquement à la fin
de la dernière ligne ou à la dernière ligne avant \n .
Notez que \Z correspond à \n , mais ne correspond pas à \r\n (combinaison de caractères CR/LF). Pour
établir une correspondance avec les caractères CR/LF, incluez \r?\Z dans le modèle d'expression régulière.
L'exemple suivant utilise l'ancre \Z dans une expression régulière qui est semblable à l'exemple dans la section
Début de chaîne ou de ligne , qui extrait des informations à propos des années pendant lesquelles certaines
équipes de base-ball professionnelles ont existé. La sous-expression \r?\Z dans l'expression régulière
^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+\r?\Z correspond à la fin d'une chaîne et
correspond également à une chaîne qui se termine par \n ou \r\n . Par conséquent, chaque élément du tableau
correspond au modèle d’expression régulière.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957",
"Chicago Cubs, National League, 1903-present" + Environment.NewLine,
"Detroit Tigers, American League, 1901-present" + Regex.Unescape(@"\n"),
"New York Giants, National League, 1885-1957",
"Washington Senators, American League, 1901-1960" + Environment.NewLine};
string pattern = @"^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+\r?\Z";

foreach (string input in inputs)


{
Console.WriteLine(Regex.Escape(input));
Match match = Regex.Match(input, pattern);
if (match.Success)
Console.WriteLine(" Match succeeded.");
else
Console.WriteLine(" Match failed.");
}
}
}
// The example displays the following output:
// Brooklyn\ Dodgers,\ National\ League,\ 1911,\ 1912,\ 1932-1957
// Match succeeded.
// Chicago\ Cubs,\ National\ League,\ 1903-present\r\n
// Match succeeded.
// Detroit\ Tigers,\ American\ League,\ 1901-present\n
// Match succeeded.
// New\ York\ Giants,\ National\ League,\ 1885-1957
// Match succeeded.
// Washington\ Senators,\ American\ League,\ 1901-1960\r\n
// Match succeeded.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"Brooklyn Dodgers, National League, 1911, 1912, 1932-1957",
"Chicago Cubs, National League, 1903-present" + vbCrLf,
"Detroit Tigers, American League, 1901-present" + vbLf,
"New York Giants, National League, 1885-1957",
"Washington Senators, American League, 1901-1960" + vbCrLf}
Dim pattern As String = "^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+\r?\Z"

For Each input As String In inputs


Console.WriteLine(Regex.Escape(input))
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine(" Match succeeded.")
Else
Console.WriteLine(" Match failed.")
End If
Next
End Sub
End Module
' The example displays the following output:
' Brooklyn\ Dodgers,\ National\ League,\ 1911,\ 1912,\ 1932-1957
' Match succeeded.
' Chicago\ Cubs,\ National\ League,\ 1903-present\r\n
' Match succeeded.
' Detroit\ Tigers,\ American\ League,\ 1901-present\n
' Match succeeded.
' New\ York\ Giants,\ National\ League,\ 1885-1957
' Match succeeded.
' Washington\ Senators,\ American\ League,\ 1901-1960\r\n
' Match succeeded.

Fin de chaîne uniquement : \z


L'ancre \z spécifie qu'une correspondance doit se produire à la fin de la chaîne d'entrée. Comme l'élément de
langage $ , \z ignore l'option RegexOptions.Multiline . Contrairement à l'élément de langage \Z , \z ne
correspond pas à un caractère \n à la fin d'une chaîne. Par conséquent, elle peut correspondre uniquement à la
dernière ligne de la chaîne d'entrée.
L'exemple suivant utilise l'ancre \z dans une expression régulière qui est sinon identique à l'exemple dans la
section précédente, qui extrait des informations à propos des années pendant lesquelles certaines équipes de
base-ball professionnelles ont existé. L'exemple essaie de faire correspondre chacun des cinq éléments dans un
tableau de chaînes avec le modèle d'expression régulière
^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+\r?\z . Deux des chaînes se terminent par un
retour chariot et des caractères de saut de ligne, l'une se termine par un caractère de saut de ligne, et deux ne se
terminent ni par un retour chariot ni par un caractère de saut de ligne. Comme le montre la sortie, seules les
chaînes sans retour chariot ou caractère de saut de ligne correspondent au modèle.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "Brooklyn Dodgers, National League, 1911, 1912, 1932-1957",
"Chicago Cubs, National League, 1903-present" + Environment.NewLine,
"Detroit Tigers, American League, 1901-present\n",
"New York Giants, National League, 1885-1957",
"Washington Senators, American League, 1901-1960" + Environment.NewLine };
string pattern = @"^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+\r?\z";

foreach (string input in inputs)


{
Console.WriteLine(Regex.Escape(input));
Match match = Regex.Match(input, pattern);
if (match.Success)
Console.WriteLine(" Match succeeded.");
else
Console.WriteLine(" Match failed.");
}
}
}
// The example displays the following output:
// Brooklyn\ Dodgers,\ National\ League,\ 1911,\ 1912,\ 1932-1957
// Match succeeded.
// Chicago\ Cubs,\ National\ League,\ 1903-present\r\n
// Match failed.
// Detroit\ Tigers,\ American\ League,\ 1901-present\n
// Match failed.
// New\ York\ Giants,\ National\ League,\ 1885-1957
// Match succeeded.
// Washington\ Senators,\ American\ League,\ 1901-1960\r\n
// Match failed.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"Brooklyn Dodgers, National League, 1911, 1912, 1932-1957",
"Chicago Cubs, National League, 1903-present" + vbCrLf,
"Detroit Tigers, American League, 1901-present" + vbLf,
"New York Giants, National League, 1885-1957",
"Washington Senators, American League, 1901-1960" + vbCrLf}
Dim pattern As String = "^((\w+(\s?)){2,}),\s(\w+\s\w+),(\s\d{4}(-(\d{4}|present))?,?)+\r?\z"

For Each input As String In inputs


Console.WriteLine(Regex.Escape(input))
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine(" Match succeeded.")
Else
Console.WriteLine(" Match failed.")
End If
Next
End Sub
End Module
' The example displays the following output:
' Brooklyn\ Dodgers,\ National\ League,\ 1911,\ 1912,\ 1932-1957
' Match succeeded.
' Chicago\ Cubs,\ National\ League,\ 1903-present\r\n
' Match failed.
' Detroit\ Tigers,\ American\ League,\ 1901-present\n
' Match failed.
' New\ York\ Giants,\ National\ League,\ 1885-1957
' Match succeeded.
' Washington\ Senators,\ American\ League,\ 1901-1960\r\n
' Match failed.

Correspondances contiguës : \G
L'ancre \G spécifie qu'une correspondance doit se produire au point où la correspondance précédente s'est
terminée. Quand vous utilisez cette ancre avec la méthode Regex.Matches ou Match.NextMatch , elle vérifie que
toutes les correspondances sont contiguës.
L'exemple suivant utilise une expression régulière pour extraire les noms d'espèces de rongeurs d'une chaîne
délimitée par des virgules.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "capybara,squirrel,chipmunk,porcupine,gopher," +
"beaver,groundhog,hamster,guinea pig,gerbil," +
"chinchilla,prairie dog,mouse,rat";
string pattern = @"\G(\w+\s?\w*),?";
Match match = Regex.Match(input, pattern);
while (match.Success)
{
Console.WriteLine(match.Groups[1].Value);
match = match.NextMatch();
}
}
}
// The example displays the following output:
// capybara
// squirrel
// chipmunk
// porcupine
// gopher
// beaver
// groundhog
// hamster
// guinea pig
// gerbil
// chinchilla
// prairie dog
// mouse
// rat

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "capybara,squirrel,chipmunk,porcupine,gopher," +
"beaver,groundhog,hamster,guinea pig,gerbil," +
"chinchilla,prairie dog,mouse,rat"
Dim pattern As String = "\G(\w+\s?\w*),?"
Dim match As Match = Regex.Match(input, pattern)
Do While match.Success
Console.WriteLine(match.Groups(1).Value)
match = match.NextMatch()
Loop
End Sub
End Module
' The example displays the following output:
' capybara
' squirrel
' chipmunk
' porcupine
' gopher
' beaver
' groundhog
' hamster
' guinea pig
' gerbil
' chinchilla
' prairie dog
' mouse
' rat
L'expression régulière \G(\w+\s?\w*),? est interprétée comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\G Commencer là où la dernière correspondance s'est terminée.

\w+ Mettre en correspondance un ou plusieurs caractères


alphabétiques.

\s? Mettre en correspondance zéro ou un espace.

\w* Mettre en correspondance zéro, un ou plusieurs caractères


alphabétiques.

(\w+\s?\w*) Mettre en correspondance un ou plusieurs caractères de mot


suivis de zéro ou d'un espace, suivi de zéro ou davantage de
caractères de mot. Il s'agit du premier groupe de capture.

,? Mettre en correspondance zéro ou une occurrence d'une


virgule littérale.

Limite de mot : \b
L'ancre \b spécifie que la correspondance doit se produire à la limite entre un caractère de mot (élément de
langage \w ) et un caractère n'appartenant pas à un mot (élément de langage \W ). Les caractères de mot se
composent de caractères alphanumériques et de traits de soulignement ; un caractère n'appartenant pas à un mot
est un caractère qui n'est pas alphanumérique ou qui n'est pas un trait de soulignement. (Pour plus
d’informations, consultez classes de caractères.) La correspondance peut également se produire à la limite d’un
mot au début ou à la fin de la chaîne.
L'ancre \b est fréquemment utilisée pour faire en sorte qu'une sous-expression corresponde à un mot entier
plutôt qu'au début ou à la fin d'un mot uniquement. L'expression régulière \bare\w*\b dans l'exemple suivant
illustre cette utilisation. Elle correspond à tout mot qui commence par la sous-chaîne « are ». La sortie de
l'exemple illustre également que \b correspond à la fois au début et la fin de la chaîne d'entrée.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "area bare arena mare";
string pattern = @"\bare\w*\b";
Console.WriteLine("Words that begin with 'are':");
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}",
match.Value, match.Index);
}
}
// The example displays the following output:
// Words that begin with 'are':
// 'area' found at position 0
// 'arena' found at position 10
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "area bare arena mare"
Dim pattern As String = "\bare\w*\b"
Console.WriteLine("Words that begin with 'are':")
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}",
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Words that begin with 'are':
' 'area' found at position 0
' 'arena' found at position 10

Le modèle d'expression régulière est interprété comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

are Mettre en correspondance la sous-chaîne « are ».

\w* Mettre en correspondance zéro, un ou plusieurs caractères


alphabétiques.

\b Terminer la correspondance à la limite d'un mot.

Limite n'appartenant pas à un mot : \B


L'ancre \B spécifie que la correspondance ne doit pas se produire à la limite d'un mot. Elle est le contraire de
l'ancre \b .
L'exemple suivant utilise l'ancre \B pour trouver des occurrences de la sous-chaîne « qu » dans un mot. Le
modèle d'expression régulière \Bqu\w+ met en correspondance une sous-chaîne qui commence par un « qu » qui
n'est pas en début de mot et continue jusqu'à la fin du mot.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "equity queen equip acquaint quiet";
string pattern = @"\Bqu\w+";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}",
match.Value, match.Index);
}
}
// The example displays the following output:
// 'quity' found at position 1
// 'quip' found at position 14
// 'quaint' found at position 21
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "equity queen equip acquaint quiet"
Dim pattern As String = "\Bqu\w+"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}",
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' 'quity' found at position 1
' 'quip' found at position 14
' 'quaint' found at position 21

Le modèle d'expression régulière est interprété comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\B Ne pas commencer la correspondance à la limite d'un mot.

qu Mettre en correspondance la sous-chaîne « qu ».

\w+ Mettre en correspondance un ou plusieurs caractères


alphabétiques.

Voir aussi
Langage des expressions régulières - Aide-mémoire
Options des expressions régulières
Constructions de regroupement dans les
expressions régulières
18/07/2020 • 65 minutes to read • Edit Online

Les constructions de regroupement délimitent les sous-expressions d'une expression régulière et capturent
les sous-chaînes d'une chaîne d'entrée. Utilisez les constructions de regroupement pour effectuer les
opérations suivantes :
Mettre en correspondance une sous-expression qui est répétée dans la chaîne d'entrée.
Appliquer un quantificateur à une sous-expression qui possède plusieurs éléments de langage
d'expression régulière. Pour plus d'informations sur les quantificateurs, voir Quantifiers.
Inclure une sous-expression dans la chaîne retournée par les méthodes Regex.Replace et Match.Result .
Récupérer des sous-expressions spécifiques de la propriété Match.Groups et les traiter séparément du
texte global mis en correspondance.
Le tableau suivant répertorie les constructions de regroupement prises en charge par le moteur d’expression
régulière de .NET et indique si ce sont des constructions avec ou sans capture.

C O N ST RUC T IO N DE REGRO UP EM EN T AVEC O U SA N S C A P T URE

Sous-expressions mises en correspondance Capture

Sous-expressions mises en correspondance nommées Capture

Définitions de groupe d'équilibrage Capture

Groupes sans capture Sans capture

Options de groupe Sans capture

Assertions de préanalyse positive de largeur nulle Sans capture

Assertions de préanalyse négative de largeur nulle Sans capture

Assertions de postanalyse positive de largeur nulle Sans capture

Assertions de postanalyse négative de largeur nulle Sans capture

Groupes atomiques Sans capture

Pour plus d’informations sur les groupes et le modèle objet d’expression régulière, consultez constructions de
regroupement et objets d’expression régulière.

Sous-expressions mises en correspondance


La construction de regroupement suivante capture une sous-expression mise en correspondance :
( sous- expression )
où subexpression représente un modèle d'expression régulière valide. Les captures qui utilisent des
parenthèses sont numérotées automatiquement de la gauche vers la droite en fonction de l'ordre des
parenthèses ouvrantes dans l'expression régulière, à partir de 1. La capture numérotée 0 représente le texte
mis en correspondance par le modèle d'expression régulière entier.

NOTE
Par défaut, l’élément de langage sous- ( expression ) capture la sous-expression mise en correspondance. Toutefois,
si le paramètre RegexOptions d'une méthode de mise en correspondance de modèle d'expression régulière comprend
l'indicateur RegexOptions.ExplicitCapture ou que l'option n soit appliquée à cette sous-expression (voir Options de
groupe plus loin dans cette rubrique), la sous-expression mise en correspondance n'est pas capturée.

Vous pouvez accéder aux groupes capturés de quatre façons :


En utilisant la construction de référence arrière dans l'expression régulière. La sous-expression mise en
correspondance est référencée dans la même expression régulière en utilisant la syntaxe \ Number,
où Number est le nombre ordinal de la sous-expression capturée.
En utilisant la construction de référence arrière nommée dans l'expression régulière. La sous-
expression mise en correspondance est référencée dans la même expression régulière en utilisant la
syntaxe \k< nom > , où nom est le nom d’un groupe de capture, ou \k< nombre > , où nombre est
le nombre ordinal d’un groupe de capture. Un groupe de capture possède un nom par défaut qui est
identique à son nombre ordinal. Pour plus d'informations, voir Sous-expressions mises en
correspondance nommées plus loin dans cette rubrique.
En utilisant la séquence de remplacement $ nombre dans un appel de méthode Regex.Replace ou
Match.Result , où nombre est le nombre ordinal de la sous-expression capturée.
Par programmation, en utilisant l'objet GroupCollection retourné par la propriété Match.Groups . Le
membre situé à la position zéro dans la collection représente la correspondance de l’expression
régulière entière. Chaque membre suivant représente une sous-expression mise en correspondance.
Pour plus d'informations, voir la section Grouping Constructs and Regular Expression Objects .
L'exemple suivant illustre une expression régulière qui identifie des mots en double dans le texte. Les deux
groupes de capture du modèle d'expression régulière représentent les deux instances du mot en double. La
capture de la seconde instance permet d'indiquer la position de départ du mot dans la chaîne d'entrée.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(\w+)\s(\1)";
string input = "He said that that was the the correct answer.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("Duplicate '{0}' found at positions {1} and {2}.",
match.Groups[1].Value, match.Groups[1].Index, match.Groups[2].Index);
}
}
// The example displays the following output:
// Duplicate 'that' found at positions 8 and 13.
// Duplicate 'the' found at positions 22 and 26.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(\w+)\s(\1)\W"
Dim input As String = "He said that that was the the correct answer."
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine("Duplicate '{0}' found at positions {1} and {2}.", _
match.Groups(1).Value, match.Groups(1).Index, match.Groups(2).Index)
Next
End Sub
End Module
' The example displays the following output:
' Duplicate 'that' found at positions 8 and 13.
' Duplicate 'the' found at positions 22 and 26.

Le modèle d'expression régulière est le suivant :


(\w+)\s(\1)\W

Le tableau suivant montre comment le modèle d'expression régulière est interprété.

M O DÈL E DESC RIP T IO N

(\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques. Il s'agit du premier groupe de capture.

\s Mettre en correspondance un espace blanc.

(\1) Mettre en correspondance la chaîne dans le premier


groupe capturé. Il s'agit du deuxième groupe de capture.
L'exemple l'affecte à un groupe capturé pour que la
position de départ du mot en double puisse être récupérée
de la propriété Match.Index .

\W Mettre en correspondance un caractère n'appartenant pas


à un mot, comme un espace blanc ou un signe de
ponctuation. Cela empêche le modèle d'expression
régulière de mettre en correspondance un mot qui
commence par le mot récupéré du premier groupe capturé.

Sous-expressions mises en correspondance nommées


La construction de regroupement suivante capture une sous-expression mise en correspondance et vous
permet d'y accéder à partir d'un nom ou d'un nombre :
(?<name>subexpression)

ou :
(?'name'subexpression)

où name est un nom de groupe valide, et subexpression représente un modèle d'expression régulière valide.
name ne doit pas contenir de caractères de ponctuation et ne peut pas commencer par un nombre.
NOTE
Si le paramètre RegexOptions d'une méthode de mise en correspondance de modèle d'expression régulière comprend
l'indicateur RegexOptions.ExplicitCapture ou que l'option n soit appliquée à cette sous-expression (voir Options de
groupe plus loin dans cette rubrique), la seule façon de capturer une sous-expression consiste à nommer explicitement
des groupes de capture.

Vous pouvez accéder aux groupes capturés nommés comme suit :


En utilisant la construction de référence arrière nommée dans l'expression régulière. La sous-
expression mise en correspondance est référencée dans la même expression régulière en utilisant la
syntaxe \k< nom > , où nom est le nom de la sous-expression capturée.
En utilisant la construction de référence arrière dans l'expression régulière. La sous-expression mise en
correspondance est référencée dans la même expression régulière en utilisant la syntaxe \ Number,
où Number est le nombre ordinal de la sous-expression capturée. Les sous-expressions mises en
correspondance nommées sont numérotées de manière consécutive de la gauche vers la droite après
les sous-expressions mises en correspondance.
En utilisant la ${ name } séquence de remplacement de nom dans un Regex.Replace Match.Result
appel de méthode ou, où nom est le nom de la sous-expression capturée.
En utilisant la séquence de remplacement $ nombre dans un appel de méthode Regex.Replace ou
Match.Result , où nombre est le nombre ordinal de la sous-expression capturée.
Par programmation, en utilisant l'objet GroupCollection retourné par la propriété Match.Groups . Le
membre situé à la position zéro dans la collection représente la correspondance de l’expression
régulière entière. Chaque membre suivant représente une sous-expression mise en correspondance.
Les groupes capturés nommés sont stockés dans la collection après les groupes capturés numérotés.
Par programmation, en fournissant le nom de la sous-expression à l'indexeur de l'objet
GroupCollection (en C#) ou à sa propriété Item[] (en Visual Basic).
Un modèle d'expression régulière simple permet d'illustrer comment les groupes numérotés (sans nom) et
nommés peuvent être référencés par programmation ou à l'aide d'une syntaxe de langage d'expression
régulière. L'expression régulière ((?<One>abc)\d+)?(?<Two>xyz)(.*) génère les groupes de capture suivants en
fonction du nombre et du nom. Le premier groupe de capture (nombre 0) fait toujours référence au modèle
entier.

N UM B ER NOM M O DÈL E

0 0 (nom par défaut) ((?<One>abc)\d+)?(?<Two>xyz)


(.*)

1 1 (nom par défaut) ((?<One>abc)\d+)

2 2 (nom par défaut) (.*)

3 Un (?<One>abc)

4 Deux (?<Two>xyz)

L'exemple suivant illustre une expression régulière qui identifie les mots en double et le mot qui se trouve
juste après chaque mot en double. Le modèle d'expression régulière définit deux sous-expressions nommées :
duplicateWord , qui représente le mot en double, et nextWord , qui représente le mot qui suit le mot en double.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(?<duplicateWord>\w+)\s\k<duplicateWord>\W(?<nextWord>\w+)";
string input = "He said that that was the the correct answer.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("A duplicate '{0}' at position {1} is followed by '{2}'.",
match.Groups["duplicateWord"].Value, match.Groups["duplicateWord"].Index,
match.Groups["nextWord"].Value);
}
}
// The example displays the following output:
// A duplicate 'that' at position 8 is followed by 'was'.
// A duplicate 'the' at position 22 is followed by 'correct'.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(?<duplicateWord>\w+)\s\k<duplicateWord>\W(?<nextWord>\w+)"
Dim input As String = "He said that that was the the correct answer."
Console.WriteLine(Regex.Matches(input, pattern, RegexOptions.IgnoreCase).Count)
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine("A duplicate '{0}' at position {1} is followed by '{2}'.", _
match.Groups("duplicateWord").Value, match.Groups("duplicateWord").Index, _
match.Groups("nextWord").Value)
Next
End Sub
End Module
' The example displays the following output:
' A duplicate 'that' at position 8 is followed by 'was'.
' A duplicate 'the' at position 22 is followed by 'correct'.

Le modèle d'expression régulière est le suivant :


(?<duplicateWord>\w+)\s\k<duplicateWord>\W(?<nextWord>\w+)

Le tableau suivant montre comment l'expression régulière est interprétée.

M O DÈL E DESC RIP T IO N

(?<duplicateWord>\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques. Nommer ce groupe de capture
duplicateWord .

\s Mettre en correspondance un espace blanc.

\k<duplicateWord> Mettre en correspondance la chaîne à partir du groupe


capturé nommé duplicateWord .

\W Mettre en correspondance un caractère n'appartenant pas


à un mot, comme un espace blanc ou un signe de
ponctuation. Cela empêche le modèle d'expression
régulière de mettre en correspondance un mot qui
commence par le mot récupéré du premier groupe capturé.
M O DÈL E DESC RIP T IO N

(?<nextWord>\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques. Nommer ce groupe de capture nextWord .

Notez qu'un nom de groupe peut être répété dans une expression régulière. Par exemple, il est possible pour
plusieurs groupes soient nommés digit , comme le montre l'exemple suivant. Dans le cas de noms en
doublon, la valeur de l'objet Group est déterminée par la dernière capture réussie dans la chaîne d'entrée. En
outre, la collection CaptureCollection est remplie avec des informations sur chaque capture, comme si le nom
du groupe n'était pas en doublon.
Dans l'exemple suivant, l'expression régulière \D+(?<digit>\d+)\D+(?<digit>\d+)? comprend deux
occurrences d'un groupe nommé digit . Le premier groupe nommé digit capture un ou plusieurs
caractères numériques. Le deuxième groupe nommé digit capture zéro ou une occurrence d'un ou
plusieurs caractères numériques. Comme la sortie de l'exemple le montre, si le deuxième groupe de capture
correspond à du texte, la valeur de ce texte définit la valeur de l'objet Group . Si le deuxième groupe de
capture ne correspond pas à la chaîne d'entrée, la valeur de la dernière correspondance définit la valeur de
l'objet Group .

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
String pattern = @"\D+(?<digit>\d+)\D+(?<digit>\d+)?";
String[] inputs = { "abc123def456", "abc123def" };
foreach (var input in inputs) {
Match m = Regex.Match(input, pattern);
if (m.Success) {
Console.WriteLine("Match: {0}", m.Value);
for (int grpCtr = 1; grpCtr < m.Groups.Count; grpCtr++) {
Group grp = m.Groups[grpCtr];
Console.WriteLine("Group {0}: {1}", grpCtr, grp.Value);
for (int capCtr = 0; capCtr < grp.Captures.Count; capCtr++)
Console.WriteLine(" Capture {0}: {1}", capCtr,
grp.Captures[capCtr].Value);
}
}
else {
Console.WriteLine("The match failed.");
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: abc123def456
// Group 1: 456
// Capture 0: 123
// Capture 1: 456
//
// Match: abc123def
// Group 1: 123
// Capture 0: 123
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\D+(?<digit>\d+)\D+(?<digit>\d+)?"
Dim inputs() As String = {"abc123def456", "abc123def"}
For Each input As String In inputs
Dim m As Match = Regex.Match(input, pattern)
If m.Success Then
Console.WriteLine("Match: {0}", m.Value)
For grpCtr As Integer = 1 to m.Groups.Count - 1
Dim grp As Group = m.Groups(grpCtr)
Console.WriteLine("Group {0}: {1}", grpCtr, grp.Value)
For capCtr As Integer = 0 To grp.Captures.Count - 1
Console.WriteLine(" Capture {0}: {1}", capCtr,
grp.Captures(capCtr).Value)
Next
Next
Else
Console.WriteLine("The match failed.")
End If
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: abc123def456
' Group 1: 456
' Capture 0: 123
' Capture 1: 456
'
' Match: abc123def
' Group 1: 123
' Capture 0: 123

Le tableau suivant montre comment l'expression régulière est interprétée.

M O DÈL E DESC RIP T IO N

\D+ Mettre en correspondance un ou plusieurs caractères non


décimaux.

(?<digit>\d+) Mettre en correspondance un ou plusieurs caractères


décimaux. Affecter la correspondance au groupe nommé
digit .

\D+ Mettre en correspondance un ou plusieurs caractères non


décimaux.

(?<digit>\d+)? Mettre en correspondance zéro ou une occurrence d'un ou


plusieurs caractères numériques décimaux. Affecter la
correspondance au groupe nommé digit .

Définitions de groupe d'équilibrage


Une définition de groupe d'équilibrage supprime la définition d'un groupe précédemment défini et stocke,
dans le groupe actuel, l'intervalle entre le groupe précédemment défini et ce dernier. Cette construction de
regroupement se présente sous la forme suivante :
(?<name1-name2>subexpression)
ou :
(?'name1-name2' subexpression)

où name1 est le groupe actuel (facultatif), name2 un groupe précédemment défini et subexpression un
modèle d'expression régulière valide. La définition de groupe d'équilibrage supprime la définition de name2
et stocke l'intervalle entre name2 et name1 dans name1. Si aucun groupe name2 n'est défini, la recherche de
correspondance s'effectue de façon rétroactive. Comme la suppression de la dernière définition de name2
révèle la définition antérieure de name2, cette construction vous permet d’utiliser la pile de captures du
groupe name2 comme compteur pour effectuer le suivi des constructions imbriquées, telles que des
parenthèses ou des crochets ouvrants et fermants.
La définition de groupe d'équilibrage utilise name2 comme pile. Le caractère initial de chaque construction
imbriquée est placé dans le groupe et dans sa collection Group.Captures . Quand le caractère fermant est mis
en correspondance, le caractère ouvrant correspondant est supprimé du groupe, et la collection Captures est
diminuée d'une unité. Une fois que les caractères ouvrant et fermant de toutes les constructions imbriquées
ont été mis en correspondance, name2 est vide.

NOTE
Une fois que vous avez modifié l'expression régulière dans l'exemple suivant pour utiliser les caractères ouvrant et
fermant appropriés d'une construction imbriquée, vous pouvez l'utiliser pour gérer la plupart des constructions
imbriquées, telles que les expressions mathématiques ou les lignes de code de programme qui comprennent plusieurs
appels de méthode imbriqués.

L'exemple suivant utilise une définition de groupe d'équilibrage pour mettre en correspondance les chevrons
gauche et droit (<>) dans une chaîne d'entrée. L'exemple définit deux groupes nommés, Open et Close , qui
sont utilisés comme une pile pour effectuer le suivi des paires de chevrons correspondantes. Chaque chevron
gauche capturé est placé dans la collection de captures du groupe Open , tandis que chaque chevron droit
capturé est placé dans la collection de captures du groupe Close . La définition de groupe d'équilibrage
s'assure qu'à chaque chevron gauche correspond un chevron droit. Si tel n'est pas le cas, le sous-modèle final,
(?(Open)(?!)) , n'est évalué que si le groupe Open n'est pas vide (signe que toutes les constructions
imbriquées n'ont pas été fermées). Si le sous-modèle final est évalué, la recherche de correspondance échoue,
car le sous-modèle (?!) est une assertion de préanalyse négative de largeur nulle qui échoue
systématiquement.
using System;
using System.Text.RegularExpressions;

class Example
{
public static void Main()
{
string pattern = "^[^<>]*" +
"(" +
"((?'Open'<)[^<>]*)+" +
"((?'Close-Open'>)[^<>]*)+" +
")*" +
"(?(Open)(?!))$";
string input = "<abc><mno<xyz>>";

Match m = Regex.Match(input, pattern);


if (m.Success == true)
{
Console.WriteLine("Input: \"{0}\" \nMatch: \"{1}\"", input, m);
int grpCtr = 0;
foreach (Group grp in m.Groups)
{
Console.WriteLine(" Group {0}: {1}", grpCtr, grp.Value);
grpCtr++;
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine(" Capture {0}: {1}", capCtr, cap.Value);
capCtr++;
}
}
}
else
{
Console.WriteLine("Match failed.");
}
}
}
// The example displays the following output:
// Input: "<abc><mno<xyz>>"
// Match: "<abc><mno<xyz>>"
// Group 0: <abc><mno<xyz>>
// Capture 0: <abc><mno<xyz>>
// Group 1: <mno<xyz>>
// Capture 0: <abc>
// Capture 1: <mno<xyz>>
// Group 2: <xyz
// Capture 0: <abc
// Capture 1: <mno
// Capture 2: <xyz
// Group 3: >
// Capture 0: >
// Capture 1: >
// Capture 2: >
// Group 4:
// Group 5: mno<xyz>
// Capture 0: abc
// Capture 1: xyz
// Capture 2: mno<xyz>
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^[^<>]*" & _
"(" + "((?'Open'<)[^<>]*)+" & _
"((?'Close-Open'>)[^<>]*)+" + ")*" & _
"(?(Open)(?!))$"
Dim input As String = "<abc><mno<xyz>>"
Dim rgx AS New Regex(pattern) '
Dim m As Match = Regex.Match(input, pattern)
If m.Success Then
Console.WriteLine("Input: ""{0}"" " & vbCrLf & "Match: ""{1}""", _
input, m)
Dim grpCtr As Integer = 0
For Each grp As Group In m.Groups
Console.WriteLine(" Group {0}: {1}", grpCtr, grp.Value)
grpCtr += 1
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: {1}", capCtr, cap.Value)
capCtr += 1
Next
Next
Else
Console.WriteLine("Match failed.")
End If
End Sub
End Module
' The example displays the following output:
' Input: "<abc><mno<xyz>>"
' Match: "<abc><mno<xyz>>"
' Group 0: <abc><mno<xyz>>
' Capture 0: <abc><mno<xyz>>
' Group 1: <mno<xyz>>
' Capture 0: <abc>
' Capture 1: <mno<xyz>>
' Group 2: <xyz
' Capture 0: <abc
' Capture 1: <mno
' Capture 2: <xyz
' Group 3: >
' Capture 0: >
' Capture 1: >
' Capture 2: >
' Group 4:
' Group 5: mno<xyz>
' Capture 0: abc
' Capture 1: xyz
' Capture 2: mno<xyz>

Le modèle d'expression régulière est le suivant :


^[^<>]*(((?'Open'<)[^<>]*)+((?'Close-Open'>)[^<>]*)+)*(?(Open)(?!))$

L'expression régulière est interprétée comme suit :

M O DÈL E DESC RIP T IO N

^ Commencer au début de la chaîne.

[^<>]* Mettre en correspondance zéro caractère, ou plus, à


l'exception des chevrons gauches ou droits.
M O DÈL E DESC RIP T IO N

(?'Open'<) Mettre en correspondance un chevron gauche et l'affecter


à un groupe nommé Open .

[^<>]* Mettre en correspondance zéro caractère, ou plus, à


l'exception des chevrons gauches ou droits.

((?'Open'<)[^<>]*)+ Mettre en correspondance une ou plusieurs occurrences


d'un chevron gauche suivies de zéro caractère, ou plus, à
l'exception des chevrons gauches ou droits. Il s'agit du
deuxième groupe de capture.

(?'Close-Open'>) Mettre en correspondance un chevron droit, affecter la


sous-chaîne entre le groupe Open et le groupe actuel au
groupe Close , puis supprimer la définition du groupe
Open .

[^<>]* Mettre en correspondance zéro occurrence, ou plus, d'un


caractère à l'exception d'un chevron gauche ou droit.

((?'Close-Open'>)[^<>]*)+ Mettre en correspondance une occurrence, ou plus, d'un


chevron droit, suivies de zéro occurrence, ou plus, d'un
caractère à l'exception d'un chevron gauche ou droit.
Durant la mise en correspondance du chevron droit,
affecter la sous-chaîne entre le groupe Open et le groupe
actuel au groupe Close , puis supprimer la définition du
groupe Open . Il s'agit du troisième groupe de capture.

(((?'Open'<)[^<>]*)+((?'Close-Open'>)[^<>]*)+)* Mettre en correspondance zéro occurrence, ou plus, du


modèle suivant : une ou plusieurs occurrences d'un
chevron gauche, suivies de zéro caractère, ou plus, autre
qu'un chevron, suivis d'une ou plusieurs occurrences d'un
chevron droit, suivies de zéro caractère, ou plus, autre
qu'un chevron. Durant la mise en correspondance du
chevron droit, supprimer la définition du groupe Open et
affecter la sous-chaîne entre le groupe Open et le groupe
actuel au groupe Close . Il s'agit du premier groupe de
capture.

(?(Open)(?!)) Si le groupe Open existe, abandonner la recherche de


correspondance si une chaîne vide peut être mise en
correspondance, mais ne pas avancer la position du moteur
d'expression régulière dans la chaîne. Il s'agit d'une
assertion de préanalyse négative de largeur nulle. Comme
une chaîne vide est toujours implicitement présente dans
une chaîne d'entrée, cette recherche de correspondance
échoue systématiquement. L'échec de cette recherche de
correspondance indique que les chevrons ne sont pas
équilibrés.

$ Mettre en correspondance la fin de la chaîne d'entrée.

La sous-expression finale, (?(Open)(?!)) , indique si les constructions d'imbrication dans la chaîne d'entrée
sont correctement équilibrées (par exemple, si à chaque chevron gauche correspond un chevron droit). Elle
utilise une mise en correspondance conditionnelle basée sur un groupe capturé valide ; pour plus
d'informations, voir Alternation Constructs. Si le groupe Open est défini, le moteur d'expression régulière
essaie de mettre en correspondance la sous-expression (?!) dans la chaîne d'entrée. Le groupe Open ne
doit être défini que si les constructions d'imbrication ne sont pas équilibrées. Le modèle à mettre en
correspondance dans la chaîne d'entrée doit donc être un modèle qui entraîne systématiquement l'échec de la
recherche de correspondance. Dans ce cas, (?!) est une assertion de préanalyse négative de largeur nulle
qui échoue systématiquement, car une chaîne vide est toujours implicitement présente à la position suivante
dans la chaîne d'entrée.
Dans l’exemple, le moteur d’expression régulière évalue la chaîne d’entrée « <abc><mno<xyz>> » comme
indiqué dans le tableau suivant.

ÉTA P E M O DÈL E RÉSULTAT S

1 ^ Commence la correspondance au
début de la chaîne d'entrée.

2 [^<>]* Recherche des caractères autres que


des chevrons avant le chevron gauche
; ne trouve aucune correspondance.

3 (((?'Open'<) Met en correspondance le chevron


gauche dans « <abc> » et l’affecte au
groupe Open .

4 [^<>]* Met en correspondance « abc ».

5 )+ « <abc » est la valeur du deuxième


groupe capturé.

Le caractère suivant dans la chaîne


d'entrée n'étant pas un chevron
gauche, le moteur d'expression
régulière ne repasse pas par le sous-
modèle (?'Open'<)[^<>]*) .

6 ((?'Close-Open'>) Met en correspondance le chevron


droit dans « <abc> », affecte « abc »,
qui est la sous-chaîne entre le groupe
Open et le chevron droit, au groupe
Close , puis supprime la valeur
actuelle (« < ») du groupe Open , qui
se trouve alors vide.

7 [^<>]* Recherche des caractères autres que


des chevrons après le chevron droit ;
ne trouve aucune correspondance.

8 )+ La valeur du troisième groupe capturé


est « > ».

Le caractère suivant dans la chaîne


d'entrée n'étant pas un chevron droit,
le moteur d'expression régulière ne
repasse pas par le sous-modèle
((?'Close-Open'>)[^<>]*) .
ÉTA P E M O DÈL E RÉSULTAT S

9 )* La valeur du premier groupe capturé


est « <abc> ».

Le caractère suivant dans la chaîne


d'entrée étant un chevron gauche, le
moteur d'expression régulière repasse
par le sous-modèle (((?'Open'<) .

10 (((?'Open'<) Correspond au chevron gauche dans


« <mno" and assigns it to the Open
group. Its Group.Captures la
collection a désormais une seule
valeur, «< ».

11 [^<>]* Met en correspondance « mno ».

12 )+ « <mno » est la valeur du deuxième


groupe capturé.

Le caractère suivant dans la chaîne


d'entrée étant un chevron gauche, le
moteur d'expression régulière repasse
par le sous-modèle
(?'Open'<)[^<>]*) .

13 (((?'Open'<) Met en correspondance le chevron


gauche dans « <xyz> » et l’affecte au
groupe Open . La Group.Captures
collection du Open groupe
comprend maintenant deux captures :
le Chevron gauche de « <mno", and
the left angle bracket from "<xyz> ».

14 [^<>]* Met en correspondance « xyz ».

15 )+ « <xyz » est la valeur du deuxième


groupe capturé.

Le caractère suivant dans la chaîne


d'entrée n'étant pas un chevron
gauche, le moteur d'expression
régulière ne repasse pas par le sous-
modèle (?'Open'<)[^<>]*) .

16 ((?'Close-Open'>) Met en correspondance le chevron


droit dans « <xyz> », "xyz" affecte la
sous-chaîne entre le groupe Open et
le chevron droit au groupe Close ,
puis supprime la valeur actuelle du
groupe Open . La valeur de la
capture précédente (le Chevron
gauche dans « <mno") becomes the
current value of the Open group. The
Captures collection du Open groupe
comprend maintenant une seule
capture, le Chevron gauche de «
<xyz> ».
ÉTA P E M O DÈL E RÉSULTAT S

17 [^<>]* Recherche des caractères autres que


des chevrons ; ne trouve aucune
correspondance.

18 )+ La valeur du troisième groupe capturé


est « > ».

Le caractère suivant dans la chaîne


d'entrée étant un chevron droit, le
moteur d'expression régulière repasse
par le sous-modèle
((?'Close-Open'>)[^<>]*) .

19 ((?'Close-Open'>) Met en correspondance le chevron


droit final dans « xyz>> », affecte
« mno<xyz> » (la sous-chaîne entre le
groupe Open et le chevron droit) au
groupe Close , puis supprime la
valeur actuelle du groupe Open . Le
groupe Open est maintenant vide.

20 [^<>]* Recherche des caractères autres que


des chevrons ; ne trouve aucune
correspondance.

21 )+ La valeur du troisième groupe capturé


est « > ».

Le caractère suivant dans la chaîne


d'entrée n'étant pas un chevron droit,
le moteur d'expression régulière ne
repasse pas par le sous-modèle
((?'Close-Open'>)[^<>]*) .

22 )* La valeur du premier groupe capturé


est « <mno<xyz>> ».

Le caractère suivant dans la chaîne


d'entrée n'étant pas un chevron
gauche, le moteur d'expression
régulière ne repasse pas par le sous-
modèle (((?'Open'<) .

23 (?(Open)(?!)) Le groupe Open n'étant pas défini,


aucune recherche de correspondance
n'est effectuée.

24 $ Met en correspondance la fin de la


chaîne d'entrée.

Groupes sans capture


La construction de regroupement suivante ne capture pas la sous-chaîne mise en correspondance par une
sous-expression :
(?:subexpression)
où subexpression représente un modèle d'expression régulière valide. En règle générale, la construction de
groupe sans capture est utilisée quand un quantificateur est appliqué à un groupe, mais que les sous-chaînes
capturées par celui-ci ne présentent aucun intérêt.

NOTE
Si une expression régulière comprend des constructions de regroupement imbriquées, une construction de groupe sans
capture externe ne s'applique pas aux constructions de groupe imbriquées internes.

L'exemple suivant illustre une expression régulière qui comprend des groupes sans capture. Notez que la
sortie ne comprend aucun groupe capturé.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(?:\b(?:\w+)\W*)+\.";
string input = "This is a short sentence.";
Match match = Regex.Match(input, pattern);
Console.WriteLine("Match: {0}", match.Value);
for (int ctr = 1; ctr < match.Groups.Count; ctr++)
Console.WriteLine(" Group {0}: {1}", ctr, match.Groups[ctr].Value);
}
}
// The example displays the following output:
// Match: This is a short sentence.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(?:\b(?:\w+)\W*)+\."
Dim input As String = "This is a short sentence."
Dim match As Match = Regex.Match(input, pattern)
Console.WriteLine("Match: {0}", match.Value)
For ctr As Integer = 1 To match.Groups.Count - 1
Console.WriteLine(" Group {0}: {1}", ctr, match.Groups(ctr).Value)
Next
End Sub
End Module
' The example displays the following output:
' Match: This is a short sentence.

L'expression régulière (?:\b(?:\w+)\W*)+\. met en correspondance une phrase terminée par un point.
Comme l'expression régulière porte sur des phrases et non sur des mots spécifiques, les constructions de
regroupement sont exclusivement utilisées en tant que quantificateurs. Le modèle d'expression régulière est
interprété comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

(?:\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques. Ne pas affecter le texte mis en
correspondance à un groupe capturé.
M O DÈL E DESC RIP T IO N

\W* Mettre en correspondance zéro ou plusieurs caractères non


alphabétiques.

(?:\b(?:\w+)\W*)+ Mettre en correspondance le modèle d'un ou plusieurs


caractères alphabétiques en commençant à la limite d'un
mot, suivi de zéro caractère non alphabétique, ou plus, une
ou plusieurs fois. Ne pas affecter le texte mis en
correspondance à un groupe capturé.

\. Mettre en correspondance un point.

Options de groupe
La construction de regroupement suivante applique ou désactive les options spécifiées dans une sous-
expression :
(?imnsx-imnsx: sous- expression )
où subexpression représente un modèle d'expression régulière valide. Par exemple, (?i-s:) désactive la
prise en compte des majuscules et des minuscules, ainsi que le mode à ligne simple. Pour plus d’informations
sur les options inline que vous pouvez spécifier, consultez Options des expressions régulières.

NOTE
Vous pouvez spécifier des options qui s'appliquent à une expression régulière entière plutôt qu'à une sous-expression
en utilisant un constructeur de classe System.Text.RegularExpressions.Regex ou une méthode statique. Vous pouvez
également spécifier des options inline qui s'appliquent après un point spécifique dans une expression régulière en
utilisant la construction de langage (?imnsx-imnsx) .

La construction des options de groupe n'est pas un groupe de capture. En d'autres termes, bien qu'une partie
d'une chaîne capturée par sous-expression soit incluse dans la correspondance, elle n'est pas placée dans un
groupe capturé, ni utilisée pour remplir l'objet GroupCollection .
Dans l’exemple suivant, l’expression régulière \b(?ix: d \w+)\s utilise des options inline dans une
construction de regroupement pour désactiver le respect de la casse et ignorer l’espace blanc du modèle
durant l’identification de tous les mots commençant par la lettre « d ». L'expression régulière est définie
comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

(?ix: d \w+) Sans prendre en compte les majuscules et les minuscules et


en ignorant l'espace blanc dans ce modèle, mettre en
correspondance un caractère « d » suivi d'un ou plusieurs
caractères alphabétiques.

\s Mettre en correspondance un espace blanc.


string pattern = @"\b(?ix: d \w+)\s";
string input = "Dogs are decidedly good pets.";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine("'{0}// found at index {1}.", match.Value, match.Index);
// The example displays the following output:
// 'Dogs // found at index 0.
// 'decidedly // found at index 9.

Dim pattern As String = "\b(?ix: d \w+)\s"


Dim input As String = "Dogs are decidedly good pets."

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("'{0}' found at index {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'Dogs ' found at index 0.
' 'decidedly ' found at index 9.

Assertions de préanalyse positive de largeur nulle


La construction de regroupement suivante définit une assertion de préanalyse positive de largeur nulle :
(?= sous- expression )
où sous-expression représente un modèle d'expression régulière. Pour qu'une recherche de correspondance
réussisse, la chaîne d'entrée doit correspondre au modèle d'expression régulière dans sous-expression, bien
que la sous-chaîne mise en correspondance ne soit pas incluse dans le résultat de la recherche de
correspondance. Une assertion de préanalyse positive de largeur nulle n'est pas rétroactive.
En règle générale, une assertion de préanalyse positive de largeur nulle est trouvée à la fin d'un modèle
d'expression régulière. Elle définit une sous-chaîne qui doit être trouvée à la fin d'une chaîne pour qu'une
mise en correspondance se produise, mais qui ne doit pas être incluse dans la correspondance. En outre, elle
est utile pour empêcher une rétroactivité excessive. Vous pouvez utiliser une assertion de préanalyse positive
de largeur nulle indiquant qu'un groupe capturé particulier doit commencer par un texte qui correspond à
une partie du modèle défini pour ce groupe. Par exemple, si un groupe de capture met en correspondance des
caractères alphabétiques consécutifs, vous pouvez utiliser une assertion de préanalyse positive de largeur
nulle pour imposer que le premier caractère soit un caractère majuscule alphabétique.
L'exemple suivant utilise une assertion de préanalyse positive de largeur nulle pour mettre en
correspondance le mot qui précède le verbe « is » dans la chaîne d'entrée.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b\w+(?=\sis\b)";
string[] inputs = { "The dog is a Malamute.",
"The island has beautiful birds.",
"The pitch missed home plate.",
"Sunday is a weekend day." };

foreach (string input in inputs)


{
Match match = Regex.Match(input, pattern);
if (match.Success)
Console.WriteLine("'{0}' precedes 'is'.", match.Value);
else
Console.WriteLine("'{0}' does not match the pattern.", input);
}
}
}
// The example displays the following output:
// 'dog' precedes 'is'.
// 'The island has beautiful birds.' does not match the pattern.
// 'The pitch missed home plate.' does not match the pattern.
// 'Sunday' precedes 'is'.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b\w+(?=\sis\b)"
Dim inputs() As String = {"The dog is a Malamute.", _
"The island has beautiful birds.", _
"The pitch missed home plate.", _
"Sunday is a weekend day."}

For Each input As String In inputs


Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine("'{0}' precedes 'is'.", match.Value)
Else
Console.WriteLine("'{0}' does not match the pattern.", input)
End If
Next
End Sub
End Module
' The example displays the following output:
' 'dog' precedes 'is'.
' 'The island has beautiful birds.' does not match the pattern.
' 'The pitch missed home plate.' does not match the pattern.
' 'Sunday' precedes 'is'.

L'expression régulière \b\w+(?=\sis\b) est interprétée comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.


M O DÈL E DESC RIP T IO N

\w+ Mettre en correspondance un ou plusieurs caractères


alphabétiques.

(?=\sis\b) Détermine si les caractères alphabétiques sont suivis d'un


espace blanc et de la chaîne « is », qui se termine à la limite
d'un mot. Si tel est le cas, la recherche de correspondance
réussit.

Assertions de préanalyse négative de largeur nulle


La construction de regroupement suivante définit une assertion de préanalyse négative de largeur nulle :
(?! sous- expression )
où sous-expression représente un modèle d'expression régulière. Pour que la recherche de correspondance
réussisse, la chaîne d'entrée ne doit pas correspondre au modèle d'expression régulière dans sous-expression,
bien que la chaîne mise en correspondance ne soit pas incluse dans le résultat de la recherche de
correspondance.
En règle générale, une assertion de préanalyse négative de largeur nulle est utilisée au début ou à la fin d'une
expression régulière. Au début d'une expression régulière, elle peut définir un modèle spécifique qui ne doit
pas être mis en correspondance quand le début de l'expression régulière définit un modèle de recherche de
correspondance similaire, mais plus général. Dans ce cas, elle est souvent utilisée pour limiter la rétroactivité.
À la fin d'une expression régulière, elle peut définir une sous-expression qui ne peut pas apparaître à la fin
d'une correspondance.
L'exemple suivant définit une expression régulière qui utilise une assertion de préanalyse de largeur nulle au
début de l'expression régulière pour mettre en correspondance les mots qui ne commencent pas par « un ».

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(?!un)\w+\b";
string input = "unite one unethical ethics use untie ultimate";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// one
// ethics
// use
// ultimate
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(?!un)\w+\b"
Dim input As String = "unite one unethical ethics use untie ultimate"
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' one
' ethics
' use
' ultimate

L'expression régulière \b(?!un)\w+\b est interprétée comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

(?!un) Déterminer si les deux caractères suivants sont « un ». Si tel


n'est pas le cas, une correspondance est possible.

\w+ Mettre en correspondance un ou plusieurs caractères


alphabétiques.

\b Terminer la correspondance à la limite d'un mot.

L'exemple suivant définit une expression régulière qui utilise une assertion de préanalyse de largeur nulle à la
fin de l'expression régulière pour mettre en correspondance les mots qui ne se terminent pas par un caractère
de ponctuation.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b\w+\b(?!\p{P})";
string input = "Disconnected, disjointed thoughts in a sentence fragment.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// disjointed
// thoughts
// in
// a
// sentence
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b\w+\b(?!\p{P})"
Dim input As String = "Disconnected, disjointed thoughts in a sentence fragment."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' disjointed
' thoughts
' in
' a
' sentence

L'expression régulière \b\w+\b(?!\p{P}) est interprétée comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

\w+ Mettre en correspondance un ou plusieurs caractères


alphabétiques.

\b Terminer la correspondance à la limite d'un mot.

\p{P}) Si le caractère suivant n'est pas un symbole de ponctuation


(tel qu'un point ou une virgule), la recherche de
correspondance réussit.

Assertions de postanalyse positive de largeur nulle


La construction de regroupement suivante définit une assertion de postanalyse positive de largeur nulle :
(?<= sous- expression )
où sous-expression représente un modèle d'expression régulière. Pour qu'une recherche de correspondance
réussisse, sous-expression doit se trouver dans la chaîne d'entrée à gauche de la position actuelle, bien que la
sous-expression ( subexpression ) ne soit pas incluse dans le résultat de la recherche de correspondance. Une
assertion de postanalyse positive de largeur nulle n'est pas rétroactive.
Les assertions de postanalyse positive de largeur nulle sont généralement utilisées au début des expressions
régulières. Le modèle qu'elles définissent est une condition préalable pour une correspondance, bien qu'il ne
fasse pas partie du résultat de la recherche de correspondance.
L'exemple suivant met en correspondance les deux derniers chiffres des années appartenant au vingt et
unième siècle (en d'autres termes, les chiffres « 20 » doivent précéder la chaîne mise en correspondance).
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "2010 1999 1861 2140 2009";
string pattern = @"(?<=\b20)\d{2}\b";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// 10
// 09

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "2010 1999 1861 2140 2009"
Dim pattern As String = "(?<=\b20)\d{2}\b"

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' 10
' 09

Le modèle d'expression régulière (?<=\b20)\d{2}\b est interprété comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\d{2} Mettre en correspondance deux chiffres décimaux.

(?<=\b20) Continuer la mise en correspondance si les deux chiffres


décimaux sont précédés des chiffres décimaux « 20 » à la
limite d'un mot.

\b Terminer la correspondance à la limite d'un mot.

Les assertions de postanalyse positive de largeur nulle permettent également de limiter la rétroactivité quand
le ou les derniers caractères d'un groupe capturé doivent être une partie des caractères qui correspondent au
modèle d'expression régulière de ce groupe. Par exemple, si un groupe capture tous les caractères
alphabétiques consécutifs, vous pouvez utiliser une assertion de postanalyse positive de largeur nulle pour
imposer que le dernier caractère soit un caractère alphabétique.

Assertions de postanalyse négative de largeur nulle


La construction de regroupement suivante définit une assertion de postanalyse négative de largeur nulle :
(?<! sous- expression )
où sous-expression représente un modèle d'expression régulière. Pour qu’une recherche de correspondance
réussisse, sous-expression ne doit pas se trouver dans la chaîne d’entrée à gauche de la position actuelle.
Toutefois, toute sous-chaîne qui ne correspond pas à subexpression est exclue du résultat de la recherche de
correspondance.
Les assertions de postanalyse négative de largeur nulle sont généralement utilisées au début des expressions
régulières. Le modèle qu'elles définissent exclut une mise en correspondance dans la chaîne qui suit. Elles
permettent également de limiter la rétroactivité quand le ou les derniers caractères d'un groupe capturé ne
doivent pas être un ou plusieurs des caractères qui correspondent au modèle d'expression régulière de ce
groupe. Par exemple, si un groupe capture tous les caractères alphabétiques consécutifs, vous pouvez utiliser
une assertion de postanalyse positive de largeur zéro pour exiger que le dernier caractère ne soit pas un trait
de soulignement (_).
L'exemple suivant met en correspondance la date de n'importe quel jour de la semaine ne tombant pas
pendant le week-end (c'est-à-dire, tous les jours sauf le samedi et le dimanche).

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] dates = { "Monday February 1, 2010",
"Wednesday February 3, 2010",
"Saturday February 6, 2010",
"Sunday February 7, 2010",
"Monday, February 8, 2010" };
string pattern = @"(?<!(Saturday|Sunday) )\b\w+ \d{1,2}, \d{4}\b";

foreach (string dateValue in dates)


{
Match match = Regex.Match(dateValue, pattern);
if (match.Success)
Console.WriteLine(match.Value);
}
}
}
// The example displays the following output:
// February 1, 2010
// February 3, 2010
// February 8, 2010
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim dates() As String = {"Monday February 1, 2010", _
"Wednesday February 3, 2010", _
"Saturday February 6, 2010", _
"Sunday February 7, 2010", _
"Monday, February 8, 2010"}
Dim pattern As String = "(?<!(Saturday|Sunday) )\b\w+ \d{1,2}, \d{4}\b"

For Each dateValue As String In dates


Dim match As Match = Regex.Match(dateValue, pattern)
If match.Success Then
Console.WriteLine(match.Value)
End If
Next
End Sub
End Module
' The example displays the following output:
' February 1, 2010
' February 3, 2010
' February 8, 2010

Le modèle d'expression régulière (?<!(Saturday|Sunday) )\b\w+ \d{1,2}, \d{4}\b est interprété comme
indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

\w+ Mettre en correspondance un ou plusieurs caractères


alphabétiques suivis d'un espace blanc.

\d{1,2}, Mettre en correspondance un ou deux chiffres décimaux


suivis d'un espace blanc et d'une virgule.

\d{4}\b Mettre en correspondance quatre chiffres décimaux, puis


terminer la correspondance à la limite d'un mot.

(?<!(Saturday|Sunday) ) Si la correspondance est précédée d'une chaîne autre que


« Saturday » ou « Sunday » suivie d'un espace, la mise en
correspondance réussit.

Groupes atomiques
La construction de regroupement suivante représente un groupe atomique (connu dans certains autres
moteurs d’expressions régulières comme une sous-expression non rétroactive, une sous-expression atomique
ou une sous-expression unique) :
(?> sous- expression )
où sous-expression représente un modèle d'expression régulière.
D'ordinaire, si une expression régulière comprend un modèle de mise en correspondance facultatif ou de
substitution et qu'aucune mise en correspondance ne réussit, le moteur d'expression régulière peut explorer
plusieurs directions pour mettre en correspondance une chaîne d'entrée avec un modèle. Si aucune
correspondance n'est trouvée au niveau de la première branche, le moteur d'expression régulière peut revenir
au point d'exécution de la première mise en correspondance et renouveler l'opération au niveau de la
deuxième branche. Ce processus peut se poursuivre jusqu'à ce que toutes les branches aient été essayées.
La construction de langage sous- (?> expression ) désactive la rétroaction. Le moteur d'expression
régulière met en correspondance tous les caractères possibles de la chaîne d'entrée. Quand aucune mise en
correspondance supplémentaire n'est possible, il n'essaie pas d'effectuer une mise en correspondance de
modèle de substitution de manière rétroactive. (En d'autres termes, la sous-expression ne met en
correspondance que les chaînes qu'elle seule peut mettre en correspondance ; elle n'essaie pas de mettre en
correspondance une chaîne avec le concours de sous-expressions qui la suivent éventuellement.)
Cette option est recommandée si vous savez que la rétroactivité est vouée à l'échec. Empêcher le moteur
d'expression régulière d'effectuer des recherches superflues améliore les performances.
L’exemple suivant montre comment un groupe atomique modifie les résultats d’une correspondance de
modèle. Contrairement à l'expression régulière non rétroactive, l'expression régulière rétroactive met en
correspondance une série de caractères répétés suivis d'une occurrence supplémentaire du même caractère à
la limite d'un mot.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "cccd.", "aaad", "aaaa" };
string back = @"(\w)\1+.\b";
string noback = @"(?>(\w)\1+).\b";

foreach (string input in inputs)


{
Match match1 = Regex.Match(input, back);
Match match2 = Regex.Match(input, noback);
Console.WriteLine("{0}: ", input);

Console.Write(" Backtracking : ");


if (match1.Success)
Console.WriteLine(match1.Value);
else
Console.WriteLine("No match");

Console.Write(" Nonbacktracking: ");


if (match2.Success)
Console.WriteLine(match2.Value);
else
Console.WriteLine("No match");
}
}
}
// The example displays the following output:
// cccd.:
// Backtracking : cccd
// Nonbacktracking: cccd
// aaad:
// Backtracking : aaad
// Nonbacktracking: aaad
// aaaa:
// Backtracking : aaaa
// Nonbacktracking: No match
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"cccd.", "aaad", "aaaa"}
Dim back As String = "(\w)\1+.\b"
Dim noback As String = "(?>(\w)\1+).\b"

For Each input As String In inputs


Dim match1 As Match = Regex.Match(input, back)
Dim match2 As Match = Regex.Match(input, noback)
Console.WriteLine("{0}: ", input)

Console.Write(" Backtracking : ")


If match1.Success Then
Console.WriteLine(match1.Value)
Else
Console.WriteLine("No match")
End If

Console.Write(" Nonbacktracking: ")


If match2.Success Then
Console.WriteLine(match2.Value)
Else
Console.WriteLine("No match")
End If
Next
End Sub
End Module
' The example displays the following output:
' cccd.:
' Backtracking : cccd
' Nonbacktracking: cccd
' aaad:
' Backtracking : aaad
' Nonbacktracking: aaad
' aaaa:
' Backtracking : aaaa
' Nonbacktracking: No match

L'expression régulière non rétroactive (?>(\w)\1+).\b est définie comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

(\w) Mettre en correspondance un seul caractère alphabétique


et l'affecter au premier groupe de capture.

\1+ Mettre en correspondance la valeur de la première sous-


chaîne capturée une ou plusieurs fois.

. Mettre en correspondance n'importe quel caractère.

\b Terminer la correspondance à la limite d'un mot.

(?>(\w)\1+) Mettre en correspondance une ou plusieurs occurrences


d'un caractère alphabétique en double, mais ne pas
effectuer rétroactivement une mise en correspondance du
dernier caractère à la limite d'un mot.

Constructions de regroupement et objets d'expression régulière


Les sous-chaînes mises en correspondance par un groupe de capture d'expression régulière sont
représentées par des objets System.Text.RegularExpressions.Group , qui peuvent être récupérés de l'objet
System.Text.RegularExpressions.GroupCollection retourné par la propriété Match.Groups . L'objet
GroupCollection est rempli comme suit :
Le premier objet Group de la collection (l'objet d'index zéro) représente la correspondance entière.
L'ensemble d'objets Group suivant représente des groupes de capture sans nom (numérotés). Ils
apparaissent dans l'ordre dans lequel ils sont définis dans l'expression régulière, de la gauche vers la
droite. Les valeurs d'index de ces groupes vont de 1 au nombre de groupes de capture sans nom dans
la collection. (L'index d'un groupe particulier est équivalent à sa référence arrière numérotée. Pour plus
d'informations sur les références arrière, voir Backreference Constructs.)
Le dernier ensemble d'objets Group représente des groupes de capture nommés. Ils apparaissent dans
l'ordre dans lequel ils sont définis dans l'expression régulière, de la gauche vers la droite. La valeur
d'index du premier groupe de capture nommé est égale à l'index du dernier groupe de capture sans
nom, plus une unité. En l'absence de groupe de capture sans nom dans l'expression régulière, la valeur
d'index du premier groupe de capture nommé est égale à un (1).
Si vous appliquez un quantificateur à un groupe de capture, les propriétés Group , Capture.Valueet
Capture.Indexde l'objet Capture.Length correspondant reflètent la dernière sous-chaîne capturée par un
groupe de capture. Vous pouvez récupérer de l'objet CaptureCollection retourné par la propriété
Group.Captures un ensemble complet de sous-chaînes capturées par des groupes possédant des
quantificateurs.
L'exemple suivant clarifie la relation entre les objets Group et Capture .
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(\b(\w+)\W+)+";
string input = "This is a short sentence.";
Match match = Regex.Match(input, pattern);
Console.WriteLine("Match: '{0}'", match.Value);
for (int ctr = 1; ctr < match.Groups.Count; ctr++)
{
Console.WriteLine(" Group {0}: '{1}'", ctr, match.Groups[ctr].Value);
int capCtr = 0;
foreach (Capture capture in match.Groups[ctr].Captures)
{
Console.WriteLine(" Capture {0}: '{1}'", capCtr, capture.Value);
capCtr++;
}
}
}
}
// The example displays the following output:
// Match: 'This is a short sentence.'
// Group 1: 'sentence.'
// Capture 0: 'This '
// Capture 1: 'is '
// Capture 2: 'a '
// Capture 3: 'short '
// Capture 4: 'sentence.'
// Group 2: 'sentence'
// Capture 0: 'This'
// Capture 1: 'is'
// Capture 2: 'a'
// Capture 3: 'short'
// Capture 4: 'sentence'
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(\b(\w+)\W+)+"
Dim input As String = "This is a short sentence."
Dim match As Match = Regex.Match(input, pattern)
Console.WriteLine("Match: '{0}'", match.Value)
For ctr As Integer = 1 To match.Groups.Count - 1
Console.WriteLine(" Group {0}: '{1}'", ctr, match.Groups(ctr).Value)
Dim capCtr As Integer = 0
For Each capture As Capture In match.Groups(ctr).Captures
Console.WriteLine(" Capture {0}: '{1}'", capCtr, capture.Value)
capCtr += 1
Next
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is a short sentence.'
' Group 1: 'sentence.'
' Capture 0: 'This '
' Capture 1: 'is '
' Capture 2: 'a '
' Capture 3: 'short '
' Capture 4: 'sentence.'
' Group 2: 'sentence'
' Capture 0: 'This'
' Capture 1: 'is'
' Capture 2: 'a'
' Capture 3: 'short'
' Capture 4: 'sentence'

Le modèle d'expression régulière (\b(\w+)\W+)+ extrait des mots spécifiques d'une chaîne. Il est défini
comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

(\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques. Ensemble, ces caractères forment un mot. Il
s'agit du deuxième groupe de capture.

\W+ Mettre en correspondance un ou plusieurs caractères non


alphabétiques.

(\b(\w+)\W+) Mettre en correspondance le modèle d'un ou plusieurs


caractères alphabétiques, suivis d'un ou plusieurs caractères
non alphabétiques, une ou plusieurs fois. Il s'agit du
premier groupe de capture.

Le second groupe de capture met en correspondance chaque mot de la phrase. Le premier groupe de capture
met en correspondance chaque mot, ainsi que la ponctuation et l’espace qui suivent le mot. L'objet Group
dont l'index a pour valeur 2 fournit des informations sur le texte mis en correspondance par le second groupe
de capture. Tous les mots capturés par le groupe de capture sont récupérables de l'objet CaptureCollection
retourné par la propriété Group.Captures .

Voir aussi
Langage des expressions régulières - Aide-mémoire
Rétroaction
quantificateurs dans les expressions régulières
18/07/2020 • 35 minutes to read • Edit Online

Les quantificateurs spécifient le nombre d’instances d’un caractère, groupe ou classe de caractères devant être
présentes dans l’entrée pour qu’une correspondance soit trouvée. Le tableau suivant répertorie les
quantificateurs pris en charge par .NET.

Q UA N T IF IC AT EUR GO URM A N D Q UA N T IF IC AT EUR PA RESSEUX DESC RIP T IO N

* *? Mettre en correspondance zéro


occurrence ou plus.

+ +? Mettre en correspondance une ou


plusieurs occurrences.

? ?? Mettre en correspondance zéro ou


une occurrence.

{ n } { n }? Mettre en correspondance exactement


n occurrences.

{ n ,} { n ,}? Mettre en correspondance au moins n


occurrences.

{ n , m} { n , m }? Mettre en correspondance entre n et


m occurrences.

Les quantités n et m sont des constantes entières. Habituellement, les quantificateurs sont gourmands ; ils
obligent le moteur d’expression régulière à faire correspondre autant d’occurrences de modèles particuliers que
possible. L’ajout du caractère ? à un quantificateur le rend paresseux ; le moteur d’expression régulière fait
alors correspondre aussi peu d’occurrences que possible. Pour une description complète de la différence entre
quantificateurs gourmands et paresseux, consultez la section Quantificateurs gourmands et paresseux plus loin
dans cette rubrique.

IMPORTANT
L’imbrication des quantificateurs (par exemple, comme le fait le modèle d’expression régulière (a*)* ) peut augmenter le
nombre de comparaisons que le moteur d’expression régulière doit exécuter, comme une fonction exponentielle du
nombre de caractères dans la chaîne d’entrée. Pour plus d’informations sur ce comportement et ses solutions de
contournement, consultez Rétroaction.

Quantificateurs d’expression régulière


Les sections suivantes présentent les quantificateurs pris en charge par les expressions régulières .NET.
NOTE
Si les caractères *, +, ?, { et } sont rencontrés dans un modèle d’expressions régulières, le moteur d’expression régulière les
interprète comme des quantificateurs ou partie de constructions de quantificateur à moins qu’ils ne soient inclus dans
une classe de caractères. Pour les interpréter comme des caractères littéraux en dehors d’une classe de caractères, vous
devez les placer dans une séquence d’échappement en les faisant précéder d’une barre oblique inverse. Par exemple, la
chaîne \* dans un modèle d’expression régulière est interprétée comme un caractère astérisque (« * ») littéral.

Mettre en correspondance zéro occurrence ou plus : *


Le quantificateur * correspond zéro fois, ou plus, à l’élément qui précède. Il équivaut au quantificateur {0,} .
* est un quantificateur gourmand dont l’équivalent paresseux est *? .

L’exemple suivant illustre cette expression régulière. La chaîne d’entrée comporte neuf chiffres dont cinq
correspondent au modèle et quatre ( 95 , 929 , 9219 et 9919 ) n’y correspondent pas.

string pattern = @"\b91*9*\b";


string input = "99 95 919 929 9119 9219 999 9919 91119";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// '99' found at position 0.
// '919' found at position 6.
// '9119' found at position 14.
// '999' found at position 24.
// '91119' found at position 33.

Dim pattern As String = "\b91*9*\b"


Dim input As String = "99 95 919 929 9119 9219 999 9919 91119"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' '99' found at position 0.
' '919' found at position 6.
' '9119' found at position 14.
' '999' found at position 24.
' '91119' found at position 33.

Le modèle d’expression régulière est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

91* Mettre en correspondance un « 9 » suivi de zéro, ou plus,


caractère « 1 ».

9* Mettre en correspondance zéro, ou plus, caractère « 9 ».

\b Terminer à une limite de mot.

Mettre en correspondance un ou plusieurs chiffres : +


Le quantificateur + correspond une ou plusieurs fois à l’élément qui précède. Il équivaut à {1,} . + est un
quantificateur gourmand dont l’équivalent paresseux est +? .
Par exemple, l’expression régulière \ban+\w*?\b tente d’établir une correspondance avec les mots entiers qui
commencent par la lettre a suivie d’une ou de plusieurs instances de la lettre n . L’exemple suivant illustre
cette expression régulière. L’expression régulière établit une correspondance avec les mots an , annual ,
announcement et antique , et pas avec les mots autumn et all .

string pattern = @"\ban+\w*?\b";

string input = "Autumn is a great time for an annual announcement to all antique collectors.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'an' found at position 27.
// 'annual' found at position 30.
// 'announcement' found at position 37.
// 'antique' found at position 57.

Dim pattern As String = "\ban+\w*?\b"

Dim input As String = "Autumn is a great time for an annual announcement to all antique collectors."
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'an' found at position 27.
' 'annual' found at position 30.
' 'announcement' found at position 37.
' 'antique' found at position 57.

Le modèle d’expression régulière est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

an+ Mettre en correspondance un « a » suivi d’un ou plusieurs


caractères « n ».

\w*? Mettre en correspondance un caractère alphabétique zéro,


une ou plusieurs fois, mais le moins de fois possible.

\b Terminer à une limite de mot.

Mettre en correspondance zéro ou une occurrence : ?


Le quantificateur ? correspond zéro ou une fois à l’élément qui précède. Il équivaut à {0,1} . ? est un
quantificateur gourmand dont l’équivalent paresseux est ?? .
Par exemple, l’expression régulière \ban?\b tente d’établir une correspondance avec les mots entiers qui
commencent par la lettre a suivie de zéro ou une instance de la lettre n . En d’autres termes, elle tente
d’établir une correspondance avec les mots a et an . L’exemple suivant illustre cette expression régulière.
string pattern = @"\ban?\b";
string input = "An amiable animal with a large snount and an animated nose.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'An' found at position 0.
// 'a' found at position 23.
// 'an' found at position 42.

Dim pattern As String = "\ban?\b"


Dim input As String = "An amiable animal with a large snount and an animated nose."
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'An' found at position 0.
' 'a' found at position 23.
' 'an' found at position 42.

Le modèle d’expression régulière est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

an? Mettre en correspondance un « a » suivi de zéro ou un


caractère « n ».

\b Terminer à une limite de mot.

Mettre en correspondance exactement n occurrences : {n}


Le { n } quantificateur n correspond à l’élément qui précède exactement n fois, où n est un entier. { n } est
un quantificateur gourmand dont l’équivalent paresseux est { n }? .
Par exemple, l’expression régulière \b\d+\,\d{3}\b tente d’établir une correspondance avec une limite de mot
suivie d’un ou de plusieurs chiffres décimaux suivis de trois chiffres décimaux suivis d’une limite de mot.
L’exemple suivant illustre cette expression régulière.

string pattern = @"\b\d+\,\d{3}\b";


string input = "Sales totaled 103,524 million in January, " +
"106,971 million in February, but only " +
"943 million in March.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// '103,524' found at position 14.
// '106,971' found at position 45.
Dim pattern As String = "\b\d+\,\d{3}\b"
Dim input As String = "Sales totaled 103,524 million in January, " + _
"106,971 million in February, but only " + _
"943 million in March."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' '103,524' found at position 14.
' '106,971' found at position 45.

Le modèle d’expression régulière est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

\d+ Mettre en correspondance un ou plusieurs chiffres décimaux.

\, Mettre en correspondance une virgule.

\d{3} Mettre en correspondance trois chiffres décimaux.

\b Terminer à une limite de mot.

Mettre en correspondance au moins n occurrences : {n,}


Le { n ,} quantificateur n correspond à l’élément qui précède au moins n fois, où n est un entier. { n ,} est
un quantificateur gourmand dont l’équivalent paresseux est { n ,}? .
Par exemple, l’expression régulière \b\d{2,}\b\D+ tente d’établir une correspondance avec une limite de mot
suivie d’au moins deux chiffres suivis d’une limite de mot et d’un caractère non numérique. L’exemple suivant
illustre cette expression régulière. L’expression régulière ne peut pas établir de correspondance avec l’expression
"7 days" , car elle ne contient qu’un chiffre. En revanche, une correspondance est établie les expressions
"10 weeks and 300 years" .

string pattern = @"\b\d{2,}\b\D+";


string input = "7 days, 10 weeks, 300 years";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// '10 weeks, ' found at position 8.
// '300 years' found at position 18.

Dim pattern As String = "\b\d{2,}\b\D+"


Dim input As String = "7 days, 10 weeks, 300 years"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' '10 weeks, ' found at position 8.
' '300 years' found at position 18.

Le modèle d’expression régulière est défini comme indiqué dans le tableau suivant.
M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

\d{2,} Mettre en correspondance au moins deux chiffres décimaux.

\b Mettre en correspondance la limite d'un mot.

\D+ Mettre en correspondance au moins un chiffre non décimal.

Mettre en correspondance entre n et m fois : {n, m}


Le { quantificateur n , m } correspond à l’élément qui précède au moins n fois, mais pas plus de m fois, où
n et m sont des entiers. { n , m } est un quantificateur gourmand dont l’équivalent paresseux est { n ,
m }? .
Dans l’exemple suivant, l’expression régulière (00\s){2,4} tente d’établir une correspondance entre deux et
quatre occurrences de deux zéros suivis d’un espace. Notez que la partie finale de la chaîne d’entrée inclut ce
modèle cinq fois au lieu du maximum de quatre. Toutefois, seule la partie initiale de cette sous-chaîne ( jusqu’à
l’espace et la cinquième paire de zéros) correspond au modèle d’expression régulière.

string pattern = @"(00\s){2,4}";


string input = "0x00 FF 00 00 18 17 FF 00 00 00 21 00 00 00 00 00";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// '00 00 ' found at position 8.
// '00 00 00 ' found at position 23.
// '00 00 00 00 ' found at position 35.

Dim pattern As String = "(00\s){2,4}"


Dim input As String = "0x00 FF 00 00 18 17 FF 00 00 00 21 00 00 00 00 00"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' '00 00 ' found at position 8.
' '00 00 00 ' found at position 23.
' '00 00 00 00 ' found at position 35.

Mettre en correspondance zéro occurrence ou plus (correspondance paresseuse ) : *?


Le quantificateur *? établit zéro, une ou plusieurs correspondances avec l’élément qui précède, mais le moins
de fois possible. Il s’agit de l’équivalent paresseux du quantificateur gourmand * .
Dans l’exemple suivant, l’expression régulière \b\w*?oo\w*?\b correspond à tous les mots qui contiennent la
chaîne oo .
string pattern = @"\b\w*?oo\w*?\b";
string input = "woof root root rob oof woo woe";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'woof' found at position 0.
// 'root' found at position 5.
// 'root' found at position 10.
// 'oof' found at position 19.
// 'woo' found at position 23.

Dim pattern As String = "\b\w*?oo\w*?\b"


Dim input As String = "woof root root rob oof woo woe"
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'woof' found at position 0.
' 'root' found at position 5.
' 'root' found at position 10.
' 'oof' found at position 19.
' 'woo' found at position 23.

Le modèle d’expression régulière est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

\w*? Correspond à zéro, un ou plusieurs caractères alphabétiques,


mais le moins de caractères possible.

oo Mettre en correspondance la chaîne « oo ».

\w*? Correspond à zéro, un ou plusieurs caractères alphabétiques,


mais le moins de caractères possible.

\b Terminer sur une limite de mot.

Mettre en correspondance une ou plusieurs occurrences (correspondance paresseuse ) : +?


Le quantificateur +? établit une ou plusieurs correspondances avec l’élément qui précède, mais le moins de
fois possible. Il s’agit de l’équivalent paresseux du quantificateur gourmand + .
Par exemple, l’expression régulière \b\w+?\b établit une correspondance avec un ou plusieurs caractères
séparés par des limites de mot. L’exemple suivant illustre cette expression régulière.
string pattern = @"\b\w+?\b";
string input = "Aa Bb Cc Dd Ee Ff";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'Aa' found at position 0.
// 'Bb' found at position 3.
// 'Cc' found at position 6.
// 'Dd' found at position 9.
// 'Ee' found at position 12.
// 'Ff' found at position 15.

Dim pattern As String = "\b\w+?\b"


Dim input As String = "Aa Bb Cc Dd Ee Ff"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'Aa' found at position 0.
' 'Bb' found at position 3.
' 'Cc' found at position 6.
' 'Dd' found at position 9.
' 'Ee' found at position 12.
' 'Ff' found at position 15.

Mettre en correspondance zéro ou une occurrence (correspondance paresseuse ) : ??


Le quantificateur ?? établit zéro ou une correspondance avec l’élément qui précède, mais le moins de fois
possible. Il s’agit de l’équivalent paresseux du quantificateur gourmand ? .
Par exemple, l’expression régulière ^\s*(System.)??Console.Write(Line)??\(?? tente d’établir une
correspondance avec les chaînes « Console.Write » ou « Console.WriteLine ». La chaîne peut également
comprendre « System. » avant « Console », et elle peut être suivie par une parenthèse ouvrante. Elle doit se
trouver au début d’une ligne, mais peut être précédée d’un espace blanc. L’exemple suivant illustre cette
expression régulière.

string pattern = @"^\s*(System.)??Console.Write(Line)??\(??";


string input = "System.Console.WriteLine(\"Hello!\")\n" +
"Console.Write(\"Hello!\")\n" +
"Console.WriteLine(\"Hello!\")\n" +
"Console.ReadLine()\n" +
" Console.WriteLine";
foreach (Match match in Regex.Matches(input, pattern,
RegexOptions.IgnorePatternWhitespace |
RegexOptions.IgnoreCase |
RegexOptions.Multiline))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'System.Console.Write' found at position 0.
// 'Console.Write' found at position 36.
// 'Console.Write' found at position 61.
// ' Console.Write' found at position 110.
Dim pattern As String = "^\s*(System.)??Console.Write(Line)??\(??"
Dim input As String = "System.Console.WriteLine(""Hello!"")" + vbCrLf + _
"Console.Write(""Hello!"")" + vbCrLf + _
"Console.WriteLine(""Hello!"")" + vbCrLf + _
"Console.ReadLine()" + vbCrLf + _
" Console.WriteLine"
For Each match As Match In Regex.Matches(input, pattern, _
RegexOptions.IgnorePatternWhitespace Or RegexOptions.IgnoreCase Or
RegexOptions.MultiLine)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'System.Console.Write' found at position 0.
' 'Console.Write' found at position 36.
' 'Console.Write' found at position 61.
' ' Console.Write' found at position 110.

Le modèle d’expression régulière est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

^ Mettre en correspondance le début du flux d’entrée.

\s* Correspond à zéro, un ou plusieurs espaces blancs.

(System.)?? Mettre en correspondance zéro ou une occurrence de la


chaîne « System. ».

Console.Write Mettre en correspondance la chaîne « Console.Write ».

(Line)?? Mettre en correspondance zéro ou une occurrence de la


chaîne « Line ».

\(?? Mettre en correspondance zéro occurrence, ou plus, de la


parenthèse ouvrante.

Mettre en correspondance exactement n occurrences (correspondance paresseuse ) : {n}?


Le { n }? quantificateur n correspond exactement à l’élément qui précède n , où n est un entier. Il s’agit de
l’équivalent paresseux du quantificateur gourmand { n } .
Dans l’exemple suivant, l’expression régulière \b(\w{3,}?\.){2}?\w{3,}?\b est utilisée pour identifier une
adresse de site web. Notez qu’elle établit une correspondance avec « www.microsoft.com » et
« msdn.microsoft.com », mais pas avec « mywebsite » ou « mycompany.com ».

string pattern = @"\b(\w{3,}?\.){2}?\w{3,}?\b";


string input = "www.microsoft.com msdn.microsoft.com mywebsite mycompany.com";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'www.microsoft.com' found at position 0.
// 'msdn.microsoft.com' found at position 18.
Dim pattern As String = "\b(\w{3,}?\.){2}?\w{3,}?\b"
Dim input As String = "www.microsoft.com msdn.microsoft.com mywebsite mycompany.com"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'www.microsoft.com' found at position 0.
' 'msdn.microsoft.com' found at position 18.

Le modèle d’expression régulière est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

(\w{3,}?\.) Mettre en correspondance au moins 3 caractères


alphabétiques, mais le moins de caractères possible, suivis
d’un point ou d’un point final. Il s'agit du premier groupe de
capture.

(\w{3,}?\.){2}? Mettre en correspondance le modèle dans le premier groupe


deux fois, mais le moins de fois possible.

\b Terminer la correspondance à la limite d'un mot.

Mettre en correspondance au moins n occurrences (correspondance paresseuse ) : {n,}?


Le { n ,}? quantificateur n correspond à l’élément qui précède au moins n fois, où n est un entier, mais le
moins de fois possible. Il s’agit de l’équivalent paresseux du quantificateur gourmand { n ,} .
Pour obtenir une illustration, consultez l’exemple du { quantificateur n }? dans la section précédente.
L’expression régulière de cet exemple utilise le { n ,} quantificateur n pour faire correspondre une chaîne
comportant au moins trois caractères suivis d’un point.
Mettre en correspondance entre n et m fois (correspondance paresseuse ) : {n,m}?
Le { quantificateur n , m }? correspond à l’élément qui précède entre n et m heures, où n et m sont des
entiers, mais le moins de fois possible. Il s’agit de l’équivalent paresseux du quantificateur gourmand { n , m
} .

Dans l’exemple suivant, l’expression régulière \b[A-Z](\w*?\s*?){1,10}[.!?] correspond aux phrases qui
contiennent entre un et dix mots. Elle établit une correspondance avec toutes les phrases de la chaîne d’entrée à
l’exception d’une phrase qui contient 18 mots.

string pattern = @"\b[A-Z](\w*?\s*?){1,10}[.!?]";


string input = "Hi. I am writing a short note. Its purpose is " +
"to test a regular expression that attempts to find " +
"sentences with ten or fewer words. Most sentences " +
"in this note are short.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);

// The example displays the following output:


// 'Hi.' found at position 0.
// 'I am writing a short note.' found at position 4.
// 'Most sentences in this note are short.' found at position 132.
Dim pattern As String = "\b[A-Z](\w*\s?){1,10}?[.!?]"
Dim input As String = "Hi. I am writing a short note. Its purpose is " + _
"to test a regular expression that attempts to find " + _
"sentences with ten or fewer words. Most sentences " + _
"in this note are short."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'Hi.' found at position 0.
' 'I am writing a short note.' found at position 4.
' 'Most sentences in this note are short.' found at position 132.

Le modèle d’expression régulière est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

[A-Z] Mettre en correspondance une majuscule de A à Z.

(\w*?\s*?) Mettre en correspondance zéro, un ou plusieurs caractères


alphabétiques, suivis d’un ou plusieurs espaces, mais le
moins de fois possible. Il s’agit du premier groupe de
capture.

{1,10} Mettre en correspondance le modèle précédent entre 1 et


10 fois.

[.!?] Mettre en correspondance un signe de ponctuation « . »,


« ! » ou « ? ».

Quantificateurs gourmands et paresseux


De nombreux quantificateurs existent en deux versions :
Une version gourmande.
Un quantificateur gourmand essaie de correspondre à un élément autant de fois que possible.
Une version non gourmande (ou paresseuse).
Un quantificateur non gourmand essaie de correspondre à un élément aussi peu de fois que possible.
Vous pouvez changer un quantificateur gourmand en quantificateur paresseux en ajoutant simplement
un ? .

Considérez une expression régulière simple censée extraire les quatre derniers chiffres d’une chaîne de
nombres telle qu’un numéro de carte de crédit. La version de l’expression régulière qui utilise le quantificateur
gourmand * est \b.*([0-9]{4})\b . Toutefois, si une chaîne contient deux nombres, cette expression régulière
correspond uniquement aux quatre derniers chiffres du deuxième nombre, comme le montre l’exemple suivant.
string greedyPattern = @"\b.*([0-9]{4})\b";
string input1 = "1112223333 3992991999";
foreach (Match match in Regex.Matches(input1, greedyPattern))
Console.WriteLine("Account ending in ******{0}.", match.Groups[1].Value);

// The example displays the following output:


// Account ending in ******1999.

Dim greedyPattern As String = "\b.*([0-9]{4})\b"


Dim input1 As String = "1112223333 3992991999"
For Each match As Match In Regex.Matches(input1, greedypattern)
Console.WriteLine("Account ending in ******{0}.", match.Groups(1).Value)
Next
' The example displays the following output:
' Account ending in ******1999.

L’expression régulière ne parvient pas à établir de correspondance avec le premier nombre ; en effet, comme le
quantificateur * tente d’établir une correspondance autant de fois que possible avec l’élément qui précède
dans la totalité de la chaîne, il trouve une correspondance à la fin de celle-ci.
Il ne s’agit pas du comportement souhaité. Vous pouvez plutôt utiliser le quantificateur paresseux *? pour
extraire les chiffres des deux nombres, comme le montre l’exemple suivant.

string lazyPattern = @"\b.*?([0-9]{4})\b";


string input2 = "1112223333 3992991999";
foreach (Match match in Regex.Matches(input2, lazyPattern))
Console.WriteLine("Account ending in ******{0}.", match.Groups[1].Value);

// The example displays the following output:


// Account ending in ******3333.
// Account ending in ******1999.

Dim lazyPattern As String = "\b.*?([0-9]{4})\b"


Dim input2 As String = "1112223333 3992991999"
For Each match As Match In Regex.Matches(input2, lazypattern)
Console.WriteLine("Account ending in ******{0}.", match.Groups(1).Value)
Next
' The example displays the following output:
' Account ending in ******3333.
' Account ending in ******1999.

Dans la plupart des cas, les expressions régulières avec des quantificateurs gourmands et paresseux retournent
les mêmes correspondances. Elles retournent le plus souvent des résultats différents quand elles sont utilisées
avec le métacaractère ( . ) générique, qui correspond à n’importe quel caractère.

Quantificateurs et correspondances vides


Les quantificateurs * , + et { n , m } et leurs équivalents paresseux ne se répètent jamais après une
correspondance vide quand le nombre minimal de captures a été trouvé. Cette règle empêche les
quantificateurs d’entrer dans des boucles infinies sur des correspondances de sous-expressions vides quand le
nombre maximal de captures de groupe possibles est infini ou proche de l’infini.
Par exemple, le code suivant montre le résultat d’un appel de la méthode Regex.Match avec le modèle
d’expression régulière (a?)* qui correspond à zéro ou un caractère « a », zéro, une ou plusieurs fois. Notez que
le seul groupe de capture capture chaque « a », ainsi que String.Empty, mais qu’il n’existe aucune deuxième
correspondance vide, car la première correspondance vide entraîne l’arrêt de la répétition du quantificateur.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "(a?)*";
string input = "aaabbb";
Match match = Regex.Match(input, pattern);
Console.WriteLine("Match: '{0}' at index {1}",
match.Value, match.Index);
if (match.Groups.Count > 1) {
GroupCollection groups = match.Groups;
for (int grpCtr = 1; grpCtr <= groups.Count - 1; grpCtr++) {
Console.WriteLine(" Group {0}: '{1}' at index {2}",
grpCtr,
groups[grpCtr].Value,
groups[grpCtr].Index);
int captureCtr = 0;
foreach (Capture capture in groups[grpCtr].Captures) {
captureCtr++;
Console.WriteLine(" Capture {0}: '{1}' at index {2}",
captureCtr, capture.Value, capture.Index);
}
}
}
}
}
// The example displays the following output:
// Match: 'aaa' at index 0
// Group 1: '' at index 3
// Capture 1: 'a' at index 0
// Capture 2: 'a' at index 1
// Capture 3: 'a' at index 2
// Capture 4: '' at index 3
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(a?)*"
Dim input As String = "aaabbb"
Dim match As Match = Regex.Match(input, pattern)
Console.WriteLine("Match: '{0}' at index {1}",
match.Value, match.Index)
If match.Groups.Count > 1 Then
Dim groups As GroupCollection = match.Groups
For grpCtr As Integer = 1 To groups.Count - 1
Console.WriteLine(" Group {0}: '{1}' at index {2}",
grpCtr,
groups(grpCtr).Value,
groups(grpCtr).Index)
Dim captureCtr As Integer = 0
For Each capture As Capture In groups(grpCtr).Captures
captureCtr += 1
Console.WriteLine(" Capture {0}: '{1}' at index {2}",
captureCtr, capture.Value, capture.Index)
Next
Next
End If
End Sub
End Module
' The example displays the following output:
' Match: 'aaa' at index 0
' Group 1: '' at index 3
' Capture 1: 'a' at index 0
' Capture 2: 'a' at index 1
' Capture 3: 'a' at index 2
' Capture 4: '' at index 3

Pour voir la différence pratique entre un groupe de capture qui définit un nombre minimal et un nombre
maximal de captures et un groupe de capture qui définit un nombre fixe de captures, examinez les modèles
d’expressions régulières (a\1|(?(1)\1)){0,2} et (a\1|(?(1)\1)){2} . Les deux expressions régulières se
composent d’un seul groupe de capture, qui est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

(a\1 Mettre en correspondance « a » avec la valeur du premier


groupe capturé …

|(?(1) … ou tester si le premier groupe capturé a été défini. (Notez


que la construction (?(1) ne définit pas un groupe de
capture.)

\1)) Si le premier groupe capturé existe, établir une


correspondance avec sa valeur. Si le groupe n’existe pas, le
groupe correspond à String.Empty.

La première expression régulière essaie de correspondre à ce modèle entre zéro et deux fois ; la deuxième,
exactement deux fois. Étant donné que le premier modèle atteint son nombre minimal de captures avec sa
première capture de String.Empty, il ne se répète jamais pour essayer d’établir une correspondance avec a\1 ;
le quantificateur {0,2} autorise uniquement les correspondances vides dans la dernière itération. En revanche,
la seconde expression régulière établit une correspondance avec « a », car elle évalue a\1 une deuxième fois ;
le nombre minimal d’itérations, 2, entraîne la répétition du moteur après une correspondance vide.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern, input;

pattern = @"(a\1|(?(1)\1)){0,2}";
input = "aaabbb";

Console.WriteLine("Regex pattern: {0}", pattern);


Match match = Regex.Match(input, pattern);
Console.WriteLine("Match: '{0}' at position {1}.",
match.Value, match.Index);
if (match.Groups.Count > 1) {
for (int groupCtr = 1; groupCtr <= match.Groups.Count - 1; groupCtr++)
{
Group group = match.Groups[groupCtr];
Console.WriteLine(" Group: {0}: '{1}' at position {2}.",
groupCtr, group.Value, group.Index);
int captureCtr = 0;
foreach (Capture capture in group.Captures) {
captureCtr++;
Console.WriteLine(" Capture: {0}: '{1}' at position {2}.",
captureCtr, capture.Value, capture.Index);
}
}
}
Console.WriteLine();

pattern = @"(a\1|(?(1)\1)){2}";
Console.WriteLine("Regex pattern: {0}", pattern);
match = Regex.Match(input, pattern);
Console.WriteLine("Matched '{0}' at position {1}.",
match.Value, match.Index);
if (match.Groups.Count > 1) {
for (int groupCtr = 1; groupCtr <= match.Groups.Count - 1; groupCtr++)
{
Group group = match.Groups[groupCtr];
Console.WriteLine(" Group: {0}: '{1}' at position {2}.",
groupCtr, group.Value, group.Index);
int captureCtr = 0;
foreach (Capture capture in group.Captures) {
captureCtr++;
Console.WriteLine(" Capture: {0}: '{1}' at position {2}.",
captureCtr, capture.Value, capture.Index);
}
}
}
}
}
// The example displays the following output:
// Regex pattern: (a\1|(?(1)\1)){0,2}
// Match: '' at position 0.
// Group: 1: '' at position 0.
// Capture: 1: '' at position 0.
//
// Regex pattern: (a\1|(?(1)\1)){2}
// Matched 'a' at position 0.
// Group: 1: 'a' at position 0.
// Capture: 1: '' at position 0.
// Capture: 2: 'a' at position 0.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern, input As String

pattern = "(a\1|(?(1)\1)){0,2}"
input = "aaabbb"

Console.WriteLine("Regex pattern: {0}", pattern)


Dim match As Match = Regex.Match(input, pattern)
Console.WriteLine("Match: '{0}' at position {1}.",
match.Value, match.Index)
If match.Groups.Count > 1 Then
For groupCtr As Integer = 1 To match.Groups.Count - 1
Dim group As Group = match.Groups(groupCtr)
Console.WriteLine(" Group: {0}: '{1}' at position {2}.",
groupCtr, group.Value, group.Index)
Dim captureCtr As Integer = 0
For Each capture As Capture In group.Captures
captureCtr += 1
Console.WriteLine(" Capture: {0}: '{1}' at position {2}.",
captureCtr, capture.Value, capture.Index)
Next
Next
End If
Console.WriteLine()

pattern = "(a\1|(?(1)\1)){2}"
Console.WriteLine("Regex pattern: {0}", pattern)
match = Regex.Match(input, pattern)
Console.WriteLine("Matched '{0}' at position {1}.",
match.Value, match.Index)
If match.Groups.Count > 1 Then
For groupCtr As Integer = 1 To match.Groups.Count - 1
Dim group As Group = match.Groups(groupCtr)
Console.WriteLine(" Group: {0}: '{1}' at position {2}.",
groupCtr, group.Value, group.Index)
Dim captureCtr As Integer = 0
For Each capture As Capture In group.Captures
captureCtr += 1
Console.WriteLine(" Capture: {0}: '{1}' at position {2}.",
captureCtr, capture.Value, capture.Index)
Next
Next
End If
End Sub
End Module
' The example displays the following output:
' Regex pattern: (a\1|(?(1)\1)){0,2}
' Match: '' at position 0.
' Group: 1: '' at position 0.
' Capture: 1: '' at position 0.
'
' Regex pattern: (a\1|(?(1)\1)){2}
' Matched 'a' at position 0.
' Group: 1: 'a' at position 0.
' Capture: 1: '' at position 0.
' Capture: 2: 'a' at position 0.

Voir aussi
Langage des expressions régulières - Aide-mémoire
Rétroaction
Constructions de backreference dans les expressions
régulières
18/07/2020 • 16 minutes to read • Edit Online

Les références arrière offrent un moyen pratique d’identifier un caractère répété ou une sous-chaîne répétée dans
une chaîne. Par exemple, si la chaîne d’entrée contient plusieurs occurrences d’une sous-chaîne arbitraire, vous
pouvez faire correspondre la première occurrence à un groupe de capture, puis utiliser une référence arrière pour
faire correspondre les occurrences suivantes de la sous-chaîne.

NOTE
Une syntaxe distincte est utilisée pour faire référence à des groupes de capture nommés et numérotés dans les chaînes de
remplacement. Pour plus d’informations, consultez Substitutions.

.NET définit des éléments de langage différents pour faire référence aux groupes de capture nommés et
numérotés. Pour plus d’informations sur les groupes de capture, consultez Constructions de regroupement.

Références arrière numérotées


Une référence arrière numérotée utilise la syntaxe suivante :
\ nombre
où numéro est la position ordinale du groupe de capture dans l’expression régulière. Par exemple, \4
correspond au contenu du quatrième groupe de capture. Si numéro n’est pas défini dans le modèle d’expression
régulière, une erreur d’analyse se produit et le moteur d’expression régulière lève une exception
ArgumentException. Par exemple, l’expression régulière \b(\w+)\s\1 est valide, car (\w+) est le premier et
unique groupe de capture dans l’expression. D’un autre côté, \b(\w+)\s\2 n’est pas valide et lève une exception
d’argument, car il n’existe aucun groupe de capture numéroté \2 . En outre, si nombre identifie un groupe de
capture dans une position ordinale particulière, mais qu’un nom numérique différent de sa position ordinale a été
affecté au groupe de capture, l’analyseur d’expression régulière lève également une ArgumentException.
Remarquez l’ambiguïté entre les codes d’échappement octaux (tels que \16 ) et les références arrière \ numéro
qui utilisent la même notation. Cette ambiguïté est résolue comme suit :
Les expressions \1 à \9 sont toujours interprétées comme références arrière et non comme codes
octaux.
Si le premier chiffre d’une expression à plusieurs chiffres est 8 ou 9 (comme \80 ou \91 ), l’expression est
interprétée comme littéral.
Les expressions à partir de \10 et plus sont considérées comme des références arrière s’il existe une
référence arrière correspondant à ce numéro ; sinon, elles sont interprétées comme des codes octaux.
Si une expression régulière contient une référence arrière à un numéro de groupe non défini, une erreur
d’analyse se produit et le moteur d’expression régulière lève une exception ArgumentException.
Si l’ambiguïté est un problème, vous pouvez utiliser la notation de \k< nom > , qui n’est pas ambiguë et ne
peut pas être confondue avec les codes de caractères octaux. De même, les codes hexadécimaux tels que \xdd ne
sont pas ambigus et ne peuvent pas être confondus avec les références arrière.
L’exemple suivant recherche des caractères de mot doubles dans une chaîne. Il définit une expression régulière,
(\w)\1 , qui se compose des éléments suivants.

ÉL ÉM EN T DESC RIP T IO N

(\w) Mettre en correspondance un caractère de mot et l’affecter


au premier groupe de capture.

\1 Mettre en correspondance le caractère suivant dont la valeur


est identique à celle du premier groupe de capture.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(\w)\1";
string input = "trellis llama webbing dresser swagger";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("Found '{0}' at position {1}.",
match.Value, match.Index);
}
}
// The example displays the following output:
// Found 'll' at position 3.
// Found 'll' at position 8.
// Found 'bb' at position 16.
// Found 'ss' at position 25.
// Found 'gg' at position 33.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(\w)\1"
Dim input As String = "trellis llama webbing dresser swagger"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Found '{0}' at position {1}.", _
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Found 'll' at position 3.
' Found 'll' at position 8.
' Found 'bb' at position 16.
' Found 'ss' at position 25.
' Found 'gg' at position 33.

Références arrière nommées


Une référence arrière nommée est définie avec la syntaxe suivante :
\k< nom >
ou :
\k' nom '
où nom est le nom d’un groupe de capture défini dans le modèle d’expression régulière. Si nom n’est pas défini
dans le modèle d’expression régulière, une erreur d’analyse se produit et le moteur d’expression régulière lève
une exception ArgumentException.
L’exemple suivant recherche des caractères de mot doubles dans une chaîne. Il définit une expression régulière,
(?<char>\w)\k<char> , qui se compose des éléments suivants.

ÉL ÉM EN T DESC RIP T IO N

(?<char>\w) Mettre en correspondance un caractère de mot et l’affecter à


un groupe de capture nommé char .

\k<char> Mettre en correspondance le caractère suivant dont la valeur


est identique à celle du groupe de capture char .

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(?<char>\w)\k<char>";
string input = "trellis llama webbing dresser swagger";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("Found '{0}' at position {1}.",
match.Value, match.Index);
}
}
// The example displays the following output:
// Found 'll' at position 3.
// Found 'll' at position 8.
// Found 'bb' at position 16.
// Found 'ss' at position 25.
// Found 'gg' at position 33.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(?<char>\w)\k<char>"
Dim input As String = "trellis llama webbing dresser swagger"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Found '{0}' at position {1}.", _
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Found 'll' at position 3.
' Found 'll' at position 8.
' Found 'bb' at position 16.
' Found 'ss' at position 25.
' Found 'gg' at position 33.

Références arrières numériques nommées


Dans une référence arrière nommée avec \k , nom peut également être la représentation d’un nombre sous
forme de chaîne. Par exemple, l’exemple suivant utilise l’expression régulière (?<2>\w)\k<2> pour rechercher des
caractères de mot doubles dans une chaîne. Dans ce cas, l’exemple définit un groupe de capture explicitement
nommé « 2 », et la référence arrière est nommée en conséquence « 2 ».

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(?<2>\w)\k<2>";
string input = "trellis llama webbing dresser swagger";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("Found '{0}' at position {1}.",
match.Value, match.Index);
}
}
// The example displays the following output:
// Found 'll' at position 3.
// Found 'll' at position 8.
// Found 'bb' at position 16.
// Found 'ss' at position 25.
// Found 'gg' at position 33.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(?<2>\w)\k<2>"
Dim input As String = "trellis llama webbing dresser swagger"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Found '{0}' at position {1}.", _
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Found 'll' at position 3.
' Found 'll' at position 8.
' Found 'bb' at position 16.
' Found 'ss' at position 25.
' Found 'gg' at position 33.

Si nom est la représentation d’un nombre sous forme de chaîne et qu’aucun groupe de capture n’a ce nom, \k<
nom > est identique au \ * nombre* de la référence arrière, où nombre est la position ordinale de la capture.
Dans l’exemple suivant, il existe un seul groupe de capture nommé char . La construction de la référence arrière
la référence en tant que \k<1> . Comme la sortie de l’exemple le montre, l’appel à Regex.IsMatch réussit, car char
est le premier groupe de capture.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
Console.WriteLine(Regex.IsMatch("aa", @"(?<char>\w)\k<1>"));
// Displays "True".
}
}
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Console.WriteLine(Regex.IsMatch("aa", "(?<char>\w)\k<1>"))
' Displays "True".
End Sub
End Module

Cependant, si nom est la représentation sous forme de chaîne d’un nombre et qu’un nom numérique a été affecté
explicitement à ce groupe de capture dans cette position, l’analyseur d’expression régulière ne peut pas identifier
le groupe de capture par sa position ordinale. Au lieu de cela, elle lève une exception ArgumentException . Le seul
groupe de capture dans l’exemple suivant est nommé « 2 ». Comme la construction \k est utilisée pour définir
une référence arrière nommée « 1 », l’analyseur d’expression régulière ne peut pas identifier le premier groupe de
capture et lève une exception.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
Console.WriteLine(Regex.IsMatch("aa", @"(?<2>\w)\k<1>"));
// Throws an ArgumentException.
}
}

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Console.WriteLine(Regex.IsMatch("aa", "(?<2>\w)\k<1>"))
' Throws an ArgumentException.
End Sub
End Module

Correspondance des références arrière


Une référence arrière référence la définition la plus récente d’un groupe (la définition la plus immédiatement à
gauche, dans le cadre d’une mise en correspondance de gauche à droite). Quand un groupe effectue plusieurs
captures, une référence arrière référence la capture la plus récente.
L’exemple suivant inclut un modèle d’expression régulière, (?<1>a)(?<1>\1b)* , qui redéfinit le groupe nommé \1.
Le tableau suivant décrit chaque modèle dans l’expression régulière.

M O DÈL E DESC RIP T IO N

(?<1>a) Mettre en correspondance le caractère « a » et affecter le


résultat au groupe de capture nommé 1 .
M O DÈL E DESC RIP T IO N

(?<1>\1b)* Mettre en correspondance zéro ou plus d’occurrences du


groupe nommé 1 avec un « b » et affecter le résultat au
groupe de capture nommé 1 .

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"(?<1>a)(?<1>\1b)*";
string input = "aababb";
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine("Match: " + match.Value);
foreach (Group group in match.Groups)
Console.WriteLine(" Group: " + group.Value);
}
}
}
// The example displays the following output:
// Group: aababb
// Group: abb

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(?<1>a)(?<1>\1b)*"
Dim input As String = "aababb"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Match: " + match.Value)
For Each group As Group In match.Groups
Console.WriteLIne(" Group: " + group.Value)
Next
Next
End Sub
End Module
' The example display the following output:
' Group: aababb
' Group: abb

En comparant l’expression régulière à la chaîne d’entrée (« aababb »), le moteur d’expression régulière effectue les
opérations suivantes :
1. Il commence au début de la chaîne et réussit à mettre en correspondance « a » avec l’expression (?<1>a) .
La valeur du groupe 1 est maintenant « a ».
2. Il passe au deuxième caractère et réussit à mettre en correspondance la chaîne « ab » avec l’expression
\1b , ou « ab ». Il affecte ensuite le résultat, « ab », à \1 .

3. Il passe au quatrième caractère. L’expression (?<1>\1b)* doit être mise en correspondance zéro fois ou
plus, donc il réussit à mettre en correspondance la chaîne « abb » avec l’expression \1b . Il réaffecte le
résultat, « abb », à \1 .

Dans cet exemple, * est un quantificateur en boucle : il est évalué à plusieurs reprises jusqu’à ce que le moteur
d’expression régulière ne puisse pas mettre en correspondance le modèle qu’il définit. Les quantificateurs en
boucle ne suppriment pas les définitions de groupe.
Si un groupe n’a capturé aucune sous-chaîne, aucune référence arrière à ce groupe n’est définie et aucune
correspondance n’est trouvée. Ce point est illustré par le modèle d’expression régulière
\b(\p{Lu}{2})(\d{2})?(\p{Lu}{2})\b qui est défini comme suit :

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

(\p{Lu}{2}) Mettre en correspondance deux lettres majuscules. Il s'agit du


premier groupe de capture.

(\d{2})? Mettre en correspondance zéro ou une occurrence de deux


chiffres décimaux. Il s'agit du deuxième groupe de capture.

(\p{Lu}{2}) Mettre en correspondance deux lettres majuscules. Il s'agit du


troisième groupe de capture.

\b Terminer la correspondance à la limite d'un mot.

Une chaîne d’entrée peut mettre en correspondre cette expression régulière même si les deux chiffres décimaux
définis par le deuxième groupe de capture ne sont pas présents. L’exemple suivant montre que, même si la mise
en correspondance réussit, un groupe de capture vide est trouvé entre deux groupes de capture trouvés.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\p{Lu}{2})(\d{2})?(\p{Lu}{2})\b";
string[] inputs = { "AA22ZZ", "AABB" };
foreach (string input in inputs)
{
Match match = Regex.Match(input, pattern);
if (match.Success)
{
Console.WriteLine("Match in {0}: {1}", input, match.Value);
if (match.Groups.Count > 1)
{
for (int ctr = 1; ctr <= match.Groups.Count - 1; ctr++)
{
if (match.Groups[ctr].Success)
Console.WriteLine("Group {0}: {1}",
ctr, match.Groups[ctr].Value);
else
Console.WriteLine("Group {0}: <no match>", ctr);
}
}
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match in AA22ZZ: AA22ZZ
// Group 1: AA
// Group 2: 22
// Group 3: ZZ
//
// Match in AABB: AABB
// Group 1: AA
// Group 2: <no match>
// Group 3: BB
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\p{Lu}{2})(\d{2})?(\p{Lu}{2})\b"
Dim inputs() As String = {"AA22ZZ", "AABB"}
For Each input As String In inputs
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine("Match in {0}: {1}", input, match.Value)
If match.Groups.Count > 1 Then
For ctr As Integer = 1 To match.Groups.Count - 1
If match.Groups(ctr).Success Then
Console.WriteLine("Group {0}: {1}", _
ctr, match.Groups(ctr).Value)
Else
Console.WriteLine("Group {0}: <no match>", ctr)
End If
Next
End If
End If
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match in AA22ZZ: AA22ZZ
' Group 1: AA
' Group 2: 22
' Group 3: ZZ
'
' Match in AABB: AABB
' Group 1: AA
' Group 2: <no match>
' Group 3: BB

Voir aussi
Langage des expressions régulières - Aide-mémoire
Constructions d'alternative dans les expressions
régulières
18/03/2020 • 14 minutes to read • Edit Online

Les constructions d'alternative modifient une expression régulière pour permettre la correspondance de type
inclusif/exclusif ou conditionnelle. .NET prend en charge trois constructions d’alternative :
Utilisation des critères spéciaux avec |
Correspondance conditionnelle avec (?(expression)oui|non)
Correspondance conditionnelle selon un groupe capturé valide

Utilisation des critères spéciaux avec |


Vous pouvez utiliser la barre verticale ( | ) pour mettre en correspondance un modèle d’une série, dans laquelle
le caractère | sépare chaque modèle.
Tout comme la classe de caractères positive, le caractère | peut être utilisé pour mettre en correspondance
n’importe quel nombre de caractères uniques. L’exemple suivant utilise une classe de caractères positive et des
critères spéciaux de type inclusif/exclusif avec le caractère | pour trouver des occurrences des mots « gray » ou
« grey » dans une chaîne. Dans ce cas, le caractère | produit une expression régulière qui est plus détaillée.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
// Regular expression using character class.
string pattern1 = @"\bgr[ae]y\b";
// Regular expression using either/or.
string pattern2 = @"\bgr(a|e)y\b";

string input = "The gray wolf blended in among the grey rocks.";
foreach (Match match in Regex.Matches(input, pattern1))
Console.WriteLine("'{0}' found at position {1}",
match.Value, match.Index);
Console.WriteLine();
foreach (Match match in Regex.Matches(input, pattern2))
Console.WriteLine("'{0}' found at position {1}",
match.Value, match.Index);
}
}
// The example displays the following output:
// 'gray' found at position 4
// 'grey' found at position 35
//
// 'gray' found at position 4
// 'grey' found at position 35
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
' Regular expression using character class.
Dim pattern1 As String = "\bgr[ae]y\b"
' Regular expression using either/or.
Dim pattern2 As String = "\bgr(a|e)y\b"

Dim input As String = "The gray wolf blended in among the grey rocks."
For Each match As Match In Regex.Matches(input, pattern1)
Console.WriteLine("'{0}' found at position {1}", _
match.Value, match.Index)
Next
Console.WriteLine()
For Each match As Match In Regex.Matches(input, pattern2)
Console.WriteLine("'{0}' found at position {1}", _
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' 'gray' found at position 4
' 'grey' found at position 35
'
' 'gray' found at position 4
' 'grey' found at position 35

L’expression régulière | qui \bgr(a|e)y\b utilise le personnage, , est interprétée comme indiqué dans le tableau
suivant:

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

gr Mettre en correspondance les caractères « gr ».

(a|e) Mettre en correspondance un « a » ou un « e ».

y\b Mettre en correspondance un « y » à la limite d'un mot.

Le caractère | peut également être utilisé pour effectuer une correspondance de type inclusif/exclusif avec
plusieurs caractères ou sous-expressions, qui peuvent inclure toute combinaison de caractère littéraux et
éléments de langage d’expressions régulières. (La classe de caractère ne fournit pas cette fonctionnalité.)
L’exemple suivant | utilise le personnage pour extraire soit un numéro de sécurité sociale des États-Unis (SSN),
qui est un numéro à 9 chiffres avec le format ddd-ddddd**dd-, ou un numéro d’identification des employeurs des
États-Unis (EIN), qui est un numéro à 9 chiffres avec le format-ddddddd. dd
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b";
string input = "01-9999999 020-333333 777-88-9999";
Console.WriteLine("Matches for {0}:", pattern);
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(" {0} at position {1}", match.Value, match.Index);
}
}
// The example displays the following output:
// Matches for \b(\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b:
// 01-9999999 at position 0
// 777-88-9999 at position 22

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b"
Dim input As String = "01-9999999 020-333333 777-88-9999"
Console.WriteLine("Matches for {0}:", pattern)
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(" {0} at position {1}", match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Matches for \b(\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b:
' 01-9999999 at position 0
' 777-88-9999 at position 22

L’expression \b(\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b régulière est interprétée comme indiquée dans le tableau


suivant :

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

(\d{2}-\d{7}|\d{3}-\d{2}-\d{4}) Mettre en correspondance l'un ou l'autre des éléments


suivants : deux chiffres décimaux suivis d'un trait d'union suivi
de sept chiffres décimaux, ou alors trois chiffres décimaux, un
trait d'union, deux chiffres décimaux, un autre trait d'union et
quatre chiffres décimaux.

\d Terminer la correspondance à la limite d'un mot.

Correspondance conditionnelle avec une expression


Cet élément de langage tente de mettre en correspondance un modèle parmi deux fournis selon qu'il parvient ou
non à mettre en correspondance un modèle initial. Sa syntaxe est la suivante :
(?( expression ) oui | non )
lorsque l’expression est le modèle initial pour correspondre, oui est le modèle à assortir si l’expression est
assortie, et non est le modèle facultatif pour correspondre si l’expression n’est pas assortie. Le moteur
d’expression régulière traite l’expression comme une assertion de largeur nulle ; c’est-à-dire que le moteur
d’expression régulière n’avance pas dans le flux d’entrée après avoir évalué l’expression. Par conséquent, cette
construction est équivalente à la suivante :
(?(?= expression ) oui | non )
où (?= l’expression ) est une construction d’affirmation à largeur zéro. (Pour plus d’informations, voir
Grouping Constructs.) Parce que le moteur d’expression régulière interprète l’expression comme une ancre (une
affirmation de largeur zéro), l’expression doit soit être une affirmation de largeur zéro (pour plus d’informations,
voir Anchors) ou une sous-expression qui est également contenue dans oui. Sinon, aucune correspondance ne
peut être établie avec le modèle oui .

NOTE
Si l’expression est un groupe de capture nommé ou numéroté, la construction d’alternance est interprétée comme un test
de capture; pour plus d’informations, voir la section suivante, Conditional Matching Based on a Valid Capture Group. En
d'autres termes, le moteur des expressions régulières ne tente pas de mettre en correspondance la sous-chaîne capturée,
mais à la place teste la présence ou l'absence du groupe.

L’exemple suivant est une variante de celui donné dans la section Critères spéciaux de type inclusif/exclusif avec |.
Il utilise la mise en correspondance conditionnelle pour déterminer si les trois premiers caractères après une
limite de mot se composent de deux chiffres suivis d'un trait d'union. Si c'est le cas, il tente de mettre en
correspondance un numéro d'identification de l'employeur (EIN) américain. Si ce n'est pas le cas, il tente de
mettre en correspondance un numéro de sécurité sociale (SSN) américain.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(?(\d{2}-)\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b";
string input = "01-9999999 020-333333 777-88-9999";
Console.WriteLine("Matches for {0}:", pattern);
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(" {0} at position {1}", match.Value, match.Index);
}
}
// The example displays the following output:
// Matches for \b(\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b:
// 01-9999999 at position 0
// 777-88-9999 at position 22
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(?(\d{2}-)\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b"
Dim input As String = "01-9999999 020-333333 777-88-9999"
Console.WriteLine("Matches for {0}:", pattern)
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(" {0} at position {1}", match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Matches for \b(?(\d{2}-)\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b:
' 01-9999999 at position 0
' 777-88-9999 at position 22

Le modèle \b(?(\d{2}-)\d{2}-\d{7}|\d{3}-\d{2}-\d{4})\b d’expression ordinaire est interprété comme indiqué


dans le tableau suivant :

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

(?(\d{2}-) Déterminer si les trois caractères suivants se composent de


deux chiffres suivis d'un trait d'union.

\d{2}-\d{7} Si le modèle précédent correspond, mettre en


correspondance deux chiffres suivis d'un trait d'union suivi de
sept chiffres.

\d{3}-\d{2}-\d{4} Si le modèle ne correspond pas, faire correspondre trois


chiffres décimaux, un trait d'union, deux chiffres décimaux, un
autre trait d'union et quatre chiffres décimaux.

\b Mettre en correspondance la limite d'un mot.

Correspondance conditionnelle selon un groupe capturé valide


Cet élément de langage essaie de faire correspondre l'un de deux modèles selon qu'il peut correspondre à un
groupe capturé spécifié. Sa syntaxe est la suivante :
(?( name ) oui | non )

or
(?( nombre ) oui | non )

lorsque le nom est le nom et le numéro est le nombre d’un groupe de capture, oui est l’expression pour
correspondre si le nom ou le numéro a une correspondance, et non est l’expression facultative pour correspondre
si elle ne le fait pas.
Si le nom ne correspond pas au nom d'un groupe de capture utilisé dans le modèle d'expression régulière, la
construction alternative est interprétée comme un test d'expression, comme expliqué dans la section précédente.
Typiquement, cela signifie que false l’expression évalue à . Si le nombre ne correspond pas à un groupe de
capture numéroté utilisé dans le modèle d'expression régulière, le moteur des expressions régulières lève une
ArgumentException.
L’exemple suivant est une variante de celui donné dans la section Critères spéciaux de type inclusif/exclusif avec |.
Il utilise un groupe de capture nommé n2 qui se compose de deux chiffres suivis d'un trait d'union. La
construction d'alternative tests si ce groupe de capture a été mis en correspondance dans la chaîne d'entrée. Si
c'est le cas, la construction alternative essaie de mettre en correspondance les sept derniers chiffres d'un numéro
d'identification de l'employeur (EIN) américain. Si ce n'est le cas, il essaie de faire correspondre un numéro de
sécurité sociale (SSN) américain.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(?<n2>\d{2}-)?(?(n2)\d{7}|\d{3}-\d{2}-\d{4})\b";
string input = "01-9999999 020-333333 777-88-9999";
Console.WriteLine("Matches for {0}:", pattern);
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(" {0} at position {1}", match.Value, match.Index);
}
}
// The example displays the following output:
// Matches for \b(?<n2>\d{2}-)?(?(n2)\d{7}|\d{3}-\d{2}-\d{4})\b:
// 01-9999999 at position 0
// 777-88-9999 at position 22

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(?<n2>\d{2}-)?(?(n2)\d{7}|\d{3}-\d{2}-\d{4})\b"
Dim input As String = "01-9999999 020-333333 777-88-9999"
Console.WriteLine("Matches for {0}:", pattern)
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(" {0} at position {1}", match.Value, match.Index)
Next
End Sub
End Module

Le modèle \b(?<n2>\d{2}-)?(?(n2)\d{7}|\d{3}-\d{2}-\d{4})\b d’expression ordinaire est interprété comme


indiqué dans le tableau suivant :

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

(?<n2>\d{2}-)? Mettre en correspondance zéro ou une occurrence de deux


chiffres suivis d'un trait d'union. Nommer ce groupe de
capture n2 .

(?(n2) Testez si n2 a été mis en correspondance dans la chaîne


d'entrée.

\d{7} Si n2 a été mis en correspondance, faites correspondre sept


chiffres décimaux.
M O DÈL E DESC RIP T IO N

|\d{3}-\d{2}-\d{4} Si n2 ne correspondait pas, faites correspondre trois chiffres


décimaux, un trait d'union, deux chiffres décimaux, un autre
trait d'union et quatre chiffres décimaux.

\b Mettre en correspondance la limite d'un mot.

Une variation de cet exemple qui utilise un groupe numéroté au lieu d'un groupe nommé est illustrée dans
l'exemple suivant. Son modèle d'expression régulière est \b(\d{2}-)?(?(1)\d{7}|\d{3}-\d{2}-\d{4})\b .

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\d{2}-)?(?(1)\d{7}|\d{3}-\d{2}-\d{4})\b";
string input = "01-9999999 020-333333 777-88-9999";
Console.WriteLine("Matches for {0}:", pattern);
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(" {0} at position {1}", match.Value, match.Index);
}
}
// The example display the following output:
// Matches for \b(\d{2}-)?(?(1)\d{7}|\d{3}-\d{2}-\d{4})\b:
// 01-9999999 at position 0
// 777-88-9999 at position 22

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\d{2}-)?(?(1)\d{7}|\d{3}-\d{2}-\d{4})\b"
Dim input As String = "01-9999999 020-333333 777-88-9999"
Console.WriteLine("Matches for {0}:", pattern)
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(" {0} at position {1}", match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Matches for \b(\d{2}-)?(?(1)\d{7}|\d{3}-\d{2}-\d{4})\b:
' 01-9999999 at position 0
' 777-88-9999 at position 22

Voir aussi
Langage d’expression régulière - Référence rapide
Substitutions dans les expressions régulières
18/07/2020 • 27 minutes to read • Edit Online

Les substitutions sont des éléments de langage reconnus uniquement dans des modèles de remplacement. Elles
utilisent un modèle d'expression régulière pour définir tout ou partie du texte qui doit remplacer le texte
correspondant dans la chaîne d'entrée. Le modèle de remplacement peut se composer d'une ou plusieurs
substitutions avec des caractères littéraux. Les modèles de remplacement sont fournis aux surcharges de la
méthode Regex.Replace qui a un paramètre replacement et à la méthode Match.Result . Les méthodes remplacent
le modèle correspondant par le modèle défini par le paramètre replacement .
Le .NET Framework définit les éléments de substitution répertoriés dans le tableau suivant.

SUB ST IT UT IO N DESC RIP T IO N

$ certain Inclut la dernière sous-chaîne correspondant au groupe de


capture identifié par nombre, où nombre est une valeur
décimale, dans la chaîne de remplacement. Pour plus
d'informations, consultez Substitution d'un groupe numéroté.

${ nom } Comprend la dernière sous-chaîne correspondant au groupe


nommé désigné par (?< nom > ) dans la chaîne de
remplacement. Pour plus d'informations, consultez
Substitution d'un groupe nommé.

$$ Inclut un littéral « $ » unique dans la chaîne de remplacement.


Pour plus d'informations, consultez Substitution d'un symbole
« $ ».

$& Inclut une copie de la correspondance entière dans la chaîne


de remplacement. Pour plus d'informations, consultez
Substitution de la correspondance entière.

$` Inclut tout le texte de la chaîne d'entrée avant la


correspondance dans la chaîne de remplacement. Pour plus
d'informations, consultez Substitution du texte avant la
correspondance.

$' Inclut tout le texte de la chaîne d'entrée après la


correspondance dans la chaîne de remplacement. Pour plus
d'informations, consultez Substitution du texte après la
correspondance.

$+ Inclut le dernier groupe capturé dans la chaîne de


remplacement. Pour plus d'informations, consultez
Substitution du dernier groupe capturé.

$_ Inclut la chaîne d'entrée entière dans la chaîne de


remplacement. Pour plus d'informations, consultez
Substitution de la chaîne d'entrée entière.

Éléments de substitution et modèles de remplacement


Les substitutions sont les seules constructions particulières acceptées dans un modèle de remplacement. Aucun
des autres éléments de langage d'expression régulière, notamment les caractères d'échappement et le point ( . ),
qui correspond à n'importe quel caractère, n'est pris en charge. De la même façon, les éléments de langage de
substitution sont reconnus uniquement dans les modèles de remplacement et ne sont jamais valides dans les
modèles d'expressions régulières.
Le seul caractère qui peut apparaître dans un modèle d'expression régulière ou dans une substitution est le
caractère $ , bien qu'il ait une signification différente dans chaque contexte. Dans un modèle d'expression
régulière, $ est une ancre qui correspond à la fin de la chaîne. Dans un modèle de remplacement, $ indique le
début d'une substitution.

NOTE
Pour les fonctionnalités semblables à un modèle de remplacement dans une expression régulière, utilisez une référence
arrière. Pour plus d’informations sur les références arrière, consultez constructions de backreference.

Substitution d'un groupe numéroté


L' $ élément de langage nombre comprend la dernière sous-chaîne correspondant au groupe de capture nombre
dans la chaîne de remplacement, où nombre est l’index du groupe de capture. Par exemple, le modèle de
remplacement $1 indique que la sous-chaîne correspondante sera remplacée par le premier groupe capturé.
Pour plus d’informations sur les groupes de capture numérotés, consultez Grouping Constructs.
Tous les chiffres qui suivent $ sont interprétés comme appartenant au groupe nombre . Si ce n'est pas votre
intention, vous pouvez remplacer un groupe nommé à la place. Par exemple, vous pouvez utiliser la chaîne de
remplacement ${1}1 au lieu de $11 pour définir la chaîne de remplacement comme la valeur du premier groupe
capturé avec le numéro « 1 ». Pour plus d'informations, consultez Substitution d'un groupe nommé.
Les groupes de capture auxquels des noms ne sont pas explicitement assignés à l’aide de la (?< name >)
syntaxe de nom sont numérotés de gauche à droite en commençant à un. Les groupes nommés sont également
numérotés de gauche à droite, en démarrant à un numéro de plus que l'index du dernier groupe sans nom. Par
exemple, dans l'expression régulière (\w)(?<digit>\d) , l'index du groupe nommé digit est 2.
Si le nombre ne spécifie pas un groupe de capture valide défini dans le modèle d'expression régulière, $ nombre
est interprété comme une séquence de caractères littéraux utilisée pour remplacer chaque correspondance.
L'exemple suivant utilise la substitution $ nombre pour supprimer le symbole monétaire d'une valeur décimale.
Elle supprime les symboles monétaires trouvés au début ou à la fin d'une valeur monétaire, et reconnaît les deux
séparateurs décimaux les plus courants (« . » et « , »).

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\p{Sc}*(\s?\d+[.,]?\d*)\p{Sc}*";
string replacement = "$1";
string input = "$16.32 12.19 £16.29 €18.29 €18,29";
string result = Regex.Replace(input, pattern, replacement);
Console.WriteLine(result);
}
}
// The example displays the following output:
// 16.32 12.19 16.29 18.29 18,29
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\p{Sc}*(\s?\d+[.,]?\d*)\p{Sc}*"
Dim replacement As String = "$1"
Dim input As String = "$16.32 12.19 £16.29 €18.29 €18,29"
Dim result As String = Regex.Replace(input, pattern, replacement)
Console.WriteLine(result)
End Sub
End Module
' The example displays the following output:
' 16.32 12.19 16.29 18.29 18,29

Le modèle d'expression régulière \p{Sc}*(\s?\d+[.,]?\d*)\p{Sc}* est défini comme indiqué dans le tableau
suivant.

M O DÈL E DESC RIP T IO N

\p{Sc}* Mettre en correspondance zéro ou plusieurs caractères de


symbole monétaire.

\s? Mettre en correspondance zéro ou des espaces blancs.

\d+ Mettre en correspondance un ou plusieurs chiffres décimaux.

[.,]? Mettre en correspondance zéro ou un point ou une virgule.

\d* Met en correspondance zéro ou plusieurs chiffres décimaux.

(\s?\d+[.,]?\d*) Mettre en correspondance un espace blanc suivi par un ou


plusieurs chiffres décimaux, suivi par zéro ou un point ou une
virgule, suivi par zéro ou plusieurs chiffres décimaux. Il s'agit
du premier groupe de capture. Étant donné que le modèle de
remplacement est $1 , l'appel à la méthode Regex.Replace
remplace l'intégralité de la sous-chaîne correspondante par ce
groupe capturé.

Substitution d'un groupe nommé


L' ${ name } élément langage de nom remplace la dernière sous-chaîne correspondant au groupe de capture
nom , où nom est le nom d’un groupe de capture défini par l' (?< élément de langage nom >) . Pour plus
d’informations sur les groupes de capture nommés, consultez Grouping Constructs.
Si le nom ne spécifie pas de groupe de capture nommé valide défini dans le modèle d'expression régulière mais se
compose de chiffres, ${ nom } est interprété en tant que groupe numéroté.
Si le nom ne spécifie ni un groupe de capture nommé valide ni un groupe de capture numéroté valide défini dans
le modèle d'expression régulière, ${ nom } est interprété comme une séquence de caractères littéraux utilisée
pour remplacer chaque correspondance.
L’exemple suivant utilise la ${ substitution de nom } pour supprimer le symbole monétaire d’une valeur
décimale. Elle supprime les symboles monétaires trouvés au début ou à la fin d'une valeur monétaire, et reconnaît
les deux séparateurs décimaux les plus courants (« . » et « , »).
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\p{Sc}*(?<amount>\s?\d+[.,]?\d*)\p{Sc}*";
string replacement = "${amount}";
string input = "$16.32 12.19 £16.29 €18.29 €18,29";
string result = Regex.Replace(input, pattern, replacement);
Console.WriteLine(result);
}
}
// The example displays the following output:
// 16.32 12.19 16.29 18.29 18,29

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\p{Sc}*(?<amount>\s?\d+[.,]?\d*)\p{Sc}*"
Dim replacement As String = "${amount}"
Dim input As String = "$16.32 12.19 £16.29 €18.29 €18,29"
Dim result As String = Regex.Replace(input, pattern, replacement)
Console.WriteLine(result)
End Sub
End Module
' The example displays the following output:
' 16.32 12.19 16.29 18.29 18,29

Le modèle d'expression régulière \p{Sc}*(?<amount>\s?\d[.,]?\d*)\p{Sc}* est défini comme indiqué dans le


tableau suivant.

M O DÈL E DESC RIP T IO N

\p{Sc}* Mettre en correspondance zéro ou plusieurs caractères de


symbole monétaire.

\s? Mettre en correspondance zéro ou des espaces blancs.

\d+ Mettre en correspondance un ou plusieurs chiffres décimaux.

[.,]? Mettre en correspondance zéro ou un point ou une virgule.

\d* Met en correspondance zéro ou plusieurs chiffres décimaux.

(?<amount>\s?\d[.,]?\d*) Mettre en correspondance un espace blanc, suivi par un ou


plusieurs chiffres décimaux, suivi par zéro ou un point ou une
virgule, suivi par zéro ou plusieurs chiffres décimaux. C'est le
groupe de capture nommé amount . Étant donné que le
modèle de remplacement est ${amount} , l'appel à la
méthode Regex.Replace remplace l'intégralité de la sous-
chaîne correspondante par ce groupe capturé.

Substitution d'un caractère « $ »


La substitution $$ insère un caractère « $ » littéral dans la chaîne remplacée.
L'exemple suivant utilise l'objet NumberFormatInfo pour déterminer le symbole monétaire de la culture actuelle et
son positionnement dans une chaîne monétaire. Il génère alors à la fois dynamiquement un modèle d'expression
régulière et un modèle de remplacement. Si l'exemple est exécuté sur un ordinateur dont la culture actuelle est en-
US, il génère le modèle d'expression régulière \b(\d+)(\.(\d+))? et le modèle de remplacement $$ $1$2 . Le
modèle de remplacement remplace le texte correspondant par un symbole monétaire et un espace suivi par les
premier et second groupes capturés.

using System;
using System.Globalization;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
// Define array of decimal values.
string[] values= { "16.35", "19.72", "1234", "0.99"};
// Determine whether currency precedes (True) or follows (False) number.
bool precedes = NumberFormatInfo.CurrentInfo.CurrencyPositivePattern % 2 == 0;
// Get decimal separator.
string cSeparator = NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator;
// Get currency symbol.
string symbol = NumberFormatInfo.CurrentInfo.CurrencySymbol;
// If symbol is a "$", add an extra "$".
if (symbol == "$") symbol = "$$";

// Define regular expression pattern and replacement string.


string pattern = @"\b(\d+)(" + cSeparator + @"(\d+))?";
string replacement = "$1$2";
replacement = precedes ? symbol + " " + replacement : replacement + " " + symbol;
foreach (string value in values)
Console.WriteLine("{0} --> {1}", value, Regex.Replace(value, pattern, replacement));
}
}
// The example displays the following output:
// 16.35 --> $ 16.35
// 19.72 --> $ 19.72
// 1234 --> $ 1234
// 0.99 --> $ 0.99
Imports System.Globalization
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
' Define array of decimal values.
Dim values() As String = {"16.35", "19.72", "1234", "0.99"}
' Determine whether currency precedes (True) or follows (False) number.
Dim precedes As Boolean = (NumberFormatInfo.CurrentInfo.CurrencyPositivePattern Mod 2 = 0)
' Get decimal separator.
Dim cSeparator As String = NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator
' Get currency symbol.
Dim symbol As String = NumberFormatInfo.CurrentInfo.CurrencySymbol
' If symbol is a "$", add an extra "$".
If symbol = "$" Then symbol = "$$"

' Define regular expression pattern and replacement string.


Dim pattern As String = "\b(\d+)(" + cSeparator + "(\d+))?"
Dim replacement As String = "$1$2"
replacement = If(precedes, symbol + " " + replacement, replacement + " " + symbol)
For Each value In values
Console.WriteLine("{0} --> {1}", value, Regex.Replace(value, pattern, replacement))
Next
End Sub
End Module
' The example displays the following output:
' 16.35 --> $ 16.35
' 19.72 --> $ 19.72
' 1234 --> $ 1234
' 0.99 --> $ 0.99

Le modèle d'expression régulière \b(\d+)(\.(\d+))? est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Démarrer la correspondance au début d'une limite de mot.

(\d+) Mettre en correspondance un ou plusieurs chiffres décimaux.


Il s'agit du premier groupe de capture.

\. Mettre en correspondance un point (le séparateur décimal).

(\d+) Mettre en correspondance un ou plusieurs chiffres décimaux.


Il s'agit du troisième groupe de capture.

(\.(\d+))? Mettre en correspondance zéro ou une occurrence d'un point


suivi par un ou plusieurs chiffres décimaux. Il s'agit du
deuxième groupe de capture.

Substitution de la correspondance entière


La substitution $& inclut la correspondance entière dans la chaîne de remplacement. Souvent, elle est utilisée
pour ajouter une sous-chaîne au début ou à la fin de la chaîne correspondante. Par exemple, le modèle de
remplacement ($&) ajoute des parenthèses au début et à la fin de chaque correspondance. S'il n'y a pas de
correspondance, la substitution $& n'a aucun effet.
L'exemple suivant utilise la substitution $& pour ajouter des guillemets au début et à la fin de titres de livres
stockés dans un tableau de chaînes.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"^(\w+\s?)+$";
string[] titles = { "A Tale of Two Cities",
"The Hound of the Baskervilles",
"The Protestant Ethic and the Spirit of Capitalism",
"The Origin of Species" };
string replacement = "\"$&\"";
foreach (string title in titles)
Console.WriteLine(Regex.Replace(title, pattern, replacement));
}
}
// The example displays the following output:
// "A Tale of Two Cities"
// "The Hound of the Baskervilles"
// "The Protestant Ethic and the Spirit of Capitalism"
// "The Origin of Species"

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^(\w+\s?)+$"
Dim titles() As String = {"A Tale of Two Cities", _
"The Hound of the Baskervilles", _
"The Protestant Ethic and the Spirit of Capitalism", _
"The Origin of Species"}
Dim replacement As String = """$&"""
For Each title As String In titles
Console.WriteLine(Regex.Replace(title, pattern, replacement))
Next
End Sub
End Module
' The example displays the following output:
' "A Tale of Two Cities"
' "The Hound of the Baskervilles"
' "The Protestant Ethic and the Spirit of Capitalism"
' "The Origin of Species"

Le modèle d'expression régulière ^(\w+\s?)+$ est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

^ Commencer la correspondance au début de la chaîne


d'entrée.

(\w+\s?)+ Mettre en correspondance le modèle d'un ou plusieurs


caractères de mot, suivis de zéro ou d'un espace blanc, une ou
plusieurs fois.

$ Mettre en correspondance la fin de la chaîne d'entrée.

Le modèle de remplacement "$&" ajoute un guillemet littéral au début et à la fin de chaque correspondance.

Substitution du texte avant la correspondance


La substitution $` remplace la chaîne correspondante par la chaîne d'entrée entière avant la correspondance.
Autrement dit, elle duplique la chaîne d'entrée jusqu'à la correspondance en supprimant le texte correspondant.
N'importe quel texte qui suit le texte correspondant est inchangé dans la chaîne de résultat. S'il existe plusieurs
correspondances dans une chaîne d'entrée, le texte de remplacement est dérivé de la chaîne d'entrée d'origine,
plutôt que de la chaîne dans laquelle le texte a été remplacé par des correspondances précédentes. (L’exemple
fournit une illustration. ) S’il n’y a aucune correspondance, la $` substitution n’a aucun effet.
L'exemple suivant utilise le modèle d'expression régulière \d+ pour faire correspondre une séquence d'un ou de
plusieurs chiffres décimaux dans la chaîne d'entrée. La chaîne de remplacement $` remplace ces chiffres par le
texte qui précède la correspondance.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "aa1bb2cc3dd4ee5";
string pattern = @"\d+";
string substitution = "$`";
Console.WriteLine("Matches:");
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(" {0} at position {1}", match.Value, match.Index);

Console.WriteLine("Input string: {0}", input);


Console.WriteLine("Output string: " +
Regex.Replace(input, pattern, substitution));
}
}
// The example displays the following output:
// Matches:
// 1 at position 2
// 2 at position 5
// 3 at position 8
// 4 at position 11
// 5 at position 14
// Input string: aa1bb2cc3dd4ee5
// Output string: aaaabbaa1bbccaa1bb2ccddaa1bb2cc3ddeeaa1bb2cc3dd4ee
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "aa1bb2cc3dd4ee5"
Dim pattern As String = "\d+"
Dim substitution As String = "$`"
Console.WriteLine("Matches:")
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(" {0} at position {1}", match.Value, match.Index)
Next
Console.WriteLine("Input string: {0}", input)
Console.WriteLine("Output string: " + _
Regex.Replace(input, pattern, substitution))
End Sub
End Module
' The example displays the following output:
' Matches:
' 1 at position 2
' 2 at position 5
' 3 at position 8
' 4 at position 11
' 5 at position 14
' Input string: aa1bb2cc3dd4ee5
' Output string: aaaabbaa1bbccaa1bb2ccddaa1bb2cc3ddeeaa1bb2cc3dd4ee

Dans cet exemple, la chaîne d'entrée "aa1bb2cc3dd4ee5" contient cinq correspondances. Le tableau suivant illustre
comment la substitution $` entraîne le remplacement de chaque correspondance dans la chaîne d'entrée par le
moteur des expressions régulières. Le texte inséré est affiché en gras dans la colonne de résultats.

C H A ÎN E AVA N T L A
C O RRESP O N D P O SIT IO N C O RRESP O N DA N C E C H A ÎN E DE RÉSULTAT

1 2 aa aaaa bb2cc3dd4ee5

2 5 aa1bb aaaabbaa1bb cc3dd4ee5

3 8 aa1bb2cc aaaabbaa1bbccaa1bb2ccdd
4ee5

4 11 aa1bb2cc3dd aaaabbaa1bbccaa1bb2ccdda
a1bb2cc3dd ee5

5 14 aa1bb2cc3dd4ee aaaabbaa1bbccaa1bb2ccdda
a1bb2cc3ddeeaa1bb2cc3d
d4ee

Substitution du texte après la correspondance


La substitution $' remplace la chaîne correspondante par la chaîne d'entrée entière après la correspondance.
Autrement dit, elle duplique la chaîne d'entrée après la correspondance en supprimant le texte correspondant.
N'importe quel texte qui précède le texte correspondant est inchangé dans la chaîne de résultat. S'il n'y a pas de
correspondance, la substitution $' n'a aucun effet.
L'exemple suivant utilise le modèle d'expression régulière \d+ pour faire correspondre une séquence d'un ou de
plusieurs chiffres décimaux dans la chaîne d'entrée. La chaîne de remplacement $' remplace ces chiffres par le
texte qui suit la correspondance.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "aa1bb2cc3dd4ee5";
string pattern = @"\d+";
string substitution = "$'";
Console.WriteLine("Matches:");
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(" {0} at position {1}", match.Value, match.Index);
Console.WriteLine("Input string: {0}", input);
Console.WriteLine("Output string: " +
Regex.Replace(input, pattern, substitution));
}
}
// The example displays the following output:
// Matches:
// 1 at position 2
// 2 at position 5
// 3 at position 8
// 4 at position 11
// 5 at position 14
// Input string: aa1bb2cc3dd4ee5
// Output string: aabb2cc3dd4ee5bbcc3dd4ee5ccdd4ee5ddee5ee

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "aa1bb2cc3dd4ee5"
Dim pattern As String = "\d+"
Dim substitution As String = "$'"
Console.WriteLine("Matches:")
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(" {0} at position {1}", match.Value, match.Index)
Next
Console.WriteLine("Input string: {0}", input)
Console.WriteLine("Output string: " + _
Regex.Replace(input, pattern, substitution))
End Sub
End Module
' The example displays the following output:
' Matches:
' 1 at position 2
' 2 at position 5
' 3 at position 8
' 4 at position 11
' 5 at position 14
' Input string: aa1bb2cc3dd4ee5
' Output string: aabb2cc3dd4ee5bbcc3dd4ee5ccdd4ee5ddee5ee

Dans cet exemple, la chaîne d'entrée "aa1bb2cc3dd4ee5" contient cinq correspondances. Le tableau suivant illustre
comment la substitution $' entraîne le remplacement de chaque correspondance dans la chaîne d'entrée par le
moteur des expressions régulières. Le texte inséré est affiché en gras dans la colonne de résultats.

C H A ÎN E A P RÈS L A
C O RRESP O N D P O SIT IO N C O RRESP O N DA N C E C H A ÎN E DE RÉSULTAT
C H A ÎN E A P RÈS L A
C O RRESP O N D P O SIT IO N C O RRESP O N DA N C E C H A ÎN E DE RÉSULTAT

1 2 bb2cc3dd4ee5 aabb2cc3dd4ee5 bb2cc3dd


4ee5

2 5 cc3dd4ee5 aabb2cc3dd4ee5bbcc3dd4
ee5 cc3dd4ee5

3 8 dd4ee5 aabb2cc3dd4ee5bbcc3dd4e
e5ccdd4ee5 dd4ee5

4 11 ee5 aabb2cc3dd4ee5bbcc3dd4e
e5ccdd4ee5ddee5 ee5

5 14 String.Empty aabb2cc3dd4ee5bbcc3dd4e
e5ccdd4ee5ddee5ee

Substitution du dernier groupe capturé


La substitution $+ remplace la chaîne correspondante par le dernier groupe capturé. S'il n'y a pas de groupes
capturés ou si la valeur du dernier groupe capturé est String.Empty, la substitution $+ n'a aucun effet.
L'exemple suivant identifie des mots en double dans une chaîne et utilise la substitution $+ pour les remplacer
par une occurrence unique du mot. L'option RegexOptions.IgnoreCase est utilisée pour vérifier que les mots, qui
diffèrent en termes de casse mais qui sont identiques par ailleurs, sont considérés comme des doublons.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\w+)\s\1\b";
string substitution = "$+";
string input = "The the dog jumped over the fence fence.";
Console.WriteLine(Regex.Replace(input, pattern, substitution,
RegexOptions.IgnoreCase));
}
}
// The example displays the following output:
// The dog jumped over the fence.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\w+)\s\1\b"
Dim substitution As String = "$+"
Dim input As String = "The the dog jumped over the fence fence."
Console.WriteLine(Regex.Replace(input, pattern, substitution, _
RegexOptions.IgnoreCase))
End Sub
End Module
' The example displays the following output:
' The dog jumped over the fence.
Le modèle d'expression régulière \b(\w+)\s\1\b est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

(\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques. Il s'agit du premier groupe de capture.

\s Mettre en correspondance un espace blanc.

\1 Mettre en correspondance le premier groupe capturé.

\b Terminer la correspondance à la limite d'un mot.

Substitution de la chaîne d'entrée entière


La substitution $_ remplace la chaîne correspondante par la chaîne d'entrée entière. Autrement dit, elle supprime
le texte correspondant et le remplace par la chaîne entière, notamment le texte correspondant.
L'exemple suivant correspond à un ou plusieurs chiffres décimaux dans la chaîne d'entrée. Il utilise la substitution
$_ pour les remplacer par la chaîne d'entrée entière.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "ABC123DEF456";
string pattern = @"\d+";
string substitution = "$_";
Console.WriteLine("Original string: {0}", input);
Console.WriteLine("String with substitution: {0}",
Regex.Replace(input, pattern, substitution));
}
}
// The example displays the following output:
// Original string: ABC123DEF456
// String with substitution: ABCABC123DEF456DEFABC123DEF456

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "ABC123DEF456"
Dim pattern As String = "\d+"
Dim substitution As String = "$_"
Console.WriteLine("Original string: {0}", input)
Console.WriteLine("String with substitution: {0}", _
Regex.Replace(input, pattern, substitution))
End Sub
End Module
' The example displays the following output:
' Original string: ABC123DEF456
' String with substitution: ABCABC123DEF456DEFABC123DEF456
Dans cet exemple, la chaîne d'entrée "ABC123DEF456" contient deux correspondances. Le tableau suivant illustre
comment la substitution $_ entraîne le remplacement de chaque correspondance dans la chaîne d'entrée par le
moteur des expressions régulières. Le texte inséré est affiché en gras dans la colonne de résultats.

C O RRESP O N D P O SIT IO N C O RRESP O N D C H A ÎN E DE RÉSULTAT

1 3 123 ABCABC123DEF456 DEF45


6

2 5 456 ABCABC123DEF456DEFAB
C123DEF456

Voir aussi
Langage des expressions régulières - Aide-mémoire
Options des expressions régulières
18/07/2020 • 70 minutes to read • Edit Online

Par défaut, la comparaison d’une chaîne d’entrée avec des caractères littéraux dans un modèle
d’expression régulière respecte la casse, l’espace blanc dans un modèle d’expression régulière est
interprété comme un espace blanc littéral et les groupes de capture dans une expression régulière sont
nommés de manière implicite aussi bien qu’explicite. Vous pouvez modifier ces aspects, ainsi que d'autres,
du comportement par défaut des expressions régulières en spécifiant des options d'expression régulière.
Ces options, qui sont répertoriées dans le tableau suivant, peuvent être incluses inline dans le cadre du
modèle d’expression régulière ou fournies à un constructeur de classe
System.Text.RegularExpressions.Regex ou à une méthode de mise en correspondance de modèle statique
en tant que valeur d’énumération System.Text.RegularExpressions.RegexOptions.

M EM B RE REGEXO P T IO N S C A RA C T ÈRE IN L IN E EF F ET

None Non disponible Utilise le comportement par défaut.


Pour plus d’informations, consultez
Options par défaut.

IgnoreCase i Utilise la correspondance qui ne


respecte pas la casse. Pour plus
d’informations, consultez
Correspondance qui ne respecte pas
la casse.

Multiline m Utilise le mode multiligne, où ^ et


$ correspondent au début et à la
fin de chaque ligne (plutôt qu'au
début et à la fin de la chaîne
d'entrée). Pour plus d’informations,
consultez Mode multiligne.

Singleline s Utilise le mode à ligne simple, où le


point (.) correspond à chaque
caractère (y compris \n ). Pour plus
d’informations, consultez mode à
ligne simple.

ExplicitCapture n Ne capture aucun groupe sans nom.


Les seules captures valides sont les
groupes explicitement nommés ou
numérotés de la forme (?< nom >
sous-expression ) . Pour plus
d’informations, consultez Captures
explicites uniquement.

Compiled Non disponible Compile l'expression régulière en un


assembly. Pour plus d’informations,
consultez Expressions régulières
compilées.
M EM B RE REGEXO P T IO N S C A RA C T ÈRE IN L IN E EF F ET

IgnorePatternWhitespace x Exclure du modèle l'espace blanc


sans séquence d'échappement et
autoriser les commentaires après un
signe dièse ( # ). Pour plus
d’informations, consultez Ignorer
l’espace blanc.

RightToLeft Non disponible Modifie le sens de la recherche. La


recherche s'effectue de droite à
gauche au lieu de gauche à droite.
Pour plus d’informations, consultez
Mode de recherche de droite à
gauche.

ECMAScript Non disponible Active le comportement compatible


ECMAScript pour l’expression. Pour
plus d’informations, consultez
Comportement de correspondance
ECMAScript.

CultureInvariant Non disponible Ignorer les différences culturelles


propres à la langue. Pour plus
d’informations, consultez
Comparaison utilisant la culture dite
indifférente.

Spécification des options


Vous pouvez spécifier les options des expressions régulières de trois façons :
Dans le paramètre options d’un constructeur de classe System.Text.RegularExpressions.Regex ou
d’une méthode de mise en correspondance de modèle statique ( Shared en Visual Basic), comme
Regex(String, RegexOptions) ou Regex.Match(String, String, RegexOptions). Le paramètre options
est une combinaison OR au niveau du bit de valeurs énumérées
System.Text.RegularExpressions.RegexOptions.
Quand des options sont fournies à une instance de Regex à l'aide du paramètre options d'un
constructeur de classe, les options sont affectées à la propriété
System.Text.RegularExpressions.RegexOptions. Cependant, la propriété
System.Text.RegularExpressions.RegexOptions ne reflète pas les options inline dans le modèle
d'expression régulière lui-même.
L'exemple suivant en est l'illustration. Il utilise le paramètre options de la méthode
Regex.Match(String, String, RegexOptions) pour autoriser la correspondance qui ne respecte pas la
casse et pour ignorer l’espace blanc du modèle pendant l’identification des mots commençant par
la lettre « d ».
string pattern = @"d \w+ \s";
string input = "Dogs are decidedly good pets.";
RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace;

foreach (Match match in Regex.Matches(input, pattern, options))


Console.WriteLine("'{0}// found at index {1}.", match.Value, match.Index);
// The example displays the following output:
// 'Dogs // found at index 0.
// 'decidedly // found at index 9.

Dim pattern As String = "d \w+ \s"


Dim input As String = "Dogs are decidedly good pets."
Dim options As RegexOptions = RegexOptions.IgnoreCase Or RegexOptions.IgnorePatternWhitespace

For Each match As Match In Regex.Matches(input, pattern, options)


Console.WriteLine("'{0}' found at index {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'Dogs ' found at index 0.
' 'decidedly ' found at index 9.

En appliquant des options inline dans un modèle d'expression régulière avec la syntaxe
(?imnsx-imnsx) . L’option s’applique au modèle depuis le point où elle est définie jusqu’à la fin du
modèle ou jusqu’au point auquel sa définition est annulée par une autre option inline. Notez que la
propriété System.Text.RegularExpressions.RegexOptions d'une instance de Regex ne reflète pas ces
options inline. Pour plus d’informations, consultez la rubrique Constructions diverses.
L'exemple suivant en est l'illustration. Il utilise des options inline pour autoriser la correspondance
qui ne respecte pas la casse et pour ignorer l’espace blanc du modèle pendant l’identification des
mots commençant par la lettre « d ».

string pattern = @"(?ix) d \w+ \s";


string input = "Dogs are decidedly good pets.";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine("'{0}// found at index {1}.", match.Value, match.Index);
// The example displays the following output:
// 'Dogs // found at index 0.
// 'decidedly // found at index 9.

Dim pattern As String = "\b(?ix) d \w+ \s"


Dim input As String = "Dogs are decidedly good pets."

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("'{0}' found at index {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'Dogs ' found at index 0.
' 'decidedly ' found at index 9.

En appliquant des options inline dans une construction de regroupement particulière dans un
modèle d’expression régulière avec la syntaxe sous- (?imnsx-imnsx: expression ) . L'absence de
signe avant un jeu d'options active ce dernier, tandis qu'un signe moins le désactive. ( ? est une
partie fixe de la syntaxe de la construction de langage qui est obligatoire, que les options soient
activées ou désactivées.) L’option s’applique uniquement à ce groupe. Pour plus d’informations,
consultez Constructions de regroupement.
L'exemple suivant en est l'illustration. Il utilise des options inline dans une construction de
regroupement pour autoriser la correspondance qui ne respecte pas la casse et pour ignorer
l’espace blanc du modèle pendant l’identification des mots commençant par la lettre « d ».

string pattern = @"\b(?ix: d \w+)\s";


string input = "Dogs are decidedly good pets.";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine("'{0}// found at index {1}.", match.Value, match.Index);
// The example displays the following output:
// 'Dogs // found at index 0.
// 'decidedly // found at index 9.

Dim pattern As String = "\b(?ix: d \w+)\s"


Dim input As String = "Dogs are decidedly good pets."

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("'{0}' found at index {1}.", match.Value, match.Index)
Next
' The example displays the following output:
' 'Dogs ' found at index 0.
' 'decidedly ' found at index 9.

Si des options sont spécifiées inline, un signe moins ( - ) avant une option ou un jeu d'options désactive
ces options. Par exemple, la construction inline (?ix-ms) active les options RegexOptions.IgnoreCase et
RegexOptions.IgnorePatternWhitespace et désactive les options RegexOptions.Multiline et
RegexOptions.Singleline. Toutes les options d'expression régulière sont désactivées par défaut.

NOTE
Si les options d'expression régulière spécifiées dans le paramètre options d'un appel de constructeur ou de
méthode entrent en conflit avec les options spécifiées inline dans un modèle d'expression régulière, ces dernières
sont utilisées.

Les cinq options d'expression régulière suivantes peuvent être définies avec le paramètre options et
inline :
RegexOptions.IgnoreCase
RegexOptions.Multiline
RegexOptions.Singleline
RegexOptions.ExplicitCapture
RegexOptions.IgnorePatternWhitespace
Les cinq options d'expression régulière suivantes peuvent être définies avec le paramètre options , mais
ne peuvent pas être définies inline :
RegexOptions.None
RegexOptions.Compiled
RegexOptions.RightToLeft
RegexOptions.CultureInvariant
RegexOptions.ECMAScript
Détermination des options
Vous pouvez déterminer les options initialement fournies à un objet Regex au moment de son
instanciation en récupérant la valeur de la propriété Regex.Options en lecture seule. Cette propriété est
particulièrement utile pour déterminer les options qui sont définies pour une expression régulière
compilée créée par la méthode Regex.CompileToAssembly.
Pour tester la présence d'une option autre que RegexOptions.None, effectuez une opération AND avec la
valeur de la propriété Regex.Options et la valeur RegexOptions qui vous intéresse. Ensuite, déterminez si le
résultat est égal à cette valeur RegexOptions. L'exemple suivant détermine si l'option
RegexOptions.IgnoreCase a été définie.

if ((rgx.Options & RegexOptions.IgnoreCase) == RegexOptions.IgnoreCase)


Console.WriteLine("Case-insensitive pattern comparison.");
else
Console.WriteLine("Case-sensitive pattern comparison.");

If (rgx.Options And RegexOptions.IgnoreCase) = RegexOptions.IgnoreCase Then


Console.WriteLine("Case-insensitive pattern comparison.")
Else
Console.WriteLine("Case-sensitive pattern comparison.")
End If

Pour tester la présence de RegexOptions.None, déterminez si la valeur de la propriété Regex.Options est


égale à RegexOptions.None, comme le montre l'exemple suivant.

if (rgx.Options == RegexOptions.None)
Console.WriteLine("No options have been set.");

If rgx.Options = RegexOptions.None Then


Console.WriteLine("No options have been set.")
End If

Les sections suivantes répertorient les options prises en charge par les expressions régulières dans .NET.

Options par défaut


L'option RegexOptions.None indique qu'aucune option n'a été spécifiée et que le moteur d'expression
régulière utilise son comportement par défaut. Ce dernier est détaillé ci-après :
Le modèle est interprété en tant qu'expression régulière canonique, plutôt qu'en tant qu'expression
régulière ECMAScript.
Le modèle d'expression régulière est appliqué à la chaîne d'entrée de la gauche vers la droite.
Les comparaisons respectent la casse.
Les éléments de langage ^ et $ correspondent au début et à la fin de la chaîne d'entrée.
L'élément de langage . correspond à chaque caractère à l'exception de \n .
Tout espace blanc dans un modèle d'expression régulière est interprété en tant qu'espace littéral.
Les conventions de la culture actuelle sont utilisées pendant la comparaison du modèle à la chaîne
d'entrée.
Les groupes de capture dans le modèle d’expression régulière sont implicites aussi bien
qu’explicites.

NOTE
L'option RegexOptions.None n'a pas d'équivalent inline. Quand les options d'expression régulière sont appliquées
inline, le comportement par défaut est restauré option par option, via la désactivation de chaque option concernée.
Par exemple, (?i) active la comparaison sans respect de la casse, tandis que (?-i) restaure la comparaison
avec respect de la casse par défaut.

Comme l'option RegexOptions.None représente le comportement par défaut du moteur d'expression


régulière, elle est rarement spécifiée de manière explicite dans un appel de méthode. Un constructeur ou
une méthode de mise en correspondance de modèle statique sans paramètre options est appelé à la
place.

Correspondance qui ne respecte pas la casse


L'option IgnoreCase, ou l'option inline i , fournit une correspondance qui ne respecte pas la casse. Par
défaut, les conventions de gestion de la casse de la culture actuelle sont utilisées.
L'exemple suivant définit un modèle d'expression régulière, \bthe\w*\b , qui met en correspondance tous
les mots commençant par « the ». Comme le premier appel de la méthode Match utilise la comparaison
avec respect de la casse par défaut, la chaîne « The » n'apparaît pas parmi les résultats de ce premier
appel. Par contre, elle est trouvée quand la méthode Match est appelée avec le paramètre options défini
sur IgnoreCase.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\bthe\w*\b";
string input = "The man then told them about that event.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index);

Console.WriteLine();
foreach (Match match in Regex.Matches(input, pattern,
RegexOptions.IgnoreCase))
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index);
}
}
// The example displays the following output:
// Found then at index 8.
// Found them at index 18.
//
// Found The at index 0.
// Found then at index 8.
// Found them at index 18.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\bthe\w*\b"
Dim input As String = "The man then told them about that event."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index)
Next
Console.WriteLine()
For Each match As Match In Regex.Matches(input, pattern, _
RegexOptions.IgnoreCase)
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Found then at index 8.
' Found them at index 18.
'
' Found The at index 0.
' Found then at index 8.
' Found them at index 18.

L'exemple suivant modifie le modèle d'expression régulière proposé dans l'exemple précédent de manière
à utiliser des options inline au lieu du paramètre options pour effectuer une comparaison sans respect de
la casse. Le premier modèle définit l’option de non-respect de la casse dans une construction de
regroupement qui s’applique uniquement à la lettre « t » de la chaîne « the ». Comme la construction de
l’option intervient au début du modèle, le second modèle applique l’option de non-respect de la casse à
l’expression régulière entière.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(?i:t)he\w*\b";
string input = "The man then told them about that event.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index);

Console.WriteLine();
pattern = @"(?i)\bthe\w*\b";
foreach (Match match in Regex.Matches(input, pattern,
RegexOptions.IgnoreCase))
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index);
}
}
// The example displays the following output:
// Found The at index 0.
// Found then at index 8.
// Found them at index 18.
//
// Found The at index 0.
// Found then at index 8.
// Found them at index 18.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(?i:t)he\w*\b"
Dim input As String = "The man then told them about that event."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index)
Next
Console.WriteLine()
pattern = "(?i)\bthe\w*\b"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Found {0} at index {1}.", match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' Found The at index 0.
' Found then at index 8.
' Found them at index 18.
'
' Found The at index 0.
' Found then at index 8.
' Found them at index 18.

Mode multiligne
L'option RegexOptions.Multiline, ou l'option inline m , permet au moteur d'expression régulière de gérer
une chaîne d'entrée constituée de plusieurs lignes. Elle modifie l'interprétation des éléments de langage
^ et $ de manière à ce qu'ils correspondent au début et à la fin d'une ligne, et non au début et à la fin de
la chaîne d'entrée.
Par défaut, $ correspond uniquement à la fin de la chaîne d'entrée. Si vous spécifiez l'option
RegexOptions.Multiline, elle correspond au caractère de saut de ligne ( \n ) ou à la fin de la chaîne
d'entrée. Toutefois, elle ne correspond pas à la combinaison de caractères retour chariot/saut de ligne.
Pour les mettre en correspondance, utilisez la sous-expression \r?$ au lieu de simplement $ .
L’exemple suivant extrait les prénoms et scores des lanceurs, puis les ajoute à une collection
SortedList<TKey,TValue> qui les trie dans l’ordre décroissant. La méthode Matches est appelée deux fois.
Dans le premier appel de la méthode, l'expression régulière est ^(\w+)\s(\d+)$ et aucune option n'est
définie. Comme le montre la sortie, aucune correspondance n’est trouvée, car le moteur d’expression
régulière ne peut pas mettre en correspondance le modèle d’entrée avec le début et la fin de la chaîne
d’entrée. Dans le second appel de la méthode, l'expression régulière devient ^(\w+)\s(\d+)\r?$ et les
options sont définies sur RegexOptions.Multiline. Comme le montre la sortie, les noms et scores sont
correctement mis en correspondance, et les scores apparaissent dans l'ordre décroissant.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
SortedList<int, string> scores = new SortedList<int, string>(new DescendingComparer<int>());

string input = "Joe 164\n" +


"Sam 208\n" +
"Allison 211\n" +
"Gwen 171\n";
string pattern = @"^(\w+)\s(\d+)$";
bool matched = false;

Console.WriteLine("Without Multiline option:");


foreach (Match match in Regex.Matches(input, pattern))
{
scores.Add(Int32.Parse(match.Groups[2].Value), (string) match.Groups[1].Value);
matched = true;
}
if (! matched)
Console.WriteLine(" No matches.");
Console.WriteLine();

// Redefine pattern to handle multiple lines.


pattern = @"^(\w+)\s(\d+)\r*$";
Console.WriteLine("With multiline option:");
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.Multiline))
scores.Add(Int32.Parse(match.Groups[2].Value), (string) match.Groups[1].Value);

// List scores in descending order.


foreach (KeyValuePair<int, string> score in scores)
Console.WriteLine("{0}: {1}", score.Value, score.Key);
}
}

public class DescendingComparer<T> : IComparer<T>


{
public int Compare(T x, T y)
{
return Comparer<T>.Default.Compare(x, y) * -1;
}
}
// The example displays the following output:
// Without Multiline option:
// No matches.
//
// With multiline option:
// Allison: 211
// Sam: 208
// Gwen: 171
// Joe: 164
Imports System.Collections.Generic
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim scores As New SortedList(Of Integer, String)(New DescendingComparer(Of Integer)())

Dim input As String = "Joe 164" + vbCrLf + _


"Sam 208" + vbCrLf + _
"Allison 211" + vbCrLf + _
"Gwen 171" + vbCrLf
Dim pattern As String = "^(\w+)\s(\d+)$"
Dim matched As Boolean = False

Console.WriteLine("Without Multiline option:")


For Each match As Match In Regex.Matches(input, pattern)
scores.Add(CInt(match.Groups(2).Value), match.Groups(1).Value)
matched = True
Next
If Not matched Then Console.WriteLine(" No matches.")
Console.WriteLine()

' Redefine pattern to handle multiple lines.


pattern = "^(\w+)\s(\d+)\r*$"
Console.WriteLine("With multiline option:")
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.Multiline)
scores.Add(CInt(match.Groups(2).Value), match.Groups(1).Value)
Next
' List scores in descending order.
For Each score As KeyValuePair(Of Integer, String) In scores
Console.WriteLine("{0}: {1}", score.Value, score.Key)
Next
End Sub
End Module

Public Class DescendingComparer(Of T) : Implements IComparer(Of T)


Public Function Compare(x As T, y As T) As Integer _
Implements IComparer(Of T).Compare
Return Comparer(Of T).Default.Compare(x, y) * -1
End Function
End Class
' The example displays the following output:
' Without Multiline option:
' No matches.
'
' With multiline option:
' Allison: 211
' Sam: 208
' Gwen: 171
' Joe: 164

Le modèle d'expression régulière ^(\w+)\s(\d+)\r*$ est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

^ Commencer au début de la ligne.

(\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques. Il s'agit du premier groupe de capture.

\s Mettre en correspondance un espace blanc.


M O DÈL E DESC RIP T IO N

(\d+) Mettre en correspondance un ou plusieurs chiffres


décimaux. Il s'agit du deuxième groupe de capture.

\r? Mettre en correspondance zéro ou un caractère de retour


chariot.

$ Terminer à la fin de la ligne.

L'exemple suivant est équivalent à l'exemple précédent, à la différence qu'il utilise l'option inline (?m)
pour définir l'option multiligne.

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
SortedList<int, string> scores = new SortedList<int, string>(new DescendingComparer<int>());

string input = "Joe 164\n" +


"Sam 208\n" +
"Allison 211\n" +
"Gwen 171\n";
string pattern = @"(?m)^(\w+)\s(\d+)\r*$";

foreach (Match match in Regex.Matches(input, pattern, RegexOptions.Multiline))


scores.Add(Convert.ToInt32(match.Groups[2].Value), match.Groups[1].Value);

// List scores in descending order.


foreach (KeyValuePair<int, string> score in scores)
Console.WriteLine("{0}: {1}", score.Value, score.Key);
}
}

public class DescendingComparer<T> : IComparer<T>


{
public int Compare(T x, T y)
{
return Comparer<T>.Default.Compare(x, y) * -1;
}
}
// The example displays the following output:
// Allison: 211
// Sam: 208
// Gwen: 171
// Joe: 164
Imports System.Collections.Generic
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim scores As New SortedList(Of Integer, String)(New DescendingComparer(Of Integer)())

Dim input As String = "Joe 164" + vbCrLf + _


"Sam 208" + vbCrLf + _
"Allison 211" + vbCrLf + _
"Gwen 171" + vbCrLf
Dim pattern As String = "(?m)^(\w+)\s(\d+)\r*$"

For Each match As Match In Regex.Matches(input, pattern, RegexOptions.Multiline)


scores.Add(CInt(match.Groups(2).Value), match.Groups(1).Value)
Next
' List scores in descending order.
For Each score As KeyValuePair(Of Integer, String) In scores
Console.WriteLine("{0}: {1}", score.Value, score.Key)
Next
End Sub
End Module

Public Class DescendingComparer(Of T) : Implements IComparer(Of T)


Public Function Compare(x As T, y As T) As Integer _
Implements IComparer(Of T).Compare
Return Comparer(Of T).Default.Compare(x, y) * -1
End Function
End Class
' The example displays the following output:
' Allison: 211
' Sam: 208
' Gwen: 171
' Joe: 164

Mode à ligne simple


L'option RegexOptions.Singleline, ou l'option inline s , indique au moteur d'expression régulière de traiter
la chaîne d'entrée comme si elle était composée d'une seule ligne. Pour ce faire, elle modifie le
comportement de l'élément de langage point ( . ) de manière à mettre en correspondance chaque
caractère, y compris le caractère de saut de ligne \n ou \u000A.
L'exemple suivant montre comment l'utilisation de l'option . modifie le comportement de l'élément de
langage RegexOptions.Singleline. L'expression régulière ^.+ commence au début de la chaîne et
correspond à tous les caractères. Par défaut, la correspondance se termine à la fin de la première ligne ; le
modèle d'expression régulière correspond au retour chariot, à \r ou à \u000D, mais il ne correspond pas
à \n . Étant donné que l'option RegexOptions.Singleline interprète la chaîne d'entrée entière comme une
ligne unique, il correspond à chaque caractère de la chaîne d'entrée, notamment \n .
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "^.+";
string input = "This is one line and" + Environment.NewLine + "this is the second.";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(Regex.Escape(match.Value));

Console.WriteLine();
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.Singleline))
Console.WriteLine(Regex.Escape(match.Value));
}
}
// The example displays the following output:
// This\ is\ one\ line\ and\r
//
// This\ is\ one\ line\ and\r\nthis\ is\ the\ second\.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^.+"
Dim input As String = "This is one line and" + vbCrLf + "this is the second."
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(Regex.Escape(match.Value))
Next
Console.WriteLine()
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.SingleLine)
Console.WriteLine(Regex.Escape(match.Value))
Next
End Sub
End Module
' The example displays the following output:
' This\ is\ one\ line\ and\r
'
' This\ is\ one\ line\ and\r\nthis\ is\ the\ second\.

L'exemple suivant est équivalent à l'exemple précédent, à la différence qu'il utilise l'option inline (?s)
pour activer le mode à ligne simple.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "(?s)^.+";
string input = "This is one line and" + Environment.NewLine + "this is the second.";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine(Regex.Escape(match.Value));
}
}
// The example displays the following output:
// This\ is\ one\ line\ and\r\nthis\ is\ the\ second\.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "(?s)^.+"
Dim input As String = "This is one line and" + vbCrLf + "this is the second."

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine(Regex.Escape(match.Value))
Next
End Sub
End Module
' The example displays the following output:
' This\ is\ one\ line\ and\r\nthis\ is\ the\ second\.

Captures explicites uniquement


Par défaut, les groupes de capture sont définis à l’aide de parenthèses dans le modèle d’expression
régulière. Les groupes nommés se voient affecter un nom ou un nombre par l’option de langage nom
sous- (?< name > expression ) , tandis que les groupes sans nom sont accessibles par index. Dans
l'objet GroupCollection, les groupes sans nom précèdent les groupes nommés.
Les constructions de regroupement sont souvent utilisées pour simplement appliquer des quantificateurs
à plusieurs éléments de langage, et les sous-chaînes capturées ne présentent aucun intérêt. Par exemple, si
l'expression régulière suivante :
\b\(?((\w+),?\s?)+[\.!?]\)?

est uniquement destinée à extraire d'un document les phrases qui se terminent par un point, un point
d'exclamation ou un point d'interrogation, seule la phrase résultante (représentée par l'objet Match)
présente un intérêt. Les différents mots de la collection n’en présentent pas.
Les groupes de capture qui ne sont pas utilisés par la suite peuvent affecter les performances, car le
moteur d’expression régulière doit remplir les deux objets de collection GroupCollection et
CaptureCollection. En guise de solution, vous pouvez utiliser l'option RegexOptions.ExplicitCapture ou
l'option inline n pour indiquer que les seules captures valides sont les groupes explicitement nommés ou
numérotés désignés par la construction (?< nom > sous-expression ) .
L’exemple suivant affiche des informations sur les correspondances retournées par le modèle d’expression
régulière \b\(?((\w+),?\s?)+[\.!?]\)? quand la méthode Match est appelée avec et sans l’option
RegexOptions.ExplicitCapture. Comme le montre la sortie du premier appel de la méthode, le moteur
d’expression régulière remplit entièrement les objets de collection GroupCollection et CaptureCollection
avec des informations relatives aux sous-chaînes capturées. Comme la seconde méthode est appelée avec
options défini sur RegexOptions.ExplicitCapture, elle ne capture pas d'information sur les groupes.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is the first sentence. Is it the beginning " +
"of a literary masterpiece? I think not. Instead, " +
"it is a nonsensical paragraph.";
string pattern = @"\b\(?((?>\w+),?\s?)+[\.!?]\)?";
Console.WriteLine("With implicit captures:");
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine("The match: {0}", match.Value);
Console.WriteLine("The match: {0}", match.Value);
int groupCtr = 0;
foreach (Group group in match.Groups)
{
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value);
groupCtr++;
int captureCtr = 0;
foreach (Capture capture in group.Captures)
{
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value);
captureCtr++;
}
}
}
Console.WriteLine();
Console.WriteLine("With explicit captures only:");
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.ExplicitCapture))
{
Console.WriteLine("The match: {0}", match.Value);
int groupCtr = 0;
foreach (Group group in match.Groups)
{
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value);
groupCtr++;
int captureCtr = 0;
foreach (Capture capture in group.Captures)
{
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value);
captureCtr++;
}
}
}
}
}
// The example displays the following output:
// With implicit captures:
// The match: This is the first sentence.
// Group 0: This is the first sentence.
// Capture 0: This is the first sentence.
// Group 1: sentence
// Capture 0: This
// Capture 1: is
// Capture 2: the
// Capture 3: first
// Capture 4: sentence
// Group 2: sentence
// Capture 0: This
// Capture 1: is
// Capture 2: the
// Capture 3: first
// Capture 4: sentence
// The match: Is it the beginning of a literary masterpiece?
// Group 0: Is it the beginning of a literary masterpiece?
// Capture 0: Is it the beginning of a literary masterpiece?
// Group 1: masterpiece
// Capture 0: Is
// Capture 1: it
// Capture 2: the
// Capture 3: beginning
// Capture 4: of
// Capture 5: a
// Capture 6: literary
// Capture 7: masterpiece
// Group 2: masterpiece
// Capture 0: Is
// Capture 1: it
// Capture 2: the
// Capture 3: beginning
// Capture 4: of
// Capture 5: a
// Capture 5: a
// Capture 6: literary
// Capture 7: masterpiece
// The match: I think not.
// Group 0: I think not.
// Capture 0: I think not.
// Group 1: not
// Capture 0: I
// Capture 1: think
// Capture 2: not
// Group 2: not
// Capture 0: I
// Capture 1: think
// Capture 2: not
// The match: Instead, it is a nonsensical paragraph.
// Group 0: Instead, it is a nonsensical paragraph.
// Capture 0: Instead, it is a nonsensical paragraph.
// Group 1: paragraph
// Capture 0: Instead,
// Capture 1: it
// Capture 2: is
// Capture 3: a
// Capture 4: nonsensical
// Capture 5: paragraph
// Group 2: paragraph
// Capture 0: Instead
// Capture 1: it
// Capture 2: is
// Capture 3: a
// Capture 4: nonsensical
// Capture 5: paragraph
//
// With explicit captures only:
// The match: This is the first sentence.
// Group 0: This is the first sentence.
// Capture 0: This is the first sentence.
// The match: Is it the beginning of a literary masterpiece?
// Group 0: Is it the beginning of a literary masterpiece?
// Capture 0: Is it the beginning of a literary masterpiece?
// The match: I think not.
// Group 0: I think not.
// Capture 0: I think not.
// The match: Instead, it is a nonsensical paragraph.
// Group 0: Instead, it is a nonsensical paragraph.
// Capture 0: Instead, it is a nonsensical paragraph.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is the first sentence. Is it the beginning " + _
"of a literary masterpiece? I think not. Instead, " + _
"it is a nonsensical paragraph."
Dim pattern As String = "\b\(?((?>\w+),?\s?)+[\.!?]\)?"
Console.WriteLine("With implicit captures:")
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("The match: {0}", match.Value)
Dim groupCtr As Integer = 0
For Each group As Group In match.Groups
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value)
groupCtr += 1
Dim captureCtr As Integer = 0
For Each capture As Capture In group.Captures
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value)
captureCtr += 1
Next
Next
Next
Next
Console.WriteLine()
Console.WriteLine("With explicit captures only:")
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.ExplicitCapture)
Console.WriteLine("The match: {0}", match.Value)
Dim groupCtr As Integer = 0
For Each group As Group In match.Groups
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value)
groupCtr += 1
Dim captureCtr As Integer = 0
For Each capture As Capture In group.Captures
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value)
captureCtr += 1
Next
Next
Next
End Sub
End Module
' The example displays the following output:
' With implicit captures:
' The match: This is the first sentence.
' Group 0: This is the first sentence.
' Capture 0: This is the first sentence.
' Group 1: sentence
' Capture 0: This
' Capture 1: is
' Capture 2: the
' Capture 3: first
' Capture 4: sentence
' Group 2: sentence
' Capture 0: This
' Capture 1: is
' Capture 2: the
' Capture 3: first
' Capture 4: sentence
' The match: Is it the beginning of a literary masterpiece?
' Group 0: Is it the beginning of a literary masterpiece?
' Capture 0: Is it the beginning of a literary masterpiece?
' Group 1: masterpiece
' Capture 0: Is
' Capture 1: it
' Capture 2: the
' Capture 3: beginning
' Capture 4: of
' Capture 5: a
' Capture 6: literary
' Capture 7: masterpiece
' Group 2: masterpiece
' Capture 0: Is
' Capture 1: it
' Capture 2: the
' Capture 3: beginning
' Capture 4: of
' Capture 5: a
' Capture 6: literary
' Capture 7: masterpiece
' The match: I think not.
' Group 0: I think not.
' Capture 0: I think not.
' Group 1: not
' Capture 0: I
' Capture 1: think
' Capture 2: not
' Group 2: not
' Capture 0: I
' Capture 1: think
' Capture 2: not
' The match: Instead, it is a nonsensical paragraph.
' Group 0: Instead, it is a nonsensical paragraph.
' Capture 0: Instead, it is a nonsensical paragraph.
' Group 1: paragraph
' Capture 0: Instead,
' Capture 1: it
' Capture 2: is
' Capture 3: a
' Capture 4: nonsensical
' Capture 5: paragraph
' Group 2: paragraph
' Capture 0: Instead
' Capture 1: it
' Capture 2: is
' Capture 3: a
' Capture 4: nonsensical
' Capture 5: paragraph
'
' With explicit captures only:
' The match: This is the first sentence.
' Group 0: This is the first sentence.
' Capture 0: This is the first sentence.
' The match: Is it the beginning of a literary masterpiece?
' Group 0: Is it the beginning of a literary masterpiece?
' Capture 0: Is it the beginning of a literary masterpiece?
' The match: I think not.
' Group 0: I think not.
' Capture 0: I think not.
' The match: Instead, it is a nonsensical paragraph.
' Group 0: Instead, it is a nonsensical paragraph.
' Capture 0: Instead, it is a nonsensical paragraph.

Le modèle d'expression régulière \b\(?((?>\w+),?\s?)+[\.!?]\)? est défini comme indiqué dans le


tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

\(? Mettre en correspondance zéro occurrence, ou plus, de la


parenthèse ouvrante (« ( »).

(?>\w+),? Mettre en correspondance un ou plusieurs caractères


alphabétiques, suivis de zéro virgule, ou plus. Ne pas
effectuer de recherches rétroactives quand des caractères
alphabétiques sont mis en correspondance.

\s? Mettre en correspondance zéro ou des espaces blancs.

((\w+),?\s?)+ Mettre en correspondance la combinaison d'un ou


plusieurs caractères alphabétiques, suivis de zéro ou
d'une virgule, suivies de zéro ou d'un espace blanc, une
ou plusieurs fois.

[\.!?]\)? Mettre en correspondance n'importe lequel des trois


symboles de ponctuation, suivi de zéro ou d'une
parenthèse fermante (« ) »).

Vous pouvez également utiliser l'élément inline (?n) pour supprimer les captures automatiques.
L’exemple suivant modifie le modèle d’expression régulière précédent pour utiliser l’élément inline (?n)
au lieu de l’option RegexOptions.ExplicitCapture.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is the first sentence. Is it the beginning " +
"of a literary masterpiece? I think not. Instead, " +
"it is a nonsensical paragraph.";
string pattern = @"(?n)\b\(?((?>\w+),?\s?)+[\.!?]\)?";

foreach (Match match in Regex.Matches(input, pattern))


{
Console.WriteLine("The match: {0}", match.Value);
int groupCtr = 0;
foreach (Group group in match.Groups)
{
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value);
groupCtr++;
int captureCtr = 0;
foreach (Capture capture in group.Captures)
{
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value);
captureCtr++;
}
}
}
}
}
// The example displays the following output:
// The match: This is the first sentence.
// Group 0: This is the first sentence.
// Capture 0: This is the first sentence.
// The match: Is it the beginning of a literary masterpiece?
// Group 0: Is it the beginning of a literary masterpiece?
// Capture 0: Is it the beginning of a literary masterpiece?
// The match: I think not.
// Group 0: I think not.
// Capture 0: I think not.
// The match: Instead, it is a nonsensical paragraph.
// Group 0: Instead, it is a nonsensical paragraph.
// Capture 0: Instead, it is a nonsensical paragraph.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is the first sentence. Is it the beginning " + _
"of a literary masterpiece? I think not. Instead, " + _
"it is a nonsensical paragraph."
Dim pattern As String = "(?n)\b\(?((?>\w+),?\s?)+[\.!?]\)?"

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("The match: {0}", match.Value)
Dim groupCtr As Integer = 0
For Each group As Group In match.Groups
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value)
groupCtr += 1
Dim captureCtr As Integer = 0
For Each capture As Capture In group.Captures
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value)
captureCtr += 1
Next
Next
Next
End Sub
End Module
' The example displays the following output:
' The match: This is the first sentence.
' Group 0: This is the first sentence.
' Capture 0: This is the first sentence.
' The match: Is it the beginning of a literary masterpiece?
' Group 0: Is it the beginning of a literary masterpiece?
' Capture 0: Is it the beginning of a literary masterpiece?
' The match: I think not.
' Group 0: I think not.
' Capture 0: I think not.
' The match: Instead, it is a nonsensical paragraph.
' Group 0: Instead, it is a nonsensical paragraph.
' Capture 0: Instead, it is a nonsensical paragraph.

Enfin, vous pouvez utiliser l'élément de groupe inline (?n:) pour supprimer les captures automatiques
groupe par groupe. L'exemple suivant modifie le modèle précédent pour supprimer les captures sans nom
du groupe externe, ((?>\w+),?\s?) . Notez que cette opération supprime également les captures sans nom
du groupe interne.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is the first sentence. Is it the beginning " +
"of a literary masterpiece? I think not. Instead, " +
"it is a nonsensical paragraph.";
string pattern = @"\b\(?(?n:(?>\w+),?\s?)+[\.!?]\)?";

foreach (Match match in Regex.Matches(input, pattern))


{
Console.WriteLine("The match: {0}", match.Value);
int groupCtr = 0;
foreach (Group group in match.Groups)
{
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value);
groupCtr++;
int captureCtr = 0;
foreach (Capture capture in group.Captures)
{
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value);
captureCtr++;
}
}
}
}
}
// The example displays the following output:
// The match: This is the first sentence.
// Group 0: This is the first sentence.
// Capture 0: This is the first sentence.
// The match: Is it the beginning of a literary masterpiece?
// Group 0: Is it the beginning of a literary masterpiece?
// Capture 0: Is it the beginning of a literary masterpiece?
// The match: I think not.
// Group 0: I think not.
// Capture 0: I think not.
// The match: Instead, it is a nonsensical paragraph.
// Group 0: Instead, it is a nonsensical paragraph.
// Capture 0: Instead, it is a nonsensical paragraph.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is the first sentence. Is it the beginning " + _
"of a literary masterpiece? I think not. Instead, " + _
"it is a nonsensical paragraph."
Dim pattern As String = "\b\(?(?n:(?>\w+),?\s?)+[\.!?]\)?"

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("The match: {0}", match.Value)
Dim groupCtr As Integer = 0
For Each group As Group In match.Groups
Console.WriteLine(" Group {0}: {1}", groupCtr, group.Value)
groupCtr += 1
Dim captureCtr As Integer = 0
For Each capture As Capture In group.Captures
Console.WriteLine(" Capture {0}: {1}", captureCtr, capture.Value)
captureCtr += 1
Next
Next
Next
End Sub
End Module
' The example displays the following output:
' The match: This is the first sentence.
' Group 0: This is the first sentence.
' Capture 0: This is the first sentence.
' The match: Is it the beginning of a literary masterpiece?
' Group 0: Is it the beginning of a literary masterpiece?
' Capture 0: Is it the beginning of a literary masterpiece?
' The match: I think not.
' Group 0: I think not.
' Capture 0: I think not.
' The match: Instead, it is a nonsensical paragraph.
' Group 0: Instead, it is a nonsensical paragraph.
' Capture 0: Instead, it is a nonsensical paragraph.

Expressions régulières compilées


Par défaut, les expressions régulières dans .NET sont interprétées. Quand un objet Regex est instancié ou
qu'une méthode Regex statique est appelée, le modèle d'expression régulière est analysé de manière à
générer un ensemble d'opcodes personnalisés, puis un interpréteur utilise ces opcodes pour exécuter
l'expression régulière. Cela implique un compromis : le coût d'initialisation du moteur d'expression
régulière est réduit au prix d'une baisse des performances au moment de l'exécution.
Vous pouvez utiliser des expressions régulières compilées à la place d'expressions régulières interprétées
en utilisant l'option RegexOptions.Compiled. Dans ce cas, quand un modèle est transmis au moteur
d’expression régulière, il est analysé de manière à générer un ensemble d’opcodes convertis ensuite en un
code MSIL (Microsoft Intermediate Language), qui peut être directement communiqué au Common
Language Runtime. Les expressions régulières compilées optimisent les performances d'exécution au
détriment du temps d'initialisation.

NOTE
Pour compiler une expression régulière, vous devez fournir la valeur RegexOptions.Compiled au paramètre
options d’un constructeur de classe Regex ou d’une méthode de mise en correspondance de modèle statique. La
compilation ne peut pas être effectuée via une option inline.

Vous pouvez utiliser des expressions régulières compilées dans les appels d'expressions régulières
statiques et d'instance. Dans les expressions régulières statiques, l'option RegexOptions.Compiled est
transmise au paramètre options de la méthode de mise en correspondance de modèle d'expression
régulière. Dans les expressions régulières d'instance, elle est transmise au paramètre options du
constructeur de classe Regex. Dans les deux cas, les performances s'en trouvent améliorées.
Toutefois, cette amélioration ne se produit que dans les conditions suivantes :
Un objet Regex qui représente une expression régulière particulière est utilisé dans plusieurs appels
de méthodes de mise en correspondance de modèle d'expression régulière.
La portée de l'objet Regex est strictement délimitée, ce qui permet sa réutilisation.
Une expression régulière statique est utilisée dans plusieurs appels de méthodes de mise en
correspondance de modèle d’expression régulière. (L'amélioration des performances est possible,
car les expressions régulières utilisées dans les appels de méthode statique sont mises en cache par
le moteur d'expression régulière.)

NOTE
L'option RegexOptions.Compiled n'est pas liée à la méthode Regex.CompileToAssembly, qui crée un assembly à
usage particulier contenant des expressions régulières compilées prédéfinies.

Ignorer l'espace blanc


Par défaut, l’espace blanc dans un modèle d’expression régulière est significatif ; il oblige le moteur
d’expression régulière à mettre en correspondance un espace blanc dans la chaîne d’entrée. Ainsi, les
expressions régulières « \b\w+\s » et « \b\w+ » sont pratiquement équivalentes. En outre, quand le signe
dièse (#) est rencontré dans un modèle d’expression régulière, il est interprété comme un caractère littéral
à mettre en correspondance.
L'option RegexOptions.IgnorePatternWhitespace, ou l'option inline x , modifie ce comportement par
défaut comme suit :
L’espace blanc sans séquence d’échappement dans le modèle d’expression régulière est ignoré.
Pour faire partie d’un modèle d’expression régulière, les espaces blancs doivent être inclus dans
une séquence d’échappement (par exemple, as \s ou « \ »).
Le signe dièse (#) est interprété comme le début d'un commentaire, plutôt que comme un caractère
littéral. Tout le texte d’un modèle d’expression régulière depuis le caractère # jusqu’à la fin de la
chaîne est interprété comme un commentaire.
Toutefois, dans les cas suivants, les espaces blancs d’une expression régulière ne sont pas ignorés, même
si vous utilisez l’option RegexOptions.IgnorePatternWhitespace :
L'espace blanc dans une classe de caractères est toujours interprété de façon littérale. Par exemple,
le modèle d'expression régulière [ .,;:] met en correspondance n'importe quel espace blanc,
point, virgule, point-virgule ou symbole deux-points unique.
L’espace blanc n’est pas autorisé dans un quantificateur entre crochets, tel que { n } , { n ,} et
{ n , m } . Par exemple, le modèle d'expression régulière \d{1, 3} ne peut pas mettre en
correspondance les séquences d'un à trois chiffres, car il contient un espace blanc.
L'espace blanc n'est pas autorisé dans une séquence de caractères qui introduit un élément de
langage. Par exemple :
L’élément de langage sous- (?: expression ) représente un groupe sans capture, et la
(?: partie de l’élément ne peut pas avoir d’espaces incorporés. Le modèle sous- (? :
expression ) lève une ArgumentException au moment de l’exécution, car le moteur
d’expression régulière ne peut pas analyser le modèle, et le modèle sous-expression ( ?:
subexpression ) ne parvient pas à faire correspondre subexpressionla sous-expression.
Le nom de l’élément \p{ name } de langage, qui représente une catégorie Unicode ou un
bloc nommé, ne peut pas inclure d’espaces dans la \p{ partie de l’élément. Si vous incluez
un espace blanc, l'élément lève une ArgumentException au moment de l'exécution.
L'activation de cette option permet de simplifier les expressions régulières qui sont souvent difficiles à
analyser et à comprendre. Elle améliore la lisibilité et rend possible la documentation d'une expression
régulière.
L’exemple ci-après définit le modèle d’expression régulière suivant :
\b \(? ( (?>\w+) ,?\s? )+ [\.!?] \)? # Matches an entire sentence.

Ce modèle est similaire au modèle défini dans la section Captures explicites uniquement, à la différence
qu’il utilise l’option RegexOptions.IgnorePatternWhitespace pour ignorer l’espace blanc du modèle.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is the first sentence. Is it the beginning " +
"of a literary masterpiece? I think not. Instead, " +
"it is a nonsensical paragraph.";
string pattern = @"\b \(? ( (?>\w+) ,?\s? )+ [\.!?] \)? # Matches an entire sentence.";

foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnorePatternWhitespace))


Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// This is the first sentence.
// Is it the beginning of a literary masterpiece?
// I think not.
// Instead, it is a nonsensical paragraph.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is the first sentence. Is it the beginning " + _
"of a literary masterpiece? I think not. Instead, " + _
"it is a nonsensical paragraph."
Dim pattern As String = "\b \(? ( (?>\w+) ,?\s? )+ [\.!?] \)? # Matches an entire sentence."

For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnorePatternWhitespace)


Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' This is the first sentence.
' Is it the beginning of a literary masterpiece?
' I think not.
' Instead, it is a nonsensical paragraph.
L'exemple suivant utilise l'option inline (?x) pour ignorer l'espace blanc du modèle.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is the first sentence. Is it the beginning " +
"of a literary masterpiece? I think not. Instead, " +
"it is a nonsensical paragraph.";
string pattern = @"(?x)\b \(? ( (?>\w+) ,?\s? )+ [\.!?] \)? # Matches an entire sentence.";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// This is the first sentence.
// Is it the beginning of a literary masterpiece?
// I think not.
// Instead, it is a nonsensical paragraph.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is the first sentence. Is it the beginning " + _
"of a literary masterpiece? I think not. Instead, " + _
"it is a nonsensical paragraph."
Dim pattern As String = "(?x)\b \(? ( (?>\w+) ,?\s? )+ [\.!?] \)? # Matches an entire
sentence."

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' This is the first sentence.
' Is it the beginning of a literary masterpiece?
' I think not.
' Instead, it is a nonsensical paragraph.

Mode de recherche de droite à gauche


Par défaut, le moteur d'expression régulière recherche de gauche à droite. Vous pouvez inverser le sens de
la recherche à l'aide de l'option RegexOptions.RightToLeft. La recherche commence automatiquement à la
position du dernier caractère de la chaîne. Pour les méthodes de mise en correspondance de modèle qui
comprennent un paramètre de position de début, comme Regex.Match(String, Int32), la position de début
est l’index de la position du caractère le plus à droite à laquelle la recherche doit commencer.

NOTE
Pour utiliser le mode de recherche de droite à gauche, vous devez fournir la valeur RegexOptions.RightToLeft au
paramètre options d’un constructeur de classe Regex ou d’une méthode de mise en correspondance de modèle
statique. La compilation ne peut pas être effectuée via une option inline.

L’option RegexOptions.RightToLeft modifie uniquement le sens de la recherche ; elle n’interprète pas le


modèle d’expression régulière de droite à gauche. Par exemple, l'expression régulière \bb\w+\s met en
correspondance les mots qui commencent par la lettre « b » et qui sont suivis d'un espace blanc. Dans
l'exemple suivant, la chaîne d'entrée se compose de trois mots qui comprennent un ou plusieurs
caractères « b ». Le premier mot commence par « b », le deuxième se termine par « b », tandis que le
troisième comprend deux caractères « b » en son milieu. Comme le montre la sortie de l’exemple, seul le
premier mot correspond au modèle d’expression régulière.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\bb\w+\s";
string input = "builder rob rabble";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.RightToLeft))
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index);
}
}
// The example displays the following output:
// 'builder ' found at position 0.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\bb\w+\s"
Dim input As String = "builder rob rabble"
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.RightToLeft)
Console.WriteLine("'{0}' found at position {1}.", match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' 'builder ' found at position 0.

Notez également que l’assertion de préanalyse (l’élément de langage sous- (?= expression ) ) et
l’assertion de postanalyse (l' (?<= subexpression ) élément de langage sous-expression) ne changent
pas le sens. Les assertions de préanalyse recherchent vers la droite, tandis que les assertions de
postanalyse recherchent vers la gauche. Par exemple, l'expression régulière (?<=\d{1,2}\s)\w+,?\s\d{4}
utilise l'assertion de postanalyse pour déterminer si une date précède le nom d'un mois. Ensuite,
l'expression régulière met en correspondance le mois et l'année. Pour plus d’informations sur les
assertions de préanalyse et de postanalyse, voir Constructions de regroupement.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "1 May 1917", "June 16, 2003" };
string pattern = @"(?<=\d{1,2}\s)\w+,?\s\d{4}";

foreach (string input in inputs)


{
Match match = Regex.Match(input, pattern, RegexOptions.RightToLeft);
if (match.Success)
Console.WriteLine("The date occurs in {0}.", match.Value);
else
Console.WriteLine("{0} does not match.", input);
}
}
}
// The example displays the following output:
// The date occurs in May 1917.
// June 16, 2003 does not match.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"1 May 1917", "June 16, 2003"}
Dim pattern As String = "(?<=\d{1,2}\s)\w+,?\s\d{4}"

For Each input As String In inputs


Dim match As Match = Regex.Match(input, pattern, RegexOptions.RightToLeft)
If match.Success Then
Console.WriteLine("The date occurs in {0}.", match.Value)
Else
Console.WriteLine("{0} does not match.", input)
End If
Next
End Sub
End Module
' The example displays the following output:
' The date occurs in May 1917.
' June 16, 2003 does not match.

Le modèle d’expression régulière est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

(?<=\d{1,2}\s) Le début de la correspondance doit être précédé d'un ou


deux chiffres décimaux suivis d'un espace.

\w+ Mettre en correspondance un ou plusieurs caractères


alphabétiques.

,? Mettre en correspondance zéro ou une virgule.

\s Mettre en correspondance un espace blanc.

\d{4} Mettre en correspondance quatre chiffres décimaux.


Comportement de correspondance ECMAScript
Par défaut, le moteur d’expression régulière utilise un comportement canonique pour appliquer un
modèle d’expression régulière à un texte d’entrée. Toutefois, vous pouvez indiquer au moteur d’expression
régulière d’utiliser un comportement de correspondance ECMAScript en spécifiant l’option
RegexOptions.ECMAScript.

NOTE
À cette fin, vous devez fournir la valeur RegexOptions.ECMAScript au paramètre options d’un constructeur de
classe Regex ou d’une méthode de mise en correspondance de modèle statique. La compilation ne peut pas être
effectuée via une option inline.

L'option RegexOptions.ECMAScript ne peut être combinée qu'aux options RegexOptions.IgnoreCase et


RegexOptions.Multiline. L'utilisation d'une autre option dans une expression régulière aboutit à une
ArgumentOutOfRangeException.
Le comportement des expressions régulières ECMAScript et canoniques diffère dans trois domaines : la
syntaxe de la classe de caractères, les groupes de capture avec référence circulaire et l’interprétation des
séquences d’échappement octales ou des références arrière.
Syntaxe de la classe de caractères. Comme les expressions régulières canoniques prennent en
charge Unicode, contrairement à ECMAScript, les classes de caractères dans ECMAScript possèdent
une syntaxe plus limitée, et certains éléments de langage des classes de caractères ont une
signification différente. Par exemple, ECMAScript ne prend pas en charge les éléments de langage
tels que la catégorie Unicode ou les éléments de bloc \p et \P . De même, l'élément \w , qui
correspond à un caractère alphabétique, est équivalent à la classe de caractères [a-zA-Z_0-9] , dans
le cas de l'utilisation d'ECMAScript, et à [\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}\p{Pc}\p{Lm}] , dans le cas
de l'utilisation du comportement canonique. Pour plus d’informations, consultez classes de
caractères.
L’exemple suivant illustre la différence entre les mises en correspondance de modèle canonique et
ECMAScript. Il définit une expression régulière, \b(\w+\s*)+ , qui met en correspondance les mots
suivis d'espaces blancs. L'entrée se compose de deux chaînes ; l'une d'elles utilise le jeu de
caractères latin, l'autre le jeu de caractères cyrillique. Comme le montre la sortie, l’appel de
méthode Regex.IsMatch(String, String, RegexOptions) qui utilise la correspondance ECMAScript ne
parvient pas à mettre en correspondance les mots cyrilliques, contrairement à l’appel de méthode
qui utilise la correspondance canonique.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] values = { "целый мир", "the whole world" };
string pattern = @"\b(\w+\s*)+";
foreach (var value in values)
{
Console.Write("Canonical matching: ");
if (Regex.IsMatch(value, pattern))
Console.WriteLine("'{0}' matches the pattern.", value);
else
Console.WriteLine("{0} does not match the pattern.", value);

Console.Write("ECMAScript matching: ");


if (Regex.IsMatch(value, pattern, RegexOptions.ECMAScript))
Console.WriteLine("'{0}' matches the pattern.", value);
else
Console.WriteLine("{0} does not match the pattern.", value);
Console.WriteLine();
}
}
}
// The example displays the following output:
// Canonical matching: 'целый мир' matches the pattern.
// ECMAScript matching: целый мир does not match the pattern.
//
// Canonical matching: 'the whole world' matches the pattern.
// ECMAScript matching: 'the whole world' matches the pattern.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim values() As String = {"целый мир", "the whole world"}
Dim pattern As String = "\b(\w+\s*)+"
For Each value In values
Console.Write("Canonical matching: ")
If Regex.IsMatch(value, pattern)
Console.WriteLine("'{0}' matches the pattern.", value)
Else
Console.WriteLine("{0} does not match the pattern.", value)
End If

Console.Write("ECMAScript matching: ")


If Regex.IsMatch(value, pattern, RegexOptions.ECMAScript)
Console.WriteLine("'{0}' matches the pattern.", value)
Else
Console.WriteLine("{0} does not match the pattern.", value)
End If
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Canonical matching: 'целый мир' matches the pattern.
' ECMAScript matching: целый мир does not match the pattern.
'
' Canonical matching: 'the whole world' matches the pattern.
' ECMAScript matching: 'the whole world' matches the pattern.

Groupes de capture avec référence circulaire. Une classe de capture d'expression régulière avec une
référence arrière à elle-même doit être mise à jour à chaque itération de capture. Comme le montre
l'exemple suivant, cette fonctionnalité permet à l'expression régulière ((a+)(\1) ?)+ de mettre en
correspondance la chaîne d'entrée « aa aaaa aaaaaa » dans le cas de l'utilisation de la
correspondance ECMAScript, mais pas dans le cas de l'utilisation de la correspondance canonique.
using System;
using System.Text.RegularExpressions;

public class Example


{
static string pattern;

public static void Main()


{
string input = "aa aaaa aaaaaa ";
pattern = @"((a+)(\1) ?)+";

// Match input using canonical matching.


AnalyzeMatch(Regex.Match(input, pattern));

// Match input using ECMAScript.


AnalyzeMatch(Regex.Match(input, pattern, RegexOptions.ECMAScript));
}

private static void AnalyzeMatch(Match m)


{
if (m.Success)
{
Console.WriteLine("'{0}' matches {1} at position {2}.",
pattern, m.Value, m.Index);
int grpCtr = 0;
foreach (Group grp in m.Groups)
{
Console.WriteLine(" {0}: '{1}'", grpCtr, grp.Value);
grpCtr++;
int capCtr = 0;
foreach (Capture cap in grp.Captures)
{
Console.WriteLine(" {0}: '{1}'", capCtr, cap.Value);
capCtr++;
}
}
}
else
{
Console.WriteLine("No match found.");
}
Console.WriteLine();
}
}
// The example displays the following output:
// No match found.
//
// '((a+)(\1) ?)+' matches aa aaaa aaaaaa at position 0.
// 0: 'aa aaaa aaaaaa '
// 0: 'aa aaaa aaaaaa '
// 1: 'aaaaaa '
// 0: 'aa '
// 1: 'aaaa '
// 2: 'aaaaaa '
// 2: 'aa'
// 0: 'aa'
// 1: 'aa'
// 2: 'aa'
// 3: 'aaaa '
// 0: ''
// 1: 'aa '
// 2: 'aaaa '
Imports System.Text.RegularExpressions

Module Example
Dim pattern As String

Public Sub Main()


Dim input As String = "aa aaaa aaaaaa "
pattern = "((a+)(\1) ?)+"

' Match input using canonical matching.


AnalyzeMatch(Regex.Match(input, pattern))

' Match input using ECMAScript.


AnalyzeMatch(Regex.Match(input, pattern, RegexOptions.ECMAScript))
End Sub

Private Sub AnalyzeMatch(m As Match)


If m.Success
Console.WriteLine("'{0}' matches {1} at position {2}.", _
pattern, m.Value, m.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In m.Groups
Console.WriteLine(" {0}: '{1}'", grpCtr, grp.Value)
grpCtr += 1
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" {0}: '{1}'", capCtr, cap.Value)
capCtr += 1
Next
Next
Else
Console.WriteLine("No match found.")
End If
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' No match found.
'
' '((a+)(\1) ?)+' matches aa aaaa aaaaaa at position 0.
' 0: 'aa aaaa aaaaaa '
' 0: 'aa aaaa aaaaaa '
' 1: 'aaaaaa '
' 0: 'aa '
' 1: 'aaaa '
' 2: 'aaaaaa '
' 2: 'aa'
' 0: 'aa'
' 1: 'aa'
' 2: 'aa'
' 3: 'aaaa '
' 0: ''
' 1: 'aa '
' 2: 'aaaa '

L'expression régulière est définie comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

(a+) Mettre en correspondance la lettre « a » une ou


plusieurs fois. Il s'agit du deuxième groupe de
capture.
M O DÈL E DESC RIP T IO N

(\1) Mettre en correspondance la sous-chaîne capturée


par le premier groupe de capture. Il s'agit du
troisième groupe de capture.

? Mettre en correspondance zéro ou un espace.

((a+)(\1) ?)+ Mettre en correspondance le modèle d'un ou


plusieurs caractères « a » suivis d'une chaîne
correspondant au premier groupe de capture, suivie
de zéro ou un espace, une ou plusieurs fois. Il s'agit
du premier groupe de capture.

Résolution des ambiguïtés entre les séquences d'échappement octales et les références arrière. Le
tableau suivant compare la façon dont les expressions régulières canoniques et ECMAScript
interprètent les séquences d'échappement octales et les références arrière.

EXP RESSIO N RÉGUL IÈRE C O M P O RT EM EN T C A N O N IQ UE C O M P O RT EM EN T EC M A SC RIP T

\0 suivi de 0 à 2 chiffres octaux Interpréter comme un octal. Par Même comportement.


exemple, \044 est toujours
interprété comme une valeur
octale et signifie « $ ».

\ suivi d'un chiffre entre 1 et 9, Interpréter comme une référence Si un groupe de capture avec
non suivi de chiffres décimaux. arrière. Par exemple, \9 signifie chiffre décimal unique existe,
toujours référence arrière 9, effectuer une référence arrière au
même si un neuvième groupe de niveau de ce chiffre. Sinon,
capture n'existe pas. Si le groupe interpréter la valeur en tant que
de capture n'existe pas, l'analyseur littéral.
de l'expression régulière lève une
ArgumentException.

\ suivi d'un chiffre entre 1 et 9, Interpréter les chiffres en tant que Interpréter en tant que référence
suivi de chiffres décimaux valeur décimale. Si ce groupe de arrière en convertissant autant de
supplémentaires. capture existe, interpréter chiffres que possible en valeur
l'expression en tant que référence décimale pouvant faire référence à
arrière. une capture. Si aucun chiffre ne
peut être converti, interpréter en
Sinon, interpréter les chiffres tant qu'octal en utilisant les
octaux de début jusqu'à chiffres octaux de début jusqu'à
l'octal 377 ; en d'autres termes, l'octal 377 ; interpréter les autres
ne prendre en compte que les chiffres en tant que littéraux.
8 bits de poids faible de la valeur.
Interpréter les autres chiffres
comme des littéraux. Par exemple,
dans l'expression \3000 , si le
groupe de capture 300 existe,
interpréter en tant que référence
arrière 300, sinon, interpréter en
tant qu'octal 300 suivi de 0.

Comparaison utilisant la culture dite indifférente


Par défaut, quand le moteur d'expression régulière effectue des comparaisons sans respect de la casse, il
utilise les conventions de gestion de la casse de la culture actuelle pour déterminer les caractères
majuscules et minuscules équivalents.
Toutefois, ce comportement n'est pas souhaitable pour certains types de comparaisons, notamment celles
entre les entrées utilisateur et les noms de ressources système, comme les mots de passe, les fichiers ou
les URL. L'exemple suivant illustre un scénario de ce type. Le code est destiné à bloquer l’accès à toute
ressource dont l’URL commence par FILE:// . Le moteur d'expression régulière essaie d'effectuer une mise
en correspondance sans respect de la casse avec la chaîne en utilisant l'expression régulière $FILE:// .
Toutefois, quand la culture système actuelle est tr-TR (Turc-Turquie), « I » n'est pas l'équivalent en
majuscule de « i ». L'appel de la méthode Regex.IsMatch renvoie donc false , et l'accès au fichier est
autorisé.

CultureInfo defaultCulture = Thread.CurrentThread.CurrentCulture;


Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");

string input = "file://c:/Documents.MyReport.doc";


string pattern = "FILE://";

Console.WriteLine("Culture-sensitive matching ({0} culture)...",


Thread.CurrentThread.CurrentCulture.Name);
if (Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("URLs that access files are not allowed.");
else
Console.WriteLine("Access to {0} is allowed.", input);

Thread.CurrentThread.CurrentCulture = defaultCulture;
// The example displays the following output:
// Culture-sensitive matching (tr-TR culture)...
// Access to file://c:/Documents.MyReport.doc is allowed.

Dim defaultCulture As CultureInfo = Thread.CurrentThread.CurrentCulture


Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")

Dim input As String = "file://c:/Documents.MyReport.doc"


Dim pattern As String = "$FILE://"

Console.WriteLine("Culture-sensitive matching ({0} culture)...", _


Thread.CurrentThread.CurrentCulture.Name)
If Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase) Then
Console.WriteLine("URLs that access files are not allowed.")
Else
Console.WriteLine("Access to {0} is allowed.", input)
End If

Thread.CurrentThread.CurrentCulture = defaultCulture
' The example displays the following output:
' Culture-sensitive matching (tr-TR culture)...
' Access to file://c:/Documents.MyReport.doc is allowed.

NOTE
Pour plus d’informations sur les comparaisons de chaînes respectant la casse et utilisant la culture dite indifférente,
consultez Bonnes pratiques pour l’utilisation de chaînes.

Au lieu d'utiliser les comparaisons sans respect de la casse de la culture actuelle, vous pouvez spécifier
l'option RegexOptions.CultureInvariant pour ignorer les différences culturelles propres à la langue et pour
utiliser les conventions de la culture dite indifférente.
NOTE
Pour effectuer une comparaison en utilisant la culture Invariant, vous devez fournir la valeur
RegexOptions.CultureInvariant au paramètre options d'un constructeur de classe Regex ou d'une méthode de
mise en correspondance de modèle statique. La compilation ne peut pas être effectuée via une option inline.

L'exemple suivant est identique à l'exemple précédent, à la différence que la méthode


Regex.IsMatch(String, String, RegexOptions) statique est appelée avec des options qui incluent
RegexOptions.CultureInvariant. Même si la culture actuelle est définie sur Turc (Turquie), le moteur
d'expression régulière parvient à mettre en correspondance « FILE » et « file » et à bloquer l'accès à la
ressource de fichier.

CultureInfo defaultCulture = Thread.CurrentThread.CurrentCulture;


Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");

string input = "file://c:/Documents.MyReport.doc";


string pattern = "FILE://";

Console.WriteLine("Culture-insensitive matching...");
if (Regex.IsMatch(input, pattern,
RegexOptions.IgnoreCase | RegexOptions.CultureInvariant))
Console.WriteLine("URLs that access files are not allowed.");
else
Console.WriteLine("Access to {0} is allowed.", input);

Thread.CurrentThread.CurrentCulture = defaultCulture;
// The example displays the following output:
// Culture-insensitive matching...
// URLs that access files are not allowed.

Dim defaultCulture As CultureInfo = Thread.CurrentThread.CurrentCulture


Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")

Dim input As String = "file://c:/Documents.MyReport.doc"


Dim pattern As String = "$FILE://"

Console.WriteLine("Culture-insensitive matching...")
If Regex.IsMatch(input, pattern, _
RegexOptions.IgnoreCase Or RegexOptions.CultureInvariant) Then
Console.WriteLine("URLs that access files are not allowed.")
Else
Console.WriteLine("Access to {0} is allowed.", input)
End If
Thread.CurrentThread.CurrentCulture = defaultCulture
' The example displays the following output:
' Culture-insensitive matching...
' URLs that access files are not allowed.

Voir aussi
Langage des expressions régulières - Aide-mémoire
Constructions diverses dans les expressions
régulières
18/07/2020 • 11 minutes to read • Edit Online

Les expressions régulières dans .NET incluent trois constructions de langage diverses. L’une d’elles vous permet
d’activer ou de désactiver des options de mise en correspondance particulières au milieu d’un modèle
d’expression régulière. Grâce aux deux autres, vous pouvez inclure des commentaires dans une expression
régulière.

Options inline
Vous pouvez définir ou désactiver des options de mise en correspondance de modèle spécifiques pour une partie
d’une expression régulière en utilisant la syntaxe suivante :
(?imnsx-imnsx)

Vous répertoriez les options à activer après le point d’interrogation et les options à désactiver après le signe
moins. Le tableau suivant décrit chaque option. Pour plus d’informations sur chaque option, consultez Options
des expressions régulières.

O P T IO N DESC RIP T IO N

i Correspondance qui ne respecte pas la casse.

m Mode multiligne.

n Captures explicites uniquement. (Les parenthèses ne font pas


office de groupes de capture.)

s Mode à ligne simple.

x Ignorer un espace blanc sans séquence d’échappement et


autoriser les commentaires en mode x.

Toute modification des options d’expression régulière définies par la construction (?imnsx-imnsx) reste en
vigueur jusqu’à la fin du groupe englobant.

NOTE
La (?imnsx-imnsx: subexpression ) construction de regroupement sous-expression fournit des fonctionnalités
identiques pour une sous-expression. Pour plus d’informations, consultez Constructions de regroupement.

L’exemple suivant utilise les options i , n et x pour activer le non-respect de la casse et les captures explicites,
et pour ignorer l’espace blanc dans le modèle d’expression régulière au milieu d’une expression régulière.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern;
string input = "double dare double Double a Drooling dog The Dreaded Deep";

pattern = @"\b(D\w+)\s(d\w+)\b";
// Match pattern using default options.
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine(match.Value);
if (match.Groups.Count > 1)
for (int ctr = 1; ctr < match.Groups.Count; ctr++)
Console.WriteLine(" Group {0}: {1}", ctr, match.Groups[ctr].Value);
}
Console.WriteLine();

// Change regular expression pattern to include options.


pattern = @"\b(D\w+)(?ixn) \s (d\w+) \b";
// Match new pattern with options.
foreach (Match match in Regex.Matches(input, pattern))
{
Console.WriteLine(match.Value);
if (match.Groups.Count > 1)
for (int ctr = 1; ctr < match.Groups.Count; ctr++)
Console.WriteLine(" Group {0}: '{1}'", ctr, match.Groups[ctr].Value);
}
}
}
// The example displays the following output:
// Drooling dog
// Group 1: Drooling
// Group 2: dog
//
// Drooling dog
// Group 1: 'Drooling'
// Dreaded Deep
// Group 1: 'Dreaded'
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String
Dim input As String = "double dare double Double a Drooling dog The Dreaded Deep"

pattern = "\b(D\w+)\s(d\w+)\b"
' Match pattern using default options.
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
If match.Groups.Count > 1 Then
For ctr As Integer = 1 To match.Groups.Count - 1
Console.WriteLine(" Group {0}: {1}", ctr, match.Groups(ctr).Value)
Next
End If
Next
Console.WriteLine()

' Change regular expression pattern to include options.


pattern = "\b(D\w+)(?ixn) \s (d\w+) \b"
' Match new pattern with options.
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
If match.Groups.Count > 1 Then
For ctr As Integer = 1 To match.Groups.Count - 1
Console.WriteLine(" Group {0}: '{1}'", ctr, match.Groups(ctr).Value)
Next
End If
Next
End Sub
End Module
' The example displays the following output:
' Drooling dog
' Group 1: Drooling
' Group 2: dog
'
' Drooling dog
' Group 1: 'Drooling'
' Dreaded Deep
' Group 1: 'Dreaded'

L’exemple définit deux expressions régulières. La première, \b(D\w+)\s(d\w+)\b , correspond à deux mots
consécutifs qui commencent par un « D » majuscule et un « d » minuscule. La seconde expression régulière,
\b(D\w+)(?ixn) \s (d\w+) \b , utilise des options inline pour modifier ce modèle, comme décrit dans le tableau
suivant. Une comparaison des résultats confirme l’effet de la construction (?ixn) .

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

(D\w+) Mettre en correspondance un « D » majuscule suivi d’un ou


de plusieurs caractères alphabétiques. Il s’agit du premier
groupe de capture.

(?ixn) À partir de ce point, effectuer des comparaisons sans respect


de la casse, effectuer seulement des captures explicites et
ignorer l’espace blanc dans le modèle d’expression régulière.

\s Mettre en correspondance un espace blanc.


M O DÈL E DESC RIP T IO N

(d\w+) Mettre en correspondance un « d » majuscule ou minuscule


suivi d’un ou de plusieurs caractères alphabétiques. Ce
groupe n’est pas capturé, car l’option n (capture explicite) a
été activée.

\b Mettre en correspondance la limite d'un mot.

Commentaire inline
La (?# comment ) construction comment vous permet d’inclure un commentaire inline dans une expression
régulière. Le moteur d’expression régulière n’utilise aucune partie du commentaire dans la mise en
correspondance du modèle, bien que le commentaire soit inclus dans la chaîne retournée par la méthode
Regex.ToString. Le commentaire se termine à la première parenthèse fermante.
L’exemple suivant répète le premier modèle d’expression régulière de l’exemple de la section précédente. Il ajoute
deux commentaires inline à l’expression régulière pour indiquer si la comparaison respecte la casse. Le modèle
d’expression régulière, \b((?# case-sensitive comparison)D\w+)\s(?ixn)((?#case-insensitive comparison)d\w+)\b ,
est défini comme suit.

M O DÈL E DESC RIP T IO N

\b Commencer à la limite d'un mot.

(?# case-sensitive comparison) Commentaire. Il n’affecte pas le comportement de mise


correspondance du modèle.

(D\w+) Mettre en correspondance un « D » majuscule suivi d’un ou


de plusieurs caractères alphabétiques. Il s'agit du premier
groupe de capture.

\s Mettre en correspondance un espace blanc.

(?ixn) À partir de ce point, effectuer des comparaisons sans respect


de la casse, effectuer seulement des captures explicites et
ignorer l’espace blanc dans le modèle d’expression régulière.

(?#case-insensitive comparison) Commentaire. Il n’affecte pas le comportement de mise


correspondance du modèle.

(d\w+) Mettre en correspondance un « d » majuscule ou minuscule


suivi d’un ou de plusieurs caractères alphabétiques. Il s’agit
du deuxième groupe de capture.

\b Mettre en correspondance la limite d'un mot.


using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b((?# case-sensitive comparison)D\w+)\s(?ixn)((?#case-insensitive
comparison)d\w+)\b";
Regex rgx = new Regex(pattern);
string input = "double dare double Double a Drooling dog The Dreaded Deep";

Console.WriteLine("Pattern: " + pattern.ToString());


// Match pattern using default options.
foreach (Match match in rgx.Matches(input))
{
Console.WriteLine(match.Value);
if (match.Groups.Count > 1)
{
for (int ctr = 1; ctr <match.Groups.Count; ctr++)
Console.WriteLine(" Group {0}: {1}", ctr, match.Groups[ctr].Value);
}
}
}
}
// The example displays the following output:
// Pattern: \b((?# case-sensitive comparison)D\w+)\s(?ixn)((?#case-insensitive comp
// arison)d\w+)\b
// Drooling dog
// Group 1: Drooling
// Dreaded Deep
// Group 1: Dreaded

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b((?# case-sensitive comparison)D\w+)\s(?ixn)((?#case-insensitive
comparison)d\w+)\b"
Dim rgx As New Regex(pattern)
Dim input As String = "double dare double Double a Drooling dog The Dreaded Deep"

Console.WriteLine("Pattern: " + pattern.ToString())


' Match pattern using default options.
For Each match As Match In rgx.Matches(input)
Console.WriteLine(match.Value)
If match.Groups.Count > 1 Then
For ctr As Integer = 1 To match.Groups.Count - 1
Console.WriteLine(" Group {0}: {1}", ctr, match.Groups(ctr).Value)
Next
End If
Next
End Sub
End Module
' The example displays the following output:
' Pattern: \b((?# case-sensitive comparison)D\w+)\s(?ixn)((?#case-insensitive comp
' arison)d\w+)\b
' Drooling dog
' Group 1: Drooling
' Dreaded Deep
' Group 1: Dreaded

Commentaire de fin de ligne


Un signe dièse ( # ) marque un commentaire en mode x, lequel démarre au caractère # sans séquence
d’échappement à la fin du modèle d’expression régulière et continue jusqu’à la fin de la ligne. Pour utiliser cette
construction, vous devez activer l’option x (par le biais d’options inline) ou fournir la valeur
RegexOptions.IgnorePatternWhitespace au paramètre option au moment de l’instanciation de l’objet Regex ou
de l’appel de la méthode Regex statique.
L’exemple suivant illustre la construction du commentaire de fin de ligne. Il détermine si une chaîne est une
chaîne de format composite qui inclut au moins un élément de format. Le tableau suivant décrit les constructions
dans le modèle d’expression régulière :
\{\d+(,-*\d+)*(\:\w{1,4}?)*\}(?x) # Looks for a composite format item.

M O DÈL E DESC RIP T IO N

\{ Mettre en correspondance une accolade ouvrante.

\d+ Mettre en correspondance un ou plusieurs chiffres décimaux.

(,-*\d+)* Mettre en correspondance zéro ou une occurrence d’une


virgule, suivie d’un signe moins facultatif, suivi par un ou
plusieurs chiffres décimaux.

(\:\w{1,4}?)* Mettre en correspondance zéro ou une occurrence d’un signe


deux-points, suivi par un à quatre espaces blancs, mais le
moins possible.

\} Mettre en correspondance une accolade fermante.

(?x) Activer l’option permettant d’ignorer l’espace blanc dans le


modèle, afin que le commentaire de fin de ligne soit reconnu.

# Looks for a composite format item. Un commentaire de fin de ligne.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\{\d+(,-*\d+)*(\:\w{1,4}?)*\}(?x) # Looks for a composite format item.";
string input = "{0,-3:F}";
Console.WriteLine("'{0}':", input);
if (Regex.IsMatch(input, pattern))
Console.WriteLine(" contains a composite format item.");
else
Console.WriteLine(" does not contain a composite format item.");
}
}
// The example displays the following output:
// '{0,-3:F}':
// contains a composite format item.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\{\d+(,-*\d+)*(\:\w{1,4}?)*\}(?x) # Looks for a composite format item."
Dim input As String = "{0,-3:F}"
Console.WriteLine("'{0}':", input)
If Regex.IsMatch(input, pattern) Then
Console.WriteLine(" contains a composite format item.")
Else
Console.WriteLine(" does not contain a composite format item.")
End If
End Sub
End Module
' The example displays the following output:
' '{0,-3:F}':
' contains a composite format item.

Notez que, au lieu de fournir la construction (?x) dans l’expression régulière, le commentaire aurait également
pu être reconnu en appelant la méthode Regex.IsMatch(String, String, RegexOptions) et en lui passant la valeur
d’énumération RegexOptions.IgnorePatternWhitespace.

Voir aussi
Langage des expressions régulières - Aide-mémoire
Meilleures pratiques pour les expressions régulières
dans .NET
18/07/2020 • 63 minutes to read • Edit Online

Le moteur d’expression régulière dans .NET est un outil puissant et complet. Il traite le texte en fonction de
correspondances de modèle plutôt qu’en comparant et en faisant correspondre le texte littéral. Dans la plupart des
cas, il exécute les critères spéciaux de façon rapide et efficace. Toutefois, dans certains cas, le moteur des
expressions régulières peut sembler très lent. Dans des cas extrêmes, il semble même cesser de répondre. Il traite
en effet peu d'entrées sur une période de plusieurs heures ou même de plusieurs jours.
Cette rubrique décrit quelques-unes des meilleures pratiques que les développeurs peuvent adopter afin de
garantir que les expressions régulières atteignent des performances optimales.

WARNING
Lorsque System.Text.RegularExpressions vous utilisez pour traiter une entrée non fiable, transmettez un délai d’attente. Un
utilisateur malveillant peut fournir une entrée pour RegularExpressions provoquer une attaque par déni de service.
ASP.NET Core les API d’infrastructure qui utilisent RegularExpressions passent un délai d’expiration.

Prise en compte de la source d’entrée


En général, les expressions régulières peuvent accepter deux types d'entrée : avec contrainte ou sans contrainte.
L'entrée avec contrainte est un texte provenant d'une source fiable ou connue, et qui suit un format prédéfini.
L'entrée sans contrainte est un texte provenant d'une source non fiable, telle qu'un utilisateur web. Elle ne suit pas
forcément un format prédéfini ou attendu.
Les modèles d'expressions régulières sont généralement écrits pour correspondre à une entrée valide. Autrement
dit, les développeurs examinent le texte qu'ils souhaitent faire correspondre, puis ils écrivent un modèle
d'expression régulière qui lui correspond. Les développeurs déterminent ensuite si ce modèle doit être corrigé ou
approfondi en le testant à l'aide de plusieurs éléments d'entrée valides. Lorsque le modèle correspond à toutes les
entrées valides supposées, il est déclaré prêt pour la production et peut être intégré à une application finale. Le
modèle d'expression régulière est ainsi adapté à la mise en correspondance d'une entrée avec contrainte. Toutefois,
il n'est pas adapté à la mise en correspondance d'une entrée sans contrainte.
Pour faire correspondre une entrée sans contrainte, une expression régulière doit pouvoir gérer efficacement trois
types de texte :
Texte correspondant au modèle d’expression régulière.
Texte ne correspondant pas au modèle d’expression régulière.
Texte correspondant presque au modèle d’expression régulière.
Le dernier type de texte est problématique pour une expression régulière écrite pour gérer les entrées avec
contrainte. Si cette expression régulière repose également sur une rétroaction complète, le traitement d’un texte
apparemment anodin par le moteur d’expression régulière risque d’être extrêmement long (dans certains cas, un
grand nombre d’heures ou de jours).
WARNING
L'exemple suivant utilise une expression régulière sujette à des rétroactions excessives et susceptible de rejeter des adresses
e-mail valides. Vous ne devez pas l’utiliser dans une routine de validation d’e-mails. Si vous souhaitez une expression régulière
qui valide des adresses e-mail, consultez Guide pratique : vérifier que des chaînes sont dans un format d’adresse e-mail valide.

Prenons l'exemple d'une expression régulière très fréquemment utilisée, mais extrêmement problématique, pour la
validation de l'alias d'une adresse e-mail. L'expression régulière ^[0-9A-Z]([-.\w]*[0-9A-Z])*$ est écrite pour
traiter ce qui est considéré comme une adresse e-mail valide. Cette dernière se compose d'un caractère
alphanumérique suivi de zéro, ou de plusieurs caractères (caractères alphanumériques, points ou traits d'union).
L'expression régulière doit se terminer par un caractère alphanumérique. Toutefois, comme illustré dans l'exemple
suivant, bien que cette expression régulière gère facilement une entrée valide, elle s'avère particulièrement
inefficace lorsqu'il s'agit de traiter une entrée presque valide.

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
Stopwatch sw;
string[] addresses = { "AAAAAAAAAAA@contoso.com",
"AAAAAAAAAAaaaaaaaaaa!@contoso.com" };
// The following regular expression should not actually be used to
// validate an email address.
string pattern = @"^[0-9A-Z]([-.\w]*[0-9A-Z])*$";
string input;

foreach (var address in addresses) {


string mailBox = address.Substring(0, address.IndexOf("@"));
int index = 0;
for (int ctr = mailBox.Length - 1; ctr >= 0; ctr--) {
index++;

input = mailBox.Substring(ctr, index);


sw = Stopwatch.StartNew();
Match m = Regex.Match(input, pattern, RegexOptions.IgnoreCase);
sw.Stop();
if (m.Success)
Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
index, m.Value, sw.Elapsed);
else
Console.WriteLine("{0,2}. Failed '{1,25}' in {2}",
index, input, sw.Elapsed);
}
Console.WriteLine();
}
}
}

// The example displays output similar to the following:


// 1. Matched ' A' in 00:00:00.0007122
// 2. Matched ' AA' in 00:00:00.0000282
// 3. Matched ' AAA' in 00:00:00.0000042
// 4. Matched ' AAAA' in 00:00:00.0000038
// 5. Matched ' AAAAA' in 00:00:00.0000042
// 6. Matched ' AAAAAA' in 00:00:00.0000042
// 7. Matched ' AAAAAAA' in 00:00:00.0000042
// 8. Matched ' AAAAAAAA' in 00:00:00.0000087
// 9. Matched ' AAAAAAAAA' in 00:00:00.0000045
// 10. Matched ' AAAAAAAAAA' in 00:00:00.0000045
// 11. Matched ' AAAAAAAAAAA' in 00:00:00.0000045
// 11. Matched ' AAAAAAAAAAA' in 00:00:00.0000045
//
// 1. Failed ' !' in 00:00:00.0000447
// 2. Failed ' a!' in 00:00:00.0000071
// 3. Failed ' aa!' in 00:00:00.0000071
// 4. Failed ' aaa!' in 00:00:00.0000061
// 5. Failed ' aaaa!' in 00:00:00.0000081
// 6. Failed ' aaaaa!' in 00:00:00.0000126
// 7. Failed ' aaaaaa!' in 00:00:00.0000359
// 8. Failed ' aaaaaaa!' in 00:00:00.0000414
// 9. Failed ' aaaaaaaa!' in 00:00:00.0000758
// 10. Failed ' aaaaaaaaa!' in 00:00:00.0001462
// 11. Failed ' aaaaaaaaaa!' in 00:00:00.0002885
// 12. Failed ' Aaaaaaaaaaa!' in 00:00:00.0005780
// 13. Failed ' AAaaaaaaaaaa!' in 00:00:00.0011628
// 14. Failed ' AAAaaaaaaaaaa!' in 00:00:00.0022851
// 15. Failed ' AAAAaaaaaaaaaa!' in 00:00:00.0045864
// 16. Failed ' AAAAAaaaaaaaaaa!' in 00:00:00.0093168
// 17. Failed ' AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
// 18. Failed ' AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
// 19. Failed ' AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
// 20. Failed ' AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
// 21. Failed ' AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372

Imports System.Diagnostics
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim sw As Stopwatch
Dim addresses() As String = {"AAAAAAAAAAA@contoso.com",
"AAAAAAAAAAaaaaaaaaaa!@contoso.com"}
' The following regular expression should not actually be used to
' validate an email address.
Dim pattern As String = "^[0-9A-Z]([-.\w]*[0-9A-Z])*$"
Dim input As String

For Each address In addresses


Dim mailBox As String = address.Substring(0, address.IndexOf("@"))
Dim index As Integer = 0
For ctr As Integer = mailBox.Length - 1 To 0 Step -1
index += 1
input = mailBox.Substring(ctr, index)
sw = Stopwatch.StartNew()
Dim m As Match = Regex.Match(input, pattern, RegexOptions.IgnoreCase)
sw.Stop()
if m.Success Then
Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
index, m.Value, sw.Elapsed)
Else
Console.WriteLine("{0,2}. Failed '{1,25}' in {2}",
index, input, sw.Elapsed)
End If
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays output similar to the following:
' 1. Matched ' A' in 00:00:00.0007122
' 2. Matched ' AA' in 00:00:00.0000282
' 3. Matched ' AAA' in 00:00:00.0000042
' 4. Matched ' AAAA' in 00:00:00.0000038
' 5. Matched ' AAAAA' in 00:00:00.0000042
' 6. Matched ' AAAAAA' in 00:00:00.0000042
' 7. Matched ' AAAAAAA' in 00:00:00.0000042
' 8. Matched ' AAAAAAAA' in 00:00:00.0000087
' 9. Matched ' AAAAAAAAA' in 00:00:00.0000045
' 9. Matched ' AAAAAAAAA' in 00:00:00.0000045
' 10. Matched ' AAAAAAAAAA' in 00:00:00.0000045
' 11. Matched ' AAAAAAAAAAA' in 00:00:00.0000045
'
' 1. Failed ' !' in 00:00:00.0000447
' 2. Failed ' a!' in 00:00:00.0000071
' 3. Failed ' aa!' in 00:00:00.0000071
' 4. Failed ' aaa!' in 00:00:00.0000061
' 5. Failed ' aaaa!' in 00:00:00.0000081
' 6. Failed ' aaaaa!' in 00:00:00.0000126
' 7. Failed ' aaaaaa!' in 00:00:00.0000359
' 8. Failed ' aaaaaaa!' in 00:00:00.0000414
' 9. Failed ' aaaaaaaa!' in 00:00:00.0000758
' 10. Failed ' aaaaaaaaa!' in 00:00:00.0001462
' 11. Failed ' aaaaaaaaaa!' in 00:00:00.0002885
' 12. Failed ' Aaaaaaaaaaa!' in 00:00:00.0005780
' 13. Failed ' AAaaaaaaaaaa!' in 00:00:00.0011628
' 14. Failed ' AAAaaaaaaaaaa!' in 00:00:00.0022851
' 15. Failed ' AAAAaaaaaaaaaa!' in 00:00:00.0045864
' 16. Failed ' AAAAAaaaaaaaaaa!' in 00:00:00.0093168
' 17. Failed ' AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
' 18. Failed ' AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
' 19. Failed ' AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
' 20. Failed ' AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
' 21. Failed ' AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372

Comme le montre la sortie de l'exemple, le moteur des expressions régulières traite l'alias de messagerie valide
dans un intervalle de temps à peu près identique, indépendamment de sa longueur. En revanche, lorsque l'adresse
e-mail presque valide comporte plus de cinq caractères, le temps de traitement est environ doublé pour chaque
caractère supplémentaire de la chaîne. Cela signifie qu'une chaîne de 28 caractères presque valide serait traitée en
plus d'une heure et qu'une chaîne de 33 caractères presque valide serait traitée en un peu moins d'un jour.
Étant donné que cette expression régulière a été développée en prenant uniquement en considération le format de
l'entrée à faire correspondre, elle ne tient pas compte des entrées qui ne correspondent pas au modèle. Une entrée
sans contrainte correspondant presque au modèle d'expression régulière risque ainsi de nuire considérablement
aux performances.
Pour résoudre ce problème, vous pouvez effectuer les opérations suivantes :
Lorsque vous développez un modèle, vous devez réfléchir à la manière dont la rétroaction peut affecter les
performances du moteur des expressions régulières, en particulier si votre expression régulière est conçue
pour traiter des entrées sans contrainte. Pour plus d’informations, consultez la section prise en décharge de
la rétroaction .
Testez intégralement votre expression régulière à l'aide d'entrées non valides et presque valides, ainsi que
d'entrées valides. Pour générer de manière aléatoire une entrée pour une expression régulière spécifique,
vous pouvez utiliser Rex, l’outil d’exploration d’expressions régulières de Microsoft Research.

Gestion correcte de l’instanciation d’objet


La classe System.Text.RegularExpressions.Regex est au cœur du modèle d'objet d'expression régulière de .NET. Elle
représente le moteur d’expressions régulières. Souvent, la façon dont le moteur Regex est utilisé est le facteur
principal ayant un impact sur les performances des expressions régulières. La définition d’une expression régulière
implique une association étroite entre le moteur des expressions régulières et un modèle d’expression régulière. Ce
processus est forcément onéreux, qu’il implique l’instanciation d’un objet Regex en passant à son constructeur un
modèle d’expression régulière ou l’appel d’une méthode statique en lui passant le modèle d’expression régulière
avec la chaîne à analyser.
NOTE
Vous trouverez une présentation plus détaillée des répercussions sur les performances des expressions régulières interprétées
et compilées sur la page Optimiser les performances des expressions régulières, deuxième partie : prendre en charge le retour
arrière du blog de l'équipe BCL.

Vous pouvez associer le moteur des expressions régulières à un modèle d’expression régulière spécifique, puis
utiliser le moteur pour faire correspondre du texte de plusieurs façons :
Vous pouvez appeler une méthode statique de critères spéciaux, comme Regex.Match(String, String).
L'instanciation d'un objet d'expression régulière n'est pas nécessaire.
Vous pouvez instancier un objet Regex et appeler une méthode d'instance de critères spéciaux d'une
expression régulière interprétée. Il s’agit de la méthode par défaut pour lier le moteur des expressions
régulières à un modèle d’expression régulière. Elle se produit lorsqu'un objet Regex est instancié sans
argument options incluant l'indicateur Compiled.
Vous pouvez instancier un objet Regex et appeler une méthode d'instance de critères spéciaux d'une
expression régulière compilée. Les objets d’expression régulière représentent des modèles compilés
lorsqu’un objet Regex est instancié avec un argument options incluant l’indicateur Compiled.
Vous pouvez créer un objet Regex qui a un usage spécial et qui est fortement couplé à un modèle
d’expression régulière particulier, le compiler et l’enregistrer dans un assembly autonome. Pour ce faire,
appelez la méthode Regex.CompileToAssembly.
La façon dont vous appelez les méthodes de correspondance d'expression régulière peut avoir un impact
significatif sur votre application. Les sections suivantes expliquent quand utiliser les appels de méthode statique, les
expressions régulières interprétées et les expressions régulières compilées afin d'améliorer les performances de
votre application.

IMPORTANT
La forme de l'appel de méthode (statique, interprétée, compilée) affecte les performances si une même expression régulière
est utilisée à plusieurs reprises dans les appels de méthode, ou si une application entraîne l'utilisation intensive d'objets
d'expression régulière.

Expressions régulières statiques


Les méthodes d'expression régulière statiques sont recommandées pour éviter d'instancier à plusieurs reprises un
objet d'expression régulière avec la même expression régulière. Contrairement aux modèles d’expressions
régulières utilisés par les objets d’expression régulière, les codes d’opération ou le langage MSIL (Microsoft
Intermediate Language) compilé à partir des modèles utilisés dans les appels de méthode statique sont mis en
cache en interne par le moteur des expressions régulières.
Par exemple, un gestionnaire d'événements appelle fréquemment une autre méthode pour valider l'entrée
d'utilisateur. Ceci se reflète dans le code suivant, dans lequel l'événement Button d'un contrôle Click est utilisé pour
appeler une méthode nommée IsValidCurrency , qui vérifie si l'utilisateur a entré un symbole monétaire suivi d'au
moins un chiffre décimal.
public void OKButton_Click(object sender, EventArgs e)
{
if (! String.IsNullOrEmpty(sourceCurrency.Text))
if (RegexLib.IsValidCurrency(sourceCurrency.Text))
PerformConversion();
else
status.Text = "The source currency value is invalid.";
}

Public Sub OKButton_Click(sender As Object, e As EventArgs) _


Handles OKButton.Click

If Not String.IsNullOrEmpty(sourceCurrency.Text) Then


If RegexLib.IsValidCurrency(sourceCurrency.Text) Then
PerformConversion()
Else
status.Text = "The source currency value is invalid."
End If
End If
End Sub

L'exemple suivant illustre une implémentation très peu efficace de la méthode IsValidCurrency . Notez que chaque
appel de méthode réinstancie un objet Regex avec le même modèle. Cela signifie ainsi que le modèle d’expression
régulière doit être recompilé chaque fois que la méthode est appelée.

using System;
using System.Text.RegularExpressions;

public class RegexLib


{
public static bool IsValidCurrency(string currencyValue)
{
string pattern = @"\p{Sc}+\s*\d+";
Regex currencyRegex = new Regex(pattern);
return currencyRegex.IsMatch(currencyValue);
}
}

Imports System.Text.RegularExpressions

Public Module RegexLib


Public Function IsValidCurrency(currencyValue As String) As Boolean
Dim pattern As String = "\p{Sc}+\s*\d+"
Dim currencyRegex As New Regex(pattern)
Return currencyRegex.IsMatch(currencyValue)
End Function
End Module

Vous devez remplacer ce code peu efficace par un appel à la méthode statique Regex.IsMatch(String, String). De
cette manière, un objet Regex n'a pas besoin d'être instancié chaque fois que vous souhaitez appeler une méthode
de critères spéciaux. En outre, le moteur des expressions régulières est alors en mesure de récupérer une version
compilée de l'expression régulière depuis son cache.
using System;
using System.Text.RegularExpressions;

public class RegexLib


{
public static bool IsValidCurrency(string currencyValue)
{
string pattern = @"\p{Sc}+\s*\d+";
return Regex.IsMatch(currencyValue, pattern);
}
}

Imports System.Text.RegularExpressions

Public Module RegexLib


Public Function IsValidCurrency(currencyValue As String) As Boolean
Dim pattern As String = "\p{Sc}+\s*\d+"
Return Regex.IsMatch(currencyValue, pattern)
End Function
End Module

Par défaut, les 15 derniers modèles d’expressions régulières statiques utilisés récemment sont mis en cache. Pour
les applications qui requièrent un plus grand nombre d'expressions régulières statiques mises en cache, la taille du
cache peut être ajustée en définissant la propriété Regex.CacheSize.
L'expression régulière \p{Sc}+\s*\d+ utilisée dans cet exemple vérifie que la chaîne d'entrée se compose d'un
symbole monétaire et d'au moins un chiffre décimal. Le modèle est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\p{Sc}+ Mettre en correspondance un ou plusieurs caractères dans la


catégorie Unicode symbole, devise.

\s* Correspond à zéro, un ou plusieurs espaces blancs.

\d+ Mettre en correspondance un ou plusieurs chiffres décimaux.

Expressions régulières interprétées ou compilées


Les modèles d’expressions régulières qui ne sont pas associés au moteur des expressions régulières par la
spécification de l’option Compiled sont interprétés. Lorsqu'un objet d'expression régulière est instancié, le moteur
des expressions régulières convertit l'expression régulière en un ensemble de codes d'opération. Lorsqu'une
méthode d'instance est appelée, les codes d'opération sont convertis en langage MSIL et exécutés par le
compilateur JIT. De même, lorsqu'une méthode d'expression régulière statique est appelée et que l'expression
régulière ne peut pas être récupérée dans le cache, le moteur des expressions régulières convertit l'expression
régulière en un ensemble de codes d'opération et les stocke dans le cache. Il convertit ensuite ces codes
d'opération en langage MSIL afin que le compilateur JIT puisse les exécuter. Les expressions régulières interprétées
réduisent le temps de démarrage, mais ralentissent le temps d'exécution. Pour cette raison, il est préférable de les
utiliser lorsque l'expression régulière est utilisée dans un nombre d'appels de méthode restreint, ou lorsque le
nombre exact d'appels de méthodes d'expression régulière est inconnu, mais qu'il est supposé être petit. À mesure
que le nombre d'appels de méthode augmente, le ralentissement de la vitesse d'exécution l'emporte sur
l'amélioration des performances liée à la réduction du temps de démarrage.
Les modèles d’expressions régulières qui sont associés au moteur des expressions régulières par la spécification de
l’option Compiled sont compilés. Cela signifie que, lorsqu'un objet d'expression régulière est instancié ou
lorsqu'une méthode d'expression régulière statique est appelée et que l'expression régulière ne peut pas être
récupérée dans le cache, le moteur des expressions régulières convertit l'expression régulière en un ensemble de
codes d'opération intermédiaire, qu'il convertit ensuite en langage MSIL. Lorsqu'une méthode est appelée, le
compilateur JIT exécute le MSIL. Contrairement aux expressions régulières interprétées, les expressions régulières
compilées augmentent le temps de démarrage, mais elles exécutent plus rapidement les méthodes de critères
spéciaux individuelles. En conséquence, l'amélioration des performances due à la compilation de l'expression
régulière augmente en fonction du nombre de méthodes d'expression régulières appelées.
Pour résumer, nous vous conseillons d'utiliser des expressions régulières interprétées lorsque vous appelez
relativement peu souvent des méthodes d'expression régulières avec une expression régulière spécifique. Nous
vous conseillons d'utiliser des expressions régulières compilées lorsque vous appelez relativement souvent des
méthodes d'expression régulière avec une expression régulière spécifique. Il est difficile de déterminer le seuil exact
auquel le ralentissement de la vitesse d'exécution des expressions normales interprétées l'emporte sur la réduction
du temps de démarrage. Il est également difficile de déterminer le seuil auquel le ralentissement du temps de
démarrage des expressions régulières compilées l'emporte sur l'amélioration des vitesses d'exécution. Différents
facteurs doivent être pris en compte, notamment la complexité des expressions régulières et les données
spécifiques qui sont traitées. Pour déterminer si ce sont les expressions régulières interprétées ou compilées qui
offrent les meilleures performances pour votre scénario d'application spécifique, vous pouvez utiliser la classe
Stopwatch pour comparer les durées d'exécution.
L’exemple suivant compare les performances des expressions régulières compilées et interprétées sur la lecture des
dix premières phrases et de toutes les phrases du texte de Theodore Dreiser, The Financier. Comme l'indique la
sortie de l'exemple, lorsque les méthodes de correspondance d'expression régulière sont appelées seulement dix
fois, une expression régulière interprétée offre de meilleures performances qu'une expression régulière compilée.
Par contre, une expression régulière compilée offre de meilleures performances dans le cas d'un grand nombre
d'appels (dans le cas présent, plus de 13 000).

using System;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";
Stopwatch sw;
Match match;
int ctr;

StreamReader inFile = new StreamReader(@".\Dreiser_TheFinancier.txt");


string input = inFile.ReadToEnd();
inFile.Close();

// Read first ten sentences with interpreted regex.


Console.WriteLine("10 Sentences with Interpreted Regex:");
sw = Stopwatch.StartNew();
Regex int10 = new Regex(pattern, RegexOptions.Singleline);
match = int10.Match(input);
for (ctr = 0; ctr <= 9; ctr++) {
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine(" {0} matches in {1}", ctr, sw.Elapsed);

// Read first ten sentences with compiled regex.


Console.WriteLine("10 Sentences with Compiled Regex:");
sw = Stopwatch.StartNew();
sw = Stopwatch.StartNew();
Regex comp10 = new Regex(pattern,
RegexOptions.Singleline | RegexOptions.Compiled);
match = comp10.Match(input);
for (ctr = 0; ctr <= 9; ctr++) {
if (match.Success)
// Do nothing with the match except get the next match.
match = match.NextMatch();
else
break;
}
sw.Stop();
Console.WriteLine(" {0} matches in {1}", ctr, sw.Elapsed);

// Read all sentences with interpreted regex.


Console.WriteLine("All Sentences with Interpreted Regex:");
sw = Stopwatch.StartNew();
Regex intAll = new Regex(pattern, RegexOptions.Singleline);
match = intAll.Match(input);
int matches = 0;
while (match.Success) {
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine(" {0:N0} matches in {1}", matches, sw.Elapsed);

// Read all sentences with compiled regex.


Console.WriteLine("All Sentences with Compiled Regex:");
sw = Stopwatch.StartNew();
Regex compAll = new Regex(pattern,
RegexOptions.Singleline | RegexOptions.Compiled);
match = compAll.Match(input);
matches = 0;
while (match.Success) {
matches++;
// Do nothing with the match except get the next match.
match = match.NextMatch();
}
sw.Stop();
Console.WriteLine(" {0:N0} matches in {1}", matches, sw.Elapsed);
}
}
// The example displays the following output:
// 10 Sentences with Interpreted Regex:
// 10 matches in 00:00:00.0047491
// 10 Sentences with Compiled Regex:
// 10 matches in 00:00:00.0141872
// All Sentences with Interpreted Regex:
// 13,443 matches in 00:00:01.1929928
// All Sentences with Compiled Regex:
// 13,443 matches in 00:00:00.7635869
//
// >compare1
// 10 Sentences with Interpreted Regex:
// 10 matches in 00:00:00.0046914
// 10 Sentences with Compiled Regex:
// 10 matches in 00:00:00.0143727
// All Sentences with Interpreted Regex:
// 13,443 matches in 00:00:01.1514100
// All Sentences with Compiled Regex:
// 13,443 matches in 00:00:00.7432921

Imports System.Diagnostics
Imports System.IO
Imports System.Text.RegularExpressions
Module Example
Public Sub Main()
Dim pattern As String = "\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]"
Dim sw As Stopwatch
Dim match As Match
Dim ctr As Integer

Dim inFile As New StreamReader(".\Dreiser_TheFinancier.txt")


Dim input As String = inFile.ReadToEnd()
inFile.Close()

' Read first ten sentences with interpreted regex.


Console.WriteLine("10 Sentences with Interpreted Regex:")
sw = Stopwatch.StartNew()
Dim int10 As New Regex(pattern, RegexOptions.SingleLine)
match = int10.Match(input)
For ctr = 0 To 9
If match.Success Then
' Do nothing with the match except get the next match.
match = match.NextMatch()
Else
Exit For
End If
Next
sw.Stop()
Console.WriteLine(" {0} matches in {1}", ctr, sw.Elapsed)

' Read first ten sentences with compiled regex.


Console.WriteLine("10 Sentences with Compiled Regex:")
sw = Stopwatch.StartNew()
Dim comp10 As New Regex(pattern,
RegexOptions.SingleLine Or RegexOptions.Compiled)
match = comp10.Match(input)
For ctr = 0 To 9
If match.Success Then
' Do nothing with the match except get the next match.
match = match.NextMatch()
Else
Exit For
End If
Next
sw.Stop()
Console.WriteLine(" {0} matches in {1}", ctr, sw.Elapsed)

' Read all sentences with interpreted regex.


Console.WriteLine("All Sentences with Interpreted Regex:")
sw = Stopwatch.StartNew()
Dim intAll As New Regex(pattern, RegexOptions.SingleLine)
match = intAll.Match(input)
Dim matches As Integer = 0
Do While match.Success
matches += 1
' Do nothing with the match except get the next match.
match = match.NextMatch()
Loop
sw.Stop()
Console.WriteLine(" {0:N0} matches in {1}", matches, sw.Elapsed)

' Read all sentences with compiled regex.


Console.WriteLine("All Sentences with Compiled Regex:")
sw = Stopwatch.StartNew()
Dim compAll As New Regex(pattern,
RegexOptions.SingleLine Or RegexOptions.Compiled)
match = compAll.Match(input)
matches = 0
Do While match.Success
matches += 1
' Do nothing with the match except get the next match.
match = match.NextMatch()
match = match.NextMatch()
Loop
sw.Stop()
Console.WriteLine(" {0:N0} matches in {1}", matches, sw.Elapsed)
End Sub
End Module
' The example displays output like the following:
' 10 Sentences with Interpreted Regex:
' 10 matches in 00:00:00.0047491
' 10 Sentences with Compiled Regex:
' 10 matches in 00:00:00.0141872
' All Sentences with Interpreted Regex:
' 13,443 matches in 00:00:01.1929928
' All Sentences with Compiled Regex:
' 13,443 matches in 00:00:00.7635869
'
' >compare1
' 10 Sentences with Interpreted Regex:
' 10 matches in 00:00:00.0046914
' 10 Sentences with Compiled Regex:
' 10 matches in 00:00:00.0143727
' All Sentences with Interpreted Regex:
' 13,443 matches in 00:00:01.1514100
' All Sentences with Compiled Regex:
' 13,443 matches in 00:00:00.7432921

Le modèle d'expression régulière utilisé dans l'exemple, \b(\w+((\r?\n)|,?\s))*\w+[.?:;!] , est défini comme
indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

\w+ Mettre en correspondance un ou plusieurs caractères


alphabétiques.

(\r?\n)|,?\s) Mettre en correspondance un ou aucun retour chariot suivi


d'un caractère de saut de ligne, ou une ou aucune virgule,
suivie d'un espace blanc.

(\w+((\r?\n)|,?\s))* Mettre en correspondance zéro, une ou plusieurs occurrences


d'un ou plusieurs caractères alphabétiques qui sont suivis par
un ou aucun retour chariot et un caractère de saut de ligne,
ou par une ou aucune virgule, suivie d'un espace blanc.

\w+ Mettre en correspondance un ou plusieurs caractères


alphabétiques.

[.?:;!] Mettre en correspondance un point, un point d'interrogation,


deux-points, un point-virgule ou un point d'exclamation.

Expressions régulières : compilées dans un assembly


.NET permet également de créer un assembly contenant des expressions régulières compilées. La baisse de
performances des expressions régulières est alors ressentie au moment du design et non au moment de
l'exécution. Toutefois, cela engendre également un travail supplémentaire : vous devez définir les expressions
régulières à l'avance et les compiler dans un assembly. Le compilateur peut ensuite référencer cet assembly lors de
la compilation du code source qui utilise les expressions régulières de l'assembly. Chaque expression régulière
compilée dans l'assembly est représentée par une classe qui dérive de Regex.
Pour compiler des expressions régulières dans un assembly, vous appelez la méthode
Regex.CompileToAssembly(RegexCompilationInfo[], AssemblyName) et vous lui passez un tableau d'objets
RegexCompilationInfo représentant les expressions régulières à compiler, ainsi qu'un objet AssemblyName
contenant des informations sur l'assembly à créer.
Nous vous conseillons de compiler les expressions régulières dans un assembly dans les situations suivantes :
Vous êtes un développeur de composants et vous souhaitez créer une bibliothèque d'expressions régulières
réutilisables.
Vous savez que les méthodes de critères spéciaux de vos expression régulières seront appelées un nombre
de fois indéterminé (entre une fois et des dizaines de milliers de fois). Contrairement aux expressions
régulières compilées ou interprétées, les expressions régulières qui sont compilées dans des assemblys
distincts offrent des performances homogènes, indépendamment du nombre d'appels de méthode.
Si vous utilisez des expressions régulières compilées pour optimiser les performances, vous ne devez pas utiliser la
réflexion pour créer l’assembly, charger le moteur des expressions régulières et exécuter ses méthodes de critères
spéciaux. Vous devez donc éviter de générer dynamiquement des modèles d’expressions régulières et spécifier les
options de critères spéciaux (tels que des critères spéciaux de non respect de la casse) au moment de la création de
l’assembly. Vous devez également séparer le code qui crée l'assembly du code qui utilise l'expression régulière.
L'exemple suivant montre comment créer un assembly qui contient une expression régulière compilée. Il crée un
assembly nommé RegexLib.dll avec une classe d’expression régulière unique, SentencePattern , qui contient le
modèle d’expression régulière de phrase qui est utilisé dans la section expressions régulières interprétées et
expressions régulières compilées .

using System;
using System.Reflection;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
RegexCompilationInfo SentencePattern =
new RegexCompilationInfo(@"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]",
RegexOptions.Multiline,
"SentencePattern",
"Utilities.RegularExpressions",
true);
RegexCompilationInfo[] regexes = { SentencePattern };
AssemblyName assemName = new AssemblyName("RegexLib, Version=1.0.0.1001, Culture=neutral,
PublicKeyToken=null");
Regex.CompileToAssembly(regexes, assemName);
}
}
Imports System.Reflection
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim SentencePattern As New RegexCompilationInfo("\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]",
RegexOptions.Multiline,
"SentencePattern",
"Utilities.RegularExpressions",
True)
Dim regexes() As RegexCompilationInfo = {SentencePattern}
Dim assemName As New AssemblyName("RegexLib, Version=1.0.0.1001, Culture=neutral, PublicKeyToken=null")
Regex.CompileToAssembly(regexes, assemName)
End Sub
End Module

Lorsque l'exemple est compilé dans un exécutable et qu'il est exécuté, il crée un assembly nommé RegexLib.dll .
L'expression régulière est représentée par une classe nommée Utilities.RegularExpressions.SentencePattern ,
dérivée de Regex. L'exemple suivant utilise ensuite l'expression régulière compilée pour extraire les phrases du
texte de Theodore Dreiser, The Financier.

using System;
using System.IO;
using System.Text.RegularExpressions;
using Utilities.RegularExpressions;

public class Example


{
public static void Main()
{
SentencePattern pattern = new SentencePattern();
StreamReader inFile = new StreamReader(@".\Dreiser_TheFinancier.txt");
string input = inFile.ReadToEnd();
inFile.Close();

MatchCollection matches = pattern.Matches(input);


Console.WriteLine("Found {0:N0} sentences.", matches.Count);
}
}
// The example displays the following output:
// Found 13,443 sentences.

Imports System.IO
Imports System.Text.RegularExpressions
Imports Utilities.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As New SentencePattern()
Dim inFile As New StreamReader(".\Dreiser_TheFinancier.txt")
Dim input As String = inFile.ReadToEnd()
inFile.Close()

Dim matches As MatchCollection = pattern.Matches(input)


Console.WriteLine("Found {0:N0} sentences.", matches.Count)
End Sub
End Module
' The example displays the following output:
' Found 13,443 sentences.

Prise en charge de la rétroaction


Normalement, le moteur des expressions régulières utilise une progression linéaire pour se déplacer dans une
chaîne d’entrée et pour la comparer à un modèle d’expression régulière. Toutefois, lorsque les quantificateurs
indéterminés, * , + et ? , par exemple, sont utilisés dans un modèle d’expression régulière, le moteur des
expressions régulières peut abandonner une partie des correspondances partielles trouvées et revenir à un état
précédemment enregistré pour trouver une correspondance pour le modèle entier. Ce processus est appelé
« rétroaction ».

NOTE
Pour plus d'informations sur le retour arrière, consultez les pages Informations sur le comportement des expressions
régulières et Retour arrière. Vous trouverez une présentation détaillée du retour arrière sur la page Optimiser les
performances des expressions régulières, deuxième partie : prendre en charge le retour arrière du blog de l'équipe BCL.

La prise en charge de la rétroaction confère aux expressions régulières leur puissance et leur flexibilité. La
responsabilité de contrôle du fonctionnement du moteur des expressions régulières est alors confiée aux
développeurs d'expressions régulières. Souvent, les développeurs ne sont pas conscients de cette responsabilité.
Leur utilisation incorrecte de la rétroaction ou leur dépendance vis-à-vis d'une rétroaction excessive a souvent un
impact négatif très important sur les performances des expressions régulières. Dans le pire des scénarios, la durée
d'exécution peut doubler pour chaque caractère supplémentaire de la chaîne d'entrée. En réalité, lorsque la
rétroaction est utilisée de manière excessive, il est facile de créer l'équivalent de programmation d'une boucle sans
fin si l'entrée correspond presque au modèle d'expression régulière. Le moteur des expressions régulières peut
alors traiter une chaîne d'entrée relativement courte en plusieurs heures, voire en plusieurs jours.
Les performances des applications sont souvent altérées par l'utilisation de la rétroaction, même si ce processus
n'est pas essentiel pour une correspondance. Par exemple, l'expression régulière \b\p{Lu}\w*\b établit une
correspondance entre tous les mots qui commencent par une majuscule, comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

\p{Lu} Mettre en correspondance une majuscule.

\w* Mettre en correspondance zéro, un ou plusieurs caractères


alphabétiques.

\b Terminer la correspondance à la limite d'un mot.

Étant donné qu'une limite de mot est différente d'un caractère alphabétique et qu'elle n'est pas un sous-ensemble
de ce dernier, il est impossible que le moteur des expressions régulières franchisse une limite de mot lors de la mise
en correspondance de caractères alphabétiques. Cela signifie que, pour cette expression régulière, une rétroaction
ne peut jamais contribuer à la réussite globale d'une correspondance. Elle risque en revanche de diminuer les
performances, étant donné que le moteur des expressions régulières doit impérativement enregistrer sont état
pour chaque correspondance préliminaire d'un caractère alphabétique trouvée.
Si vous déterminez que la rétroaction n’est pas nécessaire, vous pouvez la désactiver à l’aide de l'
(?>subexpression) élément de langage, appelé groupe atomique. L'exemple suivant analyse une chaîne d'entrée à
l'aide de deux expressions régulières. La première, \b\p{Lu}\w*\b , utilise la rétroaction. La seconde,
\b\p{Lu}(?>\w*)\b , désactive la rétroaction. Comme l'indique la sortie de l'exemple, les résultats obtenus sont
identiques.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This this word Sentence name Capital";
string pattern = @"\b\p{Lu}\w*\b";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);

Console.WriteLine();

pattern = @"\b\p{Lu}(?>\w*)\b";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// This
// Sentence
// Capital
//
// This
// Sentence
// Capital

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This this word Sentence name Capital"
Dim pattern As String = "\b\p{Lu}\w*\b"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
Console.WriteLine()

pattern = "\b\p{Lu}(?>\w*)\b"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' This
' Sentence
' Capital
'
' This
' Sentence
' Capital

Dans de nombreux cas, la rétroaction est essentielle pour faire correspondre un modèle d’expression régulière à un
texte d’entrée. Toutefois, une rétroaction excessive risque d'altérer considérablement les performances et de donner
l'impression qu'une application a cessé de répondre. Cela se produit notamment lorsque les quantificateurs sont
imbriqués et que le texte correspondant à la sous-expression externe est un sous-ensemble du texte correspondant
à la sous-expression interne.
WARNING
En plus d’éviter une rétroaction excessive, vous devez utiliser la fonctionnalité de délai d’attente pour garantir qu’une
rétroaction excessive n’altère pas considérablement les performances des expressions régulières. Pour plus d’informations,
consultez la section utiliser des valeurs de délai d’attente .

Par exemple, le modèle d'expression régulière ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$ est conçu pour mettre en
correspondance un numéro de référence composé d'au moins un caractère alphanumérique. Tous les caractères
supplémentaires peuvent être composés d'un caractère alphanumérique, d'un trait d'union, d'un trait de
soulignement ou d'un point. Toutefois, le dernier caractère doit impérativement être alphanumérique. Un signe
dollar termine le numéro de référence. Dans certains cas, ce modèle d'expression régulière peut présenter des
performances médiocres, si les quantificateurs sont imbriqués et que la sous-expression [0-9A-Z] est un sous-
ensemble de la sous-expression [-.\w]* .
Dans ces cas, vous pouvez optimiser les performances des expressions régulières en supprimant les quantificateurs
imbriqués et en remplaçant la sous-expression externe par une assertion de préanalyse ou de postanalyse de
largeur nulle. Les assertions de préanalyse et de postanalyse sont des points d'ancrage. Elles ne déplacent pas le
pointeur dans la chaîne d'entrée, mais elles vérifient en amont et en aval si une condition spécifiée est remplie. Par
exemple, l'expression régulière de numéro de référence peut être réécrite sous la forme
^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$ . Ce modèle d'expression régulière est défini comme indiqué dans le tableau
suivant.

M O DÈL E DESC RIP T IO N

^ Commencer la correspondance au début de la chaîne d'entrée.

[0-9A-Z] Mettre en correspondance un caractère alphanumérique. Le


numéro de référence doit au minimum comporter ce
caractère.

[-.\w]* Mettre en correspondance zéro, une ou plusieurs occurrences


de tout caractère alphabétique, trait d'union ou point.

\$ Mettre en correspondance un signe dollar.

(?<=[0-9A-Z]) Effectuer une préanalyse avancée du signe dollar de fin de


s'assurer que le caractère précédent est un caractère
alphanumérique.

$ Terminer la correspondance à la fin de la chaîne d'entrée.

L'exemple suivant illustre l'utilisation de cette expression régulière pour faire correspondre un tableau pouvant
contenir des numéros de référence potentiels.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$";
string[] partNos = { "A1C$", "A4", "A4$", "A1603D$", "A1603D#" };

foreach (var input in partNos) {


Match match = Regex.Match(input, pattern);
if (match.Success)
Console.WriteLine(match.Value);
else
Console.WriteLine("Match not found.");
}
}
}
// The example displays the following output:
// A1C$
// Match not found.
// A4$
// A1603D$
// Match not found.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$"
Dim partNos() As String = {"A1C$", "A4", "A4$", "A1603D$",
"A1603D#"}

For Each input As String In partNos


Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine(match.Value)
Else
Console.WriteLine("Match not found.")
End If
Next
End Sub
End Module
' The example displays the following output:
' A1C$
' Match not found.
' A4$
' A1603D$
' Match not found.

Le langage d’expression régulière dans .NET comprend les éléments de langage suivants, que vous pouvez utiliser
pour éliminer les quantificateurs imbriqués. Pour plus d’informations, consultez Constructions de regroupement.

ÉL ÉM EN T DU L A N GA GE DESC RIP T IO N

(?= subexpression ) Préanalyse positive de largeur nulle. Effectuer une préanalyse


de la position actuelle pour déterminer si subexpression
correspond à la chaîne d'entrée.
ÉL ÉM EN T DU L A N GA GE DESC RIP T IO N

(?! subexpression ) Préanalyse négative de largeur nulle. Effectuer une préanalyse


de la position actuelle pour déterminer si subexpression ne
correspond pas à la chaîne d'entrée.

(?<= subexpression ) Postanalyse positive de largeur nulle. Effectuer une


postanalyse de la position actuelle pour déterminer si
subexpression correspond à la chaîne d'entrée.

(?<! subexpression ) Postanalyse négative de largeur nulle. Effectuer une


postanalyse de la position actuelle pour déterminer si
subexpression ne correspond pas à la chaîne d'entrée.

Utilisation de valeurs de délai d’attente


Si une expression régulière traite une entrée qui correspond presque au modèle d'expression régulière, elle peut
souvent se baser sur une rétroaction excessive, laquelle affecte considérablement ses performances. En plus
d'envisager soigneusement l'utilisation de la rétroaction et de tester l'expression régulière sur une entrée presque
correspondante, vous devez toujours définir une valeur de délai d'attente pour garantir la minimalisation de
l'impact d'une rétroaction excessive, le cas échéant.
L’intervalle de délai d’attente des expressions régulières définit la période pendant laquelle le moteur d’expression
régulière recherche une correspondance unique avant d’expirer. L’intervalle de délai d’attente par défaut est, ce qui
Regex.InfiniteMatchTimeout signifie que l’expression régulière n’expirera pas. Vous pouvez remplacer cette valeur et
définir un intervalle de délai d’attente comme suit :
En fournissant une valeur de délai d'attente quand vous instanciez un objet Regex en appelant le
constructeur Regex(String, RegexOptions, TimeSpan).
En appelant une méthode de mise en correspondance de modèles statique, telle que Regex.Match(String,
String, RegexOptions, TimeSpan) ou Regex.Replace(String, String, String, RegexOptions, TimeSpan), qui inclut
un paramètre matchTimeout .
Pour les expressions régulières compilées qui sont créées en appelant la méthode
Regex.CompileToAssembly, en appelant le constructeur ayant un paramètre de type TimeSpan.
Si vous avez défini un délai d'attente et qu'aucune correspondance n'est trouvée à la fin de cet intervalle, la
méthode d'expression régulière lève une exception RegexMatchTimeoutException. Dans votre gestionnaire
d'exceptions, vous pouvez choisir de réessayer la mise en correspondance avec un délai d'attente plus long,
d'annuler la recherche de correspondance et de supposer l'absence de correspondance, ou encore d'annuler la
recherche de correspondance et de consigner les informations sur les exceptions à des fins d'analyse ultérieure.
L'exemple ci-dessous définit une méthode GetWordData qui instancie une expression régulière avec un délai
d'attente de 350 millisecondes pour calculer le nombre de mots dans un document texte et le nombre moyen de
caractères par mot. Si le délai d'attente de l'opération de recherche de correspondance expire, le délai d'attente
augmente de 350 millisecondes et l'objet Regex est réinstancié. Si le nouveau délai d'attente dépasse 1 seconde, la
méthode lève de nouveau l'exception pour l'appelant.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
public static void Main()
{
RegexUtilities util = new RegexUtilities();
string title = "Doyle - The Hound of the Baskervilles.txt";
try {
var info = util.GetWordData(title);
Console.WriteLine("Words: {0:N0}", info.Item1);
Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2);
}
catch (IOException e) {
Console.WriteLine("IOException reading file '{0}'", title);
Console.WriteLine(e.Message);
}
catch (RegexMatchTimeoutException e) {
Console.WriteLine("The operation timed out after {0:N0} milliseconds",
e.MatchTimeout.TotalMilliseconds);
}
}
}

public class RegexUtilities


{
public Tuple<int, double> GetWordData(string filename)
{
const int MAX_TIMEOUT = 1000; // Maximum timeout interval in milliseconds.
const int INCREMENT = 350; // Milliseconds increment of timeout.

List<string> exclusions = new List<string>( new string[] { "a", "an", "the" });
int[] wordLengths = new int[29]; // Allocate an array of more than ample size.
string input = null;
StreamReader sr = null;
try {
sr = new StreamReader(filename);
input = sr.ReadToEnd();
}
catch (FileNotFoundException e) {
string msg = String.Format("Unable to find the file '{0}'", filename);
throw new IOException(msg, e);
}
catch (IOException e) {
throw new IOException(e.Message, e);
}
finally {
if (sr != null) sr.Close();
}

int timeoutInterval = INCREMENT;


bool init = false;
Regex rgx = null;
Match m = null;
int indexPos = 0;
do {
try {
if (! init) {
rgx = new Regex(@"\b\w+\b", RegexOptions.None,
TimeSpan.FromMilliseconds(timeoutInterval));
m = rgx.Match(input, indexPos);
init = true;
}
else {
m = m.NextMatch();
}
if (m.Success) {
if ( !exclusions.Contains(m.Value.ToLower()))
wordLengths[m.Value.Length]++;

indexPos += m.Length + 1;
}
}
catch (RegexMatchTimeoutException e) {
catch (RegexMatchTimeoutException e) {
if (e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT) {
timeoutInterval += INCREMENT;
init = false;
}
else {
// Rethrow the exception.
throw;
}
}
} while (m.Success);

// If regex completed successfully, calculate number of words and average length.


int nWords = 0;
long totalLength = 0;

for (int ctr = wordLengths.GetLowerBound(0); ctr <= wordLengths.GetUpperBound(0); ctr++) {


nWords += wordLengths[ctr];
totalLength += ctr * wordLengths[ctr];
}
return new Tuple<int, double>(nWords, totalLength/nWords);
}
}

Imports System.Collections.Generic
Imports System.IO
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim util As New RegexUtilities()
Dim title As String = "Doyle - The Hound of the Baskervilles.txt"
Try
Dim info = util.GetWordData(title)
Console.WriteLine("Words: {0:N0}", info.Item1)
Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2)
Catch e As IOException
Console.WriteLine("IOException reading file '{0}'", title)
Console.WriteLine(e.Message)
Catch e As RegexMatchTimeoutException
Console.WriteLine("The operation timed out after {0:N0} milliseconds",
e.MatchTimeout.TotalMilliseconds)
End Try
End Sub
End Module

Public Class RegexUtilities


Public Function GetWordData(filename As String) As Tuple(Of Integer, Double)
Const MAX_TIMEOUT As Integer = 1000 ' Maximum timeout interval in milliseconds.
Const INCREMENT As Integer = 350 ' Milliseconds increment of timeout.

Dim exclusions As New List(Of String)({"a", "an", "the"})


Dim wordLengths(30) As Integer ' Allocate an array of more than ample size.
Dim input As String = Nothing
Dim sr As StreamReader = Nothing
Try
sr = New StreamReader(filename)
input = sr.ReadToEnd()
Catch e As FileNotFoundException
Dim msg As String = String.Format("Unable to find the file '{0}'", filename)
Throw New IOException(msg, e)
Catch e As IOException
Throw New IOException(e.Message, e)
Finally
If sr IsNot Nothing Then sr.Close()
End Try

Dim timeoutInterval As Integer = INCREMENT


Dim timeoutInterval As Integer = INCREMENT
Dim init As Boolean = False
Dim rgx As Regex = Nothing
Dim m As Match = Nothing
Dim indexPos As Integer = 0
Do
Try
If Not init Then
rgx = New Regex("\b\w+\b", RegexOptions.None,
TimeSpan.FromMilliseconds(timeoutInterval))
m = rgx.Match(input, indexPos)
init = True
Else
m = m.NextMatch()
End If
If m.Success Then
If Not exclusions.Contains(m.Value.ToLower()) Then
wordLengths(m.Value.Length) += 1
End If
indexPos += m.Length + 1
End If
Catch e As RegexMatchTimeoutException
If e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT Then
timeoutInterval += INCREMENT
init = False
Else
' Rethrow the exception.
Throw
End If
End Try
Loop While m.Success

' If regex completed successfully, calculate number of words and average length.
Dim nWords As Integer
Dim totalLength As Long

For ctr As Integer = wordLengths.GetLowerBound(0) To wordLengths.GetUpperBound(0)


nWords += wordLengths(ctr)
totalLength += ctr * wordLengths(ctr)
Next
Return New Tuple(Of Integer, Double)(nWords, totalLength / nWords)
End Function
End Class

Capture uniquement quand cela s’avère nécessaire


Les expressions régulières dans .NET prennent en charge plusieurs constructions de regroupement, ce qui vous
permet de regrouper un modèle d’expression régulière dans une ou plusieurs sous-expressions. Les constructions
de regroupement les plus couramment utilisées dans le langage d’expression régulière .net sont sous- (
expression ) , qui définit un groupe de capture numéroté et nom sous- (?< name > expression ) , qui définit
un groupe de capture nommé. Les constructions de regroupement sont essentielles pour créer des références
arrières et pour définir une sous-expression à laquelle un quantificateur est appliqué.
Toutefois, l'utilisation de ces éléments de langage n'est pas sans effet. Ils entraînent le remplissage de l'objet
GroupCollection renvoyé par la propriété Match.Groups avec les captures nommées ou sans nom les plus récentes.
Si une seule construction de regroupement a capturé plusieurs sous-chaînes dans la chaîne d'entrée, elles
remplissent également l'objet CaptureCollection renvoyé par la propriété Group.Captures d'un groupe de capture
particulier à l'aide de plusieurs objets Capture.
Souvent, les constructions de regroupement sont utilisées dans une expression régulière uniquement pour que les
quantificateurs puissent leur être appliqués. Les groupes capturés par ces sous-expressions ne sont alors pas
utilisés par la suite. Par exemple, l'expression régulière \b(\w+[;,]?\s?)+[.?!] est conçue pour capturer une phrase
entière. Le tableau suivant décrit les éléments de langage dans ce modèle d'expression régulière et leur effet sur les
Match des objets Match.Groups et les collections Group.Captures.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

\w+ Mettre en correspondance un ou plusieurs caractères


alphabétiques.

[;,]? Mettre en correspondance zéro ou une virgule, ou zéro ou un


point-virgule.

\s? Mettre en correspondance zéro ou un espace blanc.

(\w+[;,]?\s?)+ Mettre en correspondance une ou plusieurs occurrences d'un


ou plusieurs caractères alphabétiques suivis d'une virgule ou
d'un point-virgule facultatif suivi d'un espace blanc facultatif.
Cela définit le premier groupe de capture. Il est nécessaire
pour que la combinaison de plusieurs caractères alphabétiques
(autrement dit, un mot) suivis d'un signe de ponctuation
facultatif soit répétée jusqu'à ce que le moteur des expressions
régulières ait atteint la fin d'une phrase.

[.?!] Mettre en correspondance un point, un point d'interrogation


ou un point d'exclamation.

Comme l'indique l'exemple suivant, lorsqu'une correspondance est trouvée, l'objet GroupCollection et l'objet
CaptureCollection sont remplis avec des captures de la correspondance. Dans ce cas, le groupe de capture
(\w+[;,]?\s?) existe afin que le quantificateur + puisse lui être appliqué, ce qui permet au modèle d'expression
régulière de correspondre à chaque mot d'une phrase. Sinon, elle correspondrait au dernier mot d'une phrase.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is one sentence. This is another.";
string pattern = @"\b(\w+[;,]?\s?)+[.?!]";

foreach (Match match in Regex.Matches(input, pattern)) {


Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index);
int grpCtr = 0;
foreach (Group grp in match.Groups) {
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index);
int capCtr = 0;
foreach (Capture cap in grp.Captures) {
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index);
capCtr++;
}
grpCtr++;
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: 'This is one sentence.' at index 0.
// Group 0: 'This is one sentence.' at index 0.
// Capture 0: 'This is one sentence.' at 0.
// Group 1: 'sentence' at index 12.
// Capture 0: 'This ' at 0.
// Capture 1: 'is ' at 5.
// Capture 2: 'one ' at 8.
// Capture 3: 'sentence' at 12.
//
// Match: 'This is another.' at index 22.
// Group 0: 'This is another.' at index 22.
// Capture 0: 'This is another.' at 22.
// Group 1: 'another' at index 30.
// Capture 0: 'This ' at 22.
// Capture 1: 'is ' at 27.
// Capture 2: 'another' at 30.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is one sentence. This is another."
Dim pattern As String = "\b(\w+[;,]?\s?)+[.?!]"

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In match.Groups
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index)
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index)
capCtr += 1
Next
grpCtr += 1
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is one sentence.' at index 0.
' Group 0: 'This is one sentence.' at index 0.
' Capture 0: 'This is one sentence.' at 0.
' Group 1: 'sentence' at index 12.
' Capture 0: 'This ' at 0.
' Capture 1: 'is ' at 5.
' Capture 2: 'one ' at 8.
' Capture 3: 'sentence' at 12.
'
' Match: 'This is another.' at index 22.
' Group 0: 'This is another.' at index 22.
' Capture 0: 'This is another.' at 22.
' Group 1: 'another' at index 30.
' Capture 0: 'This ' at 22.
' Capture 1: 'is ' at 27.
' Capture 2: 'another' at 30.

Lorsque vous utilisez des sous-expressions uniquement pour y appliquer des quantificateurs et que le texte capturé
ne vous intéresse pas, vous devez désactiver les captures de groupe. Par exemple, l'élément de langage
(?:subexpression) empêche le groupe auquel il s'applique de capturer les sous-chaînes correspondantes. Dans
l'exemple suivant, le modèle d'expression régulière de l'exemple précédent est remplacé par
\b(?:\w+[;,]?\s?)+[.?!] . Comme l'indique la sortie, le moteur des expressions régulières ne peut pas remplir les
collections GroupCollection et CaptureCollection.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is one sentence. This is another.";
string pattern = @"\b(?:\w+[;,]?\s?)+[.?!]";

foreach (Match match in Regex.Matches(input, pattern)) {


Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index);
int grpCtr = 0;
foreach (Group grp in match.Groups) {
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index);
int capCtr = 0;
foreach (Capture cap in grp.Captures) {
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index);
capCtr++;
}
grpCtr++;
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// Match: 'This is one sentence.' at index 0.
// Group 0: 'This is one sentence.' at index 0.
// Capture 0: 'This is one sentence.' at 0.
//
// Match: 'This is another.' at index 22.
// Group 0: 'This is another.' at index 22.
// Capture 0: 'This is another.' at 22.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is one sentence. This is another."
Dim pattern As String = "\b(?:\w+[;,]?\s?)+[.?!]"

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine("Match: '{0}' at index {1}.",
match.Value, match.Index)
Dim grpCtr As Integer = 0
For Each grp As Group In match.Groups
Console.WriteLine(" Group {0}: '{1}' at index {2}.",
grpCtr, grp.Value, grp.Index)
Dim capCtr As Integer = 0
For Each cap As Capture In grp.Captures
Console.WriteLine(" Capture {0}: '{1}' at {2}.",
capCtr, cap.Value, cap.Index)
capCtr += 1
Next
grpCtr += 1
Next
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' Match: 'This is one sentence.' at index 0.
' Group 0: 'This is one sentence.' at index 0.
' Capture 0: 'This is one sentence.' at 0.
'
' Match: 'This is another.' at index 22.
' Group 0: 'This is another.' at index 22.
' Capture 0: 'This is another.' at 22.

Vous pouvez désactiver les captures de l'une des façons suivantes :


Utilisez l'élément de langage (?:subexpression) . Cet élément empêche la capture des sous-chaînes
correspondantes dans le groupe auquel il s'applique. Il ne désactive pas les captures de la sous-chaîne dans
les groupes imbriqués.
Utilisez l'option ExplicitCapture. Elle désactive toutes les captures implicites ou sans nom dans le modèle
d’expression régulière. Avec cette option, seules les sous-chaînes qui correspondent à des groupes nommés
définis avec l'élément de langage (?<name>subexpression) peuvent être capturées. L'indicateur
ExplicitCapture peut être passé au paramètre options d'un constructeur de classe Regex ou au paramètre
options d'une méthode correspondante statique Regex.

Utilisez l'option n dans l'élément de langage (?imnsx) . Cette option désactive toutes les captures implicites
ou sans nom à partir du point où l’élément apparaît dans le modèle d’expression régulière. Les captures sont
désactivées jusqu’à la fin du modèle ou jusqu’à ce que l’option (-n) active les captures implicites ou sans
nom. Pour plus d'informations, consultez Miscellaneous Constructs.
Utilisez l'option n dans l'élément de langage (?imnsx:subexpression) . Cette option désactive toutes les
captures implicites ou sans nom dans subexpression . Les captures effectuées par les groupes de capture
imbriqués implicites ou sans nom sont également désactivées.

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Comportement détaillé des expressions régulières Aborde l’implémentation du moteur d’expression régulière
dans .NET. Cette rubrique traite de la flexibilité des expressions
régulières. Elle explique la responsabilité du développeur pour
que le fonctionnement efficace et fiable du moteur des
expressions régulières soit garanti.

Rétroaction Aborde la rétroaction et la façon dont elle affecte les


performances des expressions régulières, ainsi que les
éléments de langage, qui offrent des alternatives à la
rétroaction.

Langage des expressions régulières - Aide-mémoire Décrit les éléments du langage d’expression régulière dans
.NET et propose des liens vers la documentation détaillée pour
chaque élément de langage.
Modèle objet d'expression régulière
18/07/2020 • 44 minutes to read • Edit Online

Cette rubrique décrit le modèle objet utilisé avec les expressions régulières .NET. Il contient les sections suivantes :
Le moteur des expressions régulières
Objets MatchCollection et match
Collection de groupes
Groupe capturé
Collection de captures
Capture individuelle

Moteur d'expression régulière


Le moteur d’expression régulière dans .NET est représenté par la classe Regex. Le moteur d’expression régulière
prend en charge l’analyse et la compilation d’une expression régulière, ainsi que les opérations qui mettent en
correspondance le modèle d’expression régulière avec une chaîne d’entrée. Le moteur est le composant central du
modèle objet des expressions régulières .NET.
Vous pouvez utiliser le moteur d'expression régulière de deux façons :
En appelant les méthodes statiques de la classe Regex. Les paramètres des méthodes comprennent la
chaîne d'entrée et le modèle d'expression régulière. Le moteur d'expression régulière met en cache les
expressions régulières utilisées dans les appels de méthode statique ; les appels répétés de méthodes
d'expression régulière statiques qui utilisent la même expression régulière offrent donc des performances
relativement bonnes.
En instanciant un objet Regex, via la transmission d'une expression régulière au constructeur de classe.
Dans ce cas, l'objet Regex est immuable (lecture seule) et représente un moteur d'expression régulière
étroitement lié à une expression régulière unique. Comme les expressions régulières utilisées par les
instances de Regex ne sont pas mises en cache, vous ne devez pas instancier un objet Regex plusieurs fois
avec la même expression régulière.
Vous pouvez appeler les méthodes de la classe Regex pour effectuer les opérations suivantes :
Déterminer si une chaîne correspond à un modèle d'expression régulière.
Extraire une correspondance unique ou la première correspondance.
Extraire toutes les correspondances.
Remplacer une sous-chaîne mise en correspondance.
Fractionner une chaîne spécifique en un tableau de chaînes.
Ces opérations sont décrites en détail dans les sections suivantes.
Mise en correspondance d'un modèle d'expression régulière
La méthode Regex.IsMatch retourne true si la chaîne correspond au modèle, false sinon. La méthode IsMatch
est souvent utilisée pour valider une entrée de chaîne. Par exemple, le code suivant s'assure qu'une chaîne
correspond à un numéro de sécurité sociale valide au États-Unis.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] values = { "111-22-3333", "111-2-3333"};
string pattern = @"^\d{3}-\d{2}-\d{4}$";
foreach (string value in values) {
if (Regex.IsMatch(value, pattern))
Console.WriteLine("{0} is a valid SSN.", value);
else
Console.WriteLine("{0}: Invalid", value);
}
}
}
// The example displays the following output:
// 111-22-3333 is a valid SSN.
// 111-2-3333: Invalid

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim values() As String = {"111-22-3333", "111-2-3333"}
Dim pattern As String = "^\d{3}-\d{2}-\d{4}$"
For Each value As String In values
If Regex.IsMatch(value, pattern) Then
Console.WriteLine("{0} is a valid SSN.", value)
Else
Console.WriteLine("{0}: Invalid", value)
End If
Next
End Sub
End Module
' The example displays the following output:
' 111-22-3333 is a valid SSN.
' 111-2-3333: Invalid

Le modèle d'expression régulière ^\d{3}-\d{2}-\d{4}$ est interprété comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

^ Mettre en correspondance le début de la chaîne d'entrée.

\d{3} Mettre en correspondance trois chiffres décimaux.

- Mettre en correspondance un trait d'union.

\d{2} Mettre en correspondance deux chiffres décimaux.

- Mettre en correspondance un trait d'union.

\d{4} Mettre en correspondance quatre chiffres décimaux.

$ Mettre en correspondance la fin de la chaîne d'entrée.

Extraction d'une correspondance unique ou de la première correspondance


La méthode Regex.Match retourne un objet Match qui contient des informations sur la première sous-chaîne qui
correspond à un modèle d'expression régulière. Si la propriété Match.Success retourne true , indiquant alors
qu'une correspondance a été trouvée, vous pouvez récupérer les informations sur les correspondances suivantes
en appelant la méthode Match.NextMatch. Ces appels de méthode peuvent se poursuivre jusqu'à ce que la
propriété Match.Success retourne false . Par exemple, le code suivant utilise la méthode Regex.Match(String,
String) pour rechercher la première occurrence d'un mot en double dans une chaîne. Il appelle ensuite la méthode
Match.NextMatch pour rechercher les occurrences supplémentaires éventuelles. L'exemple examine la propriété
Match.Success après chaque appel de méthode pour déterminer si la mise en correspondance actuelle a réussi et
si elle doit être suivie d'un appel de la méthode Match.NextMatch.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is a a farm that that raises dairy cattle.";
string pattern = @"\b(\w+)\W+(\1)\b";
Match match = Regex.Match(input, pattern);
while (match.Success)
{
Console.WriteLine("Duplicate '{0}' found at position {1}.",
match.Groups[1].Value, match.Groups[2].Index);
match = match.NextMatch();
}
}
}
// The example displays the following output:
// Duplicate 'a' found at position 10.
// Duplicate 'that' found at position 22.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is a a farm that that raises dairy cattle."
Dim pattern As String = "\b(\w+)\W+(\1)\b"
Dim match As Match = Regex.Match(input, pattern)
Do While match.Success
Console.WriteLine("Duplicate '{0}' found at position {1}.", _
match.Groups(1).Value, match.Groups(2).Index)
match = match.NextMatch()
Loop
End Sub
End Module
' The example displays the following output:
' Duplicate 'a' found at position 10.
' Duplicate 'that' found at position 22.

Le modèle d'expression régulière \b(\w+)\W+(\1)\b est interprété comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

(\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques. Il s'agit du premier groupe de capture.
M O DÈL E DESC RIP T IO N

\W+ Mettre en correspondance un ou plusieurs caractères non


alphabétiques.

(\1) Mettre en correspondance la première chaîne capturée. Il


s'agit du deuxième groupe de capture.

\b Terminer la correspondance à la limite d'un mot.

Extraction de toutes les correspondances


La méthode Regex.Matches retourne un objet MatchCollection qui contient des informations sur toutes les
correspondances trouvées par le moteur d'expression régulière dans la chaîne d'entrée. Ainsi, l'exemple précédent
peut être réécrit pour appeler la méthode Matches au lieu des méthodes Match et NextMatch.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "This is a a farm that that raises dairy cattle.";
string pattern = @"\b(\w+)\W+(\1)\b";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("Duplicate '{0}' found at position {1}.",
match.Groups[1].Value, match.Groups[2].Index);
}
}
// The example displays the following output:
// Duplicate 'a' found at position 10.
// Duplicate 'that' found at position 22.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "This is a a farm that that raises dairy cattle."
Dim pattern As String = "\b(\w+)\W+(\1)\b"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("Duplicate '{0}' found at position {1}.", _
match.Groups(1).Value, match.Groups(2).Index)
Next
End Sub
End Module
' The example displays the following output:
' Duplicate 'a' found at position 10.
' Duplicate 'that' found at position 22.

Remplacement d'une sous-chaîne mise en correspondance


La méthode Regex.Replace remplace chaque sous-chaîne mise en correspondance par le modèle d'expression
régulière par une chaîne ou un modèle d'expression régulière spécifique, puis retourne la chaîne d'entrée entière
avec les remplacements. Par exemple, le code suivant ajoute le symbole de devise des États-Unis avant un nombre
décimal dans une chaîne.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b\d+\.\d{2}\b";
string replacement = "$$$&";
string input = "Total Cost: 103.64";
Console.WriteLine(Regex.Replace(input, pattern, replacement));
}
}
// The example displays the following output:
// Total Cost: $103.64

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b\d+\.\d{2}\b"
Dim replacement As String = "$$$&"
Dim input As String = "Total Cost: 103.64"
Console.WriteLine(Regex.Replace(input, pattern, replacement))
End Sub
End Module
' The example displays the following output:
' Total Cost: $103.64

Le modèle d'expression régulière \b\d+\.\d{2}\b est interprété comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

\d+ Mettre en correspondance un ou plusieurs chiffres décimaux.

\. Mettre en correspondance un point.

\d{2} Mettre en correspondance deux chiffres décimaux.

\b Terminer la correspondance à la limite d'un mot.

Le modèle de remplacement $$$& est interprété comme indiqué dans le tableau suivant.

M O DÈL E C H A ÎN E DE REM P L A C EM EN T

$$ Caractère du signe dollar ($).

$& Sous-chaîne entière mise en correspondance.

Fractionnement d'une chaîne spécifique en un tableau de chaînes


La méthode Regex.Split fractionne la chaîne d'entrée aux positions définies par une correspondance d'expression
régulière. Par exemple, le code suivant place les éléments d'une liste numérotée dans un tableau de chaînes.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "1. Eggs 2. Bread 3. Milk 4. Coffee 5. Tea";
string pattern = @"\b\d{1,2}\.\s";
foreach (string item in Regex.Split(input, pattern))
{
if (! String.IsNullOrEmpty(item))
Console.WriteLine(item);
}
}
}
// The example displays the following output:
// Eggs
// Bread
// Milk
// Coffee
// Tea

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "1. Eggs 2. Bread 3. Milk 4. Coffee 5. Tea"
Dim pattern As String = "\b\d{1,2}\.\s"
For Each item As String In Regex.Split(input, pattern)
If Not String.IsNullOrEmpty(item) Then
Console.WriteLine(item)
End If
Next
End Sub
End Module
' The example displays the following output:
' Eggs
' Bread
' Milk
' Coffee
' Tea

Le modèle d'expression régulière \b\d{1,2}\.\s est interprété comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

\d{1,2} Mettre en correspondance un ou deux chiffres décimaux.

\. Mettre en correspondance un point.

\s Mettre en correspondance un espace blanc.

Objets MatchCollection et Match


Les méthodes d'expression régulière retournent deux objets qui font partie du modèle objet d'expression
régulière : l'objet MatchCollection et l'objet Match.
Collection de correspondances
La méthode Regex.Matches retourne un objet MatchCollection qui contient des objets Match représentant toutes
les correspondances trouvées par le moteur d'expression régulière, dans l'ordre dans lequel elles se produisent
dans la chaîne d'entrée. En l'absence de correspondance, la méthode retourne un objet MatchCollection sans
membres. La propriété MatchCollection.Item[] vous permet d'accéder à des membres spécifiques de la collection
en fonction de leur index, qui est compris entre zéro et la valeur de la propriété MatchCollection.Count moins une
unité. Item[] est l'indexeur de la collection (en C#) et la propriété par défaut (en Visual Basic).
Par défaut, l'appel de la méthode Regex.Matches utilise une évaluation tardive pour remplir l'objet
MatchCollection. L'accès aux propriétés qui nécessitent une collection entièrement remplie, telles que les
propriétés MatchCollection.Count et MatchCollection.Item[], peut affecter les performances. Nous vous
recommandons donc d'accéder à la collection en utilisant l'objet IEnumerator retourné par la méthode
MatchCollection.GetEnumerator. Des constructions propres au langage, comme For Each en Visual Basic et
foreach en C#, encapsulent l'interface IEnumerator de la collection.

L'exemple suivant utilise la méthode Regex.Matches(String) pour remplir un objet MatchCollection avec toutes les
correspondances trouvées dans une chaîne d'entrée. L’exemple énumère la collection, copie les correspondances
dans un tableau de chaînes et enregistre les positions de caractère dans un tableau d’entiers.

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
MatchCollection matches;
List<string> results = new List<string>();
List<int> matchposition = new List<int>();

// Create a new Regex object and define the regular expression.


Regex r = new Regex("abc");
// Use the Matches method to find all matches in the input string.
matches = r.Matches("123abc4abcd");
// Enumerate the collection to retrieve all matches and positions.
foreach (Match match in matches)
{
// Add the match string to the string array.
results.Add(match.Value);
// Record the character position where the match was found.
matchposition.Add(match.Index);
}
// List the results.
for (int ctr = 0; ctr < results.Count; ctr++)
Console.WriteLine("'{0}' found at position {1}.",
results[ctr], matchposition[ctr]);
}
}
// The example displays the following output:
// 'abc' found at position 3.
// 'abc' found at position 7.
Imports System.Collections.Generic
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim matches As MatchCollection
Dim results As New List(Of String)
Dim matchposition As New List(Of Integer)

' Create a new Regex object and define the regular expression.
Dim r As New Regex("abc")
' Use the Matches method to find all matches in the input string.
matches = r.Matches("123abc4abcd")
' Enumerate the collection to retrieve all matches and positions.
For Each match As Match In matches
' Add the match string to the string array.
results.Add(match.Value)
' Record the character position where the match was found.
matchposition.Add(match.Index)
Next
' List the results.
For ctr As Integer = 0 To results.Count - 1
Console.WriteLine("'{0}' found at position {1}.", _
results(ctr), matchposition(ctr))
Next
End Sub
End Module
' The example displays the following output:
' 'abc' found at position 3.
' 'abc' found at position 7.

Classe Match
La classe Match représente le résultat d'une correspondance d'expression régulière unique. Vous pouvez accéder
aux objets Match de deux façons :
En les récupérant de l'objet MatchCollection retourné par la méthode Regex.Matches. Pour récupérer des
objets Match spécifiques, itérez la collection en utilisant une construction foreach (en C#) ou For Each ...
Next (en Visual Basic), ou utilisez la propriété MatchCollection.Item[] pour récupérer un objet Match
spécifique en fonction de son index ou de son nom. Vous pouvez également récupérer des objets Match
spécifiques de la collection en itérant la collection à partir des valeurs d'index, qui peuvent aller de zéro au
nombre d'objets dans la collection moins une unité. Toutefois, cette méthode ne tire pas parti de
l'évaluation tardive, car elle accède à la propriété MatchCollection.Count.
L'exemple suivant récupère des objets Match spécifiques d'un objet MatchCollection en itérant la collection
à l'aide de la construction foreach ou For Each ... Next . L'expression régulière met simplement en
correspondance la chaîne « abc » dans la chaîne d'entrée.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "abc";
string input = "abc123abc456abc789";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("{0} found at position {1}.",
match.Value, match.Index);
}
}
// The example displays the following output:
// abc found at position 0.
// abc found at position 6.
// abc found at position 12.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "abc"
Dim input As String = "abc123abc456abc789"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("{0} found at position {1}.", _
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' abc found at position 0.
' abc found at position 6.
' abc found at position 12.

En appelant la méthode Regex.Match, qui retourne un objet Match représentant la première


correspondance dans une chaîne ou dans une partie d'une chaîne. Vous pouvez déterminer si la
correspondance a été trouvée en récupérant la valeur de la propriété Match.Success . Pour récupérer les
objets Match qui représentent les correspondances suivantes, appelez la méthode Match.NextMatch de
manière répétée, jusqu'à ce que la propriété Success de l'objet Match retourné ait pour valeur false .
L'exemple suivant utilise les méthodes Regex.Match(String, String) et Match.NextMatch pour mettre en
correspondance la chaîne « abc » dans la chaîne d'entrée.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "abc";
string input = "abc123abc456abc789";
Match match = Regex.Match(input, pattern);
while (match.Success)
{
Console.WriteLine("{0} found at position {1}.",
match.Value, match.Index);
match = match.NextMatch();
}
}
}
// The example displays the following output:
// abc found at position 0.
// abc found at position 6.
// abc found at position 12.

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "abc"
Dim input As String = "abc123abc456abc789"
Dim match As Match = Regex.Match(input, pattern)
Do While match.Success
Console.WriteLine("{0} found at position {1}.", _
match.Value, match.Index)
match = match.NextMatch()
Loop
End Sub
End Module
' The example displays the following output:
' abc found at position 0.
' abc found at position 6.
' abc found at position 12.

Deux propriétés de la classe Match retournent des objets de collection :


La propriété Match.Groups retourne un objet GroupCollection qui contient des informations sur les sous-
chaînes correspondant aux groupes de capture dans le modèle d'expression régulière.
La propriété Match.Captures retourne un objet CaptureCollection dont l'utilisation est limitée. La collection
n'est pas remplie dans le cas d'un objet Match dont la propriété Success a pour valeur false . Sinon, elle
contient un objet Capture unique qui possède les mêmes informations que l'objet Match.
Pour plus d’informations sur ces objets, consultez les sections La collection de groupes et La collection de captures
plus avant dans cette rubrique.
Deux propriétés supplémentaires de la classe Match fournissent des informations sur la correspondance. La
propriété Match.Value retourne la sous-chaîne de la chaîne d'entrée qui correspond au modèle d'expression
régulière. La propriété Match.Index retourne la position de début en base zéro de la chaîne mise en
correspondance dans la chaîne d'entrée.
En outre, la classe Match possède deux méthodes de mise en correspondance de modèle :
La méthode Match.NextMatch trouve la correspondance après la correspondance représentée par l'objet
Match actuel, puis retourne un objet Match représentant cette correspondance.
La méthode Match.Result effectue une opération de remplacement spécifique sur la chaîne mise en
correspondance et retourne le résultat.
L'exemple suivant utilise la méthode Match.Result pour ajouter un symbole $ et un espace avant chaque nombre
comprenant deux chiffres fractionnaires.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b\d+(,\d{3})*\.\d{2}\b";
string input = "16.32\n194.03\n1,903,672.08";

foreach (Match match in Regex.Matches(input, pattern))


Console.WriteLine(match.Result("$$ $&"));
}
}
// The example displays the following output:
// $ 16.32
// $ 194.03
// $ 1,903,672.08

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b\d+(,\d{3})*\.\d{2}\b"
Dim input As String = "16.32" + vbCrLf + "194.03" + vbCrLf + "1,903,672.08"

For Each match As Match In Regex.Matches(input, pattern)


Console.WriteLine(match.Result("$$ $&"))
Next
End Sub
End Module
' The example displays the following output:
' $ 16.32
' $ 194.03
' $ 1,903,672.08

Le modèle d'expression régulière \b\d+(,\d{3})*\.\d{2}\b est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

\d+ Mettre en correspondance un ou plusieurs chiffres décimaux.

(,\d{3})* Mettre en correspondance zéro occurrence, ou plus, d'une


virgule suivie de trois chiffres décimaux.

\. Mettre en correspondance le séparateur décimal.

\d{2} Mettre en correspondance deux chiffres décimaux.


M O DÈL E DESC RIP T IO N

\b Terminer la correspondance à la limite d'un mot.

Le modèle de remplacement $$ $& indique que la sous-chaîne mise en correspondance doit être remplacée par
un symbole dollar ($) (modèle $$ ), suivi d'un espace et de la valeur de la correspondance (modèle $& ).
Retour au début

Collection de groupes
La propriété Match.Groups retourne un objet GroupCollection qui contient des objets Group représentant des
groupes capturés dans une même correspondance. Le premier objet Group de la collection (index 0) représente la
correspondance entière. Chaque objet qui suit représente le résultat d'un groupe de capture spécifique.
Vous pouvez récupérer des objets Group spécifiques dans la collection en utilisant la propriété
GroupCollection.Item[]. Vous pouvez récupérer les groupes sans nom en fonction de leur position ordinale dans la
collection, et les groupes nommés en fonction de leur nom ou de leur position ordinale. Les captures sans nom
apparaissent en premier dans la collection et sont indexées de la gauche vers la droite dans l'ordre dans lequel
elles figurent dans le modèle d'expression régulière. Les captures nommées sont indexées après les captures sans
nom, de la gauche vers la droite dans l’ordre dans lequel elles apparaissent dans le modèle d’expression régulière.
Pour déterminer les groupes numérotés disponibles dans la collection retournée pour une méthode de mise en
correspondance d'expression régulière particulière, vous pouvez appeler la méthode d'instance
Regex.GetGroupNumbers. Pour déterminer les groupes nommés disponibles dans la collection, vous pouvez
appeler la méthode d'instance Regex.GetGroupNames. Les deux méthodes sont particulièrement utiles dans les
opérations générales qui analysent les correspondances trouvées par une expression régulière.
La propriété GroupCollection.Item[] représente l'indexeur de la collection en C# et la propriété par défaut de
l'objet de collection en Visual Basic. Cela signifie que les différents objets Group sont accessibles en fonction de
leur index (ou nom, dans le cas des groupes nommés) comme suit :

Group group = match.Groups[ctr];

Dim group As Group = match.Groups(ctr)

L'exemple suivant définit une expression régulière qui utilise des constructions de regroupement pour capturer les
mois, jour et année d'une date.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(\w+)\s(\d{1,2}),\s(\d{4})\b";
string input = "Born: July 28, 1989";
Match match = Regex.Match(input, pattern);
if (match.Success)
for (int ctr = 0; ctr < match.Groups.Count; ctr++)
Console.WriteLine("Group {0}: {1}", ctr, match.Groups[ctr].Value);
}
}
// The example displays the following output:
// Group 0: July 28, 1989
// Group 1: July
// Group 2: 28
// Group 3: 1989

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(\w+)\s(\d{1,2}),\s(\d{4})\b"
Dim input As String = "Born: July 28, 1989"
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
For ctr As Integer = 0 To match.Groups.Count - 1
Console.WriteLine("Group {0}: {1}", ctr, match.Groups(ctr).Value)
Next
End If
End Sub
End Module
' The example displays the following output:
' Group 0: July 28, 1989
' Group 1: July
' Group 2: 28
' Group 3: 1989

Le modèle d'expression régulière \b(\w+)\s(\d{1,2}),\s(\d{4})\b est défini comme indiqué dans le tableau
suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

(\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques. Il s'agit du premier groupe de capture.

\s Mettre en correspondance un espace blanc.

(\d{1,2}) Mettre en correspondance un ou deux chiffres décimaux. Il


s'agit du deuxième groupe de capture.

, Mettre en correspondance une virgule.

\s Mettre en correspondance un espace blanc.


M O DÈL E DESC RIP T IO N

(\d{4}) Mettre en correspondance quatre chiffres décimaux. Il s'agit


du troisième groupe de capture.

\b Terminer la correspondance à la limite d'un mot.

Retour au début

Groupe capturé
La classe Group représente le résultat d'un groupe de capture spécifique. Les objets de groupe qui représentent
les groupes de capture définis dans une expression régulière sont retournés par la propriété Item[] de l'objet
GroupCollection retourné par la propriété Match.Groups. La propriété Item[] représente l'indexeur (en C#) et la
propriété par défaut (en Visual Basic) de la classe Group. Vous pouvez également récupérer des membres
spécifiques en itérant la collection à l'aide de la construction foreach ou For Each . La section précédente
propose un exemple.
L'exemple suivant utilise des constructions de regroupement imbriquées pour capturer des sous-chaînes en
groupes. Le modèle d'expression régulière (a(b))c met en correspondance la chaîne « abc ». Il affecte la sous-
chaîne « ab » au premier groupe de capture, et la sous-chaîne « b » au second groupe de capture.

var matchposition = new List<int>();


var results = new List<string>();
// Define substrings abc, ab, b.
var r = new Regex("(a(b))c");
Match m = r.Match("abdabc");
for (int i = 0; m.Groups[i].Value != ""; i++)
{
// Add groups to string array.
results.Add(m.Groups[i].Value);
// Record character position.
matchposition.Add(m.Groups[i].Index);
}

// Display the capture groups.


for (int ctr = 0; ctr < results.Count; ctr++)
Console.WriteLine("{0} at position {1}",
results[ctr], matchposition[ctr]);
// The example displays the following output:
// abc at position 3
// ab at position 3
// b at position 4
Dim matchposition As New List(Of Integer)
Dim results As New List(Of String)
' Define substrings abc, ab, b.
Dim r As New Regex("(a(b))c")
Dim m As Match = r.Match("abdabc")
Dim i As Integer = 0
While Not (m.Groups(i).Value = "")
' Add groups to string array.
results.Add(m.Groups(i).Value)
' Record character position.
matchposition.Add(m.Groups(i).Index)
i += 1
End While

' Display the capture groups.


For ctr As Integer = 0 to results.Count - 1
Console.WriteLine("{0} at position {1}", _
results(ctr), matchposition(ctr))
Next
' The example displays the following output:
' abc at position 3
' ab at position 3
' b at position 4

L'exemple suivant utilise des constructions de regroupement nommées pour capturer des sous-chaînes dans une
chaîne qui contient des données au format « NOM_DONNÉES:VALEUR », que l'expression régulière fractionne au
niveau du symbole deux-points (:).

var r = new Regex(@"^(?<name>\w+):(?<value>\w+)");


Match m = r.Match("Section1:119900");
Console.WriteLine(m.Groups["name"].Value);
Console.WriteLine(m.Groups["value"].Value);
// The example displays the following output:
// Section1
// 119900

Dim r As New Regex("^(?<name>\w+):(?<value>\w+)")


Dim m As Match = r.Match("Section1:119900")
Console.WriteLine(m.Groups("name").Value)
Console.WriteLine(m.Groups("value").Value)
' The example displays the following output:
' Section1
' 119900

Le modèle d'expression régulière ^(?<name>\w+):(?<value>\w+) est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

^ Commencer la correspondance au début de la chaîne


d'entrée.

(?<name>\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques. Le nom de ce groupe de capture est name .

: Mettre en correspondance un symbole deux-points.

(?<value>\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques. Le nom de ce groupe de capture est value .
Les propriétés de la classe Group fournissent des informations sur le groupe capturé : la propriété Group.Value
contient la sous-chaîne capturée, la propriété Group.Index indique la position de début du groupe capturé dans le
texte d'entrée, la propriété Group.Length contient la longueur du texte capturé et la propriété Group.Success
indique si une correspondance a été trouvée entre une sous-chaîne et le modèle défini par le groupe de capture.
L'application de quantificateurs à un groupe (pour plus d'informations, consultez Quantificateurs) modifie la
relation d'une capture par groupe de capture de deux façons :
Si le quantificateur * ou *? (qui spécifie zéro correspondance, ou plus) est appliqué à un groupe, un
groupe de capture peut ne pas avoir de correspondance dans la chaîne d'entrée. En l'absence de texte
capturé, les propriétés de l'objet Group sont définies comme indiqué dans le tableau suivant.

P RO P RIÉT É DE GRO UP E VA L EUR

Success false

Value String.Empty

Length 0

L'exemple suivant en est l'illustration. Dans le modèle d'expression régulière aaa(bbb)*ccc , le premier
groupe de capture (la sous-chaîne « bbb ») peut être mis en correspondance zéro fois, ou plus. Comme la
chaîne d’entrée « aaaccc » correspond au modèle, le groupe de capture ne possède pas de correspondance.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "aaa(bbb)*ccc";
string input = "aaaccc";
Match match = Regex.Match(input, pattern);
Console.WriteLine("Match value: {0}", match.Value);
if (match.Groups[1].Success)
Console.WriteLine("Group 1 value: {0}", match.Groups[1].Value);
else
Console.WriteLine("The first capturing group has no match.");
}
}
// The example displays the following output:
// Match value: aaaccc
// The first capturing group has no match.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "aaa(bbb)*ccc"
Dim input As String = "aaaccc"
Dim match As Match = Regex.Match(input, pattern)
Console.WriteLine("Match value: {0}", match.Value)
If match.Groups(1).Success Then
Console.WriteLine("Group 1 value: {0}", match.Groups(1).Value)
Else
Console.WriteLine("The first capturing group has no match.")
End If
End Sub
End Module
' The example displays the following output:
' Match value: aaaccc
' The first capturing group has no match.

Les quantificateurs peuvent mettre en correspondance plusieurs occurrences d'un modèle défini par un
groupe de capture. Dans ce cas, les propriétés Value et Length d'un objet Group ne contiennent des
informations que sur la dernière sous-chaîne capturée. Par exemple, l'expression régulière suivante met en
correspondance une phrase unique qui se termine par un point. Elle utilise deux constructions de
regroupement : la première capture des mots individuels ainsi qu'un espace blanc, tandis que la seconde
capture des mots individuels. Comme le montre le résultat de l'exemple, bien que l'expression régulière
parvienne à capturer une phrase entière, le second groupe capture uniquement le dernier mot.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b((\w+)\s?)+\.";
string input = "This is a sentence. This is another sentence.";
Match match = Regex.Match(input, pattern);
if (match.Success)
{
Console.WriteLine("Match: " + match.Value);
Console.WriteLine("Group 2: " + match.Groups[2].Value);
}
}
}
// The example displays the following output:
// Match: This is a sentence.
// Group 2: sentence
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b((\w+)\s?)+\."
Dim input As String = "This is a sentence. This is another sentence."
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine("Match: " + match.Value)
Console.WriteLine("Group 2: " + match.Groups(2).Value)
End If
End Sub
End Module
' The example displays the following output:
' Match: This is a sentence.
' Group 2: sentence

Retour au début

Collection de captures
L'objet Group ne contient des informations que sur la dernière capture. Toutefois, l'ensemble complet des captures
effectuées par un groupe de capture est récupérable de l'objet CaptureCollection retourné par la propriété
Group.Captures. Chaque membre de la collection est un objet Capture qui représente une capture effectuée par ce
groupe de capture, dans l'ordre dans lequel ils ont été capturés (donc, dans l'ordre dans lequel les chaînes
capturées ont été mises en correspondance de la gauche vers la droite dans la chaîne d'entrée). Vous pouvez
récupérer des objets Capture spécifiques de la collection de deux façons :
En itérant la collection à l’aide d’une construction telle que foreach (en C#) ou For Each (en Visual Basic).
En utilisant la propriété CaptureCollection.Item[] pour récupérer un objet spécifique en fonction de son
index. La propriété Item[] représente l'indexeur (en C#) ou la propriété par défaut de l'objet
CaptureCollection (en Visual Basic).
Si aucun quantificateur n'est appliqué à un groupe de capture, l'objet CaptureCollection contient un objet Capture
unique qui présente peu d'intérêt, car il fournit des informations sur la même correspondance que son objet
Group. Si un quantificateur est appliqué à un groupe de capture, l'objet CaptureCollection contient toutes les
captures effectuées par le groupe de capture, et le dernier membre de la collection représente la même capture
que l'objet Group.
Par exemple, si vous utilisez le modèle d'expression régulière ((a(b))c)+ (où le quantificateur + spécifie une ou
plusieurs correspondances) pour capturer des correspondances dans la chaîne « abcabcabc », l'objet
CaptureCollection de chaque objet Group contient trois membres.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "((a(b))c)+";
string input = "abcabcabc";

Match match = Regex.Match(input, pattern);


if (match.Success)
{
Console.WriteLine("Match: '{0}' at position {1}",
match.Value, match.Index);
GroupCollection groups = match.Groups;
for (int ctr = 0; ctr < groups.Count; ctr++) {
Console.WriteLine(" Group {0}: '{1}' at position {2}",
ctr, groups[ctr].Value, groups[ctr].Index);
CaptureCollection captures = groups[ctr].Captures;
for (int ctr2 = 0; ctr2 < captures.Count; ctr2++) {
Console.WriteLine(" Capture {0}: '{1}' at position {2}",
ctr2, captures[ctr2].Value, captures[ctr2].Index);
}
}
}
}
}
// The example displays the following output:
// Match: 'abcabcabc' at position 0
// Group 0: 'abcabcabc' at position 0
// Capture 0: 'abcabcabc' at position 0
// Group 1: 'abc' at position 6
// Capture 0: 'abc' at position 0
// Capture 1: 'abc' at position 3
// Capture 2: 'abc' at position 6
// Group 2: 'ab' at position 6
// Capture 0: 'ab' at position 0
// Capture 1: 'ab' at position 3
// Capture 2: 'ab' at position 6
// Group 3: 'b' at position 7
// Capture 0: 'b' at position 1
// Capture 1: 'b' at position 4
// Capture 2: 'b' at position 7
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "((a(b))c)+"
Dim input As STring = "abcabcabc"

Dim match As Match = Regex.Match(input, pattern)


If match.Success Then
Console.WriteLine("Match: '{0}' at position {1}", _
match.Value, match.Index)
Dim groups As GroupCollection = match.Groups
For ctr As Integer = 0 To groups.Count - 1
Console.WriteLine(" Group {0}: '{1}' at position {2}", _
ctr, groups(ctr).Value, groups(ctr).Index)
Dim captures As CaptureCollection = groups(ctr).Captures
For ctr2 As Integer = 0 To captures.Count - 1
Console.WriteLine(" Capture {0}: '{1}' at position {2}", _
ctr2, captures(ctr2).Value, captures(ctr2).Index)
Next
Next
End If
End Sub
End Module
' The example dosplays the following output:
' Match: 'abcabcabc' at position 0
' Group 0: 'abcabcabc' at position 0
' Capture 0: 'abcabcabc' at position 0
' Group 1: 'abc' at position 6
' Capture 0: 'abc' at position 0
' Capture 1: 'abc' at position 3
' Capture 2: 'abc' at position 6
' Group 2: 'ab' at position 6
' Capture 0: 'ab' at position 0
' Capture 1: 'ab' at position 3
' Capture 2: 'ab' at position 6
' Group 3: 'b' at position 7
' Capture 0: 'b' at position 1
' Capture 1: 'b' at position 4
' Capture 2: 'b' at position 7

L'exemple suivant utilise l'expression régulière (Abc)+ pour trouver une ou plusieurs occurrences consécutives
de la chaîne « Abc » dans la chaîne « XYZAbcAbcAbcXYZAbcAb ». L'exemple montre comment utiliser la propriété
Group.Captures pour retourner plusieurs groupes de sous-chaînes capturées.
int counter;
Match m;
CaptureCollection cc;
GroupCollection gc;

// Look for groupings of "Abc".


var r = new Regex("(Abc)+");
// Define the string to search.
m = r.Match("XYZAbcAbcAbcXYZAbcAb");
gc = m.Groups;

// Display the number of groups.


Console.WriteLine("Captured groups = " + gc.Count.ToString());

// Loop through each group.


for (int i = 0; i < gc.Count; i++)
{
cc = gc[i].Captures;
counter = cc.Count;

// Display the number of captures in this group.


Console.WriteLine("Captures count = " + counter.ToString());

// Loop through each capture in the group.


for (int ii = 0; ii < counter; ii++)
{
// Display the capture and its position.
Console.WriteLine(cc[ii] + " Starts at character " +
cc[ii].Index);
}
}
// The example displays the following output:
// Captured groups = 2
// Captures count = 1
// AbcAbcAbc Starts at character 3
// Captures count = 3
// Abc Starts at character 3
// Abc Starts at character 6
// Abc Starts at character 9
Dim counter As Integer
Dim m As Match
Dim cc As CaptureCollection
Dim gc As GroupCollection

' Look for groupings of "Abc".


Dim r As New Regex("(Abc)+")
' Define the string to search.
m = r.Match("XYZAbcAbcAbcXYZAbcAb")
gc = m.Groups

' Display the number of groups.


Console.WriteLine("Captured groups = " & gc.Count.ToString())

' Loop through each group.


Dim i, ii As Integer
For i = 0 To gc.Count - 1
cc = gc(i).Captures
counter = cc.Count

' Display the number of captures in this group.


Console.WriteLine("Captures count = " & counter.ToString())

' Loop through each capture in the group.


For ii = 0 To counter - 1
' Display the capture and its position.
Console.WriteLine(cc(ii).ToString() _
& " Starts at character " & cc(ii).Index.ToString())
Next ii
Next i
' The example displays the following output:
' Captured groups = 2
' Captures count = 1
' AbcAbcAbc Starts at character 3
' Captures count = 3
' Abc Starts at character 3
' Abc Starts at character 6
' Abc Starts at character 9

Retour au début

Capture individuelle
La classe Capture contient le résultat d'une capture de sous-expression unique. La propriété Capture.Value
contient le texte mis en correspondance, tandis que la propriété Capture.Index indique la position, en base zéro,
dans la chaîne d'entrée à laquelle commence la sous-chaîne mise en correspondance.
L'exemple suivant analyse une chaîne d'entrée pour récupérer la température de certaines villes. Une virgule (« , »)
sépare chaque ville de sa température, tandis qu'un point-virgule (« ; ») sépare les données de chaque ville. La
chaîne d'entrée entière représente une correspondance unique. Dans le modèle d'expression régulière
((\w+(\s\w+)*),(\d+);)+ , qui permet d'analyser la chaîne, le nom de la ville est affecté au deuxième groupe de
capture, tandis que la température est attribuée au quatrième groupe de capture.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "Miami,78;Chicago,62;New York,67;San Francisco,59;Seattle,58;";
string pattern = @"((\w+(\s\w+)*),(\d+);)+";
Match match = Regex.Match(input, pattern);
if (match.Success)
{
Console.WriteLine("Current temperatures:");
for (int ctr = 0; ctr < match.Groups[2].Captures.Count; ctr++)
Console.WriteLine("{0,-20} {1,3}", match.Groups[2].Captures[ctr].Value,
match.Groups[4].Captures[ctr].Value);
}
}
}
// The example displays the following output:
// Current temperatures:
// Miami 78
// Chicago 62
// New York 67
// San Francisco 59
// Seattle 58

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "Miami,78;Chicago,62;New York,67;San Francisco,59;Seattle,58;"
Dim pattern As String = "((\w+(\s\w+)*),(\d+);)+"
Dim match As Match = Regex.Match(input, pattern)
If match.Success Then
Console.WriteLine("Current temperatures:")
For ctr As Integer = 0 To match.Groups(2).Captures.Count - 1
Console.WriteLine("{0,-20} {1,3}", match.Groups(2).Captures(ctr).Value, _
match.Groups(4).Captures(ctr).Value)
Next
End If
End Sub
End Module
' The example displays the following output:
' Current temperatures:
' Miami 78
' Chicago 62
' New York 67
' San Francisco 59
' Seattle 58

L'expression régulière est définie comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\w+ Mettre en correspondance un ou plusieurs caractères


alphabétiques.
M O DÈL E DESC RIP T IO N

(\s\w+)* Mettre en correspondance zéro occurrence, ou plus, d'un


espace blanc suivi d'un ou plusieurs caractères alphabétiques.
Ce modèle met en correspondance les noms de ville
composés de plusieurs mots. Il s'agit du troisième groupe de
capture.

(\w+(\s\w+)*) Mettre en correspondance un ou plusieurs caractères


alphabétiques suivis de zéro occurrence, ou plus, d'un espace
blanc et d'un ou plusieurs caractères alphabétiques. Il s'agit
du deuxième groupe de capture.

, Mettre en correspondance une virgule.

(\d+) Mettre en correspondance un ou plusieurs chiffres. Il s'agit du


quatrième groupe de capture.

; Mettre en correspondance un point-virgule.

((\w+(\s\w+)*),(\d+);)+ Mettre en correspondance le modèle représentant un mot


suivi d’un nombre quelconque de mots supplémentaires,
d’une virgule, d’un ou plusieurs chiffres et d’un point-virgule,
une ou plusieurs fois. Il s'agit du premier groupe de capture.

Voir aussi
System.Text.RegularExpressions
Expressions régulières .NET
Langage des expressions régulières - Aide-mémoire
Comportement détaillé des expressions régulières
18/07/2020 • 30 minutes to read • Edit Online

Le moteur d’expression régulière du .NET Framework est un analyseur d’expression régulière rétroactive qui
incorpore un moteur NFA (Nondeterministic Finite Automaton) tel que celui utilisé par Perl, Python, Emacs et Tcl.
Cette particularité le distingue des moteurs d’expression régulière pure DFA (Deterministic Finite Automaton) plus
rapides, mais plus limités, comme ceux d’awk, egrep ou lex. Elle le distingue également des moteurs NFA POSIX
standardisés, mais plus lents. La section suivante décrit les trois types de moteurs d’expression régulière et
explique pourquoi les expressions régulières dans le .NET Framework sont implémentées à l’aide d’un moteur NFA
classique.

Avantages du moteur NFA


Quand les moteurs DFA exécutent des critères spéciaux, leur ordre de traitement est piloté par la chaîne d’entrée.
Les moteurs commencent au début de la chaîne d’entrée et continuent de manière séquentielle pour déterminer si
le caractère suivant correspond au modèle d’expression régulière. Ils peuvent garantir une correspondance avec la
chaîne la plus longue possible. Étant donné qu’ils ne testent jamais le même caractère deux fois, les moteurs DFA
ne prennent pas en charge la rétroaction. Toutefois, étant donné qu’un moteur DFA contient uniquement un état
fini, il ne peut pas rechercher un modèle avec des références arrière, et comme il ne construit pas d’expansion
explicite, il ne peut pas capturer de sous-expressions.
Contrairement aux moteurs DFA, quand les moteurs NFA classiques exécutent des critères spéciaux, leur ordre de
traitement est piloté par le modèle d’expression régulière. Lorsqu’il traite un élément de langage particulier, le
moteur utilise une correspondance gourmande ; autrement dit, il cherche la chaîne d’entrée la plus longue
possible. Mais il enregistre également son état après avoir trouvé la correspondance correcte d’une sous-
expression. Si une correspondance finit par échouer, le moteur peut revenir à un état enregistré pour tenter
d’autres correspondances. Ce processus consistant à abandonner une correspondance réussie de sous-expression
pour que les éléments de langage ultérieurs dans l’expression régulière puissent également correspondre est
appelé rétroaction. Les moteurs NFA utilisent la rétroaction pour tester toutes les expansions possibles d’une
expression régulière dans un ordre spécifique et accepter la première correspondance. Comme un moteur NFA
classique construit une expansion spécifique de l’expression régulière pour obtenir une correspondance correcte, il
peut capturer des correspondances de sous-expressions et des références arrière correspondantes. Toutefois,
comme un moteur NFA classique utilise la rétroaction, il peut visiter le même état plusieurs fois s’il y parvient par
différents chemins. Par conséquent, il peut s’exécuter lentement de façon exponentielle dans le pire des cas.
Comme un moteur NFA classique accepte la première correspondance trouvée, il peut également en négliger
d’autres (éventuellement plus longues).
Les moteurs NFA POSIX sont comme les moteurs NFA classiques, sauf qu’ils poursuivent la rétroaction jusqu’à
garantir qu’ils ont trouvé la correspondance la plus longue possible. Ainsi, un moteur NFA POSIX est plus lent
qu’un moteur NFA classique. Quand vous utilisez un moteur NFA POSIX, vous ne pouvez pas favoriser une
correspondance plus courte au détriment d’une plus longue en modifiant l’ordre de la recherche rétroactive.
Les moteurs NFA classiques ont la préférence des programmeurs, car ils offrent un meilleur contrôle sur la mise
en correspondance de chaînes que les moteurs DFA ou NFA POSIX. Même si, dans le pire des cas, ils peuvent
s’exécuter lentement, vous pouvez les amener à trouver des correspondances sur des durées linéaires ou
polynomiales à l’aide de modèles qui réduisent les ambiguïtés et limitent la rétroaction. En d’autres termes, bien
que les moteurs NFA offrent des performances optimales en matière de puissance et de flexibilité, dans la plupart
des cas, ils offrent des performances acceptables si une expression régulière est bien écrite et évite les cas dans
lesquels la rétroaction dégrade les performances de façon exponentielle.
NOTE
Pour plus d’informations sur la baisse des performances due à la rétroaction excessive et sur les moyens de créer une
expression régulière pour y remédier, consultez Rétroaction.

Fonctionnalités du moteur de .NET Framework


Pour tirer parti des avantages d’un moteur NFA classique, le moteur d’expression régulière du .NET Framework
inclut un ensemble complet de constructions pour permettre aux programmeurs de diriger le moteur de
rétroaction. Ces constructions peuvent être utilisées pour rechercher des correspondances plus rapidement ou
pour favoriser des expansions spécifiques par rapport à d’autres.
Les autres fonctionnalités du moteur d’expression régulière du .NET Framework sont les suivantes :
Quantificateurs paresseux : ?? , *? , +? , { n , m }? . Ces constructions indiquent au moteur de
rétroaction de rechercher d’abord le nombre minimal de répétitions. Par opposition, les quantificateurs
gourmands ordinaires essaient de trouver d’abord le nombre maximal de répétitions. L’exemple suivant
illustre la différence entre les deux. Une expression régulière correspond à une phrase se terminant par un
nombre qu’un groupe de capture est destiné à extraire. L’expression régulière .+(\d+)\. inclut le
quantificateur gourmand .+ , qui amène le moteur d’expression régulière à capturer uniquement le dernier
chiffre du nombre. Par opposition, l’expression régulière .+?(\d+)\. inclut le quantificateur paresseux .+? ,
qui amène le moteur d’expression régulière à capturer le nombre entier.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string greedyPattern = @".+(\d+)\.";
string lazyPattern = @".+?(\d+)\.";
string input = "This sentence ends with the number 107325.";
Match match;

// Match using greedy quantifier .+.


match = Regex.Match(input, greedyPattern);
if (match.Success)
Console.WriteLine("Number at end of sentence (greedy): {0}",
match.Groups[1].Value);
else
Console.WriteLine("{0} finds no match.", greedyPattern);

// Match using lazy quantifier .+?.


match = Regex.Match(input, lazyPattern);
if (match.Success)
Console.WriteLine("Number at end of sentence (lazy): {0}",
match.Groups[1].Value);
else
Console.WriteLine("{0} finds no match.", lazyPattern);
}
}
// The example displays the following output:
// Number at end of sentence (greedy): 5
// Number at end of sentence (lazy): 107325
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim greedyPattern As String = ".+(\d+)\."
Dim lazyPattern As String = ".+?(\d+)\."
Dim input As String = "This sentence ends with the number 107325."
Dim match As Match

' Match using greedy quantifier .+.


match = Regex.Match(input, greedyPattern)
If match.Success Then
Console.WriteLine("Number at end of sentence (greedy): {0}",
match.Groups(1).Value)
Else
Console.WriteLine("{0} finds no match.", greedyPattern)
End If

' Match using lazy quantifier .+?.


match = Regex.Match(input, lazyPattern)
If match.Success Then
Console.WriteLine("Number at end of sentence (lazy): {0}",
match.Groups(1).Value)
Else
Console.WriteLine("{0} finds no match.", lazyPattern)
End If
End Sub
End Module
' The example displays the following output:
' Number at end of sentence (greedy): 5
' Number at end of sentence (lazy): 107325

Les versions gourmande et paresseuse de cette expression régulière sont définies comme indiqué dans le
tableau suivant :

M O DÈL E DESC RIP T IO N

.+ (quantificateur gourmand) Mettre en correspondance au moins une occurrence de


n’importe quel caractère. Le moteur d’expression régulière
recherche alors la chaîne entière, puis effectue une
rétroaction si nécessaire pour trouver le reste du modèle.

.+? (quantificateur paresseux) Mettre en correspondance au moins une occurrence de


n’importe quel caractère, mais une quantité la plus petite
possible.

(\d+) Mettre en correspondance au moins un caractère


numérique et l’assigner au premier groupe de capture.

\. Mettre en correspondance un point.

Pour plus d'informations sur les quantificateurs paresseux, voir Quantificateurs.


Préanalyse positive : sous- (?= expression ) . Cette fonctionnalité permet au moteur de rétroaction de
revenir au même endroit dans le texte après avoir mis en correspondance une sous-expression. Elle s’avère
utile pour effectuer une recherche dans le texte en vérifiant plusieurs modèles qui démarrent à la même
position. Elle permet également au moteur de vérifier qu’une sous-chaîne existe à la fin de la
correspondance sans inclure cette sous-chaîne dans le texte correspondant. L’exemple suivant utilise la
préanalyse positive pour extraire les mots d’une phrase qui ne sont pas suivis de symboles de ponctuation.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b[A-Z]+\b(?=\P{P})";
string input = "If so, what comes next?";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// If
// what
// comes

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b[A-Z]+\b(?=\P{P})"
Dim input As String = "If so, what comes next?"
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' If
' what
' comes

L'expression régulière \b[A-Z]+\b(?=\P{P}) est définie comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

[A-Z]+ Mettre en correspondance un caractère alphabétique une


ou plusieurs fois. Cette comparaison ne respecte pas la
casse car la méthode Regex.Matches est appelée avec
l'option RegexOptions.IgnoreCase.

\b Terminer la correspondance à la limite d'un mot.

(?=\P{P}) Préanalyser pour déterminer si le caractère suivant est un


symbole de ponctuation. Dans la négative, la
correspondance réussit.

Pour plus d’informations sur les assertions de préanalyse positive, consultez Constructions de
regroupement.
Préanalyse négative : sous- (?! expression ) . Cette fonctionnalité ajoute la possibilité de mettre en
correspondance une expression uniquement si une sous-expression ne correspond pas. Cela est puissant
pour nettoyer une recherche, car il est souvent plus simple de fournir une expression pour un cas à éliminer
qu’une expression pour les cas qui doivent être inclus. Par exemple, il est difficile d’écrire une expression
pour les mots qui ne commencent pas par « non ». L’exemple suivant utilise la préanalyse négative pour les
exclure.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = @"\b(?!non)\w+\b";
string input = "Nonsense is not always non-functional.";
foreach (Match match in Regex.Matches(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine(match.Value);
}
}
// The example displays the following output:
// is
// not
// always
// functional

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "\b(?!non)\w+\b"
Dim input As String = "Nonsense is not always non-functional."
For Each match As Match In Regex.Matches(input, pattern, RegexOptions.IgnoreCase)
Console.WriteLine(match.Value)
Next
End Sub
End Module
' The example displays the following output:
' is
' not
' always
' functional

Le modèle d'expression régulière \b(?!non)\w+\b est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

(?!non) Préanalyser pour garantir que la chaîne actuelle ne


commence pas par « non ». Si c’est le cas, la
correspondance échoue.

(\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques.

\b Terminer la correspondance à la limite d'un mot.

Pour plus d’informations sur les assertions de préanalyse négative, consultez Constructions de
regroupement.
Évaluation conditionnelle : (?( expression ) Oui | non ) et (?( nom ) Oui | non ) , où
expression est une sous-expression à faire correspondre, Name est le nom d’un groupe de capture, Oui est
la chaîne à faire correspondre si l' expression est mise en correspondance ou Name est un groupe capturé
valide, non vide, et non est la sous-expression à faire correspondre si l' expression n’est pas mise en
correspondance ou que le nom n’est pas un groupe capturé valide non vide. Grâce à cette fonctionnalité, le
moteur peut rechercher à l’aide de plusieurs autres modèles, selon le résultat d’une correspondance de
sous-expression précédente ou le résultat d’une assertion de largeur nulle. Cette fonctionnalité permet une
forme plus puissante de référence arrière qui permet, par exemple, de rechercher une sous-expression en
fonction d’une sous-expression précédente trouvée. L’expression régulière utilisée dans l’exemple suivant
trouve les paragraphes destinés à une utilisation à la fois interne et publique. Les paragraphes destinés à un
usage interne uniquement commencent par une balise <PRIVATE> . Le modèle d’expression régulière
^(?<Pvt>\<PRIVATE\>\s)?(?(Pvt)((\w+\p{P}?\s)+)|((\w+\p{P}?\s)+))\r?$ utilise une évaluation conditionnelle
pour assigner le contenu des paragraphes destinés à une utilisation publique et interne à des groupes de
capture distincts. Ces paragraphes peuvent ensuite être gérés différemment.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "<PRIVATE> This is not for public consumption." + Environment.NewLine +
"But this is for public consumption." + Environment.NewLine +
"<PRIVATE> Again, this is confidential.\n";
string pattern = @"^(?<Pvt>\<PRIVATE\>\s)?(?(Pvt)((\w+\p{P}?\s)+)|((\w+\p{P}?\s)+))\r?$";
string publicDocument = null, privateDocument = null;

foreach (Match match in Regex.Matches(input, pattern, RegexOptions.Multiline))


{
if (match.Groups[1].Success) {
privateDocument += match.Groups[1].Value + "\n";
}
else {
publicDocument += match.Groups[3].Value + "\n";
privateDocument += match.Groups[3].Value + "\n";
}
}

Console.WriteLine("Private Document:");
Console.WriteLine(privateDocument);
Console.WriteLine("Public Document:");
Console.WriteLine(publicDocument);
}
}
// The example displays the following output:
// Private Document:
// This is not for public consumption.
// But this is for public consumption.
// Again, this is confidential.
//
// Public Document:
// But this is for public consumption.
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "<PRIVATE> This is not for public consumption." + vbCrLf + _
"But this is for public consumption." + vbCrLf + _
"<PRIVATE> Again, this is confidential." + vbCrLf
Dim pattern As String = "^(?<Pvt>\<PRIVATE\>\s)?(?(Pvt)((\w+\p{P}?\s)+)|((\w+\p{P}?\s)+))\r?$"
Dim publicDocument As String = Nothing
Dim privateDocument As String = Nothing

For Each match As Match In Regex.Matches(input, pattern, RegexOptions.Multiline)


If match.Groups(1).Success Then
privateDocument += match.Groups(1).Value + vbCrLf
Else
publicDocument += match.Groups(3).Value + vbCrLf
privateDocument += match.Groups(3).Value + vbCrLf
End If
Next

Console.WriteLine("Private Document:")
Console.WriteLine(privateDocument)
Console.WriteLine("Public Document:")
Console.WriteLine(publicDocument)
End Sub
End Module
' The example displays the following output:
' Private Document:
' This is not for public consumption.
' But this is for public consumption.
' Again, this is confidential.
'
' Public Document:
' But this is for public consumption.

Le modèle d’expression régulière est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

^ Commencer la correspondance au début d’une ligne.

(?<Pvt>\<PRIVATE\>\s)? Mettre en correspondance zéro ou une occurrence de la


chaîne <PRIVATE> suivie d’un espace blanc. Assigner la
correspondance à un groupe de capture nommé Pvt .

(?(Pvt)((\w+\p{P}?\s)+) Si le groupe de capture Pvt existe, mettre en


correspondance une ou plusieurs occurrences d’un ou
plusieurs caractères de mot suivis de zéro ou un
séparateur de ponctuation suivi d’un espace blanc.
Assigner la sous-chaîne au premier groupe de capture.

|((\w+\p{P}?\s)+)) Si le groupe de capture Pvt n’existe pas, mettre en


correspondance une ou plusieurs occurrences d’un ou
plusieurs caractères de mot suivis de zéro ou un
séparateur de ponctuation suivi d’un espace blanc.
Assigner la sous-chaîne au troisième groupe de capture.

\r?$ Mettre en correspondance la fin d’une ligne ou la fin de la


chaîne.

Pour plus d’informations sur l’évaluation conditionnelle, consultez Constructions d’alternative.


Définitions de groupe d’équilibrage : (?< nom1 - nom2 sous- > expression ) . Cette fonctionnalité
permet au moteur d’expression régulière d’effectuer un suivi des constructions imbriquées telles que les
parenthèses ou les crochets ouvrants et fermants. Pour obtenir un exemple, consultez Constructions de
regroupement.
Atomic Groups : sous- (?> expression ) . Cette fonctionnalité permet au moteur de rétroaction de
garantir qu’une sous-expression correspond uniquement à la première correspondance trouvée pour cette
sous-expression, comme si l’expression s’exécutait indépendamment de l’expression qui la contient. Si vous
n’utilisez pas cette construction, les recherches rétroactives à partir de la plus grande expression peuvent
modifier le comportement d’une sous-expression. Par exemple, l’expression régulière (a+)\w met en
correspondance un ou plusieurs caractères « a », ainsi qu’un caractère de mot qui suit la séquence de
caractères « a », et assigne la séquence de caractères « a » au premier groupe de capture. Toutefois, si le
dernier caractère de la chaîne d’entrée est également un « a », il est mis en correspondance par l' \w
élément de langage et n’est pas inclus dans le groupe capturé.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "aaaaa", "aaaaab" };
string backtrackingPattern = @"(a+)\w";
Match match;

foreach (string input in inputs) {


Console.WriteLine("Input: {0}", input);
match = Regex.Match(input, backtrackingPattern);
Console.WriteLine(" Pattern: {0}", backtrackingPattern);
if (match.Success) {
Console.WriteLine(" Match: {0}", match.Value);
Console.WriteLine(" Group 1: {0}", match.Groups[1].Value);
}
else {
Console.WriteLine(" Match failed.");
}
}
Console.WriteLine();
}
}
// The example displays the following output:
// Input: aaaaa
// Pattern: (a+)\w
// Match: aaaaa
// Group 1: aaaa
// Input: aaaaab
// Pattern: (a+)\w
// Match: aaaaab
// Group 1: aaaaa
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"aaaaa", "aaaaab"}
Dim backtrackingPattern As String = "(a+)\w"
Dim match As Match

For Each input As String In inputs


Console.WriteLine("Input: {0}", input)
match = Regex.Match(input, backtrackingPattern)
Console.WriteLine(" Pattern: {0}", backtrackingPattern)
If match.Success Then
Console.WriteLine(" Match: {0}", match.Value)
Console.WriteLine(" Group 1: {0}", match.Groups(1).Value)
Else
Console.WriteLine(" Match failed.")
End If
Next
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' Input: aaaaa
' Pattern: (a+)\w
' Match: aaaaa
' Group 1: aaaa
' Input: aaaaab
' Pattern: (a+)\w
' Match: aaaaab
' Group 1: aaaaa

L’expression régulière ((?>a+))\w empêche ce comportement. Étant donné que tous les caractères « a »
consécutifs sont trouvés sans rétroaction, le premier groupe de capture inclut tous les caractères « a »
consécutifs. Si les caractères « a » ne sont pas suivis d’au moins un caractère autre que « a », la
correspondance échoue.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "aaaaa", "aaaaab" };
string nonbacktrackingPattern = @"((?>a+))\w";
Match match;

foreach (string input in inputs) {


Console.WriteLine("Input: {0}", input);
match = Regex.Match(input, nonbacktrackingPattern);
Console.WriteLine(" Pattern: {0}", nonbacktrackingPattern);
if (match.Success) {
Console.WriteLine(" Match: {0}", match.Value);
Console.WriteLine(" Group 1: {0}", match.Groups[1].Value);
}
else {
Console.WriteLine(" Match failed.");
}
}
Console.WriteLine();
}
}
// The example displays the following output:
// Input: aaaaa
// Pattern: ((?>a+))\w
// Match failed.
// Input: aaaaab
// Pattern: ((?>a+))\w
// Match: aaaaab
// Group 1: aaaaa

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"aaaaa", "aaaaab"}
Dim nonbacktrackingPattern As String = "((?>a+))\w"
Dim match As Match

For Each input As String In inputs


Console.WriteLine("Input: {0}", input)
match = Regex.Match(input, nonbacktrackingPattern)
Console.WriteLine(" Pattern: {0}", nonbacktrackingPattern)
If match.Success Then
Console.WriteLine(" Match: {0}", match.Value)
Console.WriteLine(" Group 1: {0}", match.Groups(1).Value)
Else
Console.WriteLine(" Match failed.")
End If
Next
Console.WriteLine()
End Sub
End Module
' The example displays the following output:
' Input: aaaaa
' Pattern: ((?>a+))\w
' Match failed.
' Input: aaaaab
' Pattern: ((?>a+))\w
' Match: aaaaab
' Group 1: aaaaa
Pour plus d’informations sur les groupes atomiques, consultez constructions de regroupement.
La mise en correspondance de droite à gauche est spécifiée en fournissant l’option
RegexOptions.RightToLeft à une méthode de mise en correspondance de constructeur de classe ou
d’instance statique Regex. Cette fonctionnalité s’avère utile lors de la recherche de droite à gauche au lieu
de gauche à droite, ou dans les cas où il est plus efficace de commencer une correspondance dans la partie
droite du modèle plutôt que la partie gauche. Comme l’illustre l’exemple suivant, l’utilisation de la mise en
correspondance de droite à gauche peut modifier le comportement des quantificateurs gourmands.
L’exemple effectue deux recherches d’une phrase qui se termine par un nombre. La recherche de gauche à
droite qui utilise le quantificateur gourmand + trouve l’un des six chiffres dans la phrase, tandis que la
recherche de droite à gauche trouve les six chiffres. Pour obtenir une description du modèle d’expression
régulière, consultez l’exemple qui illustre les quantificateurs paresseux plus haut dans cette section.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string greedyPattern = @".+(\d+)\.";
string input = "This sentence ends with the number 107325.";
Match match;

// Match from left-to-right using lazy quantifier .+?.


match = Regex.Match(input, greedyPattern);
if (match.Success)
Console.WriteLine("Number at end of sentence (left-to-right): {0}",
match.Groups[1].Value);
else
Console.WriteLine("{0} finds no match.", greedyPattern);

// Match from right-to-left using greedy quantifier .+.


match = Regex.Match(input, greedyPattern, RegexOptions.RightToLeft);
if (match.Success)
Console.WriteLine("Number at end of sentence (right-to-left): {0}",
match.Groups[1].Value);
else
Console.WriteLine("{0} finds no match.", greedyPattern);
}
}
// The example displays the following output:
// Number at end of sentence (left-to-right): 5
// Number at end of sentence (right-to-left): 107325
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim greedyPattern As String = ".+(\d+)\."
Dim input As String = "This sentence ends with the number 107325."
Dim match As Match

' Match from left-to-right using lazy quantifier .+?.


match = Regex.Match(input, greedyPattern)
If match.Success Then
Console.WriteLine("Number at end of sentence (left-to-right): {0}",
match.Groups(1).Value)
Else
Console.WriteLine("{0} finds no match.", greedyPattern)
End If

' Match from right-to-left using greedy quantifier .+.


match = Regex.Match(input, greedyPattern, RegexOptions.RightToLeft)
If match.Success Then
Console.WriteLine("Number at end of sentence (right-to-left): {0}",
match.Groups(1).Value)
Else
Console.WriteLine("{0} finds no match.", greedyPattern)
End If
End Sub
End Module
' The example displays the following output:
' Number at end of sentence (left-to-right): 5
' Number at end of sentence (right-to-left): 107325

Pour plus d’informations sur la mise en correspondance de droite à gauche, consultez Options des
expressions régulières.
Postanalyse positive et négative : sous- (?<= expression ) pour une postanalyse positive et sous- (?<!
expression ) pour une postanalyse négative. Cette fonctionnalité est semblable à la préanalyse décrite
précédemment dans cette rubrique. Comme le moteur d’expression régulière autorise une mise en
correspondance complète de droite à gauche, les expressions régulières autorisent les postanalyses
illimitées. La postanalyse positive et négative peut également servir à éviter d’imbriquer des quantificateurs
lorsque la sous-expression imbriquée est un sur-ensemble d’une expression externe. Les expressions
régulières comportant ces quantificateurs imbriqués offrent souvent des performances médiocres.
L’exemple suivant vérifie qu’une chaîne commence et se termine par un caractère alphanumérique et que
tout autre caractère contenu dans la chaîne fait partie d’un sur-ensemble plus grand. Il forme une partie de
l’expression régulière utilisée pour valider des adresses e-mail. Pour plus d’informations, consultez Guide
pratique : vérifier que des chaînes sont dans un format d’adresse e-mail valide.
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string[] inputs = { "jack.sprat", "dog#", "dog#1", "me.myself",
"me.myself!" };
string pattern = @"^[A-Z0-9]([-!#$%&'.*+/=?^`{}|~\w])*(?<=[A-Z0-9])$";
foreach (string input in inputs) {
if (Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase))
Console.WriteLine("{0}: Valid", input);
else
Console.WriteLine("{0}: Invalid", input);
}
}
}
// The example displays the following output:
// jack.sprat: Valid
// dog#: Invalid
// dog#1: Valid
// me.myself: Valid
// me.myself!: Invalid

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim inputs() As String = {"jack.sprat", "dog#", "dog#1", "me.myself",
"me.myself!"}
Dim pattern As String = "^[A-Z0-9]([-!#$%&'.*+/=?^`{}|~\w])*(?<=[A-Z0-9])$"
For Each input As String In inputs
If Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase) Then
Console.WriteLine("{0}: Valid", input)
Else
Console.WriteLine("{0}: Invalid", input)
End If
Next
End Sub
End Module
' The example displays the following output:
' jack.sprat: Valid
' dog#: Invalid
' dog#1: Valid
' me.myself: Valid
' me.myself!: Invalid

L'expression régulière ^[A-Z0-9]([-!#$%&'.*+/=?^`{}|~\w])*(?<=[A-Z0-9])$ est définie comme indiqué dans


le tableau suivant.

M O DÈL E DESC RIP T IO N

^ Commencer la correspondance au début de la chaîne.

[A-Z0-9] Mettre en correspondance n’importe quel caractère


numérique ou alphanumérique. (La comparaison respecte
la casse.)
M O DÈL E DESC RIP T IO N

([-!#$%&'.*+/=?^`{}|~\w])* Correspond à zéro, une ou plusieurs occurrences d’un


caractère alphabétique, ou à l’un des caractères suivants :-
, !, #, $,%, &, ',., * , +,/, =, ?, ^, `, {,}, | ou ~.

(?<=[A-Z0-9]) Postanalyser jusqu’au caractère précédent, qui doit être


numérique ou alphanumérique. (La comparaison respecte
la casse.)

$ Termine la correspondance à la fin de la chaîne.

Pour plus d’informations sur la postanalyse positive et négative, consultez Constructions de regroupement.

Articles connexes
IN T IT UL É DESC RIP T IO N

Rétroaction Fournit des informations sur la manière dont la rétroaction


d’expression régulière se ramifie pour trouver d’autres
correspondances.

Compilation et réutilisation Fournit des informations sur la compilation et la réutilisation


des expressions régulières pour augmenter les performances.

Cohérence de thread Fournit des informations sur la sécurité des threads


d’expression régulière et explique quand vous devez
synchroniser l’accès aux objets d’expression régulière.

Expressions régulières du .NET Framework Fournit une vue d’ensemble de l’aspect du langage de
programmation des expressions régulières.

Modèle objet d'expression régulière Fournit des informations et des exemples de code illustrant
l’utilisation des classes d’expression régulière.

Langage des expressions régulières - Aide-mémoire Fournit des informations sur le jeu de caractères, d’opérateurs
et de constructions permettant de définir des expressions
régulières.

Référence
System.Text.RegularExpressions
Rétroaction dans les expressions régulières
18/07/2020 • 37 minutes to read • Edit Online

La rétroaction se produit quand un modèle d’expression régulière contient des quantificateurs ou des
constructions d’alternative facultatifs, et que le moteur d’expression régulière retourne à un état enregistré
précédent pour poursuivre la recherche d’une correspondance. La rétroaction est essentielle à la puissance des
expressions régulières ; elle permet aux expressions d'être puissantes et flexibles et de correspondre à des
modèles très complexes. Cependant, cette puissance a un coût. La rétroaction est souvent le facteur le plus
important qui affecte les performances du moteur des expressions régulières. Heureusement, le développeur
contrôle le comportement du moteur des expressions régulières et comment il utilise la rétroaction. Cette
rubrique explique comment la rétroaction fonctionne et comment elle peut être activée.

NOTE
En général, un automate fini non déterministe comme le moteur d’expression régulière de .NET fait peser la responsabilité
de la conception d’expressions régulières efficaces et rapides sur le développeur.

Comparaison linéaire sans rétroaction


Si un modèle d'expression régulière ne possède pas de quantificateur facultatif ou de construction d'alternative,
le moteur des expressions régulières s'exécute en temps linéaire. Autrement dit, une fois que le moteur des
expressions régulières correspond au premier élément de langage dans le modèle avec du texte dans la chaîne
d'entrée, il tente de correspondre à l'élément de langage suivant dans le modèle avec le caractère ou le groupe
suivant de caractères dans la chaîne d'entrée. Cela se poursuit jusqu'à ce que la correspondance réussisse ou
échoue. Dans les deux cas, le moteur des expressions régulières avance d'un caractère à la fois dans la chaîne
d'entrée.
L'exemple suivant en est l'illustration. L'expression régulière e{2}\w\b recherche deux occurrences de la lettre «
e » suivie de n'importe quel caractère de mot suivi d'une limite de mot.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "needing a reed";
string pattern = @"e{2}\w\b";
foreach (Match match in Regex.Matches(input, pattern))
Console.WriteLine("{0} found at position {1}",
match.Value, match.Index);
}
}
// The example displays the following output:
// eed found at position 11
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "needing a reed"
Dim pattern As String = "e{2}\w\b"
For Each match As Match In Regex.Matches(input, pattern)
Console.WriteLine("{0} found at position {1}", _
match.Value, match.Index)
Next
End Sub
End Module
' The example displays the following output:
' eed found at position 11

Bien que cette expression régulière inclue le quantificateur {2} , elle est évaluée de façon linéaire. Le moteur des
expressions régulières ne revient pas en arrière, car {2} n'est pas un quantificateur facultatif ; il spécifie un
nombre exact et pas un nombre variable de fois que la sous-expression précédente doit correspondre. Par
conséquent, le moteur des expressions régulières tente de correspondre au modèle d'expression régulière avec
la chaîne d'entrée comme indiqué dans le tableau suivant.

P O SIT IO N DA N S L E P O SIT IO N DA N S L A
O P ÉRAT IO N M O DÈL E C H A ÎN E RÉSULTAT S

1 e "besoin d'un roseau" (index Pas de correspondance.


0)

2 e "esoin d'un roseau" (index Correspondance possible.


1)

3 e{2} "soin d'un roseau" (index 2) Correspondance possible.

4 \w "oin d'un roseau" (index 3) Correspondance possible.

5 \b "in d'un roseau" (index 4) Échecs de correspondance


possibles.

6 e "soin d'un roseau" (index 2) Correspondance possible.

7 e{2} "oin d'un roseau" (index 3) Échecs de correspondance


possibles.

8 e "oin d'un roseau" (index 3) Échec de la correspondance.

9 e "in d'un roseau" (index 4) Pas de correspondance.

10 e "n d'un roseau" (index 5) Pas de correspondance.

11 e "d'un roseau" (index 6) Pas de correspondance.

12 e " un roseau" (index 7) Pas de correspondance.

13 e "un roseau" (index 8) Pas de correspondance.

14 e " roseau" (index 9) Pas de correspondance.


P O SIT IO N DA N S L E P O SIT IO N DA N S L A
O P ÉRAT IO N M O DÈL E C H A ÎN E RÉSULTAT S

15 e "roseau" (index 10) Pas de correspondance

16 e "seau" (index 11) Correspondance possible.

17 e{2} "au" (index 12) Correspondance possible.

18 \w "u" (index 13) Correspondance possible.

19 \b "" (index 14) Correspondance

Si un modèle d'expression régulière n'inclut aucun quantificateur facultatif ou construction d'alternative, le


nombre maximal de comparaisons requises pour correspondre au modèle d'expression régulière avec la chaîne
d'entrée équivaut à peu près au nombre de caractères dans la chaîne d'entrée. Dans ce cas, le moteur des
expressions régulières utilise 19 comparaisons pour identifier les correspondances possibles dans cette chaîne
de 13 caractères. En d'autres termes, le moteur des expressions régulières s'exécute en temps quasi linéaire s'il
ne contient pas de quantificateur facultatif ou de construction d'alternative.

Rétroaction avec des quantificateurs facultatifs ou des constructions


d'alternative
Lorsqu'une expression régulière inclut des quantificateurs facultatifs ou des constructions d'alternative,
l'évaluation de la chaîne d'entrée n'est plus linéaire. Les critères spéciaux avec un moteur NFA sont pilotés par
les éléments de langage dans l'expression régulière et non par des caractères de correspondance dans la chaîne
d'entrée. Par conséquent, le moteur des expressions régulières tente de correspondre complètement aux sous-
expressions facultatives ou alternatives. Lorsqu'il avance à l'élément de langage suivant dans la sous-expression
et que la correspondance échoue, le moteur des expressions régulières peut abandonner une partie de sa
correspondance trouvée et retourner à un état enregistré précédent afin de mettre en correspondance
l'expression régulière dans son ensemble avec la chaîne d'entrée. Ce processus de retour à un état enregistré
précédent pour rechercher une correspondance est appelé rétroaction.
Considérons, par exemple, le modèle d'expression régulière .*(es) , qui correspond aux caractères « es » et
tous caractères qui précèdent. Comme l'exemple suivant l'indique, si la chaîne d'entrée est « Des services
essentiels sont fournis par les expressions régulières. », le modèle correspond à la chaîne entière jusqu'à « es »
dans « expressions ».
using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "Essential services are provided by regular expressions.";
string pattern = ".*(es)";
Match m = Regex.Match(input, pattern, RegexOptions.IgnoreCase);
if (m.Success) {
Console.WriteLine("'{0}' found at position {1}",
m.Value, m.Index);
Console.WriteLine("'es' found at position {0}",
m.Groups[1].Index);
}
}
}
// 'Essential services are provided by regular expres' found at position 0
// 'es' found at position 47

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "Essential services are provided by regular expressions."
Dim pattern As String = ".*(es)"
Dim m As Match = Regex.Match(input, pattern, RegexOptions.IgnoreCase)
If m.Success Then
Console.WriteLine("'{0}' found at position {1}", _
m.Value, m.Index)
Console.WriteLine("'es' found at position {0}", _
m.Groups(1).Index)
End If
End Sub
End Module
' 'Essential services are provided by regular expres' found at position 0
' 'es' found at position 47

Pour cela, le moteur des expressions régulières utilise la rétroaction comme suit :
Il établit une correspondance entre .* (qui correspond à zéro, une ou plusieurs occurrences d'un
caractère) et la chaîne d'entrée entière.
Il tente de faire correspondre « e » dans le modèle d'expression régulière. Toutefois, il ne reste aucun
caractère disponible pour la correspondance dans la chaîne d'entrée.
Il revient en arrière à la dernière correspondance trouvée, « Des services essentiels sont fournis par les
expressions régulières » et tente de faire correspondre « e » avec le point à la fin de la phrase. La
correspondance échoue.
Il continue à revenir en arrière à la correspondance précédente caractère après caractère jusqu'à ce qu'à
ce que la sous-chaîne en correspondance provisoire soit « Des services essentiels sont fournis par le les
expr ». Il compare ensuite le « e » dans le modèle avec le second « e » dans « expressions » et trouve une
correspondance.
Il compare le « s » dans le modèle avec le « s » qui suit le « e » mis en correspondance (le premier « s »
dans « expressions »). La correspondance réussit.
Lorsque vous utilisez la rétroaction, la mise en correspondance du modèle d'expression régulière avec la chaîne
d'entrée, qui comporte 55 caractères, requiert 67 opérations de comparaison. En général, si un modèle
d'expression régulière a une seule construction d'alternative ou un seul quantificateur facultatif, le nombre
d'opérations de comparaison requises pour correspondre au modèle est supérieur au double du nombre de
caractères dans la chaîne d'entrée.

Rétroaction avec des quantificateurs facultatifs imbriqués


Le nombre d'opérations de comparaison requises pour correspondre à un modèle d'expression régulière peut
augmenter de façon exponentielle si le modèle se compose d'un grand nombre de constructions d'alternative,
s'il est constitué de constructions d'alternative imbriquées ou, le plus souvent, s'il inclut des quantificateurs
facultatifs imbriqués. Par exemple, le modèle d'expression régulière ^(a+)+$ est conçu pour une
correspondance avec une chaîne complète qui contient un ou plusieurs caractères « a ». L'exemple fournit deux
chaînes d'entrée de longueurs identiques, mais seule la première chaîne correspond au modèle. La classe
System.Diagnostics.Stopwatch est utilisée pour déterminer la durée de l'opération de correspondance.

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string pattern = "^(a+)+$";
string[] inputs = { "aaaaaa", "aaaaa!" };
Regex rgx = new Regex(pattern);
Stopwatch sw;

foreach (string input in inputs) {


sw = Stopwatch.StartNew();
Match match = rgx.Match(input);
sw.Stop();
if (match.Success)
Console.WriteLine("Matched {0} in {1}", match.Value, sw.Elapsed);
else
Console.WriteLine("No match found in {0}", sw.Elapsed);
}
}
}

Imports System.Diagnostics
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim pattern As String = "^(a+)+$"
Dim inputs() As String = {"aaaaaa", "aaaaa!"}
Dim rgx As New Regex(pattern)
Dim sw As Stopwatch

For Each input As String In inputs


sw = Stopwatch.StartNew()
Dim match As Match = rgx.Match(input)
sw.Stop()
If match.Success Then
Console.WriteLine("Matched {0} in {1}", match.Value, sw.Elapsed)
Else
Console.WriteLine("No match found in {0}", sw.Elapsed)
End If
Next
End Sub
End Module
Comme la sortie de l'exemple indique, il a fallu presque deux fois plus de temps au moteur des expressions
régulières pour constater qu'une chaîne d'entrée ne correspondait pas au modèle que pour identifier une chaîne
correspondante. Cela est dû au fait qu'une correspondance infructueuse représente toujours le pire des
scénarios. Le moteur des expressions régulières doit utiliser l'expression régulière pour suivre tous les chemins
d'accès possibles dans les données avant qu'il puisse conclure que la correspondance est infructueuse, et les
parenthèses imbriquées créent plusieurs chemins d'accès supplémentaires dans les données. Le moteur des
expressions régulières conclut que la deuxième chaîne ne correspondait pas au modèle en procédant comme
suit :
Il vérifie qu'il était au début de la chaîne, puis fait correspondre les cinq premiers caractères dans la
chaîne avec le modèle a+ . Il détermine ensuite qu'il n'y a aucun groupe supplémentaire de caractères « a
» dans la chaîne. Enfin, il effectue en test pour déterminer la fin de la chaîne. Étant donné qu'il reste un
caractère supplémentaire dans la chaîne, la correspondance échoue. Cette correspondance infructueuse
requiert 9 comparaisons. Le moteur des expressions régulières enregistre également des informations
d'état de ses correspondances de « a » (que nous appellerons correspondance 1), « aa » (correspondance
2), « aaa » (correspondance 3) et « aaaa » (correspondance 4).
Il revient à la correspondance 4 enregistrée précédemment. Il détermine qu'il y a un caractère « a »
supplémentaire à assigner à un groupe capturé supplémentaire. Enfin, il effectue en test pour déterminer
la fin de la chaîne. Étant donné qu'il reste un caractère supplémentaire dans la chaîne, la correspondance
échoue. Cette correspondance infructueuse requiert 4 comparaisons. Jusqu'à présent, un total de 13
comparaisons ont été effectuées.
Il revient à la correspondance 3 enregistrée précédemment. Il détermine qu'il y a deux caractères « a »
supplémentaires à assigner à un groupe capturé supplémentaire. Toutefois, le test de fin de chaîne
échoue. Il revient ensuite à la correspondance 3 et tente de faire correspondre les deux caractères « a »
supplémentaire dans deux groupes capturés supplémentaires. Le test de fin de chaîne échoue encore. Ces
correspondances infructueuses requièrent 12 comparaisons. Jusqu'à présent, un total de 25
comparaisons ont été effectuées.
La comparaison de la chaîne d'entrée avec l'expression régulière continue de cette façon jusqu'à ce que le
moteur des expressions régulières ait tenté toutes les combinaisons possibles des correspondances et conclue
qu'il n'existe aucune correspondance. En raison des quantificateurs imbriqués, cette comparaison est une
opération O (2n ) ou exponentielle, où n est le nombre de caractères dans la chaîne d’entrée. Cela signifie que
dans le pire des cas, une chaîne d'entrée de 30 caractères requiert environ 1 073 741 824 comparaisons et une
chaîne d'entrée de 40 caractères requiert environ 1 099 511 627 776 comparaisons. Si vous utilisez des chaînes
de longueurs identiques ou supérieures, l'exécution des méthodes d'expression régulières peut prendre très
longtemps lorsqu'elles traitent une entrée qui ne correspond pas au modèle d'expression régulière.

Contrôle de la rétroaction
La rétroaction vous permet de créer des expressions régulières puissantes et flexibles. Toutefois, comme la
section précédente l'indiquait, ces avantages peuvent s'accompagner de performances médiocres. Pour
empêcher la rétroaction excessive, vous devez définir un intervalle de délai d'attente lorsque vous instanciez un
objet Regex ou appelez une méthode de mise en correspondance statique d'expression régulière. Cette situation
est présentée dans la section suivante. En outre, .NET prend en charge trois éléments de langage d’expression
régulière qui limitent ou suppriment la rétroaction et qui prennent en charge des expressions régulières
complexes avec peu ou pas de dégradation des performances : les groupes atomiques, les assertions de
postanalyseet les assertions de préanalyse. Pour plus d’informations sur chaque élément de langage, consultez
Grouping Constructs.
Définir un intervalle de délai d'attente
À partir de .NET Framework 4.5, vous pouvez définir un intervalle de délai d’attente qui représente la durée
maximale pendant laquelle le moteur d’expression régulière recherche une correspondance avant d’abandonner
la tentative et de lever une exception RegexMatchTimeoutException. Vous spécifiez l'intervalle de délai d'attente
en donnant une valeur TimeSpan au constructeur Regex(String, RegexOptions, TimeSpan) pour les expressions
régulières d'instances. De plus, chaque méthode de mise en correspondance statique possède une surcharge
avec un paramètre TimeSpan qui vous permet de spécifier une valeur de délai d'attente. Par défaut, l'intervalle
de délai d'attente est défini sur Regex.InfiniteMatchTimeout et le moteur des expressions régulières n'expire pas.

IMPORTANT
Il est recommandé de toujours définir un intervalle de délai d'attente si votre expression régulière repose sur la
restauration.

Une exception RegexMatchTimeoutException indique que le moteur des expressions régulières n’a pas pu
trouver de correspondance dans l’intervalle de délai d’attente spécifié, mais n’indique pas pourquoi l’exception a
été levée. Cela peut provenir de la rétroaction excessive, mais il est possible que l'intervalle de délai d'attente
était trop faible étant donné la charge système au moment où l'exception a été levée. Lorsque vous gérez
l'exception, vous pouvez choisir d'abandonner d'autres correspondances avec la chaîne d'entrée ou augmenter
l'intervalle de délai d'attente et de retenter l'opération de mise en correspondance.
Par exemple, le code suivant appelle le constructeur Regex(String, RegexOptions, TimeSpan) pour instancier un
objet Regex avec un délai d'attente d'une seconde. Le modèle d'expression régulière (a+)+$ , qui correspond à
une ou plusieurs séquences d'un ou plusieurs caractères « a » à la fin d'une ligne, est soumis à une rétroaction
excessive. Si une RegexMatchTimeoutException est levée, l'exemple augmente la valeur du délai d'attente
jusqu'à un intervalle maximal de trois secondes. Après cela, il abandonne la tentative pour mettre en
correspondance le modèle.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Security;
using System.Text.RegularExpressions;
using System.Threading;

public class Example


{
const int MaxTimeoutInSeconds = 3;

public static void Main()


{
string pattern = @"(a+)+$"; // DO NOT REUSE THIS PATTERN.
Regex rgx = new Regex(pattern, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
Stopwatch sw = null;

string[] inputs= { "aa", "aaaa>",


"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaa>",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>" };

foreach (var inputValue in inputs) {


Console.WriteLine("Processing {0}", inputValue);
bool timedOut = false;
do {
try {
sw = Stopwatch.StartNew();
// Display the result.
if (rgx.IsMatch(inputValue)) {
sw.Stop();
Console.WriteLine(@"Valid: '{0}' ({1:ss\.fffffff} seconds)",
inputValue, sw.Elapsed);
}
else {
sw.Stop();
Console.WriteLine(@"'{0}' is not a valid string. ({1:ss\.fffff} seconds)",
inputValue, sw.Elapsed);
}
}
catch (RegexMatchTimeoutException e) {
sw.Stop();
// Display the elapsed time until the exception.
Console.WriteLine(@"Timeout with '{0}' after {1:ss\.fffff}",
inputValue, sw.Elapsed);
Thread.Sleep(1500); // Pause for 1.5 seconds.

// Increase the timeout interval and retry.


TimeSpan timeout = e.MatchTimeout.Add(TimeSpan.FromSeconds(1));
if (timeout.TotalSeconds > MaxTimeoutInSeconds) {
Console.WriteLine("Maximum timeout interval of {0} seconds exceeded.",
MaxTimeoutInSeconds);
timedOut = false;
}
else {
Console.WriteLine("Changing the timeout interval to {0}",
timeout);
rgx = new Regex(pattern, RegexOptions.IgnoreCase, timeout);
timedOut = true;
}
}
} while (timedOut);
Console.WriteLine();
}
}
}
// The example displays output like the following :
// Processing aa
// Valid: 'aa' (00.0000779 seconds)
//
// Processing aaaa>
// 'aaaa>' is not a valid string. (00.00005 seconds)
//
// Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
// Valid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' (00.0000043 seconds)
//
// Processing aaaaaaaaaaaaaaaaaaaaaa>
// Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 01.00469
// Changing the timeout interval to 00:00:02
// Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 02.01202
// Changing the timeout interval to 00:00:03
// Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 03.01043
// Maximum timeout interval of 3 seconds exceeded.
//
// Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>
// Timeout with 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>' after 03.01018
// Maximum timeout interval of 3 seconds exceeded.

Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Security
Imports System.Text.RegularExpressions
Imports System.Threading

Module Example
Const MaxTimeoutInSeconds As Integer = 3

Public Sub Main()


Dim pattern As String = "(a+)+$" ' DO NOT REUSE THIS PATTERN.
Dim rgx As New Regex(pattern, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1))
Dim sw As Stopwatch = Nothing

Dim inputs() As String = {"aa", "aaaa>",


"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"aaaaaaaaaaaaaaaaaaaaaa>",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>"}

For Each inputValue In inputs


Console.WriteLine("Processing {0}", inputValue)
Dim timedOut As Boolean = False
Do
Try
sw = Stopwatch.StartNew()
' Display the result.
If rgx.IsMatch(inputValue) Then
sw.Stop()
Console.WriteLine("Valid: '{0}' ({1:ss\.fffffff} seconds)",
inputValue, sw.Elapsed)
Else
sw.Stop()
Console.WriteLine("'{0}' is not a valid string. ({1:ss\.fffff} seconds)",
inputValue, sw.Elapsed)
End If
Catch e As RegexMatchTimeoutException
sw.Stop()
' Display the elapsed time until the exception.
Console.WriteLine("Timeout with '{0}' after {1:ss\.fffff}",
inputValue, sw.Elapsed)
Thread.Sleep(1500) ' Pause for 1.5 seconds.

' Increase the timeout interval and retry.


Dim timeout As TimeSpan = e.MatchTimeout.Add(TimeSpan.FromSeconds(1))
If timeout.TotalSeconds > MaxTimeoutInSeconds Then
Console.WriteLine("Maximum timeout interval of {0} seconds exceeded.",
MaxTimeoutInSeconds)
timedOut = False
Else
Console.WriteLine("Changing the timeout interval to {0}",
timeout)
rgx = New Regex(pattern, RegexOptions.IgnoreCase, timeout)
timedOut = True
End If
End Try
Loop While timedOut
Console.WriteLine()
Next
End Sub
End Module
' The example displays output like the following:
' Processing aa
' Valid: 'aa' (00.0000779 seconds)
'
' Processing aaaa>
' 'aaaa>' is not a valid string. (00.00005 seconds)
'
' Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
' Valid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' (00.0000043 seconds)
'
' Processing aaaaaaaaaaaaaaaaaaaaaa>
' Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 01.00469
' Changing the timeout interval to 00:00:02
' Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 02.01202
' Changing the timeout interval to 00:00:03
' Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 03.01043
' Maximum timeout interval of 3 seconds exceeded.
'
' Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>
' Timeout with 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>' after 03.01018
' Maximum timeout interval of 3 seconds exceeded.
Groupes atomiques
L’élément de langage sous- (?> expression ) supprime la rétroaction dans la sous-expression. Une fois qu’il a
été mis en correspondance, il n’accorde aucune partie de sa correspondance à la rétroaction suivante. Par
exemple, dans le modèle (?>\w*\d*)1 , si le 1 ne peut pas être mis en correspondance, le n' \d* abandonnera
aucune de ses correspondances, même si cela signifie qu’il autorisera la 1 correspondance avec. Les groupes
atomiques peuvent aider à éviter les problèmes de performances associés aux correspondances ayant échoué.
L'exemple suivant montre comment la suppression de la rétroaction améliore les performances lors de
l'utilisation de quantificateurs imbriqués. Il mesure le temps nécessaire pour que le moteur des expressions
régulières détermine qu'une chaîne d'entrée ne correspond pas à deux expressions régulières. La première
expression régulière utilise la rétroaction pour tenter de mettre en correspondance une chaîne qui contient une
ou plusieurs occurrences d'un ou plusieurs chiffres hexadécimaux, suivi d'un signe deux-points, suivi d'un ou
plusieurs chiffres hexadécimaux, suivi de deux signes deux-points. La seconde expression régulière est identique
à la première, à ceci près qu'elle désactive la rétroaction. Comme la sortie de l'exemple l'indique, l'amélioration
des performances due à la désactivation de la rétroaction est considérable.

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "b51:4:1DB:9EE1:5:27d60:f44:D4:cd:E:5:0A5:4a:D24:41Ad:";
bool matched;
Stopwatch sw;

Console.WriteLine("With backtracking:");
string backPattern = "^(([0-9a-fA-F]{1,4}:)*([0-9a-fA-F]{1,4}))*(::)$";
sw = Stopwatch.StartNew();
matched = Regex.IsMatch(input, backPattern);
sw.Stop();
Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, backPattern), sw.Elapsed);
Console.WriteLine();

Console.WriteLine("Without backtracking:");
string noBackPattern = "^((?>[0-9a-fA-F]{1,4}:)*(?>[0-9a-fA-F]{1,4}))*(::)$";
sw = Stopwatch.StartNew();
matched = Regex.IsMatch(input, noBackPattern);
sw.Stop();
Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, noBackPattern), sw.Elapsed);
}
}
// The example displays output like the following:
// With backtracking:
// Match: False in 00:00:27.4282019
//
// Without backtracking:
// Match: False in 00:00:00.0001391
Imports System.Diagnostics
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "b51:4:1DB:9EE1:5:27d60:f44:D4:cd:E:5:0A5:4a:D24:41Ad:"
Dim matched As Boolean
Dim sw As Stopwatch

Console.WriteLine("With backtracking:")
Dim backPattern As String = "^(([0-9a-fA-F]{1,4}:)*([0-9a-fA-F]{1,4}))*(::)$"
sw = Stopwatch.StartNew()
matched = Regex.IsMatch(input, backPattern)
sw.Stop()
Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, backPattern), sw.Elapsed)
Console.WriteLine()

Console.WriteLine("Without backtracking:")
Dim noBackPattern As String = "^((?>[0-9a-fA-F]{1,4}:)*(?>[0-9a-fA-F]{1,4}))*(::)$"
sw = Stopwatch.StartNew()
matched = Regex.IsMatch(input, noBackPattern)
sw.Stop()
Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, noBackPattern), sw.Elapsed)
End Sub
End Module
' The example displays the following output:
' With backtracking:
' Match: False in 00:00:27.4282019
'
' Without backtracking:
' Match: False in 00:00:00.0001391

assertions de postanalyse
.NET comprend deux éléments de langage, sous- (?<= expression ) et sous- (?<! expression ) , qui
correspondent au ou aux caractères précédents de la chaîne d’entrée. Les deux éléments de langage sont des
assertions de largeur nulle ; c’est-à-dire qu’ils déterminent si le ou les caractères qui précèdent immédiatement
le caractère actuel peuvent être mis en correspondance par la sous-expression, sans avancer ou utiliser la
rétroaction.
(?<= sous- expression ) est une assertion de postanalyse positive ; autrement dit, le ou les caractères situés
avant la position actuelle doivent correspondre à la sous- expression. (?<! sous- expression ) est une
assertion de postanalyse négative ; autrement dit, le ou les caractères situés avant la position actuelle ne doivent
pas correspondre à la sous- expression. Les assertions de postanalyse positive et négative sont très utiles quand
la sous- expression est un sous-ensemble de la sous-expression précédente.
L'exemple suivant utilise deux modèles d'expressions régulières équivalents qui valident le nom d'utilisateur
dans une adresse e-mail. Le premier modèle est sujet à des performances médiocres dues à une rétroaction
excessive. Le second modèle modifie la première expression régulière en remplaçant un quantificateur imbriqué
par une assertion de postanalyse positive. La sortie de l'exemple indique la durée d'exécution de la méthode
Regex.IsMatch .
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
Stopwatch sw;
string input = "test@contoso.com";
bool result;

string pattern = @"^[0-9A-Z]([-.\w]*[0-9A-Z])?@";


sw = Stopwatch.StartNew();
result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
sw.Stop();
Console.WriteLine("Match: {0} in {1}", result, sw.Elapsed);

string behindPattern = @"^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@";


sw = Stopwatch.StartNew();
result = Regex.IsMatch(input, behindPattern, RegexOptions.IgnoreCase);
sw.Stop();
Console.WriteLine("Match with Lookbehind: {0} in {1}", result, sw.Elapsed);
}
}
// The example displays output similar to the following:
// Match: True in 00:00:00.0017549
// Match with Lookbehind: True in 00:00:00.0000659

Module Example
Public Sub Main()
Dim sw As Stopwatch
Dim input As String = "test@contoso.com"
Dim result As Boolean

Dim pattern As String = "^[0-9A-Z]([-.\w]*[0-9A-Z])?@"


sw = Stopwatch.StartNew()
result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase)
sw.Stop()
Console.WriteLine("Match: {0} in {1}", result, sw.Elapsed)

Dim behindPattern As String = "^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@"


sw = Stopwatch.StartNew()
result = Regex.IsMatch(input, behindPattern, RegexOptions.IgnoreCase)
sw.Stop()
Console.WriteLine("Match with Lookbehind: {0} in {1}", result, sw.Elapsed)
End Sub
End Module
' The example displays output similar to the following:
' Match: True in 00:00:00.0017549
' Match with Lookbehind: True in 00:00:00.0000659

Le premier modèle d'expression régulière, ^[0-9A-Z]([-.\w]*[0-9A-Z])*@ , est défini comme indiqué dans le
tableau suivant.

M O DÈL E DESC RIP T IO N

^ Démarrer la correspondance au début de la chaîne.


M O DÈL E DESC RIP T IO N

[0-9A-Z] Mettre en correspondance un caractère alphanumérique.


Cette comparaison ne respecte pas la casse, parce que la
méthode Regex.IsMatch est appelée avec l'option
RegexOptions.IgnoreCase .

[-.\w]* Mettre en correspondance zéro, une ou plusieurs


occurrences d'un trait d'union, d'un point ou d'un caractère
alphabétique.

[0-9A-Z] Mettre en correspondance un caractère alphanumérique.

([-.\w]*[0-9A-Z])* Mettre en correspondance zéro, une ou plusieurs


occurrences de la combinaison de zéro ou plus traits d'union,
points ou caractères alphabétiques, suivis d'un caractère
alphanumérique. Il s'agit du premier groupe de capture.

@ Mettre en correspondance une arobase (« @ »).

Le second modèle d'expression régulière, ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@ , utilise une assertion de postanalyse


positive. Il est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

^ Démarrer la correspondance au début de la chaîne.

[0-9A-Z] Mettre en correspondance un caractère alphanumérique.


Cette comparaison ne respecte pas la casse, parce que la
méthode Regex.IsMatch est appelée avec l'option
RegexOptions.IgnoreCase .

[-.\w]* Mettre en correspondance zéro, une ou plusieurs


occurrences d'un trait d'union, d'un point ou d'un caractère
alphabétique.

(?<=[0-9A-Z]) Remonter au dernier caractère mis en correspondance et


continuer la mise en correspondances si elle est
alphanumérique. Notez que les caractères alphanumériques
sont un sous-ensemble du jeu qui se compose de points, de
traits d'union et de tous les caractères alphabétiques.

@ Mettre en correspondance une arobase (« @ »).

assertions de préanalyse
.NET comprend deux éléments de langage, sous- (?= expression ) et sous- (?! expression ) , qui
correspondent au ou aux caractères suivants dans la chaîne d’entrée. Les deux éléments de langage sont des
assertions de largeur nulle ; c’est-à-dire qu’ils déterminent si le ou les caractères qui suivent immédiatement le
caractère actuel peuvent être mis en correspondance par la sous-expression, sans avancer ou utiliser la
rétroaction.
(?= sous- expression ) est une assertion de préanalyse positive ; autrement dit, le ou les caractères situés
après la position actuelle doivent correspondre à la sous- expression. (?! sous- expression ) est une assertion
de préanalyse négative ; autrement dit, le ou les caractères situés après la position actuelle ne doivent pas
correspondre à la sous- expression. Les assertions de préanalyse positive et négative sont très utiles quand la
sous- expression est un sous-ensemble de la sous-expression suivante.
L'exemple suivant utilise deux modèles d'expressions régulières équivalentes qui valident un nom de type
qualifié complet. Le premier modèle est sujet à des performances médiocres dues à une rétroaction excessive.
Le second modifie la première expression régulière en remplaçant un quantificateur imbriqué par une assertion
de préanalyse positive. La sortie de l'exemple indique la durée d'exécution de la méthode Regex.IsMatch .

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string input = "aaaaaaaaaaaaaaaaaaaaaa.";
bool result;
Stopwatch sw;

string pattern = @"^(([A-Z]\w*)+\.)*[A-Z]\w*$";


sw = Stopwatch.StartNew();
result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
sw.Stop();
Console.WriteLine("{0} in {1}", result, sw.Elapsed);

string aheadPattern = @"^((?=[A-Z])\w+\.)*[A-Z]\w*$";


sw = Stopwatch.StartNew();
result = Regex.IsMatch(input, aheadPattern, RegexOptions.IgnoreCase);
sw.Stop();
Console.WriteLine("{0} in {1}", result, sw.Elapsed);
}
}
// The example displays the following output:
// False in 00:00:03.8003793
// False in 00:00:00.0000866

Imports System.Diagnostics
Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim input As String = "aaaaaaaaaaaaaaaaaaaaaa."
Dim result As Boolean
Dim sw As Stopwatch

Dim pattern As String = "^(([A-Z]\w*)+\.)*[A-Z]\w*$"


sw = Stopwatch.StartNew()
result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase)
sw.Stop()
Console.WriteLine("{0} in {1}", result, sw.Elapsed)

Dim aheadPattern As String = "^((?=[A-Z])\w+\.)*[A-Z]\w*$"


sw = Stopwatch.StartNew()
result = Regex.IsMatch(input, aheadPattern, RegexOptions.IgnoreCase)
sw.Stop()
Console.WriteLine("{0} in {1}", result, sw.Elapsed)
End Sub
End Module
' The example displays the following output:
' False in 00:00:03.8003793
' False in 00:00:00.0000866

Le premier modèle d'expression régulière, ^(([A-Z]\w*)+\.)*[A-Z]\w*$ , est défini comme indiqué dans le
tableau suivant.

M O DÈL E DESC RIP T IO N

^ Démarrer la correspondance au début de la chaîne.

([A-Z]\w*)+\. Mettre en correspondance un caractère alphabétique (A-Z)


suivi de zéro, un ou plusieurs caractères alphabétiques une
ou plusieurs fois, suivi d'un point. Cette comparaison ne
respecte pas la casse, parce que la méthode Regex.IsMatch
est appelée avec l'option RegexOptions.IgnoreCase .

(([A-Z]\w*)+\.)* Mettre en correspondance le modèle précédent, zéro, une


ou plusieurs fois.

[A-Z]\w* Mettre en correspondance un caractère alphabétique suivi


de zéro, un ou plusieurs caractères alphabétiques.

$ Terminer la correspondance à la fin de la chaîne d'entrée.

Le second modèle d'expression régulière, ^((?=[A-Z])\w+\.)*[A-Z]\w*$ , utilise une assertion de préanalyse


positive. Il est défini comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

^ Démarrer la correspondance au début de la chaîne.

(?=[A-Z]) Effectuer une préanalyse du premier caractère et continuer la


mise en correspondance s'il est alphabétique (A-Z). Cette
comparaison ne respecte pas la casse, parce que la méthode
Regex.IsMatch est appelée avec l'option
RegexOptions.IgnoreCase .

\w+\. Mettre en correspondance un ou plusieurs caractères


alphabétiques suivis d'un point.

((?=[A-Z])\w+\.)* Mettre en correspondance un ou plusieurs caractères


alphabétiques suivis d'un point zéro, une ou plusieurs fois.
Le caractère alphabétique initial doit être alphabétique.

[A-Z]\w* Mettre en correspondance un caractère alphabétique suivi


de zéro, un ou plusieurs caractères alphabétiques.

$ Terminer la correspondance à la fin de la chaîne d'entrée.

Voir aussi
Expressions régulières .NET
Langage des expressions régulières - Aide-mémoire
Quantificateurs
Constructions d’alternative
Constructions de regroupement
Compilation et réutilisation dans les expressions
régulières
18/07/2020 • 4 minutes to read • Edit Online

Vous pouvez optimiser les performances des applications qui utilisent très souvent des expressions régulières en
comprenant comment le moteur d’expression régulière compile les expressions et comment les expressions
régulières sont mises en cache. Cette rubrique décrit la compilation et la mise en cache.

Expressions régulières compilées


Par défaut, le moteur d’expression régulière compile une expression régulière en une séquence d’instructions
internes (il s’agit des codes généraux différents de MSIL, ou Microsoft Intermediate Language). Quand le moteur
exécute une expression régulière, il interprète les codes internes.
Si un objet Regex est construit avec l’option RegexOptions.Compiled, il compile l’expression régulière en code MSIL
explicite plutôt qu’en instructions internes d’expression régulière générale. Ainsi, le compilateur juste-à-temps (JIT)
de .NET convertit l’expression en code machine natif pour de meilleures performances. Le coût de la construction de
l' Regex objet peut être plus élevé, mais le coût lié à l’exécution de correspondances est probablement plus petit.
Une alternative serait d’utiliser des expressions régulières précompilées. Vous pouvez compiler l’ensemble de vos
expressions dans une DLL réutilisable à l’aide de la méthode CompileToAssembly. Cela évite de devoir compiler au
moment de l’exécution tout en bénéficiant de la vitesse des expressions régulières compilées.

Cache des expressions régulières


Pour améliorer les performances, le moteur d’expression régulière gère un cache à l’échelle de l’application des
expressions régulières compilées. Ce cache stocke les modèles d’expression régulière utilisés uniquement dans les
appels de méthode statique. (Les modèles d’expression régulière fournis aux méthodes d’instance ne sont pas mis
en cache.) Cela évite de devoir réanalyser une expression en code d’octet de haut niveau chaque fois qu’elle est
utilisée.
Le nombre maximal d’expressions régulières mises en cache est déterminé par la valeur de la propriété
Regex.CacheSize static ( Shared en Visual Basic). Par défaut, le moteur d’expression régulière met en cache
jusqu’à 15 expressions régulières compilées. Si le nombre d’expressions régulières compilées dépasse la taille du
cache, l’expression régulière la plus anciennement utilisée est ignorée et la nouvelle expression régulière est mise
en cache.
Votre application peut réutiliser des expressions régulières de l’une des deux manières suivantes :
En utilisant une méthode statique de l’objet Regex pour définir l’expression régulière. Si vous utilisez un
modèle d’expression régulière qui a déjà été défini par un autre appel de méthode statique, le moteur
d’expression régulière essaiera de le récupérer à partir du cache. S’il n’est pas disponible dans le cache, le
moteur compile l’expression régulière et l’ajoute au cache.
En réutilisant un objet Regex existant tant que son modèle d’expression régulière est nécessaire.
En raison de la surcharge liée à l’instanciation d’objets et à la compilation d’expressions régulières, la création et la
destruction rapide d’un grand nombre d’objets Regex est un processus très coûteux. Pour les applications qui
utilisent un grand nombre d’expressions régulières différentes, vous pouvez optimiser les performances en utilisant
des appels de méthodes Regex statiques et en augmentant éventuellement la taille du cache des expressions
régulières.
Voir aussi
Expressions régulières .NET
Sécurité des threads dans les expressions régulières
18/07/2020 • 2 minutes to read • Edit Online

La classe Regex proprement dite est thread-safe et immuable (en lecture seule). Autrement dit, des objets Regex
peuvent être créés sur n’importe quel thread et partagés par plusieurs threads ; les méthodes de mise en
correspondance peuvent être appelées à partir de n’importe quel thread et elles ne modifient jamais un état global.
Toutefois, les objets de résultat (Match et MatchCollection ) retournés par Regex doivent être utilisés sur un seul
thread. Bien que bon nombre de ces objets soient logiquement immuables, leurs implémentations peuvent
retarder le calcul de certains résultats pour améliorer les performances, et par conséquent, les appelants doivent en
sérialiser l’accès.
S’il est nécessaire de partager des objets de résultat Regex sur plusieurs threads, ces objets peuvent être convertis
en instances thread-safe en appelant leurs méthodes synchronisées. À l’exception des énumérateurs, toutes les
classes d’expression régulière sont thread-safe ou peuvent être converties en objets thread-safe par une méthode
synchronisée.
Les énumérateurs sont la seule exception. Une application doit sérialiser les appels aux énumérateurs de
collections. La règle est que si une collection peut être énumérée simultanément sur plusieurs threads, vous devez
synchroniser les méthodes d’énumération sur l’objet racine de la collection traversée par l’énumérateur.

Voir aussi
Expressions régulières .NET
Exemple d'expression régulière : recherche de valeurs
HREF
18/07/2020 • 6 minutes to read • Edit Online

L’exemple suivant recherche une chaîne d’entrée et affiche toutes les valeurs href="…" et leurs emplacements dans
la chaîne.

WARNING
Lorsque System.Text.RegularExpressions vous utilisez pour traiter une entrée non fiable, transmettez un délai d’attente. Un
utilisateur malveillant peut fournir une entrée pour RegularExpressions provoquer une attaque par déni de service.
ASP.NET Core les API d’infrastructure qui utilisent RegularExpressions passent un délai d’expiration.

Objet Regex
Étant donné que la méthode DumpHRefs peut être appelée plusieurs fois à partir du code utilisateur, la méthode
Regex.Match(String, String, RegexOptions) static ( Shared dans Visual Basic) est utilisée. Ainsi, le moteur
d’expression régulière peut mettre en cache l’expression régulière et éviter le traitement de l’instanciation d’un
nouvel objet Regex chaque fois que la méthode est appelée. Un objet Match est alors utilisé pour effectuer une
itération dans toutes les correspondances dans la chaîne.

private static void DumpHRefs(string inputString)


{
Match m;
string HRefPattern = @"href\s*=\s*(?:[""'](?<1>[^""']*)[""']|(?<1>\S+))";

try
{
m = Regex.Match(inputString, HRefPattern,
RegexOptions.IgnoreCase | RegexOptions.Compiled,
TimeSpan.FromSeconds(1));
while (m.Success)
{
Console.WriteLine("Found href " + m.Groups[1] + " at "
+ m.Groups[1].Index);
m = m.NextMatch();
}
}
catch (RegexMatchTimeoutException)
{
Console.WriteLine("The matching operation timed out.");
}
}
Private Sub DumpHRefs(inputString As String)
Dim m As Match
Dim HRefPattern As String = "href\s*=\s*(?:[""'](?<1>[^""']*)[""']|(?<1>\S+))"

Try
m = Regex.Match(inputString, HRefPattern, _
RegexOptions.IgnoreCase Or RegexOptions.Compiled,
TimeSpan.FromSeconds(1))
Do While m.Success
Console.WriteLine("Found href {0} at {1}.", _
m.Groups(1), m.Groups(1).Index)
m = m.NextMatch()
Loop
Catch e As RegexMatchTimeoutException
Console.WriteLine("The matching operation timed out.")
End Try
End Sub

L’exemple suivant illustre un appel à la méthode DumpHRefs .

public static void Main()


{
string inputString = "My favorite web sites include:</P>" +
"<A HREF=\"http://msdn2.microsoft.com\">" +
"MSDN Home Page</A></P>" +
"<A HREF=\"http://www.microsoft.com\">" +
"Microsoft Corporation Home Page</A></P>" +
"<A HREF=\"http://blogs.msdn.com/bclteam\">" +
".NET Base Class Library blog</A></P>";
DumpHRefs(inputString);
}
// The example displays the following output:
// Found href http://msdn2.microsoft.com at 43
// Found href http://www.microsoft.com at 102
// Found href http://blogs.msdn.com/bclteam at 176

Public Sub Main()


Dim inputString As String = "My favorite web sites include:</P>" & _
"<A HREF=""http://msdn2.microsoft.com"">" & _
"MSDN Home Page</A></P>" & _
"<A HREF=""http://www.microsoft.com"">" & _
"Microsoft Corporation Home Page</A></P>" & _
"<A HREF=""http://blogs.msdn.com/bclteam"">" & _
".NET Base Class Library blog</A></P>"
DumpHRefs(inputString)
End Sub
' The example displays the following output:
' Found href http://msdn2.microsoft.com at 43
' Found href http://www.microsoft.com at 102
' Found href http://blogs.msdn.com/bclteam/) at 176

Le modèle d'expression régulière href\s*=\s*(?:["'](?<1>[^"']*)["']|(?<1>\S+)) est interprété comme indiqué


dans le tableau suivant.

M O DÈL E DESC RIP T IO N

href Correspond à la chaîne littérale « href ». La recherche de


correspondance ne respecte pas la casse.

\s* Correspond à zéro, un ou plusieurs espaces blancs.


M O DÈL E DESC RIP T IO N

= Correspond au signe égal.

\s* Correspond à zéro, un ou plusieurs espaces blancs.

(?:\["'\](?<1>\[^"'\]*)["']|(?<1>\S+)) Correspond à l’un des éléments suivants sans assigner le


résultat à un groupe capturé :
Un guillemet ou une apostrophe, suivi(e) de zéro,
une ou plusieurs occurrences de tout caractère
autre qu’un guillemet ou une apostrophe, suivie(s)
d’un guillemet ou d’une apostrophe. Le groupe
nommé 1 est inclus dans ce modèle.
Un ou plusieurs caractères autres que des espaces.
Le groupe nommé 1 est inclus dans ce modèle.

(?<1>[^"']*) Affecte zéro, une ou plusieurs occurrences de tout caractère


autre qu’un guillemet ou une apostrophe au groupe de
capture nommé 1 .

(?<1>\S+) Affecte un ou plusieurs caractères non espace blanc au groupe


de capture nommé 1 .

Classe de résultats Match


Les résultats d’une recherche sont stockés dans la classe Match, qui donne accès à toutes les sous-chaînes extraites
par la recherche. Cette classe mémorise également la chaîne recherchée et l’expression régulière utilisée, pour
pouvoir appeler la méthode Match.NextMatch afin d’effectuer une nouvelle recherche qui commence là où la
dernière s’est terminée.

Captures explicitement nommées


Dans les expressions régulières classiques, les parenthèses de capture sont automatiquement numérotées dans
l’ordre. Cela génère deux problèmes. Tout d’abord, si une expression régulière est modifiée par l’insertion ou la
suppression d’un jeu de parenthèses, tout code qui fait référence aux captures numérotées doit être réécrit pour
refléter la nouvelle numérotation. Ensuite, étant donné que différents jeux de parenthèses sont souvent utilisés
pour fournir deux expressions différentes pour une correspondance acceptable, il peut s’avérer difficile de
déterminer laquelle des deux expressions en fait retourné un résultat.
Pour résoudre ces problèmes, la classe Regex prend en charge la syntaxe (?<name>…) pour capturer une
correspondance dans un emplacement spécifié (l’emplacement peut être nommé à l’aide d’une chaîne ou d’un
entier ; les entiers peuvent être rappelés plus rapidement). Ainsi, les autres correspondances de la même chaîne
peuvent toutes être dirigées vers le même emplacement. En cas de conflit, la dernière correspondance placée dans
un emplacement est la correspondance correcte. (Toutefois, la liste complète des différentes correspondances pour
un seul emplacement est disponible. Consultez la collection Group.Captures pour plus d’informations.)

Voir aussi
Expressions régulières .NET
Exemple d'expression régulière : modification des
formats de date
18/07/2020 • 3 minutes to read • Edit Online

L’exemple de code suivant utilise la Regex.Replace méthode pour remplacer les dates au format mm / JJ / AA par
les dates au format JJ - mm - AA.

WARNING
Lorsque System.Text.RegularExpressions vous utilisez pour traiter une entrée non fiable, transmettez un délai d’attente. Un
utilisateur malveillant peut fournir une entrée pour RegularExpressions provoquer une attaque par déni de service.
ASP.NET Core les API d’infrastructure qui utilisent RegularExpressions passent un délai d’expiration.

Exemple
static string MDYToDMY(string input)
{
try {
return Regex.Replace(input,
@"\b(?<month>\d{1,2})/(?<day>\d{1,2})/(?<year>\d{2,4})\b",
"${day}-${month}-${year}", RegexOptions.None,
TimeSpan.FromMilliseconds(150));
}
catch (RegexMatchTimeoutException) {
return input;
}
}

Function MDYToDMY(input As String) As String


Try
Return Regex.Replace(input, _
"\b(?<month>\d{1,2})/(?<day>\d{1,2})/(?<year>\d{2,4})\b", _
"${day}-${month}-${year}", RegexOptions.None,
TimeSpan.FromMilliseconds(150))
Catch e As RegexMatchTimeoutException
Return input
End Try
End Function

Le code suivant montre comment la méthode MDYToDMY peut être appelée dans une application.
using System;
using System.Globalization;
using System.Text.RegularExpressions;

public class Class1


{
public static void Main()
{
string dateString = DateTime.Today.ToString("d",
DateTimeFormatInfo.InvariantInfo);
string resultString = MDYToDMY(dateString);
Console.WriteLine("Converted {0} to {1}.", dateString, resultString);
}

static string MDYToDMY(string input)


{
try {
return Regex.Replace(input,
@"\b(?<month>\d{1,2})/(?<day>\d{1,2})/(?<year>\d{2,4})\b",
"${day}-${month}-${year}", RegexOptions.None,
TimeSpan.FromMilliseconds(150));
}
catch (RegexMatchTimeoutException) {
return input;
}
}
}
// The example displays the following output to the console if run on 8/21/2007:
// Converted 08/21/2007 to 21-08-2007.

Imports System.Globalization
Imports System.Text.RegularExpressions

Module DateFormatReplacement
Public Sub Main()
Dim dateString As String = Date.Today.ToString("d", _
DateTimeFormatInfo.InvariantInfo)
Dim resultString As String = MDYToDMY(dateString)
Console.WriteLine("Converted {0} to {1}.", dateString, resultString)
End Sub

Function MDYToDMY(input As String) As String


Try
Return Regex.Replace(input, _
"\b(?<month>\d{1,2})/(?<day>\d{1,2})/(?<year>\d{2,4})\b", _
"${day}-${month}-${year}", RegexOptions.None,
TimeSpan.FromMilliseconds(150))
Catch e As RegexMatchTimeoutException
Return input
End Try
End Function
End Module
' The example displays the following output to the console if run on 8/21/2007:
' Converted 08/21/2007 to 21-08-2007.

Commentaires
Le modèle d'expression régulière \b(?<month>\d{1,2})/(?<day>\d{1,2})/(?<year>\d{2,4})\b est interprété comme
indiqué dans le tableau suivant.
M O DÈL E DESC RIP T IO N

\b Commencer la correspondance à la limite d'un mot.

(?<month>\d{1,2}) Mettre en correspondance un ou deux chiffres décimaux. Il


s’agit du groupe capturé month .

/ Mettre en correspondance la barre oblique.

(?<day>\d{1,2}) Mettre en correspondance un ou deux chiffres décimaux. Il


s’agit du groupe capturé day .

/ Mettre en correspondance la barre oblique.

(?<year>\d{2,4}) Mettre en correspondance de deux à quatre chiffres décimaux.


Il s’agit du groupe capturé year .

\b Terminer la correspondance à la limite d'un mot.

Le modèle ${day}-${month}-${year} définit la chaîne de remplacement comme indiqué dans le tableau suivant.

M O DÈL E DESC RIP T IO N

$(day) Ajouter la chaîne capturée par le groupe de capture day .

- Ajouter un trait d’union.

$(month) Ajouter la chaîne capturée par le groupe de capture month .

- Ajouter un trait d’union.

$(year) Ajouter la chaîne capturée par le groupe de capture year .

Voir aussi
Expressions régulières .NET
Procédure : extraire un protocole et un numéro de
port d’une URL
18/07/2020 • 2 minutes to read • Edit Online

L’exemple suivant montre comment extraire un protocole et un numéro de port d’une URL.

WARNING
Lorsque System.Text.RegularExpressions vous utilisez pour traiter une entrée non fiable, transmettez un délai d’attente. Un
utilisateur malveillant peut fournir une entrée pour RegularExpressions provoquer une attaque par déni de service.
ASP.NET Core les API d’infrastructure qui utilisent RegularExpressions passent un délai d’expiration.

Exemple
L’exemple utilise la méthode Match.Result pour retourner le protocole, suivi d’un signe deux-points, lui-même suivi
du numéro de port.

using System;
using System.Text.RegularExpressions;

public class Example


{
public static void Main()
{
string url = "http://www.contoso.com:8080/letters/readme.html";

Regex r = new Regex(@"^(?<proto>\w+)://[^/]+?(?<port>:\d+)?/",


RegexOptions.None, TimeSpan.FromMilliseconds(150));
Match m = r.Match(url);
if (m.Success)
Console.WriteLine(m.Result("${proto}${port}"));
}
}
// The example displays the following output:
// http:8080

Imports System.Text.RegularExpressions

Module Example
Public Sub Main()
Dim url As String = "http://www.contoso.com:8080/letters/readme.html"
Dim r As New Regex("^(?<proto>\w+)://[^/]+?(?<port>:\d+)?/",
RegexOptions.None, TimeSpan.FromMilliseconds(150))

Dim m As Match = r.Match(url)


If m.Success Then
Console.WriteLine(m.Result("${proto}${port}"))
End If
End Sub
End Module
' The example displays the following output:
' http:8080

Le modèle d’expression régulière ^(?<proto>\w+)://[^/]+?(?<port>:\d+)?/ peut être interprété comme indiqué dans
le tableau suivant.

M O DÈL E DESC RIP T IO N

^ Commence la recherche de correspondance au début de la


chaîne.

(?<proto>\w+) Mettre en correspondance un ou plusieurs caractères


alphabétiques. Nommer ce groupe proto .

:// Mettre en correspondance un signe deux-points suivi de deux


barres obliques.

[^/]+? Mettre en correspondance une ou plusieurs occurrences (mais


le moins possible) de tout caractère autre qu’une barre
oblique.

(?<port>:\d+)? Mettre en correspondance zéro ou une occurrence d’un signe


deux-points suivi d’un ou de plusieurs caractères de chiffre.
Nommer ce groupe port .

/ Mettre en correspondance une barre oblique.

La méthode Match.Result étend la séquence de remplacement ${proto}${port} , qui concatène la valeur des deux
groupes nommés capturés dans le modèle d’expression régulière. Elle est plus pratique que la concaténation
explicite des chaînes récupérées de l’objet de collection retourné par la propriété Match.Groups.
L’exemple utilise la méthode Match.Result avec deux substitutions, ${proto} et ${port} , pour inclure les groupes
capturés dans la chaîne de sortie. Vous pouvez à la place récupérer les groupes capturés de l’objet GroupCollection
de la mise en correspondance, comme le montre le code suivant.

Console.WriteLine(m.Groups["proto"].Value + m.Groups["port"].Value);

Console.WriteLine(m.Groups("proto").Value + m.Groups("port").Value)

Voir aussi
Expressions régulières .NET
Procédure : supprimer des caractères non valides
d’une chaîne
18/07/2020 • 2 minutes to read • Edit Online

L’exemple suivant utilise la méthode Regex.Replace statique pour supprimer des caractères non valides d’une
chaîne.

WARNING
Lorsque System.Text.RegularExpressions vous utilisez pour traiter une entrée non fiable, transmettez un délai d’attente. Un
utilisateur malveillant peut fournir une entrée pour RegularExpressions provoquer une attaque par déni de service.
ASP.NET Core les API d’infrastructure qui utilisent RegularExpressions passent un délai d’expiration.

Exemple
Vous pouvez utiliser la méthode CleanInput définie dans cet exemple pour supprimer des caractères
potentiellement nuisibles entrés dans un champ de texte qui accepte une entrée d’utilisateur. Dans ce cas,
CleanInput supprime tous les caractères non alphanumériques à l’exception des points (.), des arrobases (@) et
des traits d’union (-), puis retourne la chaîne restante. Toutefois, vous pouvez modifier le modèle d’expression
régulière afin qu’il supprime tous les caractères qui ne doivent pas être inclus dans une chaîne d’entrée.

using System;
using System.Text.RegularExpressions;

public class Example


{
static string CleanInput(string strIn)
{
// Replace invalid characters with empty strings.
try {
return Regex.Replace(strIn, @"[^\w\.@-]", "",
RegexOptions.None, TimeSpan.FromSeconds(1.5));
}
// If we timeout when replacing invalid characters,
// we should return Empty.
catch (RegexMatchTimeoutException) {
return String.Empty;
}
}
}
Imports System.Text.RegularExpressions

Module Example
Function CleanInput(strIn As String) As String
' Replace invalid characters with empty strings.
Try
Return Regex.Replace(strIn, "[^\w\.@-]", "")
' If we timeout when replacing invalid characters,
' we should return String.Empty.
Catch e As RegexMatchTimeoutException
Return String.Empty
End Try
End Function
End Module

Le modèle d’expression régulière [^\w\.@-] recherche tout caractère qui n’est pas un caractère de mot, un point,
un symbole @ ou un trait d’union. Un caractère de mot correspond aux lettres, chiffres décimaux ou connecteurs
de ponctuation tel qu’un trait de soulignement. Tout caractère qui correspond à ce modèle est remplacé par
String.Empty, qui est la chaîne définie par le modèle de remplacement. Pour autoriser des caractères
supplémentaires dans une entrée d’utilisateur, ajoutez-les à la classe de caractères dans le modèle d’expression
régulière. Par exemple, le modèle d’expression régulière [^\w\.@-\\%] autorise également un symbole de
pourcentage et une barre oblique inverse dans une chaîne d’entrée.

Voir aussi
Expressions régulières .NET
Guide pratique pour vérifier que des chaînes sont
dans un format d’adresse e-mail valide
18/07/2020 • 11 minutes to read • Edit Online

L'exemple suivant utilise une expression régulière pour vérifier qu'une chaîne est dans un format d'adresse e-mail
valide.

WARNING
Lorsque System.Text.RegularExpressions vous utilisez pour traiter une entrée non fiable, transmettez un délai d’attente. Un
utilisateur malveillant peut fournir une entrée pour RegularExpressions provoquer une attaque par déni de service.
ASP.NET Core les API d’infrastructure qui utilisent RegularExpressions passent un délai d’expiration.

Exemple
L'exemple définit une méthode IsValidEmail qui retourne la valeur true si la chaîne contient une adresse de
messagerie valide et la valeur false dans le cas contraire, mais qui n'effectue aucune autre action.
Pour vérifier que l'adresse de messagerie est valide, la méthode IsValidEmail appelle la méthode
Regex.Replace(String, String, MatchEvaluator) avec le modèle d'expression régulière (@)(.+)$ pour séparer le
nom de domaine de l'adresse de messagerie. Le troisième paramètre est un délégué MatchEvaluator qui
représente la méthode qui traite et remplace le texte correspondant. Le modèle d'expression régulière est
interprété comme suit.

M O DÈL E DESC RIP T IO N

(@) Correspond à l'arobase (@). Il s'agit du premier groupe de


capture.

(.+) Correspond à une ou plusieurs occurrences d'un caractère


quelconque. Il s'agit du deuxième groupe de capture.

$ Termine la correspondance à la fin de la chaîne.

Le nom de domaine avec le caractère @ est passé à la méthode DomainMapper , qui utilise la classe IdnMapping
pour convertir les caractères Unicode situés en dehors de la plage de caractères US-ASCII au format Punycode. La
méthode affecte également à l'indicateur invalid la valeur True si la méthode IdnMapping.GetAscii détecte des
caractères non valides dans le nom de domaine. La méthode retourne le nom de domaine Punycode précédé du
symbole @ à la méthode IsValidEmail .
La méthode IsValidEmail appelle alors la méthode Regex.IsMatch(String, String) pour vérifier que l'adresse est
conforme à un modèle d'expression régulière.
Notez que la méthode IsValidEmail n'effectue pas d'authentification pour valider l'adresse de messagerie. Elle
détermine simplement si son format est valide pour une adresse de messagerie. En outre, la méthode
IsValidEmail ne vérifie pas si le nom de domaine de premier niveau est un nom de domaine valide répertorié
dans la base de données des zones racines de l’IANA. Cela nécessite une opération de recherche. À la place,
l'expression régulière vérifie simplement que le nom de domaine de niveau supérieur comprend entre deux et
vingt-quatre caractères ASCII, les premier et dernier caractères étant des caractères alphanumériques, les autres
des caractères alphanumériques ou un trait d'union (-).

using System;
using System.Globalization;
using System.Text.RegularExpressions;

public class RegexUtilities


{
public static bool IsValidEmail(string email)
{
if (string.IsNullOrWhiteSpace(email))
return false;

try
{
// Normalize the domain
email = Regex.Replace(email, @"(@)(.+)$", DomainMapper,
RegexOptions.None, TimeSpan.FromMilliseconds(200));

// Examines the domain part of the email and normalizes it.


string DomainMapper(Match match)
{
// Use IdnMapping class to convert Unicode domain names.
var idn = new IdnMapping();

// Pull out and process domain name (throws ArgumentException on invalid)


var domainName = idn.GetAscii(match.Groups[2].Value);

return match.Groups[1].Value + domainName;


}
}
catch (RegexMatchTimeoutException e)
{
return false;
}
catch (ArgumentException e)
{
return false;
}

try
{
return Regex.IsMatch(email,
@"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-
z])@))" +
@"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-0-9a-z]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}
[a-z0-9]))$",
RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250));
}
catch (RegexMatchTimeoutException)
{
return false;
}
}
}
Imports System.Globalization
Imports System.Text.RegularExpressions

Public Class RegexUtilities

Public Shared Function IsValidEmail(email As String) As Boolean

If String.IsNullOrWhiteSpace(email) Then Return False

' Use IdnMapping class to convert Unicode domain names.


Try
'Examines the domain part of the email and normalizes it.
Dim DomainMapper =
Function(match As Match) As String

'Use IdnMapping class to convert Unicode domain names.


Dim idn = New IdnMapping

'Pull out and process domain name (throws ArgumentException on invalid)


Dim domainName As String = idn.GetAscii(match.Groups(2).Value)

Return match.Groups(1).Value & domainName

End Function

'Normalize the domain


email = Regex.Replace(email, "(@)(.+)$", DomainMapper,
RegexOptions.None, TimeSpan.FromMilliseconds(200))

Catch e As RegexMatchTimeoutException
Return False

Catch e As ArgumentException
Return False

End Try

Try
Return Regex.IsMatch(email,
"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\
{\}\|~\w])*)(?<=[0-9a-z])@))" +
"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-0-9a-z]*[0-9a-z]*\.)+[a-z0-9]
[\-a-z0-9]{0,22}[a-z0-9]))$",
RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250))

Catch e As RegexMatchTimeoutException
Return False

End Try

End Function

End Class

Dans cet exemple, le modèle d’expression régulière


^(?(")(".+?(?<!\\)"@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))(?(\[)(\
[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-0-9a-z]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$
est interprété comme indiqué dans la légende suivante. L’expression régulière est compilée à l’aide de l'
RegexOptions.IgnoreCase indicateur.
Pattern ^ : commencer la correspondance au début de la chaîne.
Modèle (?(") : Déterminez si le premier caractère est un guillemet. (?(") est le début d'une construction
d'alternative.
Pattern (?(")(".+?(?<!\\)"@) : si le premier caractère est un guillemet, mettre en correspondance un guillemet
ouvrant suivi d’au moins une occurrence d’un caractère quelconque, suivi d’un guillemet fermant. Les guillemets
fermants ne doivent pas être précédés d'une barre oblique inverse (\). (?<! est le début d'une assertion de
postanalyse négative de largeur nulle. La chaîne doit se terminer par un arobase (@).
Pattern |(([0-9a-z] : si le premier caractère n’est pas un guillemet, correspond à n’importe quel caractère
alphabétique de a à z ou de a à z (la comparaison ne respecte pas la casse) ou n’importe quel caractère
numérique compris entre 0 et 9.
Modèle (\.(?!\.)) : si le caractère suivant est un point, associez-le. Dans le cas contraire, effectue une préanalyse
du caractère suivant et continue la recherche de correspondances. (?!\.) est une assertion de préanalyse
négative de largeur nulle qui empêche deux points consécutifs de s'afficher dans la partie locale d'une adresse de
messagerie.
Modèle |[-!#\$%&'\*\+/=\?\^`\{\}\|~\w] : si le caractère suivant n’est pas un point, correspond à un caractère
alphabétique ou à l’un des caractères suivants :- ! # $% & ' * +/= ? ^ ` {} | ~
Modèle ((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])* : correspond au modèle d’alternative (un point suivi d’un
autre que le point, ou un nombre de caractères) zéro, une ou plusieurs fois.
Modèle @ : correspond au caractère @.
Modèle (?<=[0-9a-z]) : continue la correspondance si le caractère qui précède le caractère @ est de a à z, de a à z
ou de 0 à 9. Ce modèle définit une assertion de postanalyse positive de largeur nulle.
Pattern (?(\[) : Vérifiez si le caractère qui suit @ est un crochet ouvrant.
Modèle (\[(\d{1,3}\.){3}\d{1,3}\]) : s’il s’agit d’un crochet ouvrant, établit une correspondance avec le crochet
ouvrant suivi d’une adresse IP (quatre ensembles de un à trois chiffres, chaque ensemble étant séparé par un
point) et d’un crochet fermant.
Modèle |(([0-9a-z][-0-9a-z]*[0-9a-z]*\.)+ : si le caractère qui suit @ n’est pas un crochet ouvrant, correspond à
un caractère alphanumérique avec la valeur a-z, a-z ou 0-9, suivi de zéro occurrence, ou plus, d’un trait d’Union,
suivi de zéro ou d’un caractère alphanumérique avec la valeur a-z, a-z ou 0-9, suivi d’un point. Ce modèle peut
être répété une ou plusieurs fois et doit être suivi du nom de domaine de niveau supérieur.
Modèle [a-z0-9][\-a-z0-9]{0,22}[a-z0-9])) : le nom de domaine de niveau supérieur doit commencer et se
terminer par un caractère alphanumérique (a-z, a-z et 0-9). Il peut également comprendre entre zéro et 22
caractères ASCII (soit des caractères alphanumériques, soit des traits d'union).
Pattern $ : termine la correspondance à la fin de la chaîne.

Compiler le code
Les méthodes IsValidEmail et DomainMapper peuvent être incluses dans une bibliothèque de méthodes utilitaires
d'expression régulière ou peuvent être incluses en tant que méthodes d'instance ou statiques privées dans la
classe d'application.
Vous pouvez également utiliser la méthode Regex.CompileToAssembly pour inclure cette expression régulière
dans une bibliothèque d'expressions régulières.
Si elles sont utilisées dans une bibliothèque d'expressions régulières, vous pouvez les appeler en utilisant un code
semblable au suivant :
class Program
{
static void Main(string[] args)
{
string[] emailAddresses = { "david.jones@proseware.com", "d.j@server1.proseware.com",
"jones@ms1.proseware.com", "j.@server1.proseware.com",
"j@proseware.com9", "js#internal@proseware.com",
"j_9@[129.126.118.1]", "j..s@proseware.com",
"js*@proseware.com", "js@proseware..com",
"js@proseware.com9", "j.s@server1.proseware.com",
"\"j\\\"s\\\"\"@proseware.com", "js@contoso.中国" };

foreach (var emailAddress in emailAddresses)


{
if (RegexUtilities.IsValidEmail(emailAddress))
Console.WriteLine($"Valid: {emailAddress}");
else
Console.WriteLine($"Invalid: {emailAddress}");
}

Console.ReadKey();
}
}
// The example displays the following output:
// Valid: david.jones@proseware.com
// Valid: d.j@server1.proseware.com
// Valid: jones@ms1.proseware.com
// Invalid: j.@server1.proseware.com
// Valid: j@proseware.com9
// Valid: js#internal@proseware.com
// Valid: j_9@[129.126.118.1]
// Invalid: j..s@proseware.com
// Invalid: js*@proseware.com
// Invalid: js@proseware..com
// Valid: js@proseware.com9
// Valid: j.s@server1.proseware.com
// Valid: "j\"s\""@proseware.com
// Valid: js@contoso.中国
Public Class Application
Public Shared Sub Main()
Dim emailAddresses() As String = {"david.jones@proseware.com", "d.j@server1.proseware.com",
"jones@ms1.proseware.com", "j.@server1.proseware.com",
"j@proseware.com9", "js#internal@proseware.com",
"j_9@[129.126.118.1]", "j..s@proseware.com",
"js*@proseware.com", "js@proseware..com",
"js@proseware.com9", "j.s@server1.proseware.com",
"""j\""s\""""@proseware.com", "js@contoso.中国"}

For Each emailAddress As String In emailAddresses


If RegexUtilities.IsValidEmail(emailAddress) Then
Console.WriteLine($"Valid: {emailAddress}")
Else
Console.WriteLine($"Invalid: {emailAddress}")
End If
Next
End Sub
End Class
' The example displays the following output:
' Valid: david.jones@proseware.com
' Valid: d.j@server1.proseware.com
' Valid: jones@ms1.proseware.com
' Invalid: j.@server1.proseware.com
' Valid: j@proseware.com9
' Valid: js#internal@proseware.com
' Valid: j_9@[129.126.118.1]
' Invalid: j..s@proseware.com
' Invalid: js*@proseware.com
' Invalid: js@proseware..com
' Valid: js@proseware.com9
' Valid: j.s@server1.proseware.com
' Valid: "j\"s\""@proseware.com
' Valid: js@contoso.中国

Voir aussi
Expressions régulières du .NET Framework
Encodage de caractères dans .NET
18/07/2020 • 31 minutes to read • Edit Online

Cet article fournit une introduction aux systèmes d’encodage de caractères qui sont utilisés par .NET. L’article
explique comment les String Char types,, Rune et StringInfo fonctionnent avec Unicode, UTF-16 et UTF-8.
Le terme caractère est utilisé ici dans le sens général de ce qu’un lecteur perçoit comme un seul élément
d’affichage. Les exemples les plus courants sont la lettre « a », le symbole « @ » et l’Emoji « ». Parfois, l’aspect
d’un caractère se compose en fait de plusieurs éléments d’affichage indépendants, comme l’explique la section sur
les clusters graphèmes .

stringTypes et char
Une instance de la string classe représente du texte. Un string est logiquement une séquence de valeurs 16 bits,
chacune d’elles étant une instance de la char structure. string . La propriété Length retourne le nombre d' char
instances dans l' string instance.
L’exemple de fonction suivant imprime les valeurs en notation hexadécimale de toutes les char instances dans un
string :

void PrintChars(string s)
{
Console.WriteLine($"\"{s}\".Length = {s.Length}");
for (int i = 0; i < s.Length; i++)
{
Console.WriteLine($"s[{i}] = '{s[i]}' ('\\u{(int)s[i]:x4}')");
}
Console.WriteLine();
}

Transmettez le string « Hello » à cette fonction et vous recevez la sortie suivante :

PrintChars("Hello");

"Hello".Length = 5
s[0] = 'H' ('\u0048')
s[1] = 'e' ('\u0065')
s[2] = 'l' ('\u006c')
s[3] = 'l' ('\u006c')
s[4] = 'o' ('\u006f')

Chaque caractère est représenté par une char valeur unique. Ce modèle est valable pour la plupart des langues
du monde. Par exemple, voici la sortie de deux caractères chinois qui ressemble à Nǐ hǎo et Mean Hello:

PrintChars("你好");

"你好".Length = 2
s[0] = '你' ('\u4f60')
s[1] = '好' ('\u597d')
Toutefois, pour certains langages et pour certains symboles et emoji, il faut deux char instances pour représenter
un caractère unique. Par exemple, comparez les caractères et les char instances dans le mot qui signifie Osage
dans le langage Osage :

PrintChars(" ");

" ".Length = 17
s[0] = '�' ('\ud801')
s[1] = '�' ('\udccf')
s[2] = '�' ('\ud801')
s[3] = '�' ('\udcd8')
s[4] = '�' ('\ud801')
s[5] = '�' ('\udcfb')
s[6] = '�' ('\ud801')
s[7] = '�' ('\udcd8')
s[8] = '�' ('\ud801')
s[9] = '�' ('\udcfb')
s[10] = '�' ('\ud801')
s[11] = '�' ('\udcdf')
s[12] = ' ' ('\u0020')
s[13] = '�' ('\ud801')
s[14] = '�' ('\udcbb')
s[15] = '�' ('\ud801')
s[16] = '�' ('\udcdf')

Dans l’exemple précédent, chaque caractère à l’exception de l’espace est représenté par deux char instances.
Un seul Emoji Unicode est également représenté par deux char , comme illustré dans l’exemple suivant, qui
montre un Emoji Ox :

" ".Length = 2
s[0] = '�' ('\ud83d')
s[1] = '�' ('\udc02')

Ces exemples montrent que la valeur de string.Length , qui indique le nombre d' char instances, n’indique pas
nécessairement le nombre de caractères affichés. Une seule char instance ne représente pas nécessairement un
caractère.
Les char paires mappées à un caractère unique sont appelées paires de substitution. Pour comprendre comment
elles fonctionnent, vous devez comprendre l’encodage Unicode et UTF-16.

Points de code Unicode


Unicode est une norme internationale d’encodage à utiliser sur différentes plateformes et avec différents langages
et scripts.
La norme Unicode définit plus de 1,1 million points de code. Un point de code est une valeur entière qui peut être
comprise entre 0 et U+10FFFF (décimal 1 114 111). Certains points de code sont assignés à des lettres, des
symboles ou des Emoji. D’autres sont affectés aux actions qui contrôlent le mode d’affichage du texte ou des
caractères, par exemple avancer sur une nouvelle ligne. De nombreux points de code ne sont pas encore assignés.
Voici quelques exemples d’affectations de point de code, avec des liens vers les graphiques Unicode dans lesquels
elles apparaissent :
DEC IM A L H EX EXEM P L E DESC RIP T IO N

10 U+000A N/A SAUT DE LIGNE

65 U+0061 a LETTRE MINUSCULE LATINE


A

562 U+0232 Ȳ LETTRE MAJUSCULE LATINE


Y AVEC MACRON

68 675 U+10C43 ANCIENNE LETTRE TURQUE


ORKHON À

127 801 U+1F339 ROSE-Emoji

Les points de code sont habituellement référencés à l’aide de la syntaxe U+xxxx , où xxxx est la valeur entière
encodée en hexadécimal.
Au sein de la plage complète de points de code, il existe deux sous-plages :
Le plan multilingue de base (BMP) de la plage U+0000..U+FFFF . Cette plage de 16 bits fournit 65 536 points
de code, suffisants pour couvrir la majorité des systèmes d’écriture au monde.
Points de code supplémentaires dans la plage U+10000..U+10FFFF . Cette plage de 21 bits fournit plus d’un
million de points de code supplémentaires qui peuvent être utilisés pour des langages moins connus et d’autres
fins, telles que des Emoji.
Le diagramme suivant illustre la relation entre le BMP et les points de code supplémentaires.

Unités de code UTF-16


le formatUTF-16(Unicode Transformation Format) 16 bits est un système de codage de caractères qui utilise des
unités de code 16 bits pour représenter les points de code Unicode. .NET utilise UTF-16 pour encoder le texte dans
un string . Une char instance représente une unité de code 16 bits.
Une seule unité de code 16 bits peut représenter n’importe quel point de code dans la plage de 16 bits du plan
multilingue de base. Toutefois, pour un point de code dans la plage supplémentaire, deux char instances sont
nécessaires.

Paires de substitution
La conversion de valeurs 2 16 bits en valeur 21 bits unique est facilitée par une plage spéciale appelée points de
code de substitution, de U+D800 à U+DFFF (décimal 55 296 à 57 343), incluse.
Le diagramme suivant illustre la relation entre le BMP et les points de code de substitution.

Quand un point de code de substitution étendu ( U+D800..U+DBFF ) est immédiatement suivi d’un point de code de
substitution faible ( U+DC00..U+DFFF ), la paire est interprétée comme un point de code supplémentaire à l’aide de
la formule suivante :
code point = 0x10000 +
((high surrogate code point - 0xD800) * 0x0400) +
(low surrogate code point - 0xDC00)

Voici la même formule utilisant la notation décimale :

code point = 65,536 +


((high surrogate code point - 55,296) * 1,024) +
(low surrogate code point - 56,320)

Un point de code de substitution étendu n’a pas une valeur numérique supérieure à un point de code de
substitution faible . Le point de code de substitution étendu est appelé « High », car il est utilisé pour calculer les 11
bits d’ordre supérieur de la plage de points de code 21 bits complète. Le point de code de substitution faible est
utilisé pour calculer les 10 bits de poids faible.
Par exemple, le point de code réel qui correspond à la paire de substitution 0xD83C et 0xDF39 est calculé comme
suit :

actual = 0x10000 + ((0xD83C - 0xD800) * 0x0400) + (0xDF39 - 0xDC00)


= 0x10000 + ( 0x003C * 0x0400) + 0x0339
= 0x10000 + 0xF000 + 0x0339
= 0x1F339

Voici le même calcul à l’aide de la notation décimale :

actual = 65,536 + ((55,356 - 55,296) * 1,024) + (57,145 - 56320)


= 65,536 + ( 60 * 1,024) + 825
= 65,536 + 61,440 + 825
= 127,801

L’exemple précédent montre que "\ud83c\udf39" est l’encodage UTF-16 du U+1F339 ROSE (' ') point de code
mentionné précédemment.

Valeurs scalaires Unicode


Le terme valeur scalaire Unicode fait référence à tous les points de code autres que les points de code de
substitution. En d’autres termes, une valeur scalaire correspond à n’importe quel point de code auquel un caractère
est affecté ou auquel un caractère peut être affecté à l’avenir. « Caractère » fait référence à tout ce qui peut être
assigné à un point de code, qui comprend des actions qui contrôlent l’affichage du texte ou des caractères.
Le diagramme suivant illustre les points de code de la valeur scalaire.

RuneType en tant que valeur scalaire


À compter de .NET Core 3,0, le System.Text.Rune type représente une valeur scalaire Unicode. Rune n’est pas
disponible dans .NET Core 2. x ou .NET Framework 4. x.
Les Rune constructeurs vérifient que l’instance résultante est une valeur scalaire Unicode valide, sinon elles lèvent
une exception. L’exemple suivant montre le code qui instancie correctement Rune des instances, car l’entrée
représente des valeurs scalaires valides :
Rune a = new Rune('a');
Rune b = new Rune(0x0061);
Rune c = new Rune('\u0061');
Rune d = new Rune(0x10421);
Rune e = new Rune('\ud801', '\udc21');

L’exemple suivant lève une exception, car le point de code se trouve dans la plage de substitution et ne fait pas
partie d’une paire de substitution :

Rune f = new Rune('\ud801');

L’exemple suivant lève une exception, car le point de code est au-delà de la plage supplémentaire :

Rune g = new Rune(0x12345678);

Rune exemple d’utilisation : modification de la casse des lettres


Une API qui prend un char et suppose qu’elle utilise un point de code qui est une valeur scalaire ne fonctionne pas
correctement si le char est issu d’une paire de substitution. Par exemple, considérez la méthode suivante qui
appelle Char.ToUpperInvariant sur chaque char dans un string :

// THE FOLLOWING METHOD SHOWS INCORRECT CODE.


// DO NOT DO THIS IN A PRODUCTION APPLICATION.
static string ConvertToUpperBadExample(string input)
{
StringBuilder builder = new StringBuilder(input.Length);
for (int i = 0; i < input.Length; i++) /* or 'foreach' */
{
builder.Append(char.ToUpperInvariant(input[i]));
}
return builder.ToString();
}

Si input string contient la lettre de Deseret minuscules er ( ), ce code ne le convertit pas en majuscules ( ).
Le code appelle char.ToUpperInvariant séparément sur chaque point de code de substitution, U+D801 et U+DC49 .
Mais U+D801 ne dispose pas d’informations suffisantes pour l’identifier comme une lettre minuscule
char.ToUpperInvariant . Et il gère U+DC49 de la même façon. Le résultat est que les minuscules « » dans le
input string ne sont pas converties en majuscules « ».

Voici deux options pour convertir correctement un string en majuscules :


Appelez String.ToUpperInvariant sur l’entrée string plutôt que d’effectuer char une itération par char . La
string.ToUpperInvariant méthode a accès aux deux parties de chaque paire de substitution. elle peut donc
gérer correctement tous les points de code Unicode.
Itérez au sein des valeurs scalaires Unicode en tant qu’instances plutôt qu’en tant Rune char qu’instances,
comme indiqué dans l’exemple suivant. Comme une Rune instance est une valeur scalaire Unicode valide,
elle peut être passée à des API qui s’attendent à fonctionner sur une valeur scalaire. Par exemple, l’appel de
Rune.ToUpperInvariant comme indiqué dans l’exemple suivant donne des résultats corrects :
static string ConvertToUpper(string input)
{
StringBuilder builder = new StringBuilder(input.Length);
foreach (Rune rune in input.EnumerateRunes())
{
builder.Append(Rune.ToUpperInvariant(rune));
}
return builder.ToString();
}

Autres Rune API


Le Rune type expose des analogies de nombreuses char API. Par exemple, les méthodes suivantes reflètent les
API statiques sur le char type :
Rune.IsLetter
Rune.IsWhiteSpace
Rune.IsLetterOrDigit
Rune.GetUnicodeCategory
Pour obtenir la valeur scalaire brute d’une Rune instance, utilisez la Rune.Value propriété.
Pour reconvertir une Rune instance en une séquence de char , utilisez Rune.ToString ou la Rune.EncodeToUtf16
méthode.
Étant donné que toute valeur scalaire Unicode char peut être représentée par un unique ou par une paire de
substitution, toute Rune instance peut être représentée par au plus 2 char instances. Utilisez
Rune.Utf16SequenceLength pour voir le nombre d' char instances nécessaires pour représenter une Rune
instance.
Pour plus d’informations sur le Rune type .net, consultez la référence de l' Rune API.

Clusters graphèmes
Comme un caractère peut résulter d’une combinaison de plusieurs points de code, un terme plus descriptif,
souvent utilisé à la place de « Character », est le cluster graphèmes. Le terme équivalent dans .NET est l' élément de
texte.
Examinez les string instances « a », « á ». « á » et « ». Si votre système d’exploitation les gère comme spécifié
par la norme Unicode, chacune de ces string instances apparaît sous la forme d’un seul élément de texte ou d’un
seul cluster graphèmes. Toutefois, les deux derniers sont représentés par plusieurs points de code de valeur
scalaire.
Le string « a » est représenté par une valeur scalaire et contient une char instance.
U+0061 LATIN SMALL LETTER A
Le string « á » est représenté par une valeur scalaire et contient une char instance.
U+00E1 LATIN SMALL LETTER A WITH ACUTE
Le string « á » ressemble à « á », mais il est représenté par deux valeurs scalaires et contient char deux
instances.
U+0061 LATIN SMALL LETTER A
U+0301 COMBINING ACUTE ACCENT
Enfin, le string « » est représenté par quatre valeurs scalaires et contient sept char instances.
U+1F469 WOMAN (plage supplémentaire, nécessite une paire de substitution)
U+1F3FD EMOJI MODIFIER FITZPATRICK TYPE-4 (plage supplémentaire, nécessite une paire de substitution)
U+200D ZERO WIDTH JOINER
U+1F692 FIRE ENGINE (plage supplémentaire, nécessite une paire de substitution)

Dans certains des exemples précédents, tels que le modificateur d’accent ou le modificateur de tonalité de la peau,
le point de code ne s’affiche pas en tant qu’élément autonome à l’écran. Au lieu de cela, elle permet de modifier
l’apparence d’un élément de texte qui précède. Ces exemples montrent qu’il peut prendre plusieurs valeurs
scalaires pour faire ce que nous considérons comme un « caractère » unique ou un « cluster graphèmes ».
Pour énumérer les clusters graphèmes d’un string , utilisez la StringInfo classe comme indiqué dans l’exemple
suivant. Si vous êtes familiarisé avec SWIFT, le StringInfo type .net est conceptuellement similaire au character
type SWIFT.
Exemple : char instances d' Rune éléments de texte Count, et
Dans les API .NET, un cluster graphèmes est appelé un élément de texte. La méthode suivante montre les
différences entre char les Rune instances d’élément de texte, et dans un string :

static void PrintTextElementCount(string s)


{
Console.WriteLine(s);
Console.WriteLine($"Number of chars: {s.Length}");
Console.WriteLine($"Number of runes: {s.EnumerateRunes().Count()}");

TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(s);

int textElementCount = 0;
while (enumerator.MoveNext())
{
textElementCount++;
}

Console.WriteLine($"Number of text elements: {textElementCount}");

PrintTextElementCount("á");
// Number of chars: 1
// Number of runes: 1
// Number of text elements: 1

PrintTextElementCount("a"́);
// Number of chars: 2
// Number of runes: 2
// Number of text elements: 1

PrintTextElementCount(" ");
// Number of chars: 7
// Number of runes: 4
// Number of text elements: 1

Si vous exécutez ce code dans .NET Framework ou .NET Core 3,1 ou une version antérieure, le nombre d’éléments
de texte pour l’Emoji s’affiche 4 . Cela est dû à un bogue dans la StringInfo classe qui est résolu dans .net 5.
Exemple : fractionnement d' string instances
Lors du fractionnement string des instances, évitez de fractionner les paires de substitution et les clusters
graphèmes. Prenons l’exemple suivant de code incorrect, qui envisage d’insérer des sauts de ligne tous les 10
caractères dans un string :
// THE FOLLOWING METHOD SHOWS INCORRECT CODE.
// DO NOT DO THIS IN A PRODUCTION APPLICATION.
static string InsertNewlinesEveryTencharsBadExample(string input)
{
StringBuilder builder = new StringBuilder();

// First, append chunks in multiples of 10 chars


// followed by a newline.
int i = 0;
for (; i < input.Length - 10; i += 10)
{
builder.Append(input, i, 10);
builder.AppendLine(); // newline
}

// Then append any leftover data followed by


// a final newline.
builder.Append(input, i, input.Length - i);
builder.AppendLine(); // newline

return builder.ToString();
}

Étant donné que ce code énumère char les instances, une paire de substitution qui se trouve sur un
chevauchement de 10 char limites est fractionnée et un saut de ligne est injecté entre eux. Cette insertion
introduit une altération des données, car les points de code de substitution sont significatifs uniquement en tant
que paires.
Le risque d’altération des données n’est pas éliminé si vous énumérez Rune des instances (valeurs scalaires) au
lieu d' char instances. Un ensemble d' Rune instances peut créer un cluster graphèmes qui chevauche 10 char
limites. Si le jeu de clusters graphèmes est fractionné, il ne peut pas être interprété correctement.
Une meilleure approche consiste à rompre le string en comptant les clusters graphèmes ou les éléments de texte,
comme dans l’exemple suivant :

static string InsertNewlinesEveryTenTextElements(string input)


{
StringBuilder builder = new StringBuilder();

// Append chunks in multiples of 10 chars

TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(input);

int textElementCount = 0;
while (enumerator.MoveNext())
{
builder.Append(enumerator.Current);
if (textElementCount % 10 == 0 && textElementCount > 0)
{
builder.AppendLine(); // newline
}
textElementCount++;
}

// Add a final newline.


builder.AppendLine(); // newline
return builder.ToString();

Comme indiqué précédemment, toutefois, dans les implémentations de .NET autres que .NET 5, la StringInfo
classe peut gérer des clusters graphèmes de manière incorrecte.
UTF-8 et UTF-32
Les sections précédentes sont axées sur UTF-16, car c’est ce que .NET utilise pour encoder des string instances. Il
existe d’autres systèmes d’encodage pour Unicode- UTF-8 et UTF-32. Ces encodages utilisent des unités de code 8
bits et des unités de code 32 bits, respectivement.
Comme UTF-16, UTF-8 requiert que plusieurs unités de code représentent des valeurs scalaires Unicode. UTF-32
peut représenter n’importe quelle valeur scalaire dans une seule unité de code 32 bits.
Voici quelques exemples montrant comment le même point de code Unicode est représenté dans chacun de ces
trois systèmes d’encodage Unicode :

Scalar: U+0061 LATIN SMALL LETTER A ('a')


UTF-8 : [ 61 ] (1x 8-bit code unit = 8 bits total)
UTF-16: [ 0061 ] (1x 16-bit code unit = 16 bits total)
UTF-32: [ 00000061 ] (1x 32-bit code unit = 32 bits total)

Scalar: U+0429 CYRILLIC CAPITAL LETTER SHCHA ('Щ')


UTF-8 : [ D0 A9 ] (2x 8-bit code units = 16 bits total)
UTF-16: [ 0429 ] (1x 16-bit code unit = 16 bits total)
UTF-32: [ 00000429 ] (1x 32-bit code unit = 32 bits total)

Scalar: U+A992 JAVANESE LETTER GA (' ')


UTF-8 : [ EA A6 92 ] (3x 8-bit code units = 24 bits total)
UTF-16: [ A992 ] (1x 16-bit code unit = 16 bits total)
UTF-32: [ 0000A992 ] (1x 32-bit code unit = 32 bits total)

Scalar: U+104CC OSAGE CAPITAL LETTER TSHA (' ')


UTF-8 : [ F0 90 93 8C ] (4x 8-bit code units = 32 bits total)
UTF-16: [ D801 DCCC ] (2x 16-bit code units = 32 bits total)
UTF-32: [ 000104CC ] (1x 32-bit code unit = 32 bits total)

Comme indiqué précédemment, une seule unité de code UTF-16 d’une paire de substitution n’est pas significative.
De la même façon, une seule unité de code UTF-8 n’a pas de sens pour elle-même si elle se trouve dans une
séquence de deux, trois ou quatre utilisées pour calculer une valeur scalaire.
Endianness
Dans .NET, les unités de code UTF-16 d’un string sont stockées dans une mémoire contiguë sous la forme d’une
séquence d’entiers 16 bits ( char instances). Les bits des unités de code individuelles sont disposés selon le
endianness de l’architecture actuelle.
Sur une architecture avec primauté des octets de poids faible, les string points de code UTF-16 [ D801 DCCC ] sont
disposés en mémoire comme octets [ 0x01, 0xD8, 0xCC, 0xDC ] . Sur une architecture avec primauté des octets de
poids fort (Big-endian), string elle serait présentée en mémoire comme octets [ 0xD8, 0x01, 0xDC, 0xCC ] .
Les systèmes informatiques qui communiquent entre eux doivent s’accorder sur la représentation des données qui
transitent par le réseau. La plupart des protocoles réseau utilisent UTF-8 comme norme lors de la transmission de
texte, en partie afin d’éviter les problèmes qui peuvent résulter d’une machine à Big-endian communiquant avec
un ordinateur Little-endian. Le string composé des points de code UTF-8 [ F0 90 93 8C ] est toujours représenté
sous la forme d’octets [ 0xF0, 0x90, 0x93, 0x8C ] , quel que soit le endianness.
Pour utiliser UTF-8 pour transmettre du texte, les applications .NET utilisent souvent du code similaire à l’exemple
suivant :

string stringToWrite = GetString();


byte[] stringAsUtf8Bytes = Encoding.UTF8.GetBytes(stringToWrite);
await outputStream.WriteAsync(stringAsUtf8Bytes, 0, stringAsUtf8Bytes.Length);
Dans l’exemple précédent, la méthode Encoding. UTF8. GetBytes décode le UTF-16 string en une série de valeurs
scalaires Unicode, puis Recode ces valeurs scalaires en UTF-8 et place la séquence résultante dans un byte
tableau. L' encodage de méthode. UTF8. GetString effectue la transformation inverse, en convertissant un tableau
UTF-8 byte en UTF-16 string .

WARNING
Étant donné qu’UTF-8 est courant sur Internet, il peut être tentant de lire des octets bruts à partir du câble et de traiter les
données comme s’il s’agissait d’UTF-8. Toutefois, vous devez vérifier qu’il est bien formé. Un client malveillant peut soumettre
un UTF-8 incorrect à votre service. Si vous opérez sur ces données comme si elles étaient correctement formées, cela peut
entraîner des erreurs ou des failles de sécurité dans votre application. Pour valider des données UTF-8, vous pouvez utiliser
une méthode comme Encoding.UTF8.GetString , qui effectuera la validation lors de la conversion des données entrantes
en string .

Encodage bien formé


Un encodage Unicode bien formé est un string d’unités de code qui peuvent être décodées sans ambiguïté et sans
erreur dans une séquence de valeurs scalaires Unicode. Les données correctement formées peuvent être
transcodées librement entre UTF-8, UTF-16 et UTF-32.
La question de savoir si une séquence d’encodage est correctement formée ou non n’est pas liée à l’endianness de
l’architecture d’une machine. Une séquence UTF-8 incorrecte est incorrecte de la même façon sur les ordinateurs
Big-endian et Little-endian.
Voici quelques exemples de codages incorrects :
En UTF-8, la séquence [ 6C C2 61 ] est incorrecte car C2 ne peut pas être suivi de 61 .
En UTF-16, la séquence [ DC00 DD00 ] (ou, en C#,) est incorrecte string "\udc00\udd00" , car le substitut
faible DC00 ne peut pas être suivi d’un autre substitut faible DD00 .
En UTF-32, la séquence [ 0011ABCD ] est incorrecte car 0011ABCD est en dehors de la plage de valeurs
scalaires Unicode.
Dans .NET, les string instances contiennent presque toujours des données UTF-16 bien formées, mais cela n’est
pas garanti. Les exemples suivants illustrent du code C# valide qui crée des données UTF-16 incorrectes dans des
string instances de.

Littéral incorrect :

const string s = "\ud800";

Une sous-chaîne qui fractionne une paire de substitution :

string x = "\ud83e\udd70"; // " "


string y = x.Substring(1, 1); // "\udd70" standalone low surrogate

Les API telles que Encoding.UTF8.GetString ne retournent jamais des instances mal formées string .
Encoding.GetString les Encoding.GetBytes méthodes et détectent les séquences incorrectes dans l’entrée et
effectuent la substitution de caractères lors de la génération de la sortie. Par exemple, si
Encoding.ASCII.GetString(byte[]) détecte un octet non-ASCII dans l’entrée (en dehors de la plage u + 0000.. U +
007F), il insère un « ? » dans l' string instance retournée. Encoding.UTF8.GetString(byte[]) remplace les
séquences UTF-8 incorrectes par U+FFFD REPLACEMENT CHARACTER ('�') dans l' string instance retournée. Pour
plus d’informations, consultez la norme Unicode, sections 5,22 et 3,9.
Les Encoding classes intégrées peuvent également être configurées pour lever une exception au lieu d’effectuer
une substitution de caractères quand des séquences incorrectes sont affichées. Cette approche est souvent utilisée
dans les applications sensibles à la sécurité où la substitution de caractères peut ne pas être acceptable.

byte[] utf8Bytes = ReadFromNetwork();


UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
string asString = encoding.GetString(utf8Bytes); // will throw if 'utf8Bytes' is ill-formed

Pour plus d’informations sur l’utilisation des classes intégrées Encoding , consultez Guide pratique pour utiliser
des classes d’encodage de caractères dans .net.

Voir aussi
String
Char
Rune
Globalisation et localisation
Guide pratique pour utiliser des classes d’encodage
de caractères dans .NET
18/07/2020 • 67 minutes to read • Edit Online

Cet article explique comment utiliser les classes fournies par .NET pour encoder et décoder du texte à l’aide de
différents schémas d’encodage. Les instructions partent du principe que vous avez lu la Présentation de l’encodage
de caractères dans .net.

Encodeurs et décodeurs
.NET fournit des classes d’encodage qui encodent et décodent du texte à l’aide de divers systèmes d’encodage. Par
exemple, la UTF8Encoding classe décrit les règles d’encodage et de décodage UTF-8. .NET utilise l’encodage UTF-16
(représenté par la UnicodeEncoding classe) pour les string instances. Les encodeurs et les décodeurs sont
disponibles pour d’autres schémas d’encodage.
L'encodage et le décodage peuvent également inclure une validation. Par exemple, la UnicodeEncoding classe
vérifie toutes les char instances de la plage de substitution pour s’assurer qu’elles se trouvent dans des paires de
substitution valides. Une stratégie de secours détermine comment un encodeur traite les caractères non valides ou
comment un décodeur gère les octets non valides.

WARNING
Les classes d’encodage .NET permettent de stocker et de convertir les données caractères. Elles ne doivent pas utilisées pour
stocker des données binaires sous forme de chaîne. En fonction de l'encodage utilisé, la conversion de données binaires en
un format chaîne avec les classes d'encodage peut entraîner un comportement inattendu et produire des données
incorrectes ou endommagées. Pour convertir des données binaires en chaîne, utilisez la méthode Convert.ToBase64String .

Toutes les classes d'encodage de caractères de .NET héritent de la classe System.Text.Encoding, qui est une classe
abstraite définissant les fonctionnalités communes à tous les encodages de caractères. Pour accéder aux objets
d’encodage individuels implémentés dans .NET, procédez comme suit :
Utilisez les propriétés statiques de la classe Encoding, qui retournent des objets représentant les encodages
de caractères standard disponibles dans .NET (ASCII, UTF-7, UTF-8, UTF-16 et UTF-32). Par exemple, la
propriété Encoding.Unicode retourne un objet UnicodeEncoding . Chaque objet utilise la stratégie de
secours pour les remplacements pour traiter les chaînes qu'il ne peut pas encoder et les octets qu'il ne peut
pas décoder. Pour plus d’informations, consultez remplacement de secours.
Appelez le constructeur de classe de l'encodage. Les objets pour les encodages ASCII, UTF-7, UTF-8, UTF-16
et UTF-32 peuvent être instanciés de cette façon. Par défaut, chaque objet utilise la stratégie de secours pour
les remplacements pour traiter les chaînes qu'il ne peut pas encoder et les octets qu'il ne peut pas décoder,
mais vous pouvez spécifier qu'au lieu de cela, une exception doit être levée. Pour plus d’informations,
consultez remplacement de secours et exception de secours.
Appelez le constructeur Encoding(Int32) et passez-lui un entier qui représente l'encodage. Les objets
d'encodage standard utilisent la stratégie de secours pour les remplacements. Les objets d'encodage de
page de codes et de jeu de caractères codés sur deux octets (DBCS) utilisent la stratégie de secours la mieux
adaptée pour traiter les chaînes qu'ils ne peuvent pas encoder et les octets qu'ils ne peuvent pas décoder.
Pour plus d’informations, consultez la page de secours la mieux adaptée.
Appelez la méthode Encoding.GetEncoding, qui retourne les encodages standard, de page de codes ou
DBCS disponibles dans .NET. Les surcharges vous permettent de spécifier un objet de secours pour
l'encodeur et pour le décodeur.
Vous pouvez récupérer des informations sur les encodages disponibles dans .NET en appelant la méthode
Encoding.GetEncodings. .NET prend en charge les schémas d’encodage de caractères listés dans le tableau suivant.

C L A SSE D’EN C O DA GE DESC RIP T IO N

R Encode une plage de caractères limitée en utilisant les sept


bits de poids le plus faible d'un octet. Étant donné que cet
encodage prend en charge seulement des valeurs de caractère
de U+0000 à U+007F, dans la plupart des cas, il ne convient
pas aux applications internationalisées.

UTF-7 Représente les caractères sous forme de séquences de


caractères ASCII sur 7 bits. Les caractères Unicode non-ASCII
sont représentés par une séquence d'échappement de
caractères ASCII. UTF-7 prend en charge les protocoles tels
que le courrier électronique et le groupe de discussion. UTF-7
n'est cependant pas particulièrement sécurisé ou robuste.
Dans certains cas, la modification d'un seul bit peut changer
radicalement l'interprétation de toute une chaîne UTF-7. Dans
d'autres cas, des chaînes UTF-7 différentes peuvent
correspondre à l'encodage d'un même texte. Pour les
séquences incluant des caractères non-ASCII, UTF-7 nécessite
davantage d'espace qu'UTF-8, et l'encodage/décodage est
plus lent. Par conséquent, il est préférable d'utiliser UTF-8 au
lieu d'UTF-7 si c'est possible.

UTF-8 Représente chaque point de code Unicode sous la forme d'une


séquence de un à quatre octets. UTF-8 prend en charge des
tailles de données de 8 bits et fonctionne bien avec de
nombreux systèmes d'exploitation. Pour la plage de caractères
ASCII, UTF-8 est identique à l'encodage ASCII et permet un
ensemble de caractères plus large. Toutefois, pour les scripts
chinois-japonais-coréen (CJC), UTF-8 peut nécessiter trois
octets pour chaque caractère et peut entraîner des tailles de
données supérieures à celles d’UTF-16. Parfois, la quantité de
données ASCII, telles que les balises HTML, justifie
l’augmentation de la taille de la plage CJC.

UTF-16 Représente chaque point de code Unicode sous la forme d'une


séquence de un ou deux entiers sur 16 bits. La plupart des
caractères Unicode courants ne nécessitent qu'un seul point
de code UTF-16, tandis que les caractères Unicode
additionnels (U+10000 et supérieurs) nécessitent deux points
de code de substitution UTF-16. Les ordres des octets Little
Endian et Big Endian sont pris en charge. L'encodage UTF-16
est utilisé par le common language runtime pour représenter
les valeurs Char et String , et il est utilisé par le système
d'exploitation Windows pour représenter les WCHAR .

UTF-32 Représente chaque point de code Unicode sous la forme d'un


entier 32 bits. Les ordres des octets Little Endian et Big Endian
sont pris en charge. L'encodage UTF-32 est utilisé quand les
applications ne doivent pas avoir le comportement du point
de code de substitution de l'encodage UTF-16 sur les
systèmes d'exploitation pour lesquels l'espace encodé est trop
important. Les glyphes uniques restitués à l'affichage peuvent
néanmoins encore être encodés avec plusieurs caractères
UTF-32.
C L A SSE D’EN C O DA GE DESC RIP T IO N

Codage ANSI/ISO Prend en charge différentes pages de codes. Sur les systèmes
d'exploitation Windows, les pages de codes sont utilisées pour
prendre en charge une langue spécifique ou un groupe de
langues spécifique. Pour un tableau répertoriant les pages de
codes prises en charge par .NET, consultez la classe Encoding.
Vous pouvez récupérer un objet d'encodage pour une page
de codes spécifique en appelant la méthode
Encoding.GetEncoding(Int32) . Une page de codes contient
256 points de code et commence à zéro. Dans la plupart des
pages de codes, les points de code de 0 à 127 représentent le
jeu de caractères ASCII, et les points de code de 128 à 255
diffèrent considérablement entre les pages de code. Par
exemple, la page de codes 1252 fournit les caractères pour les
systèmes d'écriture latins, y compris l'anglais, l'allemand et le
français. Les derniers 128 points de code de la page de codes
1252 contiennent les caractères accentués. La page de codes
1253 fournit les codes des caractères qui sont requis dans le
système d'écriture grec. Les derniers 128 points de code de la
page de codes 1253 contiennent les caractères grecs. Par
conséquent, une application qui s'appuie sur les pages de
codes ANSI ne peut pas stocker le grec et l'allemand dans le
même flux de texte, sauf si elle inclut un identificateur
indiquant la page de codes référencée.

Encodages DBCS (Double-Byte Character Set, Jeu de Prend en charge des langues comme le chinois, le japonais et
caractères codés sur deux octets) le coréen, qui contiennent plus de 256 caractères. Dans un jeu
de caractères DBCS, une paire de points de code (un double
octet) représente chaque caractère. La propriété
Encoding.IsSingleByte retourne false pour les encodages
DBCS. Vous pouvez récupérer un objet d'encodage pour un
jeu de caractères DBCS spécifique en appelant la méthode
Encoding.GetEncoding(Int32) . Quand une application traite
des données DBCS, le premier octet d'un caractère DBCS
(l'octet de tête) est traité en combinaison avec l'octet de fin
qui le suit immédiatement. Comme une seule paire de points
de code sur un double octet peut représenter des caractères
différents en fonction de la page de codes, ce schéma ne
permet pas non plus la combinaison de deux langues, comme
le japonais et le chinois, dans le même flux de données.

Ces encodages vous permettent de travailler avec des caractères Unicode ainsi qu'avec les encodages les plus
couramment utilisés dans les applications héritées. En outre, vous pouvez créer un encodage personnalisé en
définissant une classe qui dérive de Encoding et en remplaçant ses membres.

Prise en charge de l’encodage .NET Core


Par défaut, .NET Core ne met à disposition aucune page de codes autre que la page de codes 28591 et les
encodages Unicode, comme UTF-8 et UTF-16. Vous pouvez cependant ajouter à votre application les encodages
des pages de code qui se trouvent dans les applications Windows standard ciblant .NET. Pour plus d'informations,
voir la rubrique CodePagesEncodingProvider.

Sélection d'une classe d'encodage


Si vous avez la possibilité de choisir l'encodage à utiliser par votre application, utilisez un encodage Unicode, de
préférence UTF8Encoding ou UnicodeEncoding. (.NET prend également en charge un troisième encodage Unicode,
UTF32Encoding.)
Si vous envisagez d'utiliser un encodage ASCII (ASCIIEncoding), choisissez UTF8Encoding à la place. Les deux
encodages sont identiques pour le jeu de caractères ASCII, mais UTF8Encoding présente les avantages suivants :
Il peut représenter tous les caractères Unicode, alors que ASCIIEncoding prend en charge seulement les
valeurs des caractères Unicode entre U+0000 et U+007F.
Il offre une fonction de détection d'erreur et une meilleure sécurité.
Il a été optimisé pour être aussi rapide que possible et il doit normalement être plus rapide n'importe quel
autre encodage. Même pour du contenu qui est entièrement en ASCII, les opérations effectuées avec
UTF8Encoding sont plus rapides que les opérations effectuées avec ASCIIEncoding.
Vous devez envisager d'utiliser ASCIIEncoding seulement pour les applications héritées. Cependant, même pour les
applications héritées, UTF8Encoding peut être un meilleur choix pour les raisons suivantes (en supposant que les
paramètres par défaut sont utilisés) :
Si votre application a du contenu qui n'est pas strictement ASCII et qu'elle l'encode avec ASCIIEncoding,
chaque caractère non-ASCII est encodé sous la forme d'un point d'interrogation (?). Si l'application décode
ensuite ces données, les informations sont perdues.
Si votre application a du contenu qui n'est pas strictement ASCII et qu'elle l'encode avec UTF8Encoding, le
résultat paraît inintelligible s'il est interprété comme étant en ASCII. Cependant, si l'application utilise
ensuite un décodeur UTF-8 pour décoder ces données, les données apparaissent à nouveau correctes.
Dans une application web, les caractères envoyés au client en réponse à une demande web doivent refléter
l'encodage utilisé sur le client. Dans la plupart des cas, vous devez définir la propriété
HttpResponse.ContentEncoding à la valeur retournée par la propriété HttpRequest.ContentEncoding pour afficher
le texte dans le encodage attendu par l'utilisateur.

Utilisation d'un objet d'encodage


Un encodeur convertit une chaîne de caractères (le plus souvent, des caractères Unicode) en son équivalent
numérique (en octets). Par exemple, vous pouvez utiliser un encodeur ASCII pour convertir des caractères Unicode
en ASCII, pour pouvoir les afficher sur la console. Pour effectuer la conversion, vous appelez la méthode
Encoding.GetBytes . Si vous voulez déterminer le nombre d'octets nécessaires pour stocker les caractères encodés
avant de procéder à l'encodage, vous pouvez appeler la méthode GetByteCount .
L'exemple suivant utilise un tableau d'octets seuls pour encoder des chaînes dans deux opérations distinctes. Il
gère un index qui indique la position de départ dans le tableau d'octets pour l'ensemble suivant d'octets encodés
en ASCII. Il appelle la méthode ASCIIEncoding.GetByteCount(String) pour vérifier que le tableau d'octets est assez
grand pour contenir la chaîne encodée. Il appelle ensuite la méthode ASCIIEncoding.GetBytes(String, Int32, Int32,
Byte[], Int32) pour encoder les caractères de la chaîne.
using System;
using System.Text;

public class Example


{
public static void Main()
{
string[] strings= { "This is the first sentence. ",
"This is the second sentence. " };
Encoding asciiEncoding = Encoding.ASCII;

// Create array of adequate size.


byte[] bytes = new byte[49];
// Create index for current position of array.
int index = 0;

Console.WriteLine("Strings to encode:");
foreach (var stringValue in strings) {
Console.WriteLine(" {0}", stringValue);

int count = asciiEncoding.GetByteCount(stringValue);


if (count + index >= bytes.Length)
Array.Resize(ref bytes, bytes.Length + 50);

int written = asciiEncoding.GetBytes(stringValue, 0,


stringValue.Length,
bytes, index);

index = index + written;


}
Console.WriteLine("\nEncoded bytes:");
Console.WriteLine("{0}", ShowByteValues(bytes, index));
Console.WriteLine();

// Decode Unicode byte array to a string.


string newString = asciiEncoding.GetString(bytes, 0, index);
Console.WriteLine("Decoded: {0}", newString);
}

private static string ShowByteValues(byte[] bytes, int last )


{
string returnString = " ";
for (int ctr = 0; ctr <= last - 1; ctr++) {
if (ctr % 20 == 0)
returnString += "\n ";
returnString += String.Format("{0:X2} ", bytes[ctr]);
}
return returnString;
}
}
// The example displays the following output:
// Strings to encode:
// This is the first sentence.
// This is the second sentence.
//
// Encoded bytes:
//
// 54 68 69 73 20 69 73 20 74 68 65 20 66 69 72 73 74 20 73 65
// 6E 74 65 6E 63 65 2E 20 54 68 69 73 20 69 73 20 74 68 65 20
// 73 65 63 6F 6E 64 20 73 65 6E 74 65 6E 63 65 2E 20
//
// Decoded: This is the first sentence. This is the second sentence.
Imports System.Text

Module Example
Public Sub Main()
Dim strings() As String = {"This is the first sentence. ",
"This is the second sentence. "}
Dim asciiEncoding As Encoding = Encoding.ASCII

' Create array of adequate size.


Dim bytes(50) As Byte
' Create index for current position of array.
Dim index As Integer = 0

Console.WriteLine("Strings to encode:")
For Each stringValue In strings
Console.WriteLine(" {0}", stringValue)

Dim count As Integer = asciiEncoding.GetByteCount(stringValue)


If count + index >= bytes.Length Then
Array.Resize(bytes, bytes.Length + 50)
End If
Dim written As Integer = asciiEncoding.GetBytes(stringValue, 0,
stringValue.Length,
bytes, index)

index = index + written


Next
Console.WriteLine()
Console.WriteLine("Encoded bytes:")
Console.WriteLine("{0}", ShowByteValues(bytes, index))
Console.WriteLine()

' Decode Unicode byte array to a string.


Dim newString As String = asciiEncoding.GetString(bytes, 0, index)
Console.WriteLine("Decoded: {0}", newString)
End Sub

Private Function ShowByteValues(bytes As Byte(), last As Integer) As String


Dim returnString As String = " "
For ctr As Integer = 0 To last - 1
If ctr Mod 20 = 0 Then returnString += vbCrLf + " "
returnString += String.Format("{0:X2} ", bytes(ctr))
Next
Return returnString
End Function
End Module
' The example displays the following output:
' Strings to encode:
' This is the first sentence.
' This is the second sentence.
'
' Encoded bytes:
'
' 54 68 69 73 20 69 73 20 74 68 65 20 66 69 72 73 74 20 73 65
' 6E 74 65 6E 63 65 2E 20 54 68 69 73 20 69 73 20 74 68 65 20
' 73 65 63 6F 6E 64 20 73 65 6E 74 65 6E 63 65 2E 20
'
' Decoded: This is the first sentence. This is the second sentence.

Un décodeur convertit un tableau d'octets qui reflète un encodage de caractères spécifique en un jeu de caractères
dans un tableau de caractères ou dans une chaîne. Pour décoder un tableau d'octets en un tableau de caractères,
vous appelez la méthode Encoding.GetChars . Pour décoder un tableau d'octets en un tableau de caractères, vous
appelez la méthode GetString . Si vous voulez déterminer le nombre d'octets nécessaires pour stocker les
caractères décodés avant de procéder au décodage, vous pouvez appeler la méthode GetCharCount .
L'exemple suivant encode trois chaînes, puis les décode dans un même tableau de caractères. Il gère un index qui
indique la position de départ dans le tableau de caractères pour l'ensemble suivant de caractères décodés. Il
appelle la méthode GetCharCount pour vérifier que le tableau de caractères est assez grand pour contenir les
caractères décodés. Il appelle ensuite la méthode ASCIIEncoding.GetChars(Byte[], Int32, Int32, Char[], Int32) pour
décoder le tableau d'octets.
using System;
using System.Text;

public class Example


{
public static void Main()
{
string[] strings = { "This is the first sentence. ",
"This is the second sentence. ",
"This is the third sentence. " };
Encoding asciiEncoding = Encoding.ASCII;
// Array to hold encoded bytes.
byte[] bytes;
// Array to hold decoded characters.
char[] chars = new char[50];
// Create index for current position of character array.
int index = 0;

foreach (var stringValue in strings) {


Console.WriteLine("String to Encode: {0}", stringValue);
// Encode the string to a byte array.
bytes = asciiEncoding.GetBytes(stringValue);
// Display the encoded bytes.
Console.Write("Encoded bytes: ");
for (int ctr = 0; ctr < bytes.Length; ctr++)
Console.Write(" {0}{1:X2}",
ctr % 20 == 0 ? Environment.NewLine : "",
bytes[ctr]);
Console.WriteLine();

// Decode the bytes to a single character array.


int count = asciiEncoding.GetCharCount(bytes);
if (count + index >= chars.Length)
Array.Resize(ref chars, chars.Length + 50);

int written = asciiEncoding.GetChars(bytes, 0,


bytes.Length,
chars, index);
index = index + written;
Console.WriteLine();
}

// Instantiate a single string containing the characters.


string decodedString = new string(chars, 0, index - 1);
Console.WriteLine("Decoded string: ");
Console.WriteLine(decodedString);
}
}
// The example displays the following output:
// String to Encode: This is the first sentence.
// Encoded bytes:
// 54 68 69 73 20 69 73 20 74 68 65 20 66 69 72 73 74 20 73 65
// 6E 74 65 6E 63 65 2E 20
//
// String to Encode: This is the second sentence.
// Encoded bytes:
// 54 68 69 73 20 69 73 20 74 68 65 20 73 65 63 6F 6E 64 20 73
// 65 6E 74 65 6E 63 65 2E 20
//
// String to Encode: This is the third sentence.
// Encoded bytes:
// 54 68 69 73 20 69 73 20 74 68 65 20 74 68 69 72 64 20 73 65
// 6E 74 65 6E 63 65 2E 20
//
// Decoded string:
// This is the first sentence. This is the second sentence. This is the third sentence.
Imports System.Text

Module Example
Public Sub Main()
Dim strings() As String = {"This is the first sentence. ",
"This is the second sentence. ",
"This is the third sentence. "}
Dim asciiEncoding As Encoding = Encoding.ASCII
' Array to hold encoded bytes.
Dim bytes() As Byte
' Array to hold decoded characters.
Dim chars(50) As Char
' Create index for current position of character array.
Dim index As Integer

For Each stringValue In strings


Console.WriteLine("String to Encode: {0}", stringValue)
' Encode the string to a byte array.
bytes = asciiEncoding.GetBytes(stringValue)
' Display the encoded bytes.
Console.Write("Encoded bytes: ")
For ctr As Integer = 0 To bytes.Length - 1
Console.Write(" {0}{1:X2}", If(ctr Mod 20 = 0, vbCrLf, ""),
bytes(ctr))
Next
Console.WriteLine()

' Decode the bytes to a single character array.


Dim count As Integer = asciiEncoding.GetCharCount(bytes)
If count + index >= chars.Length Then
Array.Resize(chars, chars.Length + 50)
End If
Dim written As Integer = asciiEncoding.GetChars(bytes, 0,
bytes.Length,
chars, index)
index = index + written
Console.WriteLine()
Next

' Instantiate a single string containing the characters.


Dim decodedString As New String(chars, 0, index - 1)
Console.WriteLine("Decoded string: ")
Console.WriteLine(decodedString)
End Sub
End Module
' The example displays the following output:
' String to Encode: This is the first sentence.
' Encoded bytes:
' 54 68 69 73 20 69 73 20 74 68 65 20 66 69 72 73 74 20 73 65
' 6E 74 65 6E 63 65 2E 20
'
' String to Encode: This is the second sentence.
' Encoded bytes:
' 54 68 69 73 20 69 73 20 74 68 65 20 73 65 63 6F 6E 64 20 73
' 65 6E 74 65 6E 63 65 2E 20
'
' String to Encode: This is the third sentence.
' Encoded bytes:
' 54 68 69 73 20 69 73 20 74 68 65 20 74 68 69 72 64 20 73 65
' 6E 74 65 6E 63 65 2E 20
'
' Decoded string:
' This is the first sentence. This is the second sentence. This is the third sentence.

Les méthodes d'encodage et de décodage d'une classe dérivée de Encoding sont conçues pour fonctionner sur un
ensemble de données complet ; autrement dit, toutes les données à encoder ou à décoder sont fournies dans un
seul appel de méthode. Cependant, dans certains cas, les données sont disponibles dans un flux, et les données à
encoder ou à décoder peuvent être disponibles seulement via des opérations de lecture distinctes. Ceci nécessite la
conservation par l'opération d'encodage ou de décodage des états enregistrés depuis son précédent appel. Les
méthodes des classes dérivées de Encoder et de Decoder sont capables de traiter les opérations de codage et de
décodage qui recouvrent plusieurs appels de méthode.
Un objet Encoder pour un encodage particulier est disponible auprès de la propriété Encoding.GetEncoder de cet
encodage. Un objet Decoder pour un encodage particulier est disponible auprès de la propriété
Encoding.GetDecoder de cet encodage. Pour les opérations de décodage, notez que les classes dérivées de Decoder
incluent une méthode Decoder.GetChars , mais qu'elles n'ont pas de méthode correspondant à Encoding.GetString.
L'exemple suivant montre la différence entre l'utilisation des méthodes Encoding.GetString et Decoder.GetChars
pour le décodage d'un tableau d'octets Unicode. L'exemple encode une chaîne qui contient des caractères Unicode
vers un fichier, puis utilise les deux méthodes de décodage pour les décoder par dix octets à la fois. Une paire de
substitution se trouvant dans les dixième et onzième octets, elle est décodée dans des appels de méthode distincts.
Comme la sortie le montre, la méthode Encoding.GetString ne peut pas décoder correctement les octets et les
remplace par U+FFFD (CARACTÈRE DE REMPLACEMENT). En revanche, la méthode Decoder.GetChars peut
décoder correctement le tableau d'octets pour obtenir la chaîne d'origine.

using System;
using System.IO;
using System.Text;

public class Example


{
public static void Main()
{
// Use default replacement fallback for invalid encoding.
UnicodeEncoding enc = new UnicodeEncoding(true, false, false);

// Define a string with various Unicode characters.


string str1 = "AB YZ 19 \uD800\udc05 \u00e4";
str1 += "Unicode characters. \u00a9 \u010C s \u0062\u0308";
Console.WriteLine("Created original string...\n");

// Convert string to byte array.


byte[] bytes = enc.GetBytes(str1);

FileStream fs = File.Create(@".\characters.bin");
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(bytes);
bw.Close();

// Read bytes from file.


FileStream fsIn = File.OpenRead(@".\characters.bin");
BinaryReader br = new BinaryReader(fsIn);

const int count = 10; // Number of bytes to read at a time.


byte[] bytesRead = new byte[10]; // Buffer (byte array).
int read; // Number of bytes actually read.
string str2 = String.Empty; // Decoded string.

// Try using Encoding object for all operations.


do {
read = br.Read(bytesRead, 0, count);
str2 += enc.GetString(bytesRead, 0, read);
} while (read == count);
br.Close();
Console.WriteLine("Decoded string using UnicodeEncoding.GetString()...");
CompareForEquality(str1, str2);
Console.WriteLine();

// Use Decoder for all operations.


fsIn = File.OpenRead(@".\characters.bin");
fsIn = File.OpenRead(@".\characters.bin");
br = new BinaryReader(fsIn);
Decoder decoder = enc.GetDecoder();
char[] chars = new char[50];
int index = 0; // Next character to write in array.
int written = 0; // Number of chars written to array.
do {
read = br.Read(bytesRead, 0, count);
if (index + decoder.GetCharCount(bytesRead, 0, read) - 1 >= chars.Length)
Array.Resize(ref chars, chars.Length + 50);

written = decoder.GetChars(bytesRead, 0, read, chars, index);


index += written;
} while (read == count);
br.Close();
// Instantiate a string with the decoded characters.
string str3 = new String(chars, 0, index);
Console.WriteLine("Decoded string using UnicodeEncoding.Decoder.GetString()...");
CompareForEquality(str1, str3);
}

private static void CompareForEquality(string original, string decoded)


{
bool result = original.Equals(decoded);
Console.WriteLine("original = decoded: {0}",
original.Equals(decoded, StringComparison.Ordinal));
if (! result) {
Console.WriteLine("Code points in original string:");
foreach (var ch in original)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));
Console.WriteLine();

Console.WriteLine("Code points in decoded string:");


foreach (var ch in decoded)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));
Console.WriteLine();
}
}
}
// The example displays the following output:
// Created original string...
//
// Decoded string using UnicodeEncoding.GetString()...
// original = decoded: False
// Code points in original string:
// 0041 0042 0020 0059 005A 0020 0031 0039 0020 D800 DC05 0020 00E4 0055 006E 0069 0063 006F
// 0064 0065 0020 0063 0068 0061 0072 0061 0063 0074 0065 0072 0073 002E 0020 00A9 0020 010C
// 0020 0073 0020 0062 0308
// Code points in decoded string:
// 0041 0042 0020 0059 005A 0020 0031 0039 0020 FFFD FFFD 0020 00E4 0055 006E 0069 0063 006F
// 0064 0065 0020 0063 0068 0061 0072 0061 0063 0074 0065 0072 0073 002E 0020 00A9 0020 010C
// 0020 0073 0020 0062 0308
//
// Decoded string using UnicodeEncoding.Decoder.GetString()...
// original = decoded: True

Imports System.IO
Imports System.Text

Module Example
Public Sub Main()
' Use default replacement fallback for invalid encoding.
Dim enc As New UnicodeEncoding(True, False, False)

' Define a string with various Unicode characters.


Dim str1 As String = String.Format("AB YZ 19 {0}{1} {2}",
ChrW(&hD800), ChrW(&hDC05), ChrW(&h00e4))
str1 += String.Format("Unicode characters. {0} {1} s {2}{3}",
str1 += String.Format("Unicode characters. {0} {1} s {2}{3}",
ChrW(&h00a9), ChrW(&h010C), ChrW(&h0062), ChrW(&h0308))
Console.WriteLine("Created original string...")
Console.WriteLine()

' Convert string to byte array.


Dim bytes() As Byte = enc.GetBytes(str1)

Dim fs As FileStream = File.Create(".\characters.bin")


Dim bw As New BinaryWriter(fs)
bw.Write(bytes)
bw.Close()

' Read bytes from file.


Dim fsIn As FileStream = File.OpenRead(".\characters.bin")
Dim br As New BinaryReader(fsIn)

Const count As Integer = 10 ' Number of bytes to read at a time.


Dim bytesRead(9) As Byte ' Buffer (byte array).
Dim read As Integer ' Number of bytes actually read.
Dim str2 As String = "" ' Decoded string.

' Try using Encoding object for all operations.


Do
read = br.Read(bytesRead, 0, count)
str2 += enc.GetString(bytesRead, 0, read)
Loop While read = count
br.Close()
Console.WriteLine("Decoded string using UnicodeEncoding.GetString()...")
CompareForEquality(str1, str2)
Console.WriteLine()

' Use Decoder for all operations.


fsIn = File.OpenRead(".\characters.bin")
br = New BinaryReader(fsIn)
Dim decoder As Decoder = enc.GetDecoder()
Dim chars(50) As Char
Dim index As Integer = 0 ' Next character to write in array.
Dim written As Integer = 0 ' Number of chars written to array.
Do
read = br.Read(bytesRead, 0, count)
If index + decoder.GetCharCount(bytesRead, 0, read) - 1 >= chars.Length Then
Array.Resize(chars, chars.Length + 50)
End If
written = decoder.GetChars(bytesRead, 0, read, chars, index)
index += written
Loop While read = count
br.Close()
' Instantiate a string with the decoded characters.
Dim str3 As New String(chars, 0, index)
Console.WriteLine("Decoded string using UnicodeEncoding.Decoder.GetString()...")
CompareForEquality(str1, str3)
End Sub

Private Sub CompareForEquality(original As String, decoded As String)


Dim result As Boolean = original.Equals(decoded)
Console.WriteLine("original = decoded: {0}",
original.Equals(decoded, StringComparison.Ordinal))
If Not result Then
Console.WriteLine("Code points in original string:")
For Each ch In original
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()

Console.WriteLine("Code points in decoded string:")


For Each ch In decoded
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
Console.WriteLine()
End If
End Sub
End Module
' The example displays the following output:
' Created original string...
'
' Decoded string using UnicodeEncoding.GetString()...
' original = decoded: False
' Code points in original string:
' 0041 0042 0020 0059 005A 0020 0031 0039 0020 D800 DC05 0020 00E4 0055 006E 0069 0063 006F
' 0064 0065 0020 0063 0068 0061 0072 0061 0063 0074 0065 0072 0073 002E 0020 00A9 0020 010C
' 0020 0073 0020 0062 0308
' Code points in decoded string:
' 0041 0042 0020 0059 005A 0020 0031 0039 0020 FFFD FFFD 0020 00E4 0055 006E 0069 0063 006F
' 0064 0065 0020 0063 0068 0061 0072 0061 0063 0074 0065 0072 0073 002E 0020 00A9 0020 010C
' 0020 0073 0020 0062 0308
'
' Decoded string using UnicodeEncoding.Decoder.GetString()...
' original = decoded: True

Choix d'une stratégie de secours


Quand une méthode tente d'encoder ou de décoder un caractère, mais qu'il n'existe pas de mappage, elle doit
implémenter une stratégie de secours qui détermine comment l'échec du mappage doit être traité. Il existe trois
types de stratégies de secours :
Best-Fit Fallback
Stratégie de secours pour les remplacements
Exception Fallback

IMPORTANT
Les problèmes les plus courants des opérations d'encodage se produisent quand un caractère Unicode ne peut pas être
mappé à un encodage de page de codes particulier. Les problèmes les plus courants des opérations de décodage se
produisent quand des séquences d'octets non valides ne peut pas être traduites en caractères Unicode valides. Pour ces
raisons, vous devez savoir quelle stratégie de secours est utilisée par un objet de codage particulier. Chaque fois que c'est
possible, vous devez spécifier la stratégie de secours utilisée par un objet d'encodage quand vous instanciez l'objet.

Best-Fit Fallback
Quand un caractère n'a pas de correspondance exacte dans l'encodage cible, l'encodeur peut essayer de le mapper
à un caractère similaire. (La stratégie de secours la mieux adaptée est principalement un codage plutôt qu'un
problème de décodage. Il y a très peu de pages de codes qui contiennent des caractères qui ne peuvent pas être
correctement mappés au format Unicode.) L’option de secours la mieux adaptée est la valeur par défaut pour les
encodages de pages de codes et de jeux de caractères codés sur deux octets, qui sont récupérés par les
Encoding.GetEncoding(Int32) Encoding.GetEncoding(String) surcharges et.

NOTE
En théorie, les classes d'encodage Unicode fournies dans .NET (UTF8Encoding, UnicodeEncoding et UTF32Encoding)
prennent en charge tous les caractères de tous les jeux de caractères : elles peuvent donc être utilisées pour éliminer les
problèmes de la stratégie de secours la mieux adaptée.

Les stratégies de secours les mieux adaptées varient pour les différentes pages de codes. Par exemple, pour
certaines pages de codes, les caractères latins à pleine chasse sont mappés aux caractères latins à demi-chasse
plus courants. Pour d'autres pages de codes, ce mappage n'est pas effectué. Même avec une stratégie la plus
adaptée appliquée de façon agressive, il n'existe pas d'ajustement possible pour certains caractères dans certains
encodages. Par exemple, un idéogramme chinois n'a pas de mappage acceptable avec la page de codes 1252. Dans
ce cas, une chaîne de remplacement est utilisée. Par défaut, cette chaîne est simplement un POINT
D'INTERROGATION (U+003F).

NOTE
Les stratégies de secours les mieux adaptées ne sont pas documentées en détail. Toutefois, plusieurs pages de codes sont
documentées sur le site Web du consortium Unicode. Consultez le fichier readme.txt de ce dossier pour obtenir une
description sur la manière d’interpréter les fichiers de mappage.

L'exemple suivant utilise la page de codes 1252 (la page de codes Windows pour les langues d'Europe occidentale)
pour illustrer le mappage le mieux adapté et ses inconvénients. La méthode Encoding.GetEncoding(Int32) est
utilisée pour récupérer un objet d'encodage pour la page de codes 1252. Par défaut, elle utilise un mappage le
mieux adapté pour les caractères Unicode qu'elle ne prend pas en charge. L'exemple instancie une chaîne
contenant trois caractères non-ASCII, LETTRE MAJUSCULE LATINE S CERCLÉE (U+24C8), EXPOSANT CINQ
(U+2075) et INFINI (U+221E), séparés par des espaces. Comme le montre la sortie de l'exemple, quand la chaîne
est encodée, les trois caractères d'origine autres qu'un espace sont remplacés par POINT D'INTERROGATION
(U+003F), CHIFFRE CINQ (U+0035) et CHIFFRE HUIT (U+0038). CHIFFRE HUIT est un substitut particulièrement
médiocre pour le caractère INFINI non pris en charge, et POINT D'INTERROGATION indique qu'aucun mappage
n'est disponible pour le caractère d'origine.
using System;
using System.Text;

public class Example


{
public static void Main()
{
// Get an encoding for code page 1252 (Western Europe character set).
Encoding cp1252 = Encoding.GetEncoding(1252);

// Define and display a string.


string str = "\u24c8 \u2075 \u221e";
Console.WriteLine("Original string: " + str);
Console.Write("Code points in string: ");
foreach (var ch in str)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine("\n");

// Encode a Unicode string.


Byte[] bytes = cp1252.GetBytes(str);
Console.Write("Encoded bytes: ");
foreach (byte byt in bytes)
Console.Write("{0:X2} ", byt);
Console.WriteLine("\n");

// Decode the string.


string str2 = cp1252.GetString(bytes);
Console.WriteLine("String round-tripped: {0}", str.Equals(str2));
if (! str.Equals(str2)) {
Console.WriteLine(str2);
foreach (var ch in str2)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));
}
}
}
// The example displays the following output:
// Original string: Ⓢ ⁵ ∞
// Code points in string: 24C8 0020 2075 0020 221E
//
// Encoded bytes: 3F 20 35 20 38
//
// String round-tripped: False
// ? 5 8
// 003F 0020 0035 0020 0038
Imports System.Text

Module Example
Public Sub Main()
' Get an encoding for code page 1252 (Western Europe character set).
Dim cp1252 As Encoding = Encoding.GetEncoding(1252)

' Define and display a string.


Dim str As String = String.Format("{0} {1} {2}", ChrW(&h24c8), ChrW(&H2075), ChrW(&h221E))
Console.WriteLine("Original string: " + str)
Console.Write("Code points in string: ")
For Each ch In str
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
Console.WriteLine()

' Encode a Unicode string.


Dim bytes() As Byte = cp1252.GetBytes(str)
Console.Write("Encoded bytes: ")
For Each byt In bytes
Console.Write("{0:X2} ", byt)
Next
Console.WriteLine()
Console.WriteLine()

' Decode the string.


Dim str2 As String = cp1252.GetString(bytes)
Console.WriteLine("String round-tripped: {0}", str.Equals(str2))
If Not str.Equals(str2) Then
Console.WriteLine(str2)
For Each ch In str2
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
End If
End Sub
End Module
' The example displays the following output:
' Original string: Ⓢ ⁵ ∞
' Code points in string: 24C8 0020 2075 0020 221E
'
' Encoded bytes: 3F 20 35 20 38
'
' String round-tripped: False
' ? 5 8
' 003F 0020 0035 0020 0038

Le mappage le mieux adapté est le comportement par défaut pour un objet Encoding qui encode des données
Unicode en données de page de codes, et il existe des applications héritées qui s'appuient sur ce comportement.
Cependant, la plupart des nouvelles applications doivent éviter ce comportement le mieux adapté pour des raisons
de sécurité. Par exemple, les applications ne doivent pas établir un nom de domaine via un encodage le mieux
adapté.

NOTE
Vous pouvez également implémenter un mappage de stratégie de secours la mieux adaptée personnalisé pour un encodage.
Pour plus d’informations, consultez la section implémentation d’une stratégie de secours personnalisée .

Si la stratégie de secours la mieux adaptée est la stratégie par défaut pour un objet d'encodage, vous pouvez
choisir une autre stratégie de secours quand vous récupérez un objet Encoding , en appelant la surcharge
Encoding.GetEncoding(Int32, EncoderFallback, DecoderFallback) ou Encoding.GetEncoding(String, EncoderFallback,
DecoderFallback) . La section suivante contient un exemple qui remplace chaque caractère qui ne peut pas être
mappé à la page de codes 1252 par un astérisque (*).

using System;
using System.Text;

public class Example


{
public static void Main()
{
Encoding cp1252r = Encoding.GetEncoding(1252,
new EncoderReplacementFallback("*"),
new DecoderReplacementFallback("*"));

string str1 = "\u24C8 \u2075 \u221E";


Console.WriteLine(str1);
foreach (var ch in str1)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();

byte[] bytes = cp1252r.GetBytes(str1);


string str2 = cp1252r.GetString(bytes);
Console.WriteLine("Round-trip: {0}", str1.Equals(str2));
if (! str1.Equals(str2)) {
Console.WriteLine(str2);
foreach (var ch in str2)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();
}
}
}
// The example displays the following output:
// Ⓢ ⁵ ∞
// 24C8 0020 2075 0020 221E
// Round-trip: False
// * * *
// 002A 0020 002A 0020 002A
Imports System.Text

Module Example
Public Sub Main()
Dim cp1252r As Encoding = Encoding.GetEncoding(1252,
New EncoderReplacementFallback("*"),
New DecoderReplacementFallback("*"))

Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&h24C8), ChrW(&h2075), ChrW(&h221E))


Console.WriteLine(str1)
For Each ch In str1
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()

Dim bytes() As Byte = cp1252r.GetBytes(str1)


Dim str2 As String = cp1252r.GetString(bytes)
Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
If Not str1.Equals(str2) Then
Console.WriteLine(str2)
For Each ch In str2
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
End If
End Sub
End Module
' The example displays the following output:
' Ⓢ ⁵ ∞
' 24C8 0020 2075 0020 221E
' Round-trip: False
' * * *
' 002A 0020 002A 0020 002A

Replacement Fallback
Quand un caractère n'a pas de correspondance exacte dans le schéma cible, mais qu'il n'existe pas de caractère
approprié auquel il peut être mappé, l'application peut spécifier un caractère ou une chaîne de remplacement. Il
s'agit du comportement par défaut pour le décodeur Unicode, qui remplace toutes les séquences de deux octets
qu'il ne peut pas décoder par CARACTÈRE_DE_REMPLACEMENT (U+FFFD). C'est également le comportement par
défaut de la classe ASCIIEncoding , qui remplace chaque caractère qu'elle ne peut pas encoder ou décoder par un
point d'interrogation. L'exemple suivant illustre le remplacement de caractères pour la chaîne Unicode de
l'exemple précédent. Comme le montre la sortie, chaque caractère qui ne peut pas être décodé en une valeur
d'octet ASCII est remplacé par 0x3F, qui est le code ASCII pour un point d'interrogation.
using System;
using System.Text;

public class Example


{
public static void Main()
{
Encoding enc = Encoding.ASCII;

string str1 = "\u24C8 \u2075 \u221E";


Console.WriteLine(str1);
foreach (var ch in str1)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine("\n");

// Encode the original string using the ASCII encoder.


byte[] bytes = enc.GetBytes(str1);
Console.Write("Encoded bytes: ");
foreach (var byt in bytes)
Console.Write("{0:X2} ", byt);
Console.WriteLine("\n");

// Decode the ASCII bytes.


string str2 = enc.GetString(bytes);
Console.WriteLine("Round-trip: {0}", str1.Equals(str2));
if (! str1.Equals(str2)) {
Console.WriteLine(str2);
foreach (var ch in str2)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();
}
}
}
// The example displays the following output:
// Ⓢ ⁵ ∞
// 24C8 0020 2075 0020 221E
//
// Encoded bytes: 3F 20 3F 20 3F
//
// Round-trip: False
// ? ? ?
// 003F 0020 003F 0020 003F
Imports System.Text

Module Example
Public Sub Main()
Dim enc As Encoding = Encoding.Ascii

Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&h24C8), ChrW(&h2075), ChrW(&h221E))


Console.WriteLine(str1)
For Each ch In str1
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
Console.WriteLine()

' Encode the original string using the ASCII encoder.


Dim bytes() As Byte = enc.GetBytes(str1)
Console.Write("Encoded bytes: ")
For Each byt In bytes
Console.Write("{0:X2} ", byt)
Next
Console.WriteLine()
Console.WriteLine()

' Decode the ASCII bytes.


Dim str2 As String = enc.GetString(bytes)
Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
If Not str1.Equals(str2) Then
Console.WriteLine(str2)
For Each ch In str2
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
End If
End Sub
End Module
' The example displays the following output:
' Ⓢ ⁵ ∞
' 24C8 0020 2075 0020 221E
'
' Encoded bytes: 3F 20 3F 20 3F
'
' Round-trip: False
' ? ? ?
' 003F 0020 003F 0020 003F

.NET comprend les classes EncoderReplacementFallback et DecoderReplacementFallback, qui substituent une


chaîne de remplacement si un caractère ne se mappe pas exactement dans une opération d'encodage ou de
décodage. Par défaut, cette chaîne de remplacement est un point d'interrogation, mais vous pouvez appeler une
surcharge de constructeur de classe pour choisir une autre chaîne. En général, la chaîne de remplacement est un
caractère unique, même si ce n'est pas obligatoire. L'exemple suivant change le comportement de l'encodeur de la
page de codes 1252 en instanciant un objet EncoderReplacementFallback qui utilise un astérisque (*) comme
chaîne de remplacement.
using System;
using System.Text;

public class Example


{
public static void Main()
{
Encoding cp1252r = Encoding.GetEncoding(1252,
new EncoderReplacementFallback("*"),
new DecoderReplacementFallback("*"));

string str1 = "\u24C8 \u2075 \u221E";


Console.WriteLine(str1);
foreach (var ch in str1)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();

byte[] bytes = cp1252r.GetBytes(str1);


string str2 = cp1252r.GetString(bytes);
Console.WriteLine("Round-trip: {0}", str1.Equals(str2));
if (! str1.Equals(str2)) {
Console.WriteLine(str2);
foreach (var ch in str2)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();
}
}
}
// The example displays the following output:
// Ⓢ ⁵ ∞
// 24C8 0020 2075 0020 221E
// Round-trip: False
// * * *
// 002A 0020 002A 0020 002A
Imports System.Text

Module Example
Public Sub Main()
Dim cp1252r As Encoding = Encoding.GetEncoding(1252,
New EncoderReplacementFallback("*"),
New DecoderReplacementFallback("*"))

Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&h24C8), ChrW(&h2075), ChrW(&h221E))


Console.WriteLine(str1)
For Each ch In str1
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()

Dim bytes() As Byte = cp1252r.GetBytes(str1)


Dim str2 As String = cp1252r.GetString(bytes)
Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
If Not str1.Equals(str2) Then
Console.WriteLine(str2)
For Each ch In str2
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
End If
End Sub
End Module
' The example displays the following output:
' Ⓢ ⁵ ∞
' 24C8 0020 2075 0020 221E
' Round-trip: False
' * * *
' 002A 0020 002A 0020 002A

NOTE
Vous pouvez également implémenter une classe de remplacement pour un encodage. Pour plus d’informations, consultez la
section implémentation d’une stratégie de secours personnalisée .

En plus du POINT D'INTERROGATION (U+003F), le CARACTÈRE DE REMPLACEMENT Unicode (U+FFFD) est


couramment utilisé comme chaîne de remplacement, en particulier lors du décodage de séquences d'octets qui ne
peuvent pas être converties en caractères Unicode. Vous êtes cependant libre de choisir n'importe quelle chaîne de
remplacement, qui peut contenir plusieurs caractères.
Exception Fallback
Au lieu de fournir une stratégie de secours la mieux adaptée ou une chaîne de remplacement, un encodeur peut
lever une EncoderFallbackException si elle ne peut pas encoder un jeu de caractères, et un décodeur peut lever une
DecoderFallbackException s'il ne peut pas décoder un tableau d'octets. Pour lever une exception dans les
opérations d'encodage et de décodage, vous fournissez un objet EncoderExceptionFallback et un objet
DecoderExceptionFallback , respectivement, à la méthode Encoding.GetEncoding(String, EncoderFallback,
DecoderFallback) . L'exemple suivant montre la stratégie de secours pour les exceptions avec la classe
ASCIIEncoding .

using System;
using System.Text;

public class Example


{
public static void Main()
{
Encoding enc = Encoding.GetEncoding("us-ascii",
Encoding enc = Encoding.GetEncoding("us-ascii",
new EncoderExceptionFallback(),
new DecoderExceptionFallback());

string str1 = "\u24C8 \u2075 \u221E";


Console.WriteLine(str1);
foreach (var ch in str1)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine("\n");

// Encode the original string using the ASCII encoder.


byte[] bytes = {};
try {
bytes = enc.GetBytes(str1);
Console.Write("Encoded bytes: ");
foreach (var byt in bytes)
Console.Write("{0:X2} ", byt);

Console.WriteLine();
}
catch (EncoderFallbackException e) {
Console.Write("Exception: ");
if (e.IsUnknownSurrogate())
Console.WriteLine("Unable to encode surrogate pair 0x{0:X4} 0x{1:X3} at index {2}.",
Convert.ToUInt16(e.CharUnknownHigh),
Convert.ToUInt16(e.CharUnknownLow),
e.Index);
else
Console.WriteLine("Unable to encode 0x{0:X4} at index {1}.",
Convert.ToUInt16(e.CharUnknown),
e.Index);
return;
}
Console.WriteLine();

// Decode the ASCII bytes.


try {
string str2 = enc.GetString(bytes);
Console.WriteLine("Round-trip: {0}", str1.Equals(str2));
if (! str1.Equals(str2)) {
Console.WriteLine(str2);
foreach (var ch in str2)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();
}
}
catch (DecoderFallbackException e) {
Console.Write("Unable to decode byte(s) ");
foreach (byte unknown in e.BytesUnknown)
Console.Write("0x{0:X2} ");

Console.WriteLine("at index {0}", e.Index);


}
}
}
// The example displays the following output:
// Ⓢ ⁵ ∞
// 24C8 0020 2075 0020 221E
//
// Exception: Unable to encode 0x24C8 at index 0.
Imports System.Text

Module Example
Public Sub Main()
Dim enc As Encoding = Encoding.GetEncoding("us-ascii",
New EncoderExceptionFallback(),
New DecoderExceptionFallback())

Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&h24C8), ChrW(&h2075), ChrW(&h221E))


Console.WriteLine(str1)
For Each ch In str1
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
Console.WriteLine()

' Encode the original string using the ASCII encoder.


Dim bytes() As Byte = {}
Try
bytes = enc.GetBytes(str1)
Console.Write("Encoded bytes: ")
For Each byt In bytes
Console.Write("{0:X2} ", byt)
Next
Console.WriteLine()
Catch e As EncoderFallbackException
Console.Write("Exception: ")
If e.IsUnknownSurrogate() Then
Console.WriteLine("Unable to encode surrogate pair 0x{0:X4} 0x{1:X3} at index {2}.",
Convert.ToUInt16(e.CharUnknownHigh),
Convert.ToUInt16(e.CharUnknownLow),
e.Index)
Else
Console.WriteLine("Unable to encode 0x{0:X4} at index {1}.",
Convert.ToUInt16(e.CharUnknown),
e.Index)
End If
Exit Sub
End Try
Console.WriteLine()

' Decode the ASCII bytes.


Try
Dim str2 As String = enc.GetString(bytes)
Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
If Not str1.Equals(str2) Then
Console.WriteLine(str2)
For Each ch In str2
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
End If
Catch e As DecoderFallbackException
Console.Write("Unable to decode byte(s) ")
For Each unknown As Byte In e.BytesUnknown
Console.Write("0x{0:X2} ")
Next
Console.WriteLine("at index {0}", e.Index)
End Try
End Sub
End Module
' The example displays the following output:
' Ⓢ ⁵ ∞
' 24C8 0020 2075 0020 221E
'
' Exception: Unable to encode 0x24C8 at index 0.
NOTE
Vous pouvez également implémenter un gestionnaire d'exceptions personnalisé pour une opération d'encodage. Pour plus
d’informations, consultez la section implémentation d’une stratégie de secours personnalisée .

Les objets EncoderFallbackException et DecoderFallbackException fournissent les informations suivantes sur la


condition qui a provoqué l'exception :
L'objet EncoderFallbackException comprend une méthode IsUnknownSurrogate , qui indique si le ou les
caractères qui ne peuvent pas être encodés représentent une paire de substitution inconnue (auquel cas la
méthode retourne true ) ou un seul caractère inconnu (auquel cas la méthode retourne false ). Les
caractères de la paire de substitution sont disponibles dans les propriétés
EncoderFallbackException.CharUnknownHigh et EncoderFallbackException.CharUnknownLow . Le caractère
unique inconnu est disponible dans la propriété EncoderFallbackException.CharUnknown . La propriété
EncoderFallbackException.Index indique la position dans la chaîne du premier caractère qui n'a pas pu être
encodé.
L'objet DecoderFallbackException comprend une propriété BytesUnknown qui retourne un tableau d'octets
qui ne peut pas être décodé. La propriété DecoderFallbackException.Index indique la position de départ des
octets inconnus.
Bien que les objets EncoderFallbackException et DecoderFallbackException fournissent des informations de
diagnostic adéquates sur l'exception, ils ne fournissent pas d'accès à la mémoire tampon d'encodage ou de
décodage. Par conséquent, ils ne permettent pas le remplacement ou la correction des données non valides dans la
méthode d'encodage ou de décodage.

Implementing a Custom Fallback Strategy


En plus du mappage le mieux adapté implémenté en interne par les pages de codes, .NET comprend les classes
suivantes pour l’implémentation d’une stratégie de secours :
Utilisez EncoderReplacementFallback et EncoderReplacementFallbackBuffer pour remplacer des caractères
dans les opérations d'encodage.
Utilisez DecoderReplacementFallback et DecoderReplacementFallbackBuffer pour remplacer des caractères
dans les opérations de décodage.
Utilisez EncoderExceptionFallback et EncoderExceptionFallbackBuffer pour lever une
EncoderFallbackException quand un caractère ne peut pas être encodé.
Utilisez DecoderExceptionFallback et DecoderExceptionFallbackBuffer pour lever une
DecoderFallbackException quand un caractère ne peut pas être décodé.
En outre, vous pouvez implémenter une solution personnalisée qui utilise une stratégie de secours la mieux
adaptée, une stratégie de secours pour les remplacements ou une stratégie de secours pour les exceptions, en
procédant comme suit :
1. Dérivez une classe de EncoderFallback pour les opérations d'encodage, et de DecoderFallback pour les
opérations de décodage.
2. Dérivez une classe de EncoderFallbackBuffer pour les opérations d'encodage, et de DecoderFallbackBuffer
pour les opérations de décodage.
3. Pour la stratégie de secours pour les exceptions, si les classes prédéfinies EncoderFallbackException et
DecoderFallbackException ne répondent pas à vos besoins, dérivez une classe depuis un objet d'exception
comme Exception ou ArgumentException.
Dérivation depuis EncoderFallback ou DecoderFallback
Pour implémenter une solution de stratégie de secours personnalisée, vous devez créer une classe qui hérite de
EncoderFallback pour les opérations d'encodage, et de DecoderFallback pour les opérations de décodage. Les
instances de ces classes sont passées à la méthode Encoding.GetEncoding(String, EncoderFallback,
DecoderFallback) et elle servent d'intermédiaire entre la classe d'encodage et l'implémentation de la stratégie de
secours.
Quand vous créez une solution de secours personnalisée pour un encodeur ou un décodeur, vous devez
implémenter les membres suivants :
La propriété EncoderFallback.MaxCharCount ou DecoderFallback.MaxCharCount , qui retourne le nombre
maximal possible de caractères qui peuvent être retournés par la stratégie de secours la mieux adaptée, par
la stratégie de secours pour les remplacements ou par la stratégie de secours pour les exceptions pour
remplacer un caractère unique. Pour une stratégie de secours pour les exceptions personnalisée, sa valeur
est zéro.
La méthode EncoderFallback.CreateFallbackBuffer ou DecoderFallback.CreateFallbackBuffer , qui retourne
votre implémentation personnalisée de EncoderFallbackBuffer ou de DecoderFallbackBuffer . La méthode
est appelée par l'encodeur quand il rencontre le premier caractère qu'il est impossible d'encoder
correctement, ou par le décodeur quand il rencontre le premier octet qu'il est impossible de décoder
correctement.
Dérivation depuis EncoderFallbackBuffer ou DecoderFallbackBuffer
Pour implémenter une solution de secours personnalisée, vous devez aussi créer une classe qui hérite de
EncoderFallbackBuffer pour les opérations d'encodage, et de DecoderFallbackBuffer pour les opérations de
décodage. Les instances de ces classes sont retournées par la méthode CreateFallbackBuffer des classes
EncoderFallback et DecoderFallback . La méthode EncoderFallback.CreateFallbackBuffer est appelée par l'encodeur
quand il rencontre le premier caractère qu'il ne peut pas encoder, et la méthode
DecoderFallback.CreateFallbackBuffer est appelée par le décodeur quand il rencontre un ou plusieurs octets qu'il
ne peut pas décoder. Les classes EncoderFallbackBuffer et DecoderFallbackBuffer fournissent l'implémentation de
la stratégie de secours. Chaque instance représente une mémoire tampon qui contient les caractères de secours
destinés à remplacer le caractère qui ne peut pas être encodé ou la séquence d'octets qui ne peut pas être décodée.
Quand vous créez une solution de secours personnalisée pour un encodeur ou un décodeur, vous devez
implémenter les membres suivants :
La méthode EncoderFallbackBuffer.Fallback ou DecoderFallbackBuffer.Fallback .
EncoderFallbackBuffer.Fallback est appelée par l'encodeur pour fournir à la mémoire tampon de secours des
informations concernant le caractère qu'elle ne peut pas encoder. Étant donné que le caractère à encoder
peut être une paire de substitution, cette méthode est surchargée. Le caractère à encoder et son index dans
la chaîne sont passés à une surcharge. La demi-zone haute et la demi-zone basse, ainsi que son index dans
la chaîne, sont passés à la deuxième surcharge. La méthode DecoderFallbackBuffer.Fallback est appelée par
le décodeur pour fournir à la mémoire tampon de secours des informations sur les octets qu'elle ne peut
pas décoder. Cette méthode reçoit un tableau d'octets qu'elle ne peut pas décoder, ainsi que l'index du
premier octet. La méthode de secours doit retourner true si la mémoire tampon de secours peut fournir
un ou plusieurs caractères les mieux adaptés ou de remplacement ; sinon, elle doit retourner false . Pour
une stratégie de secours pour les exceptions, la méthode de secours doit lever une exception.
La méthode EncoderFallbackBuffer.GetNextChar ou DecoderFallbackBuffer.GetNextChar , qui est appelée de
façon répétée par l'encodeur ou par le décodeur pour obtenir le caractère suivant de la mémoire tampon de
secours. Quand tous les caractères de secours ont été retournés, la méthode doit retourner U+0000.
La propriété EncoderFallbackBuffer.Remaining ou DecoderFallbackBuffer.Remaining , qui retourne le nombre
de caractères restants dans la mémoire tampon de secours.
La méthode EncoderFallbackBuffer.MovePrevious ou DecoderFallbackBuffer.MovePrevious , qui déplace la
position actuelle dans la mémoire tampon de secours au caractère précédent.
La méthode EncoderFallbackBuffer.Reset ou DecoderFallbackBuffer.Reset , qui réinitialise la mémoire tampon
de secours.
Si l'implémentation de la stratégie de secours est une stratégie de secours la mieux adaptée ou de remplacement,
les classes dérivées de EncoderFallbackBuffer et de DecoderFallbackBuffer gèrent également deux champs
d'instance privés : le nombre exact de caractères dans la mémoire tampon et l'index du caractère suivant dans la
mémoire tampon à retourner.
Exemple de stratégie de secours d'encodeur
L'exemple précédent utilisait une stratégie de sauvegarde pour les remplacements pour remplacer les caractères
Unicode qui ne correspondaient pas aux caractères ASCII par un astérisque (*). L'exemple suivant utilise à la place
une implémentation de stratégie de secours la mieux adaptée personnalisée pour fournir un meilleur mappage des
caractères non-ASCII.
Le code suivant définit une classe nommée CustomMapper qui est dérivée de EncoderFallback pour gérer le
mappage le mieux adapté des caractères non-ASCII. Sa méthode CreateFallbackBuffer retourne un objet
CustomMapperFallbackBuffer , qui fournit l'implémentation de EncoderFallbackBuffer . La classe CustomMapper
utilise un objet Dictionary<TKey,TValue> pour stocker les mappages des caractères Unicode non pris en charge (la
valeur de la clé) et leurs caractères 8 bits correspondants (qui sont stockés dans deux octets consécutifs dans un
entier 64 bits). Pour que ce mappage soit disponible pour la mémoire tampon de secours, l'instance de
CustomMapper est passée comme paramètre au constructeur de classe CustomMapperFallbackBuffer . Comme le
mappage le plus long est la chaîne "INF" pour le caractère Unicode U+221E, la propriété MaxCharCount retourne 3.

public class CustomMapper : EncoderFallback


{
public string DefaultString;
internal Dictionary<ushort, ulong> mapping;

public CustomMapper() : this("*")


{
}

public CustomMapper(string defaultString)


{
this.DefaultString = defaultString;

// Create table of mappings


mapping = new Dictionary<ushort, ulong>();
mapping.Add(0x24C8, 0x53);
mapping.Add(0x2075, 0x35);
mapping.Add(0x221E, 0x49004E0046);
}

public override EncoderFallbackBuffer CreateFallbackBuffer()


{
return new CustomMapperFallbackBuffer(this);
}

public override int MaxCharCount


{
get { return 3; }
}
}
Public Class CustomMapper : Inherits EncoderFallback
Public DefaultString As String
Friend mapping As Dictionary(Of UShort, ULong)

Public Sub New()


Me.New("?")
End Sub

Public Sub New(ByVal defaultString As String)


Me.DefaultString = defaultString

' Create table of mappings


mapping = New Dictionary(Of UShort, ULong)
mapping.Add(&H24C8, &H53)
mapping.Add(&H2075, &H35)
mapping.Add(&H221E, &H49004E0046)
End Sub

Public Overrides Function CreateFallbackBuffer() As System.Text.EncoderFallbackBuffer


Return New CustomMapperFallbackBuffer(Me)
End Function

Public Overrides ReadOnly Property MaxCharCount As Integer


Get
Return 3
End Get
End Property
End Class

Le code suivant définit la classe CustomMapperFallbackBuffer , qui est dérivée de EncoderFallbackBuffer. Le


dictionnaire qui contient les mappages les mieux adaptés et qui est défini dans l'instance de CustomMapper est
disponible à partir de son constructeur de classe. Sa méthode Fallback retourne true si un des caractères
Unicode que l'encodeur ASCII ne peut pas encoder est défini dans le dictionnaire de mappage ; sinon, elle retourne
false . Pour chaque stratégie de secours, la variable privée count indique le nombre de caractères qui restent à
retourner, et la variable privée index indique la position du caractère suivant à retourner dans la mémoire tampon
de la chaîne, charsToReturn .

public class CustomMapperFallbackBuffer : EncoderFallbackBuffer


{
int count = -1; // Number of characters to return
int index = -1; // Index of character to return
CustomMapper fb;
string charsToReturn;

public CustomMapperFallbackBuffer(CustomMapper fallback)


{
this.fb = fallback;
}

public override bool Fallback(char charUnknownHigh, char charUnknownLow, int index)


{
// Do not try to map surrogates to ASCII.
return false;
}

public override bool Fallback(char charUnknown, int index)


{
// Return false if there are already characters to map.
if (count >= 1) return false;

// Determine number of characters to return.


charsToReturn = String.Empty;

ushort key = Convert.ToUInt16(charUnknown);


ushort key = Convert.ToUInt16(charUnknown);
if (fb.mapping.ContainsKey(key)) {
byte[] bytes = BitConverter.GetBytes(fb.mapping[key]);
int ctr = 0;
foreach (var byt in bytes) {
if (byt > 0) {
ctr++;
charsToReturn += (char) byt;
}
}
count = ctr;
}
else {
// Return default.
charsToReturn = fb.DefaultString;
count = 1;
}
this.index = charsToReturn.Length - 1;

return true;
}

public override char GetNextChar()


{
// We'll return a character if possible, so subtract from the count of chars to return.
count--;
// If count is less than zero, we've returned all characters.
if (count < 0)
return '\u0000';

this.index--;
return charsToReturn[this.index + 1];
}

public override bool MovePrevious()


{
// Original: if count >= -1 and pos >= 0
if (count >= -1) {
count++;
return true;
}
else {
return false;
}
}

public override int Remaining


{
get { return count < 0 ? 0 : count; }
}

public override void Reset()


{
count = -1;
index = -1;
}
}

Public Class CustomMapperFallbackBuffer : Inherits EncoderFallbackBuffer

Dim count As Integer = -1 ' Number of characters to return


Dim index As Integer = -1 ' Index of character to return
Dim fb As CustomMapper
Dim charsToReturn As String

Public Sub New(ByVal fallback As CustomMapper)


MyBase.New()
Me.fb = fallback
End Sub

Public Overloads Overrides Function Fallback(ByVal charUnknownHigh As Char, ByVal charUnknownLow As Char,
ByVal index As Integer) As Boolean
' Do not try to map surrogates to ASCII.
Return False
End Function

Public Overloads Overrides Function Fallback(ByVal charUnknown As Char, ByVal index As Integer) As Boolean
' Return false if there are already characters to map.
If count >= 1 Then Return False

' Determine number of characters to return.


charsToReturn = String.Empty

Dim key As UShort = Convert.ToUInt16(charUnknown)


If fb.mapping.ContainsKey(key) Then
Dim bytes() As Byte = BitConverter.GetBytes(fb.mapping.Item(key))
Dim ctr As Integer
For Each byt In bytes
If byt > 0 Then
ctr += 1
charsToReturn += Chr(byt)
End If
Next
count = ctr
Else
' Return default.
charsToReturn = fb.DefaultString
count = 1
End If
Me.index = charsToReturn.Length - 1

Return True
End Function

Public Overrides Function GetNextChar() As Char


' We'll return a character if possible, so subtract from the count of chars to return.
count -= 1
' If count is less than zero, we've returned all characters.
If count < 0 Then Return ChrW(0)

Me.index -= 1
Return charsToReturn(Me.index + 1)
End Function

Public Overrides Function MovePrevious() As Boolean


' Original: if count >= -1 and pos >= 0
If count >= -1 Then
count += 1
Return True
Else
Return False
End If
End Function

Public Overrides ReadOnly Property Remaining As Integer


Get
Return If(count < 0, 0, count)
End Get
End Property

Public Overrides Sub Reset()


count = -1
index = -1
End Sub
End Class
Le code suivant instancie ensuite l'objet CustomMapper et passe une instance de celui-ci à la méthode
Encoding.GetEncoding(String, EncoderFallback, DecoderFallback) . La sortie indique que l'implémentation de la
stratégie de secours la mieux adaptée gère correctement les trois caractères non-ASCII de la chaîne d'origine.

using System;
using System.Collections.Generic;
using System.Text;

class Program
{
static void Main()
{
Encoding enc = Encoding.GetEncoding("us-ascii", new CustomMapper(), new DecoderExceptionFallback());

string str1 = "\u24C8 \u2075 \u221E";


Console.WriteLine(str1);
for (int ctr = 0; ctr <= str1.Length - 1; ctr++) {
Console.Write("{0} ", Convert.ToUInt16(str1[ctr]).ToString("X4"));
if (ctr == str1.Length - 1)
Console.WriteLine();
}
Console.WriteLine();

// Encode the original string using the ASCII encoder.


byte[] bytes = enc.GetBytes(str1);
Console.Write("Encoded bytes: ");
foreach (var byt in bytes)
Console.Write("{0:X2} ", byt);

Console.WriteLine("\n");

// Decode the ASCII bytes.


string str2 = enc.GetString(bytes);
Console.WriteLine("Round-trip: {0}", str1.Equals(str2));
if (! str1.Equals(str2)) {
Console.WriteLine(str2);
foreach (var ch in str2)
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"));

Console.WriteLine();
}
}
}
Imports System.Text
Imports System.Collections.Generic

Module Module1

Sub Main()
Dim enc As Encoding = Encoding.GetEncoding("us-ascii", New CustomMapper(), New
DecoderExceptionFallback())

Dim str1 As String = String.Format("{0} {1} {2}", ChrW(&H24C8), ChrW(&H2075), ChrW(&H221E))


Console.WriteLine(str1)
For ctr As Integer = 0 To str1.Length - 1
Console.Write("{0} ", Convert.ToUInt16(str1(ctr)).ToString("X4"))
If ctr = str1.Length - 1 Then Console.WriteLine()
Next
Console.WriteLine()

' Encode the original string using the ASCII encoder.


Dim bytes() As Byte = enc.GetBytes(str1)
Console.Write("Encoded bytes: ")
For Each byt In bytes
Console.Write("{0:X2} ", byt)
Next
Console.WriteLine()
Console.WriteLine()

' Decode the ASCII bytes.


Dim str2 As String = enc.GetString(bytes)
Console.WriteLine("Round-trip: {0}", str1.Equals(str2))
If Not str1.Equals(str2) Then
Console.WriteLine(str2)
For Each ch In str2
Console.Write("{0} ", Convert.ToUInt16(ch).ToString("X4"))
Next
Console.WriteLine()
End If
End Sub
End Module

Voir aussi
Présentation de l’encodage de caractères dans .NET
Encoder
Decoder
DecoderFallback
Encoding
EncoderFallback
Globalisation et localisation
Analyse de chaînes dans .NET
18/07/2020 • 2 minutes to read • Edit Online

Une opération d’analyse convertit une chaîne qui représente un type de base .NET en ce type de base. Par
exemple, une opération d'analyse permet de convertir une chaîne en nombre à virgule flottante ou en valeur de
date et d'heure. La méthode la plus couramment utilisée pour effectuer une opération d’analyse est la méthode
Parse . Étant donné que l’analyse est l’opération inverse de la mise en forme (qui consiste à convertir un type de
base en sa représentation sous forme de chaîne), de nombreuses règles et conventions identiques s’appliquent.
Tout comme la mise en forme utilise un objet qui implémente l’interface IFormatProvider pour fournir des
informations de mise en forme dépendantes de la culture, l’analyse utilise un objet qui implémente l’interface
IFormatProvider pour déterminer comment interpréter une représentation sous forme de chaîne. Pour plus
d’informations, consultez Mise en forme des types.

Dans cette section


Analyse de chaînes numériques
Explique comment convertir des chaînes en types numériques .NET.
Analyse de chaînes de date et d’heure
Explique comment convertir des chaînes en types DateTime .NET.
Analyse d'autres chaînes
Explique comment convertir des chaînes en types Char , Boolean et Enum .

Sections connexes
Mise en forme des types
Décrit les concepts de mise en forme de base, tels que les spécificateurs de format et les fournisseurs de format.
Conversion de type dans .NET
Décrit comment convertir des types.
Analyse de chaînes numériques dans .NET
18/07/2020 • 14 minutes to read • Edit Online

Tous les types numériques disposent de deux méthodes d’analyse statiques, Parse et TryParse , que vous pouvez
utiliser pour convertir la représentation sous forme de chaîne d’un nombre en type numérique. Ces méthodes vous
permettent d’analyser les chaînes qui ont été générées à l’aide de chaînes de format documentées dans Chaînes de
format numériques standard et Chaînes de format numériques personnalisées. Par défaut, les méthodes Parse et
TryParse peuvent convertir correctement les chaînes qui contiennent uniquement des chiffres décimaux intégraux
en valeurs entières. Ils peuvent convertir correctement les chaînes qui contiennent des chiffres décimaux intégraux
et fractionnaires, des séparateurs de groupe et un séparateur décimal en valeurs à virgule flottante. La méthode
Parse lève une exception si l’opération échoue, tandis que la méthode TryParse retourne false .

Analyse et fournisseurs de format


En général, les représentations sous forme de chaîne de valeurs numériques varient selon la culture. Les éléments
des chaînes numériques, tels que les symboles monétaires, les séparateurs de groupes (ou de milliers) et les
séparateurs décimaux, varient selon la culture. Les méthodes d’analyse utilisent implicitement ou explicitement un
fournisseur de format qui identifie ces variantes spécifiques à la culture. Si aucun fournisseur de format n’est
spécifié dans un appel à la méthode Parse ou TryParse , le fournisseur de format associé à la culture du thread
actuel (l’objet NumberFormatInfo retourné par la propriété NumberFormatInfo.CurrentInfo) est utilisé.
Un fournisseur de format est représenté par une implémentation IFormatProvider. Cette interface a un seul
membre, la méthode GetFormat, dont l’unique paramètre est un objet Type qui représente le type à mettre en
forme. Cette méthode retourne l’objet qui fournit des informations de mise en forme. .NET prend en charge les
deux implémentations IFormatProvider suivantes pour analyser les chaînes numériques :
Objet CultureInfo dont la méthode CultureInfo.GetFormat retourne un objet NumberFormatInfo qui fournit
des informations de mise en forme propres à la culture.
Objet NumberFormatInfo dont la méthode NumberFormatInfo.GetFormat est retournée.
L’exemple suivant essaie de convertir chaque chaîne d’un tableau en une valeur Double. Il commence par essayer
d’analyser la chaîne à l’aide d’un fournisseur de format qui reflète les conventions de la culture anglophone (États-
Unis). Si cette opération lève une exception FormatException, il tente d’analyser la chaîne à l’aide d’un fournisseur
de format qui reflète les conventions de la culture française (France).
using System;
using System.Globalization;

public class Example


{
public static void Main()
{
string[] values = { "1,304.16", "$1,456.78", "1,094", "152",
"123,45 €", "1 304,16", "Ae9f" };
double number;
CultureInfo culture = null;

foreach (string value in values) {


try {
culture = CultureInfo.CreateSpecificCulture("en-US");
number = Double.Parse(value, culture);
Console.WriteLine("{0}: {1} --> {2}", culture.Name, value, number);
}
catch (FormatException) {
Console.WriteLine("{0}: Unable to parse '{1}'.",
culture.Name, value);
culture = CultureInfo.CreateSpecificCulture("fr-FR");
try {
number = Double.Parse(value, culture);
Console.WriteLine("{0}: {1} --> {2}", culture.Name, value, number);
}
catch (FormatException) {
Console.WriteLine("{0}: Unable to parse '{1}'.",
culture.Name, value);
}
}
Console.WriteLine();
}
}
}
// The example displays the following output:
// en-US: 1,304.16 --> 1304.16
//
// en-US: Unable to parse '$1,456.78'.
// fr-FR: Unable to parse '$1,456.78'.
//
// en-US: 1,094 --> 1094
//
// en-US: 152 --> 152
//
// en-US: Unable to parse '123,45 €'.
// fr-FR: Unable to parse '123,45 €'.
//
// en-US: Unable to parse '1 304,16'.
// fr-FR: 1 304,16 --> 1304.16
//
// en-US: Unable to parse 'Ae9f'.
// fr-FR: Unable to parse 'Ae9f'.
Imports System.Globalization

Module Example
Public Sub Main()
Dim values() As String = {"1,304.16", "$1,456.78", "1,094", "152",
"123,45 €", "1 304,16", "Ae9f"}
Dim number As Double
Dim culture As CultureInfo = Nothing

For Each value As String In values


Try
culture = CultureInfo.CreateSpecificCulture("en-US")
number = Double.Parse(value, culture)
Console.WriteLine("{0}: {1} --> {2}", culture.Name, value, number)
Catch e As FormatException
Console.WriteLine("{0}: Unable to parse '{1}'.",
culture.Name, value)
culture = CultureInfo.CreateSpecificCulture("fr-FR")
Try
number = Double.Parse(value, culture)
Console.WriteLine("{0}: {1} --> {2}", culture.Name, value, number)
Catch ex As FormatException
Console.WriteLine("{0}: Unable to parse '{1}'.",
culture.Name, value)
End Try
End Try
Console.WriteLine()
Next
End Sub
End Module
' The example displays the following output:
' en-US: 1,304.16 --> 1304.16
'
' en-US: Unable to parse '$1,456.78'.
' fr-FR: Unable to parse '$1,456.78'.
'
' en-US: 1,094 --> 1094
'
' en-US: 152 --> 152
'
' en-US: Unable to parse '123,45 €'.
' fr-FR: Unable to parse '123,45 €'.
'
' en-US: Unable to parse '1 304,16'.
' fr-FR: 1 304,16 --> 1304.16
'
' en-US: Unable to parse 'Ae9f'.
' fr-FR: Unable to parse 'Ae9f'.

Analyse et valeurs NumberStyles


Les éléments de style (tels que les espaces blancs, les séparateurs de groupe et le séparateur décimal) que
l’opération d’analyse peut gérer sont définis par une valeur d’énumération NumberStyles. Par défaut, les chaînes
qui représentent des valeurs entières sont analysées à l’aide de la valeur NumberStyles.Integer, qui autorise
uniquement les chiffres numériques, les espaces blancs de début et de fin et un signe de début. Les chaînes qui
représentent des valeurs à virgule flottante sont analysées à l’aide d’une combinaison des valeurs
NumberStyles.Float et NumberStyles.AllowThousands. Ce style composite autorise les chiffres décimaux avec un
espace blanc de début et de fin, un signe de début, un séparateur décimal, un séparateur de groupes et un
exposant. Quand vous appelez une surcharge de la méthode Parse ou TryParse qui inclut un paramètre de type
NumberStyles et que vous définissez un ou plusieurs indicateurs NumberStyles, vous pouvez contrôler les
éléments de style pouvant être présents dans la chaîne pour que l’opération d’analyse aboutisse.
Par exemple, une chaîne qui contient un séparateur de groupes ne peut pas être convertie en valeur Int32 à l’aide
de la méthode Int32.Parse(String). Toutefois, la conversion réussit si vous utilisez l’indicateur
NumberStyles.AllowThousands, comme le montre l’exemple suivant.

using System;
using System.Globalization;

public class Example


{
public static void Main()
{
string value = "1,304";
int number;
IFormatProvider provider = CultureInfo.CreateSpecificCulture("en-US");
if (Int32.TryParse(value, out number))
Console.WriteLine("{0} --> {1}", value, number);
else
Console.WriteLine("Unable to convert '{0}'", value);

if (Int32.TryParse(value, NumberStyles.Integer | NumberStyles.AllowThousands,


provider, out number))
Console.WriteLine("{0} --> {1}", value, number);
else
Console.WriteLine("Unable to convert '{0}'", value);
}
}
// The example displays the following output:
// Unable to convert '1,304'
// 1,304 --> 1304

Imports System.Globalization

Module Example
Public Sub Main()
Dim value As String = "1,304"
Dim number As Integer
Dim provider As IFormatProvider = CultureInfo.CreateSpecificCulture("en-US")
If Int32.TryParse(value, number) Then
Console.WriteLine("{0} --> {1}", value, number)
Else
Console.WriteLine("Unable to convert '{0}'", value)
End If

If Int32.TryParse(value, NumberStyles.Integer Or NumberStyles.AllowThousands,


provider, number) Then
Console.WriteLine("{0} --> {1}", value, number)
Else
Console.WriteLine("Unable to convert '{0}'", value)
End If
End Sub
End Module
' The example displays the following output:
' Unable to convert '1,304'
' 1,304 --> 1304

WARNING
L’opération d’analyse utilise toujours les conventions de mise en forme d’une culture particulière. Si vous ne spécifiez pas de
culture en passant un objet CultureInfo ou NumberFormatInfo, la culture associée au thread actuel est utilisée.

Le tableau suivant répertorie les membres de l’énumération NumberStyles et décrit l’effet qu’ils ont sur l’opération
d’analyse.

VA L EUR N UM B ERST Y L ES EF F ET SUR L A C H A ÎN E À A N A LY SER

NumberStyles.None Seuls les chiffres sont autorisés.

NumberStyles.AllowDecimalPoint Le séparateur décimal et les chiffres fractionnaires sont


autorisés. Pour les valeurs entières, le seul chiffre fractionnaire
autorisé est zéro. Les séparateurs décimaux valides sont
déterminés par la propriété
NumberFormatInfo.NumberDecimalSeparator ou
NumberFormatInfo.CurrencyDecimalSeparator.

NumberStyles.AllowExponent Les caractères « e » ou « E » peuvent être utilisés pour


indiquer une notation exponentielle. Pour plus d’informations,
consultez NumberStyles.

NumberStyles.AllowLeadingWhite L’espace blanc de début est autorisé.

NumberStyles.AllowTrailingWhite L’espace blanc de fin est autorisé.

NumberStyles.AllowLeadingSign Un signe positif ou négatif peut précéder les chiffres.

NumberStyles.AllowTrailingSign Un signe positif ou négatif peut suivre les chiffres.

NumberStyles.AllowParentheses Les parenthèses peuvent être utilisées pour indiquer des


valeurs négatives.

NumberStyles.AllowThousands Le séparateur de groupes est autorisé. Le caractère de


séparateur de groupes est déterminé par la propriété
NumberFormatInfo.NumberGroupSeparator ou
NumberFormatInfo.CurrencyGroupSeparator.

NumberStyles.AllowCurrencySymbol Le symbole monétaire est autorisé. Le symbole monétaire est


défini par la propriété NumberFormatInfo.CurrencySymbol.

NumberStyles.AllowHexSpecifier La chaîne à analyser est interprétée comme un nombre


hexadécimal. Elle peut inclure les chiffres hexadécimaux 0-9, A-
F et a-f. Cet indicateur peut être utilisé uniquement pour
analyser des valeurs entières.

En outre, l’énumération NumberStyles fournit les styles composites suivants, qui comprennent plusieurs
indicateurs NumberStyles.

VA L EUR N UM B ERST Y L ES C O M P O SIT E M EM B RES

NumberStyles.Integer Inclut les styles NumberStyles.AllowLeadingWhite,


NumberStyles.AllowTrailingWhite et
NumberStyles.AllowLeadingSign. Il s’agit du style par défaut
utilisé pour analyser des valeurs entières.

NumberStyles.Number Inclut les styles NumberStyles.AllowLeadingWhite,


NumberStyles.AllowTrailingWhite,
NumberStyles.AllowLeadingSign,
NumberStyles.AllowTrailingSign,
NumberStyles.AllowDecimalPoint et
NumberStyles.AllowThousands.
VA L EUR N UM B ERST Y L ES C O M P O SIT E M EM B RES

NumberStyles.Float Inclut les styles NumberStyles.AllowLeadingWhite,


NumberStyles.AllowTrailingWhite,
NumberStyles.AllowLeadingSign,
NumberStyles.AllowDecimalPoint et
NumberStyles.AllowExponent.

NumberStyles.Currency Inclut tous les styles sauf NumberStyles.AllowExponent et


NumberStyles.AllowHexSpecifier.

NumberStyles.Any Inclut tous les styles sauf NumberStyles.AllowHexSpecifier.

NumberStyles.HexNumber Inclut les styles NumberStyles.AllowLeadingWhite,


NumberStyles.AllowTrailingWhite et
NumberStyles.AllowHexSpecifier.

Analyse et chiffres Unicode


La norme Unicode définit des points de code pour les chiffres dans différents systèmes d’écriture. Par exemple, les
points de code U+0030 à U+0039 représentent les chiffres latins de base 0 à 9, les points de code de U+09E6 à
U+09EF représentent les chiffres bengalis compris entre 0 et 9, et les points de code U+FF10 à U+FF19
représentent les chiffres pleine chasse compris entre 0 et 9. Toutefois, les seuls chiffres identifiés par les méthodes
d’analyse sont les chiffres latins de base 0 à 9 avec des points de code compris entre U+0030 et U+0039. Si une
chaîne contenant tout autre chiffre est passée à une méthode d’analyse numérique, celle-ci lève une exception
FormatException.
L’exemple suivant utilise la méthode Int32.Parse pour analyser des chaînes composées de chiffres dans différents
systèmes d’écriture. Comme l’indique le résultat de l’exemple, la tentative d’analyse des chiffres latins de base
réussit, mais la tentative d’analyse des chiffres pleine chasse, arabe-hindi et bengali échoue.
using System;

public class Example


{
public static void Main()
{
string value;
// Define a string of basic Latin digits 1-5.
value = "\u0031\u0032\u0033\u0034\u0035";
ParseDigits(value);

// Define a string of Fullwidth digits 1-5.


value = "\uFF11\uFF12\uFF13\uFF14\uFF15";
ParseDigits(value);

// Define a string of Arabic-Indic digits 1-5.


value = "\u0661\u0662\u0663\u0664\u0665";
ParseDigits(value);

// Define a string of Bangla digits 1-5.


value = "\u09e7\u09e8\u09e9\u09ea\u09eb";
ParseDigits(value);
}

static void ParseDigits(string value)


{
try {
int number = Int32.Parse(value);
Console.WriteLine("'{0}' --> {1}", value, number);
}
catch (FormatException) {
Console.WriteLine("Unable to parse '{0}'.", value);
}
}
}
// The example displays the following output:
// '12345' --> 12345
// Unable to parse '12345'.
// Unable to parse '١٢٣٤٥'.
// Unable to parse ' '.
Module Example
Public Sub Main()
Dim value As String
' Define a string of basic Latin digits 1-5.
value = ChrW(&h31) + ChrW(&h32) + ChrW(&h33) + ChrW(&h34) + ChrW(&h35)
ParseDigits(value)

' Define a string of Fullwidth digits 1-5.


value = ChrW(&hff11) + ChrW(&hff12) + ChrW(&hff13) + ChrW(&hff14) + ChrW(&hff15)
ParseDigits(value)

' Define a string of Arabic-Indic digits 1-5.


value = ChrW(&h661) + ChrW(&h662) + ChrW(&h663) + ChrW(&h664) + ChrW(&h665)
ParseDigits(value)

' Define a string of Bangla digits 1-5.


value = ChrW(&h09e7) + ChrW(&h09e8) + ChrW(&h09e9) + ChrW(&h09ea) + ChrW(&h09eb)
ParseDigits(value)
End Sub

Sub ParseDigits(value As String)


Try
Dim number As Integer = Int32.Parse(value)
Console.WriteLine("'{0}' --> {1}", value, number)
Catch e As FormatException
Console.WriteLine("Unable to parse '{0}'.", value)
End Try
End Sub
End Module
' The example displays the following output:
' '12345' --> 12345
' Unable to parse '12345'.
' Unable to parse '١٢٣٤٥'.
' Unable to parse ' '.

Voir aussi
NumberStyles
Analyse de chaînes
Mise en forme des types
Analyser les chaînes de date et d’heure dans .NET
18/07/2020 • 13 minutes to read • Edit Online

Pour analyser des chaînes pour les convertir en objets DateTime, vous devez spécifier des informations sur la façon
dont les dates et heures sont représentées sous forme de texte. L’ordre du jour, du mois et de l’année est différent
selon les cultures. Certaines représentations de l’heure utilisent une horloge de 24 heures, d’autres spécifient
« AM » et « PM ». Certaines applications n’ont besoin que de la date. D’autres n’utilisent que l’heure. Et d’autres
encore doivent spécifier la date et l’heure. Les méthodes qui convertissent des chaînes en objets DateTime vous
permettent de fournir des informations détaillées sur les formats souhaités et les éléments d’une date et d’une
heure dont votre application a besoin. Il existe trois tâches subordonnées pour convertir correctement du texte en
DateTime :
1. Vous devez spécifier le texte qui représente une date et une heure au format attendu.
2. Vous pouvez spécifier la culture qui correspond au format de date et d’heure.
3. Vous pouvez spécifier la façon de définir les composants manquants d’une représentation de texte dans la date
et l’heure.
Les méthodes Parse et TryParse convertissent de nombreuses représentations communes de date et d’heure. Les
méthodes ParseExact et TryParseExact convertissent une représentation sous forme de chaîne conforme au modèle
spécifié par une chaîne de format de date et d’heure. (Consultez les articles sur les chaînes de format de date et
d’heure standard et les chaînes de format de date et d’heure personnalisées pour plus d’informations.)
L’objet courant DateTimeFormatInfo fournit davantage de contrôle sur la façon d’interpréter le texte comme une
date et une heure. Les propriétés d’un DateTimeFormatInfo décrivent les séparateurs de date et d’heure et les
noms de mois, de jours et de zones ainsi que le format des désignations « AM » et « PM ». La culture du thread en
cours fournit un DateTimeFormatInfo qui représente la culture actuelle. Si vous souhaitez une culture spécifique ou
des paramètres personnalisés, spécifiez le paramètre IFormatProvider d’une méthode d’analyse. Pour le paramètre
IFormatProvider, vous devez spécifier un objet CultureInfo qui représente une culture, ou un objet
DateTimeFormatInfo.
Certaines informations sur le texte représentant une date ou une heure peuvent être manquantes. Par exemple,
pour la plupart des gens, la date « mars 12 » correspond à l’année en cours. De même que « mars 2018 »
correspond au mois de mars de l’année 2018. Le texte représentant l’heure n’inclut souvent que les heures, les
minutes et une désignation AM/PM. Les méthodes d’analyse gèrent ces informations manquantes à l’aide de
valeurs par défaut raisonnables :
Lorsque seule l’heure est présente, la partie date utilise la date actuelle.
Lorsque seule la date est présente, la partie heure correspond à minuit.
Lors de l’année n’est pas spécifiée dans une date, l’année en cours est utilisée.
Lorsque le jour du mois n’est pas spécifié, le premier jour du mois est utilisé.
Si la date est présente dans la chaîne, elle doit inclure le mois et le jour ou l’année. Si l’heure est présente, elle doit
inclure l’heure et soit les minutes soit l’indicateur AM/PM.
Vous pouvez spécifier la constante NoCurrentDateDefault pour remplacer ces valeurs par défaut. Lorsque vous
utilisez cette constante, toute propriété manquante de l’année, du mois ou du jour est définie sur la valeur 1 . Le
dernier exemple qui utilise Parse illustre ce comportement.
Outre le composant de date et d’heure, la représentation sous forme de chaîne d’une date et d’une heure peut
inclure un offset qui indique le décalage horaire par rapport au temps universel coordonné (UTC, Coordinated
Universal Time). Par exemple, la chaîne « 2/14/2007 5:32:00 -7:00 » définit une heure qui est sept heures plus tôt
que l’heure UTC. Si la représentation d’une heure sous forme de chaîne n’inclut pas d’offset, l’analyse retourne un
objet DateTime dont la propriété Kind a la valeur DateTimeKind.Unspecified. Si un offset est spécifié, l’analyse
retourne un objet DateTime dont la propriété Kind a la valeur DateTimeKind.Local et dont la valeur est ajustée en
fonction du fuseau horaire local de votre ordinateur. Vous pouvez modifier ce comportement en utilisant une
valeur DateTimeStyles avec la méthode d’analyse.
Le fournisseur de format est également utilisé pour interpréter une date numérique ambiguë. Il n’est pas évident de
savoir quels composants de la date représentée par la chaîne « 02/03/04 » correspondent au mois, au jour et à
l’année. Les composants sont interprétés d’après l’ordre des formats de date similaires dans le fournisseur de
format.

Analyser
L’exemple suivant illustre l’utilisation de la méthode DateTime.Parse pour convertir une string en DateTime. Cet
exemple fait appel à la culture associée au thread actuel. Si l’objet CultureInfo associé à la culture actuelle ne peut
pas analyser la chaîne d’entrée, une exception FormatException est levée.

TIP
Tous les exemples c# de cet article s’exécutent dans votre navigateur. Appuyez sur le bouton Exécuter pour afficher la sortie.
Vous pouvez également les modifier pour vous entrainer.

NOTE
Ces exemples sont disponibles dans GitHub docs référentiel pour C# et Visual Basic.

string dateInput = "Jan 1, 2009";


var parsedDate = DateTime.Parse(dateInput);
Console.WriteLine(parsedDate);
// Displays the following output on a system whose culture is en-US:
// 1/1/2009 00:00:00

Dim MyString As String = "Jan 1, 2009"


Dim MyDateTime As DateTime = DateTime.Parse(MyString)
Console.WriteLine(MyDateTime)
' Displays the following output on a system whose culture is en-US:
' 1/1/2009 00:00:00

Vous pouvez également définir explicitement la culture dont les conventions de mise en forme sont utilisées
lorsque vous analysez une chaîne. Spécifiez l’un des objets standard DateTimeFormatInfo renvoyés par la propriété
CultureInfo.DateTimeFormat. L’exemple suivant utilise un fournisseur de format pour analyser une chaîne en
allemand dans DateTime. Il crée une CultureInfo représentant la culture de-DE . Cet objet CultureInfo garantit la
réussite de l’analyse de cette chaîne particulière. Ceci exclut tous les paramètres contenus dans CurrentCulture de
CurrentThread.

var cultureInfo = new CultureInfo("de-DE");


string dateString = "12 Juni 2008";
var dateTime = DateTime.Parse(dateString, cultureInfo);
Console.WriteLine(dateTime);
// The example displays the following output:
// 6/12/2008 00:00:00
Dim MyCultureInfo As New CultureInfo("de-DE")
Dim MyString As String = "12 Juni 2008"
Dim MyDateTime As DateTime = DateTime.Parse(MyString, MyCultureInfo)
Console.WriteLine(MyDateTime)
' The example displays the following output:
' 6/12/2008 00:00:00

Toutefois, bien que vous puissiez utiliser les surcharges de la méthode Parse pour spécifier des fournisseurs de
format personnalisé, la méthode ne prend pas en charge l’analyse de formats non standard. Pour analyser une date
et une heure exprimées dans un format non standard, utilisez à la place la méthode ParseExact.
L’exemple suivant utilise l’énumération DateTimeStyles pour spécifier que les informations de date et d’heure
actuelles ne doivent pas être ajoutées à DateTime pour les champs non spécifiés.

var cultureInfo = new CultureInfo("de-DE");


string dateString = "12 Juni 2008";
var dateTime = DateTime.Parse(dateString, cultureInfo,
DateTimeStyles.NoCurrentDateDefault);
Console.WriteLine(dateTime);
// The example displays the following output if the current culture is en-US:
// 6/12/2008 00:00:00

Dim MyCultureInfo As New CultureInfo("de-DE")


Dim MyString As String = "12 Juni 2008"
Dim MyDateTime As DateTime = DateTime.Parse(MyString, MyCultureInfo,
DateTimeStyles.NoCurrentDateDefault)
Console.WriteLine(MyDateTime)
' The example displays the following output if the current culture is en-US:
' 6/12/2008 00:00:00

ParseExact
La méthode DateTime.ParseExact convertit une chaîne en un objet DateTime si elle est conforme à l’un des modèles
de chaîne spécifiés. Quand une chaîne qui ne correspond pas aux formats spécifiés est transmise vers cette
méthode, une FormatException est levée. Vous pouvez spécifier un des spécificateurs de format standard de date et
d’heure ou une combinaison de spécificateurs de format personnalisé de date et d’heure. En utilisant les
spécificateurs de format personnalisé, vous pouvez construire une chaîne de reconnaissance personnalisée. Pour
obtenir une explication des spécificateurs, consultez les rubriques relatives aux chaînes de format de date et
d’heure standard et aux chaînes de format de date et d’heure personnalisées.
Dans l’exemple suivant, la méthode DateTime.ParseExact reçoit un objet de chaîne à analyser, suivi par un
spécificateur de format, puis un objet CultureInfo. Cette méthode ParseExact peut uniquement analyser des chaînes
qui suivent le modèle de date longue dans la culture en-US .
var cultureInfo = new CultureInfo("en-US");
string[] dateStrings = { " Friday, April 10, 2009", "Friday, April 10, 2009" };
foreach (string dateString in dateStrings)
{
try
{
var dateTime = DateTime.ParseExact(dateString, "D", cultureInfo);
Console.WriteLine(dateTime);
}
catch (FormatException)
{
Console.WriteLine("Unable to parse '{0}'", dateString);
}
}
// The example displays the following output:
// Unable to parse ' Friday, April 10, 2009'
// 4/10/2009 00:00:00

Dim MyCultureInfo As New CultureInfo("en-US")


Dim MyString() As String = {" Friday, April 10, 2009", "Friday, April 10, 2009"}
For Each dateString As String In MyString
Try
Dim MyDateTime As DateTime = DateTime.ParseExact(dateString, "D",
MyCultureInfo)
Console.WriteLine(MyDateTime)
Catch e As FormatException
Console.WriteLine("Unable to parse '{0}'", dateString)
End Try
Next
' The example displays the following output:
' Unable to parse ' Friday, April 10, 2009'
' 4/10/2009 00:00:00

Chaque surcharge des méthodes Parse et ParseExact a un paramètre IFormatProvider qui fournit des informations
spécifiques à la culture sur la mise en forme de la chaîne. Cet objet IFormatProvider est un objet CultureInfo qui
représente une culture standard ou un objet DateTimeFormatInfo qui est renvoyé par la propriété
CultureInfo.DateTimeFormat. ParseExact utilise également une chaîne supplémentaire ou un argument de tableau
de chaînes qui définit un ou plusieurs formats de date et d’heure.

Voir aussi
Analyser des chaînes
Mise en forme des types
Conversion de types dans .NET
Formats de date et d’heure standard
Chaînes de format de date et d’heure personnalisées
Analyse d’autres chaînes dans .NET
18/07/2020 • 3 minutes to read • Edit Online

Outre des chaînes numériques et DateTime, vous pouvez analyser des chaînes qui représentent les types Char,
Boolean et Enum dans des types de données.

Char
La méthode d’analyse statique associée au type de données Char est utile pour la conversion d’une chaîne qui
contient un caractère unique en une valeur Unicode. L’exemple de code suivant analyse une chaîne en un caractère
Unicode.

String^ MyString1 = "A";


char MyChar = Char::Parse(MyString1);
// MyChar now contains a Unicode "A" character.

string MyString1 = "A";


char MyChar = Char.Parse(MyString1);
// MyChar now contains a Unicode "A" character.

Dim MyString1 As String = "A"


Dim MyChar As Char = Char.Parse(MyString1)
' MyChar now contains a Unicode "A" character.

Boolean
Le type de données Boolean contient une méthode Parse que vous pouvez utiliser pour convertir une chaîne
représentant une valeur Boolean en type Boolean réel. Cette méthode ne respecte pas la casse et peut analyser
une chaîne contenant « True » ou « False ». La méthode Parse associée au type Boolean peut aussi analyser des
chaînes entourées d’espaces blancs. Si une autre chaîne est passée, une exception FormatException est levée.
L’exemple de code suivant utilise la méthode Parse pour convertir une chaîne en valeur Boolean.

String^ MyString2 = "True";


bool MyBool = bool::Parse(MyString2);
// MyBool now contains a True Boolean value.

string MyString2 = "True";


bool MyBool = bool.Parse(MyString2);
// MyBool now contains a True Boolean value.

Dim MyString2 As String = "True"


Dim MyBool As Boolean = Boolean.Parse(MyString2)
' MyBool now contains a True Boolean value.

Énumération
Vous pouvez utiliser la méthode Parse statique pour initialiser un type d’énumération avec la valeur d’une chaîne.
Cette méthode accepte le type d’énumération que vous analysez, la chaîne à analyser et un indicateur Boolean
facultatif indiquant si l’analyse respecte la casse. La chaîne que vous analysez peut contenir plusieurs valeurs
séparées par des virgules, lesquelles peuvent être précédées ou suivies d’un ou plusieurs espaces vides (aussi
appelés espaces blancs). Quand la chaîne contient plusieurs valeurs, celle de l’objet retourné est la valeur de toutes
les valeurs spécifiées, combinées avec une opération OR au niveau du bit.
L’exemple suivant utilise la méthode Parse pour convertir une représentation sous forme de chaîne en valeur
d’énumération. L’énumération DayOfWeek est initialisée à Thursday à partir d’une chaîne.

String^ MyString3 = "Thursday";


DayOfWeek MyDays = (DayOfWeek)Enum::Parse(DayOfWeek::typeid, MyString3);
Console::WriteLine(MyDays);
// The result is Thursday.

string MyString3 = "Thursday";


DayOfWeek MyDays = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), MyString3);
Console.WriteLine(MyDays);
// The result is Thursday.

Dim MyString3 As String = "Thursday"


Dim MyDays As DayOfWeek = CType([Enum].Parse(GetType(DayOfWeek), MyString3), DayOfWeek)
Console.WriteLine("{0:G}", MyDays)
' The result is Thursday.

Voir aussi
Analyse de chaînes
Mise en forme des types
Conversion de type dans .NET
Bibliothèques de classes .NET
13/05/2020 • 7 minutes to read • Edit Online

Les bibliothèques de classes représentent le concept de bibliothèque partagée pour .NET. Elles vous permettent
d’inclure des fonctionnalités utiles dans des modules utilisables par plusieurs applications. Elles peuvent également
servir à charger des fonctionnalités qui ne sont pas nécessaires ou connues au démarrage de l’application. Les
bibliothèques de classes sont décrites à l’aide du format de fichier d’assembly .NET.
Il existe trois types de bibliothèques de classes que vous pouvez utiliser :
Les bibliothèques de classes spécifiques d’une plateforme ont accès à toutes les API dans une plateforme
donnée (par exemple, .NET Framework, Xamarin iOS), mais sont utilisables uniquement par les applications et
les bibliothèques qui ciblent cette plateforme.
Les bibliothèques de classes por tables ont accès à un sous-ensemble d’API et sont utilisables par les
applications et les bibliothèques qui ciblent plusieurs plateformes.
Les bibliothèques de classes .NET Standard sont une fusion du concept de bibliothèques spécifiques d’une
plateforme et portables en un seul modèle qui offre le meilleur des deux.

Bibliothèques de classes propres à la plateforme


Les bibliothèques spécifiques d’une plateforme sont liées à une seule implémentation .NET (par exemple, .NET
Framework sur Windows) et peuvent donc accepter des dépendances significatives sur un environnement
d’exécution connu. Un tel environnement expose un ensemble connu d’API (API .NET et de système d’exploitation),
et maintient et expose l’état attendu (par exemple, le Registre Windows).
Les développeurs qui créent des bibliothèques spécifiques à la plateforme peuvent exploiter pleinement la
plateforme sous-jacente. Les bibliothèques ne s’exécutent que sur cette plateforme donnée, ce qui rend inutile les
vérifications de plateforme ou d’autres formes de code conditionnel (code d’approvisionnement modulo unique
pour plusieurs plateformes).
Les bibliothèques spécifiques d’une plateforme ont été le type de bibliothèque de classes principal du .NET
Framework. Même si d’autres implémentations .NET sont apparues, les bibliothèques spécifiques d’une plateforme
sont restées le type de bibliothèque prédominant.

Bibliothèques de classes portables


Les bibliothèques portables sont prises en charge sur plusieurs implémentations .NET. Ils peuvent toujours prendre
des dépendances sur un environnement d’exécution connu, mais l’environnement est un synthétique généré par
l’intersection d’un ensemble d’implémentations .NET concrètes. Les API exposées et les hypothèses de plateforme
sont un sous-ensemble de ce qui est disponible pour une bibliothèque spécifique à une plateforme.
Vous choisissez une configuration de plateforme quand vous créez une bibliothèque portable. La configuration de
la plateforme est l’ensemble des plateformes que vous devez prendre en charge (par exemple, .NET Framework 4.5
+, Windows Phone 8.0 +). Plus vous avez de plateformes à prendre en charge, moins vous avez d’API et moins vous
pouvez faire d’hypothèses de plateforme, selon le plus petit dénominateur commun. Cette caractéristique peut
prêter à confusion dans un premier temps, étant donné que les gens pensent souvent que « plus est mieux », mais
qu’il y a plus de plateformes prises en charge, moins il y a d’API disponibles.
De nombreux développeurs de bibliothèques ont abandonné la production de plusieurs bibliothèques spécifiques
d’une plateforme à partir d’une seule source (à l’aide de directives de compilation conditionnelle) au profit des
bibliothèques portables. Il existe plusieurs approches permettant d’accéder à une fonctionnalité spécifique d’une
plateforme dans les bibliothèques portables, bait-and-switch étant la technique la plus répandue à ce stade.

Bibliothèques de classes .NET Standard


Les bibliothèques .NET Standard remplacent les concepts de bibliothèques spécifiques d’une plateforme et
portables. Elles sont spécifiques d’une plateforme en ce sens qu’elles exposent toutes les fonctionnalités de la
plateforme sous-jacente (pas de plateforme synthétique ni d’intersection de plateformes). Elles sont portables en ce
sens qu’elles fonctionnent sur toutes les plateformes de prise en charge.
.NET Standard expose un ensemble de contrats de bibliothèque. Les implémentations .NET doivent prendre en
charge chaque contrat dans son intégralité ou pas du tout. Ainsi, chaque implémentation prend en charge un
ensemble de contrats .NET Standard. Le corollaire est que chaque bibliothèque de classes .NET Standard est prise en
charge sur les plateformes qui prennent en charge les dépendances de son contrat.
.NET Standard n’expose pas toutes les fonctionnalités du .NET Framework (ce n’est d’ailleurs pas son objectif),
toutefois, il expose bien plus d’API que les bibliothèques de classes portables. D’autres API seront ajoutées au fil du
temps.
Les plateformes suivantes prennent en charge les bibliothèques .NET Standard :
.NET Core
.NET Framework
Mono
Xamarin.iOS, Xamarin.Mac, Xamarin.Android
Plateforme Windows universelle (UWP)
Windows
Windows Phone
Windows Phone Silverlight
Pour plus d'informations, consultez .NET Standard.

Bibliothèques de classes Mono


Les bibliothèques de classes sont prises en charge sur mono, y compris les trois types de bibliothèques décrits
précédemment. Mono a souvent été vu (correctement) comme une implémentation multiplateforme de .NET
Framework. C’est en partie parce que les bibliothèques .NET Framework spécifiques d’une plateforme peuvent
s’exécuter sur le runtime Mono sans modification ou recompilation. Cette caractéristique existait avant la création
des bibliothèques de classes portables, elle était donc choisie en priorité pour permettre la portabilité binaire entre
le .NET Framework et Mono (même si cela ne fonctionnait que dans un sens).
Les analyseurs de Roslyn
18/03/2020 • 3 minutes to read • Edit Online

Les analyseurs de Roslyn utilisent le Kit de développement logiciel du compilateur .NET (API Roslyn) pour analyser
le code source de votre projet pour détecter les problèmes et suggérer des corrections. Différents analyseurs
recherchent différentes classes de problèmes, allant des pratiques susceptibles de provoquer des bogues à des
problèmes de sécurité liés à la compatibilité avec les API.
Les analyseurs de Roslyn fonctionnent à la fois et de manière interactive pendant les builds. L’analyseur guide de
différentes façons dans Visual Studio ou dans les builds de ligne de commande.
Lorsque vous modifiez le code dans Visual Studio, les analyseurs s’exécutent au moment de l’apport des
modifications, interceptant d’éventuels problèmes dès que vous créez le code qui déclenche des problèmes. Les
problèmes sont mis en surbrillance avec des tildes en-dessous. Visual Studio affiche une ampoule et lorsque vous
cliquez dessus l’analyseur propose des solutions possibles à ce problème. Lorsque vous générez le projet, dans
Visual Studio ou à partir de la ligne de commande, tout le code source est analysé et l’analyseur fournit une liste
complète des problèmes potentiels. La figure suivante illustre un exemple.

Les analyseurs de roslyn signalent des problèmes potentiels sous forme d’erreurs, d’avertissements ou
d’informations en fonction de la gravité du problème.
Vous installez des analyseurs de Roslyn sous forme de packages NuGet dans votre projet. Les analyseurs
configurés et tous les paramètres de chaque analyseur sont restaurés et s’exécutent sur l’ordinateur d’un
développeur pour ce projet.

NOTE
L’expérience utilisateur des analyseurs de Roslyn est différente de celle pour les bibliothèques d’analyse du code telles que les
versions plus anciennes de FxCop et les outils d’analyse de sécurité. Vous n’avez pas besoin exécuter explicitement les
analyseurs de Roslyn. Il est inutile d’utiliser les éléments de menu « Exécuter l’analyse du code » dans le menu « Analyser » de
Visual Studio. Les analyseurs de Roslyn s’exécutent de façon asynchrone lorsque vous travaillez.
En savoir plus sur les analyseurs spécifiques
Les analyseurs suivants sont couverts dans la présente section :
Analyseur d’API : cet analyseur examine votre code à la recherche de risques potentiels de compatibilité ou
utilisations d’API déconseillées.
Analyseur d’infrastructure : cet outil examine votre code pour s’assurer qu’il suit les instructions pour les
applications .NET Framework. Ces règles incluent plusieurs recommandations de sécurité.
.NET Portability Analyzer: cet analyseur examine votre code afin de déterminer la quantité de travail nécessaire
pour rendre votre application compatible avec d’autres profils et implémentations .NET, notamment .NET Core,
.NET Standard, UWP et Xamarin pour iOS, Android et Mac.
Analyseur d’API .NET
18/03/2020 • 10 minutes to read • Edit Online

L’analyseur d’API .NET est un analyseur Roslyn qui détecte les risques potentiels liés à la compatibilité des API C#
sur différentes plateformes, et détecte les appels à des API déconseillées. Il peut être utile à tous les développeurs
C#, quel que soit le stade du développement.
L’analyseur d’API est fourni sous la forme d’un package NuGet Microsoft.DotNet.Analyzers.Compatibility. Dès lors
que vous y faites référence dans un projet, il effectue automatiquement le monitoring du code et indique
l’utilisation d’API problématiques. Vous pouvez également obtenir des suggestions sur les corrections possibles en
cliquant sur l’ampoule. Le menu déroulant comprend une option permettant de supprimer les avertissements.

NOTE
L’analyseur d’API .NET est toujours en préversion.

Conditions préalables requises


Visual Studio 2017 et versions ultérieures, ou Visual Studio pour Mac (toutes les versions).

Découvrez les API dépréciées


Que sont les API déconseillées ?
La famille .NET est un ensemble de grands produits, qui sont constamment mis à niveau pour mieux répondre aux
besoins des clients. Il est naturel de déconseiller certaines API et de les remplacer par d’autres. Une API est
considérée comme déconseillées lorsqu’il existe une meilleure solution. Une API déconseillée, qu’il ne faut pas
utiliser, peut être marquée avec l’attribut ObsoleteAttribute. L’inconvénient de cette approche est qu’il n'existe
qu’un seul ID de diagnostic pour toutes les API obsolètes (pour C#, CS0612). Cela signifie que :
Il est impossible d’avoir un document dédié à chacun des cas.
Il n’est pas possible de supprimer certaines catégories d’avertissements. On peut soit tous les supprimer, soit
n’en supprimer aucun.
Pour informer les utilisateurs qu’une API est à présent déconseillée, il faut mettre à jour un package de ciblage
ou un assembly de référence.
L’analyseur d’API utilise des codes d’erreur propres aux API, qui commencent par DE (de l’anglais « Deprecation
Error »), ce qui permet de contrôler l’affichage des différents avertissements. Les API déconseillées identifiées par
l’analyseur sont définies dans le référentiel dotnet/plateforme-compat.
Ajouter l’analyseur API à votre projet
1. Ouvrez Visual Studio.
2. Ouvrez le projet sur qui vous souhaitez exécuter l’analyseur.
3. Dans Solution Explorer , cliquez à droite sur votre projet et choisissez Manage NuGet Packages . (Cette
option est également disponible dans le menu Projet .)
4. Sur l’onglet NuGet Package Manager :
a. Sélectionnez «nuget.org» comme source de paquet.
b. Allez à l’onglet Parcourir.
c. Sélectionnez Inclure la préversion .
d. Recherchez Microsoft.DotNet.Analyzers.Compatibility .
e. Sélectionnez ce paquet dans la liste.
f. Sélectionnez le bouton Installer .
g. Cliquez sur le bouton OK dans la boîte de dialogue Aperçu des modifications , puis sur le bouton
J’accepte dans la boîte de dialogue Acceptation de la licence si vous acceptez les termes du contrat
de licence pour les packages répertoriés.
Utilisez l’analyseur de l’API
Lorsqu’une API déconseillée, par exemple, WebClient, est utilisée dans un code, l’analyseur d’API la souligne d’un
trait vert ondulé. Lorsque vous placez le curseur sur l’appel d’API, une ampoule donne des informations sur les API
déconseillées, comme dans l’exemple suivant :

La fenêtre Liste d’erreurs contient des avertissements avec un ID unique par API déconseillée, comme dans
l’exemple suivant ( DE004 ) :

En cliquant sur l’ID, vous accédez à une page web présentant des informations détaillées sur la raison pour laquelle
l’API a été déconseillée, ainsi que des suggestions d’autres API utilisables.
Pour supprimer des avertissements, cliquez sur le membre en surbrillance et sélectionnez Supprimer <ID de
diagnostic > . Il existe deux moyens de supprimer les avertissements :
localement (dans la source) ;
globalement (dans un fichier de suppression) – recommandé.
Supprimer les avertissements localement
Pour supprimer les avertissements localement, cliquez à droite sur le membre que vous souhaitez supprimer les
avertissements pour puis sélectionner des actions rapides et des refactorings > Supprimer diagnostic**
ID<ID ID>** > dans Source . La directive du préprocesseur d’avertissement #pragma est ajoutée à votre code
source dans l’étendue définie :
Supprimer les avertissements à l’échelle mondiale
Pour supprimer les avertissements à l’échelle mondiale, cliquez à droite sur le membre que vous souhaitez
supprimer les avertissements pour puis sélectionner des actions rapides et des refactorings > Supprimer
diagnostic** ID<diagnostic ID>** > dans le fichier de suppression .

Un fichier GlobalSuppressions.cs est ajouté à votre projet après la première suppression. Les nouvelles
suppressions globales seront ajoutées à ce fichier.

La suppression globale est la méthode recommandée pour garantir une utilisation cohérente de l’API d’un projet à
l’autre.

Découvrez les enjeux interplateformes


Tout comme les API déconseillées, l’analyseur identifie toutes les API non multiplateformes. Par exemple,
Console.WindowWidth fonctionne sous Windows, mais pas sous Linux ou macOS. L’ID de diagnostic apparaît dans
la fenêtre Liste d’erreurs . Vous pouvez supprimer cet avertissement en cliquant avec le bouton droit et en
sélectionnant Actions rapides et refactorisations . Contrairement au cas des API déconseillées, dans lequel
deux options sont proposées (continuer d’utiliser le membre déconseillé et supprimer les avertissements, ou ne
pas l’utiliser du tout), ici, si vous développez votre code pour certaines plateformes seulement, vous pouvez
supprimer les avertissements de toutes les autres plateformes sur lesquelles vous n’envisagez pas d’exécuter votre
code. Il vous suffit pour cela de modifier votre fichier projet et d’ajouter la propriété PlatformCompatIgnore , qui liste
toutes les plateformes à ignorer. Les valeurs acceptées sont les suivantes : Linux , macOS et Windows .

<PropertyGroup>
<PlatformCompatIgnore>Linux;macOS</PlatformCompatIgnore>
</PropertyGroup>

Si votre code cible plusieurs plateformes et que vous souhaitez tirer parti d’une API non prise en charge sur
certaines d'entre elles, vous pouvez protéger cette partie du code avec une instruction if :

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var w = Console.WindowWidth;
// More code
}

Vous pouvez également effectuer une compilation conditionnelle par système d’exploitation/version cible de .NET
Framework ; il s’agit pour le moment d’une opération manuelle.

Diagnostics pris en charge


Actuellement, l’analyseur gère les cas suivants :
utilisation d’une API .NET Standard qui lève PlatformNotSupportedException (PC001) ;
utilisation d’une API .NET Standard non disponible sur .NET Framework 4.6.1 (PC002) ;
utilisation d’une API native qui n’existe pas dans UWP (PC003) ;
Utilisation des API Delegate.BeginInvoke et EndInvoke (PC004).
utilisation d’une API marquée comme déconseillée (DEXXXX).

Machine de CI
Tous ces diagnostics sont disponibles non seulement dans l’IDE, mais également en ligne de commande dans le
cadre de la création du projet, qui inclut le serveur de CI.

Configuration
L’utilisateur choisit le mode de traitement des diagnostics : en tant qu’avertissements, en tant qu’erreurs, en tant
que suggestions ou désactivés. Par exemple, l’architecte peut décider que les problèmes de compatibilité doivent
être traités comme des erreurs, que les appels à certaines API déconseillées génèrent des avertissements, tandis
que les autres ne produiront que des suggestions. Vous pouvez configurer cela séparément, ID de diagnostic par
ID de diagnostic et projet par projet. Pour cela, accédez au nœud Dépendances sous votre projet dans
l’Explorateur de solutions . Élargir les nœuds dépendances > Analyseurs >
Microsoft.DotNet.Analyzers.Compatibility . Cliquez avec le bouton droit sur l’ID de diagnostic, sélectionnez
Définir la gravité de l’ensemble de règles et choisissez l’option souhaitée.
Voir aussi
Billet de blog Présentation de l’analyseur d’API.
Vidéo de démonstration sur YouTube de l’analyseur d’API.
Analyseur de portabilité .NET
18/07/2020 • 10 minutes to read • Edit Online

Vous voulez que vos bibliothèques prennent en charge plusieurs plateformes ? Vous voulez savoir quelle quantité
de travail est nécessaire pour que votre application .NET Framework s’exécute sur .NET Core ? L' Analyseur de
portabilité .net est un outil qui analyse les assemblys et fournit un rapport détaillé sur les API .net qui manquent
pour que les applications ou les bibliothèques soient portables sur les plateformes .net ciblées spécifiées.
L’analyseur de portabilité est proposé sous la forme d’une extension Visual Studio, qui analyse un assembly par
projet, et en tant qu' application console ApiPort, qui analyse les assemblys par fichiers ou répertoires spécifiés.
Une fois que vous avez converti votre projet pour cibler la nouvelle plateforme, comme .NET Core, vous pouvez
utiliser l' outil Analyseur d’API basé sur Roslyn pour identifier les API levant PlatformNotSupportedException des
exceptions et d’autres problèmes de compatibilité.

Cibles courantes
.Net Core: possède une conception modulaire, prend en charge l’installation côte à côte et cible des scénarios
multiplateformes. L’installation côte à côte vous permet d’adopter de nouvelles versions de .NET Core sans
interrompre les autres applications. Si votre objectif est de porter votre application sur .NET Core et de prendre
en charge plusieurs plateformes, il s’agit de la cible recommandée.
. NET standard: comprend les API .NET standard disponibles sur toutes les implémentations .net. Si votre
objectif est de faire en sorte que votre bibliothèque s’exécute sur toutes les plates-formes prises en charge par
.NET, il s’agit de la cible recommandée.
ASP.net Core: une infrastructure Web moderne reposant sur .net core. Si votre objectif est de porter votre
application web vers .NET Core pour prendre en charge plusieurs plateformes, c’est la cible recommandée.
.NET Core + extensions de plateforme: inclut les API .net core en plus du Pack de compatibilité Windows, qui
fournit un grand nombre des .NET Framework technologies disponibles. Il s’agit d’une cible recommandée pour
le portage de votre application depuis le .NET Framework vers .NET Core sur Windows.
.NET Standard + extensions de plateforme: inclut les API .NET standard en plus du Pack de compatibilité
Windows, qui fournit une grande partie des .NET Framework technologies disponibles. Il s’agit d’une cible
recommandée pour le portage de votre bibliothèque depuis le .NET Framework vers .NET Core sur Windows.

Comment utiliser l’Analyseur de portabilité .NET


Pour commencer à utiliser l’Analyseur de portabilité .NET dans Visual Studio, vous devez d’abord télécharger
l’extension à partir du Visual Studio Marketplace et l’installer. Elle fonctionne sur Visual Studio 2017 et les versions
ultérieures. Configurez-le dans Visual Studio via analyser les > paramètres de l'Analyseur de por tabilité et
sélectionnez vos plateformes cibles, qui correspondent aux plateformes/versions .net dont vous souhaitez évaluer
les écarts de portabilité comparés à la plateforme/version avec laquelle votre assembly actuel est généré.
Vous pouvez aussi utiliser l’application console ApiPort et la télécharger à partir du référentiel ApiPort. Vous
pouvez utiliser l’option de commande listTargets pour afficher la liste des cibles disponibles, puis sélectionner
les plateformes cibles en spécifiant l’option de commande -t ou --target .
Vue étendue de la solution
Une étape utile pour analyser une solution avec de nombreux projets consiste à visualiser les dépendances pour
comprendre quel sous-ensemble d’assemblys dépend de quoi. La recommandation générale consiste à appliquer
les résultats de l’analyse dans une approche ascendante, en commençant par les nœuds terminaux dans un
graphique de dépendance.
Pour récupérer ce code, vous pouvez exécuter la commande suivante :

ApiPort.exe analyze -r DGML -f [directory or file]

Un résultat de ce type ressemble à ce qui suit quand il est ouvert dans Visual Studio :
Analyser la portabilité
Pour analyser l’ensemble de votre projet dans Visual Studio, cliquez avec le bouton droit sur votre projet dans
l’Explorateur de solutions et sélectionnez Analyser la por tabilité des assemblys . Sinon, accédez au menu
Analyser et sélectionnez Analyser la por tabilité des assemblys . À partir de là, sélectionnez l’exécutable ou la
DLL de votre projet.

Vous pouvez également utiliser l’application console ApiPort.


Tapez la commande suivante pour analyser le répertoire actif : ApiPort.exe analyze -f .
Pour analyser une liste spécifique de fichiers .dll, tapez la commande suivante :
ApiPort.exe analyze -f first.dll -f second.dll -f third.dll
Exécuter ApiPort.exe -? pour obtenir plus d’aide

Il est recommandé d’inclure tous les fichiers exe et dll associés dont vous êtes propriétaire et que vous souhaitez
porter, et d’exclure les fichiers dont dépend votre application, mais dont vous n’êtes pas propriétaire et que vous ne
pouvez pas porter. Vous obtiendrez ainsi le rapport de portabilité le plus pertinent possible.
Consulter et interpréter les résultats de portabilité
Seules les API qui ne sont pas prises en charge par une plateforme cible apparaissent dans le rapport. Après avoir
exécuté l’analyse dans Visual Studio, vous verrez s’afficher un lien vers le fichier du rapport de portabilité .NET. Si
vous avez utilisé l’application console ApiPort, votre rapport de portabilité de .NET est enregistré en tant que
fichier dans le format que vous avez spécifié. Le format par défaut est un fichier Excel (.xlsx) dans votre répertoire
actuel.
Synthèse de la portabilité

La section sur la synthèse de la portabilité du rapport montre le pourcentage de portabilité de chaque assembly
inclus lors de l’exécution. Dans l’exemple précédent, 71,24 % des API .NET Framework utilisées dans l’application
svcutil sont disponibles dans .NET Core + Extensions de plateforme. Si vous exécutez l’outil Analyseur de
portabilité .NET sur plusieurs assemblys, chaque assembly doit avoir une ligne dans le rapport de synthèse de la
portabilité.
Détails

La section Détails du rapport répertorie les API manquantes dans les plateformes ciblées sélectionnées.
Type cible : le type a une API manquante dans une plateforme cible
Membre cible : la méthode est manquante dans une plateforme cible
Nom de l’assembly : assembly .NET Framework dans lequel réside l’API manquante.
Chacune des plateformes cibles sélectionnées est une colonne, par exemple « .NET Core » : la valeur « non pris
en charge » signifie que l’API n’est pas prise en charge sur cette plateforme cible.
Modifications recommandées : l’API ou la technologie recommandée pour passer à. Actuellement, ce champ est
vide ou obsolète pour de nombreuses API. En raison du grand nombre d’API, nous avons un défi important à
rester à jour. Nous examinons d’autres solutions pour fournir des informations utiles aux clients.
Assemblys manquants

Vous trouverez peut-être une section sur les assemblys manquants dans votre rapport. Cette section contient la
liste des assemblys qui sont référencés par vos assemblys analysés et qui n’ont pas été analysés. S’il s’agit d’un
assembly dont vous êtes propriétaire, incluez-le dans l’exécution de l’analyseur de portabilité des API pour obtenir
un rapport de portabilité détaillé au niveau de l’API. S’il s’agit d’une bibliothèque tierce, vérifiez s’il existe une
version plus récente qui prend en charge votre plateforme cible et passez à la version plus récente. Finalement, la
liste doit inclure tous les assemblys tiers dont dépend votre application et qui ont une version prenant en charge
votre plateforme cible.
Pour plus d’informations sur l’Analyseur de portabilité .NET, consultez la documentation GitHub et la vidéo
Channel 9 A brief look at the .NET Portability Analyzer (Aperçu rapide de l’Analyseur de portabilité .NET).
L’analyseur .NET Framework
18/03/2020 • 10 minutes to read • Edit Online

Vous pouvez utiliser l’Analyseur .NET Framework pour rechercher les problèmes potentiels dans votre code
d’application basé sur .NET Framework. Cet analyseur recherche les problèmes potentiels et suggère des correctifs
pour les résoudre.
L’analyseur s’exécute de façon interactive dans Visual Studio au fil de l’écriture du code ou dans le cadre d’une
build CI. Il est recommandé d’ajouter l’analyseur à votre projet dès que possible dans votre développement. Plus
tôt vous trouvez les problèmes potentiels dans votre code, plus ils sont faciles à corriger. Cependant, vous pouvez
l’ajouter à tout moment au cours du cycle de développement. Il détecte les problèmes dans le code existant et
signale les problèmes au fil de votre développement.

Installation et configuration de l’Analyseur .NET Framework


Les analyseurs de cadre .NET doivent être installés comme un paquet NuGet sur chaque projet où vous voulez
qu’ils fonctionnent. Il suffit qu’un seul développeur les ajoute au projet. Le package de l’analyseur est une
dépendance de projet et il s’exécute sur la machine de chaque développeur une fois qu’il dispose de la solution
mise à jour.
L’Analyseur .NET Framework est livré dans le package NuGet Microsoft.NetFramework.Analyzers. Ce package
fournit seulement les analyseurs spécifiques à .NET Framework, qui comprend des analyseurs de sécurité. Dans la
plupart des cas, vous allez utiliser le package NuGet Microsoft.CodeAnalysis.FxCopAnalyzers. Le package
d’agrégation FxCopAnalyzers contient tous les analyseurs de framework inclus dans le package
Framework.Analyzers, ainsi que les analyseurs suivants :
Microsoft.CodeQuality.Analyzers : fournit des indications générales et des conseils pour les API .NET Standard.
Microsoft.NetCore.Analyzers : fournit des analyseurs spécifiques aux API .NET Core.
Text.Analyzers : fournit des conseils pour le texte inclus en tant que code, notamment les commentaires.
Pour l’installer, cliquez avec le bouton droit sur le projet et sélectionnez « Gérer les dépendances ». Dans
l’Explorateur NuGet, recherchez « NetFramework Analyzer » ou, si vous préférez, « Fx Cop Analyzerx ». Installez la
dernière version stable dans tous les projets de votre solution.

Utilisation de l’Analyseur .NET Framework


Une fois le package NuGet installé, générez votre solution. L’analyseur signale les éventuels problèmes qu’il trouve
dans votre code base. Les problèmes sont signalés sous forme d’avertissements dans la fenêtre Liste d’erreurs de
Visual Studio, comme le montre l’image suivante :
Au fil de l’écriture du code, vous voyez des marques ondulées sous les problèmes potentiels de votre code. Placez
le curseur sur un problème pour voir plus d’informations sur celui-ci, ainsi que des suggestions pour une
correction possible, comme le montre l’image suivante :

Les analyseurs examinent le code de votre solution et vous fournissent une liste d’avertissements pour ces
problèmes :
CA1058 : Les types ne doivent pas étendre certains types de base
Vous ne devez rien dériver directement d’un petit nombre de types du .NET Framework.
Catégorie : Conception
Sévérité: Avertissement
Informations supplémentaires : CA1058 : Les types ne doivent pas étendre certains types de base
CA2153 : Ne pas intercepter des exceptions d’état endommagé
L’interception des exceptions d’état endommagé pourrait masquer des erreurs (comme des violations d’accès),
aboutissant à un état d’exécution incohérent ou facilitant la compromission d’un système par des attaquants. Au
lieu de cela, il faut intercepter et gérer un ensemble de types d’exception plus spécifiques, ou lever à nouveau
l’exception.
Catégorie : Sécurité
Sévérité: Avertissement
Informations supplémentaires : ## CA2153 : Ne pas intercepter les exceptions d’état endommagé
CA2229 : Implémentez des constructeurs de sérialisation
L’analyseur génère cet avertissement quand vous créez un type qui implémente l’interface ISerializable, mais ne
définit pas le constructeur de sérialisation nécessaire. Pour corriger une violation de cette règle, implémentez le
constructeur de sérialisation. Dans le cas d'une classe sealed, rendez le constructeur privé ; sinon, attribuez-lui l'état
protégé. Le constructeur de sérialisation a la signature suivante :

public class MyItemType


{
// The special constructor is used to deserialize values.
public MyItemType(SerializationInfo info, StreamingContext context)
{
// implementation removed.
}
}

Catégorie : Utilisation
Sévérité: Avertissement
Informations supplémentaires : CA2229 : Implémentez des constructeurs de sérialisation
CA2235 : Marquez tous les champs non sérialisés
Un champ d'instance d'un type non sérialisable est déclaré dans un type sérialisable. Vous devez marquer
explicitement ce champ avec NonSerializedAttribute pour résoudre cet avertissement.
Catégorie : Utilisation
Sévérité: Avertissement
Informations supplémentaires : CA2235 : Marquez tous les champs non sérialisables
CA2237 : Marquez les types ISerializable comme étant sérialisables
Pour être reconnus par le Common Language Runtime comme étant sérialisables, les types doivent être marqués
avec l’attribut SerializableAttribute, même s’ils utilisent une routine de sérialisation personnalisée en implémentant
l’interface ISerializable.
Catégorie : Utilisation
Sévérité: Avertissement
Informations supplémentaires : CA2237 : Marquez les types ISerializable comme étant sérialisables
CA3075 : Traitement du DTD non sécurisé dans XML
Si vous utilisez des instances de DtdProcessing non sécurisées ou référencez des sources d’entités externes,
l’analyseur peut accepter une entrée non fiable et divulguer des informations sensibles à des personnes
malveillantes.
Catégorie : Sécurité
Sévérité: Avertissement
Informations supplémentaires : A3075 : Traitement du DTD non sécurisé dans XML
CA5350 : N’utilisez pas d’algorithmes de chiffrement faibles
Les algorithmes de chiffrement se dégradent au fil du temps, car les attaques deviennent plus sophistiquées. Selon
le type et l’application de cet algorithme de chiffrement, une dégradation progressive de sa force de chiffrement
peut permettre aux attaquants de lire des messages chiffrés, de falsifier des messages chiffrés, de falsifier des
signatures numériques, de falsifier du contenu haché ou de compromettre les systèmes de chiffrement basés sur
cet algorithme. Pour le chiffrement, utilisez un algorithme AES (AES-256, AES-192 et AES-128 sont acceptables)
avec une longueur de clé supérieure ou égale à 128 bits. Pour le hachage, utilisez une fonction de hachage de la
famille SHA-2, comme SHA-2 512, SHA-2 384 ou SHA-2 256.
Catégorie : Sécurité
Sévérité: Avertissement
Informations supplémentaires : CA5350 : N’utilisez pas d’algorithmes de chiffrement faibles
CA5351 : N’utilisez pas les algorithmes de chiffrement cassés
Il existe une attaque qui permet de casser cet algorithme par voie informatique. Ceci permet aux attaquants de
passer outre les garanties en matière de chiffrement qu’il doit normalement fournir. Selon le type et l’application
de cet algorithme de chiffrement, ceci peut permettre aux attaquants de lire des messages chiffrés, de falsifier des
messages chiffrés, de falsifier des signatures numériques, de falsifier du contenu haché ou de compromettre les
systèmes de chiffrement basés sur cet algorithme. Pour le chiffrement, utilisez un algorithme AES (AES-256, AES-
192 et AES-128 sont acceptables) avec une longueur de clé supérieure ou égale à 128 bits. Pour le hachage, utilisez
une fonction de hachage de la famille SHA-2, comme SHA512, SHA384 ou SHA256. Pour les signatures
numériques, utilisez RSA avec une longueur de clé supérieure ou égale à 2048 bits, ou ECDSA avec une longueur
de clé supérieure ou égale à 256 bits.
Catégorie : Sécurité
Sévérité: Avertissement
Informations supplémentaires : CA5351 : N’utilisez pas d’algorithmes de chiffrement cassés
Gestion et levée d’exceptions dans .NET
18/07/2020 • 5 minutes to read • Edit Online

Les applications doivent pouvoir gérer de manière cohérente les erreurs qui se produisent au moment de
l'exécution. .NET fournit un modèle pour avertir les applications de façon uniforme de la présence d’erreurs : les
opérations .NET indiquent un échec en levant des exceptions.

Exceptions
Une exception est une condition d'erreur ou un comportement inattendu rencontré par un programme en cours
d'exécution. Les exceptions peuvent être levées à cause d’une erreur dans votre code ou dans le code que vous
appelez (une bibliothèque partagée, par exemple), de ressources de système d’exploitation non disponibles, de
conditions inattendues rencontrées par le runtime (du code qui ne peut pas être vérifié, par exemple), etc. Votre
application peut récupérer suite à certaines de ces conditions, mais pas toutes. Bien que vous puissiez récupérer
suite à la plupart des exceptions d'application, vous ne pouvez pas récupérer suite à la plupart des exceptions
runtime.
Dans .NET, une exception est un objet qui hérite de la classe System.Exception. Une exception est levée à partir
d'une partie du code où un problème s'est produit. L'exception remonte la pile jusqu'à sa prise en charge par
l'application ou l'arrêt du programme.

Exceptions et méthodes traditionnelles de gestion des erreurs


Traditionnellement, le modèle de gestion des erreurs d'un langage reposait soit sur le mode unique utilisé par le
langage en question pour détecter des erreurs et leur trouver des gestionnaires appropriés, soit sur le mécanisme
de gestion des erreurs fourni par le système d'exploitation. La façon dont .NET implémente la gestion des
exceptions offre les avantages suivants :
La gestion et la levée des exceptions fonctionne de la même façon pour les langages de programmation
.NET.
ne requiert aucune syntaxe particulière pour la gestion des exceptions, mais laisse chaque langage définir
sa propre syntaxe.
Les exceptions peuvent être levées au-delà des limites des processus et même des ordinateurs.
Un code de gestion des exceptions peut être ajouté à une application pour augmenter la fiabilité du
programme.
Les exceptions offrent des avantages par rapport à d’autres méthodes de notification des erreurs, comme les
codes de retour. Les erreurs ne passent pas inaperçues, car si une exception est levée et que vous ne la gérez pas,
le runtime arrête votre application. Les valeurs non valides ne continuent pas à se propager dans le système parce
que du code n’a pas pu vérifier un code de retour d’échec.

Exceptions courantes
Le tableau suivant répertorie certaines exceptions courantes avec des exemples de cause possible.

T Y P E D'EXC EP T IO N DESC RIP T IO N EXEM P L E


T Y P E D'EXC EP T IO N DESC RIP T IO N EXEM P L E

Exception Classe de base pour toutes les Aucun (utilisez une classe dérivée de
exceptions. cette exception).

IndexOutOfRangeException Levée par le runtime uniquement en Indexation d’un tableau en dehors de


cas d’indexation incorrecte du tableau. sa plage valide :
arr[arr.Length+1]

NullReferenceException Levée par le runtime uniquement si un object o = null;


objet Null est référencé. o.ToString();

InvalidOperationException Levée par les méthodes en cas d’état Appel de Enumerator.MoveNext()


non valide. après la suppression d’un élément de la
collection sous-jacente.

ArgumentException Classe de base pour toutes les Aucun (utilisez une classe dérivée de
exceptions d’argument. cette exception).

ArgumentNullException Levée par les méthodes qui n’acceptent String s = null;


pas la valeur Null pour un argument. "Calculate".IndexOf(s);

ArgumentOutOfRangeException Levée par les méthodes qui vérifient String s = "string";


que les arguments sont inclus dans une s.Substring(s.Length+1);
plage donnée.

Voir aussi
Classe et propriétés d’exception
Guide pratique pour utiliser le bloc try/catch pour intercepter des exceptions
Comment : utiliser des exceptions spécifiques dans un bloc catch
Comment : lever explicitement des exceptions
Comment : créer des exceptions définies par l’utilisateur
Utilisation de gestionnaires filtrés par l'utilisateur
Comment : utiliser des blocs finally
Gestion des exceptions COM Interop
Meilleures pratiques pour les exceptions
What Every Dev needs to Know About Exceptions in the Runtime
Classe et propriétés d’exception
18/03/2020 • 4 minutes to read • Edit Online

La classe Exception est la classe de base dont héritent les exceptions. Par exemple, la hiérarchie de classes
InvalidCastException se présente comme suit :
Object
Exception
SystemException
InvalidCastException
La classe Exception a les propriétés suivantes qui vous permettront de mieux comprendre une exception.

N O M DE L A P RO P RIÉT É DESC RIP T IO N

Data IDictionary qui contient des données arbitraires dans des


paires clé-valeur.

HelpLink Peut contenir une URL (ou URN) vers un fichier d’aide qui
fournit des informations détaillées sur la cause d’une
exception.

InnerException Cette propriété peut être utilisée pour créer et conserver une
série d’exceptions pendant la gestion des exceptions. Vous
pouvez l’utiliser pour créer une exception qui contient des
exceptions interceptées précédemment. L’exception d’origine
peut être capturée par la deuxième exception dans la propriété
InnerException, ce qui permet au code qui gère la deuxième
exception d’examiner les informations supplémentaires. Par
exemple, supposons que vous disposez d’une méthode qui
reçoit un argument avec une mise en forme incorrecte. Le code
essaie de lire l’argument, mais une exception est levée. La
méthode intercepte l’exception et lève une exception
FormatException. Pour améliorer la capacité de l’appelant à
déterminer la raison pour laquelle une exception est levée, il
est parfois souhaitable qu’une méthode intercepte une
exception levée par une routine d’assistance, puis qu’elle lève
une exception plus évocatrice de l’erreur qui s’est produite.
Une exception plus significative peut être créée, dans laquelle
la référence à l’exception interne peut être définie sur
l’exception d’origine. Cette exception plus significative peut
ensuite être levée pour l’appelant. Notez que cette
fonctionnalité vous permet de créer une série d’exceptions
liées qui se termine avec l’exception initialement levée.

Message Fournit les détails de la cause d’une exception.

Source Obtient ou définit le nom de l'application ou de l'objet qui est


à l'origine de l'erreur.

StackTrace Contient une trace de pile qui peut être utilisée pour
déterminer où une erreur s’est produite. La trace de la pile
comprend le nom du fichier source et le numéro de ligne du
programme si les informations de débogage sont disponibles.
La plupart des classes qui héritent de Exception n’implémentent pas de membres supplémentaires ni ne
fournissent de fonctionnalités supplémentaires, elles héritent simplement de Exception. Par conséquent, vous
pouvez trouver les informations les plus importantes d’une exception dans la hiérarchie des classes d’exception, le
nom de l’exception et les informations contenues dans l’exception.
Nous vous recommandons de lever et d’intercepter uniquement des objets qui dérivent de Exception, mais vous
pouvez lever comme exception n’importe quel objet qui dérive de la classe Object. Notez que tous les langages ne
prennent pas forcément en charge la levée et l’interception d’objets qui ne dérivent pas de Exception.

Voir aussi
Exceptions
Guide pratique pour utiliser le bloc try/catch pour
intercepter des exceptions
18/07/2020 • 3 minutes to read • Edit Online

Placez les instructions de code qui peuvent déclencher ou lever une exception dans un bloc try et placez les
instructions utilisées pour gérer l’exception ou les exceptions dans un ou plusieurs blocs catch sous le bloc try .
Chaque bloc catch inclut le type d’exception et peut contenir des instructions supplémentaires nécessaires pour
gérer ce type d’exception.
Dans l’exemple suivant, un StreamReader ouvre un fichier appelé data.txt et récupère une ligne de ce fichier. Étant
donné que le code peut lever une des trois exceptions, il est placé dans un bloc try . Trois blocs catch interceptent
les exceptions et les gèrent en affichant les résultats dans la console.

using System;
using System.IO;

public class ProcessFile


{
public static void Main()
{
try
{
using (StreamReader sr = File.OpenText("data.txt"))
{
Console.WriteLine($"The first line of this file is {sr.ReadLine()}");
}
}
catch (FileNotFoundException e)
{
Console.WriteLine($"The file was not found: '{e}'");
}
catch (DirectoryNotFoundException e)
{
Console.WriteLine($"The directory was not found: '{e}'");
}
catch (IOException e)
{
Console.WriteLine($"The file could not be opened: '{e}'");
}
}
}
Imports System.IO

Public Class ProcessFile


Public Shared Sub Main()
Try
Using sr As StreamReader = File.OpenText("data.txt")
Console.WriteLine($"The first line of this file is {sr.ReadLine()}")
End Using
Catch e As FileNotFoundException
Console.WriteLine($"The file was not found: '{e}'")
Catch e As DirectoryNotFoundException
Console.WriteLine($"The directory was not found: '{e}'")
Catch e As IOException
Console.WriteLine($"The file could not be opened: '{e}'")
End Try
End Sub
End Class

Le Common Language Runtime (CLR) intercepte les exceptions non gérées par les blocs catch . Si une exception
est interceptée par le CLR, il peut se produire l’un des résultats suivants selon la configuration du CLR :
Une boîte de dialogue Débogage s’affiche.
Le programme s’arrête et une boîte de dialogue d’information sur l’exception s’affiche.
Une erreur s’affiche dans le flux de sortie d’erreur standard.

NOTE
La plupart du code peut lever une exception, et quelques exceptions comme OutOfMemoryException peuvent être levées
par le CLR lui-même à tout moment. L’utilisation d’applications n’est pas obligatoire pour traiter ces exceptions, mais
envisagez cette possibilité quand vous écrivez des bibliothèques destinées à l’usage d’autres utilisateurs. Si vous souhaitez
des conseils sur la définition de code dans un bloc try , consultez Bonnes pratiques pour les exceptions.

Voir aussi
Exceptions
Gestion des erreurs E/S dans .NET
Utiliser des exceptions spécifiques dans un bloc Catch
18/03/2020 • 3 minutes to read • Edit Online

En général, il est conseillé en programmation d’intercepter un type d’exception spécifique au lieu d’utiliser une
instruction catch de base.
Quand une exception se produit, elle remonte la pile et chaque bloc catch a la possibilité de la gérer. L’ordre des
instructions catch est important. Placez les blocs catch ciblés sur des exceptions spécifiques avant un bloc catch
d’exceptions générales, sinon le compilateur peut générer une erreur. Le bloc catch approprié est déterminé en
mettant en correspondance le type de l’exception avec le nom de l’exception spécifiée dans le bloc catch. S’il n’existe
aucun bloc catch spécifique, l’exception est interceptée par un bloc catch général, le cas échéant.
L’exemple de code suivant utilise un bloc try / catch pour intercepter une exception InvalidCastException.
L’exemple crée une classe appelée Employee avec une propriété unique, le niveau de l’employé ( Emlevel ). Une
méthode PromoteEmployee prend un objet et incrémente le niveau de l’employé. Une exception
InvalidCastException se produit quand une instance DateTime est passée à la méthode PromoteEmployee .
using namespace System;

public ref class Employee


{
public:
Employee()
{
emlevel = 0;
}

//Create employee level property.


property int Emlevel
{
int get()
{
return emlevel;
}
void set(int value)
{
emlevel = value;
}
}

private:
int emlevel;
};

public ref class Ex13


{
public:
static void PromoteEmployee(Object^ emp)
{
//Cast object to Employee.
Employee^ e = (Employee^) emp;
// Increment employee level.
e->Emlevel++;
}

static void Main()


{
try
{
Object^ o = gcnew Employee();
DateTime^ newyears = gcnew DateTime(2001, 1, 1);
//Promote the new employee.
PromoteEmployee(o);
//Promote DateTime; results in InvalidCastException as newyears is not an employee instance.
PromoteEmployee(newyears);
}
catch (InvalidCastException^ e)
{
Console::WriteLine("Error passing data to PromoteEmployee method. " + e->Message);
}
}
};

int main()
{
Ex13::Main();
}
using System;

public class Employee


{
//Create employee level property.
public int Emlevel
{
get
{
return(emlevel);
}
set
{
emlevel = value;
}
}

private int emlevel = 0;


}

public class Ex13


{
public static void PromoteEmployee(Object emp)
{
// Cast object to Employee.
var e = (Employee) emp;
// Increment employee level.
e.Emlevel = e.Emlevel + 1;
}

public static void Main()


{
try
{
Object o = new Employee();
DateTime newYears = new DateTime(2001, 1, 1);
// Promote the new employee.
PromoteEmployee(o);
// Promote DateTime; results in InvalidCastException as newYears is not an employee instance.
PromoteEmployee(newYears);
}
catch (InvalidCastException e)
{
Console.WriteLine("Error passing data to PromoteEmployee method. " + e.Message);
}
}
}
Public Class Employee
'Create employee level property.
Public Property Emlevel As Integer
Get
Return emlevelValue
End Get
Set
emlevelValue = Value
End Set
End Property

Private emlevelValue As Integer = 0


End Class

Public Class Ex13


Public Shared Sub PromoteEmployee(emp As Object)
' Cast object to Employee.
Dim e As Employee = CType(emp, Employee)
' Increment employee level.
e.Emlevel = e.Emlevel + 1
End Sub

Public Shared Sub Main()


Try
Dim o As Object = New Employee()
Dim newYears As New DateTime(2001, 1, 1)
' Promote the new employee.
PromoteEmployee(o)
' Promote DateTime; results in InvalidCastException as newYears is not an employee instance.
PromoteEmployee(newYears)
Catch e As InvalidCastException
Console.WriteLine("Error passing data to PromoteEmployee method. " + e.Message)
End Try
End Sub
End Class

Voir aussi
Exceptions
Guide pratique pour lever explicitement des
exceptions
18/07/2020 • 2 minutes to read • Edit Online

Vous pouvez lever explicitement une exception à l’aide de l' throw instruction C# ou Visual Basic Throw . Vous
pouvez aussi lever de nouveau une exception interceptée à l’aide de l’instruction throw . En codage, il est conseillé
d’ajouter des informations à une exception levée une deuxième fois pour fournir plus d’informations durant le
débogage.
L’exemple de code suivant utilise un bloc try / catch pour intercepter une exception FileNotFoundException
possible. À la suite du bloc try , un bloc catch intercepte l’exception FileNotFoundException et écrit un message
dans la console si le fichier de données est introuvable. L’instruction suivante est throw , qui lève une nouvelle
exception FileNotFoundException et ajoute des informations de texte à l’exception.

using System;
using System.IO;

public class ProcessFile


{
public static void Main()
{
FileStream fs;
try
{
// Opens a text tile.
fs = new FileStream(@"C:\temp\data.txt", FileMode.Open);
var sr = new StreamReader(fs);

// A value is read from the file and output to the console.


string line = sr.ReadLine();
Console.WriteLine(line);
}
catch(FileNotFoundException e)
{
Console.WriteLine($"[Data File Missing] {e}");
throw new FileNotFoundException(@"[data.txt not in c:\temp directory]", e);
}
finally
{
if (fs != null)
fs.Close();
}
}
}
Option Strict On

Imports System.IO

Public Class ProcessFile

Public Shared Sub Main()


Dim fs As FileStream
Try
' Opens a text file.
fs = New FileStream("c:\temp\data.txt", FileMode.Open)
Dim sr As New StreamReader(fs)

' A value is read from the file and output to the console.
Dim line As String = sr.ReadLine()
Console.WriteLine(line)
Catch e As FileNotFoundException
Console.WriteLine($"[Data File Missing] {e}")
Throw New FileNotFoundException("[data.txt not in c:\temp directory]", e)
Finally
If fs IsNot Nothing Then fs.Close()
End Try
End Sub
End Class

Voir aussi
Exceptions
Guide pratique pour créer des exceptions définies par
l’utilisateur
18/07/2020 • 2 minutes to read • Edit Online

.NET fournit une hiérarchie de classes d’exception dérivées de la classe de base Exception. Toutefois, si aucune
exception prédéfinie ne répond à vos besoins, vous pouvez créer vos propres classes d’exception en les dérivant de
la classe Exception.
Quand vous créez vos propres exceptions, terminez le nom de la classe définie par l’utilisateur par le mot
« Exception » et implémentez les trois constructeurs communs, comme indiqué dans l’exemple suivant. L’exemple
définit une nouvelle classe d’exception nommée EmployeeListNotFoundException . La classe est dérivée de l’exception
Exception et inclut trois constructeurs.

using namespace System;

public ref class EmployeeListNotFoundException : Exception


{
public:
EmployeeListNotFoundException()
{
}

EmployeeListNotFoundException(String^ message)
: Exception(message)
{
}

EmployeeListNotFoundException(String^ message, Exception^ inner)


: Exception(message, inner)
{
}
};

using System;

public class EmployeeListNotFoundException : Exception


{
public EmployeeListNotFoundException()
{
}

public EmployeeListNotFoundException(string message)


: base(message)
{
}

public EmployeeListNotFoundException(string message, Exception inner)


: base(message, inner)
{
}
}
Public Class EmployeeListNotFoundException
Inherits Exception

Public Sub New()


End Sub

Public Sub New(message As String)


MyBase.New(message)
End Sub

Public Sub New(message As String, inner As Exception)


MyBase.New(message, inner)
End Sub
End Class

NOTE
Dans les situations où vous utilisez la communication à distance, vous devez vérifier que les métadonnées des exceptions
définies par l’utilisateur sont disponibles sur le serveur (appelé) et le client (objet proxy ou appelant). Pour plus
d’informations, consultez les Bonnes pratiques pour les exceptions.

Voir aussi
Exceptions
Comment créer des exceptions définies par
l’utilisateur avec des messages d’exception localisés
18/07/2020 • 5 minutes to read • Edit Online

Dans cet article, vous allez apprendre à créer des exceptions définies par l’utilisateur qui sont héritées de la classe
de base Exception avec des messages d’exception localisés à l’aide d’assemblys satellites.

Créer des exceptions personnalisées


.NET contient de nombreuses exceptions que vous pouvez utiliser. Toutefois, dans certains cas, si aucune d’entre
elles ne répond à vos besoins, vous pouvez créer vos propres exceptions personnalisées.
Supposons que vous souhaitiez créer un StudentNotFoundException qui contient une StudentName propriété. Pour
créer une exception personnalisée, procédez comme suit :
1. Créez une classe sérialisable qui hérite de Exception . Le nom de la classe doit se terminer par « exception » :

[Serializable]
public class StudentNotFoundException : Exception { }

<Serializable>
Public Class StudentNotFoundException
Inherits Exception
End Class

2. Ajoutez les constructeurs par défaut :

[Serializable]
public class StudentNotFoundException : Exception
{
public StudentNotFoundException() { }

public StudentNotFoundException(string message)


: base(message) { }

public StudentNotFoundException(string message, Exception inner)


: base(message, inner) { }
}
<Serializable>
Public Class StudentNotFoundException
Inherits Exception

Public Sub New()


End Sub

Public Sub New(message As String)


MyBase.New(message)
End Sub

Public Sub New(message As String, inner As Exception)


MyBase.New(message, inner)
End Sub
End Class

3. Définissez les propriétés et constructeurs supplémentaires suivants :

[Serializable]
public class StudentNotFoundException : Exception
{
public string StudentName { get; }

public StudentNotFoundException() { }

public StudentNotFoundException(string message)


: base(message) { }

public StudentNotFoundException(string message, Exception inner)


: base(message, inner) { }

public StudentNotFoundException(string message, string studentName)


: this(message)
{
StudentName = studentName;
}
}

<Serializable>
Public Class StudentNotFoundException
Inherits Exception

Public ReadOnly Property StudentName As String

Public Sub New()


End Sub

Public Sub New(message As String)


MyBase.New(message)
End Sub

Public Sub New(message As String, inner As Exception)


MyBase.New(message, inner)
End Sub

Public Sub New(message As String, studentName As String)


Me.New(message)
StudentName = studentName
End Sub
End Class

Créer des messages d’exception localisés


Vous avez créé une exception personnalisée et vous pouvez la lever n’importe où avec du code similaire à ce qui
suit :

throw new StudentNotFoundException("The student cannot be found.", "John");

Throw New StudentNotFoundException("The student cannot be found.", "John")

Le problème avec la ligne précédente est qu' "The student cannot be found." il s’agit simplement d’une chaîne
constante. Dans une application localisée, vous souhaitez avoir des messages différents en fonction de la culture de
l’utilisateur. Les assemblys satellites sont un bon moyen de le faire. Un assembly satellite est un fichier. dll qui
contient des ressources pour une langue spécifique. Lorsque vous demandez des ressources spécifiques au
moment de l’exécution, le CLR trouve cette ressource en fonction de la culture de l’utilisateur. Si aucun assembly
satellite n’est trouvé pour cette culture, les ressources de la culture par défaut sont utilisées.
Pour créer les messages d’exception localisés :
1. Créez un dossier nommé Resources pour contenir les fichiers de ressources.
2. Ajoutez-lui un nouveau fichier de ressources. Pour ce faire, dans Visual Studio, cliquez avec le bouton droit
sur le dossier dans Explorateur de solutions , puis sélectionnez Ajouter > un nouveau > fichier de
ressources d’élément. Nommez le fichier ExceptionMessages. resx. Il s’agit du fichier de ressources par
défaut.
3. Ajoutez une paire nom/valeur pour votre message d’exception, comme dans l’illustration suivante :

4. Ajoutez un nouveau fichier de ressources pour le français. Nommez-le ExceptionMessages.fr-fr. resx.


5. Ajoutez une nouvelle paire nom/valeur pour le message d’exception, mais avec une valeur en français :

6. Une fois le projet généré, le dossier de sortie de la génération doit contenir le dossier fr-fr avec un fichier . dll
, qui est l’assembly satellite.
7. Vous levez l’exception à l’aide d’un code semblable au suivant :

var resourceManager = new ResourceManager("FULLY_QUALIFIED_NAME_OF_RESOURCE_FILE",


Assembly.GetExecutingAssembly());
throw new StudentNotFoundException(resourceManager.GetString("StudentNotFound"), "John");

Dim resourceManager As New ResourceManager("FULLY_QUALIFIED_NAME_OF_RESOURCE_FILE",


Assembly.GetExecutingAssembly())
Throw New StudentNotFoundException(resourceManager.GetString("StudentNotFound"), "John")
NOTE
Si le nom du projet est TestProject et que le fichier de ressources ExceptionMessages. resx se trouve dans le
dossier ressources du projet, le nom qualifié complet du fichier de ressources est
TestProject.Resources.ExceptionMessages .

Voir aussi
Guide pratique pour créer des exceptions définies par l’utilisateur
Création d'assemblys satellites pour les applications bureautiques
base (référence C#)
this (référence C#)
Guide pratique pour utiliser des blocs finally
18/03/2020 • 2 minutes to read • Edit Online

Quand une exception se produit, l’exécution s’arrête et le contrôle est donné au gestionnaire d’exceptions approprié.
Cela signifie souvent que les lignes de code qui doivent être exécutées sont ignorées. Un nettoyage des ressources,
comme la fermeture d’un fichier, doit être effectué même si une exception est levée. Pour ce faire, vous pouvez
utiliser un bloc finally . Un bloc finally s’exécute toujours, qu’une exception soit levée ou non.
L’exemple de code suivant utilise un bloc try / catch pour intercepter une exception
ArgumentOutOfRangeException. La méthode Main crée deux tableaux et tente de copier l’un dans l’autre. Cette
action génère une exception ArgumentOutOfRangeException et l’erreur est écrite dans la console. Le bloc finally
s’exécute indépendamment du résultat de l’action de copie.

using namespace System;

ref class ArgumentOutOfRangeExample


{
public:
static void Main()
{
array<int>^ array1 = {0, 0};
array<int>^ array2 = {0, 0};

try
{
Array::Copy(array1, array2, -1);
}
catch (ArgumentOutOfRangeException^ e)
{
Console::WriteLine("Error: {0}", e);
throw;
}
finally
{
Console::WriteLine("This statement is always executed.");
}
}
};

int main()
{
ArgumentOutOfRangeExample::Main();
}
using System;

class ArgumentOutOfRangeExample
{
public static void Main()
{
int[] array1 = {0, 0};
int[] array2 = {0, 0};

try
{
Array.Copy(array1, array2, -1);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine("Error: {0}", e);
throw;
}
finally
{
Console.WriteLine("This statement is always executed.");
}
}
}

Class ArgumentOutOfRangeExample
Public Shared Sub Main()
Dim array1() As Integer = {0, 0}
Dim array2() As Integer = {0, 0}

Try
Array.Copy(array1, array2, -1)
Catch e As ArgumentOutOfRangeException
Console.WriteLine("Error: {0}", e)
Throw
Finally
Console.WriteLine("This statement is always executed.")
End Try
End Sub
End Class

Voir aussi
Exceptions
Utilisation de gestionnaires filtrés par l'utilisateur
18/03/2020 • 2 minutes to read • Edit Online

Visual Basic prend actuellement en charge les exceptions filtrées par l’utilisateur. Les gestionnaires d’exceptions
filtrés par l’utilisateur interceptent et gèrent les exceptions selon des critères que vous définissez pour l’exception.
Ces gestionnaires utilisent l’instruction Catch avec le mot clé When .
Cette technique est utile lorsqu’un objet d’exception particulier correspond à plusieurs erreurs. Dans ce cas, l’objet
possède généralement une propriété qui contient le code d’erreur associé à l’erreur. Vous pouvez utiliser la
propriété de code d’erreur dans l’expression pour sélectionner uniquement l’erreur particulière que vous souhaitez
gérer dans cette clause Catch .
L’exemple Visual Basic suivant illustre l’instruction Catch/When .

Try
'Try statements.
Catch When Err = VBErr_ClassLoadException
'Catch statements.
End Try

L’expression de la clause filtrée par l’utilisateur n’est pas limitée. Si une exception se produit pendant l’exécution de
l’expression filtrée par l’utilisateur, cette exception est ignorée et l’expression filtrée est considérée comme ayant la
valeur False. Dans ce cas, le common language runtime continue la recherche d’un gestionnaire pour l’exception
actuelle.

Combinaison de l’exception spécifique et des clauses filtrées par


l’utilisateur
Une instruction catch peut contenir à la fois l’exception spécifique et les clauses filtrées par l’utilisateur. Le runtime
teste tout d’abord l’exception spécifique. Si l’exception spécifique réussit, le runtime exécute le filtre de l’utilisateur.
Le filtre générique peut contenir une référence à la variable déclarée dans le filtre de la classe. Notez que l’ordre des
deux clauses de filtre ne peut pas être inversé.
L’exemple Visual Basic suivant illustre l’exception spécifique ClassLoadException dans l’instruction Catch ainsi que
la clause filtrée par l’utilisateur à l’aide du mot clé When .

Try
'Try statements.
Catch cle As ClassLoadException When cle.IsRecoverable()
'Catch statements.
End Try

Voir aussi
Exceptions
Gestion des exceptions COM Interop
18/07/2020 • 2 minutes to read • Edit Online

Le code managé et le code non managé peuvent collaborer pour gérer les exceptions. Si une méthode lève une
exception dans du code managé, le common language runtime peut passer HRESULT à un objet COM. Si une
méthode échoue dans du code non managé en retournant un échec HRESULT, le runtime lève une exception qui
peut être interceptée par du code managé.
Le runtime mappe automatiquement la valeur HRESULT de COM Interop à des exceptions plus spécifiques. Par
exemple, E_ACCESSDENIED devient UnauthorizedAccessException, E_OUTOFMEMORY devient
OutOfMemoryException, etc.
Si la valeur HRESULT est un résultat personnalisé ou si elle est inconnue du runtime, le runtime passe une
exception générique COMException au client. La propriété ErrorCode de la COMException contient la valeur
HRESULT.

Utilisation d'IErrorInfo
Quand une erreur est passée de COM à du code managé, le runtime renseigne l'objet exception avec les
informations de l'erreur. Les objets COM qui prennent en charge IErrorInfo et qui retournent des valeurs HRESULT
fournissent ces informations aux exceptions du code managé. Par exemple, le runtime mappe la description de
l'erreur COM à la propriété Message de l'exception. Si la valeur HRESULT ne fournit pas d'informations d'erreur
supplémentaires, le runtime renseigne un grand nombre de propriétés de l'exception avec des valeurs par défaut.
Si une méthode échoue dans du code non managé, une exception peut être passée à un segment de code managé.
La rubrique Valeurs HRESULT et exceptions contient un tableau montrant comment les valeurs HRESULT sont
mappées aux objets exception du runtime.

Voir aussi
Exceptions
Bonnes pratiques pour les exceptions
18/07/2020 • 17 minutes to read • Edit Online

Une application bien conçue gère les exceptions et les erreurs pour empêcher les incidents d'applications. Cette
section présente les bonnes pratiques pour la gestion et la création des exceptions.

Utiliser des blocs try/catch/finally pour procéder à une récupération


après des erreurs ou libérer des ressources
Utilisez des blocs try / catch autour du code susceptible de générer une exception et votre code peut récupérer
suite à cette exception. Dans les blocs catch , veillez à toujours classer les exceptions de la plus dérivée à la moins
dérivée. Toutes les exceptions dérivent de Exception. Les exceptions les plus dérivées ne sont pas gérées par une
clause catch qui est précédée d’une clause catch pour une classe d’exception de base. Quand votre code ne peut
pas récupérer suite à une exception, n’interceptez pas cette exception. Activez des méthodes un peu plus haut
dans la pile d’appels pour récupérer si possible.
Nettoyez les ressources allouées avec des instructions using ou des blocs finally . Préférez les instructions
using pour nettoyer automatiquement les ressources quand des exceptions sont levées. Utilisez des blocs
finally pour nettoyer les ressources qui n’implémentent pas IDisposable. Le code dans une clause finally est
presque toujours exécuté même lorsque des exceptions sont levées.

Gérer les conditions courantes sans lever d’exception


En ce qui concerne les conditions susceptibles de se produire, mais pouvant déclencher une exception, gérez-les
de façon à éviter l’exception. Par exemple, si vous essayez de fermer une connexion déjà fermée, vous obtenez une
exception InvalidOperationException . Vous pouvez l’éviter avec une instruction if qui permet de vérifier l’état
de la connexion avant d’essayer de la fermer.

if (conn->State != ConnectionState::Closed)
{
conn->Close();
}

if (conn.State != ConnectionState.Closed)
{
conn.Close();
}

If conn.State <> ConnectionState.Closed Then


conn.Close()
End IF

Si vous ne vérifiez pas l’état de la connexion avant de la fermer, vous pouvez intercepter l’exception
InvalidOperationException .
try
{
conn->Close();
}
catch (InvalidOperationException^ ex)
{
Console::WriteLine(ex->GetType()->FullName);
Console::WriteLine(ex->Message);
}

try
{
conn.Close();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.GetType().FullName);
Console.WriteLine(ex.Message);
}

Try
conn.Close()
Catch ex As InvalidOperationException
Console.WriteLine(ex.GetType().FullName)
Console.WriteLine(ex.Message)
End Try

Le choix de la méthode dépend de la fréquence à laquelle l’événement doit se produire.


Utilisez la gestion des exceptions si l'événement ne se produit pas très souvent, c'est-à-dire, si l'événement
est véritablement exceptionnel et indique une erreur (telle qu'une fin de fichier inattendue). Lorsque vous
utilisez la gestion des exceptions, la quantité de code exécutée en situation normale est moindre.
Recherchez les conditions d’erreur dans le code si l’événement se produit régulièrement et peut être
considéré comme faisant partie de l’exécution normale. Quand vous recherchez les conditions d’erreur
courantes, vous exécutez moins de code, car vous évitez les exceptions.

Concevoir des classes pour éviter les exceptions


Une classe peut fournir des méthodes ou propriétés qui vous permettent d’éviter d’effectuer un appel susceptible
de déclencher une exception. Par exemple, une classe FileStream fournit des méthodes qui permettent de
déterminer si la fin du fichier a été atteinte. Ces méthodes peuvent servir à éviter l’exception qui est levée si vous
dépassez la fin du fichier pendant la lecture. L’exemple suivant montre comment lire un fichier jusqu’à la fin sans
lever d’exception.
class FileRead
{
public:
void ReadAll(FileStream^ fileToRead)
{
// This if statement is optional
// as it is very unlikely that
// the stream would ever be null.
if (fileToRead == nullptr)
{
throw gcnew System::ArgumentNullException();
}

int b;

// Set the stream position to the beginning of the file.


fileToRead->Seek(0, SeekOrigin::Begin);

// Read each byte to the end of the file.


for (int i = 0; i < fileToRead->Length; i++)
{
b = fileToRead->ReadByte();
Console::Write(b.ToString());
// Or do something else with the byte.
}
}
};

class FileRead
{
public void ReadAll(FileStream fileToRead)
{
// This if statement is optional
// as it is very unlikely that
// the stream would ever be null.
if (fileToRead == null)
{
throw new ArgumentNullException();
}

int b;

// Set the stream position to the beginning of the file.


fileToRead.Seek(0, SeekOrigin.Begin);

// Read each byte to the end of the file.


for (int i = 0; i < fileToRead.Length; i++)
{
b = fileToRead.ReadByte();
Console.Write(b.ToString());
// Or do something else with the byte.
}
}
}
Class FileRead
Public Sub ReadAll(fileToRead As FileStream)
' This if statement is optional
' as it is very unlikely that
' the stream would ever be null.
If fileToRead Is Nothing Then
Throw New System.ArgumentNullException()
End If

Dim b As Integer

' Set the stream position to the beginning of the file.


fileToRead.Seek(0, SeekOrigin.Begin)

' Read each byte to the end of the file.


For i As Integer = 0 To fileToRead.Length - 1
b = fileToRead.ReadByte()
Console.Write(b.ToString())
' Or do something else with the byte.
Next i
End Sub
End Class

Un autre moyen d’éviter les exceptions est de retourner Null (ou une valeur par défaut) pour les cas d’erreur très
répandus au lieu de lever une exception. Un cas d'erreur très répandu peut être considéré comme un flux de
contrôle normal. En retournant null (ou une valeur par défaut) dans ces cas-là, vous réduisez l'impact sur les
performances d'une application.
Pour les types valeur, s’il faut utiliser Nullable<T> ou une valeur par défaut comme indicateur d’erreur est quelque
chose à prendre en compte pour votre application particulière. À l’aide de Nullable<Guid> , default devient null
au lieu de Guid.Empty . Parfois, l’ajout de Nullable<T> peut éclaircir les choses, lorsqu’une valeur est présente ou
absente. Autres fois, l’ajout de Nullable<T> peut créer des cas supplémentaires qui ne sont pas nécessaires et
uniquement servir pour créer les sources potentielles d’erreurs.

Lever des exceptions au lieu de retourner un code d’erreur


Les exceptions font en sorte que les échecs ne passent pas inaperçus parce que l’appel du code n’a pas vérifié le
code de retour.

Utiliser les types d’exception .NET prédéfinis


N'introduisez une nouvelle classe d'exception que quand aucune classe d'exception prédéfinie ne s'applique. Par
exemple :
Levez une exception InvalidOperationException si un appel de jeu de propriétés ou de méthode n'est pas
approprié étant donné l'état actuel de l'objet.
Levez une exception ArgumentException ou l’une des classes prédéfinies qui dérivent de
ArgumentException si des paramètres non valides sont passés.

Terminer les noms de classes d’exception par le mot Exception


Quand une exception personnalisée est nécessaire, nommez-la de manière appropriée et dérivez-la de la classe
Exception. Par exemple :
public ref class MyFileNotFoundException : public Exception
{
};

public class MyFileNotFoundException : Exception


{
}

Public Class MyFileNotFoundException


Inherits Exception
End Class

Inclure trois constructeurs dans des classes d’exception personnalisées


Utilisez au moins les trois constructeurs communs pendant la création de vos propres classes d’exception : le
constructeur sans paramètre, un constructeur qui prend un message de type chaîne et un constructeur qui prend
un message de type chaîne et une exception interne.
Exception(), qui utilise les valeurs par défaut.
Exception(String), qui accepte un message de type chaîne.
Exception(String, Exception), qui accepte un message de type chaîne et une exception interne.
Pour obtenir un exemple, consultez Guide pratique : créer des exceptions définies par l’utilisateur.

Vérifier que les données d’exception sont disponibles quand le code


s’exécute à distance
Quand vous créez des exceptions définies par l’utilisateur, vous devez vérifier que les métadonnées des exceptions
sont disponibles pour le code qui s’exécute à distance.
Par exemple, sur les implémentations .NET qui prennent en charge des domaines d’application, des exceptions
peuvent se produire entre domaines d’application. Supposons que le domaine d’application A crée le domaine
d’application B, lequel exécute du code qui lève une exception. Pour que le domaine d’application A intercepte et
gère l’exception correctement, il doit pouvoir trouver l’assembly qui contient l’exception levée par le domaine
d’application B. Si le domaine d’application B lève une exception qui est contenue dans un assembly sous sa base
d’application, mais pas sous la base d’application du domaine d’application A, le domaine d’application A ne peut
pas trouver l’exception et le Common Language Runtime lève une exception FileNotFoundException. Pour éviter
cette situation, vous pouvez déployer l'assembly qui contient les informations sur les exceptions de deux façons :
Placez l'assembly dans une base d'application commune partagée par les deux domaines d'application.
- ou -
Si les domaines ne partagent pas une base d'application commune, signez l'assembly qui contient les
informations sur les exceptions à l'aide d'un nom fort et déployez l'assembly dans le Global Assembly
Cache.

Utiliser des messages d’erreur grammaticalement corrects


Écrivez des phrases claires et insérez une ponctuation finale. Chaque phrase de la chaîne affectée à la propriété
Exception.Message doit se terminer par un point. Par exemple, « La table du journal a débordé. » est une chaîne de
message appropriée.
Inclure une chaîne de message localisée dans chaque exception
Le message d’erreur que l’utilisateur voit est dérivé de la propriété Exception.Message de l’exception qui a été
levée, et non pas du nom de la classe d’exception. En règle générale, vous affectez une valeur à la propriété
Exception.Message en passant la chaîne de message à l’argument message d’un constructeur d’exception.
Pour les applications localisées, vous devez fournir une chaîne de message localisée pour chaque exception que
votre application peut lever. Vous utilisez des fichiers de ressources pour fournir les messages d’erreur localisés.
Pour plus d’informations sur la localisation d’applications et la récupération de chaînes localisées, consultez les
articles suivants :
Guide pratique : créer des exceptions définies par l’utilisateur avec des messages d’exception localisés
Ressources dans les applications de bureau
System.Resources.ResourceManager

Dans les exceptions personnalisées, fournir des propriétés


supplémentaires si nécessaire
Spécifiez des propriétés supplémentaires (en plus de la chaîne de message personnalisée) pour une exception
seulement dans le cas d’un scénario du programme où les informations supplémentaires sont utiles. Par exemple,
la classe FileNotFoundException fournit la propriété FileName.

Placer des instructions throw pour que la trace de la pile soit utile
La trace de la pile commence à l'instruction où l'exception est levée et se termine à l'instruction catch qui
intercepte l'exception.

Utiliser des méthodes de générateur d’exceptions


Il est fréquent qu'une classe lève la même exception à partir de différents endroits de son implémentation. Pour
éviter un excès de code, utilisez des méthodes d'assistance qui créent une exception et la retournent. Par exemple :
ref class FileReader
{
private:
String^ fileName;

public:
FileReader(String^ path)
{
fileName = path;
}

array<Byte>^ Read(int bytes)


{
array<Byte>^ results = FileUtils::ReadFromFile(fileName, bytes);
if (results == nullptr)
{
throw NewFileIOException();
}
return results;
}

FileReaderException^ NewFileIOException()
{
String^ description = "My NewFileIOException Description";

return gcnew FileReaderException(description);


}
};

class FileReader
{
private string fileName;

public FileReader(string path)


{
fileName = path;
}

public byte[] Read(int bytes)


{
byte[] results = FileUtils.ReadFromFile(fileName, bytes);
if (results == null)
{
throw NewFileIOException();
}
return results;
}

FileReaderException NewFileIOException()
{
string description = "My NewFileIOException Description";

return new FileReaderException(description);


}
}
Class FileReader
Private fileName As String

Public Sub New(path As String)


fileName = path
End Sub

Public Function Read(bytes As Integer) As Byte()


Dim results() As Byte = FileUtils.ReadFromFile(fileName, bytes)
If results Is Nothing
Throw NewFileIOException()
End If
Return results
End Function

Function NewFileIOException() As FileReaderException


Dim description As String = "My NewFileIOException Description"

Return New FileReaderException(description)


End Function
End Class

Dans certains cas, il est plus approprié d’utiliser le constructeur de l’exception pour générer l’exception. Par
exemple, une classe d’exception globale comme ArgumentException.

Restaurer l’état quand les méthodes ne sont pas exécutées en raison


d’exceptions
Les appelants doivent supposer qu'il n'y a aucun effet secondaire quand une exception est levée à partir d'une
méthode. Par exemple, si vous avez du code qui transfère de l’argent en le retirant d’un compte pour le déposer
dans un autre, et qu’une exception est levée pendant l’exécution du transfert, vous ne voulez pas que le retrait
reste en vigueur.

public void TransferFunds(Account from, Account to, decimal amount)


{
from.Withdrawal(amount);
// If the deposit fails, the withdrawal shouldn't remain in effect.
to.Deposit(amount);
}

Public Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)


from.Withdrawal(amount)
' If the deposit fails, the withdrawal shouldn't remain in effect.
[to].Deposit(amount)
End Sub

La méthode ci-dessus ne lève pas directement d’exceptions, mais doit être écrite avec précaution afin d’inverser le
retrait si l’opération de dépôt échoue.
Pour gérer cette situation, vous pouvez intercepter toutes les exceptions levées par la transaction de dépôt et
annuler le retrait.
private static void TransferFunds(Account from, Account to, decimal amount)
{
string withdrawalTrxID = from.Withdrawal(amount);
try
{
to.Deposit(amount);
}
catch
{
from.RollbackTransaction(withdrawalTrxID);
throw;
}
}

Private Shared Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)


Dim withdrawalTrxID As String = from.Withdrawal(amount)
Try
[to].Deposit(amount)
Catch
from.RollbackTransaction(withdrawalTrxID)
Throw
End Try
End Sub

Cet exemple illustre l’utilisation de throw pour lever de nouveau l’exception d’origine, ce qui peut permettre aux
appelants de voir plus facilement la véritable cause du problème sans avoir à examiner la propriété
InnerException. Vous pouvez aussi lever une nouvelle exception et inclure l’exception d’origine comme exception
interne :

catch (Exception ex)


{
from.RollbackTransaction(withdrawalTrxID);
throw new TransferFundsException("Withdrawal failed.", innerException: ex)
{
From = from,
To = to,
Amount = amount
};
}

Catch ex As Exception
from.RollbackTransaction(withdrawalTrxID)
Throw New TransferFundsException("Withdrawal failed.", innerException:=ex) With
{
.From = from,
.[To] = [to],
.Amount = amount
}
End Try

Voir aussi
Exceptions
Assemblys dans .NET
18/07/2020 • 14 minutes to read • Edit Online

Les assemblys constituent les unités fondamentales de déploiement, de contrôle de version, de réutilisation, de
portée d’activation et d’autorisations de sécurité pour. Applications basées sur le réseau. Un assembly est une
collection de types et de ressources conçus pour opérer ensemble et former une unité logique de fonctionnalité.
Les assemblys prennent la forme de fichiers exécutables (. exe) ou de bibliothèques de liens dynamiques (. dll), et
sont les blocs de construction des applications .net. Ils fournissent au Common Language Runtime les informations
dont il a besoin pour connaître les implémentations de type.
Dans .NET Core et .NET Framework, vous pouvez créer un assembly à partir d’un ou de plusieurs fichiers de code
source. Dans .NET Framework, les assemblys peuvent contenir un ou plusieurs modules. Cela permet de planifier
des projets plus volumineux de manière à ce que plusieurs développeurs puissent travailler sur des modules ou
des fichiers de code source distincts, qui sont combinés pour créer un assembly unique. Pour plus d’informations
sur les modules, consultez Comment : générer un assembly multifichier.
Les assemblys ont les propriétés suivantes :
Les assemblys sont implémentés en tant que fichiers. exe ou . dll .
Pour les bibliothèques qui ciblent le .NET Framework, vous pouvez partager des assemblys entre les
applications en les plaçant dans le global assembly cache (GAC). Vous devez nommer les assemblys avec
nom fort avant de pouvoir les inclure dans le GAC. Pour plus d’informations, consultez assemblys avec nom
fort.
Les assemblys sont chargés en mémoire uniquement s’ils sont requis. S’ils ne sont pas utilisés, ils ne sont
pas chargés. Cela signifie que les assemblys peuvent être un moyen efficace pour gérer les ressources dans
les grands projets.
Vous pouvez obtenir par programme des informations sur un assembly à l’aide de la réflexion. Pour plus
d’informations, consultez Réflexion (C#) ou Réflexion (Visual Basic).
Vous pouvez charger un assembly juste pour l’inspecter à l’aide de la MetadataLoadContext classe dans .net
Core et des Assembly.ReflectionOnlyLoad Assembly.ReflectionOnlyLoadFrom méthodes ou dans .net Core et
.NET Framework.

Assemblys dans le common language runtime


Les assemblys fournissent le common language runtime avec les informations dont il a besoin pour connaître les
implémentations de type. Pour le runtime, un type n'existe pas en dehors du contexte d'un assembly.
Un assembly définit les informations suivantes :
Code que l’common language runtime exécute. Notez que chaque assembly ne peut avoir qu’un seul point
d’entrée : DllMain , WinMain ou Main .
Limite de sécurité. Un assembly est l'unité au niveau de laquelle les autorisations sont demandées et
octroyées. Pour plus d’informations sur les limites de sécurité dans les assemblys, consultez Considérations
sur la sécuritédes assemblys.
Limite de type. L'identité de chaque type inclut le nom de l'assembly dans lequel il réside. Un type appelé
MyType chargé dans l'étendue d'un assembly n'est pas le même qu'un type appelé MyType chargé dans
l'étendue d'un autre assembly.
Limite d’étendue de référence. Le manifeste d’assembly a des métadonnées qui sont utilisées pour résoudre
les types et satisfaire les demandes de ressources. Le manifeste spécifie les types et les ressources à exposer
en dehors de l’assembly, et énumère les autres assemblys dont il dépend. Le code MSIL (Microsoft
Intermediate Language) dans un fichier exécutable portable (PE) ne sera pas exécuté à moins qu’il n’ait un
manifeste d’assemblyassocié.
Limite de version. L’assembly est la plus petite unité de versionable dans le common language runtime. Tous
les types et toutes les ressources dans le même assembly sont associés à une version en tant qu’unité. Le
manifeste d’assembly décrit les dépendances de version que vous spécifiez pour tous les assemblys
dépendants. Pour plus d’informations sur le contrôle de version, consultez contrôle de version des
assemblys.
Unité de déploiement. Quand une application démarre, seuls les assemblys qu'elle appelle initialement
doivent être présents. D’autres assemblys, tels que les assemblys contenant des ressources de localisation
ou des classes utilitaires, peuvent être récupérés à la demande. Cela permet aux applications d’être simples
et légères lors de leur premier téléchargement. Pour plus d’informations sur le déploiement d’assemblys,
consultez déployer des applications.
Unité d’exécution côte à côte. Pour plus d’informations sur l’exécution de plusieurs versions d’un assembly,
consultez assemblys et exécution côte à côte.

Créer un assembly
Les assemblys peuvent être statiques ou dynamiques. Les assemblys statiques sont stockés sur le disque dans des
fichiers exécutables portables (PE). Les assemblys statiques peuvent inclure des interfaces, des classes et des
ressources telles que des bitmaps, des fichiers JPEG et d’autres fichiers de ressources. Vous pouvez également
créer des assemblys dynamiques, qui sont exécutés directement à partir de la mémoire et ne sont pas enregistrés
sur le disque avant l’exécution. Vous pouvez enregistrer des assemblys dynamiques sur le disque une fois qu'ils ont
été exécutés.
Pour créer des assemblys, différentes possibilités s'offrent à vous. Vous pouvez utiliser des outils de
développement, tels que Visual Studio, qui peuvent créer des fichiers . dll ou. exe . Vous pouvez utiliser les outils de
la SDK Windows pour créer des assemblys avec des modules à partir d’autres environnements de développement.
Vous pouvez également utiliser les API du Common Language Runtime, comme System.Reflection.Emit, pour créer
des assemblys dynamiques.
Compilez les assemblys en les générant dans Visual Studio, en les créant avec des outils d’interface de ligne de
commande .NET Core ou en générant des assemblys .NET Framework avec un compilateur de ligne de commande.
Pour plus d’informations sur la génération d’assemblys à l’aide de CLI .NET Core, consultez CLI .net Core vue
d’ensemble. Pour générer des assemblys avec les compilateurs de ligne de commande, consultez génération à
partir de la ligne de commande avec CSC. exe pour C#, ou créez à partir de la ligne de commande pour Visual
Basic.

NOTE
Pour générer un assembly dans Visual Studio, dans le menu générer , sélectionnez générer .

Manifeste d'assembly
Chaque assembly possède un fichier manifeste de l' assembly . À l’instar d’une table des matières, le manifeste de
l’assembly contient les éléments suivants :
L’identité de l’assembly (nom et version).
Une table de fichiers décrivant tous les autres fichiers qui composent l’assembly, tels que les autres
assemblys que vous avez créés et sur lesquels votre fichier . exe ou . dll s’appuie, des fichiers bitmap ou des
fichiers Lisez-moi.
Une liste de références d’assembly, qui est une liste de toutes les dépendances externes, telles que les
fichiers . dllou autres. Les références de l’assembly contiennent des références aux objets globaux et privés.
Les objets globaux sont disponibles pour toutes les autres applications. Dans .NET Core, les objets globaux
sont associés à un Runtime .NET Core particulier. Dans .NET Framework, les objets globaux résident dans le
Global Assembly Cache (GAC). System. IO. dll est un exemple d’assembly dans le GAC. Les objets privés
doivent se trouver dans un répertoire au niveau ou au-dessous du répertoire dans lequel votre application
est installée.
Étant donné que les assemblys contiennent des informations sur le contenu, le contrôle de version et les
dépendances, les applications qui les utilisent ne s’appuient pas sur des sources externes, telles que le registre sur
les systèmes Windows, pour fonctionner correctement. Les assemblys réduisent les conflits de dll et rendent vos
applications plus fiables et plus faciles à déployer. Dans de nombreux cas, vous pouvez installer une application
.NET simplement en copiant ses fichiers sur l’ordinateur cible. Pour plus d’informations, consultez manifeste de l'
assembly.

Ajouter une référence à un assembly


Pour utiliser un assembly dans une application, vous devez lui ajouter une référence. Une fois qu’un assembly est
référencé, tous les types, propriétés, méthodes et autres membres accessibles de ses espaces de noms sont
disponibles pour votre application comme si leur code faisaient partie de votre fichier source.

NOTE
La plupart des assemblys à partir de la bibliothèque de classes .NET sont référencés automatiquement. Si un assembly
système n’est pas automatiquement référencé, pour .NET Core, vous pouvez ajouter une référence au package NuGet qui
contient l’assembly. Utilisez le gestionnaire de package NuGet dans Visual Studio ou ajoutez un <PackageReference> élément
pour l’assembly au projet . csproj ou . vbproj . Dans .NET Framework, vous pouvez ajouter une référence à l’assembly à l’aide
de la boîte de dialogue Ajouter une référence dans Visual Studio, ou à l’aide -reference de l’option de ligne de
commande pour les compilateurs C# ou Visual Basic .

En C#, vous pouvez utiliser deux versions du même assembly dans une même application. Pour plus
d’informations, consultez extern alias.

Contenu connexe
IN T IT UL É DESC RIP T IO N

Contenu d’assembly Éléments qui composent un assembly.

Manifeste d’assembly Les données dans le manifeste de l’assembly et la manière


dont elles sont stockées dans les assemblys.

Global assembly cache Comment le GAC stocke et utilise les assemblys.

Assemblys avec nom fort Caractéristiques des assemblys avec nom fort.

Aspects de la sécurité des assemblys Fonctionnement de la sécurité avec les assemblys.

Contrôle de version des assemblys Vue d’ensemble de la stratégie de contrôle de version de .NET
Framework.
IN T IT UL É DESC RIP T IO N

Emplacement d’assembly Où trouver les assemblys.

Assemblys et exécution côte à côte Utilisez plusieurs versions du runtime ou d’un assembly
simultanément.

Envoyer des assemblys et des méthodes dynamiques Comment créer des assemblys dynamiques.

Comment le runtime localise les assemblys Comment le .NET Framework résout les références d’assembly
au moment de l’exécution.

Référence
System.Reflection.Assembly

Voir aussi
Format de fichier d’assembly .NET
Assemblys friend
Assemblys de référence
Comment : charger et décharger des assemblys
Comment : utiliser et déboguer l’inchargement d’assembly dans .NET Core
Comment : déterminer si un fichier est un assembly
Comment : inspecter le contenu d’un assembly à l’aide de MetadataLoadContext
Nettoyage de ressources non managées
18/07/2020 • 4 minutes to read • Edit Online

Pour la majorité des objets créés par votre application, vous pouvez vous appuyer sur le garbage collector .net
pour gérer la gestion de la mémoire. Toutefois, lorsque vous créez des objets qui incluent des ressources non
managées, vous devez libérer explicitement ces ressources une fois que vous avez fini de les utiliser. Les types les
plus courants de ressources non managées sont des objets qui encapsulent les ressources du système
d’exploitation, telles que les fichiers, les fenêtres, les connexions réseau ou les connexions de base de données. Bien
que le récupérateur de mémoire puisse assurer le suivi de la durée de vie d'un objet qui encapsule une ressource
non managée, il ne sait pas comment libérer et nettoyer la ressource non managée.
Si vos types utilisent les ressources non managées, procédez comme suit :
Implémentez le modèle de suppression. Pour cela, vous devez fournir une IDisposable.Dispose
implémentation pour activer la version déterministe des ressources non managées. Un consommateur de
votre type appelle Dispose lorsque l’objet (et les ressources qu’il utilise) n’est plus nécessaire. La méthode
Dispose libère immédiatement les ressources non managées.
Dans le cas où un consommateur de votre type oublie d’appeler Dispose , vous pouvez libérer vos
ressources non managées. Il existe deux façons d'effectuer cette opération :
Utilisez un handle sécurisé pour encapsuler votre ressource non managée. Il s’agit de la technique
recommandée. Les Handles sécurisés sont dérivés de la System.Runtime.InteropServices.SafeHandle
classe abstraite et incluent une Finalize méthode robuste. Lorsque vous utilisez un handle sécurisé,
implémentez simplement l'interface IDisposable et appelez la méthode Dispose de votre handle
sécurisé dans l'implémentation de IDisposable.Dispose. Le finaliseur du handle sécurisé est appelé
automatiquement par le récupérateur de mémoire si sa méthode Dispose n'est pas appelé.
—ou —
Remplacez la méthode Object.Finalize . La finalisation active la mise en production non déterministe
des ressources non managées lorsque le consommateur d’un type n’appelle pas la méthode
IDisposable.Dispose pour les supprimer de manière déterministe. Définissez un finaliseur en
substituant la Object.Finalize méthode.

WARNING
Toutefois, étant donné que la finalisation de l’objet peut être une opération complexe et sujette aux erreurs, nous
vous recommandons d’utiliser un handle sécurisé au lieu de fournir votre propre finaliseur.

Les consommateurs de votre type peuvent ensuite appeler directement votre implémentation de
IDisposable.Dispose pour libérer la mémoire utilisée par les ressources non managées. Lorsque vous implémentez
correctement une méthode Dispose, la méthode Finalize de votre handle sécurisé ou votre propre substitution de
la méthode Object.Finalize devient un dispositif de protection pour nettoyer les ressources si la méthode Dispose
n'est pas appelée.

Dans cette section


Implémentation d’une méthode dispose décrit comment implémenter le modèle de suppression pour libérer des
ressources non managées.
Utilisation d’objets qui IDisposable implémentent décrit comment les consommateurs d’un type vérifient que son
Dispose implémentation est appelée. using Pour ce faire, nous vous recommandons d’utiliser l’instruction C# (ou
l’Visual Basic Using ).

Informations de référence
T Y P E/ M EM B RE DESC RIP T IO N

System.IDisposable Définit la méthode Dispose pour libérer des ressources non


managées.

Object.Finalize Prévoit la finalisation de l’objet si les ressources non managées


ne sont pas libérées par la méthode Dispose.

GC.SuppressFinalize Supprime la finalisation. Cette méthode est généralement


appelée à partir d'une méthode Dispose pour empêcher un
finaliseur de s'exécuter.
Implémenter une méthode Dispose
18/07/2020 • 24 minutes to read • Edit Online

L’implémentation de la Dispose méthode est principalement destinée à libérer des ressources non managées
utilisées par votre code. Lors de l’utilisation de membres d’instance qui sont des IDisposable implémentations, il
est courant d’appeler en cascade Dispose . Il existe des raisons supplémentaires pour l’implémentation de
Dispose , telles que l’annulation d’une opération précédemment effectuée. Par exemple, la libération de la
mémoire qui a été allouée, la suppression d’un élément d’une collection qui a été ajoutée, le signalement de la
libération d’un verrou acquis, et ainsi de suite.
Le garbage collector .net n’alloue pas ou ne libère pas de mémoire non managée. Le modèle de suppression d’un
objet, appelé modèle de suppression, impose un ordre sur la durée de vie d’un objet. Le modèle de suppression
est utilisé pour les objets qui implémentent l' IDisposable interface et est courant lors de l’interaction avec les
handles de fichiers et de canaux, les handles de Registre, les handles d’attente ou les pointeurs vers des blocs de
mémoire non managée. Cela est dû au fait que le garbage collector ne peut pas récupérer les objets non
managés.
Pour garantir que les ressources sont toujours nettoyées de manière appropriée, une Dispose méthode doit être
idempotent, de sorte qu’elle peut être appelée plusieurs fois sans lever d’exception. En outre, les appels suivants
de Dispose ne doivent rien faire.
L’exemple de code fourni pour la GC.KeepAlive méthode montre comment garbage collection peut provoquer
l’exécution d’un finaliseur, alors qu’une référence non managée à l’objet ou à ses membres est toujours en cours
d’utilisation. Il peut être judicieux d’utiliser GC.KeepAlive pour rendre l’objet inéligible pour garbage collection du
début de la routine actuelle jusqu’au point où cette méthode est appelée.

Handles sécurisés
L’écriture de code pour le finaliseur d’un objet est une tâche complexe qui peut provoquer des problèmes si elle
n’est pas effectuée correctement. Par conséquent, nous vous recommandons de construire des objets
System.Runtime.InteropServices.SafeHandle au lieu d'implémenter un finaliseur.
Un System.Runtime.InteropServices.SafeHandle est un type managé abstrait qui encapsule un System.IntPtr qui
identifie une ressource non managée. Sur Windows, il peut identifier un handle sur UNIX, un descripteur de
fichier. Il fournit toute la logique nécessaire pour s’assurer que cette ressource est libérée une seule fois, lorsque
la SafeHandle est supprimée de ou lorsque toutes les références à SafeHandle ont été supprimées et que l'
SafeHandle instance est finalisée.

System.Runtime.InteropServices.SafeHandleEst une classe de base abstraite. Les classes dérivées fournissent des
instances spécifiques pour différents genres de handles. Ces classes dérivées valident les valeurs de qui
System.IntPtr sont considérées comme non valides et comment libérer le handle. Par exemple, SafeFileHandle
dérive de SafeHandle à Wrap IntPtrs qui identifient les descripteurs/descripteurs de fichiers ouverts et
substitue sa SafeHandle.ReleaseHandle() méthode pour le fermer (via la close fonction sur UNIX ou
CloseHandle Function sur Windows). La plupart des API dans les bibliothèques .NET qui créent une ressource
non managée l’encapsulent dans un SafeHandle et le renvoient en SafeHandle fonction des besoins, plutôt que
de transmettre le pointeur brut. Dans les situations où vous interagissez avec un composant non managé et
récupérez un IntPtr pour une ressource non managée, vous pouvez créer votre propre SafeHandle type pour
l’encapsuler. Par conséquent, peu de types n' SafeHandle ont pas besoin d’implémenter des finaliseurs ; la plupart
des implémentations de modèle jetables finissent par encapsuler d’autres ressources managées, dont certaines
peuvent être des SafeHandle s.
Les classes dérivées suivantes de l'espace de noms Microsoft.Win32.SafeHandles fournissent des handles
sécurisés :
La classe SafeFileHandle, SafeMemoryMappedFileHandle et SafePipeHandle, pour les fichiers, les fichiers
mappés en mémoire et les canaux.
La classe SafeMemoryMappedViewHandle, pour les vues de la mémoire.
Les classes SafeNCryptKeyHandle, SafeNCryptProviderHandle et SafeNCryptSecretHandle pour les
constructions de chiffrement.
La classe SafeRegistryHandle pour les clés de Registre.
La classe SafeWaitHandle, pour les handles d'attente.

Dispose () et dispose (bool)


L'interface IDisposable requiert l'implémentation d'une méthode unique sans paramètre, Dispose. En outre, toute
classe non scellée doit avoir une Dispose(bool) méthode de surcharge supplémentaire à implémenter :
Implémentation non virtuelle (
public NonInheritable en Visual Basic) IDisposable.Dispose qui n’a aucun
paramètre.
protected virtual Méthode ( Overridable en Visual Basic) Dispose dont la signature est :

protected virtual void Dispose(bool disposing)


{
}

Protected Overridable Sub Dispose(disposing As Boolean)


End Sub

IMPORTANT
Le disposing paramètre doit être false lorsqu’il est appelé à partir d’un finaliseur, et lorsqu’il est true appelé
à partir de la IDisposable.Dispose méthode. En d’autres termes, il est quand il est appelé de façon true
déterministe et lorsqu’il n’est false pas appelé de manière déterministe.

La méthode Dispose ()
Étant donné que le public , non virtuel ( NonInheritable en Visual Basic), la Dispose méthode sans paramètre
est appelée par un consommateur du type, son objectif est de libérer des ressources non managées, d’effectuer
un nettoyage général et d’indiquer que le finaliseur, s’il en existe un, ne doit pas s’exécuter. La libération de la
mémoire réelle associée à un objet géré est toujours le domaine du garbage collector. De ce fait, son
implémentation standard est la suivante :

public void Dispose()


{
// Dispose of unmanaged resources.
Dispose(true);
// Suppress finalization.
GC.SuppressFinalize(this);
}
Public Sub Dispose() _
Implements IDisposable.Dispose
' Dispose of unmanaged resources.
Dispose(True)
' Suppress finalization.
GC.SuppressFinalize(Me)
End Sub

La méthode Dispose effectue le nettoyage de tous les objets, le récupérateur de mémoire n'a plus donc besoin
d'appeler la remplacement de Object.Finalize des objets. Par conséquent, l'appel à la méthode SuppressFinalize
empêche le récupérateur de mémoire d'exécuter le finaliseur. Si le type n'a pas de finaliseur, l'appel à
GC.SuppressFinalize n'a aucun effet. Notez que le nettoyage réel est effectué par la Dispose(bool) surcharge de
méthode.
Surcharge de la méthode Dispose (bool)
Dans la surcharge, le disposing paramètre est un Boolean qui indique si l’appel de méthode provient d’une
Dispose méthode (sa valeur est true ) ou d’un finaliseur (sa valeur est false ).
Le corps de la méthode se compose de deux blocs de code :
Un bloc qui libère les ressources non managées. Ce bloc s'exécute indépendamment de la valeur du
paramètre disposing .
Un bloc conditionnel qui libère les ressources managées. Ce bloc s'exécute si la valeur de disposing est
true . Les ressources managées qu'il libère peuvent inclure :

Objets managés qui implémentent IDisposable . Le bloc conditionnel peut être utilisé pour
appeler leur Dispose implémentation (dispose en cascade). Si vous avez utilisé une classe dérivée
de System.Runtime.InteropServices.SafeHandle pour encapsuler votre ressource non managée,
vous devez appeler l' SafeHandle.Dispose() implémentation ici.
Objets managés qui consomment de grandes quantités de mémoire ou consomment
des ressources rares. Affectez des références d’objets managés volumineux à null pour les
rendre plus susceptibles d’être inaccessibles. Cela les libère plus rapidement que s’ils étaient
récupérés de façon non déterministe.
Si l’appel de méthode provient d’un finaliseur, seul le code qui libère les ressources non managées doit s’exécuter.
L’implémenteur est chargé de s’assurer que le chemin d’accès faux n’interagit pas avec les objets managés qui
ont pu être récupérés. Cela est important, car l’ordre dans lequel le garbage collector détruit les objets managés
pendant la finalisation n’est pas déterministe.

Appels de suppression en cascade


Si votre classe possède un champ ou une propriété et que son type implémente IDisposable , la classe conteneur
elle-même doit également implémenter IDisposable . Une classe qui instancie une IDisposable implémentation et
la stocke en tant que membre d’instance est également responsable de son nettoyage. Cela permet de s’assurer
que les types jetables référencés ont la possibilité de procéder à un nettoyage déterministe à l’aide de la Dispose
méthode. Dans cet exemple, la classe est sealed (ou NotInheritable dans Visual Basic).
public sealed class Foo : IDisposable
{
private readonly IDisposable _bar;

public Foo()
{
_bar = new Bar();
}

public void Dispose()


{
_bar?.Dispose();
}
}

Public NotInheritable Class Foo


Implements IDisposable

Private ReadOnly _bar As IDisposable

Public Sub New()


_bar = New Bar()
End Sub

Public Sub Dispose() Implements IDisposable.Dispose


_bar.Dispose()
End Sub
End Class

Implémenter le modèle de suppression


Toutes les classes non-sealed ou (classes Visual Basic non modifiées comme NotInheritable ) doivent être
considérées comme une classe de base potentielle, car elles peuvent être héritées. Si vous implémentez le modèle
de suppression pour une classe de base potentielle quelconque, vous devez fournir les éléments suivants :
Une implémentation de Dispose qui appelle la méthode Dispose(bool) .
Dispose(bool) Méthode qui effectue le nettoyage réel.
Une classe dérivée de SafeHandle qui encapsule votre ressource managée (recommandée) ou une
substitution de la méthode Object.Finalize. La SafeHandle classe fournit un finaliseur, donc vous n’avez pas
besoin d’en écrire un vous-même.

IMPORTANT
Une classe de base peut uniquement référencer des objets managés et implémenter le modèle de suppression. Dans ce cas,
un finaliseur n’est pas nécessaire. Un finaliseur est requis uniquement si vous référencez directement des ressources non
managées.

Voici le modèle général d’implémentation du modèle de suppression d’une classe de base qui utilise un handle
sécurisé.
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

class BaseClass : IDisposable


{
// To detect redundant calls
private bool _disposed = false;

// Instantiate a SafeHandle instance.


private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

// Public implementation of Dispose pattern callable by consumers.


public void Dispose() => Dispose(true);

// Protected implementation of Dispose pattern.


protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}

if (disposing)
{
// Dispose managed state (managed objects).
_safeHandle?.Dispose();
}

_disposed = true;
}
}

Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class BaseClass : Implements IDisposable


' Flag: Has Dispose already been called?
Dim disposed As Boolean = False
' Instantiate a SafeHandle instance.
Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

' Public implementation of Dispose pattern callable by consumers.


Public Sub Dispose() _
Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub

' Protected implementation of Dispose pattern.


Protected Overridable Sub Dispose(disposing As Boolean)
If disposed Then Return

If disposing Then
handle.Dispose()
' Free any other managed objects here.
'
End If

disposed = True
End Sub
End Class
NOTE
L'exemple précédent utilise un objet SafeFileHandle pour illustrer le modèle, mais il est possible d'utiliser à la place
n'importe quel objet dérivé de SafeHandle. Notez que l'exemple n'instancie pas correctement son objet SafeFileHandle.

Voici le modèle général d’implémentation du modèle de suppression d’une classe de base qui remplace
Object.Finalize.

using System;

class BaseClass : IDisposable


{
// To detect redundant calls
private bool _disposed = false;

~BaseClass() => Dispose(false);

// Public implementation of Dispose pattern callable by consumers.


public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

// Protected implementation of Dispose pattern.


protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}

if (disposing)
{
// TODO: dispose managed state (managed objects).
}

// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.

_disposed = true;
}
}
Class BaseClass : Implements IDisposable
' Flag: Has Dispose already been called?
Dim disposed As Boolean = False

' Public implementation of Dispose pattern callable by consumers.


Public Sub Dispose() _
Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub

' Protected implementation of Dispose pattern.


Protected Overridable Sub Dispose(disposing As Boolean)
If disposed Then Return

If disposing Then
' Free any other managed objects here.
'
End If

' Free any unmanaged objects here.


'
disposed = True
End Sub

Protected Overrides Sub Finalize()


Dispose(False)
End Sub
End Class

TIP
En C#, vous créez un finaliseur en substituant Object.Finalize . Dans Visual Basic, cette opération s’effectue à l’aide de
Protected Overrides Sub Finalize() .

Implémenter le modèle de suppression d’une classe dérivée


Une classe dérivée d'une classe qui implémente l'interface IDisposable ne doit pas implémenter IDisposable, car
l'implémentation de la classe de base de IDisposable.Dispose est héritée par les classes dérivées. Au lieu de cela,
pour nettoyer une classe dérivée, vous devez fournir les éléments suivants :
protected override void Dispose(bool) Méthode qui remplace la méthode de la classe de base et effectue le
nettoyage réel de la classe dérivée. Cette méthode doit également appeler la base.Dispose(bool)
MyBase.Dispose(bool) méthode (en Visual Basic) de la classe de base et passer son état de suppression pour
l’argument.
Une classe dérivée de SafeHandle qui encapsule votre ressource managée (recommandée) ou une
substitution de la méthode Object.Finalize. La classe SafeHandle fournit un finaliseur qui vous permet de ne
pas avoir à en coder un. Si vous fournissez un finaliseur, il doit appeler la Dispose(bool) surcharge avec un
disposing argument de false .

Voici le modèle général d’implémentation du modèle de suppression d’une classe dérivée qui utilise un handle
sécurisé :
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

class DerivedClass : BaseClass


{
// To detect redundant calls
private bool _disposed = false;

// Instantiate a SafeHandle instance.


private SafeHandle _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

// Protected implementation of Dispose pattern.


protected override void Dispose(bool disposing)
{
if (_disposed)
{
return;
}

if (disposing)
{
// Dispose managed state (managed objects).
_safeHandle?.Dispose();
}

_disposed = true;

// Call base class implementation.


base.Dispose(disposing);
}
}

Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Class DerivedClass : Inherits BaseClass


' Flag: Has Dispose already been called?
Dim disposed As Boolean = False
' Instantiate a SafeHandle instance.
Dim handle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

' Protected implementation of Dispose pattern.


Protected Overrides Sub Dispose(disposing As Boolean)
If disposed Then Return

If disposing Then
handle.Dispose()
' Free any other managed objects here.
'
End If

' Free any unmanaged objects here.


'
disposed = True

' Call base class implementation.


MyBase.Dispose(disposing)
End Sub
End Class
NOTE
L'exemple précédent utilise un objet SafeFileHandle pour illustrer le modèle, mais il est possible d'utiliser à la place
n'importe quel objet dérivé de SafeHandle. Notez que l'exemple n'instancie pas correctement son objet SafeFileHandle.

Voici le modèle général d'implémentation du modèle de suppression d'une classe dérivée qui remplace
Object.Finalize :

using System;

class DerivedClass : BaseClass


{
// To detect redundant calls
bool _disposed = false;

~DerivedClass() => Dispose(false);

// Protected implementation of Dispose pattern.


protected override void Dispose(bool disposing)
{
if (_disposed)
{
return;
}

if (disposing)
{
// TODO: dispose managed state (managed objects).
}

// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
_disposed = true;

// Call the base class implementation.


base.Dispose(disposing);
}
}
Class DerivedClass : Inherits BaseClass
' Flag: Has Dispose already been called?
Dim disposed As Boolean = False

' Protected implementation of Dispose pattern.


Protected Overrides Sub Dispose(disposing As Boolean)
If disposed Then Return

If disposing Then
' Free any other managed objects here.
'
End If

' Free any unmanaged objects here.


'
disposed = True

' Call the base class implementation.


MyBase.Dispose(disposing)
End Sub

Protected Overrides Sub Finalize()


Dispose(False)
End Sub
End Class

Implémenter le modèle de suppression avec des Handles sécurisés


L'exemple suivant illustre le modèle de suppression d'une classe de base, DisposableStreamResource , qui utilise
un handle sécurisé pour encapsuler les ressources non managées. Il définit une classe DisposableStreamResource
qui utilise SafeFileHandle pour encapsuler un objet Stream qui représente un fichier ouvert. La classe comprend
également une seule propriété, Size , qui retourne le nombre total d’octets dans le flux de fichier.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

public class DisposableStreamResource : IDisposable


{
// Define constants.
protected const uint GENERIC_READ = 0x80000000;
protected const uint FILE_SHARE_READ = 0x00000001;
protected const uint OPEN_EXISTING = 3;
protected const uint FILE_ATTRIBUTE_NORMAL = 0x80;
private const int INVALID_FILE_SIZE = unchecked((int)0xFFFFFFFF);

// Define Windows APIs.


[DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode)]
protected static extern SafeFileHandle CreateFile(
string lpFileName, uint dwDesiredAccess,
uint dwShareMode, IntPtr lpSecurityAttributes,
uint dwCreationDisposition, uint dwFlagsAndAttributes,
IntPtr hTemplateFile);

[DllImport("kernel32.dll")]
private static extern int GetFileSize(
SafeFileHandle hFile, out int lpFileSizeHigh);

// Define locals.
private bool _disposed = false;
private readonly SafeFileHandle _safeHandle;
private readonly int _upperWord;

public DisposableStreamResource(string fileName)


public DisposableStreamResource(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
{
throw new ArgumentException("The fileName cannot be null or an empty string");
}

_safeHandle = CreateFile(
fileName, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);

// Get file size.


Size = GetFileSize(_safeHandle, out _upperWord);
if (Size == INVALID_FILE_SIZE)
{
Size = -1;
}
else if (_upperWord > 0)
{
Size = (((long)_upperWord) << 32) + Size;
}
}

public long Size { get; }

public void Dispose()


{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)


{
if (_disposed)
{
return;
}

// Dispose of managed resources here.


if (disposing)
{
_safeHandle?.Dispose();
}

// Dispose of any unmanaged resources not wrapped in safe handles.

_disposed = true;
}
}
Imports Microsoft.Win32.SafeHandles
Imports System.IO

Public Class DisposableStreamResource : Implements IDisposable


' Define constants.
Protected Const GENERIC_READ As UInteger = &H80000000UI
Protected Const FILE_SHARE_READ As UInteger = &H0I
Protected Const OPEN_EXISTING As UInteger = 3
Protected Const FILE_ATTRIBUTE_NORMAL As UInteger = &H80
Private Const INVALID_FILE_SIZE As Integer = &HFFFFFFFF

' Define Windows APIs.


Protected Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (
lpFileName As String, dwDesiredAccess As UInt32,
dwShareMode As UInt32, lpSecurityAttributes As IntPtr,
dwCreationDisposition As UInt32, dwFlagsAndAttributes As UInt32,
hTemplateFile As IntPtr) As SafeFileHandle

Private Declare Function GetFileSize Lib "kernel32" (


hFile As SafeFileHandle, ByRef lpFileSizeHigh As Integer) As Integer

' Define locals.


Private disposed As Boolean = False
Private ReadOnly safeHandle As SafeFileHandle
Private ReadOnly upperWord As Integer

Public Sub New(fileName As String)


If String.IsNullOrWhiteSpace(fileName) Then
Throw New ArgumentNullException("The fileName cannot be null or an empty string")
End If

safeHandle = CreateFile(
fileName, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero)

' Get file size.


Size = GetFileSize(safeHandle, upperWord)
If Size = INVALID_FILE_SIZE Then
Size = -1
ElseIf upperWord > 0 Then
Size = (CLng(upperWord) << 32) + Size
End If
End Sub

Public ReadOnly Property Size As Long

Public Sub Dispose() _


Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub

Protected Overridable Sub Dispose(disposing As Boolean)


If disposed Then Exit Sub

' Dispose of managed resources here.


If disposing Then
safeHandle.Dispose()
End If

' Dispose of any unmanaged resources not wrapped in safe handles.

disposed = True
End Sub
End Class
Implémenter le modèle de suppression d’une classe dérivée avec des
Handles sécurisés
L'exemple suivant illustre le modèle de suppression d'une classe dérivée, DisposableStreamResource2 , qui hérite
de la classe DisposableStreamResource présentée dans l'exemple précédent. La classe ajoute une méthode
supplémentaire, WriteFileInfo , et utilise un objet SafeFileHandle pour encapsuler le handle du fichier accessible
en écriture.
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

public class DisposableStreamResource2 : DisposableStreamResource


{
// Define additional constants.
protected const uint GENERIC_WRITE = 0x40000000;
protected const uint OPEN_ALWAYS = 4;

// Define additional APIs.


[DllImport("kernel32.dll")]
protected static extern bool WriteFile(
SafeFileHandle safeHandle, string lpBuffer,
int nNumberOfBytesToWrite, out int lpNumberOfBytesWritten,
IntPtr lpOverlapped);

// To detect redundant calls


private bool _disposed = false;
private bool _created = false;
private SafeFileHandle _safeHandle;
private readonly string _fileName;

public DisposableStreamResource2(string fileName) : base(fileName) => _fileName = fileName;

public void WriteFileInfo()


{
if (!_created)
{
_safeHandle = CreateFile(
@".\FileInfo.txt", GENERIC_WRITE, 0, IntPtr.Zero,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero);

_created = true;
}

string output = $"{_fileName}: {Size:N0} bytes\n";


_ = WriteFile(_safeHandle, output, output.Length, out _, IntPtr.Zero);
}

protected override void Dispose(bool disposing)


{
if (_disposed)
{
return;
}

// Release any managed resources here.


if (disposing)
{
// Dispose managed state (managed objects).
_safeHandle?.Dispose();
}

// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.

_disposed = true;

// Call the base class implementation.


base.Dispose(disposing);
}
}
Imports Microsoft.Win32.SafeHandles
Imports System.IO

Public Class DisposableStreamResource2 : Inherits DisposableStreamResource


' Define additional constants.
Protected Const GENERIC_WRITE As Integer = &H40000000
Protected Const OPEN_ALWAYS As Integer = 4

' Define additional APIs.


Protected Declare Function WriteFile Lib "kernel32.dll" (
safeHandle As SafeFileHandle, lpBuffer As String,
nNumberOfBytesToWrite As Integer, ByRef lpNumberOfBytesWritten As Integer,
lpOverlapped As Object) As Boolean

' Define locals.


Private disposed As Boolean = False
Private created As Boolean = False
Private safeHandle As SafeFileHandle
Private ReadOnly filename As String

Public Sub New(filename As String)


MyBase.New(filename)
Me.filename = filename
End Sub

Public Sub WriteFileInfo()


If Not created Then
safeHandle = CreateFile(
".\FileInfo.txt", GENERIC_WRITE, 0, IntPtr.Zero,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero)
created = True
End If

Dim output As String = $"{filename }: {Size:N0} bytes {vbCrLf }"


Dim result = WriteFile(safeHandle, output, output.Length, 0&, Nothing)
End Sub

Protected Overridable Overloads Sub Dispose(disposing As Boolean)


If disposed Then Exit Sub

' Release any managed resources here.


If disposing Then
safeHandle?.Dispose()
End If

disposed = True
' Release any unmanaged resources not wrapped by safe handles here.

' Call the base class implementation.


MyBase.Dispose(disposing)
End Sub
End Class

Voir aussi
SuppressFinalize
IDisposable
IDisposable.Dispose
Microsoft.Win32.SafeHandles
System.Runtime.InteropServices.SafeHandle
Object.Finalize
Définir et utiliser des classes et des structs (C++/CLI)
Implémenter une méthode DisposeAsync
18/07/2020 • 7 minutes to read • Edit Online

L' System.IAsyncDisposable interface a été introduite dans le cadre de C# 8,0. Vous implémentez la
IAsyncDisposable.DisposeAsync() méthode lorsque vous devez effectuer un nettoyage des ressources, comme vous
le feriez lors de l' implémentation d’une méthode dispose. Toutefois, l’une des principales différences est que cette
implémentation autorise les opérations de nettoyage asynchrones. DisposeAsync()Retourne un ValueTask qui
représente l’opération de suppression asynchrone.
Lors de l’implémentation de l' IAsyncDisposable interface, les classes implémentent également l' IDisposable
interface. Un modèle d’implémentation correct de l' IAsyncDisposable interface doit être préparé pour une
suppression synchrone ou asynchrone. Toutes les instructions pour l’implémentation du modèle de suppression
s’appliquent à l’implémentation asynchrone. Cet article suppose que vous êtes déjà familiarisé avec
l’implémentation d' une méthode dispose.

DisposeAsync () et DisposeAsyncCore ()
L' IAsyncDisposable interface déclare une méthode sans paramètre unique, DisposeAsync() . Toute classe non
scellée doit avoir une DisposeAsyncCore() méthode supplémentaire qui retourne également un ValueTask .
publicIAsyncDisposable.DisposeAsync() Implémentation qui n’a aucun paramètre.
Une protected virtual ValueTask DisposeAsyncCore() méthode dont la signature est :

protected virtual ValueTask DisposeAsyncCore()


{
}

La DisposeAsyncCore() méthode virtual permet aux classes dérivées de définir un nettoyage supplémentaire
dans leurs substitutions.
Méthode DisposeAsync ()
La public méthode sans paramètre DisposeAsync() est appelée implicitement dans une await using instruction,
et son objectif est de libérer des ressources non managées, d’effectuer un nettoyage général et d’indiquer que le
finaliseur, le cas échéant, doit être exécuté. La libération de la mémoire associée à un objet géré est toujours le
domaine du garbage collector. De ce fait, son implémentation standard est la suivante :

public async ValueTask DisposeAsync()


{
// Perform async cleanup.
await DisposeAsyncCore();

// Dispose of managed resources.


Dispose(false);
// Suppress finalization.
GC.SuppressFinalize(this);
}
NOTE
L’une des principales différences dans le modèle de suppression asynchrone par rapport au modèle de suppression est que
l’appel de DisposeAsync() à la Dispose(bool) méthode de surcharge est donné false comme argument. Toutefois, lors de
l’implémentation de la IDisposable.Dispose() méthode, true est passé. Cela permet de garantir l’équivalence fonctionnelle
avec le modèle de suppression synchrone et de s’assurer que les chemins de code du finaliseur sont toujours appelés. En
d’autres termes, la DisposeAsyncCore() méthode supprime les ressources managées de manière asynchrone, donc vous ne
voulez pas les supprimer de manière synchrone. Par conséquent, appelez Dispose(false) au lieu de Dispose(true) .

Implémenter le modèle de suppression asynchrone


Toutes les classes non-sealed doivent être considérées comme une classe de base potentielle, car elles peuvent être
héritées. Si vous implémentez le modèle de suppression Async pour une classe de base potentielle quelconque,
vous devez fournir la protected virtual ValueTask DisposeAsyncCore() méthode. Voici un exemple
d’implémentation du modèle asynchrone dispose qui utilise un System.Text.Json.Utf8JsonWriter .
using System;
using System.Text.Json;
using System.Threading.Tasks;

public class ExampleAsyncDisposable : IAsyncDisposable, IDisposable


{
// To detect redundant calls
private bool _disposed = false;

// Created in .ctor, omitted for brevity.


private Utf8JsonWriter _jsonWriter;

public async ValueTask DisposeAsync()


{
await DisposeAsyncCore();

Dispose(false);
GC.SuppressFinalize(this);
}

protected virtual async ValueTask DisposeAsyncCore()


{
// Cascade async dispose calls
if (_jsonWriter != null)
{
await _jsonWriter.DisposeAsync();
_jsonWriter = null;
}
}

public void Dispose()


{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)


{
if (_disposed)
{
return;
}

if (disposing)
{
_jsonWriter?.Dispose();
// TODO: dispose managed state (managed objects).
}

// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.

_disposed = true;
}
}

L’exemple précédent utilise Utf8JsonWriter . Pour plus d’informations sur System.Text.Json , voir How to migrate
from Newtonsoft.Json to System.Text.Json.

Utilisation de Async jetable


Pour utiliser correctement un objet qui implémente l' IAsyncDisposable interface, vous utilisez l' expression awaitet
les mots clés en même temps. Prenons l’exemple suivant, où la ExampleAsyncDisposable classe est instanciée puis
encapsulée dans une await using instruction.
class ExampleProgram
{
static async Task Main()
{
var exampleAsyncDisposable = new ExampleAsyncDisposable();
await using (exampleAsyncDisposable.ConfigureAwait(false))
{
// Interact with the exampleAsyncDisposable instance.
}

Console.ReadLine();
}
}

IMPORTANT
Utilisez la ConfigureAwait(IAsyncDisposable, Boolean) méthode d’extension de l' IAsyncDisposable interface pour configurer la
façon dont la continuation de la tâche est marshalée sur son contexte ou planificateur d’origine. Pour plus d’informations sur
ConfigureAwait , consultez le Forum aux questions ConfigureAwait.

Dans les situations où l’utilisation de ConfigureAwait n’est pas nécessaire, l' await using instruction peut être
simplifiée comme suit :

class ExampleProgram
{
static async Task Main()
{
await using (var exampleAsyncDisposable = new ExampleAsyncDisposable())
{
// Interact with the exampleAsyncDisposable instance.
}

Console.ReadLine();
}
}

En outre, il peut être écrit pour utiliser l’étendue implicite d’une déclaration using.

class ExampleProgram
{
static async Task Main()
{
await using var exampleAsyncDisposable = new ExampleAsyncDisposable();

// Interact with the exampleAsyncDisposable instance.

Console.ReadLine();
}
}

Utilisation empilée
Dans les situations où vous créez et utilisez plusieurs objets qui implémentent IAsyncDisposable , il est possible que
l’empilement using des instructions dans des conditions errantes puisse empêcher les appels à DisposeAsync() .
Afin d’éviter tout problème potentiel, vous devez éviter l’empilement, et suivez plutôt le modèle d’exemple suivant :
class ExampleProgram
{
static async Task Main()
{
var objOne = new ExampleAsyncDisposable();
await using objOne.ConfigureAwait(false);
// Interact with the objOne instance.

var objTwo = new ExampleAsyncDisposable();


await using objTwo.ConfigureAwait(false))
{
// Interact with the objTwo instance.
}

Console.ReadLine();
}
}

Voir aussi
IAsyncDisposable
IAsyncDisposable.DisposeAsync()
ConfigureAwait(IAsyncDisposable, Boolean)
Utilisation d’objets implémentant IDisposable
18/07/2020 • 7 minutes to read • Edit Online

Le « garbage collector » de l’common language runtime récupère la mémoire utilisée par les objets managés, mais
les types qui utilisent des ressources non managées implémentent l' IDisposable interface pour permettre la
récupération des ressources requises par ces ressources non managées. Une fois que vous avez fini d'utiliser un
objet qui implémente IDisposable, vous devez appeler l'implémentation de IDisposable.Dispose de l'objet. Vous
pouvez le faire de deux façons :
Avec l' using instruction C# ( Using dans Visual Basic).
En implémentant un try/finally bloc et en appelant le IDisposable.Dispose dans le finally .

Instruction using
L' using instruction en C# et l' Using instruction dans Visual Basic simplifient le code que vous devez écrire pour
nettoyer un objet. L’instruction using obtient une ou plusieurs ressources, exécute les instructions que vous
spécifiez, puis supprime automatiquement l’objet. Toutefois, l’instruction using est utile uniquement pour les
objets utilisés dans la portée de la méthode dans laquelle elles sont construites.
L’exemple suivant utilise l’instruction using pour créer et libérer un objet System.IO.StreamReader.

using System;
using System.IO;

class Example
{
static void Main()
{
char[] buffer = new char[50];
using var streamReader = new StreamReader("file1.txt");

int charsRead = 0;
while (streamReader.Peek() != -1)
{
charsRead = streamReader.Read(buffer, 0, buffer.Length);
//
// Process characters read.
//
}
}
}
Imports System.IO

Module Example
Public Sub Main()
Dim buffer(49) As Char
Using s As New StreamReader("File1.txt")
Dim charsRead As Integer
Do While s.Peek() <> -1
charsRead = s.Read(buffer, 0, buffer.Length)
'
' Process characters read.
'
Loop
End Using
End Sub
End Module

Bien que la StreamReader classe implémente l' IDisposable interface, ce qui indique qu’elle utilise une ressource
non managée, l’exemple n’appelle pas explicitement la StreamReader.Dispose méthode. Quand le compilateur C#
ou Visual Basic rencontre l’instruction using , il émet en langage intermédiaire qui est équivalent au code suivant
contenant explicitement un bloc try/finally .

using System;
using System.IO;

class Example
{
static void Main()
{
char[] buffer = new char[50];
var streamReader = new StreamReader("file1.txt");
try
{
int charsRead = 0;
while (streamReader.Peek() != -1)
{
charsRead = streamReader.Read(buffer, 0, buffer.Length);
//
// Process characters read.
//
}
}
finally
{
if (streamReader != null)
{
((IDisposable)streamReader).Dispose();
}
}
}
}
Imports System.IO

Module Example
Public Sub Main()
Dim buffer(49) As Char
'' Dim s As New StreamReader("File1.txt")
With s As New StreamReader("File1.txt")
Try
Dim charsRead As Integer
Do While s.Peek() <> -1
charsRead = s.Read(buffer, 0, buffer.Length)
'
' Process characters read.
'
Loop
Finally
If s IsNot Nothing Then DirectCast(s, IDisposable).Dispose()
End Try
End With
End Sub
End Module

L’instruction using en C# vous permet d’acquérir plusieurs ressources dans une seule instruction, ce qui équivaut
en interne à des instructions using imbriquées. L'exemple suivant instancie deux objets StreamReader pour lire le
contenu de deux fichiers différents.

using System.IO;

class Example
{
static void Main()
{
char[] buffer1 = new char[50];
char[] buffer2 = new char[50];

using StreamReader version1 = new StreamReader("file1.txt"),


version2 = new StreamReader("file2.txt");

int charsRead1, charsRead2 = 0;


while (version1.Peek() != -1 && version2.Peek() != -1)
{
charsRead1 = version1.Read(buffer1, 0, buffer1.Length);
charsRead2 = version2.Read(buffer2, 0, buffer2.Length);
//
// Process characters read.
//
}
}
}

Bloc try/finally
Au lieu d’encapsuler un bloc try/finally dans une instruction using , vous pouvez choisir d’implémenter le bloc
try/finally directement. Il peut s’agir de votre style de codage personnel, ou vous pouvez le faire pour l’une des
raisons suivantes :
Pour inclure un catch bloc pour gérer les exceptions levées dans le try bloc. Sinon, toutes les exceptions
levées dans l' using instruction ne sont pas gérées.
Pour instancier un objet qui implémente IDisposable dont la portée n'est pas locale au bloc dans lequel elle
est déclarée.
L'exemple suivant est similaire à l'exemple précédent, mais il utilise un bloc try/catch/finally pour instancier,
utiliser et supprimer un objet StreamReader, ainsi que pour gérer les exceptions levées par le constructeur
StreamReader et sa méthode ReadToEnd. Le code du finally bloc vérifie que l’objet qui implémente IDisposable
n’est pas null avant d’appeler la Dispose méthode. La non-exécution de cette opération peut générer une
NullReferenceException au moment de l'exécution.

using System;
using System.Globalization;
using System.IO;

class Example
{
static void Main()
{
StreamReader? streamReader = null;
try
{
streamReader = new StreamReader("file1.txt");
string contents = streamReader.ReadToEnd();
var info = new StringInfo(contents);
Console.WriteLine($"The file has {info.LengthInTextElements} text elements.");
}
catch (FileNotFoundException)
{
Console.WriteLine("The file cannot be found.");
}
catch (IOException)
{
Console.WriteLine("An I/O error has occurred.");
}
catch (OutOfMemoryException)
{
Console.WriteLine("There is insufficient memory to read the file.");
}
finally
{
streamReader?.Dispose();
}
}
}

Imports System.Globalization
Imports System.IO

Module Example
Sub Main()
Dim streamReader As StreamReader = Nothing
Try
streamReader = New StreamReader("file1.txt")
Dim contents As String = streamReader.ReadToEnd()
Dim info As StringInfo = New StringInfo(contents)
Console.WriteLine($"The file has {info.LengthInTextElements} text elements.")
Catch e As FileNotFoundException
Console.WriteLine("The file cannot be found.")
Catch e As IOException
Console.WriteLine("An I/O error has occurred.")
Catch e As OutOfMemoryException
Console.WriteLine("There is insufficient memory to read the file.")
Finally
If streamReader IsNot Nothing Then streamReader.Dispose()
End Try
End Sub
End Module
Vous pouvez suivre ce modèle de base si vous choisissez d’implémenter (ou que vous devez implémenter) un bloc
try/finally , car votre langage de programmation ne prend pas en charge l’instruction using , mais autorise des
appels directs à la méthode Dispose.

Membres d’instance IDisposable


Si une classe contient une IDisposable implémentation en tant que membre d’instance, qu’il s’agisse d’un champ
ou d’une propriété, la classe doit également implémenter IDisposable . Pour plus d’informations, consultez
implémenter une suppression en cascade.

Voir aussi
Nettoyage de ressources non managées
using, instruction (référence C#)
Using, instruction (Visual Basic)
Garbage collection
18/07/2020 • 3 minutes to read • Edit Online

Le « garbage collector » du .NET gère l’allocation et la libération de mémoire pour votre application. Chaque
fois que vous créez un objet, le Common Language Runtime alloue de la mémoire pour l’objet à partir du tas
managé. Aussi longtemps que de l'espace d'adressage est disponible dans le tas managé, le Runtime continue
à allouer de l'espace pour de nouveaux objets. Toutefois, la mémoire n’est pas infinie. Pour finir, le garbage
collector doit exécuter une collecte afin de libérer de la mémoire. Le moteur d'optimisation du « garbage
collector » détermine le meilleur moment pour lancer une opération garbage collection sur base des
allocations de mémoire effectuées. Lorsque le garbage collector effectue une collecte, il recherche les objets
dans le tas managé qui ne sont plus utilisés par l’application et effectue les opérations nécessaires pour
récupérer leur mémoire.

Dans cette section


IN T IT UL É DESC RIP T IO N

Notions de base de garbage collection Décrit le fonctionnement du garbage collection, l’allocation


des objets sur le tas managé, ainsi que d’autres concepts
principaux.

Garbage collection de station de travail et de serveur Décrit les différences entre les garbage collection de station
de travail pour les applications clientes et les garbage
collection de serveur pour les applications serveur.

garbage collection d’arrière-plan Décrit les garbage collection d’arrière-plan, qui est la
collection d’objets de génération 0 et 1 alors que la
collection de génération 2 est en cours.

Tas d’objets volumineux Décrit le tas d’objets volumineux (LOH) et la façon dont les
objets volumineux sont récupérés par le garbage collector.

Garbage collection et performances Décrit les contrôles de performances que vous pouvez
utiliser pour diagnostiquer les problèmes de garbage
collection et de performances.

Collections forcées Décrit comment faire pour qu’un garbage collection se


produise.

Modes de latence Décrit les modes qui déterminent le niveau d’intrusion du


garbage collection.

Optimisation pour l’hébergement web partagé Explique comment optimiser le garbage collection sur des
serveurs partagés par plusieurs petits sites web.

Notifications du garbage collection Explique comment déterminer si un garbage collection est


presque atteint et s’il est terminé.

Supervision des ressource de domaine d’application Explique comment surveiller l’utilisation du processeur et de
la mémoire par un domaine d’application.
IN T IT UL É DESC RIP T IO N

Références faibles Décrit les fonctionnalités qui permettent au Garbage


collector de collecter un objet tout en permettant à
l’application d’accéder à cet objet.

Informations de référence
System.GC
System.GCCollectionMode
System.GCNotificationStatus
System.Runtime.GCLatencyMode
System.Runtime.GCSettings
GCSettings.LargeObjectHeapCompactionMode
Object.Finalize
System.IDisposable

Voir aussi
Nettoyer les ressources non managées
Notions de base du garbage collection
18/07/2020 • 31 minutes to read • Edit Online

Dans le common language runtime (CLR), le garbage collector (GC) sert de gestionnaire de mémoire automatique.
Le garbage collector gère l’allocation et la libération de mémoire pour une application. Pour les développeurs qui
utilisent du code managé, cela signifie que vous n’avez pas besoin d’écrire du code pour effectuer des tâches de
gestion de la mémoire. La gestion automatique de la mémoire permet d’éliminer les problèmes courants, tels que
l’oubli de libérer un objet et de provoquer une fuite de mémoire ou une tentative d’accès à la mémoire pour un
objet qui a déjà été libéré.
Cet article décrit les concepts fondamentaux de garbage collection.

Avantages
Le garbage collector offre les avantages suivants :
Évite aux développeurs d’avoir à libérer manuellement de la mémoire.
Il alloue efficacement les objets sur le tas managé.
Il libère les objets qui ne sont plus utilisés, efface leur mémoire et garde la mémoire disponible pour les
futures allocations. Les objets managés obtiennent automatiquement un contenu propre au démarrage, de
sorte que leurs constructeurs n’ont pas à initialiser chaque champ de données.
Il sécurise la mémoire en s'assurant qu'un objet ne peut pas utiliser le contenu d'un autre objet.

Notions de base de la mémoire


La liste suivante résume les concepts importants de la mémoire CLR.
Chaque processus possède son propre espace d'adressage virtuel séparé. Tous les processus sur le même
ordinateur partagent la même mémoire physique et le même fichier d’échange, le cas échéant.
Par défaut, sur les ordinateurs 32 bits, chaque processus a un espace d'adressage virtuel en mode
utilisateur de 2 Go.
En tant que développeur d'applications, vous travaillez uniquement avec l'espace d'adressage virtuel et ne
gérez jamais directement la mémoire physique. Le garbage collector alloue et libère la mémoire virtuelle
pour vous sur le tas managé.
Si vous écrivez du code natif, vous utilisez des fonctions Windows pour travailler avec l’espace d’adressage
virtuel. Ces fonctions allouent et libèrent la mémoire virtuelle pour vous sur les tas natifs.
La mémoire virtuelle peut être dans trois états :

STAT E DESC RIP T IO N

Gratuit Il n'existe aucune référence au bloc de mémoire et celui-ci


est disponible pour allocation.
STAT E DESC RIP T IO N

Réservé Le bloc de mémoire est disponible pour votre utilisation et


ne peut pas être utilisé pour une autre demande
d'allocation. Toutefois, vous ne pouvez pas stocker de
données dans ce bloc de mémoire tant qu'il n'est pas
validé.

Committed Le bloc de mémoire est assigné au stockage physique.

L'espace d'adressage virtuel peut être fragmenté. Cela signifie qu'il existe des blocs libres, également
appelés trous, dans l'espace d'adressage. Lorsqu'une allocation de mémoire virtuelle est demandée, le
gestionnaire de mémoire virtuelle doit rechercher un bloc unique libre suffisamment grand pour satisfaire
la demande d'allocation. Même si vous disposez de 2 Go d’espace libre, une allocation qui requiert 2 Go
échoue, sauf si tout cet espace libre se trouve dans un bloc d’adresses unique.
Vous pouvez manquer de mémoire s’il n’y a pas suffisamment d’espace d’adressage virtuel à réserver ou
d’espace physique à valider.
Le fichier d’échange est utilisé même si la sollicitation de la mémoire physique (c’est-à-dire, la demande de
mémoire physique) est faible. La première fois que la sollicitation de la mémoire physique est élevée, le
système d’exploitation doit libérer de la place dans la mémoire physique pour stocker les données, et il
sauvegarde certaines des données qui se trouvent dans la mémoire physique dans le fichier d’échange. Ces
données ne sont pas paginées tant qu’elles ne sont pas nécessaires, il est donc possible de rencontrer la
pagination dans les situations où la sollicitation de la mémoire physique est faible.
Allocation de mémoire
Lorsque vous initialisez un nouveau processus, le runtime réserve une région d'espace d'adressage contigu pour
le processus. Cet espace d'adressage est appelé le tas managé. Le tas managé garde un pointeur vers l'adresse qui
sera allouée au nouvel objet du tas. À l’origine, ce pointeur indique l’adresse de base du tas managé. Tous les types
référence sont alloués sur le tas managé. Lorsqu’une application crée le premier type référence, la mémoire est
allouée pour le type à l’adresse de base du tas managé. Lorsque l'application crée l'objet suivant, le « garbage
collector » lui alloue de la mémoire dans l'espace d'adressage qui suit immédiatement le premier objet. Aussi
longtemps que de l'espace d'adressage est disponible, le « garbage collector » continue à allouer de l'espace pour
de nouveaux objets selon la même procédure.
L'allocation de mémoire à partir du tas managé est plus rapide que l'allocation de mémoire non managée. Étant
donné que le runtime alloue de la mémoire pour un objet en ajoutant une valeur à un pointeur, il est presque aussi
rapide que d’allouer de la mémoire à partir de la pile. En outre, étant donné que les nouveaux objets qui sont
alloués consécutivement sont stockés de façon contiguë dans le tas managé, une application peut accéder
rapidement aux objets.
Mise en mémoire
Le moteur d'optimisation du « garbage collector » détermine le meilleur moment pour lancer une opération
garbage collection sur base des allocations de mémoire effectuées. Lorsque le « garbage collector » effectue une
opération garbage collection, il libère la mémoire pour les objets qui ne sont plus utilisées par l'application. Il
détermine les objets qui ne sont plus utilisés en examinant les racinesde l’application. Les racines de l’application
comprennent des champs statiques, des variables et des paramètres locaux sur la pile d’un thread et des Registres
du processeur. Chaque racine fait référence à un objet du tas managé ou, à défaut, a la valeur Null. Le Garbage
collector a accès à la liste des racines actives entretenues par le compilateur juste-à-temps (JIT) et le runtime. À
l’aide de cette liste, le garbage collector crée un graphique qui contient tous les objets accessibles à partir des
racines.
Les objets non compris dans le graphique ne sont pas accessibles à partir des racines de l'application. Le garbage
collector considère que les objets inaccessibles sont nettoyés et libère la mémoire qui leur est allouée. Au cours
d'une opération garbage collection, le « garbage collector » examine le tas managé pour y détecter les blocs de
mémoire occupés par des objets inaccessibles. Chaque fois qu'il détecte un objet inaccessible, il utilise une fonction
de copie de mémoire pour compacter les objets accessibles en mémoire et libérer les blocs d'espaces d'adressage
alloués aux objets inaccessibles. Lorsque la mémoire allouée aux objets accessibles a été réduite, le « garbage
collector » procède aux corrections de pointeurs nécessaires pour que les racines des applications pointent vers les
nouveaux emplacements des objets. Il positionne aussi le pointeur du tas managé après le dernier objet accessible.
La mémoire est compactée uniquement si une collection Découvre un nombre significatif d’objets inaccessibles. Si
tous les objets du tas managé survivent à une collection, il n'est pas nécessaire de procéder à un compactage de la
mémoire.
Pour améliorer les performances, le runtime alloue de la mémoire pour les objets de grandes dimensions dans un
tas séparé. Le « garbage collector » libère automatiquement la mémoire des objets de grande dimension.
Toutefois, pour éviter de déplacer des objets volumineux en mémoire, cette mémoire n’est généralement pas
compactée.

Conditions pour une opération garbage collection


Le garbage collection se produit lorsque l'une des conditions suivantes est vraie :
Le système possède peu de mémoire physique. Cela est détecté par la notification de mémoire insuffisante
du système d’exploitation ou la mémoire insuffisante, comme indiqué par l’hôte.
La mémoire utilisée par les objets alloués sur le tas managé dépasse un seuil acceptable. Ce seuil est
continuellement ajusté à mesure que le processus s'exécute.
La méthode GC.Collect est appelée. Dans presque tous les cas, il n’est pas nécessaire d’appeler cette
méthode, car le garbage collector s’exécute en continu. Cette méthode est principalement utilisée pour les
situations uniques et les tests.

Tas managé
Une fois que le garbage collector est initialisé par le CLR, il alloue un segment de mémoire pour stocker et gérer
des objets. Cette mémoire est appelée tas managé, par opposition à un tas natif dans le système d'exploitation.
Il existe un tas managé pour chaque processus managé. Tous les threads du processus allouent de la mémoire
pour les objets sur le même tas.
Pour réserver de la mémoire, le garbage collector appelle la fonction VirtualAlloc de Windows et réserve un
segment de mémoire à la fois pour les applications managées. Le garbage collector réserve également des
segments, si nécessaire, et libère des segments dans le système d’exploitation (après avoir effacé leurs objets) en
appelant la fonction VirtualFree de Windows.

IMPORTANT
La taille des segments alloués par le garbage collector est spécifique à l'implémentation et susceptible de changer à tout
moment, y compris les mises à jour périodiques. Votre application ne doit jamais faire d'hypothèses concernant une taille de
segment particulière, ni dépendre de celle-ci. Elle ne doit pas non plus tenter de configurer la quantité de mémoire
disponible pour les allocations de segments.

Moins il y a d'objets alloués sur le tas, moins le garbage collector a à faire. Lorsque vous allouez des objets,
n’utilisez pas de valeurs arrondies qui dépassent vos besoins, par exemple l’allocation d’un tableau de 32 octets
lorsque vous n’avez besoin que de 15 octets.
Lorsqu’un garbage collection est déclenché, le garbage collector récupère la mémoire occupée par les objets
morts. Le processus de libération compacte les objets vivants afin qu'ils soient déplacés ensemble, et l'espace
inutilisé est supprimé, ce qui entraîne la diminution du tas. Cela garantit que les objets alloués ensemble restent
ensemble sur le tas managé pour conserver leur localité.
Le déroulement (fréquence et durée) des garbage collection est le résultat du volume des allocations et de la
quantité de mémoire restante sur le tas managé.
Le tas peut être considéré comme l’accumulation de deux tas : le tas de grands objets et le tas de petits objets. Le
tas d’objets volumineux contient des objets de 85 000 octets et plus grands, qui sont généralement des tableaux. Il
est rare qu’un objet d’instance soit extrêmement volumineux.

TIP
Vous pouvez configurer la taille de seuil pour les objets à atteindre sur le tas d’objets volumineux.

Générations
L’algorithme GC est basé sur plusieurs considérations :
Il est plus rapide de compacter la mémoire pour une partie du tas managé que pour l’intégralité du tas
managé.
Les objets plus récents ont des durées de vie plus courtes et les objets plus anciens ont des durées de vie plus
longues.
Les objets les plus récents ont tendance à être liés les uns aux autres et à être accessibles à l’application en
même temps.
Le garbage collection se produit principalement avec la récupération d’objets éphémères. Pour optimiser les
performances du garbage collector, le tas managé est divisé en trois générations, 0, 1 et 2. il peut donc gérer
séparément les objets à durée de vie et à courte durée de vie. Le garbage collector stocke les nouveaux objets dans
la génération 0. Les objets qui sont créés à un stade précoce de la durée de vie de l'application et qui survivent aux
collectes sont promus et stockés dans les générations 1 et 2. Étant donné qu’il est plus rapide de compacter une
partie du tas managé que le tas entier, ce schéma permet au garbage collector de libérer la mémoire dans une
génération spécifique plutôt que de libérer la mémoire pour l’intégralité du tas managé chaque fois qu’il effectue
une collection.
Génération 0 . Il s'agit de la génération la plus jeune, qui contient des objets éphémères. Un exemple
d'objet éphémère est une variable temporaire. Le garbage collection a le plus souvent lieu dans cette
génération.
Les objets alloués récemment forment une nouvelle génération d’objets et sont implicitement des
collections de génération 0. Toutefois, s’il s’agit d’objets volumineux, ils sont placés sur le tas d’objets
volumineux (LOH), parfois appelé génération 3. La génération 3 est une génération physique qui est
collectée logiquement dans le cadre de la génération 2.
La plupart des objets sont récupérés pour garbage collection dans la génération 0 et ne survivent pas à la
génération suivante.
Si une application tente de créer un nouvel objet lorsque la génération 0 est complète, le garbage collector
effectue une collection pour tenter de libérer de l’espace d’adressage pour l’objet. Le « garbage collector »
commence par examiner les objets de la génération 0 plutôt que tous les objets du tas managé. Une
collection de génération 0 récupère souvent suffisamment de mémoire pour permettre à l’application de
continuer à créer des objets.
Génération 1 . Cette génération contient des objets éphémères et sert de tampon entre objets éphémères
et objets durables.
Une fois que le « garbage collector » effectue une opération garbage collection de la génération 0, il
compacte la mémoire pour les objets accessibles et les promeut à la génération 1. Étant donné que les
objets qui survivent aux collectes ont tendance à avoir de plus longues durées de vie, il est logique de les
promouvoir à une génération supérieure. Le garbage collector n’a pas à réexaminer les objets des
générations 1 et 2 chaque fois qu’il exécute une collection de génération 0.
Si une collection de génération 0 ne libère pas assez de mémoire pour que l’application puisse créer un
nouvel objet, le garbage collector peut exécuter une collection de génération 1, puis de génération 2. Les
objets qui survivent aux collectes sont promus et stockés dans les générations 1 et 2.
Génération 2 . Cette génération contient des objets durables. Un exemple d’objet de longue durée est un
objet dans une application serveur qui contient des données statiques qui sont actives pendant la durée du
processus.
Les objets de génération 2 qui survivent à une collection restent dans la génération 2 jusqu’à ce qu’ils
soient considérés comme inaccessibles dans une prochaine collection.
Les objets sur le tas d’objets volumineux (parfois appelé génération 3) sont également collectés dans la
génération 2.
Les opérations garbage collection se produisent sur des générations spécifiques, selon les conditions spécifiées. La
collecte d'une génération signifie la collecte des objets de cette génération et de toutes ses générations plus
jeunes. Une garbage collection de génération 2 est également appelée garbage collection complète, car elle libère
des objets dans toutes les générations (autrement dit, tous les objets du tas managé).
Survie et promotions
Les objets qui ne sont pas récupérés dans un garbage collection sont appelés survivants et sont promus à la
génération suivante :
Les objets qui survivent à une garbage collection de génération 0 sont promus à la génération 1.
Les objets qui survivent à une garbage collection de génération 1 sont promus à la génération 2.
Les objets qui survivent à une garbage collection de génération 2 restent dans la génération 2.
Lorsque le garbage collector détecte que le taux de survie est élevé dans une génération, il augmente le seuil des
allocations pour cette génération. La collection suivante obtient une taille substantielle de mémoire libérée. Le CLR
équilibre continuellement deux priorités : ne pas permettre à la plage de travail d’une application d’être trop
volumineuse en retardant garbage collection et en empêchant l’exécution trop fréquente du garbage collection.
Segments et générations éphémères
Étant donné que les objets des générations 0 et 1 sont éphémères, ces générations sont appelées générations
éphémères.
Les générations éphémères sont allouées dans le segment de mémoire appelé segment éphémère. Chaque
nouveau segment acquis par le garbage collector devient le nouveau segment éphémère et contient les objets qui
ont survécu à un garbage collection de génération 0. L'ancien segment éphémère devient le nouveau segment de
génération 2.
La taille du segment éphémère varie selon qu’il s’agit d’un système 32 bits ou 64 bits et du type de garbage
collector en cours d’exécution (station detravail ou GC de serveur). Le tableau suivant indique les tailles par défaut
du segment éphémère.

GC STAT IO N DE T RAVA IL / SERVEUR 32 B IT S 64 B IT S

Garbage collector pour station de 16 Mo 256 octets


travail

Garbage collector pour serveur 64 Mo 4 Go


GC STAT IO N DE T RAVA IL / SERVEUR 32 B IT S 64 B IT S

Garbage collector pour serveur > 4 32 Mo 2 Go


processeurs logiques

Garbage collector pour serveur > 8 16 Mo 1 Go


processeurs logiques

Le segment éphémère peut inclure des objets de la génération 2. Les objets de génération 2 peuvent utiliser
plusieurs segments (autant que votre processus en requiert et que la mémoire en autorise).
La quantité de mémoire libérée à partir d'un garbage collection éphémère est limitée à la taille du segment
éphémère. La quantité de mémoire libérée est proportionnelle à l'espace occupé par les objets morts.

Déroulement d’une opération garbage collection


Une opération garbage collection présente les phases suivantes :
Une phase de marquage qui recherche et crée une liste de tous les objets actifs.
Une phase de déplacement qui met à jour les références aux objets qui seront compactés.
Une phase de compactage qui libère l'espace occupé par les objets morts et compacte les objets survivants.
La phase de compactage déplace les objets qui ont survécu à un garbage collection vers l'extrémité la plus
ancienne du segment.
Étant donné que les collections de génération 2 peuvent occuper plusieurs segments, les objets promus
dans la génération 2 peuvent être déplacés dans un segment plus ancien. Les survivants des générations 1
et 2 peuvent être déplacés vers un autre segment, car ils sont promus à la génération 2.
En règle générale, le tas d’objets volumineux (LOH) n’est pas compacté, car la copie d’objets volumineux
impose une baisse des performances. Toutefois, dans .NET Core et dans .NET Framework 4.5.1 et versions
ultérieures, vous pouvez utiliser la GCSettings.LargeObjectHeapCompactionMode propriété pour
compacter le tas d’objets volumineux à la demande. En outre, le LOH est automatiquement compacté
lorsqu’une limite inconditionnelle est définie en spécifiant l’un ou l’autre des éléments suivants :
Limite de mémoire sur un conteneur.
Options de configuration GCHeapHardLimit ou GCHeapHardLimitPercent Runtime.
Le garbage collector utilise les informations suivantes pour déterminer si les objets sont vivants :
Racines de pile . Variables de pile fournies par le compilateur juste-à-temps (JIT) et l'explorateur de pile.
Les optimisations JIT peuvent rallonger ou raccourcir les régions de code au sein desquelles les variables de
pile sont signalées au garbage collector.
Handles de garbage collection . Handles qui pointent vers les objets managés qui peuvent être alloués
par le code utilisateur ou par le Common Language Runtime.
Données statiques . Objets statiques des domaines d'application qui pourraient référencer d'autres objets.
Chaque domaine d'application effectue le suivi de ses objets statiques.
Avant qu'une opération garbage collection ne démarre, tous les threads managés sont suspendus à l'exception du
thread qui a déclenché l'opération.
L'illustration suivante montre un thread qui déclenche un garbage collection et entraîne l'interruption des autres
threads.
Ressources non managées
Pour la plupart des objets créés par votre application, vous pouvez vous appuyer sur garbage collection pour
effectuer automatiquement les tâches de gestion de mémoire nécessaires. Cependant, les ressources non
managées requièrent un nettoyage explicite. Le type le plus répandu de ressource non managée est un objet qui
enveloppe une ressource de système d'exploitation telle qu'un handle de fichier ou de fenêtre ou une connexion
réseau. Bien que le « garbage collector » soit en mesure de suivre la durée de vie d’un objet managé qui encapsule
une ressource non managée, il n’a pas de connaissance spécifique sur le nettoyage de la ressource.
Lorsque vous créez un objet qui encapsule une ressource non managée, il est recommandé de fournir le code
nécessaire pour nettoyer la ressource non managée dans une Dispose méthode publique. En fournissant une
méthode Dispose , vous donnez la possibilité aux utilisateurs de votre objet d’en libérer explicitement la mémoire
lorsqu’ils ont fini de s’en servir. Quand vous utilisez un objet qui encapsule une ressource non managée, veillez à
appeler Dispose si nécessaire.
Vous devez également fournir un moyen pour que vos ressources non managées soient libérées si un
consommateur de votre type oublie d’appeler Dispose . Vous pouvez utiliser un handle sécurisé pour encapsuler
la ressource non managée ou substituer la Object.Finalize() méthode.
Pour plus d’informations sur le nettoyage des ressources non managées, consultez nettoyer les ressources non
managées.

Voir aussi
Garbage collection de station de travail et de serveur
garbage collection d’arrière-plan
Options de configuration pour GC
Garbage collection
Garbage collection de station de travail et de serveur
18/07/2020 • 5 minutes to read • Edit Online

Le garbage collector s'ajuste automatiquement et peut travailler dans une large gamme de scénarios. Toutefois,
vous pouvez définir le type de garbage collection en fonction des caractéristiques de la charge de travail. Le CLR
fournit les types de garbage collection suivants :
Station de travail garbage collection (GC), conçue pour les applications clientes. Il s’agit de la version de
catalogue global par défaut pour les applications autonomes. Pour les applications hébergées, par exemple,
celles hébergées par ASP.NET, l’hôte détermine la version GC par défaut.
Le garbage collection de station de travail peut être simultané ou non simultané. La garbage collection
simultanée (ou d' arrière-plan) permet aux threads managés de continuer à fonctionner pendant une
garbage collection. L' garbage collection d’arrière-plan remplace les garbage collection simultanées dans
.NET Framework 4 et versions ultérieures.
Garbage collection de serveur, prévu pour les applications serveur qui ont besoin d'un débit et d'une
extensibilité.
Dans .NET Core, les garbage collection serveur peuvent être non simultanés ou en arrière-plan.
Dans .NET Framework 4,5 et versions ultérieures, le garbage collection de serveur peut être non
simultané ou en arrière-plan. Dans .NET Framework 4 et versions antérieures, le garbage collection
de serveur n’est pas simultané.
L’illustration suivante montre les threads dédiés qui exécutent les garbage collection sur un serveur :

Considérations relatives aux performances


Garbage collector pour station de travail
Voici les considérations liées aux threads et aux performances pour le garbage collection de station de travail :
La collecte se produit sur le thread utilisateur qui a déclenché le garbage collection et reste à la même
priorité. Étant donné que les threads utilisateur sont généralement exécutés à la priorité normale, le
garbage collector (qui s'exécute sur un thread de priorité normale) doit rivaliser avec d'autres threads pour
le temps processeur. (Les threads qui exécutent du code natif ne sont pas suspendus sur le serveur ou la
station de travail garbage collection.)
Le garbage collection de station de travail est toujours utilisé sur un ordinateur doté d’un seul processeur,
quel que soit le paramètre de configuration.
Garbage collector pour serveur
Voici les considérations liées aux threads et aux performances pour le garbage collection de serveur :
La collecte se produit sur plusieurs threads dédiés qui s'exécutent au niveau de priorité
THREAD_PRIORITY_HIGHEST .

Un tas et un thread dédié pour effectuer le garbage collection sont fournis pour chaque UC, et les tas sont
collectés au même moment. Chaque tas contient un tas de petits objets et un tas d'objets volumineux, et
tous les tas peuvent faire l'objet d'accès par du code utilisateur. Les objets des différents tas peuvent faire
référence les uns aux autres.
Étant donné que plusieurs threads de garbage collection fonctionnent ensemble, le garbage collection de
serveur est plus rapide que le garbage collection de station de travail sur un tas de même taille.
Le garbage collection de serveur présente souvent des segments de plus grande taille. Toutefois, il ne s’agit
que d’une généralisation : la taille de segment est spécifique à l’implémentation et peut faire l’objet de
modifications. N’effectuez pas d’hypothèses sur la taille des segments alloués par le garbage collector lors
du paramétrage de votre application.
Le garbage collection de serveur peut consommer beaucoup de ressources. Par exemple, imaginez qu’il y a
12 processus qui utilisent le garbage collector de serveur exécuté sur un ordinateur doté de quatre
processeurs. Si tous les processus se produisent pour collecter les opérations de garbage collection en
même temps, ils interféreraient entre eux, car 12 threads sont planifiés sur le même processeur. Si les
processus sont actifs, il n’est pas judicieux de leur demander d’utiliser le garbage collection de serveur.
Si vous exécutez des centaines d’instances d’une application, envisagez d’utiliser des garbage collection de station
de travail avec garbage collection simultanée désactivée. Cela provoquera moins de changements de contexte, ce
qui peut améliorer les performances.

Voir aussi
garbage collection d’arrière-plan
Options de configuration au moment de l’exécution pour garbage collection
garbage collection d’arrière-plan
18/07/2020 • 6 minutes to read • Edit Online

En arrière-plan garbage collection (GC), les générations éphémères (0 et 1) sont collectées si nécessaire, tandis
que la collection de génération 2 est en cours. L’garbage collection d’arrière-plan est exécuté sur un ou plusieurs
threads dédiés, selon qu’il s’agit de l’arrière-plan ou du garbage collector du serveur, et s’applique uniquement aux
collections de génération 2.
L’garbage collection d’arrière-plan est activé par défaut. Il peut être activé ou désactivé à l’aide du paramètre de
configuration gcConcurrent dans .NET Framework applications ou du paramètre System. gc. concurrent dans les
applications .net core.

NOTE
L’garbage collection d’arrière-plan remplace la garbage collection simultanée et est disponible dans .NET Framework 4 et
versions ultérieures. Dans .NET Framework 4, il est pris en charge uniquement pour les garbage collection de station de
travail . À compter de .NET Framework 4,5, le garbage collection d’arrière-plan est disponible pour les garbage collection de
station de travail et de serveur .

Une collection sur les générations éphémères au cours de la garbage collection d’arrière-plan est appelée garbage
collection de premier plan . Lorsque des opérations garbage collection de premier plan ont lieu, tous les threads
managés sont suspendus.
Lorsque garbage collection d’arrière-plan est en cours et que vous avez alloué assez d’objets dans la génération 0,
le CLR effectue une garbage collection de premier plan de génération 0 ou 1. Le thread de garbage collection
d'arrière-plan dédié vérifie des points sécurisés fréquents de façon à déterminer s'il existe une demande de
garbage collection de premier plan. Le cas échéant, la collecte d'arrière-plan s'interrompt afin que le garbage
collection de premier plan puisse se produire. Une fois le garbage collection de premier plan terminé, l’arrière-
plan dédié garbage collection threads et les threads utilisateur reprennent.
Le garbage collection d'arrière-plan supprime les restrictions d'allocation imposées par le garbage collection
simultané, car des opérations garbage collection éphémères peuvent se produire pendant le garbage collection
d'arrière-plan. L’garbage collection d’arrière-plan peut supprimer des objets morts dans les générations
éphémères. Il peut également développer le tas si nécessaire pendant une garbage collection de la génération 1.

Station de travail en arrière-plan et GC de serveur


À compter de .NET Framework 4,5, le garbage collection d’arrière-plan est disponible pour le garbage collection
de serveur. Le GC d’arrière-plan est le mode par défaut pour le garbage collection serveur.
Le serveur d’arrière-plan garbage collection fonctionne de la même façon que les garbage collection de station de
travail en arrière-plan, mais il existe quelques différences :
La station de travail en arrière-plan garbage collection utilise un thread d’arrière-plan garbage collection
dédié, tandis que le serveur d’arrière-plan garbage collection utilise plusieurs En règle générale, il existe un
thread dédié pour chaque processeur logique.
Contrairement au thread d’arrière-plan de station de travail garbage collection, les threads GC du serveur
d’arrière-plan n’expirent pas.
L’illustration suivante montre les garbage collection de station de travail en arrière-plan effectuées sur un thread
distinct dédié :
L’illustration suivante montre les garbage collection de serveur d’arrière-plan effectuées sur des threads distincts
et dédiés :

Garbage collection simultané


TIP
Cette section s’applique à :
.NET Framework 3,5 et versions antérieures pour stations de travail garbage collection
.NET Framework 4 et versions antérieures pour le serveur garbage collection
Le garbage collection simultané est remplacé par le garbage collection d’arrière-plan dans les versions ultérieures.

Dans les garbage collection de station de travail ou de serveur, vous pouvez activer des garbage collection
simultanées, ce qui permet aux threads de s’exécuter simultanément avec un thread dédié qui exécute le garbage
collection pour la majeure partie de la durée de la collection. Cette option affecte uniquement les garbage
collections de génération 2. les générations 0 et 1 sont toujours non simultanées car elles se terminent
rapidement.
Le garbage collection simultané permet aux applications interactives d'être plus réactives en réduisant les pauses
d'une collection. Les threads managés peuvent continuer à s'exécuter une grande partie de la durée du garbage
collection simultané. Cette conception produit des pauses plus courtes pendant qu’un garbage collection se
produit.
Le garbage collection simultané est exécuté sur un thread dédié. Par défaut, le CLR exécute des garbage collection
de station de travail avec des garbage collection simultanées activées sur des ordinateurs monoprocesseurs et
multiprocesseurs.
L'illustration suivante montre le garbage collection simultané exécuté sur un thread dédié différent.

Voir aussi
Garbage collection de station de travail et de serveur
Options de configuration au moment de l’exécution pour garbage collection
Tas de grands objets sur les systèmes Windows
18/07/2020 • 31 minutes to read • Edit Online

Le garbage collector (GC) .NET divise les objets en objets petits et grands. Quand un objet est grand, certains de
ses attributs prennent plus d’importance que s’il est petit. Par exemple, si vous la compactez — , la copie en
mémoire ailleurs sur le tas — peut être coûteuse. Pour cette raison, le garbage collector place des objets
volumineux sur le tas d’objets volumineux (LOH). Cet article explique ce qui qualifie un objet en tant qu’objet
volumineux, la façon dont les objets volumineux sont collectés et les implications en termes de performances
imposées par les objets volumineux.

IMPORTANT
Cet article décrit le tas d’objets volumineux dans .NET Framework et .NET Core s’exécutant sur des systèmes Windows
uniquement. Elle ne couvre pas le LOH exécuté sur des implémentations de .NET sur d’autres plateformes.

Comment un objet se termine sur le LOH


Si la taille d’un objet est supérieure ou égale à 85 000 octets, il est considéré comme un objet volumineux. Ce
chiffre a été déterminé par le réglage des performances. Quand une demande d’allocation d’objet est de 85 000
octets ou plus, le runtime l’alloue sur le tas de grands objets.
Pour comprendre ce que cela signifie, il est utile d’examiner quelques notions de base sur le garbage collector.
Le garbage collector est un collecteur générationnel. Il a trois générations : génération 0, génération 1 et
génération 2. La raison de ces 3 générations est que la plupart des objets meurent dans la génération 0 (dans une
application optimisée). Par exemple, dans une application serveur, les allocations associées à chaque demande
doivent mourir une fois la demande terminée. Les demandes d’allocation en cours passent en génération 1 et y
meurent. La génération 1 joue, pour ainsi dire, le rôle de tampon entre les zones d’objets jeunes et les zones
d’objets qui vivent déjà depuis un certain temps.
Les petits objets sont toujours alloués dans la génération 0 et, selon leur durée de vie, peuvent être promus dans
les générations 1 ou 2. Les grands objets sont toujours alloués dans la génération 2.
Les grands objets appartiennent à la génération 2 parce qu’ils sont nettoyés uniquement lors d’un nettoyage de la
génération 2. Quand une génération est nettoyée, ses générations plus jeunes sont également nettoyées. Par
exemple, pendant le nettoyage de la mémoire (GC, Garbage Collection) de la génération 1, les générations 1 et 0
sont toutes deux nettoyées. De la même façon, pendant le GC de la génération 2, le tas tout entier est nettoyé. Pour
cette raison, un GC de la génération 2 est également appelé GC complet. Cet article fait référence au GC de la
génération 2 et non au GC complet, mais les termes sont interchangeables.
Les générations fournissent une vue logique du tas du récupérateur de mémoire. Physiquement, les objets vivent
dans des segments de tas managés. Un segment de tas managé est un bloc de mémoire que le récupérateur de
mémoire réserve sur le système d’exploitation en appelant la fonction VirtualAlloc pour le compte du code
managé. Lorsque le CLR est chargé, le garbage collector alloue deux segments de tas initiaux : un pour les petits
objets (le tas de petits objets, ou SOH) et l’autre pour les objets volumineux (le tas d’objets volumineux).
Les demandes d’allocation sont alors traitées en plaçant des objets managés sur ces segments de tas managés. Si
l’objet est inférieur à 85 000 octets, il est placé sur un segment SOH, sinon, sur un segment LOH. Les segments
sont réservés (en blocs plus petits) à mesure que leur nombre d’objets alloués augmente. Pour le SOH, les objets
qui survivent à un GC sont promus à la génération suivante. Les objets qui survivent à un nettoyage de la
génération 0 sont considérés comme des objets de génération 1, et ainsi de suite. Toutefois, les objets qui
survivent à la plus vieille génération sont toujours considérés comme des objets de cette génération. En d’autres
termes, les survivants de la génération 2 sont des objets de la génération 2 et les survivants du LOH sont des
objets du LOH (qui sont nettoyés avec la génération 2).
Le code d’utilisateur peut seulement allouer dans la génération 0 (petits objets) ou le LOH (grands objets). Seul le
récupérateur de mémoire peut « allouer » des objets dans la génération 1 (en promouvant les survivants de la
génération 0) et la génération 2 (en promouvant les survivants des générations 1 et 2).
Quand un nettoyage de la mémoire est déclenché, le récupérateur de mémoire repère les objets en vie et les
compacte. Parce que le compactage coûte cher, le récupérateur de mémoire balaye le LOH et dresse une liste des
objets morts qui peuvent être réutilisés plus tard pour répondre aux demandes d’allocation des grands objets. Les
objets morts adjacents sont transformés en un seul objet libre.
Le .NET Framework (à partir de .NET Framework 4.5.1) et .NET Core intègrent la propriété
GCSettings.LargeObjectHeapCompactionMode qui permet aux utilisateurs de spécifier que le LOH doit être
compacté au prochain GC bloquant complet. Par la suite, .NET peut décider de compacter le LOH
automatiquement. Donc, si vous allouez des grands objets et voulez garantir qu'ils ne bougent pas, vous devez
quand même les épingler.
La figure 1 illustre un scénario dans lequel le récupérateur de mémoire forme la génération 1 après le premier GC
de la génération 0 où Obj1 et Obj3 sont morts, et forme la génération 2 après le premier GC de la génération 1
où Obj2 et Obj5 sont morts. Notez que cette figure et les suivantes sont uniquement à titre d’illustration. Elles
contiennent très peu d’objets pour mieux montrer ce qui se passe sur le tas. En réalité, un GC implique
généralement bien plus d’objets.

Figure 1 : GC des générations 0 et 1.


La figure 2 montre qu’après un GC de la génération 2 qui a vu que Obj1 et Obj2 étaient morts, le récupérateur
de mémoire forme un espace libre contigu dans la mémoire qui était auparavant occupée par Obj1 et Obj2 ,
lequel est ensuite utilisé pour répondre à une demande d’allocation concernant Obj4 . L’espace entre le dernier
objet Obj3 et la fin du segment peut aussi être utilisé pour répondre aux demandes d’allocation.
Figure 2 : Après un GC de la génération 2
Si l’espace libre est insuffisant pour répondre aux demandes d’allocation des grands objets, le récupérateur de
mémoire tente d’acquérir d’autres segments du système d’exploitation. En cas d’échec, il déclenche un GC de la
génération 2 pour tenter de libérer l’espace.
Pendant un GC de la génération 1 ou 2, le récupérateur de mémoire libère les segments qui n’ont pas d’objet en vie
et les rend au système d’exploitation en appelant la fonction VirtualFree. La réservation de l’espace entre le dernier
objet en vie et la fin du segment est annulée (sauf sur le segment éphémère, où vivent les générations 0 et 1, sur
lequel le récupérateur de mémoire maintient la réservation pour que votre application puisse l’utiliser
immédiatement). Par ailleurs, les espaces libres restent réservés bien qu’ils soient réinitialisés, ce qui signifie que le
système d’exploitation n’a pas besoin d’écrire de données dans ces espaces une fois revenus sur le disque.
Comme que le LOH est collecté uniquement pendant le GC de la génération 2, le segment LOH peut seulement
être libéré pendant ce GC. La figure 3 illustre un scénario où le récupérateur de mémoire rend un segment
(segment 2) au système d’exploitation et annule la réservation d’espace supplémentaire sur les segments restants.
S’il doit utiliser l’espace libéré à la fin du segment pour répondre aux demandes d’allocation de grands objets, il
réserve de nouveau la mémoire. (Pour obtenir une explication de la réservation/libération, consultez la
documentation de VirtualAlloc.

Figure 3 : LOH après un GC de la génération 2


Quand un grand objet est-il collecté ?
En général, un GC se produit dans l’une des trois conditions suivantes :
L’allocation dépasse le seuil des grands objets ou de la génération 0.
Le seuil est une propriété des générations. Le seuil d’une génération est défini quand le récupérateur de
mémoire lui alloue des objets. Quand le seuil est dépassé, un GC est déclenché sur cette génération. Quand
vous allouez des petits ou des grands objets, vous consommez les seuils de la génération 0 et du LOH,
respectivement. Quand le récupérateur de mémoire alloue des objets dans les générations 1 et 2, il
consomme leurs seuils. Ces seuils sont réglés dynamiquement pendant l’exécution du programme.
C’est le cas par défaut. La plupart des GC se produisent suite à des allocations sur le tas managé.
La méthode GC.Collect est appelée.
Si la méthode GC.Collect() sans paramètre est appelée ou qu’une autre surcharge reçoit GC.MaxGeneration
comme argument, le LOH est nettoyé avec le reste du tas managé.
Le système est en situation d’insuffisance de mémoire.
Cela se produit quand le récupérateur de mémoire reçoit une notification de mémoire haute du système
d’exploitation. Si le récupérateur de mémoire pense qu’un GC de la génération 2 peut être productif, il le
déclenche.

Implications sur les performances de LOH


Les allocations sur le tas de grands objets impacte les performances des façons suivantes.
Coût d’allocation.
Le CLR garantit que la mémoire allouée pour chaque nouvel objet est libérée. Cela signifie que le coût
d’allocation d’un grand objet est complètement dominé par la libération de la mémoire (sauf s’il déclenche
un GC). S’il faut 2 cycles pour libérer un octet, il faut 170 000 cycles pour libérer le plus petit des grands
objets. Pour libérer la mémoire d’un objet de 16 Mo sur une machine de 2 GHz, il faut environ 16 ms. C’est
un coût plutôt élevé.
Coût de nettoyage.
Comme le LOH et la génération 2 sont nettoyés ensemble, si le seuil de l’un des deux est dépassé, un
nettoyage de la génération 2 est déclenché. Si le nettoyage de la génération 2 est déclenché à cause du LOH,
la génération 2 n’est pas forcément plus petite après le GC. Si la génération 2 n’a pas beaucoup de données,
l’impact est minime. En revanche, si la génération 2 est grande, le nettoyage peut entraîner des problèmes
de performances s’il faut déclencher plusieurs GC sur la génération 2. Si de nombreux grands objets sont
alloués de façon très temporaire et que vous avez un grand SOH, vous risquez de passer trop de temps sur
les GC. Par ailleurs, le coût d’allocation vient s’ajouter si vous continuez d’allouer et de libérer de très grands
objets.
Éléments de tableau avec des types référence.
Les très grands objets sur le LOH sont généralement des tableaux (il est très rare d’avoir un objet d’instance
très grand). Si les éléments d’un tableau ont beaucoup de références, le coût est plus élevé. Si l’élément n’a
aucune référence, le récupérateur de mémoire n’a pas besoin de traiter le tableau. Par exemple, si vous
utilisez un tableau pour stocker des nœuds dans une arborescence binaire, vous pouvez l’implémenter en
référençant les nœuds droit et gauche d’un nœud comme étant les nœuds eux-mêmes :
class Node
{
Data d;
Node left;
Node right;
};

Node[] binary_tr = new Node [num_nodes];

Si num_nodes est grand, le récupérateur de mémoire doit traiter au moins deux références par élément. Une
autre méthode est de stocker l’index des nœuds droit et gauche :

class Node
{
Data d;
uint left_index;
uint right_index;
} ;

Au lieu de référencer les données du nœud gauche comme left.d , vous les référencez comme
binary_tr[left_index].d . Ainsi, le récupérateur de mémoire n’a pas besoin d’examiner les références des
nœuds gauche et droit.
Des trois facteurs, les deux premiers ont généralement plus d’impact que le troisième. Pour cette raison, nous vous
recommandons d’allouer un pool de grands objets que vous réutilisez au lieu d’allouer des objets temporaires.

Collecter les données de performances du LOH


Avant de collecter des données de performances pour une zone spécifique, vous devez déjà avoir effectué les
étapes suivantes :
1. Rechercher les raisons d’examiner cette zone.
2. Examiner toutes les autres zones connues sans trouver ce qui pourrait expliquer le problème de
performances rencontré.
Consultez le blog Understand the problem before you try to find a solution (Comprendre le problème avant
d’essayer de chercher une solution) pour plus d’informations sur les principes fondamentaux de la mémoire et du
processeur.
Vous pouvez utiliser les outils suivants pour collecter des données sur les performances du LOH :
Compteurs de performance pour la mémoire CRL .NET
Événements ETW
Débogueur
Compteurs de performances pour la mémoire CRL .NET
Ces compteurs de performances sont une bonne première étape pour rechercher les problèmes de performances
(même si nous vous recommandons d’utiliser les événements ETW). Vous configurez le moniteur de performances
en ajoutant les compteurs souhaités, comme le montre la Figure 4. Ceux qui sont pertinents pour le LOH sont les
suivants :
Collections de la génération 2
Affiche le nombre d’occurrences de GC de la génération 2 depuis le démarrage du processus. Ce compteur
est incrémenté à la fin de chaque nettoyage de la génération 2 (aussi appelé nettoyage complet de la
mémoire). Ce compteur affiche la dernière valeur observée.
Taille du tas d’objets volumineux
Affiche la taille actuelle du LOH en octets (y compris l’espace libre). Ce compteur est actualisé à la fin de
chaque garbage collection, et non à chaque allocation.
En général, vous surveillez les compteurs de performances par le biais du moniteur de performances
(PerfMon.exe). Utilisez « Ajouter des compteurs » pour ajouter le compteur de votre choix pour les processus qui
vous intéressent. Vous pouvez enregistrer les données des compteurs de performances dans un fichier journal,
comme illustré dans la figure 4 :

Figure 4 : LOH après un GC de la génération 2


Les compteurs de performances peuvent également être interrogés par programmation. Beaucoup d’utilisateurs
les collectent de cette façon dans le cadre de leur processus de test normal. S’ils repèrent des compteurs avec des
valeurs anormales, ils utilisent d’autres moyens d’obtenir des données plus détaillées pour les aider dans leurs
recherches.

NOTE
Nous vous recommandons d’utiliser les événements ETW au lieu des compteurs de performances, car ETW fournit des
informations plus détaillées.

Événements ETW
Le récupérateur de mémoire fournit un riche ensemble d’événements ETW pour vous aider à comprendre ce que
fait le tas et pourquoi. Les billets de blog suivants décrivent comment collecter et comprendre les événements GC
avec ETW :
Événements ETW GC-1
Événements ETW de GC - 2
Événements ETW de GC - 3
Événements ETW de GC - 4
Pour identifier le nombre excessif de GC de la génération 2 dus à des allocations de LOH temporaires, observez la
colonne Raison du déclencheur pour les GC. Pour un test simple qui alloue uniquement des grands objets
temporaires, vous pouvez collecter des informations sur les événements ETW avec la ligne de commande PerfView
suivante :

perfview /GCCollectOnly /AcceptEULA /nogui collect

Le résultat ressemble à ceci :


Figure 5 : Événements ETW affichés à l’aide de PerfView
Comme vous pouvez le voir, tous les GC sont effectués sur la génération 2 et ils sont déclenchés par AllocLarge, ce
qui signifie que c’est l’allocation d’un grand objet qui a déclenché ce GC. Nous savons que ces allocations sont
temporaires parce que la colonne % de taux de sur vie LOH indique 1 %.
Vous pouvez collecter d’autres événements ETW qui vous indiquent qui a alloué ces grands objets. La ligne de
commande suivante :

perfview /GCOnly /AcceptEULA /nogui collect

collecte un événement AllocationTick qui est déclenché toutes les 100 000 allocations environ. En d’autres termes,
un événement est déclenché chaque fois qu’un grand objet est alloué. Vous pouvez alors examiner une des vues
d’allocation de tas du récupérateur de mémoire qui indique les pile d’appels qui ont alloué des grands objets :

Figure 6 : Une vue d’allocation de tas du récupérateur de mémoire


Comme vous pouvez le voir, il s’agit d’un test très simple qui alloue simplement de grands objets à partir de sa
méthode Main .
Débogueur
Si tout ce que vous avez est un vidage de mémoire et que vous devez examiner les objets qui se trouvent sur le
LOH, vous pouvez utiliser l’extension de débogueur SoS fournie par .NET.

NOTE
Les commandes de débogage indiquées dans cette section sont applicables aux débogueurs Windows.

Le code suivant illustre un exemple de sortie de l’analyse du LOH :

0:003> .loadby sos mscorwks


0:003> !eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x013e35ec
sdgeneration 1 starts at 0x013e1b6c
generation 2 starts at 0x013e1000
ephemeral segment allocation context: none
segment begin allocated size
0018f2d0 790d5588 790f4b38 0x0001f5b0(128432)
013e0000 013e1000 013e35f8 0x000025f8(9720)
Large object heap starts at 0x023e1000
segment begin allocated size
023e0000 023e1000 033db630 0x00ffa630(16754224)
033e0000 033e1000 043cdf98 0x00fecf98(16699288)
043e0000 043e1000 05368b58 0x00f87b58(16284504)
Total Size 0x2f90cc8(49876168)
------------------------------
GC Heap Size 0x2f90cc8(49876168)
0:003> !dumpheap -stat 023e1000 033db630
total 133 objects
Statistics:
MT Count TotalSize Class Name
001521d0 66 2081792 Free
7912273c 63 6663696 System.Byte[]
7912254c 4 8008736 System.Object[]
Total 133 objects

La taille du tas LOH est (16 754 224 + 16 699 288 + 16 284 504) = 49 738 016 octets. Entre les adresses
023e1000 et 033db630, 8 008 736 octets sont occupés par un tableau d’objets System.Object, 6 663 696 octets
sont occupés par un tableau d’objets System.Byte et 2 081 792 octets sont occupés par de l’espace libre.
Parfois, le débogueur montre que la taille totale du LOH est inférieure à 85 000 octets. C’est parce que le runtime
lui-même utilise le LOH pour allouer des objets dont la taille est inférieure à celle d’un grand objet.
Comme le LOH n’est pas compacté, il est parfois perçu comme la source de la fragmentation. Une fragmentation
peut désigner :
La fragmentation du tas managé, indiquée par la quantité d’espace libre entre les objets managés. Dans
SoS, la commande !dumpheap –type Free affiche la quantité d’espace libre entre les objets managés.
La fragmentation de l’espace d’adressage de mémoire virtuelle, qui est la mémoire marquée comme
MEM_FREE . Vous pouvez l’obtenir à l’aide de diverses commandes de débogueur dans windbg.

L’exemple suivant montre une fragmentation dans l’espace de mémoire virtuelle :


0:000> !address
00000000 : 00000000 - 00010000
Type 00000000
Protect 00000001 PAGE_NOACCESS
State 00010000 MEM_FREE
Usage RegionUsageFree
00010000 : 00010000 - 00002000
Type 00020000 MEM_PRIVATE
Protect 00000004 PAGE_READWRITE
State 00001000 MEM_COMMIT
Usage RegionUsageEnvironmentBlock
00012000 : 00012000 - 0000e000
Type 00000000
Protect 00000001 PAGE_NOACCESS
State 00010000 MEM_FREE
Usage RegionUsageFree
… [omitted]
-------------------- Usage SUMMARY --------------------------
TotSize ( KB) Pct(Tots) Pct(Busy) Usage
701000 ( 7172) : 00.34% 20.69% : RegionUsageIsVAD
7de15000 ( 2062420) : 98.35% 00.00% : RegionUsageFree
1452000 ( 20808) : 00.99% 60.02% : RegionUsageImage
300000 ( 3072) : 00.15% 08.86% : RegionUsageStack
3000 ( 12) : 00.00% 00.03% : RegionUsageTeb
381000 ( 3588) : 00.17% 10.35% : RegionUsageHeap
0 ( 0) : 00.00% 00.00% : RegionUsagePageHeap
1000 ( 4) : 00.00% 00.01% : RegionUsagePeb
1000 ( 4) : 00.00% 00.01% : RegionUsageProcessParametrs
2000 ( 8) : 00.00% 00.02% : RegionUsageEnvironmentBlock
Tot: 7fff0000 (2097088 KB) Busy: 021db000 (34668 KB)

-------------------- Type SUMMARY --------------------------


TotSize ( KB) Pct(Tots) Usage
7de15000 ( 2062420) : 98.35% : <free>
1452000 ( 20808) : 00.99% : MEM_IMAGE
69f000 ( 6780) : 00.32% : MEM_MAPPED
6ea000 ( 7080) : 00.34% : MEM_PRIVATE

-------------------- State SUMMARY --------------------------


TotSize ( KB) Pct(Tots) Usage
1a58000 ( 26976) : 01.29% : MEM_COMMIT
7de15000 ( 2062420) : 98.35% : MEM_FREE
783000 ( 7692) : 00.37% : MEM_RESERVE

Largest free region: Base 01432000 - Size 707ee000 (1843128 KB)

Souvent, la fragmentation de mémoire virtuelle est causée par des grands objets temporaires qui obligent le
récupérateur de mémoire à fréquemment acquérir de nouveaux segments de tas managé du système
d’exploitation et lui en rendre des vides.
Pour vérifier si le LOH provoque une fragmentation de mémoire virtuelle, vous pouvez définir un point d’arrêt sur
VirtualAlloc et VirtualFree et voir qui les appelle. Par exemple, pour voir qui a essayé d’allouer des blocs de
mémoire virtuelle de système d’exploitation supérieurs à 8 Mo, vous pouvez définir un point d’arrêt de la façon
suivante :

bp kernel32!virtualalloc "j (dwo(@esp+8)>800000) 'kb';'g'"

Cette commande s’arrête dans le débogueur et affiche la pile des appels uniquement si VirtualAlloc est appelé
avec une taille d’allocation supérieure à 8 Mo (0x800000).
CLR 2.0 a ajouté une fonctionnalité appelée VM Hoarding (Réserve de mémoire virtuelle) qui peut être utile dans
les scénarios où des segments (y compris ceux des tas de petits et grands objets) sont fréquemment acquis et
libérés. Pour utiliser la fonctionnalité VM Hoarding, vous spécifiez un indicateur de démarrage appelé
STARTUP_HOARD_GC_VM via l’API d’hébergement. Au lieu de renvoyer des segments vides au système d’exploitation, le
CLR annule la réservation de mémoire sur ces segments et les met sur liste d’attente. (Notez que le CLR ne le fait
pas pour les segments trop grands.) Le CLR utilise ensuite ces segments pour répondre aux nouvelles demandes
de segment. La prochaine fois que votre application a besoin d’un nouveau segment, le CLR en utilise un de cette
liste d’attente, s’il est assez grand.
Les Hoarding d’ordinateur virtuel sont également utiles pour les applications qui souhaitent tenir sur les segments
qu’ils ont déjà acquis, par exemple certaines applications serveur qui sont des applications dominantes s’exécutant
sur le système, afin d’éviter les exceptions de mémoire insuffisante.
Nous vous recommandons vivement de tester soigneusement votre application quand vous utilisez cette
fonctionnalité, pour vérifier qu’elle utilise la mémoire de façon suffisamment stable.
Garbage Collection et niveau de performance
18/07/2020 • 49 minutes to read • Edit Online

Cette rubrique décrit les problèmes liés au garbage collection et à l’utilisation de la mémoire. Elle apporte des
solutions aux problèmes concernant les tas managés et explique comment réduire l’effet du garbage collection sur
vos applications. Chaque problème contient des liens vers des procédures à suivre pour résoudre le problème.

Outils d'analyse des performances


Les sections suivantes décrivent les outils disponibles pour analyser les problèmes liés à l’utilisation de la mémoire
et au garbage collection. Les procédures fournies plus loin dans cette rubrique font référence à ces outils.
Compteurs de performance mémoire
Vous pouvez utiliser les compteurs de performances pour collecter des données sur les performances. Pour obtenir
des instructions, voir Génération de profils d'exécution. La catégorie Mémoire CLR .NET des compteurs de
performances, telle qu'elle est décrite dans Compteurs de performances dans le .NET Framework, fournit des
informations sur le garbage collector.
Débogage avec l'extension SOS
Vous pouvez utiliser le Débogueur Windows (WinDbg) pour inspecter les objets du tas managé.
Pour installer WinDbg, installez les outils de débogage pour Windows à partir de la page Outils de débogage pour
Windows.
Événements ETW de garbage collection
Le suivi d'événements pour Windows (ETW) est un système de suivi qui complète la prise en charge du profilage et
du débogage fournie par .NET Framework. À compter de .NET Framework 4, les événements ETW de garbage
collection capturent des informations utiles pour l’analyse du tas managé d’un point de vue statistique. Par
exemple, l'événement GCStart_V1 , qui est déclenché quand un garbage collection est sur le point de se produire,
fournit les informations suivantes :
La génération d'objets qui est collectée
Ce qui a déclenché le garbage collection
Le type de garbage collection (simultané ou non simultané)
La journalisation des événements ETW est efficace et ne masque pas les problèmes de performances liés au
garbage collection. Un processus peut fournir ses propres événements conjointement aux événements ETW. Quand
ils sont journalisés, les événements d'application et les événements de garbage collection peuvent être corrélés
pour déterminer quand et comment les problèmes de tas se produisent. Par exemple, une application serveur peut
fournir des événements au début et à la fin d'une demande client.
L'API de profilage
Les interfaces de profilage du common language runtime (CLR) fournissent des informations détaillées sur les
objets qui ont été affectés pendant le garbage collection. Un profileur peut être informé quand un garbage
collection commence et se termine. Il peut fournir des rapports sur les objets du tas managé, y compris une
identification des objets au cours de chaque génération. Pour plus d'informations, voir Vue d'ensemble du
profilage.
Les profileurs peuvent fournir des informations complètes. Toutefois, les profileurs complexes peuvent
potentiellement modifier le comportement d'une application.
Analyse de ressource de domaine d'application
À compter de .NET Framework 4, l’outil ARM (Application Domain Resource Monitoring) permet aux hôtes de
superviser l’utilisation du processeur et de la mémoire par domaine d’application. Pour plus d'informations, voir
Analyse de ressource de domaine d'application.

Résolution des problèmes de performances


La première étape consiste à déterminer si le problème est effectivement lié au garbage collection. Si c'est le cas,
cliquez sur l'un des liens suivants pour résoudre le problème.
Une exception de mémoire insuffisante est levée
Le processus utilise trop de mémoire
Le garbage collector ne récupère pas les objets suffisamment rapidement
Le tas managé est trop fragmenté
Les pauses du nettoyage de la mémoire sont trop longues
La génération 0 est trop volumineuse
L’utilisation du processeur pendant une garbage collection est trop élevée
Problème : une exception de mémoire insuffisante est levée
Il existe deux cas légitimes pour la levée d'une OutOfMemoryException managée :
Une mémoire virtuelle insuffisante.
Le garbage collector alloue la mémoire depuis le système en segments d'une taille prédéterminée. Si une
allocation requiert un segment supplémentaire, mais qu'il n'existe aucun bloc contigu libre dans l'espace de
mémoire virtuelle du processus, l'allocation du tas managé échoue.
Ne pas avoir suffisamment de mémoire physique à allouer.

C O N T RÔ L E DES P ERF O RM A N C ES

Déterminez si l’exception de mémoire insuffisante est gérée.

Identifiez la quantité de mémoire virtuelle pouvant être réservée.

Déterminez si la mémoire physique est suffisante.

Si vous déterminez que l'exception n'est pas légitime, contactez le support technique Microsoft et préparez-vous à
fournir les informations suivantes :
La pile où a été levée l'exception d'insuffisance de mémoire managée.
Le fichier complet de vidage mémoire.
Les données qui prouvent qu'il ne s'agit pas d'une exception de mémoire insuffisante légitime, y compris les
données indiquant que ni la mémoire virtuelle ni la mémoire physique ne posent un problème.
Problème : le processus utilise trop de mémoire
On suppose souvent que l’utilisation de la mémoire qui s’affiche sous l’onglet Performances du Gestionnaire des
tâches Windows peut indiquer à l’utilisateur que trop de mémoire est utilisée. Toutefois, cet affichage concerne le
jeu de travail et ne fournit pas d'informations sur l'utilisation de la mémoire virtuelle.
Si vous déterminez que le problème est provoqué par le tas managé, vous devez examiner le tas managé dans le
temps pour déterminer les éventuels schémas récurrents.
Si vous déterminez que le problème n'est pas provoqué par le tas managé, vous devez utiliser le débogage natif.

C O N T RÔ L E DES P ERF O RM A N C ES

Identifiez la quantité de mémoire virtuelle pouvant être réservée.

Identifiez la quantité de mémoire validée par le tas managé.

Identifiez la quantité de mémoire réservée par le tas managé.

Identifiez les objets volumineux dans la génération 2.

Identifiez les références aux objets.

Problème : le garbage collector ne récupère pas les objets suffisamment rapidement


Quand les objets semblent ne pas être récupérés comme prévu pour le garbage collection, vous devez déterminer
s'il existe des références solides à ces objets.
Vous pouvez également rencontrer ce problème si aucun garbage collection n'a eu lieu pour la génération qui
contient un objet mort, ce qui indique que le finaliseur de l'objet mort n'a pas été exécuté. Par exemple, ceci est
possible quand vous exécutez une application à thread unique cloisonné (STA) et que le thread qui gère la file
d'attente du finaliseur ne peut pas appeler depuis celle-ci.

C O N T RÔ L E DES P ERF O RM A N C ES

Vérifiez les références aux objets.

Déterminez si un finaliseur a été exécuté.

Déterminez s’il y a des objets en attente de finalisation.

Problème : le tas managé est trop fragmenté


Le niveau de fragmentation est calculé comme le rapport entre l'espace libre et le total de la mémoire allouée pour
la génération. Pour la génération 2, un niveau acceptable de fragmentation ne doit pas dépasser 20 %. Étant donné
que la génération 2 peut devenir très volumineuse, le taux de fragmentation est plus important que la valeur
absolue.
L'espace libre n'est pas une préoccupation pour la génération 0, car il s'agit de la génération où les nouveaux objets
sont alloués.
La fragmentation se produit toujours dans le tas des objets volumineux, car il n'est pas compacté. Les objets libres
qui sont adjacents sont réduits naturellement dans un même espace pour répondre aux requêtes d'allocation des
objets volumineux.
La fragmentation peut devenir un problème avec la génération 1 et la génération 2. Si ces générations disposent
d'une grande quantité d'espace libre après un garbage collection, l'utilisation des objets d'une application peut
nécessiter des modifications et vous devrez envisager de réévaluer la durée de vie des objets à long terme.
L'épinglage excessif d'objets peut augmenter la fragmentation. Si la fragmentation est élevée, un trop grand
nombre d’objets a pu être épinglé.
Si la fragmentation de la mémoire virtuelle empêche le garbage collector d'ajouter des segments, la cause peut se
trouver dans la liste ci-dessous :
Le chargement et le déchargement fréquents de nombreux petits assemblys.
Des références trop nombreuses à des objets COM en cas d'interopérabilité avec du code non managé.
La création d'objets transitoires volumineux, ce qui entraîne l'allocation et la libération fréquentes de
segments par le tas des objets volumineux.
Lors de l'hébergement du CLR, une application peut demander que le garbage collector conserve ses
segments. Cela réduit la fréquence des allocations de segments. Pour cela, utilisez la balise
STARTUP_HOARD_GC_VM dans l'énumération STARTUP_FLAGS.

C O N T RÔ L E DES P ERF O RM A N C ES

Identifiez la quantité d’espace libre dans le tas managé.

Identifiez le nombre d’objets épinglés.

Si vous pensez qu'il n'existe aucune cause légitime à la fragmentation, contactez le support technique Microsoft.
Problème : les pauses du garbage collection sont trop longues
Le garbage collection fonctionne en temps réel souple. Les applications doivent donc être en mesure de tolérer
quelques pauses. L'un des critères du temps réel souple est que 95 % des opérations doivent se terminer à temps.
Avec le garbage collection simultané, les threads managés sont autorisés à s'exécuter pendant une collection, ce
qui signifie que les pauses sont très minimes.
Les garbage collection éphémères (générations 0 et 1) ne durent que quelques millisecondes. La réduction du
temps de pause n'est donc généralement pas possible. Toutefois, vous pouvez réduire les temps de pause des
collections de génération 2 en modifiant le modèle des demandes d’allocation effectuées par une application.
Une autre méthode plus précise est celle qui consiste à utiliser les événements ETW de garbage collection. Vous
pouvez rechercher le minutage des collections en ajoutant les différences de timestamp pour une séquence
d’événements. L’intégralité de la séquence de collection inclut la suspension du moteur d’exécution, le garbage
collection proprement dit et la reprise du moteur d’exécution.
Vous pouvez utiliser les notifications de garbage collection pour déterminer si un serveur est sur le point de
disposer d’une collection de génération 2, et si la redirection des demandes vers un autre serveur peut atténuer les
problèmes liés aux pauses.

C O N T RÔ L E DES P ERF O RM A N C ES

Identifiez la durée d’un nettoyage de la mémoire.

Déterminez ce qui a provoqué un nettoyage de la mémoire.

Problème : la génération 0 est trop volumineuse


La génération 0 est susceptible de contenir un plus grand nombre d'objets sur un système 64 bits, notamment si
vous utilisez le garbage collection pour serveur au lieu du garbage collection pour station de travail. En effet, le
seuil auquel se déclenche un garbage collection de génération 0 est plus élevé dans ces environnements, et les
collections de génération 0 peuvent donc être beaucoup plus volumineuses. Les performances sont améliorées
quand une application alloue plus de mémoire avant qu'un garbage collection ne soit déclenché.
Problème : l'utilisation du processeur pendant un garbage collection est trop élevée
L'utilisation du processeur est élevée pendant les garbage collection. Si un temps de processus suffisant est
consacré à un garbage collection, les collections seront trop fréquent ou la collection durera trop longtemps. Un
taux d'allocation d'objets plus élevé sur le tas managé entraîne des garbage collection plus fréquents. La
diminution du taux d'allocation réduit la fréquence des garbage collection.
Vous pouvez surveiller les taux d'allocation à l'aide du compteur de performances Allocated Bytes/second . Pour
plus d'informations, voir Compteurs de performance dans le .NET Framework.
La durée d’une collection est principalement un facteur du nombre d’objets qui survivent après l’allocation. Le
garbage collector devra parcourir une grande quantité de mémoire si de nombreux objets restent à collecter. Le
travail qui consiste à compacter les survivants prend beaucoup de temps. Pour déterminer le nombre d'objets
traités au cours d'une collection, définissez un point d'arrêt dans le débogueur à la fin d'un garbage collection pour
une génération spécifique.

C O N T RÔ L E DES P ERF O RM A N C ES

Déterminez si l’utilisation élevée du processeur est provoquée par le nettoyage de la mémoire.

Définissez un point d’arrêt à la fin du nettoyage de la mémoire.

Instructions de dépannage
Cette section comporte les instructions que vous devez prendre en compte avant de commencer vos recherches.
Garbage collection pour station de travail et pour serveur
Déterminez si vous utilisez le type de garbage collection qui convient. Si votre application utilise plusieurs threads
et instances d'objet, utilisez le garbage collection pour serveur au lieu du garbage collection pour station de travail.
Le garbage collection pour serveur traite plusieurs threads, tandis que le garbage collection pour station de travail
nécessite que plusieurs instances d'une application exécutent leurs propres threads de garbage collection et soient
en concurrence pour le temps processeur.
Une application qui possède une faible charge et qui n'effectue que rarement des tâches en arrière-plan, telles
qu'un service, peut utiliser le garbage collection pour station de travail en désactivant le garbage collection
simultané.
Quand mesurer la taille du tas managé
Sauf si vous utilisez un profileur, vous devrez établir un modèle cohérent de mesure pour diagnostiquer
efficacement les problèmes de performances. Prenez en compte les points suivants pour établir une planification :
Si vous effectuez la mesure après un garbage collection de génération 2, le tas managé ne contiendra aucun
objet mort.
Si vous effectuez la mesure immédiatement après un garbage collection de génération 0, les objets de
génération 1 et 2 n'auront pas encore été collectés.
Si vous effectuez la mesure immédiatement avant un garbage collection, vous mesurerez autant de
l'allocation que possible avant le démarrage du garbage collection.
Effectuer une mesure pendant un garbage collection pose problème, car l'état des structures de données du
garbage collector n'est pas valide pour la traversée et les résultats obtenus peuvent ne pas être complets.
C'est la procédure normale.
Quand utilisez le garbage collection pour station de travail avec le garbage collection simultané, les objets
récupérés ne sont pas compactés, donc la taille du tas peut être identique voire supérieure (la fragmentation
peut le faire apparaître plus volumineux).
Le garbage collection simultané sur la génération 2 est différé quand la charge de mémoire physique est
trop élevée.
La procédure suivante décrit comment définir un point d'arrêt pour mesurer le tas managé.
Pour définir un point d'arrêt à la fin du garbage collection
Dans le débogueur WinDbg, après avoir chargé l’extension SOS, tapez la commande suivante :
bp mscor wks!WKS::GCHeap::Restar tEE "j
(dwo(mscor wks!WKS::GCHeap::GcCondemnedGeneration)==2) ’kb’;’g’"
où GcCondemnedGeneration est défini sur la génération de votre choix. Cette commande requiert des
symboles privés.
Cette commande force un arrêt si Restar tEE est exécuté après la récupération des objets de génération 2
pour le garbage collection.
Dans le garbage collection pour serveur, un seul thread appelle Restar tEE . Ainsi, le point d’arrêt se produit
une seule fois pendant un garbage collection de génération 2.

Procédures de contrôle des performances


Cette section décrit les procédures suivantes permettant d'isoler la cause des problèmes de performances :
Déterminez si le problème est causé par le nettoyage de la mémoire.
Déterminez si l’exception de mémoire insuffisante est gérée.
Identifiez la quantité de mémoire virtuelle pouvant être réservée.
Déterminez si la mémoire physique est suffisante.
Identifiez la quantité de mémoire validée par le tas managé.
Identifiez la quantité de mémoire réservée par le tas managé.
Identifiez les objets volumineux dans la génération 2.
Identifiez les références aux objets.
Déterminez si un finaliseur a été exécuté.
Déterminez s’il y a des objets en attente de finalisation.
Identifiez la quantité d’espace libre dans le tas managé.
Identifiez le nombre d’objets épinglés.
Identifiez la durée d’un nettoyage de la mémoire.
Déterminez ce qui a déclenché le nettoyage de la mémoire.
Déterminez si l’utilisation élevée du processeur est causée par le nettoyage de la mémoire.
Pour déterminer si le problème est causé par le garbage collection
Examinez les deux compteurs de performances de mémoire suivants :
% Temps dans le GC . Affiche le pourcentage de la durée calendaire passé à effectuer un garbage
collection après le dernier cycle de garbage collection. Utilisez ce compteur pour déterminer si le
garbage collector passe trop de temps à libérer de l'espace dans le tas managé. Si le temps passé au
garbage collection est relativement faible, cela peut indiquer un problème de ressources en dehors
du tas managé. Ce compteur peut être inexact si le garbage collection simultané ou le garbage
collection d’arrière-plan sont impliqués.
Nombre total d’octets validés . Affiche la quantité de mémoire virtuelle actuellement validée par le
garbage collector. Utilisez ce compteur pour déterminer si la mémoire consommée par le garbage
collector est trop élevée par rapport à la mémoire totale utilisée par votre application.
La plupart des compteurs de performances de mémoire sont mis à jour à la fin de chaque garbage
collection. Ils peuvent donc ne pas refléter les conditions actuelles à propos desquelles vous voulez obtenir
des informations.
Pour déterminer si l'exception de mémoire insuffisante est managée
1. Dans le débogueur WinDbg ou Visual Studio avec l’extension SOS chargée, tapez la commande d’exception
d’impression (pe ) suivante :
! PE
Si l'exception est managée, OutOfMemoryException s'affiche comme le type d'exception, comme illustré
dans l'exemple suivant.

Exception object: 39594518


Exception type: System.OutOfMemoryException
Message: <none>
InnerException: <none>
StackTrace (generated):

2. Si la sortie ne spécifie pas d'exception, vous devez déterminer de quel thread provient l'exception de
mémoire insuffisante. Tapez la commande suivante dans le débogueur pour afficher tous les threads avec
leurs piles d'appels :
~*kbit
Le thread avec la pile associée aux appels d'exception est indiqué par l'argument RaiseTheException . Il s'agit
de l'objet exception managée.

28adfb44 7923918f 5b61f2b4 00000000 5b61f2b4 mscorwks!RaiseTheException+0xa0

3. Vous pouvez utiliser la commande suivante pour faire un dump des exceptions imbriquées.
!pe -nested
Si vous ne trouvez pas d'exceptions, l'exception de mémoire insuffisante provient de code non managé.
Pour déterminer la quantité de mémoire virtuelle pouvant être réservée
Dans le débogueur WinDbg avec l’extension SOS chargée, tapez la commande suivante pour obtenir la
région libre la plus grande :
!address -summar y
La plus grande région libre est affichée comme dans la sortie suivante.

Largest free region: Base 54000000 - Size 0003A980

Dans cet exemple, la taille de la région libre la plus grande est d'environ 24 000 Ko (3A980 en hexadécimal).
Cette région est beaucoup plus petite que ce dont le garbage collector a besoin pour un segment.
-ou-
Utilisez la commande vmstat :
!vmstat
La région libre la plus grande correspond à la plus grande valeur de la colonne MAXIMUM, comme illustré
dans la sortie suivante.
TYPE MINIMUM MAXIMUM AVERAGE BLK COUNT TOTAL
~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~ ~~~~~~~~~~ ~~~~
Free:
Small 8K 64K 46K 36 1,671K
Medium 80K 864K 349K 3 1,047K
Large 1,384K 1,278,848K 151,834K 12 1,822,015K
Summary 8K 1,278,848K 35,779K 51 1,824,735K

Pour déterminer si la mémoire physique est suffisante


1. Démarrez le Gestionnaire des tâches Windows.
2. Sous l’onglet Performances , regardez la valeur validée. (Dans Windows 7, regardez Validation (Ko) sous
Groupe système .)
Si le Total est proche de la Limite , la mémoire physique devient insuffisante.
Pour déterminer la quantité de mémoire que le tas managé valide
Utilisez le compteur de performances de mémoire # Total committed bytes pour obtenir le nombre d'octets
que le tas managé valide. Le garbage collector valide des parties d'un segment au fur et à mesure des
besoins, et non tous en même temps.

NOTE
N'utilisez pas le compteur de performances # Bytes in all Heaps , car il ne représente pas l'utilisation réelle de la
mémoire par le tas managé. La taille d'une génération est incluse dans cette valeur et correspond à la taille de son
seuil, c'est-à-dire, à la taille qui déclenche un garbage collection si la génération est remplie d'objets. Cette valeur est
donc généralement égale à zéro.

Pour déterminer la quantité de mémoire réservée pour le tas managé


Utilisez le compteur de performances de mémoire # Total reserved bytes .
Le garbage collector réserve de la mémoire sous forme de segments, et vous pouvez déterminer où
démarre un segment à l'aide de la commande eeheap .

IMPORTANT
Même s'il est possible de déterminer la quantité de mémoire que le garbage collector alloue à chaque segment, la
taille d'un segment dépend de l'implémentation et est susceptible de changer à tout moment, y compris lors des
mises à jour périodiques. Votre application ne doit jamais faire d'hypothèses concernant une taille de segment
particulière, ni dépendre de celle-ci. Elle ne doit pas non plus tenter de configurer la quantité de mémoire disponible
pour les allocations de segments.

Dans le débogueur WinDbg ou Visual Studio avec l’extension SOS chargée, tapez la commande suivante :
!eeheap -gc
Le résultat est le suivant :
Number of GC Heaps: 2
------------------------------
Heap 0 (002db550)
generation 0 starts at 0x02abe29c
generation 1 starts at 0x02abdd08
generation 2 starts at 0x02ab0038
ephemeral segment allocation context: none
segment begin allocated size
02ab0000 02ab0038 02aceff4 0x0001efbc(126908)
Large object heap starts at 0x0aab0038
segment begin allocated size
0aab0000 0aab0038 0aab2278 0x00002240(8768)
Heap Size 0x211fc(135676)
------------------------------
Heap 1 (002dc958)
generation 0 starts at 0x06ab1bd8
generation 1 starts at 0x06ab1bcc
generation 2 starts at 0x06ab0038
ephemeral segment allocation context: none
segment begin allocated size
06ab0000 06ab0038 06ab3be4 0x00003bac(15276)
Large object heap starts at 0x0cab0038
segment begin allocated size
0cab0000 0cab0038 0cab0048 0x00000010(16)
Heap Size 0x3bbc(15292)
------------------------------
GC Heap Size 0x24db8(150968)

Les adresses indiquées par "segment" sont les adresses de début des segments.
Pour déterminer les objets volumineux dans la génération 2
Dans le débogueur WinDbg ou Visual Studio avec l’extension SOS chargée, tapez la commande suivante :
!dumpheap –stat
Si le tas managé est volumineux, l’exécution de dumpheap peut prendre un certain temps.
Vous pouvez commencer l'analyse des dernières lignes de la sortie, car elles contiennent les objets qui
utilisent le plus d'espace. Par exemple :

2c6108d4 173712 14591808 DevExpress.XtraGrid.Views.Grid.ViewInfo.GridCellInfo


00155f80 533 15216804 Free
7a747c78 791070 15821400 System.Collections.Specialized.ListDictionary+DictionaryNode
7a747bac 700930 19626040 System.Collections.Specialized.ListDictionary
2c64e36c 78644 20762016 DevExpress.XtraEditors.ViewInfo.TextEditViewInfo
79124228 121143 29064120 System.Object[]
035f0ee4 81626 35588936 Toolkit.TlkOrder
00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[]
791242ec 40182 90664128 System.Collections.Hashtable+bucket[]
790fa3e0 3154024 137881448 System.String
Total 8454945 objects

Le dernier objet répertorié est une chaîne qui occupe le plus d'espace. Vous pouvez examiner votre
application pour voir comment vos objets de chaîne peuvent être optimisés. Pour afficher les chaînes dont la
taille est comprise entre 150 et 200 octets, tapez la commande suivante :
!dumpheap -type System.String -min 150 -max 200
Voici un exemple de résultat.
Address MT Size Gen
1875d2c0 790fa3e0 152 2 System.String HighlightNullStyle_Blotter_PendingOrder-
11_Blotter_PendingOrder-11

L'utilisation d'un entier au lieu d'une chaîne pour un ID peut être plus efficace. Si la même chaîne est répétée
des milliers de fois, envisagez la centralisation des chaînes. Pour plus d'informations sur la centralisation des
chaînes, voir la rubrique de référence sur la méthode String.Intern.
Pour déterminer les références aux objets
Dans le débogueur WinDbg avec l’extension SOS chargée, tapez la commande suivante pour obtenir la liste
des références aux objets :
!gcroot
-or-

Pour déterminer les références à un objet spécifique, incluez l'adresse suivante :


!gcroot 1c37b2ac
Les racines trouvées sur les piles peuvent être de faux positifs. Pour plus d'informations, utilisez la
commande !help gcroot .

ebx:Root:19011c5c(System.Windows.Forms.Application+ThreadContext)->
19010b78(DemoApp.FormDemoApp)->
19011158(System.Windows.Forms.PropertyStore)->
… [omitted]
1c3745ec(System.Data.DataTable)->
1c3747a8(System.Data.DataColumnCollection)->
1c3747f8(System.Collections.Hashtable)->
1c376590(System.Collections.Hashtable+bucket[])->
1c376c98(System.Data.DataColumn)->
1c37b270(System.Data.Common.DoubleStorage)->
1c37b2ac(System.Double[])
Scan Thread 0 OSTHread 99c
Scan Thread 6 OSTHread 484

L'exécution de la commande gcroot peut prendre beaucoup de temps. Tout objet qui n’est pas récupéré par
le garbage collection est un objet dynamique. Cela signifie qu’une racine maintient directement ou
indirectement l’objet. gcroot doit donc retourner des informations de chemin d’accès à l’objet. Vous devez
examiner les graphiques retournés et voir pourquoi ces objets sont encore référencés.
Pour déterminer si un finaliseur a été exécuté
Exécutez un programme de test contenant le code suivant :

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

Si le test résout le problème, cela signifie que le garbage collector ne récupérait pas les objets, parce que les
finaliseurs de ces objets avaient été interrompus. La méthode GC.WaitForPendingFinalizers permet aux
finaliseurs d'effectuer leurs tâches et résout le problème.
Pour déterminer s'il existe des objets en attente de finalisation
1. Dans le débogueur WinDbg ou Visual Studio avec l’extension SOS chargée, tapez la commande suivante :
!finalizequeue
Regardez le nombre d'objets qui sont prêts pour la finalisation. Si le nombre est élevé, vous devez examiner
les raisons pour lesquelles ces finaliseurs ne peuvent pas progresser du tout ou pas assez rapidement.
2. Pour obtenir une sortie des threads, tapez la commande suivante :
threads -special
Cette commande fournit une sortie semblable à la suivante.

OSID Special thread type


2 cd0 DbgHelper
3 c18 Finalizer
4 df0 GC SuspendEE

Le thread finaliseur indique quel finaliseur, est en cours d'exécution, le cas échéant. Quand un thread
finaliseur n'exécute pas de finaliseur, il attend qu'un événement lui demande d'effectuer son travail. La
plupart du temps, vous verrez le thread finaliseur dans cet état, car il s'exécute avec une
THREAD_HIGHEST_PRIORITY et est supposé terminer l'exécution des finaliseurs, le cas échéant, très
rapidement.
Pour déterminer la quantité d'espace libre dans le tas managé
Dans le débogueur WinDbg ou Visual Studio avec l’extension SOS chargée, tapez la commande suivante :
!dumpheap -type Free -stat
Cette commande affiche la taille totale de tous les objets libres sur le tas managé, comme illustré dans
l’exemple suivant.

total 230 objects


Statistics:
MT Count TotalSize Class Name
00152b18 230 40958584 Free
Total 230 objects

Pour déterminer l'espace libre dans la génération 0, tapez la commande suivante pour obtenir des
informations sur la consommation de mémoire par génération :
!eeheap -gc
Cette commande affiche une sortie similaire à la suivante : La dernière ligne indique le segment éphémère.

Heap 0 (0015ad08)
generation 0 starts at 0x49521f8c
generation 1 starts at 0x494d7f64
generation 2 starts at 0x007f0038
ephemeral segment allocation context: none
segment begin allocated size
00178250 7a80d84c 7a82f1cc 0x00021980(137600)
00161918 78c50e40 78c7056c 0x0001f72c(128812)
007f0000 007f0038 047eed28 0x03ffecf0(67103984)
3a120000 3a120038 3a3e84f8 0x002c84c0(2917568)
46120000 46120038 49e05d04 0x03ce5ccc(63855820)

Calculez l'espace utilisé par la génération 0 :


? 49e05d04-0x49521f8c
Le résultat est le suivant : La génération 0 est d'environ 9 Mo.
Evaluate expression: 9321848 = 008e3d78

La commande suivante fait un dump pour l'espace libre dans la plage de la génération 0 :
!dumpheap -type Free -stat 0x49521f8c 49e05d04
Le résultat est le suivant :

------------------------------
Heap 0
total 409 objects
------------------------------
Heap 1
total 0 objects
------------------------------
Heap 2
total 0 objects
------------------------------
Heap 3
total 0 objects
------------------------------
total 409 objects
Statistics:
MT Count TotalSize Class Name
0015a498 409 7296540 Free
Total 409 objects

Cette sortie indique que la partie du tas consacrée à la génération 0 utilise 9 Mo d'espace pour les objets et
dispose de 7 Mo d'espace libre. Cette analyse montre dans quelle mesure la génération 0 contribue à la
fragmentation. Cette quantité d'utilisation du tas doit être retirée de la quantité totale, car elle est la cause de
la fragmentation par les objets à long terme.
Pour déterminer le nombre d'objets épinglés
Dans le débogueur WinDbg ou Visual Studio avec l’extension SOS chargée, tapez la commande suivante :
!gchandles
Les statistiques affichées incluent le nombre de handles épinglés, comme le montre l'exemple suivant.

GC Handle Statistics:
Strong Handles: 29
Pinned Handles: 10

Pour déterminer la durée d'un garbage collection


Regardez ce qu'affiche le compteur de performances de mémoire % Time in GC .
La valeur est calculée à partir d'un échantillon d'intervalle de temps. Les compteurs sont mis à jour à la fin
de chaque garbage collection. L'exemple actuel aura donc la même valeur que l'exemple précédent si
aucune collection ne s'est produite pendant l'intervalle.
La durée de la collection est obtenue en multipliant l'échantillon d'intervalle de temps par la valeur de
pourcentage.
Les données suivantes montrent quatre échantillons d'intervalles de deux secondes, pour une étude de
8 secondes. Les colonnes Gen0 , Gen1 et Gen2 indiquent le nombre de garbage collection qui se sont
produits pendant cet intervalle pour cette génération.
Interval Gen0 Gen1 Gen2 % Time in GC
1 9 3 1 10
2 10 3 1 1
3 11 3 1 3
4 11 3 1 3

Ces informations n'affichent pas le moment auquel s'est produit le garbage collection, mais vous pouvez
toutefois connaître le nombre de garbage collection qui se sont produits dans un intervalle de temps. Dans
le pire des cas, le dixième garbage collection de génération 0 s'est terminé au début du deuxième intervalle
et le onzième garbage collection de génération 0 s'est terminé à la fin du cinquième intervalle. Le délai entre
la fin du dixième et la fin du onzième garbage collection est d'environ 2 secondes. Le compteur de
performances indique 3 %. La durée du onzième garbage collection de génération 0 était donc de
(2 secondes * 3 % = 60 ms).
Cet exemple comprend 5 périodes.

Interval Gen0 Gen1 Gen2 % Time in GC


1 9 3 1 3
2 10 3 1 1
3 11 4 2 1
4 11 4 2 1
5 11 4 2 20

Le deuxième garbage collection de génération 2 a démarré pendant le troisième intervalle et s'est terminé
au cinquième intervalle. Dans le pire des cas, le dernier garbage collection était pour une collection de
génération 0 qui s'est terminée au début du deuxième intervalle et le garbage collection de génération 2
s'est terminé à la fin du cinquième intervalle. Le délai entre la fin du garbage collection de génération 0 et la
fin du garbage collection de génération 2 est donc de 4 secondes. Étant donné que le compteur
% Time in GC affiche 20 %, la durée maximale du garbage collection de génération 2 est de (4 secondes *
20 % = 800 ms).
Vous pouvez également déterminer la durée d’un garbage collection à l’aide des événements ETW de
garbage collection en analysant les informations fournies.
Par exemple, les données suivantes indiquent une séquence d’événements qui se sont produits pendant un
garbage collection non simultané.

Timestamp Event name


513052 GCSuspendEEBegin_V1
513078 GCSuspendEEEnd
513090 GCStart_V1
517890 GCEnd_V1
517894 GCHeapStats
517897 GCRestartEEBegin
517918 GCRestartEEEnd

La suspension des threads managés a pris 26 us ( GCSuspendEEEnd – GCSuspendEEBegin_V1 ).


Le garbage collection réel a pris 4,8 ms ( GCEnd_V1 – GCStart_V1 ).
La reprise des threads managés a pris 21 us ( GCRestartEEEnd – GCRestartEEBegin ).
La sortie suivante fournit un exemple de garbage collection d'arrière-plan et inclut les champs de processus,
de thread et d'événement (toutes les données ne sont pas affichées).
timestamp(us) event name process thread event field
42504385 GCSuspendEEBegin_V1 Test.exe 4372 1
42504648 GCSuspendEEEnd Test.exe 4372
42504816 GCStart_V1 Test.exe 4372 102019
42504907 GCStart_V1 Test.exe 4372 102020
42514170 GCEnd_V1 Test.exe 4372
42514204 GCHeapStats Test.exe 4372 102020
42832052 GCRestartEEBegin Test.exe 4372
42832136 GCRestartEEEnd Test.exe 4372
63685394 GCSuspendEEBegin_V1 Test.exe 4744 6
63686347 GCSuspendEEEnd Test.exe 4744
63784294 GCRestartEEBegin Test.exe 4744
63784407 GCRestartEEEnd Test.exe 4744
89931423 GCEnd_V1 Test.exe 4372 102019
89931464 GCHeapStats Test.exe 4372

L'événement GCStart_V1 à la ligne 42504816 indique qu'il s'agit d'un garbage collection d'arrière-plan, car
le dernier champ est 1 . Cela devient le garbage collection numéro 102019.
L'événement GCStart se produit, car un garbage collection éphémère doit être effectué avant de démarrer
un garbage collection d'arrière-plan. Cela devient le garbage collection numéro 102020.
À la ligne 42514170, le garbage collection numéro 102020 prend fin. Les threads managés sont redémarrés
à cet endroit. L'opération se termine au thread 4372, qui a déclenché ce garbage collection d'arrière-plan.
Au thread 4744, une suspension se produit. C'est la seule fois où le garbage collection d'arrière-plan doit
suspendre les threads managés. Cette durée est d'environ 99 ms ((63784407-63685394)/1000).
L'événement GCEnd pour le garbage collection d'arrière-plan se trouve à la ligne 89931423. Cela signifie
que le garbage collection d'arrière-plan a duré environ 47 secondes ((89931423-42504816)/1000).
Pendant que les threads managés sont exécutés, vous pouvez voir tous les garbage collection éphémères en
cours.
Pour déterminer ce qui a déclenché un garbage collection
Dans le débogueur WinDbg ou Visual Studio avec l’extension de débogueur SOS chargée, tapez la
commande suivante pour afficher tous les threads avec leurs piles d’appels :
~*kbit
Cette commande affiche une sortie similaire à la suivante :

0012f3b0 79ff0bf8 mscorwks!WKS::GCHeap::GarbageCollect


0012f454 30002894 mscorwks!GCInterface::CollectGeneration+0xa4
0012f490 79fa22bd fragment_ni!request.Main(System.String[])+0x48

Si le garbage collection a été provoqué par une notification de mémoire insuffisante du système
d'exploitation, la pile des appels sera similaire, à ceci près que le thread sera le thread finaliseur. Le thread
finaliseur reçoit une notification asynchrone de mémoire insuffisante et déclenche le garbage collection.
Si le garbage collection a été provoqué par l'allocation de mémoire, la pile se présente comme ceci :
0012f230 7a07c551 mscorwks!WKS::GCHeap::GarbageCollectGeneration
0012f2b8 7a07cba8 mscorwks!WKS::gc_heap::try_allocate_more_space+0x1a1
0012f2d4 7a07cefb mscorwks!WKS::gc_heap::allocate_more_space+0x18
0012f2f4 7a02a51b mscorwks!WKS::GCHeap::Alloc+0x4b
0012f310 7a02ae4c mscorwks!Alloc+0x60
0012f364 7a030e46 mscorwks!FastAllocatePrimitiveArray+0xbd
0012f424 300027f4 mscorwks!JIT_NewArr1+0x148
000af70f 3000299f fragment_ni!request..ctor(Int32, Single)+0x20c
0000002a 79fa22bd fragment_ni!request.Main(System.String[])+0x153

Un programme d'assistance juste-à-temps ( JIT_New* ) appelle GCHeap::GarbageCollectGeneration . Si vous


déterminez que les garbage collection de génération 2 sont dus à des allocations, vous devez déterminer
quels objets sont collectés par les garbage collection de génération 2 et comment les éviter. Autrement dit,
vous devez déterminer la différence entre le début et la fin d'un garbage collection de génération 2, ainsi
que les objets qui ont provoqué la collection de génération 2.
Par exemple, tapez la commande suivante dans le débogueur pour afficher le début d’une collection de
génération 2 :
!dumpheap –stat
Exemple de sortie (abrégé pour montrer les objets qui utilisent le plus d'espace) :

79124228 31857 9862328 System.Object[]


035f0384 25668 11601936 Toolkit.TlkPosition
00155f80 21248 12256296 Free
79103b6c 297003 13068132 System.Threading.ReaderWriterLock
7a747ad4 708732 14174640 System.Collections.Specialized.HybridDictionary
7a747c78 786498 15729960 System.Collections.Specialized.ListDictionary+DictionaryNode
7a747bac 700298 19608344 System.Collections.Specialized.ListDictionary
035f0ee4 89192 38887712 Toolkit.TlkOrder
00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[]
7912c444 91616 71887080 System.Double[]
791242ec 32451 82462728 System.Collections.Hashtable+bucket[]
790fa3e0 2459154 112128436 System.String
Total 6471774 objects

Répétez la commande à la fin de la génération 2 :


!dumpheap –stat
Exemple de sortie (abrégé pour montrer les objets qui utilisent le plus d'espace) :

79124228 26648 9314256 System.Object[]


035f0384 25668 11601936 Toolkit.TlkPosition
79103b6c 296770 13057880 System.Threading.ReaderWriterLock
7a747ad4 708730 14174600 System.Collections.Specialized.HybridDictionary
7a747c78 786497 15729940 System.Collections.Specialized.ListDictionary+DictionaryNode
7a747bac 700298 19608344 System.Collections.Specialized.ListDictionary
00155f80 13806 34007212 Free
035f0ee4 89187 38885532 Toolkit.TlkOrder
00fcae40 6193 44911636 WaveBasedStrategy.Tick_Snap[]
791242ec 32370 82359768 System.Collections.Hashtable+bucket[]
790fa3e0 2440020 111341808 System.String
Total 6417525 objects

Les objets double[] ont disparu de la fin de la sortie, ce qui signifie qu'ils ont été collectés. Ces objets
représentent environ 70 Mo. Les objets restants n'ont pas beaucoup changé. Ces objets double[] étaient
donc à l'origine du déclenchement du garbage collection de génération 2. L'étape suivante consiste à
déterminer pourquoi les objets double[] y sont situés et pourquoi ils sont morts. Vous pouvez demander à
un développeur d'où proviennent ces objets ou vous pouvez utiliser la commande gcroot .
Pour déterminer si l’utilisation élevée du processeur est causée par le garbage collection
Mettez en corrélation la valeur du compteur de performances de mémoire % Time in GC avec le temps
d'exécution.
Si la valeur % Time in GC connaît un pic en même temps que le temps d'exécution, le garbage collection
provoque une utilisation élevée du processeur. Dans le cas contraire, profilez l'application pour trouver où se
produit l'utilisation élevée.

Voir aussi
Garbage collection
Collections forcées
18/07/2020 • 5 minutes to read • Edit Online

Dans la plupart des cas, le Garbage collector peut déterminer le meilleur moment pour exécuter une collection. En
outre, vous devez lui permettre de s’exécuter de façon indépendante. Dans de rares cas, une collection forcée peut
toutefois améliorer les performances de votre application. Vous pouvez alors induire le garbage collection à l’aide
de la méthode GC.Collect pour forcer un garbage collection.
Utilisez la méthode GC.Collect quand la quantité de mémoire utilisée à un point spécifique dans le code de votre
application diminue considérablement. Par exemple, si votre application utilise une boîte de dialogue complexe
comportant plusieurs contrôles, l’appel de Collect quand la boîte de dialogue est fermée peut améliorer les
performances en libérant immédiatement la mémoire utilisée par la boîte de dialogue. Vérifiez que votre
application n’induit pas trop fréquemment le garbage collection. En effet, les performances risquent d’être
affectées si le Garbage collector tente de récupérer des objets à des moments inopportuns. Vous pouvez fournir
une valeur d’énumération GCCollectionMode.Optimized à la méthode Collect pour effectuer une collection
uniquement lorsqu’elle est productive, comme décrit dans la section suivante.

Mode de collection GC
Vous pouvez utiliser l’une des surcharges de méthode GC.Collect qui inclut une valeur GCCollectionMode pour
spécifier le comportement d’une collection forcée, comme suit.

VA L EUR GCCOLLECTIONMODE DESC RIP T IO N

Default Utilise le paramètre de garbage collection par défaut pour la


version en cours d’exécution de .NET.

Forced Force l’exécution immédiate du garbage collection. Cela


équivaut à appeler la surcharge GC.Collect(). Il en résulte une
collection de blocage complète de toutes les générations.

Vous pouvez également compacter le tas d’objets volumineux


en définissant la propriété
GCSettings.LargeObjectHeapCompactionMode sur
GCLargeObjectHeapCompactionMode.CompactOnce avant de
forcer un garbage collection de blocage complet immédiat.

Optimized Permet au Garbage collector de déterminer si le moment est


opportun pour récupérer des objets.

Le Garbage collector peut déterminer qu’une collection n’est


pas assez productive pour être effectuée. Dans ce cas, aucun
objet n’est récupéré.

Collections d’arrière-plan ou de blocage


Vous pouvez appeler la surcharge de méthode GC.Collect(Int32, GCCollectionMode, Boolean) pour spécifier si une
collection induite bloque ou non. Le type de collection effectué dépend d’une combinaison des paramètres mode et
blocking de la méthode. mode est membre de l’énumération GCCollectionMode et blocking est une valeur
Boolean. Le tableau suivant résume l’interaction des arguments mode et blocking .
MODE BLOCKING = TRUE BLOCKING = FALSE

Forced ou Default Une collection de blocage est exécutée Une collection est exécutée dès que
dès que possible. Si une collection possible. La méthode Collect(Int32,
d’arrière-plan est en cours et que la GCCollectionMode, Boolean) demande
génération a la valeur 0 ou 1, la une collection d'arrière-plan, mais cela
méthode Collect(Int32, n'est pas garanti ; selon les cas, une
GCCollectionMode, Boolean) déclenche collection bloquante peut toujours être
immédiatement une collection de exécutée. Si une collection d’arrière-plan
blocage et retourne une valeur quand la est déjà en cours, la méthode retourne
collection est terminée. Si une collection immédiatement une valeur.
d’arrière-plan est en cours et que le
paramètre generation a la valeur 2, la
méthode attend la fin de la collection
d’arrière-plan, déclenche une collection
de blocage de génération 2, puis
retourne une valeur.

Optimized Une collecte bloquante peut être Une collection de blocage peut être
exécutée, selon l'état du récupérateur effectuée, en fonction de l’état du
de mémoire et du paramètre Garbage collector. La méthode
generation . Le Garbage collector Collect(Int32, GCCollectionMode,
tente de fournir des performances Boolean) demande une collection
optimales. d'arrière-plan, mais cela n'est pas
garanti ; selon les cas, une collection
bloquante peut toujours être exécutée.
Le Garbage collector tente de fournir
des performances optimales. Si une
collection d’arrière-plan est déjà en
cours, la méthode retourne
immédiatement une valeur.

Voir aussi
Modes de latence
Garbage collection
Modes de latence
18/07/2020 • 6 minutes to read • Edit Online

Pour récupérer des objets, le garbage collector (GC) doit arrêter tous les threads en cours d’exécution dans une
application. La durée pendant laquelle le garbage collector est actif est appelée « latence».
Dans certaines situations, par exemple, quand une application récupère des données ou affiche du contenu, un
garbage collection complet peut se produire à un moment critique et nuire aux performances. Vous pouvez ajuster
le niveau d'intrusion du garbage collector en définissant la propriété GCSettings.LatencyMode sur l'une des
valeursSystem.Runtime.GCLatencyMode.

Paramètres de latence faible


L’utilisation d’un paramètre de latence « faible » signifie que le garbage collector est à l’abri d’une baisse de votre
application. Le garbage collection est plus prudent en ce qui concerne la récupération de la mémoire.
L'énumération System.Runtime.GCLatencyMode fournit deux paramètres de latence faible :
GCLatencyMode. LowLatency supprime les collections de génération 2 et effectue uniquement des
collections de génération 0 et 1. Elle ne peut être utilisée que pour de courtes durées. Sur des périodes plus
longues, si le système connaît une sollicitation de mémoire importante, le garbage collector déclenchera
une collection, qui pourra brièvement suspendre l'application et interrompre une opération critique. Ce
paramètre est disponible uniquement pour le garbage collection des stations de travail.
GCLatencyMode. SustainedLowLatency supprime les collections de 2e génération 2 et exécute uniquement
les collections de génération 0, 1 et d’arrière-plan 2. Il peut être utilisé pour de longues périodes et est
disponible à la fois pour le garbage collection des stations de travail et des serveurs. Ce paramètre ne peut
pas être utilisé si la garbage collection d’arrière-plan est désactivée.
Pendant les périodes de faible latence, les collections de génération 2 sont empêchées, sauf dans les cas suivants :
Le système reçoit une notification de mémoire insuffisante du système d'exploitation.
Le code d’application induit une collection en appelant la GC.Collect méthode et en spécifiant 2 pour le
generation paramètre.

Scénarios
Le tableau suivant répertorie les scénarios d’application pour l’utilisation des GCLatencyMode valeurs :

M O DE DE L AT EN C E SC ÉN A RIO S D’A P P L IC AT IO N

Batch Pour les applications qui n’ont pas d’interface utilisateur ou


d’opérations côté serveur.

Lorsque la garbage collection d’arrière-plan est désactivée, il


s’agit du mode par défaut pour les garbage collection de
station de travail et de serveur. Batchle mode remplace
également le paramètre gcConcurrent , autrement dit, il
empêche les collections en arrière-plan ou simultanées.
M O DE DE L AT EN C E SC ÉN A RIO S D’A P P L IC AT IO N

Interactive Pour la plupart des applications qui ont une interface


utilisateur.

Il s’agit du mode par défaut pour les garbage collection de


station de travail et de serveur. Toutefois, si une application est
hébergée, les paramètres du garbage collector du processus
d’hébergement sont prioritaires.

LowLatency Pour les applications qui ont des opérations de courte durée
pour lesquelles le temps est important, et durant lesquelles les
interruptions du garbage collector pourraient provoquer des
perturbations. Par exemple, les applications qui restituent des
animations ou des fonctions d’acquisition de données.

SustainedLowLatency Pour les applications qui ont des opérations pour lesquelles le
temps est important, pendant une durée définie mais
potentiellement longue durant laquelle les interruptions du
garbage collector pourraient provoquer des perturbations. Par
exemple, les applications nécessitant des temps de réponse
rapides en raison du changement des données de marché
pendant les heures de négociation.

Ce mode augmente davantage la taille du tas managé que les


autres modes. Parce qu'il ne compacte pas le tas managé, une
fragmentation plus importante est possible. Assurez-vous que
suffisamment de mémoire est disponible.

Instructions pour l’utilisation de la faible latence


Lorsque vous utilisez le mode GCLatencyMode. LowLatency , tenez compte des instructions suivantes :
Choisissez une période aussi courte que possible pour la faible latence.
Évitez d'allouer de grandes quantités de mémoire pendant les périodes de faible latence. Vous pouvez
recevoir des notifications de mémoire insuffisante, car le garbage collection récupère moins d'objets.
En mode de latence faible, réduisez le nombre de nouvelles allocations, en particulier les allocations sur le
tas d’objets volumineux et les objets épinglés.
N'oubliez pas les threads qui peuvent être à l'origine d'allocations. Étant donné que le LatencyMode
paramètre de propriété est à l’ensemble du processus, des OutOfMemoryException exceptions peuvent être
générées sur tout thread qui alloue.
Encapsulez le code de latence faible dans les régions d’exécution limitée. Pour plus d’informations, consultez
régions d’exécution restreinte.
Vous pouvez forcer les collections de génération 2 pendant une période de latence basse en appelant la
méthode GC.Collect(Int32, GCCollectionMode).

Voir aussi
System.GC
Collections induites
Garbage collection
Optimisation de l'hébergement Web partagé
18/07/2020 • 2 minutes to read • Edit Online

Si vous êtes l’administrateur d’un serveur qui est partagé via l’hébergement de plusieurs petits sites web, vous
pouvez optimiser les performances et augmenter la capacité du site en ajoutant le paramètre
gcTrimCommitOnLowMemory suivant au nœud runtime dans le fichier Aspnet.config dans le répertoire .NET :

<gcTrimCommitOnLowMemory enabled="true|false"/>

NOTE
Ce paramètre n’est recommandé que dans les scénarios d’hébergement web partagé.

Étant donné que le récupérateur de mémoire conserve de la mémoire pour les répartitions futures, l’espace alloué
peut être supérieur à ce qui est strictement nécessaire. Vous pouvez réduire cet espace en cas de charge
importante sur la mémoire système. La réduction de cet espace alloué améliore les performances et développe la
capacité à héberger plusieurs sites.
Lorsque le paramètre gcTrimCommitOnLowMemory est activé, le récupérateur de mémoire évalue la charge de la
mémoire système et entre en mode de suppression lorsque la charge atteint 90 %. Il conserve le mode de
suppression jusqu'à ce que la charge soit inférieure à 85 %.
Lorsque les conditions le permettent, le récupérateur de mémoire peut décider que le paramètre
gcTrimCommitOnLowMemory n’aidera pas l’application actuelle et l’ignorer.

Exemple
Le fragment XML suivant montre comment activer le paramètre gcTrimCommitOnLowMemory . Les points de
suspension indiquent d’autres paramètres dans le nœud runtime .

<?xml version="1.0" encoding="UTF-8"?>


<configuration>
<runtime>
. . .
<gcTrimCommitOnLowMemory enabled="true"/>
</runtime>
. . .
</configuration>

Voir aussi
Garbage collection
Notifications de garbage collection
18/07/2020 • 30 minutes to read • Edit Online

Il existe des cas où une opération garbage collection complète (c’est-à-dire, une opération garbage collection de
génération 2) par le CLR peut avoir des effets néfastes sur les performances. Cela peut être particulièrement
problématique avec les serveurs qui traitent de gros volumes de requêtes ; dans ce cas, une longue garbage
collection peut entraîner un délai d’attente de requête. Pour éviter qu’une collecte complète ne se produise
pendant une période critique, vous pouvez être averti qu’une garbage collection complète approche, puis agir
pour rediriger la charge de travail vers une autre instance de serveur. Vous pouvez également déclencher vous-
même une collection, sous réserve que l’instance de serveur actuelle n’a pas besoin de traiter de requêtes.
La méthode RegisterForFullGCNotification permet de déclencher une notification lorsque le runtime détecte qu’un
garbage collection complet est sur le point de se produire. Cette notification est divisée en deux parties : lorsqu’un
garbage collection complet est imminent et lorsque le garbage collection complet est terminé.

WARNING
Seul le blocage des garbage collections génère des notifications. Lorsque l' <gcConcurrent> élément de configuration est
activé, les garbage collection d’arrière-plan ne déclenchent pas de notifications.

Pour déterminer quand une notification a été levée, utilisez les méthodes WaitForFullGCApproach et
WaitForFullGCComplete. En général, vous utilisez ces méthodes dans une boucle while pour obtenir en
permanence une énumération GCNotificationStatus qui indique l’état de la notification. Si cette valeur est égale à
Succeeded, vous pouvez procéder comme suit :
En réponse à une notification obtenue avec la méthode WaitForFullGCApproach, vous pouvez rediriger la
charge de travail et éventuellement déclencher vous-même une collection.
En réponse à une notification obtenue avec la méthode WaitForFullGCComplete, vous pouvez rendre
l’instance de serveur actuelle disponible pour traiter à nouveau les requêtes. Vous pouvez également
rassembler des informations. Par exemple, vous pouvez utiliser la méthode CollectionCount pour
enregistrer le nombre de collections.
Les méthodes WaitForFullGCApproach et WaitForFullGCComplete sont conçues pour fonctionner ensemble. Le fait
d’utiliser l’une sans l’autre peut produire des résultats indéterminés.

Garbage collection complet


Le runtime génère un garbage collection complet lorsque l’un des scénarios suivants est vrai :
Une quantité suffisante de mémoire a été promue au niveau génération 2 pour entraîner le garbage
collection de génération 2 suivant.
Une quantité suffisante de mémoire a été promue dans le tas d’objets volumineux pour entraîner le garbage
collection de génération 2 suivant.
Une collection de génération 1 passe à une collection de génération 2 en raison d’autres facteurs.
Les seuils que vous spécifiez dans la méthode RegisterForFullGCNotification s’appliquent aux deux premiers
scénarios. Toutefois, dans le premier scénario, vous ne recevrez pas toujours la notification en temps proportionnel
aux valeurs de seuil que vous spécifiez, pour deux raisons :
Le runtime ne vérifie pas chaque allocation de petit objet (pour des raisons de performances).
Seules les collections de génération 1 promeuvent de la mémoire au niveau génération 2.
Le troisième scénario contribue également à l’incertitude sur la réception de la notification. Bien que cela ne soit
pas garanti, cela peut constituer un moyen utile pour atténuer les effets d’un garbage collection complet
intempestif en redirigeant les requêtes durant ce laps de temps ou en déclenchant vous-même la collection
lorsque cela est approprié.

Paramètres de seuil de notification


La méthode RegisterForFullGCNotification possède deux paramètres pour spécifier les valeurs de seuil des objets
de génération 2 et le tas d’objets volumineux. Lorsque ces valeurs sont atteintes, une notification de garbage
collection doit être déclenchée. Le tableau suivant décrit ces paramètres.

PA RA M ÈT RE DESC RIP T IO N

maxGenerationThreshold Nombre entre 1 et 99 qui spécifie le moment auquel la


notification doit être émise selon les objets promus dans la
génération 2.

largeObjectHeapThreshold Nombre entre 1 et 99 qui spécifie le moment auquel la


notification doit être émise selon les objets alloués dans le tas
d’objets volumineux.

Si vous spécifiez une valeur trop élevée, il est très probable que vous receviez une notification, mais la période
d’attente pourrait être trop longue avant que le runtime ne déclenche une collection. Si vous déclenchez vous-
même une collection, vous pourriez récupérer plus d’objets que si le runtime provoque la collection.
Si vous spécifiez une valeur trop faible, le runtime peut déclencher la collection avant que vous n’ayez eu le temps
d’être averti.

Exemple
Description
Dans l’exemple suivant, un groupe de serveurs traitent des requêtes web entrantes. Pour simuler la charge de
travail de traitement des requêtes, des tableaux d’octets sont ajoutés à une collection List<T>. Chaque serveur
s’inscrit pour une notification de garbage collection, puis démarre un thread sur la méthode utilisateur
WaitForFullGCProc afin de surveiller en permanence l’énumération GCNotificationStatus retournée par les
méthodes WaitForFullGCApproach et WaitForFullGCComplete.
Les méthodes WaitForFullGCApproach et WaitForFullGCComplete appellent leurs méthodes utilisateur de gestion
des événements respectives lorsqu’une notification est levée :
OnFullGCApproachNotify

Cette méthode appelle la méthode utilisateur RedirectRequests , qui indique au serveur de mise en file
d’attente des requêtes d’interrompre l’envoi de requêtes au serveur. Cela est simulé en définissant la
variable de niveau classe bAllocate sur false afin qu’aucun autre objet ne soit alloué.
Ensuite, la méthode utilisateur FinishExistingRequests est appelée pour terminer le traitement des
requêtes serveur en attente. Cela est simulé en supprimant la collection List<T>.
Enfin, un garbage collection est déclenché car la charge de travail est faible.
OnFullGCCompleteNotify
Cette méthode appelle la méthode utilisateur AcceptRequests pour reprendre l’acceptation des requêtes,
car le serveur n’est plus susceptible d’être soumis à un garbage collection complet. Cette action est simulée
en définissant la variable bAllocate sur true afin que l’ajout d’objets à la collection List<T> puisse
reprendre.
Le code suivant contient la méthode Main de l’exemple.

using namespace System;


using namespace System::Collections::Generic;
using namespace System::Threading;

namespace GCNotify
{
ref class Program
{
private:
// Variable for continual checking in the
// While loop in the WaitForFullGCProc method.
static bool checkForNotify = false;

// Variable for suspending work


// (such servicing allocated server requests)
// after a notification is received and then
// resuming allocation after inducing a garbage collection.
static bool bAllocate = false;

// Variable for ending the example.


static bool finalExit = false;

// Collection for objects that


// simulate the server request workload.
static List<array<Byte>^>^ load = gcnew List<array<Byte>^>();

public:
static void Main()
{
try
{
// Register for a notification.
GC::RegisterForFullGCNotification(10, 10);
Console::WriteLine("Registered for GC notification.");

checkForNotify = true;
bAllocate = true;

// Start a thread using WaitForFullGCProc.


Thread^ thWaitForFullGC = gcnew Thread(gcnew ThreadStart(&WaitForFullGCProc));
thWaitForFullGC->Start();

// While the thread is checking for notifications in


// WaitForFullGCProc, create objects to simulate a server workload.
try
{
int lastCollCount = 0;
int newCollCount = 0;

while (true)
{
if (bAllocate)
{
load->Add(gcnew array<Byte>(1000));
newCollCount = GC::CollectionCount(2);
if (newCollCount != lastCollCount)
{
// Show collection count when it increases:
// Show collection count when it increases:
Console::WriteLine("Gen 2 collection count: {0}",
GC::CollectionCount(2).ToString());
lastCollCount = newCollCount;
}

// For ending the example (arbitrary).


if (newCollCount == 500)
{
finalExit = true;
checkForNotify = false;
break;
}
}
}

}
catch (OutOfMemoryException^)
{
Console::WriteLine("Out of memory.");
}

finalExit = true;
checkForNotify = false;
GC::CancelFullGCNotification();

}
catch (InvalidOperationException^ invalidOp)
{

Console::WriteLine("GC Notifications are not supported while concurrent GC is enabled.\n"


+ invalidOp->Message);
}
}

public:
static void OnFullGCApproachNotify()
{
Console::WriteLine("Redirecting requests.");

// Method that tells the request queuing


// server to not direct requests to this server.
RedirectRequests();

// Method that provides time to


// finish processing pending requests.
FinishExistingRequests();

// This is a good time to induce a GC collection


// because the runtime will induce a full GC soon.
// To be very careful, you can check precede with a
// check of the GC.GCCollectionCount to make sure
// a full GC did not already occur since last notified.
GC::Collect();
Console::WriteLine("Induced a collection.");

public:
static void OnFullGCCompleteEndNotify()
{
// Method that informs the request queuing server
// that this server is ready to accept requests again.
AcceptRequests();
Console::WriteLine("Accepting requests again.");
}

public:
public:
static void WaitForFullGCProc()
{
while (true)
{
// CheckForNotify is set to true and false in Main.
while (checkForNotify)
{
// Check for a notification of an approaching collection.
GCNotificationStatus s = GC::WaitForFullGCApproach();
if (s == GCNotificationStatus::Succeeded)
{
Console::WriteLine("GC Notifiction raised.");
OnFullGCApproachNotify();
}
else if (s == GCNotificationStatus::Canceled)
{
Console::WriteLine("GC Notification cancelled.");
break;
}
else
{
// This can occur if a timeout period
// is specified for WaitForFullGCApproach(Timeout)
// or WaitForFullGCComplete(Timeout)
// and the time out period has elapsed.
Console::WriteLine("GC Notification not applicable.");
break;
}

// Check for a notification of a completed collection.


s = GC::WaitForFullGCComplete();
if (s == GCNotificationStatus::Succeeded)
{
Console::WriteLine("GC Notification raised.");
OnFullGCCompleteEndNotify();
}
else if (s == GCNotificationStatus::Canceled)
{
Console::WriteLine("GC Notification cancelled.");
break;
}
else
{
// Could be a time out.
Console::WriteLine("GC Notification not applicable.");
break;
}
}

Thread::Sleep(500);
// FinalExit is set to true right before
// the main thread cancelled notification.
if (finalExit)
{
break;
}
}
}

private:
static void RedirectRequests()
{
// Code that sends requests
// to other servers.

// Suspend work.
bAllocate = false;
}

static void FinishExistingRequests()


{
// Code that waits a period of time
// for pending requests to finish.

// Clear the simulated workload.


load->Clear();

static void AcceptRequests()


{
// Code that resumes processing
// requests on this server.

// Resume work.
bAllocate = true;
}
};
}

int main()
{
GCNotify::Program::Main();
}
public static void Main(string[] args)
{
try
{
// Register for a notification.
GC.RegisterForFullGCNotification(10, 10);
Console.WriteLine("Registered for GC notification.");

checkForNotify = true;
bAllocate = true;

// Start a thread using WaitForFullGCProc.


Thread thWaitForFullGC = new Thread(new ThreadStart(WaitForFullGCProc));
thWaitForFullGC.Start();

// While the thread is checking for notifications in


// WaitForFullGCProc, create objects to simulate a server workload.
try
{

int lastCollCount = 0;
int newCollCount = 0;

while (true)
{
if (bAllocate)
{
load.Add(new byte[1000]);
newCollCount = GC.CollectionCount(2);
if (newCollCount != lastCollCount)
{
// Show collection count when it increases:
Console.WriteLine("Gen 2 collection count: {0}", GC.CollectionCount(2).ToString());
lastCollCount = newCollCount;
}

// For ending the example (arbitrary).


if (newCollCount == 500)
{
finalExit = true;
checkForNotify = false;
break;
}
}
}
}
catch (OutOfMemoryException)
{
Console.WriteLine("Out of memory.");
}

finalExit = true;
checkForNotify = false;
GC.CancelFullGCNotification();
}
catch (InvalidOperationException invalidOp)
{

Console.WriteLine("GC Notifications are not supported while concurrent GC is enabled.\n"


+ invalidOp.Message);
}
}

Imports System.Collections.Generic
Imports System.Threading
Class Program
' Variables for continual checking in the
' While loop in the WaitForFullGcProc method.
Private Shared checkForNotify As Boolean = False

' Variable for suspending work


' (such as servicing allocated server requests)
' after a notification is received and then
' resuming allocation after inducing a garbage collection.
Private Shared bAllocate As Boolean = False

' Variable for ending the example.


Private Shared finalExit As Boolean = False

' Collection for objects that


' simulate the server request workload.
Private Shared load As New List(Of Byte())

Public Shared Sub Main(ByVal args() As String)


Try
' Register for a notification.
GC.RegisterForFullGCNotification(10, 10)
Console.WriteLine("Registered for GC notification.")

bAllocate = True
checkForNotify = True

' Start a thread using WaitForFullGCProc.


Dim thWaitForFullGC As Thread = _
New Thread(New ThreadStart(AddressOf WaitForFullGCProc))
thWaitForFullGC.Start()

' While the thread is checking for notifications in


' WaitForFullGCProc, create objects to simulate a server workload.
Try
Dim lastCollCount As Integer = 0
Dim newCollCount As Integer = 0

While (True)
If bAllocate = True Then

load.Add(New Byte(1000) {})


newCollCount = GC.CollectionCount(2)
If (newCollCount <> lastCollCount) Then
' Show collection count when it increases:
Console.WriteLine("Gen 2 collection count: {0}", _
GC.CollectionCount(2).ToString)
lastCollCount = newCollCount
End If

' For ending the example (arbitrary).


If newCollCount = 500 Then
finalExit = True
checkForNotify = False
bAllocate = False
Exit While
End If

End If
End While

Catch outofMem As OutOfMemoryException


Console.WriteLine("Out of memory.")
End Try

finalExit = True
checkForNotify = False
GC.CancelFullGCNotification()

Catch invalidOp As InvalidOperationException


Console.WriteLine("GC Notifications are not supported while concurrent GC is enabled." _
& vbLf & invalidOp.Message)
End Try
End Sub

Public Shared Sub OnFullGCApproachNotify()


Console.WriteLine("Redirecting requests.")

' Method that tells the request queuing


' server to not direct requests to this server.
RedirectRequests()

' Method that provides time to


' finish processing pending requests.
FinishExistingRequests()

' This is a good time to induce a GC collection


' because the runtime will induce a ful GC soon.
' To be very careful, you can check precede with a
' check of the GC.GCCollectionCount to make sure
' a full GC did not already occur since last notified.
GC.Collect()
Console.WriteLine("Induced a collection.")
End Sub

Public Shared Sub OnFullGCCompleteEndNotify()


' Method that informs the request queuing server
' that this server is ready to accept requests again.
AcceptRequests()
Console.WriteLine("Accepting requests again.")
End Sub

Public Shared Sub WaitForFullGCProc()

While True
' CheckForNotify is set to true and false in Main.

While checkForNotify
' Check for a notification of an approaching collection.
Dim s As GCNotificationStatus = GC.WaitForFullGCApproach
If (s = GCNotificationStatus.Succeeded) Then
Console.WriteLine("GC Notification raised.")
OnFullGCApproachNotify()
ElseIf (s = GCNotificationStatus.Canceled) Then
Console.WriteLine("GC Notification cancelled.")
Exit While
Else
' This can occur if a timeout period
' is specified for WaitForFullGCApproach(Timeout)
' or WaitForFullGCComplete(Timeout)
' and the time out period has elapsed.
Console.WriteLine("GC Notification not applicable.")
Exit While
End If

' Check for a notification of a completed collection.


s = GC.WaitForFullGCComplete
If (s = GCNotificationStatus.Succeeded) Then
Console.WriteLine("GC Notifiction raised.")
OnFullGCCompleteEndNotify()
ElseIf (s = GCNotificationStatus.Canceled) Then
Console.WriteLine("GC Notification cancelled.")
Exit While
Else
' Could be a time out.
Console.WriteLine("GC Notification not applicable.")
Console.WriteLine("GC Notification not applicable.")
Exit While
End If

End While
Thread.Sleep(500)
' FinalExit is set to true right before
' the main thread cancelled notification.
If finalExit Then
Exit While
End If

End While
End Sub

Private Shared Sub RedirectRequests()


' Code that sends requests
' to other servers.

' Suspend work.


bAllocate = False
End Sub

Private Shared Sub FinishExistingRequests()


' Code that waits a period of time
' for pending requests to finish.

' Clear the simulated workload.


load.Clear()

End Sub

Private Shared Sub AcceptRequests()


' Code that resumes processing
' requests on this server.

' Resume work.


bAllocate = True
End Sub
End Class

Le code suivant contient la méthode utilisateur WaitForFullGCProc , qui contient une boucle continue while pour
vérifier les notifications de garbage collection.
public:
static void WaitForFullGCProc()
{
while (true)
{
// CheckForNotify is set to true and false in Main.
while (checkForNotify)
{
// Check for a notification of an approaching collection.
GCNotificationStatus s = GC::WaitForFullGCApproach();
if (s == GCNotificationStatus::Succeeded)
{
Console::WriteLine("GC Notifiction raised.");
OnFullGCApproachNotify();
}
else if (s == GCNotificationStatus::Canceled)
{
Console::WriteLine("GC Notification cancelled.");
break;
}
else
{
// This can occur if a timeout period
// is specified for WaitForFullGCApproach(Timeout)
// or WaitForFullGCComplete(Timeout)
// and the time out period has elapsed.
Console::WriteLine("GC Notification not applicable.");
break;
}

// Check for a notification of a completed collection.


s = GC::WaitForFullGCComplete();
if (s == GCNotificationStatus::Succeeded)
{
Console::WriteLine("GC Notification raised.");
OnFullGCCompleteEndNotify();
}
else if (s == GCNotificationStatus::Canceled)
{
Console::WriteLine("GC Notification cancelled.");
break;
}
else
{
// Could be a time out.
Console::WriteLine("GC Notification not applicable.");
break;
}
}

Thread::Sleep(500);
// FinalExit is set to true right before
// the main thread cancelled notification.
if (finalExit)
{
break;
}
}
}
public static void WaitForFullGCProc()
{
while (true)
{
// CheckForNotify is set to true and false in Main.
while (checkForNotify)
{
// Check for a notification of an approaching collection.
GCNotificationStatus s = GC.WaitForFullGCApproach();
if (s == GCNotificationStatus.Succeeded)
{
Console.WriteLine("GC Notification raised.");
OnFullGCApproachNotify();
}
else if (s == GCNotificationStatus.Canceled)
{
Console.WriteLine("GC Notification cancelled.");
break;
}
else
{
// This can occur if a timeout period
// is specified for WaitForFullGCApproach(Timeout)
// or WaitForFullGCComplete(Timeout)
// and the time out period has elapsed.
Console.WriteLine("GC Notification not applicable.");
break;
}

// Check for a notification of a completed collection.


GCNotificationStatus status = GC.WaitForFullGCComplete();
if (status == GCNotificationStatus.Succeeded)
{
Console.WriteLine("GC Notification raised.");
OnFullGCCompleteEndNotify();
}
else if (status == GCNotificationStatus.Canceled)
{
Console.WriteLine("GC Notification cancelled.");
break;
}
else
{
// Could be a time out.
Console.WriteLine("GC Notification not applicable.");
break;
}
}

Thread.Sleep(500);
// FinalExit is set to true right before
// the main thread cancelled notification.
if (finalExit)
{
break;
}
}
}
Public Shared Sub WaitForFullGCProc()

While True
' CheckForNotify is set to true and false in Main.

While checkForNotify
' Check for a notification of an approaching collection.
Dim s As GCNotificationStatus = GC.WaitForFullGCApproach
If (s = GCNotificationStatus.Succeeded) Then
Console.WriteLine("GC Notification raised.")
OnFullGCApproachNotify()
ElseIf (s = GCNotificationStatus.Canceled) Then
Console.WriteLine("GC Notification cancelled.")
Exit While
Else
' This can occur if a timeout period
' is specified for WaitForFullGCApproach(Timeout)
' or WaitForFullGCComplete(Timeout)
' and the time out period has elapsed.
Console.WriteLine("GC Notification not applicable.")
Exit While
End If

' Check for a notification of a completed collection.


s = GC.WaitForFullGCComplete
If (s = GCNotificationStatus.Succeeded) Then
Console.WriteLine("GC Notifiction raised.")
OnFullGCCompleteEndNotify()
ElseIf (s = GCNotificationStatus.Canceled) Then
Console.WriteLine("GC Notification cancelled.")
Exit While
Else
' Could be a time out.
Console.WriteLine("GC Notification not applicable.")
Exit While
End If

End While
Thread.Sleep(500)
' FinalExit is set to true right before
' the main thread cancelled notification.
If finalExit Then
Exit While
End If

End While
End Sub

Le code suivant contient la méthode OnFullGCApproachNotify telle qu’elle est appelée à partir de la
Méthode WaitForFullGCProc .
public:
static void OnFullGCApproachNotify()
{
Console::WriteLine("Redirecting requests.");

// Method that tells the request queuing


// server to not direct requests to this server.
RedirectRequests();

// Method that provides time to


// finish processing pending requests.
FinishExistingRequests();

// This is a good time to induce a GC collection


// because the runtime will induce a full GC soon.
// To be very careful, you can check precede with a
// check of the GC.GCCollectionCount to make sure
// a full GC did not already occur since last notified.
GC::Collect();
Console::WriteLine("Induced a collection.");

public static void OnFullGCApproachNotify()


{

Console.WriteLine("Redirecting requests.");

// Method that tells the request queuing


// server to not direct requests to this server.
RedirectRequests();

// Method that provides time to


// finish processing pending requests.
FinishExistingRequests();

// This is a good time to induce a GC collection


// because the runtime will induce a full GC soon.
// To be very careful, you can check precede with a
// check of the GC.GCCollectionCount to make sure
// a full GC did not already occur since last notified.
GC.Collect();
Console.WriteLine("Induced a collection.");
}
Public Shared Sub OnFullGCApproachNotify()
Console.WriteLine("Redirecting requests.")

' Method that tells the request queuing


' server to not direct requests to this server.
RedirectRequests()

' Method that provides time to


' finish processing pending requests.
FinishExistingRequests()

' This is a good time to induce a GC collection


' because the runtime will induce a ful GC soon.
' To be very careful, you can check precede with a
' check of the GC.GCCollectionCount to make sure
' a full GC did not already occur since last notified.
GC.Collect()
Console.WriteLine("Induced a collection.")
End Sub

Le code suivant contient la méthode OnFullGCApproachComplete telle qu’elle est appelée à partir de la
Méthode WaitForFullGCProc .

public:
static void OnFullGCCompleteEndNotify()
{
// Method that informs the request queuing server
// that this server is ready to accept requests again.
AcceptRequests();
Console::WriteLine("Accepting requests again.");
}

public static void OnFullGCCompleteEndNotify()


{
// Method that informs the request queuing server
// that this server is ready to accept requests again.
AcceptRequests();
Console.WriteLine("Accepting requests again.");
}

Public Shared Sub OnFullGCCompleteEndNotify()


' Method that informs the request queuing server
' that this server is ready to accept requests again.
AcceptRequests()
Console.WriteLine("Accepting requests again.")
End Sub

Le code suivant contient les méthodes utilisateur qui sont appelées à partir des méthodes OnFullGCApproachNotify
et OnFullGCCompleteNotify . Les méthodes utilisateur redirigent les requêtes, terminent les requêtes existantes, puis
reprennent les requêtes après qu’un garbage collection a eu lieu.
private:
static void RedirectRequests()
{
// Code that sends requests
// to other servers.

// Suspend work.
bAllocate = false;

static void FinishExistingRequests()


{
// Code that waits a period of time
// for pending requests to finish.

// Clear the simulated workload.


load->Clear();

static void AcceptRequests()


{
// Code that resumes processing
// requests on this server.

// Resume work.
bAllocate = true;
}

private static void RedirectRequests()


{
// Code that sends requests
// to other servers.

// Suspend work.
bAllocate = false;
}

private static void FinishExistingRequests()


{
// Code that waits a period of time
// for pending requests to finish.

// Clear the simulated workload.


load.Clear();
}

private static void AcceptRequests()


{
// Code that resumes processing
// requests on this server.

// Resume work.
bAllocate = true;
}
Private Shared Sub RedirectRequests()
' Code that sends requests
' to other servers.

' Suspend work.


bAllocate = False
End Sub

Private Shared Sub FinishExistingRequests()


' Code that waits a period of time
' for pending requests to finish.

' Clear the simulated workload.


load.Clear()

End Sub

Private Shared Sub AcceptRequests()


' Code that resumes processing
' requests on this server.

' Resume work.


bAllocate = True
End Sub

L’exemple de code complet est le suivant :

using namespace System;


using namespace System::Collections::Generic;
using namespace System::Threading;

namespace GCNotify
{
ref class Program
{
private:
// Variable for continual checking in the
// While loop in the WaitForFullGCProc method.
static bool checkForNotify = false;

// Variable for suspending work


// (such servicing allocated server requests)
// after a notification is received and then
// resuming allocation after inducing a garbage collection.
static bool bAllocate = false;

// Variable for ending the example.


static bool finalExit = false;

// Collection for objects that


// simulate the server request workload.
static List<array<Byte>^>^ load = gcnew List<array<Byte>^>();

public:
static void Main()
{
try
{
// Register for a notification.
GC::RegisterForFullGCNotification(10, 10);
Console::WriteLine("Registered for GC notification.");

checkForNotify = true;
bAllocate = true;
// Start a thread using WaitForFullGCProc.
Thread^ thWaitForFullGC = gcnew Thread(gcnew ThreadStart(&WaitForFullGCProc));
thWaitForFullGC->Start();

// While the thread is checking for notifications in


// WaitForFullGCProc, create objects to simulate a server workload.
try
{
int lastCollCount = 0;
int newCollCount = 0;

while (true)
{
if (bAllocate)
{
load->Add(gcnew array<Byte>(1000));
newCollCount = GC::CollectionCount(2);
if (newCollCount != lastCollCount)
{
// Show collection count when it increases:
Console::WriteLine("Gen 2 collection count: {0}",
GC::CollectionCount(2).ToString());
lastCollCount = newCollCount;
}

// For ending the example (arbitrary).


if (newCollCount == 500)
{
finalExit = true;
checkForNotify = false;
break;
}
}
}

}
catch (OutOfMemoryException^)
{
Console::WriteLine("Out of memory.");
}

finalExit = true;
checkForNotify = false;
GC::CancelFullGCNotification();

}
catch (InvalidOperationException^ invalidOp)
{

Console::WriteLine("GC Notifications are not supported while concurrent GC is enabled.\n"


+ invalidOp->Message);
}
}

public:
static void OnFullGCApproachNotify()
{
Console::WriteLine("Redirecting requests.");

// Method that tells the request queuing


// server to not direct requests to this server.
RedirectRequests();

// Method that provides time to


// finish processing pending requests.
FinishExistingRequests();
// This is a good time to induce a GC collection
// because the runtime will induce a full GC soon.
// To be very careful, you can check precede with a
// check of the GC.GCCollectionCount to make sure
// a full GC did not already occur since last notified.
GC::Collect();
Console::WriteLine("Induced a collection.");

public:
static void OnFullGCCompleteEndNotify()
{
// Method that informs the request queuing server
// that this server is ready to accept requests again.
AcceptRequests();
Console::WriteLine("Accepting requests again.");
}

public:
static void WaitForFullGCProc()
{
while (true)
{
// CheckForNotify is set to true and false in Main.
while (checkForNotify)
{
// Check for a notification of an approaching collection.
GCNotificationStatus s = GC::WaitForFullGCApproach();
if (s == GCNotificationStatus::Succeeded)
{
Console::WriteLine("GC Notifiction raised.");
OnFullGCApproachNotify();
}
else if (s == GCNotificationStatus::Canceled)
{
Console::WriteLine("GC Notification cancelled.");
break;
}
else
{
// This can occur if a timeout period
// is specified for WaitForFullGCApproach(Timeout)
// or WaitForFullGCComplete(Timeout)
// and the time out period has elapsed.
Console::WriteLine("GC Notification not applicable.");
break;
}

// Check for a notification of a completed collection.


s = GC::WaitForFullGCComplete();
if (s == GCNotificationStatus::Succeeded)
{
Console::WriteLine("GC Notification raised.");
OnFullGCCompleteEndNotify();
}
else if (s == GCNotificationStatus::Canceled)
{
Console::WriteLine("GC Notification cancelled.");
break;
}
else
{
// Could be a time out.
Console::WriteLine("GC Notification not applicable.");
break;
}
}
}

Thread::Sleep(500);
// FinalExit is set to true right before
// the main thread cancelled notification.
if (finalExit)
{
break;
}
}
}

private:
static void RedirectRequests()
{
// Code that sends requests
// to other servers.

// Suspend work.
bAllocate = false;

static void FinishExistingRequests()


{
// Code that waits a period of time
// for pending requests to finish.

// Clear the simulated workload.


load->Clear();

static void AcceptRequests()


{
// Code that resumes processing
// requests on this server.

// Resume work.
bAllocate = true;
}
};
}

int main()
{
GCNotify::Program::Main();
}

using System;
using System.Collections.Generic;
using System.Threading;

namespace GCNotify
{
class Program
{
// Variable for continual checking in the
// While loop in the WaitForFullGCProc method.
static bool checkForNotify = false;

// Variable for suspending work


// (such servicing allocated server requests)
// after a notification is received and then
// resuming allocation after inducing a garbage collection.
static bool bAllocate = false;
// Variable for ending the example.
static bool finalExit = false;

// Collection for objects that


// simulate the server request workload.
static List<byte[]> load = new List<byte[]>();

public static void Main(string[] args)


{
try
{
// Register for a notification.
GC.RegisterForFullGCNotification(10, 10);
Console.WriteLine("Registered for GC notification.");

checkForNotify = true;
bAllocate = true;

// Start a thread using WaitForFullGCProc.


Thread thWaitForFullGC = new Thread(new ThreadStart(WaitForFullGCProc));
thWaitForFullGC.Start();

// While the thread is checking for notifications in


// WaitForFullGCProc, create objects to simulate a server workload.
try
{

int lastCollCount = 0;
int newCollCount = 0;

while (true)
{
if (bAllocate)
{
load.Add(new byte[1000]);
newCollCount = GC.CollectionCount(2);
if (newCollCount != lastCollCount)
{
// Show collection count when it increases:
Console.WriteLine("Gen 2 collection count: {0}",
GC.CollectionCount(2).ToString());
lastCollCount = newCollCount;
}

// For ending the example (arbitrary).


if (newCollCount == 500)
{
finalExit = true;
checkForNotify = false;
break;
}
}
}
}
catch (OutOfMemoryException)
{
Console.WriteLine("Out of memory.");
}

finalExit = true;
checkForNotify = false;
GC.CancelFullGCNotification();
}
catch (InvalidOperationException invalidOp)
{

Console.WriteLine("GC Notifications are not supported while concurrent GC is enabled.\n"


+ invalidOp.Message);
}
}
}

public static void OnFullGCApproachNotify()


{

Console.WriteLine("Redirecting requests.");

// Method that tells the request queuing


// server to not direct requests to this server.
RedirectRequests();

// Method that provides time to


// finish processing pending requests.
FinishExistingRequests();

// This is a good time to induce a GC collection


// because the runtime will induce a full GC soon.
// To be very careful, you can check precede with a
// check of the GC.GCCollectionCount to make sure
// a full GC did not already occur since last notified.
GC.Collect();
Console.WriteLine("Induced a collection.");
}

public static void OnFullGCCompleteEndNotify()


{
// Method that informs the request queuing server
// that this server is ready to accept requests again.
AcceptRequests();
Console.WriteLine("Accepting requests again.");
}

public static void WaitForFullGCProc()


{
while (true)
{
// CheckForNotify is set to true and false in Main.
while (checkForNotify)
{
// Check for a notification of an approaching collection.
GCNotificationStatus s = GC.WaitForFullGCApproach();
if (s == GCNotificationStatus.Succeeded)
{
Console.WriteLine("GC Notification raised.");
OnFullGCApproachNotify();
}
else if (s == GCNotificationStatus.Canceled)
{
Console.WriteLine("GC Notification cancelled.");
break;
}
else
{
// This can occur if a timeout period
// is specified for WaitForFullGCApproach(Timeout)
// or WaitForFullGCComplete(Timeout)
// and the time out period has elapsed.
Console.WriteLine("GC Notification not applicable.");
break;
}

// Check for a notification of a completed collection.


GCNotificationStatus status = GC.WaitForFullGCComplete();
if (status == GCNotificationStatus.Succeeded)
{
Console.WriteLine("GC Notification raised.");
OnFullGCCompleteEndNotify();
}
else if (status == GCNotificationStatus.Canceled)
else if (status == GCNotificationStatus.Canceled)
{
Console.WriteLine("GC Notification cancelled.");
break;
}
else
{
// Could be a time out.
Console.WriteLine("GC Notification not applicable.");
break;
}
}

Thread.Sleep(500);
// FinalExit is set to true right before
// the main thread cancelled notification.
if (finalExit)
{
break;
}
}
}

private static void RedirectRequests()


{
// Code that sends requests
// to other servers.

// Suspend work.
bAllocate = false;
}

private static void FinishExistingRequests()


{
// Code that waits a period of time
// for pending requests to finish.

// Clear the simulated workload.


load.Clear();
}

private static void AcceptRequests()


{
// Code that resumes processing
// requests on this server.

// Resume work.
bAllocate = true;
}
}
}

Imports System.Collections.Generic
Imports System.Threading

Class Program
' Variables for continual checking in the
' While loop in the WaitForFullGcProc method.
Private Shared checkForNotify As Boolean = False

' Variable for suspending work


' (such as servicing allocated server requests)
' after a notification is received and then
' resuming allocation after inducing a garbage collection.
Private Shared bAllocate As Boolean = False

' Variable for ending the example.


Private Shared finalExit As Boolean = False
Private Shared finalExit As Boolean = False

' Collection for objects that


' simulate the server request workload.
Private Shared load As New List(Of Byte())

Public Shared Sub Main(ByVal args() As String)


Try
' Register for a notification.
GC.RegisterForFullGCNotification(10, 10)
Console.WriteLine("Registered for GC notification.")

bAllocate = True
checkForNotify = True

' Start a thread using WaitForFullGCProc.


Dim thWaitForFullGC As Thread = _
New Thread(New ThreadStart(AddressOf WaitForFullGCProc))
thWaitForFullGC.Start()

' While the thread is checking for notifications in


' WaitForFullGCProc, create objects to simulate a server workload.
Try
Dim lastCollCount As Integer = 0
Dim newCollCount As Integer = 0

While (True)
If bAllocate = True Then

load.Add(New Byte(1000) {})


newCollCount = GC.CollectionCount(2)
If (newCollCount <> lastCollCount) Then
' Show collection count when it increases:
Console.WriteLine("Gen 2 collection count: {0}", _
GC.CollectionCount(2).ToString)
lastCollCount = newCollCount
End If

' For ending the example (arbitrary).


If newCollCount = 500 Then
finalExit = True
checkForNotify = False
bAllocate = False
Exit While
End If

End If
End While

Catch outofMem As OutOfMemoryException


Console.WriteLine("Out of memory.")
End Try

finalExit = True
checkForNotify = False
GC.CancelFullGCNotification()

Catch invalidOp As InvalidOperationException


Console.WriteLine("GC Notifications are not supported while concurrent GC is enabled." _
& vbLf & invalidOp.Message)
End Try
End Sub

Public Shared Sub OnFullGCApproachNotify()


Console.WriteLine("Redirecting requests.")

' Method that tells the request queuing


' server to not direct requests to this server.
' server to not direct requests to this server.
RedirectRequests()

' Method that provides time to


' finish processing pending requests.
FinishExistingRequests()

' This is a good time to induce a GC collection


' because the runtime will induce a ful GC soon.
' To be very careful, you can check precede with a
' check of the GC.GCCollectionCount to make sure
' a full GC did not already occur since last notified.
GC.Collect()
Console.WriteLine("Induced a collection.")
End Sub

Public Shared Sub OnFullGCCompleteEndNotify()


' Method that informs the request queuing server
' that this server is ready to accept requests again.
AcceptRequests()
Console.WriteLine("Accepting requests again.")
End Sub

Public Shared Sub WaitForFullGCProc()

While True
' CheckForNotify is set to true and false in Main.

While checkForNotify
' Check for a notification of an approaching collection.
Dim s As GCNotificationStatus = GC.WaitForFullGCApproach
If (s = GCNotificationStatus.Succeeded) Then
Console.WriteLine("GC Notification raised.")
OnFullGCApproachNotify()
ElseIf (s = GCNotificationStatus.Canceled) Then
Console.WriteLine("GC Notification cancelled.")
Exit While
Else
' This can occur if a timeout period
' is specified for WaitForFullGCApproach(Timeout)
' or WaitForFullGCComplete(Timeout)
' and the time out period has elapsed.
Console.WriteLine("GC Notification not applicable.")
Exit While
End If

' Check for a notification of a completed collection.


s = GC.WaitForFullGCComplete
If (s = GCNotificationStatus.Succeeded) Then
Console.WriteLine("GC Notifiction raised.")
OnFullGCCompleteEndNotify()
ElseIf (s = GCNotificationStatus.Canceled) Then
Console.WriteLine("GC Notification cancelled.")
Exit While
Else
' Could be a time out.
Console.WriteLine("GC Notification not applicable.")
Exit While
End If

End While
Thread.Sleep(500)
' FinalExit is set to true right before
' the main thread cancelled notification.
If finalExit Then
Exit While
End If

End While
End Sub
End Sub

Private Shared Sub RedirectRequests()


' Code that sends requests
' to other servers.

' Suspend work.


bAllocate = False
End Sub

Private Shared Sub FinishExistingRequests()


' Code that waits a period of time
' for pending requests to finish.

' Clear the simulated workload.


load.Clear()

End Sub

Private Shared Sub AcceptRequests()


' Code that resumes processing
' requests on this server.

' Resume work.


bAllocate = True
End Sub
End Class

Voir aussi
Garbage collection
Analyse de ressource de domaine d'application
18/07/2020 • 10 minutes to read • Edit Online

L'analyse de ressource de domaine d'application (ARM) permet aux hôtes de contrôler l'utilisation de l'UC et de la
mémoire par domaine d'application. Il est utile pour les ordinateurs hôtes comme ASP.NET qui utilisent de
nombreux domaines d’application dans un processus à long terme. L'hôte peut décharger le domaine
d'application d'une application qui affecte de façon défavorable les performances de l'ensemble du processus,
mais uniquement s'il peut identifier l'application problématique. ARM fournit des informations qui peuvent être
utilisées pour faciliter la prise de telles décisions.
Par exemple, un service d’hébergement peut avoir de nombreuses applications s’exécutant sur un serveur ASP.NET.
Si une application du processus commence à consommer trop de mémoire ou trop de temps processeur, le service
d'hébergement peut utiliser ARM pour identifier le domaine d'application qui provoque le problème.
ARM est suffisamment léger pour être utilisé dans des applications actives. Vous pouvez accéder aux informations
à l’aide du suivi d’événements pour Windows (ETW) ou directement via des API managées ou natives.

Activation de l’analyse des ressources


ARM peut être activé de quatre façons : en fournissant un fichier de configuration lorsque le Common Language
Runtime (CLR) est démarré, en utilisant une API d'hébergement non managée, en utilisant du code managé ou en
écoutant des événements ETW ARM.
Dès que ARM est activé, il commence à collecter des données sur tous les domaines d'application du processus. Si
un domaine d'application a été créé avant l'activation de ARM, les données cumulatives démarrent avec
l'activation de ARM, pas au moment de la création du domaine d'application. Une fois activé, ARM ne peut pas être
désactivé.
Vous pouvez activer ARM au démarrage du CLR en ajoutant l' <appDomainResourceMonitoring> élément
au fichier de configuration et en affectant à l’attribut la valeur enabled true . La valeur false (valeur par
défaut) signifie uniquement que ARM n'est pas activé au démarrage. Vous pouvez l'activer ultérieurement à
l'aide de l'un des autres mécanismes d'activation.
L’hôte peut activer ARM en demandant l’interface d’hébergement ICLRAppDomainResourceMonitor. Une
fois cette interface obtenue avec succès, ARM est activé.
Un code managé peut activer ARM en définissant la propriété statique ( Shared en Visual Basic)
AppDomain.MonitoringIsEnabled sur true . Une fois la propriété définie, ARM est activé.
Vous pouvez activer ARM après le démarrage en écoutant les événements ETW. ARM est activé et
commence à déclencher des événements pour tous les domaines d'application lorsque vous activez le
fournisseur public Microsoft-Windows-DotNETRuntime à l’aide du mot clé AppDomainResourceManagementKeyword .
Pour mettre en corrélation des données avec les domaines d'application et les threads, vous devez
également activer le fournisseur Microsoft-Windows-DotNETRuntimeRundown à l’aide du mot clé
ThreadingKeyword .

Utilisation d’ARM
ARM fournit le temps processeur total utilisé par un domaine d’application ainsi que trois types d’informations
concernant l’utilisation de la mémoire.
Temps processeur total pour un domaine d'application, en secondes : calculé en additionnant les
temps de thread signalés par le système d'exploitation pour tous les threads qui ont passé du temps à
s'exécuter dans le domaine d'application pendant leur durée de vie. Les threads bloqués ou en veille
n'utilisent pas de temps processeur. Lorsqu'un thread appelle en code natif, le temps que le thread passe en
code natif est inclus dans le nombre pour le domaine d'application où l'appel a été passé.
API managée : propriété AppDomain.MonitoringTotalProcessorTime.
API d’hébergement : méthode ICLRAppDomainResourceMonitor::GetCurrentCpuTime.
Événements ETW : événements ThreadCreated , ThreadAppDomainEnter et ThreadTerminated . Pour plus
d’informations sur les fournisseurs et les mots clés, consultez « Événements de contrôle des
ressources AppDomain » dans Événements ETW du CLR.
Allocations managées totales faites par un domaine d'application pendant sa durée de vie, en
octets : les allocations totales ne reflètent pas toujours l'utilisation de la mémoire par un domaine
d'application, car les objets alloués peuvent être éphémères. Toutefois, si une application alloue et libère de
grandes quantités d'objets, le coût des allocations peut être significatif.
API managée : propriété AppDomain.MonitoringTotalAllocatedMemorySize.
API d’hébergement : méthode ICLRAppDomainResourceMonitor::GetCurrentAllocated.
Événements ETW : événement AppDomainMemAllocated , champ Allocated .
Mémoire managée, en octets, qui est référencée par un domaine d'application et qui a sur vécu
à la collecte bloquante complète la plus récente : ce nombre est exact uniquement après une collecte
bloquante complète. (Contrairement aux collections simultanées, qui se produisent en arrière-plan et ne
bloquent pas l’application.) Par exemple, la GC.Collect() surcharge de méthode provoque une collection
bloquante complète.
API managée : propriété AppDomain.MonitoringSurvivedMemorySize.
API d’hébergement : méthode ICLRAppDomainResourceMonitor::GetCurrentSurvived, paramètre
pAppDomainBytesSurvived .

Événements ETW : événement AppDomainMemSurvived , champ Survived .


Mémoire managée totale, en octets, qui est référencée par le processus et qui a sur vécu à la
collecte bloquante complète la plus récente : la mémoire ayant survécu des domaines d'application
individuels peut être comparée à ce nombre.
API managée : propriété AppDomain.MonitoringSurvivedProcessMemorySize.
API d’hébergement : méthode ICLRAppDomainResourceMonitor::GetCurrentSurvived, paramètre
pTotalBytesSurvived .

Événements ETW : événement AppDomainMemSurvived , champ ProcessSurvived .


Détermination d'une collecte bloquante complète
Pour déterminer quand la quantité de mémoire ayant survécu est exacte, vous devez savoir quand une collecte
bloquante complète s'est produite. La méthode utilisée pour cela dépend de l'API que vous utilisez pour examiner
les statistiques ARM.
API gérée
Si vous utilisez les propriétés de la classe AppDomain, vous pouvez utiliser la méthode
GC.RegisterForFullGCNotification afin de vous inscrire pour la notification des collectes complètes. Le seuil que
vous utilisez n'est pas important, car vous attendez l'achèvement d'une collecte plutôt que l'approche d'une
collecte. Vous pouvez ensuite appeler la méthode GC.WaitForFullGCComplete, qui se bloque jusqu'à ce qu'une
collecte complète soit terminée. Vous pouvez créer un thread qui appelle la méthode dans une boucle et effectue
toutes les analyses nécessaires chaque fois que la méthode est retournée.
Sinon, vous pouvez appeler la méthode GC.CollectionCount périodiquement pour voir si le nombre de collectes de
génération 2 a augmenté. En fonction de la fréquence d'interrogation, cette technique peut ne pas fournir une
indication exacte de l'occurrence d'une collecte complète.
API d’hébergement
Si vous utilisez l'API d'hébergement non managée, votre hôte doit passer une implémentation de l'interface
IHostGCManager au CLR. Le CLR appelle la méthode IHostGCManager::SuspensionEnding lorsqu'il reprend
l'exécution des threads suspendus pendant une collecte. Le CLR passe la génération de la collecte terminée comme
un paramètre de la méthode, donc l'hôte peut déterminer si la collecte était complète ou partielle. Votre
implémentation de la méthode IHostGCManager::SuspensionEnding peut lancer une requête de la mémoire ayant
survécu, afin de vérifier que les nombres sont extraits dès qu'ils sont mis à jour.

Voir aussi
AppDomain.MonitoringIsEnabled
ICLRAppDomainResourceMonitor, interface
<appDomainResourceMonitoring>
Événements ETW du CLR
Références faibles
18/07/2020 • 5 minutes to read • Edit Online

Le Garbage collector ne peut pas collecter un objet actuellement utilisé par une application, tandis que le code de
l’application peut accéder à cet objet. On dit de l’application qu’elle a une référence forte à l’objet.
Une référence faible permet au Garbage collector de collecter l’objet tout en permettant à l’application d’y accéder.
Une référence faible est valide uniquement pour une période indéterminée jusqu’à ce que l’objet soit collecté
quand il n’existe aucune référence forte. Quand vous utilisez une référence faible, l’application peut toujours
obtenir une référence forte à l’objet, ce qui l’empêche d’être collecté. Toutefois, il existe toujours un risque que le
Garbage collector accède à l’objet avant qu’une référence forte soit rétablie.
Les références faibles sont utiles pour les objets qui utilisent beaucoup de mémoire, mais peuvent être recréées
facilement si elles sont récupérées par le garbage collection.
Supposons que l’arborescence d’une application Windows Forms présente à l’utilisateur un choix hiérarchique
d’options complexe. Si les données sous-jacentes sont volumineuses, la conservation de l’arborescence en
mémoire est inefficace quand l’utilisateur est impliqué dans un autre élément de l’application.
Quand l’utilisateur passe à une autre partie de l’application, vous pouvez utiliser la classe WeakReference pour
créer une référence faible à l’arborescence et détruire toutes les références fortes. Quand l’utilisateur revient à
l’arborescence, l’application tente d’obtenir une référence forte à l’arborescence et, en cas de réussite, évite de
reconstruire l’arborescence.
Pour établir une référence faible à un objet, vous créez un WeakReference à l’aide de l’instance de l’objet à suivre.
Vous affectez ensuite cet objet à la propriété Target et vous définissez la référence d’origine à l’objet sur null . Pour
obtenir un exemple de code, consultez WeakReference dans la bibliothèque de classes.

Références faibles courtes et longues


Vous pouvez créer une référence faible courte ou une référence faible longue :
Court
La cible d’une référence faible courte devient null quand l’objet est récupéré par le garbage collection. La
référence faible est elle-même un objet managé et fait l’objet d’un garbage collection comme tout autre
objet managé. Une référence faible courte est le constructeur sans paramètre pour WeakReference.
Long
Une référence faible longue est conservée après l’appel de la méthode Finalize. Cela permet à l’objet d’être
recréé, mais l’état de l’objet reste imprévisible. Pour utiliser une référence longue, spécifiez true dans le
constructeur WeakReference.
Si le type de l’objet ne dispose pas d’une méthode Finalize, la fonctionnalité de la référence faible courte
s’applique et la référence faible est valide uniquement jusqu’à la collecte de la cible, ce qui peut se produire à
tout moment après l’exécution du finaliseur.
Pour établir une référence forte et réutiliser l’objet, effectuez un cast de la propriété Target d’un WeakReference
vers le type de l’objet. Si la propriété Target retourne la valeur null , l’objet a été collecté ; sinon, vous pouvez
continuer à utiliser l’objet, car l’application a récupéré une référence forte à celui-ci.

Instructions d’utilisation de références faibles


Utilisez des références faibles longues uniquement quand cela est nécessaire, car l’état de l’objet est imprévisible
après la finalisation.
Évitez d’utiliser des références faibles aux petits objets, car le pointeur lui-même peut être de la même taille ou
d’une taille supérieure.
Évitez d’utiliser les références faibles comme solution automatique aux problèmes de gestion de la mémoire.
Développez plutôt une stratégie de mise en cache efficace pour gérer les objets de votre application.

Voir aussi
Garbage collection
Génériques en .NET
18/07/2020 • 18 minutes to read • Edit Online

Les génériques vous permettent d'adapter une méthode, une classe ou une structure au type de données sur
lequel elle agit. Par exemple, au lieu d'utiliser la classe Hashtable qui accepte tous les types de clés et de valeurs,
vous pouvez utiliser la classe générique Dictionary<TKey,TValue> et spécifier le type autorisé pour la clé et pour la
valeur. Les génériques présentent plusieurs avantages, notamment une plus grande réutilisabilité du code et une
meilleure cohérence des types.

Définition et utilisation des génériques


Les génériques sont des classes, des structures, des interfaces et des méthodes qui possèdent des espaces réservés
(ou paramètres de type) pour un ou plusieurs des types qu'ils stockent ou utilisent. Une classe de collection
générique peut utiliser un paramètre de type comme espace réservé pour le type des objets qu'elle stocke. Les
paramètres de type apparaissent alors comme les types de ses champs, et les types de paramètre, comme ses
méthodes. Une méthode générique peut utiliser son paramètre de type comme le type de sa valeur de retour ou
comme le type de l'un de ses paramètres formels. Le code suivant montre une définition de classe générique
simple.

generic<typename T>
public ref class Generics
{
public:
T Field;
};

public class Generic<T>


{
public T Field;
}

Public Class Generic(Of T)


Public Field As T

End Class

Quand vous créez une instance de classe générique, vous spécifiez les types à substituer aux paramètres de type.
Cela crée une nouvelle classe générique, appelée classe générique construite, avec les types substitués là où
s'affichent les paramètres de type. Le résultat est une classe de type sécurisé adaptée à votre choix de types,
comme le montre le code suivant.

static void Main()


{
Generics<String^>^ g = gcnew Generics<String^>();
g->Field = "A string";
//...
Console::WriteLine("Generics.Field = \"{0}\"", g->Field);
Console::WriteLine("Generics.Field.GetType() = {0}", g->Field->GetType()->FullName);
}
public static void Main()
{
Generic<string> g = new Generic<string>();
g.Field = "A string";
//...
Console.WriteLine("Generic.Field = \"{0}\"", g.Field);
Console.WriteLine("Generic.Field.GetType() = {0}", g.Field.GetType().FullName);
}

Public Shared Sub Main()


Dim g As New Generic(Of String)
g.Field = "A string"
'...
Console.WriteLine("Generic.Field = ""{0}""", g.Field)
Console.WriteLine("Generic.Field.GetType() = {0}", g.Field.GetType().FullName)
End Sub

Terminologie relative aux génériques


Les termes suivants sont employés quand on aborde le sujet des génériques de .NET :
Une définition de type générique est une déclaration de classe, de structure ou d'interface qui fonctionne
comme un modèle, avec des espaces réservés pour les types qu'elle peut contenir ou utiliser. Par exemple, la
classe System.Collections.Generic.Dictionary<TKey,TValue> peut contenir deux types : des clés et des
valeurs. Étant donné qu'une définition de type générique n'est qu'un modèle, vous ne pouvez pas créer
d'instances d'une classe, d'une structure ou d'une interface qui correspond à une définition de type
générique.
Lesparamètres de type générique, ou paramètres de type, sont des espaces réservés compris dans une
définition de type ou de méthode générique. Le type générique
System.Collections.Generic.Dictionary<TKey,TValue> possède deux paramètres de type, TKey et TValue ,
qui représentent les types de ses clés et de ses valeurs.
Un type générique construit, ou type construit, est le résultat de la spécification de types pour les
paramètres de type générique d'une définition de type générique.
Un argument de type générique correspond à tout type substitué par un paramètre de type générique.
Le terme général type générique correspond à la fois aux types construits et aux définitions de type
générique.
Lacovariance et la contravariance des paramètres de type générique permettent d'utiliser des types
génériques construits dont les arguments de type sont plus dérivés (covariance) ou moins dérivés
(contravariance) qu'un type construit cible. La covariance et la contravariance sont désignées collectivement
sous le nom de variation. Pour plus d’informations, consultez Covariance et contravariance.
Lescontraintes sont des limites appliquées aux paramètres de type générique. Par exemple, vous pouvez
limiter un paramètre de type aux types qui implémentent l'interface générique
System.Collections.Generic.IComparer<T> afin que les instances de ce type puissent être classées. Vous
pouvez également limiter les paramètres de type à des types qui ont une classe de base particulière, qui ont
un constructeur sans paramètre, ou qui sont des types référence ou des types valeur. Les utilisateurs du type
générique ne peuvent pas remplacer les arguments de type qui ne respectent pas les contraintes.
Une définition de méthode générique est une méthode qui comporte deux listes de paramètres : une liste
de paramètres de type générique et une liste de paramètres formels. Les paramètres de type peuvent
apparaître comme le type de retour ou comme les types des paramètres formels, comme le montre le code
suivant.
generic<typename T>
T Generic(T arg)
{
T temp = arg;
//...
return temp;
}

T Generic<T>(T arg)
{
T temp = arg;
//...
return temp;
}

Function Generic(Of T)(ByVal arg As T) As T


Dim temp As T = arg
'...
Return temp
End Function

Les méthodes génériques peuvent appartenir à des types génériques ou non génériques. Il est important de noter
qu'une méthode n'est pas générique simplement parce qu'elle appartient à un type générique, ou encore parce
qu'elle a des paramètres formels dont les types sont les paramètres génériques du type englobant. Une méthode
est générique uniquement si elle possède sa propre liste de paramètres de type. Dans le code suivant, seule la
méthode G est générique.

ref class A
{
generic<typename T>
T G(T arg)
{
T temp = arg;
//...
return temp;
}
};
generic<typename T>
ref class Generic
{
T M(T arg)
{
T temp = arg;
//...
return temp;
}
};
class A
{
T G<T>(T arg)
{
T temp = arg;
//...
return temp;
}
}
class Generic<T>
{
T M(T arg)
{
T temp = arg;
//...
return temp;
}
}

Class A
Function G(Of T)(ByVal arg As T) As T
Dim temp As T = arg
'...
Return temp
End Function
End Class
Class Generic(Of T)
Function M(ByVal arg As T) As T
Dim temp As T = arg
'...
Return temp
End Function
End Class

Avantages et inconvénients des génériques


Il existe de nombreux avantages à l'utilisation de collections et de délégués génériques :
Sécurité de type. Grâce aux génériques, le compilateur se charge de la cohérence des types à votre place. Il
est inutile d'écrire du code pour tester le type de données nécessaire, car il est appliqué au moment de la
compilation. Ainsi, le besoin d'un cast de type et le risque d'erreurs au moment de l'exécution sont réduits.
Moins de code est nécessaire et il est plus facile de le réutiliser. Il n'est pas nécessaire d'hériter d'un type de
base et de substituer des membres. Par exemple, LinkedList<T> peut être utilisé immédiatement. Vous
pouvez créer une liste liée de chaînes à l'aide de la déclaration de variable suivante :

LinkedList<String^>^ llist = gcnew LinkedList<String^>();

LinkedList<string> llist = new LinkedList<string>();

Dim llist As New LinkedList(Of String)()

Performances améliorées. Les types de collections génériques sont généralement plus efficaces pour le
stockage et la manipulation des types valeur, car ceux-ci ne nécessitent pas de boxing.
Les délégués génériques permettent d'effectuer des rappels de type sécurisé sans avoir à créer plusieurs
classes déléguées. Par exemple, le délégué générique Predicate<T> permet de créer une méthode qui
implémente vos propres critères de recherche pour un type particulier et d'utiliser votre méthode avec les
méthodes du type Array telles que Find, FindLastet FindAll.
Les génériques simplifient le code généré dynamiquement. Quand vous utilisez des génériques avec du
code généré dynamiquement, vous n'avez pas besoin de générer le type. Cela permet d'augmenter le
nombre de scénarios dans lesquels vous pouvez utiliser des méthodes dynamiques légères au lieu de
générer des assemblys entiers. Pour plus d'informations, consultez Guide pratique pour définir et exécuter
des méthodes dynamiques et DynamicMethod.
Voici quelques-unes des limitations des génériques :
Les types génériques peuvent être dérivés de la plupart des classes de base, telles que MarshalByRefObject
(et les contraintes peuvent être utilisées pour exiger que les paramètres de type générique dérivent de
classes de base comme MarshalByRefObject). Cependant, le .NET Framework ne prend pas en charge les
types génériques liés au contexte. Un type générique peut être dérivé de ContextBoundObject. Toutefois, si
vous tentez de créer une instance de ce type, une exception TypeLoadExceptionsera levée.
Les énumérations ne peuvent pas avoir de paramètres de type générique. Une énumération ne peut être
générique que de manière secondaire (par exemple, parce qu'elle est imbriquée dans un type générique qui
est défini à l'aide de Visual Basic, C# ou C++). Pour plus d’informations, consultez la section "Énumérations"
dans Common Type System.
Les méthodes dynamiques légères ne peuvent pas être génériques.
Dans Visual Basic, C# et C++, un type imbriqué qui est inclus dans un type générique ne peut pas être
instancié, à moins que des types aient été assignés aux paramètres de type de tous les types englobants. En
d'autres termes, un type imbriqué défini à l'aide de ces langages comprend les paramètres de type de tous
ses types englobants. Ainsi, les paramètres de type des types englobants peuvent être utilisés dans les
définitions de membres d'un type imbriqué. Pour plus d'informations, consultez "Types imbriqués" dans
MakeGenericType.

NOTE
Un type imbriqué qui est défini par émission de code dans un assembly dynamique ou avec Ilasm.exe (IL Assembler)
n’a pas besoin d’inclure les paramètres de type de ses types englobants. Toutefois, s’il ne les inclut pas, les paramètres
de type ne seront pas dans la portée de la classe imbriquée.

Pour plus d'informations, consultez "Types imbriqués" dans MakeGenericType.

Bibliothèque de classes et prise en charge des langages


.NET fournit plusieurs classes de collection génériques dans les espaces de noms suivants :
L'espace de noms System.Collections.Generic contient la plupart des types de collections génériques fournis
par .NET, comme les classes génériques List<T> et Dictionary<TKey,TValue>.
L'espace de noms System.Collections.ObjectModel contient d’autres types de collections génériques, tels
que la classe générique ReadOnlyCollection<T>, qui sont utiles pour exposer des modèles objet aux
utilisateurs de vos classes.
Les interfaces génériques qui servent à l'implémentation des comparaisons d'égalité et de tri sont fournies dans
l'espace de noms System , en même temps que les types délégués génériques pour les gestionnaires
d'événements, les conversions et les prédicats de recherche.
La prise en charge des génériques a été ajoutée à l'espace de noms System.Reflection pour l'examen des types et
des méthodes génériques, à System.Reflection.Emit pour l'émission d'assemblys dynamiques qui contiennent des
types et des méthodes génériques, et à System.CodeDom pour la génération de graphiques source comprenant
des génériques.
Le Common Language Runtime fournit de nouveaux codes d'opérations et préfixes pour la prise en charge des
types génériques dans le langage MSIL (Microsoft Intermediate Language), notamment Stelem, Ldelem,
Unbox_Any, Constrainedet Readonly.
Visual C++, C# et Visual Basic fournissent une prise en charge complète de la définition et de l'utilisation des
génériques. Pour plus d'informations sur la prise en charge des langages, consultez Types génériques en Visual
Basic, Introduction aux génériques et Vue d'ensemble des génériques dans Visual C++.

Types imbriqués et génériques


Un type qui est imbriqué dans un type générique peut dépendre des paramètres de type du type générique
englobant. Le Common Language Runtime considère que les types imbriqués sont génériques, même s'ils n'ont
pas leurs propres paramètres de type générique. Lorsque vous créez une instance d'un type imbriqué, vous devez
spécifier les arguments de type pour tous les types génériques englobants.

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Collections génériques dans .NET Aborde les classes de collection générique et d’autres types
génériques de .NET.

Délégués génériques pour la manipulation de tableaux et de Aborde les délégués génériques pour les conversions, les
listes prédicats de recherche et les actions à effectuer sur les
éléments d'un tableau ou d'une collection.

Interfaces génériques Traite des interfaces génériques qui fournissent des


fonctionnalités communes à plusieurs familles de types
génériques.

Covariance et contravariance Aborde la covariance et la contravariance dans les paramètres


de type générique.

Types de collections couramment utilisés Fournit des informations récapitulatives sur les
caractéristiques et les scénarios d’utilisation des types de
collections de .NET, notamment les types génériques.

Quand utiliser des collections génériques Traite des règles générales permettant de déterminer le
moment auquel utiliser des types de collections génériques.

Procédure : définir un type générique avec l’émission de Explique comment générer des assemblys dynamiques qui
réflexion incluent des types et des méthodes génériques.

Generic Types in Visual Basic Explique la fonction des génériques pour les utilisateurs de
Visual Basic et fournit des procédures concernant l'utilisation
et la définition des types génériques.

Introduction aux génériques Fournit une vue d'ensemble de la définition et de l'utilisation


des types génériques pour les utilisateurs de C#.

Vue d’ensemble des génériques dans Visual C++ Explique la fonction des génériques pour les utilisateurs C++,
y compris les différences entre les génériques et les modèles.
Informations de référence
System.Collections.Generic
System.Collections.ObjectModel
System.Reflection.Emit.OpCodes
Vue d’ensemble des types génériques
18/07/2020 • 5 minutes to read • Edit Online

Les développeurs utilisent tout le temps des génériques dans .NET, implicitement ou explicitement. Quand vous
utilisez LINQ dans .NET, avez-vous déjà remarqué que vous utilisez IEnumerable<T> ? Ou si vous avez déjà vu un
exemple en ligne d’un « référentiel générique » pour communiquer avec les bases de données à l’aide de Entity
Framework, Saviez-vous que la plupart des méthodes retournent IQueryable<T> ? Vous vous êtes peut-être
demandé ce que signifie le T dans ces exemples et pourquoi il s’y trouve.
Les génériques introduits dans .NET Framework 2.0 sont essentiellement un « modèle de code » qui permet aux
développeurs de définir des structures de données de type sécurisé sans se limiter à un type de données réel. Par
exemple, List<T> est une collection générique qui peut être déclarée et utilisée avec n’importe quel type, tel que
List<int> , List<string> ou List<Person> .

Pour comprendre en quoi les génériques sont utiles, nous devons jeter un œil à une classe spécifique avant et
après l’ajout de génériques : ArrayList. Dans .NET Framework 1.0, les éléments ArrayList étaient de type Object.
Tout élément ajouté à la collection a été converti en mode silencieux en Object . Cela se produit lors de la lecture
d’éléments de la liste. Ce processus est appelé boxing et unboxing, et il a un impact sur le niveau de performance.
Toutefois, il n’existe aucun moyen de déterminer le type de données dans la liste au moment de la compilation, ce
qui crée un code fragile. Les génériques résolvent ce problème en définissant le type de données que chaque
instance de la liste contiendra. Par exemple, vous pouvez ajouter uniquement des entiers à List<int> et
uniquement des personnes à List<Person> .
Les génériques sont également disponibles au moment de l’exécution. Le runtime sait quel type de structure de
données vous utilisez et peut le stocker dans la mémoire plus efficacement.
L’exemple suivant est un petit programme qui illustre l’efficacité de la connaissance du type de structure de
données au moment de l’exécution :
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;

namespace GenericsExample {
class Program {
static void Main(string[] args) {
//generic list
List<int> ListGeneric = new List<int> { 5, 9, 1, 4 };
//non-generic list
ArrayList ListNonGeneric = new ArrayList { 5, 9, 1, 4 };
// timer for generic list sort
Stopwatch s = Stopwatch.StartNew();
ListGeneric.Sort();
s.Stop();
Console.WriteLine($"Generic Sort: {ListGeneric} \n Time taken: {s.Elapsed.TotalMilliseconds}ms");

//timer for non-generic list sort


Stopwatch s2 = Stopwatch.StartNew();
ListNonGeneric.Sort();
s2.Stop();
Console.WriteLine($"Non-Generic Sort: {ListNonGeneric} \n Time taken:
{s2.Elapsed.TotalMilliseconds}ms");
Console.ReadLine();
}
}
}

Ce programme produit une sortie similaire à ce qui suit :

Generic Sort: System.Collections.Generic.List`1[System.Int32]


Time taken: 0.0034ms
Non-Generic Sort: System.Collections.ArrayList
Time taken: 0.2592ms

La première chose que vous pouvez remarquer ici est que le tri de la liste générique est beaucoup plus rapide que
le tri de la liste non générique. Vous pouvez également noter que le type de la liste générique est différent
([System.Int32]), alors que le type de la liste non générique est généralisé. Étant donné que le runtime sait que le
générique List<int> est de type Int32 , il peut stocker les éléments de liste dans un tableau d’entiers sous-jacent
en mémoire, tandis que le non générique ArrayList doit effectuer un cast de chaque élément de liste en un objet.
Dans cet exemple, les transtypages supplémentaires prennent du temps et ralentissent le tri de la liste.
Un autre avantage de la connaissance du type de votre générique par le runtime est une meilleure expérience de
débogage. Quand vous déboguez un générique en C#, vous connaissez le type de chaque élément dans votre
structure de données. Sans les génériques, vous ne pourriez pas connaître le type de chaque élément.

Voir aussi
Guide de programmation C# - Génériques
Collections génériques dans .NET
18/07/2020 • 3 minutes to read • Edit Online

La bibliothèque de classes .NET fournit plusieurs classes de collections génériques dans les espaces de noms
System.Collections.Generic et System.Collections.ObjectModel. Pour plus d’informations sur ces classes, consultez
Types de collections fréquemment utilisés.

System.Collections.Generic
La plupart des types de collections génériques sont des équivalents directs de types non génériques.
Dictionary<TKey,TValue> est une version générique de Hashtable ; elle utilise la structure générique
KeyValuePair<TKey,TValue> pour l'énumération, au lieu de DictionaryEntry.
List<T> est une version générique de ArrayList. Il existe des classes génériques Queue<T> et Stack<T> qui
correspondent aux versions non génériques.
Il existe des versions génériques et non génériques de SortedList<TKey,TValue>. Les deux versions sont des
hybrides d'un dictionnaire et une liste. La classe générique SortedDictionary<TKey,TValue> est un dictionnaire pur
et n'a pas de contrepartie non générique.
La classe générique LinkedList<T> est une véritable liste liée. Il n'a pas de contrepartie non générique.

System.Collections.ObjectModel
La classe générique Collection<T> fournit une classe de base pour dériver vos propres types de collections
génériques. La classe ReadOnlyCollection<T> offre un moyen facile de produire une collection en lecture seule à
partir de n'importe quel type implémentant l'interface générique IList<T>. La classe générique
KeyedCollection<TKey,TItem> fournit un moyen de stocker des objets qui contiennent leurs propres clés.

Autres types génériques


La structure générique Nullable<T> vous permet d'utiliser des types valeur comme si la valeur null pouvait leur
être affectée. Ceci peut être utile quand vous travaillez avec des requêtes de base de données, où des champs
contenant des types valeur peuvent être manquants. Le paramètre de type générique peut être n'importe quel type
valeur.

NOTE
En C# et Visual Basic, il n'est pas nécessaire d'utiliser Nullable<T> explicitement, car le langage a une syntaxe pour les types
Nullables. Consultez types valeur Nullable (référence C#) et types valeur Nullable (Visual Basic).

La structure générique ArraySegment<T> offre un moyen de délimiter une plage d'éléments dans un tableau
unidimensionnel de base zéro, de n'importe quel type. Le paramètre de type générique est le type des éléments du
tableau.
Le délégué générique EventHandler<TEventArgs> élimine le besoin de déclarer un type de délégué pour gérer les
événements, si votre événement respecte le modèle de gestion des événements utilisé par le .NET Framework. Par
exemple, supposons que vous avez créé une classe MyEventArgs dérivée de EventArgs, pour stocker les données
de votre événement. Vous pouvez ensuite déclarer l'événement comme suit :
public:
event EventHandler<MyEventArgs^>^ MyEvent;

public event EventHandler<MyEventArgs> MyEvent;

Public Event MyEvent As EventHandler(Of MyEventArgs)

Voir aussi
System.Collections.Generic
System.Collections.ObjectModel
Génériques
Délégués génériques pour la manipulation de tableaux et de listes
Interfaces génériques
Délégués génériques pour la manipulation de
tableaux et de listes
18/07/2020 • 5 minutes to read • Edit Online

Cette rubrique donne une vue d’ensemble des délégués génériques pour les conversions, des prédicats de
recherche et des actions à effectuer sur les éléments d’un tableau ou d’une collection.

Délégués génériques pour la manipulation de tableaux et de listes


Le délégué générique Action<T> représente une méthode qui effectue une action sur un élément du type spécifié.
Vous pouvez créer une méthode qui exécute l'action souhaitée sur l'élément, créer une instance du délégué
Action<T> pour représenter cette méthode, puis passer le tableau et le délégué à la méthode générique statique
Array.ForEach. La méthode est appelée pour chaque élément du tableau.
La classe générique List<T> fournit également une méthode ForEach qui utilise le délégué Action<T>. Cette
méthode n'est pas générique.

NOTE
Ceci constitue un point intéressant concernant les types et les méthodes génériques. La méthode Array.ForEach doit être
statique ( Shared en Visual Basic) et générique, car Array n’est pas un type générique. La seule raison pour laquelle vous
pouvez spécifier un type pour que Array.ForEach fonctionne est que la méthode a sa propre liste de paramètres de type. En
revanche, la méthode List<T>.ForEach non générique appartient à la classe générique List<T>, de sorte qu’elle utilise
simplement le paramètre de type de sa classe. La classe est fortement typée :la méthode peut donc être une méthode
d'instance.

Le délégué générique Predicate<T> représente une méthode qui détermine si un élément particulier répond aux
critères que vous définissez. Vous pouvez l'utiliser avec les méthodes génériques statiques suivantes de Array pour
rechercher un élément ou un ensemble d'éléments : Exists, Find, FindAll, FindIndex, FindLast, FindLastIndex et
TrueForAll.
Predicate<T> fonctionne également avec les méthodes d'instance non génériques correspondantes de la classe
générique List<T>.
Le délégué générique Comparison<T> vous permet de fournir un ordre de tri pour les éléments d'un tableau ou
d'une liste qui n'ont pas un ordre de tri natif, ou de remplacer l'ordre de tri natif. Créez une méthode qui effectue la
comparaison, créez une instance du délégué Comparison<T> pour représenter votre méthode, puis passez le
tableau et le délégué à la méthode générique statique Array.Sort<T>(T[], Comparison<T>). La classe générique
List<T> fournit une surcharge de méthode d'instance correspondante, List<T>.Sort(Comparison<T>).
Le délégué générique Converter<TInput,TOutput> vous permet de définir une conversion entre deux types et de
convertir un tableau d'un type en un tableau de l'autre type, ou de convertir une liste d'un type en une liste de
l'autre type. Créez une méthode qui convertit les éléments de la liste existante en un nouveau type, créez une
instance de délégué pour représenter la méthode, et utilisez la méthode statique générique Array.ConvertAll pour
générer un tableau du nouveau type à partir du tableau d'origine ou bien la méthode d'instance générique
List<T>.ConvertAll<TOutput>(Converter<T,TOutput>) pour produire une liste du nouveau type à partir de la liste
d'origine.
Chaînage de délégués
La plupart des méthodes qui utilisent ces délégués retournent un tableau ou une liste, qui peut être passé à une
autre méthode. Par exemple, si vous voulez sélectionner certains éléments d'un tableau, convertir ces éléments en
un nouveau type, puis les enregistrer dans un nouveau tableau, vous pouvez passer le tableau retourné par la
méthode générique FindAll à la méthode générique ConvertAll. Si le nouveau type d'élément n'a pas un ordre de
tri naturel, vous pouvez passer le tableau retourné par la méthode générique ConvertAll à la méthode générique
Sort<T>(T[], Comparison<T>).

Voir aussi
System.Collections.Generic
System.Collections.ObjectModel
Génériques
Collections génériques dans le .NET Framework
Interfaces génériques
Covariance et contravariance
Interfaces génériques
18/07/2020 • 4 minutes to read • Edit Online

Cette rubrique donne une vue d'ensemble des interfaces génériques qui fournissent des fonctionnalités
communes à plusieurs familles de types génériques.

Interfaces génériques
Les interfaces génériques fournissent des contreparties de type sécurisé aux interfaces non génériques pour les
comparaisons de classement et d’égalité, et pour les fonctionnalités partagées par les types de collections
génériques.

NOTE
À compter de .NET Framework 4, les paramètres de type de plusieurs interfaces génériques sont marqués comme étant
covariants ou contravariants, fournissant ainsi une plus grande flexibilité pour l’affectation et l’utilisation des types qui
implémentent ces interfaces. Consultez Covariance et contravariance.

Comparaisons d'égalité et de classement


Dans l'espace de noms System, les interfaces génériques System.IComparable<T> et System.IEquatable<T>,
comme leurs contreparties non génériques, définissent respectivement des méthodes pour les comparaisons de
classement et les comparaisons d'égalité. Les types implémentent ces interfaces pour offrir la capacité à réaliser de
telles comparaisons.
Dans l'espace de noms System.Collections.Generic, les interfaces génériques IComparer<T> et
IEqualityComparer<T> offrent un moyen de définir une comparaison de classement ou d'égalité pour les types
qui n'implémentent pas les interfaces génériques System.IComparable<T> ou System.IEquatable<T>, et ils
permettent de redéfinir ces relations pour les types qui les implémentent. Ces interfaces sont utilisées par les
méthodes et les constructeurs de nombreuses classes de collection génériques. Par exemple, vous pouvez passer
un objet IComparer<T> générique au constructeur de la classe SortedDictionary<TKey,TValue> pour spécifier un
ordre de tri pour un type qui n'implémente pas l'interface générique System.IComparable<T>. Il existe des
surcharges de la méthode statique générique Array.Sort et de la méthode d'instance List<T>.Sort pour le tri des
tableaux et des listes à l'aide d'implémentations génériques de IComparer<T>.
Les classes génériques Comparer<T> et EqualityComparer<T> fournissent des classes de base pour les
implémentations des interfaces génériques IComparer<T> et IEqualityComparer<T>, et fournissent également
des comparaisons de classement et d'égalité par défaut via leurs propriétés respectives Comparer<T>.Default et
EqualityComparer<T>.Default.
Fonctionnalités des collections
L'interface générique ICollection<T> est l'interface de base pour les types de collections génériques. Elle offre des
fonctionnalités de base pour l'ajout, la suppression, la copie et l'énumération d'éléments. ICollection<T> hérite à la
fois de l'interface générique IEnumerable<T> et de l'interface non générique IEnumerable.
L'interface générique IList<T> étend l'interface générique ICollection<T> avec des méthodes pour la récupération
indexée.
L'interface générique IDictionary<TKey,TValue> étend l'interface générique ICollection<T> avec des méthodes
pour la récupération indexée. Les types de dictionnaires génériques de la bibliothèque de classes de base du .NET
Framework implémentent également l'interface IDictionary non générique.
L'interface générique IEnumerable<T> fournit une structure d'énumérateur générique. L'interface générique
IEnumerator<T> implémentée par les énumérateurs génériques hérite de l'interface non générique IEnumerator ;
les membres MoveNext et Reset, qui ne dépendent pas du paramètre de type T , apparaissent seulement sur
l'interface non générique. Cela signifie que tout consommateur de l'interface non générique peut également
consommer l'interface générique.

Voir aussi
System.Collections.Generic
System.Collections.ObjectModel
Génériques
Collections génériques dans le .NET Framework
Délégués génériques pour la manipulation de tableaux et de listes
Covariance et contravariance
Covariance et contravariance dans les génériques
18/07/2020 • 25 minutes to read • Edit Online

La covariance et la contravariance sont des termes qui font référence à la possibilité d’utiliser un type plus dérivé
(plus spécifique) ou moins dérivé (moins spécifique) que celui spécifié à l’origine. Les paramètres de type
générique prennent en charge la covariance et la contravariance afin de fournir une meilleure flexibilité dans
l'assignation et l'utilisation des types génériques. Lorsque vous faites référence à un système de type, la
covariance, la contravariance et l'invariance ont les définitions suivantes. Les exemples supposent qu'une classe
de base est nommée Base et qu'une classe dérivée est nommée Derived .
Covariance

Vous permet d'utiliser un type plus dérivé que celui spécifié à l'origine.
Vous pouvez assigner une instance de IEnumerable<Derived> ( IEnumerable(Of Derived) en Visual Basic) à
une variable de type IEnumerable<Base> .
Contravariance

Vous permet d'utiliser un type plus générique (moins dérivé) que celui spécifié à l'origine.
Vous pouvez assigner une instance de Action<Base> ( Action(Of Base) en Visual Basic) à une variable de
type Action<Derived> .
Invariance

Signifie que vous pouvez utiliser uniquement le type spécifié à l'origine ; pour un paramètre de type
générique indifférent, il n'est ni covariant ni contravariant.
Vous ne pouvez pas attribuer une instance de List<Base> ( List(Of Base) en Visual Basic) à une variable
de type List<Derived> et inversement.
Les paramètres de type covariant vous permettent d'effectuer des assignations très similaires au Polymorphisme
ordinaire, comme indiqué dans le code suivant.

IEnumerable<Derived> d = new List<Derived>();


IEnumerable<Base> b = d;

Dim d As IEnumerable(Of Derived) = New List(Of Derived)


Dim b As IEnumerable(Of Base) = d

La classe List<T> implémente l'interface générique IEnumerable<T> , donc List<Derived> ( List(Of Derived) en
Visual Basic) implémente IEnumerable<Derived> . Le paramètre de type covariant fait le reste.
La contravariance, en revanche, paraît peu intuitive. L'exemple suivant crée un délégué de type Action<Base> (
Action(Of Base) en Visual Basic), puis assigne ce délégué à une variable de type Action<Derived> .

Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };


Action<Derived> d = b;
d(new Derived());
Dim b As Action(Of Base) = Sub(target As Base)
Console.WriteLine(target.GetType().Name)
End Sub
Dim d As Action(Of Derived) = b
d(New Derived())

Cela peut paraître rétrograde, mais c'est le code de type sécurisé qui est compilé et exécuté. L'expression lambda
correspond au délégué auquel elle est assignée, elle définit donc une méthode qui prend un paramètre de type
Base et n'a aucune valeur de retour. Le délégué résultant peut être assigné à une variable de type
Action<Derived> parce que le paramètre de type T du délégué Action<T> est contravariant. Le code est de type
sécurisé parce que T spécifie un type de paramètre. Lorsque le délégué de type Action<Base> est appelé comme
s'il était de type Action<Derived> , son argument doit être de type Derived . Cet argument peut toujours être
passé sans risque à la méthode sous-jacente, parce que le paramètre de la méthode est de type Base .
En général, un paramètre de type covariant peut être utilisé comme type de retour d'un délégué et les paramètres
de type contravariant peuvent être utilisés comme types de paramètres. Pour une interface, les paramètres de
type covariant peuvent être utilisés comme types de retour des méthodes de l'interface et les paramètres de type
contravariant peuvent être utilisés comme types de paramètres des méthodes de l'interface.
La covariance et la contravariance sont désignées collectivement sous le nom de variation. Un paramètre de type
générique qui n'est marqué ni comme étant covariant, ni comme étant contravariant, est appelé indifférent.
Récapitulatif des informations relatives à la variance dans le common language runtime :
Dans .NET Framework 4, les paramètres de type variant sont limités aux types d’interfaces génériques et
aux types délégués génériques.
Un type d'interface générique ou un type délégué générique peut avoir des paramètres de type covariant
et contravariant.
La variance s'applique uniquement aux types référence ; si vous spécifiez un type valeur pour un
paramètre de type variant, ce paramètre de type est indifférent pour le type construit résultant.
La variance ne s'applique pas à la combinaison de délégués. Autrement dit, avec deux délégués de types
Action<Derived> et Action<Base> ( Action(Of Derived) et Action(Of Base) en Visual Basic), il n'est pas
possible de combiner le deuxième délégué avec le premier, même si le résultat sera de type sécurisé. La
variance permet au deuxième délégué d'être assigné à une variable de type Action<Derived> , mais les
délégués peuvent uniquement être combinés si leurs types correspondent exactement.

Interfaces génériques avec paramètres de type covariant


À compter de .NET Framework 4, plusieurs interfaces génériques ont des paramètres de type covariant, par
exemple IEnumerable<T>, IEnumerator<T>, IQueryable<T> et IGrouping<TKey,TElement>. Tous les paramètres
de type de ces interfaces sont covariants, les paramètres de type sont donc uniquement utilisés pour les types de
retour des membres.
L'exemple suivant illustre les paramètres de type covariant. L'exemple définit deux types : Base a une méthode
statique nommée PrintBases qui prend un IEnumerable<Base> ( IEnumerable(Of Base) en Visual Basic) et
imprime les éléments. Derived hérite de Base . L'exemple crée un List<Derived> vide ( List(Of Derived) en
Visual Basic) et montre que ce type peut être passé à PrintBases et assigné à une variable de type
IEnumerable<Base> sans cast. List<T> implémente IEnumerable<T>, qui a un paramètre de type covariant unique.
Le paramètre de type covariant est la raison pour laquelle une instance de IEnumerable<Derived> peut être utilisée
au lieu de IEnumerable<Base> .
using System;
using System.Collections.Generic;

class Base
{
public static void PrintBases(IEnumerable<Base> bases)
{
foreach(Base b in bases)
{
Console.WriteLine(b);
}
}
}

class Derived : Base


{
public static void Main()
{
List<Derived> dlist = new List<Derived>();

Derived.PrintBases(dlist);
IEnumerable<Base> bIEnum = dlist;
}
}

Imports System.Collections.Generic

Class Base
Public Shared Sub PrintBases(ByVal bases As IEnumerable(Of Base))
For Each b As Base In bases
Console.WriteLine(b)
Next
End Sub
End Class

Class Derived
Inherits Base

Shared Sub Main()


Dim dlist As New List(Of Derived)()

Derived.PrintBases(dlist)
Dim bIEnum As IEnumerable(Of Base) = dlist
End Sub
End Class

Interfaces génériques avec paramètres de type générique


contravariant
À compter de .NET Framework 4, plusieurs interfaces génériques ont des paramètres de type contravariant, par
exemple IComparer<T>, IComparable<T> et IEqualityComparer<T>. Ces interfaces ont des paramètres de type
contravariant uniquement, par conséquent, les paramètres de type sont utilisés uniquement comme types de
paramètre dans les membres des interfaces.
L'exemple suivant illustre les paramètres de type contravariant. L'exemple définit une classe abstraite (
MustInherit dans Visual Basic) Shape avec une propriété Area . L'exemple définit également une classe
ShapeAreaComparer qui implémente IComparer<Shape> ( IComparer(Of Shape) dans Visual Basic). L'implémentation
de la méthode IComparer<T>.Compare est basée sur la valeur de la propriété Area , de sorte que
ShapeAreaComparer peut être utilisé pour trier des objets Shape par zone.
La classe Circle hérite de Shape et remplace Area . L'exemple crée un SortedSet<T> d'objets Circle , à l'aide
d'un constructeur qui accepte un IComparer<Circle> ( IComparer(Of Circle) dans Visual Basic). Toutefois, au lieu
de passer un IComparer<Circle> , l'exemple passe un objet ShapeAreaComparer qui implémente IComparer<Shape> .
L'exemple peut passer un comparateur d'un type moins dérivé ( Shape ) lorsque le code appelle un comparateur
d'un type plus dérivé ( Circle ), parce que le paramètre de type de l'interface générique IComparer<T> est
contravariant.
Lorsqu'un nouvel objet Circle est ajouté au SortedSet<Circle> , la méthode IComparer<Shape>.Compare (méthode
IComparer(Of Shape).Compare dans Visual Basic) de l'objet ShapeAreaComparer est appelée chaque fois que le
nouvel élément est comparé à un élément existant. Le type de paramètre de la méthode ( Shape ) étant moins
dérivé que le type passé ( Circle ), l'appel garantit la cohérence des types. La contravariance permet à
ShapeAreaComparer de trier une collection d'un type unique, ainsi qu'une collection mixte de types, qui dérivent de
Shape .
using System;
using System.Collections.Generic;

abstract class Shape


{
public virtual double Area { get { return 0; }}
}

class Circle : Shape


{
private double r;
public Circle(double radius) { r = radius; }
public double Radius { get { return r; }}
public override double Area { get { return Math.PI * r * r; }}
}

class ShapeAreaComparer : System.Collections.Generic.IComparer<Shape>


{
int IComparer<Shape>.Compare(Shape a, Shape b)
{
if (a == null) return b == null ? 0 : -1;
return b == null ? 1 : a.Area.CompareTo(b.Area);
}
}

class Program
{
static void Main()
{
// You can pass ShapeAreaComparer, which implements IComparer<Shape>,
// even though the constructor for SortedSet<Circle> expects
// IComparer<Circle>, because type parameter T of IComparer<T> is
// contravariant.
SortedSet<Circle> circlesByArea =
new SortedSet<Circle>(new ShapeAreaComparer())
{ new Circle(7.2), new Circle(100), null, new Circle(.01) };

foreach (Circle c in circlesByArea)


{
Console.WriteLine(c == null ? "null" : "Circle with area " + c.Area);
}
}
}

/* This code example produces the following output:

null
Circle with area 0.000314159265358979
Circle with area 162.860163162095
Circle with area 31415.9265358979
*/
Imports System.Collections.Generic

MustInherit Class Shape


Public MustOverride ReadOnly Property Area As Double
End Class

Class Circle
Inherits Shape

Private r As Double
Public Sub New(ByVal radius As Double)
r = radius
End Sub
Public ReadOnly Property Radius As Double
Get
Return r
End Get
End Property
Public Overrides ReadOnly Property Area As Double
Get
Return Math.Pi * r * r
End Get
End Property
End Class

Class ShapeAreaComparer
Implements System.Collections.Generic.IComparer(Of Shape)

Private Function AreaComparer(ByVal a As Shape, ByVal b As Shape) As Integer _


Implements System.Collections.Generic.IComparer(Of Shape).Compare
If a Is Nothing Then Return If(b Is Nothing, 0, -1)
Return If(b Is Nothing, 1, a.Area.CompareTo(b.Area))
End Function
End Class

Class Program
Shared Sub Main()
' You can pass ShapeAreaComparer, which implements IComparer(Of Shape),
' even though the constructor for SortedSet(Of Circle) expects
' IComparer(Of Circle), because type parameter T of IComparer(Of T)
' is contravariant.
Dim circlesByArea As New SortedSet(Of Circle)(New ShapeAreaComparer()) _
From {New Circle(7.2), New Circle(100), Nothing, New Circle(.01)}

For Each c As Circle In circlesByArea


Console.WriteLine(If(c Is Nothing, "Nothing", "Circle with area " & c.Area))
Next
End Sub
End Class

' This code example produces the following output:


'
'Nothing
'Circle with area 0.000314159265358979
'Circle with area 162.860163162095
'Circle with area 31415.9265358979

Délégués génériques avec paramètres de type variant


Dans .NET Framework 4, les délégués génériques Func , tels que Func<T,TResult>, ont des types de retours
covariants et des types de paramètres contravariants. Les délégués génériques Action , tels que Action<T1,T2>,
ont des types de paramètres contravariants. Cela signifie que les délégués peuvent être assignés à des variables
avec des types de paramètres plus dérivés et (dans le cas des délégués génériques Func ) des types de retour
moins dérivés.
NOTE
Le dernier paramètre de type générique des délégués génériques Func spécifie le type de la valeur de retour dans la
signature du délégué. Il est covariant (mot clé out ), alors que les autres paramètres de type générique sont contravariants
(mot clé in ).

Le code suivant illustre ce comportement : La première partie du code définit une classe nommée Base , une
classe nommée Derived qui hérite de Base , et une autre classe avec une méthode static ( Shared en Visual
Basic) nommée MyMethod . La méthode prend une instance de Base et retourne une instance de Derived . (Si
l’argument est une instance de Derived , le MyMethod retourne ; si l’argument est une instance de Base ,
MyMethod retourne une nouvelle instance de Derived .) Dans Main() , l’exemple crée une instance de
Func<Base, Derived> ( Func(Of Base, Derived) dans Visual Basic) qui représente MyMethod et le stocke dans la
variable f1 .

public class Base {}


public class Derived : Base {}

public class Program


{
public static Derived MyMethod(Base b)
{
return b as Derived ?? new Derived();
}

static void Main()


{
Func<Base, Derived> f1 = MyMethod;

Public Class Base


End Class
Public Class Derived
Inherits Base
End Class

Public Class Program


Public Shared Function MyMethod(ByVal b As Base) As Derived
Return If(TypeOf b Is Derived, b, New Derived())
End Function

Shared Sub Main()


Dim f1 As Func(Of Base, Derived) = AddressOf MyMethod

La deuxième partie du code indique que le délégué peut être assigné à une variable de type Func<Base, Base> (
Func(Of Base, Base) en Visual Basic), car le type de retour est covariant.

// Covariant return type.


Func<Base, Base> f2 = f1;
Base b2 = f2(new Base());

' Covariant return type.


Dim f2 As Func(Of Base, Base) = f1
Dim b2 As Base = f2(New Base())

La troisième partie du code indique que le délégué peut être assigné à une variable de type
Func<Derived, Derived> ( Func(Of Derived, Derived) en Visual Basic), car le type de paramètre est contravariant.
// Contravariant parameter type.
Func<Derived, Derived> f3 = f1;
Derived d3 = f3(new Derived());

' Contravariant parameter type.


Dim f3 As Func(Of Derived, Derived) = f1
Dim d3 As Derived = f3(New Derived())

La dernière partie du code indique que le délégué peut être assigné à une variable de type Func<Derived, Base> (
Func(Of Derived, Base) en Visual Basic), ce qui combine les effets du type de paramètre contravariant et du type
de valeur de retour covariant.

// Covariant return type and contravariant parameter type.


Func<Derived, Base> f4 = f1;
Base b4 = f4(new Derived());

' Covariant return type and contravariant parameter type.


Dim f4 As Func(Of Derived, Base) = f1
Dim b4 As Base = f4(New Derived())

Variance dans les délégués génériques et non génériques


Dans le code précédent, la signature de MyMethod correspond exactement à la signature du délégué générique
construit : Func<Base, Derived> ( Func(Of Base, Derived) en Visual Basic). L'exemple montre que ce délégué
générique peut être stocké dans des variables ou des paramètres de méthode qui ont des types de paramètres
plus dérivés et des types de retour moins dérivés, tant que tous les types délégués sont construits à partir du type
délégué générique Func<T,TResult>.
Ceci est un point important. Les effets de la covariance et de la contravariance dans les paramètres de type des
délégués génériques sont semblables aux effets de la covariance et de la contravariance dans la liaison de
délégués ordinaire (consultez Variance dans les délégués (C#) et Variance dans les délégués (Visual Basic)).
Toutefois, la variance dans la liaison de délégués fonctionne avec tous les types délégués, et pas seulement les
types délégués génériques qui ont des paramètres de type variant. En outre, la variance dans la liaison de
délégués permet de lier une méthode à tout délégué disposant de types de paramètres plus restrictifs et d'un
type de retour moins restrictif, alors que l'assignation de délégués génériques fonctionne uniquement si les deux
types délégués sont construits à partir de la même définition de type générique.
L'exemple suivant indique les effets combinés de la variance dans la liaison de délégués et de la variance dans les
paramètres de type générique. L'exemple définit une hiérarchie de type qui inclut trois types, du moins dérivé (
Type1 ) au plus dérivé ( Type3 ). La variance dans la liaison de délégués ordinaire est utilisée pour lier une
méthode avec le type de paramètre Type1 et le type de retour Type3 à un délégué générique avec le type de
paramètre Type2 et le type de retour Type2 . Le délégué générique résultant est ensuite assigné à une autre
variable dont le type délégué générique a un paramètre de type Type3 et le type de retour Type1 , à l'aide de la
covariance et de la contravariance de paramètres de type générique. La deuxième assignation requiert que le type
de variable et le type délégué soient construits à partir de la même définition de type générique, dans ce cas,
Func<T,TResult>.
using System;

public class Type1 {}


public class Type2 : Type1 {}
public class Type3 : Type2 {}

public class Program


{
public static Type3 MyMethod(Type1 t)
{
return t as Type3 ?? new Type3();
}

static void Main()


{
Func<Type2, Type2> f1 = MyMethod;

// Covariant return type and contravariant parameter type.


Func<Type3, Type1> f2 = f1;
Type1 t1 = f2(new Type3());
}
}

Public Class Type1


End Class
Public Class Type2
Inherits Type1
End Class
Public Class Type3
Inherits Type2
End Class

Public Class Program


Public Shared Function MyMethod(ByVal t As Type1) As Type3
Return If(TypeOf t Is Type3, t, New Type3())
End Function

Shared Sub Main()


Dim f1 As Func(Of Type2, Type2) = AddressOf MyMethod

' Covariant return type and contravariant parameter type.


Dim f2 As Func(Of Type3, Type1) = f1
Dim t1 As Type1 = f2(New Type3())
End Sub
End Class

Définition d'interfaces et de délégués génériques variants


À compter de .NET Framework 4, Visual Basic et Visual C# ont des mots clés qui vous permettent de marquer les
paramètres de type générique des interfaces et des délégués comme covariants ou contravariants.

NOTE
Depuis le .NET Framework version 2.0, le Common Language Runtime prend en charge les annotations de variance sur les
paramètres de type générique. Avant .NET Framework 4, la seule méthode pour définir une classe générique avec ces
annotations consistait à utiliser le langage MSIL (Microsoft Intermediate Language), en compilant la classe avec Ilasm.exe
(Assembleur IL) ou en l’émettant dans un assembly dynamique.

Un paramètre de type covariant est marqué avec le mot clé out (mot clé Out en Visual Basic, + pour
l’assembleur MSIL). Vous pouvez utiliser un paramètre de type covariant comme valeur de retour d'une méthode
qui appartient à une interface ou comme type de retour d'un délégué. Vous ne pouvez pas utiliser un paramètre
de type covariant comme contrainte de type générique pour les méthodes d'interface.

NOTE
Si une méthode d'une interface a un paramètre qui est un type délégué générique, un paramètre de type covariant du type
d'interface peut être utilisé pour spécifier un paramètre de type contravariant du type délégué.

Un paramètre de type contravariant est marqué avec le mot clé in (mot clé In en Visual Basic, - pour l'
assembleur MSIL). Vous pouvez utiliser un paramètre de type contravariant comme type d'un paramètre d'une
méthode qui appartient à une interface ou comme type d'un paramètre d'un délégué. Vous pouvez utiliser un
paramètre de type contravariant comme contrainte de type générique pour une méthode d'interface.
Seuls les types d'interfaces et les types délégués peuvent avoir des paramètres de type variant. Un type
d'interface ou un type délégué peut avoir à la fois des paramètres de type covariant et contravariant.
Visual Basic et Visual C# ne vous permettent pas de violer les règles d'utilisation des paramètres de type
covariant et contravariant ou d'ajouter des annotations de covariance et de contravariance aux paramètres qui
diffèrent des types d'interfaces et des types délégués. L' assembleur MSIL n'exécute pas ce type de contrôle, mais
une exception TypeLoadException est levée si vous essayez de charger un type qui viole les règles.
Pour obtenir des informations et un exemple de code, consultez Variance dans les interfaces génériques (C#) et
Variance dans les interfaces génériques (Visual Basic).

Liste des types d'interfaces et des types délégués génériques variants


Dans .NET Framework 4, les types d’interfaces et les types délégués suivants ont des paramètres de type
covariant et/ou contravariant.

PA RA M ÈT RES DE T Y P E
TYPE PA RA M ÈT RES DE T Y P E C O VA RIA N T C O N T RAVA RIA N T

Il lance Action<T> sur Oui


Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,
T11,T12,T13,T14,T15,T16>.

Comparison<T> Oui

Converter<TInput,TOutput> Oui Oui

Func<TResult> Oui

Il lance Func<T,TResult> sur Oui Oui


Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T
11,T12,T13,T14,T15,T16,TResult>.

IComparable<T> Oui

Predicate<T> Oui

IComparer<T> Oui

IEnumerable<T> Oui

IEnumerator<T> Oui
PA RA M ÈT RES DE T Y P E
TYPE PA RA M ÈT RES DE T Y P E C O VA RIA N T C O N T RAVA RIA N T

IEqualityComparer<T> Oui

IGrouping<TKey,TElement> Oui

IOrderedEnumerable<TElement> Oui

IOrderedQueryable<T> Oui

IQueryable<T> Oui

Voir aussi
Covariance et contravariance (C#)
Covariance et contravariance (Visual Basic)
Variance dans les délégués (C#)
Variance dans les délégués (Visual Basic)
Délégués et expressions lambda
18/07/2020 • 7 minutes to read • Edit Online

Les délégués définissent un type qui spécifie une signature de méthode particulière. Une méthode (statique ou
d’instance) qui répond à cette signature peut être attribuée à une variable de ce type, puis appelée directement
(avec les arguments appropriés) ou passée elle-même comme argument à une autre méthode, puis appelée.
L’exemple suivant montre l’utilisation des délégués.

using System;
using System.Linq;

public class Program


{
public delegate string Reverse(string s);

static string ReverseString(string s)


{
return new string(s.Reverse().ToArray());
}

static void Main(string[] args)


{
Reverse rev = ReverseString;

Console.WriteLine(rev("a string"));
}
}

La ligne public delegate string Reverse(string s); crée un type délégué d’une certaine signature : dans notre
exemple, une méthode qui prend un paramètre de type chaîne, puis retourne un paramètre de type chaîne.
La méthode static string ReverseString(string s) , qui a exactement la même signature que le type délégué
défini, implémente le délégué.
La ligne Reverse rev = ReverseString; montre que vous pouvez affecter une méthode à une variable du type
délégué correspondant.
La ligne Console.WriteLine(rev("a string")); montre comment utiliser une variable d’un type délégué pour
appeler le délégué.
Pour simplifier le processus de développement, .NET inclut un ensemble de types délégués que les programmeurs
peuvent réutiliser sans avoir à en créer. Ces types sont Func<> , Action<> et Predicate<> , et ils peuvent être
utilisés sans avoir à définir de nouveaux types délégués. Il existe quelques différences entre les trois types qui sont
à faire avec la manière dont ils étaient destinés à être utilisés :
Action<> est utilisé quand une action doit être effectuée à l’aide des arguments du délégué. La méthode qu’elle
encapsule ne retourne pas de valeur.
Func<> est généralement utilisé dans le cas d’une transformation, autrement dit, quand vous devez
transformer les arguments du délégué en un résultat différent. Les projections sont un bon exemple. La
méthode qu’il encapsule retourne une valeur spécifiée.
Predicate<> est utilisé quand vous devez déterminer si l’argument répond à la condition du délégué. Elle peut
également être écrite sous la forme d’un Func<T, bool> , ce qui signifie que la méthode retourne une valeur
booléenne.
Nous pouvons maintenant reprendre notre exemple ci-dessus et le réécrire à l’aide du délégué Func<> au lieu
d’un type personnalisé. Le programme continue à s’exécuter exactement de la même façon.

using System;
using System.Linq;

public class Program


{
static string ReverseString(string s)
{
return new string(s.Reverse().ToArray());
}

static void Main(string[] args)


{
Func<string, string> rev = ReverseString;

Console.WriteLine(rev("a string"));
}
}

Dans cet exemple simple, il peut sembler un peu superflu de définir une méthode en dehors de la méthode Main .
.NET Framework 2,0 a introduit le concept de délégués anonymes, qui vous permettent de créer des délégués
« Inline » sans avoir à spécifier de type ou de méthode supplémentaire.
Dans l’exemple suivant, un délégué anonyme filtre une liste uniquement sur les nombres pairs, puis les imprime
sur la console.

using System;
using System.Collections.Generic;

public class Program


{
public static void Main(string[] args)
{
List<int> list = new List<int>();

for (int i = 1; i <= 100; i++)


{
list.Add(i);
}

List<int> result = list.FindAll(


delegate (int no)
{
return (no % 2 == 0);
}
);

foreach (var item in result)


{
Console.WriteLine(item);
}
}
}

Comme vous pouvez le voir, le corps du délégué n’est qu’un ensemble d’expressions, comme tout autre délégué.
Mais au lieu d’être une définition distincte, nous l’avons introduite ad hoc dans notre appel à la List<T>.FindAll
méthode.
Toutefois, même avec cette approche, nous avons encore beaucoup de code inutile. C’est là que les expressions
lambda interviennent. Les expressions lambda, ou simplement des « lambdas », ont été introduites en C# 3,0
comme un des blocs de construction de base de LINQ (Language Integrated Query). Il s’agit simplement d’une
syntaxe plus pratique pour l’utilisation des délégués. Elles déclarent une signature et un corps de méthode, mais
n’ont pas d’identité formelle propre, sauf si elles sont assignées à un délégué. Contrairement aux délégués, ils
peuvent être directement assignés comme partie droite de l’inscription des événements ou dans différentes
clauses et méthodes LINQ.
Dans la mesure où une expression lambda est simplement une autre façon de spécifier un délégué, nous pouvons
réécrire l’exemple ci-dessus pour utiliser une expression lambda au lieu d’un délégué anonyme.

using System;
using System.Collections.Generic;

public class Program


{
public static void Main(string[] args)
{
List<int> list = new List<int>();

for (int i = 1; i <= 100; i++)


{
list.Add(i);
}

List<int> result = list.FindAll(i => i % 2 == 0);

foreach (var item in result)


{
Console.WriteLine(item);
}
}
}

Dans l’exemple précédent, l’expression lambda utilisée est i => i % 2 == 0 . Là encore, il s’agit simplement d’une
syntaxe pratique pour l’utilisation de délégués. Ce qui se passe en coulisses est semblable à ce qui se passe avec le
délégué anonyme.
Comme les expressions lambda sont juste des délégués, elles peuvent servir de gestionnaire d’événements sans
aucun problème, comme l’illustre l’extrait de code suivant.

public MainWindow()
{
InitializeComponent();

Loaded += (o, e) =>


{
this.Title = "Loaded";
};
}

Dans ce contexte, l’opérateur += est utilisé pour s’abonner à un événement. Pour plus d’informations, consultez
Comment s’abonner et annuler l’abonnement à des événements.

Ressources et informations supplémentaires


Délégués
Fonctions anonymes
Expressions lambda
LINQ (Language Integrated Query)
18/07/2020 • 12 minutes to read • Edit Online

Qu’est-ce que c’est ?


LINQ fournit des fonctionnalités d’interrogation au niveau du langage et une API de fonction d’ordre supérieur
pour C# et Visual Basic pour écrire du code déclaratif expressif.
Syntaxe de requête au niveau du langage :

var linqExperts = from p in programmers


where p.IsNewToLINQ
select new LINQExpert(p);

Dim linqExperts = From p in programmers


Where p.IsNewToLINQ
Select New LINQExpert(p)

Même exemple en utilisant l’API IEnumerable<T> :

var linqExperts = programmers.Where(p => p.IsNewToLINQ)


.Select(p => new LINQExpert(p));

Dim linqExperts = programmers.Where(Function(p) p.IsNewToLINQ).


Select(Function(p) New LINQExpert(p))

LINQ est expressif


Imaginez que vous avez une liste d’animaux domestiques, mais que vous voulez la convertir en dictionnaire dans
lequel vous pouvez accéder à un animal directement par sa valeur RFID .
Code impératif traditionnel :

var petLookup = new Dictionary<int, Pet>();

foreach (var pet in pets)


{
petLookup.Add(pet.RFID, pet);
}

Dim petLookup = New Dictionary(Of Integer, Pet)()

For Each pet in pets


petLookup.Add(pet.RFID, pet)
Next

L’intention du code n’est pas de créer un Dictionary<int, Pet> et d’y ajouter des éléments par une boucle, c’est de
convertir une liste existante en dictionnaire ! LINQ conserve l’intention contrairement au code impératif.
Expression LINQ équivalente :

var petLookup = pets.ToDictionary(pet => pet.RFID);

Dim petLookup = pets.ToDictionary(Function(pet) pet.RFID)

Le code qui utilise LINQ est utile, car il égalise le terrain entre l’intention et le code dans un contexte de
programmation. Un autre bonus est la concision du code. Imaginez que vous pouvez réduire d’un tiers les grandes
parties d’un code Base comme illustré ci-dessus. Intéressant, n’est-ce pas ?

Les fournisseurs LINQ simplifient l’accès aux données


Pour la plupart des logiciels en général, tout tourne autour du traitement de données à partir d’une source (bases
de données, JSON, XML, etc.). Cela implique souvent d’apprendre une nouvelle API par source de données, ce qui
peut s’avérer fastidieux. LINQ simplifie le problème en faisant abstraction de l’accès aux éléments de donnée
communs dans une syntaxe de requête qui semble la même, quelle que soit la source de données choisie.
Imaginons que vous recherchez tous les éléments XML avec une valeur d’attribut spécifique.

public static IEnumerable<XElement> FindAllElementsWithAttribute(XElement documentRoot, string elementName,


string attributeName, string value)
{
return from el in documentRoot.Elements(elementName)
where (string)el.Element(attributeName) == value
select el;
}

Public Shared Function FindAllElementsWithAttribute(documentRoot As XElement, elementName As String,


attributeName As String, value As String) As IEnumerable(Of
XElement)
Return From el In documentRoot.Elements(elementName)
Where el.Element(attributeName).ToString() = value
Select el
End Function

Écrire du code pour parcourir manuellement le document XML afin de rechercher ces éléments est une tâche très
complexe.
Les fournisseurs LINQ ne vous permettent pas seulement d’interagir avec le XML. LINQ to SQL est un mappeur
ORM (Object-Relational Mapper) de base de données MSSQL relativement minimaliste. La bibliothèque JSON.NET
fournit un balayage efficace du document JSON via LINQ. Par ailleurs, si vous n’avez pas de bibliothèque pour faire
ce dont vous avez besoin, vous pouvez également écrire votre propre fournisseur LINQ !

Pourquoi utiliser la syntaxe de requête ?


C’est une question qui revient souvent. Après tout,

var filteredItems = myItems.Where(item => item.Foo);

Dim filteredItems = myItems.Where(Function(item) item.Foo)

est beaucoup plus concis que ce qui suit :


var filteredItems = from item in myItems
where item.Foo
select item;

Dim filteredItems = From item In myItems


Where item.Foo
Select item

La syntaxe d’API n’est-elle pas simplement un moyen plus concis d’effectuer la syntaxe de requête ?
Non. La syntaxe de requête permet d’utiliser la clause let , ce qui vous permet d’introduire et de lier une variable
dans la portée de l’expression, en l’utilisant dans les parties suivantes de l’expression. Vous pouvez reproduire le
même code avec la seule syntaxe d’API, mais vous obtiendrez très probablement du code difficile à lire.
Ce qui nous amène à la question suivante : devez-vous vous contenter d’utiliser la syntaxe de requête ?
La réponse à cette question est oui , si...
Votre code Base utilise déjà la syntaxe de requête
Vous devez définir l’étendue des variables dans vos requêtes en raison de la complexité
Vous préférez la syntaxe de requête et elle ne vous détournera pas de votre code Base
La réponse à cette question est non , si...
Votre code Base utilise déjà la syntaxe d’API
Vous n’avez pas besoin de définir l’étendue des variables dans vos requêtes
Vous préférez la syntaxe d’API et elle ne vous détournera pas de votre code Base

Exemples essentiels
Pour obtenir la liste complète des exemples LINQ, consultez 101 LINQ Samples (101 exemples LINQ).
Voici une démonstration rapide de quelques-uns des éléments essentiels de LINQ. Ces exemples ne sont pas
exhaustifs, car LINQ fournit beaucoup plus de fonctionnalités que ce qui est présenté ici.
Les indispensables : Where , Select et Aggregate :

// Filtering a list.
var germanShepards = dogs.Where(dog => dog.Breed == DogBreed.GermanShepard);

// Using the query syntax.


var queryGermanShepards = from dog in dogs
where dog.Breed == DogBreed.GermanShepard
select dog;

// Mapping a list from type A to type B.


var cats = dogs.Select(dog => dog.TurnIntoACat());

// Using the query syntax.


var queryCats = from dog in dogs
select dog.TurnIntoACat();

// Summing the lengths of a set of strings.


int seed = 0;
int sumOfStrings = strings.Aggregate(seed, (s1, s2) => s1.Length + s2.Length);
' Filtering a list.
Dim germanShepards = dogs.Where(Function(dog) dog.Breed = DogBreed.GermanShepard)

' Using the query syntax.


Dim queryGermanShepards = From dog In dogs
Where dog.Breed = DogBreed.GermanShepard
Select dog

' Mapping a list from type A to type B.


Dim cats = dogs.Select(Function(dog) dog.TurnIntoACat())

' Using the query syntax.


Dim queryCats = From dog In dogs
Select dog.TurnIntoACat()

' Summing the lengths of a set of strings.


Dim seed As Integer = 0
Dim sumOfStrings As Integer = strings.Aggregate(seed, Function(s1, s2) s1.Length + s2.Length)

Aplanissement d’une liste de listes :

// Transforms the list of kennels into a list of all their dogs.


var allDogsFromKennels = kennels.SelectMany(kennel => kennel.Dogs);

' Transforms the list of kennels into a list of all their dogs.
Dim allDogsFromKennels = kennels.SelectMany(Function(kennel) kennel.Dogs)

Union entre deux ensembles (avec un comparateur personnalisé) :

public class DogHairLengthComparer : IEqualityComparer<Dog>


{
public bool Equals(Dog a, Dog b)
{
if (a == null && b == null)
{
return true;
}
else if ((a == null && b != null) ||
(a != null && b == null))
{
return false;
}
else
{
return a.HairLengthType == b.HairLengthType;
}
}

public int GetHashCode(Dog d)


{
// Default hashcode is enough here, as these are simple objects.
return d.GetHashCode();
}
}

...

// Gets all the short-haired dogs between two different kennels.


var allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, new DogHairLengthComparer());
Public Class DogHairLengthComparer
Inherits IEqualityComparer(Of Dog)

Public Function Equals(a As Dog,b As Dog) As Boolean


If a Is Nothing AndAlso b Is Nothing Then
Return True
ElseIf (a Is Nothing AndAlso b IsNot Nothing) OrElse (a IsNot Nothing AndAlso b Is Nothing) Then
Return False
Else
Return a.HairLengthType = b.HairLengthType
End If
End Function

Public Function GetHashCode(d As Dog) As Integer


' Default hashcode is enough here, as these are simple objects.
Return d.GetHashCode()
End Function
End Class

...

' Gets all the short-haired dogs between two different kennels.
Dim allShortHairedDogs = kennel1.Dogs.Union(kennel2.Dogs, New DogHairLengthComparer())

Intersection entre deux ensembles :

// Gets the volunteers who spend share time with two humane societies.
var volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
new VolunteerTimeComparer());

' Gets the volunteers who spend share time with two humane societies.
Dim volunteers = humaneSociety1.Volunteers.Intersect(humaneSociety2.Volunteers,
New VolunteerTimeComparer())

Tri :

// Get driving directions, ordering by if it's toll-free before estimated driving time.
var results = DirectionsProcessor.GetDirections(start, end)
.OrderBy(direction => direction.HasNoTolls)
.ThenBy(direction => direction.EstimatedTime);

' Get driving directions, ordering by if it's toll-free before estimated driving time.
Dim results = DirectionsProcessor.GetDirections(start, end).
OrderBy(Function(direction) direction.HasNoTolls).
ThenBy(Function(direction) direction.EstimatedTime)

Enfin, un exemple plus avancé : déterminer si les valeurs des propriétés de deux instances du même type sont
égales (emprunté à ce billet StackOverflow et modifié) :
public static bool PublicInstancePropertiesEqual<T>(this T self, T to, params string[] ignore) where T : class
{
if (self == null || to == null)
{
return self == to;
}

// Selects the properties which have unequal values into a sequence of those properties.
var unequalProperties = from property in typeof(T).GetProperties(BindingFlags.Public |
BindingFlags.Instance)
where !ignore.Contains(property.Name)
let selfValue = property.GetValue(self, null)
let toValue = property.GetValue(to, null)
where !Equals(selfValue, toValue)
select property;
return !unequalProperties.Any();
}

<System.Runtime.CompilerServices.Extension()>
Public Function PublicInstancePropertiesEqual(Of T As Class)(self As T, [to] As T, ParamArray ignore As
String()) As Boolean
If self Is Nothing OrElse [to] Is Nothing Then
Return self Is [to]
End If

' Selects the properties which have unequal values into a sequence of those properties.
Dim unequalProperties = From [property] In GetType(T).GetProperties(BindingFlags.Public Or
BindingFlags.Instance)
Where Not ignore.Contains([property].Name)
Let selfValue = [property].GetValue(self, Nothing)
Let toValue = [property].GetValue([to], Nothing)
Where Not Equals(selfValue, toValue) Select [property]
Return Not unequalProperties.Any()
End Function

PLINQ
PLINQ, ou Parallel LINQ, est un moteur d’exécution parallèle pour les expressions LINQ. En d’autres termes, une
expression régulière LINQ peut être parallélisée de manière simple sur n’importe quel nombre de threads. Cela
s’effectue par un appel à AsParallel() avant l’expression.
Tenez compte des éléments suivants :

public static string GetAllFacebookUserLikesMessage(IEnumerable<FacebookUser> facebookUsers)


{
var seed = default(UInt64);

Func<UInt64, UInt64, UInt64> threadAccumulator = (t1, t2) => t1 + t2;


Func<UInt64, UInt64, UInt64> threadResultAccumulator = (t1, t2) => t1 + t2;
Func<Uint64, string> resultSelector = total => $"Facebook has {total} likes!";

return facebookUsers.AsParallel()
.Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector);
}
Public Shared GetAllFacebookUserLikesMessage(facebookUsers As IEnumerable(Of FacebookUser)) As String
{
Dim seed As UInt64 = 0

Dim threadAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2


Dim threadResultAccumulator As Func(Of UInt64, UInt64, UInt64) = Function(t1, t2) t1 + t2
Dim resultSelector As Func(Of Uint64, string) = Function(total) $"Facebook has {total} likes!"

Return facebookUsers.AsParallel().
Aggregate(seed, threadAccumulator, threadResultAccumulator, resultSelector)
}

Ce code partitionne facebookUsers entre les threads système si nécessaire, additionne le nombre total de
mentions J’aime sur chaque thread en parallèle, additionne les résultats calculés par chaque thread et projette ce
résultat dans une chaîne très pratique.
Sous forme de diagramme :

Les tâches parallèles utilisant le processeur qui peuvent être facilement exprimées par LINQ (en d’autres termes,
qui sont des fonctions pures et n’ont aucun effet secondaire) sont parfaites pour PLINQ. Pour les tâches qui ont un
effet secondaire, utilisez la bibliothèque parallèle de tâches.

Ressources supplémentaires :
101 exemples LINQ
LINQPad, environnement de laboratoire et moteur d’interrogation de base de données pour C#/F #/Visual Basic
EduLinq, livre électronique pour apprendre comment LINQ-to-objects est implémenté
Système de type commun (CTS, Common Type
System) et spécification CLS (Common Language
Specification)
18/03/2020 • 6 minutes to read • Edit Online

Là encore, ce sont deux termes largement utilisés dans l’environnement .NET ; ils sont véritablement essentiels
pour comprendre comment une implémentation de .NET permet le développement dans plusieurs langages et
comment elle fonctionne.

Système de type commun


Pour commencer, n’oubliez pas qu’une implémentation de .NET est indépendante du langage. Cela ne signifie pas
seulement qu’un programmeur peut écrire son code dans n’importe quelle langue qui peut être compilée à l’IL.
Cela signifie également qu’ils doivent être en mesure d’interagir avec le code écrit dans d’autres langues qui sont
en mesure d’être utilisés sur une implémentation .NET.
Pour que cela se fasse de manière transparente, il doit exister un moyen commun de décrire tous les types pris en
charge. C’est ce que fait le système de type commun (CTS). Il a été créé pour effectuer plusieurs opérations :
Établir un framework pour l’exécution multilingue.
Fournir un modèle orienté objet pour prendre en charge l’implémentation de différents langages sur une
implémentation de .NET.
Définir un ensemble de règles que tous les langages doivent respecter pour utiliser les types.
Fournir une bibliothèque contenant les types primitifs de base utilisés dans le développement d’applications
(par ex., Boolean , Byte , Char , etc.)
CTS définit deux grandes sortes de types qui doivent être pris en charge : les types valeur et référence. Leur nom
pointe sur leur définition.
Les objets des types de référence sont représentés par une référence à la valeur réelle de l’objet; une référence ici
est similaire à un pointeur dans C / C. Il se réfère simplement à un endroit de mémoire où les valeurs des objets
sont. Cela a un profond impact sur l’utilisation de ces types. Si vous attribuez un type référence à une variable et
que vous passez ensuite cette variable dans une méthode, par exemple, les modifications de l’objet sont reflétées
sur l’objet principal, il n’y a pas de copie.
Les types valeur sont l’opposé, c’est-à-dire que les objets sont représentés par leurs valeurs. Si vous attribuez un
type valeur à une variable, vous copiez en fait une valeur de l’objet.
CTS définit plusieurs catégories de types, chacun avec une sémantique et une utilisation propres :
Classes
Structures
Enums
Interfaces
Délégués
CTS définit également toutes les autres propriétés des types, comme les modificateurs d’accès, les membres de
type valides, le fonctionnement de l’héritage et de la surcharge, et ainsi de suite. Malheureusement, cet article de
présentation n’a pas pour but d’approfondir davantage, mais vous pouvez consulter la section Autres ressources à
la fin pour obtenir des liens vers un contenu plus détaillé qui couvre ces rubriques.

CLS (Common Language Specification)


Pour permettre des scénarios d’interopérabilité complète, tous les objets qui sont créés dans le code doivent
s’appuyer sur certains points communs entre les langages qui les consomment (leurs appelants). Comme il existe
de nombreux langages différents, .NET a défini ces points communs dans ce que l’on appelle la spécification CLS
(Common Language Specification) . CLS définit un ensemble de fonctionnalités nécessaires pour de
nombreuses applications courantes. Il fournit également une sorte de recette pour n’importe quel langage
implémenté sur .NET qui explique ce qu’il doit prendre en charge.
CLS est un sous-ensemble du système CTS. Cela signifie que toutes les règles du CTS s’appliquent également à la
spécification CLS, sauf si les règles CLS sont plus strictes. Si un composant est créé uniquement à l’aide des règles
CLS, autrement dit, s’il expose uniquement les fonctionnalités CLS dans son API, il est considéré comme étant
conforme CLS . Par exemple, les <framework-librares> sont conformes CLS justement parce qu’ils ont besoin
d’utiliser tous les langages pris en charge sur .NET.
Vous pouvez consulter les documents de la section Autres ressources ci-dessous pour obtenir une vue d’ensemble
de toutes les fonctionnalités de la spécification CLS.

Plus de ressources
Système de type commun
CLS (Common Language Specification)
Traitement parallèle, accès concurrentiel et
programmation asynchrone dans .NET
18/07/2020 • 2 minutes to read • Edit Online

.NET offre plusieurs façons d’écrire du code asynchrone permettant de rendre votre application plus réactive pour
l’utilisateur, et d’écrire du code parallèle qui utilise plusieurs threads d’exécution afin d’optimiser les performances
de l’ordinateur de votre utilisateur.

Dans cette section


Programmation asynchrone
Décrit les mécanismes de la programmation asynchrone fournis par .NET.
Programmation parallèle
Décrit un modèle de programmation basée sur les tâches qui simplifie le développement parallèle, vous permettant
d’écrire du code parallèle efficace, précis et scalable dans un idiome naturel sans devoir utiliser directement des
threads ou le pool de threads.
Thread
Décrit les mécanismes de base de l’accès concurrentiel et de la synchronisation fournis par .NET.
Vue d’ensemble d’async
18/03/2020 • 4 minutes to read • Edit Online

Il n’y a pas si longtemps, il suffisait d’acheter un PC ou un serveur plus récent pour que les applications soient plus
rapides. Mais ce n’est plus le cas maintenant. En fait, c’est même tout le contraire. Les téléphones mobiles utilisent
des processeurs ARM 1 GHz à cœur unique et les charges de travail serveur sont passées aux machines virtuelles.
Les utilisateurs veulent toujours une interface utilisateur réactive et les chefs d’entreprise veulent des serveurs qui
s’adaptent à leur activité. La transition vers le mobile et le cloud ainsi qu’une population de plus de 3 milliards
d’utilisateurs connectés à Internet ont donné naissance à un nouvel ensemble de modèles logiciels.
Les applications clientes doivent être toujours actives, toujours connectées, constamment réactives à
l’interaction de l’utilisateur (interface tactile, par exemple) et en haut du classement des magasins
d’applications !
Les services doivent gérer les pics de trafic en ayant la possibilité de monter et descendre en puissance
facilement.
La programmation asynchrone est une technique clé qui facilite le blocage des E/S et des opérations simultanées
sur plusieurs cœurs. .NET permet aux applications et aux services d’être réactifs et élastiques avec des modèles de
programmation asynchrones faciles à utiliser et au niveau du langage dans les modèles de programmation C,
Visual Basic et F.

Pourquoi écrire du code asynchrone ?


Les applications modernes utilisent beaucoup d’E/S de fichier et de réseau. Les API d’E/S se bloquent par défaut, ce
qui se traduit par une expérience utilisateur et une utilisation du matériel médiocres, sauf si vous avez l’intention
d’apprendre et d’utiliser des modèles complexes. Les API asynchrones basées sur des tâches et le modèle de
programmation asynchrone au niveau du langage inversent ce modèle en faisant de l’exécution asynchrone la
valeur par défaut avec quelques nouveaux concepts à découvrir.
Le code asynchrone présente les caractéristiques suivantes :
Il gère davantage de demandes de serveur en cédant des threads pour traiter plus de demandes quand il
attend le retour des demande d’E/S.
Il permet aux interfaces utilisateur d’être plus réactives en cédant des threads à l’interaction de l’interface
utilisateur quand il attend les demandes d’E/S et en transmettant les tâches d’exécution longue aux autres
cœurs de processeur.
De nombreuses API .NET plus récentes sont asynchrones.
Il est facile d’écrire du code asynchrone dans .NET !

Quelle est l’étape suivante ?


Pour plus d’informations, consultez la rubrique Présentation détaillée des opérations asynchrones.
La rubrique Modèles de programmation asynchrone fournit une vue d’ensemble des trois modèles de
programmation asynchrone pris en charge dans .NET :
Modèle de programmation asynchrone (hérité)
Modèle asynchrone basé sur les événements (EAP) (hérité)
Modèle asynchrone basé sur les tâches (TAP, Task-based Asynchronous Pattern) (recommandé pour un
nouveau développement)
Pour plus d’informations sur le modèle de programmation basée sur des tâches recommandé, consultez la
rubrique Programmation asynchrone basée sur les tâches.
Async en détail
18/03/2020 • 19 minutes to read • Edit Online

L’écriture de code asynchrone utilisant des E/S et le processeur est simple avec le modèle asynchrone .NET basé
sur des tâches. Le modèle est exposé par les types Task et Task<T> et les mots clés async et await en C# et
Visual Basic. (Des ressources spécifiques à la langue se trouvent également dans la section Voir.) Cet article
explique comment utiliser .NET async et fournit un aperçu du cadre async utilisé sous les couvertures.

Task et Task<T>
Les tâches sont des constructions utilisées pour implémenter ce que l’on appelle le modèle de promesses de
concurrence. En bref, elles vous offrent la « promesse » que le travail sera terminé à un moment ultérieur, ce qui
vous permet de coordonner la promesse et une nouvelle API.
Task représente une opération unique qui ne retourne pas de valeur.
Task<T> représente une opération unique qui retourne une valeur de type T .
Il est important de considérer les tâches comme des abstractions de travail effectuées de manière asynchrone et
pas comme une abstraction sur le modèle de thread. Par défaut, les tâches s’exécutent sur le thread actuel et
délèguent le travail au système d’exploitation, comme il convient. Éventuellement, l’API Task.Run peut servir à
demander explicitement aux tâches de s’exécuter sur un thread distinct.
Les tâches exposent un protocole d’API pour surveiller et attendre la valeur de résultat d’une tâche et y accéder
(dans le cas de Task<T> ). L’intégration au langage, avec le mot clé await , fournit une abstraction de niveau
supérieur pour l’utilisation des tâches.
L’utilisation de await permet à votre application ou service d’effectuer un travail utile pendant l’exécution d’une
tâche en cédant le contrôle à son appelant jusqu’à ce que la tâche soit terminée. Votre code n’a pas besoin de
s’appuyer sur des rappels ou des événements pour continuer l’exécution une fois la tâche terminée. L’intégration
des API de langage et de tâche s’en charge pour vous. Si vous utilisez Task<T> , le mot clé await « désencapsule »
également la valeur retournée quand la tâche est terminée. Les détails de ce fonctionnement sont expliqués plus
bas.
Pour en savoir plus sur les tâches et les différentes façons d’interagir avec elles, lisez la rubrique Modèle
asynchrone basé sur les tâches (TAP, Task-based Asynchronous Pattern).

Approfondissement : Tâches pour une opération utilisant des E/S


La section suivante décrit en détail toutes les étapes d’un appel d’E/S asynchrone standard. Commençons par deux
exemples.
Le premier exemple appelle une méthode async et retourne une tâche active qui n’est pas encore terminée.

public Task<string> GetHtmlAsync()


{
// Execution is synchronous here
var client = new HttpClient();

return client.GetStringAsync("https://www.dotnetfoundation.org");
}

Le deuxième exemple ajoute l’utilisation des mots clés async et await pour agir sur la tâche.
public async Task<string> GetFirstCharactersCountAsync(string url, int count)
{
// Execution is synchronous here
var client = new HttpClient();

// Execution of GetFirstCharactersCountAsync() is yielded to the caller here


// GetStringAsync returns a Task<string>, which is *awaited*
var page = await client.GetStringAsync("https://www.dotnetfoundation.org");

// Execution resumes when the client.GetStringAsync task completes,


// becoming synchronous again.

if (count > page.Length)


{
return page;
}
else
{
return page.Substring(0, count);
}
}

L’appel de GetStringAsync() s’effectue par le biais de bibliothèques .NET de niveau inférieur (peut-être en appelant
d’autres méthodes async) jusqu’à ce qu’il atteigne un appel interop P/Invoke dans une bibliothèque de réseau
native. La bibliothèque native peut ensuite effectuer un appel de l’API système (tel que write() pour un socket sur
Linux). Un objet de tâche est créé dans la limite native/managée, éventuellement à l’aide de TaskCompletionSource.
L’objet de tâche est transmis à travers les couches, éventuellement traité ou directement retourné, ou retourné à
l’appelant initial.
Dans le deuxième exemple ci-dessus, un objet Task<T> est retourné par GetStringAsync . L’utilisation du mot clé
await indique à la méthode de retourner un objet de tâche nouvellement créé. Le contrôle retourne à l’appelant à
partir de cet emplacement dans la méthode GetFirstCharactersCountAsync . Les méthodes et propriétés de l’objet
Task<T> permettent aux appelants de surveiller la progression de la tâche, qui se termine quand le code restant
dans GetFirstCharactersCountAsync a été exécuté.
Après l’appel de l’API système, la demande se trouve dans l’espace du noyau et transite vers le sous-système de
réseau du système d’exploitation (comme /net dans le noyau Linux). Ici, le système d’exploitation gère la demande
de mise en réseau de manière asynchrone. Les détails peuvent être différents selon le système d’exploitation utilisé
(l’appel du pilote de périphérique peut être planifié comme un signal envoyé au runtime, ou il peut être effectué et
ensuite un signal est renvoyé), mais finalement le runtime est informé que la demande de mise en réseau est en
cours. À ce stade, le travail du pilote de périphérique est planifié, en cours ou déjà terminé (la demande est déjà
« sur le réseau »), mais parce que tout se passe de manière asynchrone, le pilote de périphérique est en mesure de
gérer immédiatement autre chose !
Par exemple, dans Windows, un thread de système d’exploitation effectue un appel au pilote de périphérique
réseau et lui demande d’effectuer l’opération de mise en réseau via un paquet de requêtes d’interruption qui
représente l’opération. Le pilote de périphérique reçoit le paquet de requêtes d’interruption, effectue l’appel au
réseau, marque le paquet comme étant « en attente » et le renvoie au système d’exploitation. Le thread du système
d’exploitation sait maintenant que le paquet de requêtes d’interruption est « en attente », il n’a donc rien d’autre à
faire pour ce travail et « revient » pour pouvoir être utilisé pour une autre opération.
Quand la demande est satisfaite et que les données reviennent à travers le pilote de périphérique, il avertit le
processeur que de nouvelles données sont reçues via une interruption. La façon dont cette interruption est gérée
varie selon le système d’exploitation, mais les données sont ensuite transmises au système d’exploitation jusqu’à ce
que se produise un appel d’interopérabilité système (par exemple, dans Linux, un gestionnaire d’interruptions
planifie la moitié inférieure de l’IRQ pour qu’elle transmette les données via le système d’exploitation de façon
asynchrone). Notez que cela se produit également de façon asynchrone ! Le résultat est placé en file d’attente
jusqu’à ce que le prochain thread disponible soit en mesure d’exécuter la méthode asynchrone et de
« désencapsuler » le résultat de la tâche effectuée.
Tout au long de ce processus, un élément clé à retenir est qu’aucun thread n’est dédié à l’exécution de la
tâche . Bien que le travail soit exécuté dans un contexte (c’est-à-dire que le système d’exploitation doit passer des
données à un pilote de périphérique et répondre à une interruption), aucun thread n’est destiné à attendre le
retour des données de la demande. Cela permet au système de gérer une plus grande quantité de travail au lieu
d’attendre la fin des appels d’E/S.
Bien que les étapes ci-dessus puissent donner l’impression d’un grand nombre d’opérations à effectuer, en termes
de durée totale d’exécution, ce n’est rien comparé au temps nécessaire pour effectuer le travail d’E/S réel. Voici une
vague idée de ce que pourrait être la chronologie de ces étapes :
0-1
————————————————————————————————————————————————–2-
3
La durée entre les points 0 et 1 représente tout ce qui se passe avant qu’une méthode async cède le contrôle
à son appelant.
La durée entre les points 1 et 2 représente le temps consacré aux E/S, sans coût de processeur.
Enfin, la durée entre les points 2 et 3 représente le temps consacré à rendre le contrôle (et éventuellement
une valeur) à la méthode async, moment à partir duquel elle s’exécute à nouveau.
Qu’est-ce que cela signifie dans un scénario de serveur ?
Ce modèle fonctionne correctement avec la charge de travail d’un scénario de serveur classique. Comme aucun
thread n’est destiné à s’interrompre sur les tâches non terminées, le pool de threads serveur peut traiter un plus
grand nombre de demandes web.
Prenons deux serveurs : l’un d’eux exécute du code asynchrone et l’autre pas. Pour les besoins de cet exemple,
chaque serveur n’a que 5 threads disponibles pour traiter les demandes. Notez que ces chiffres sont
intentionnellement petits et servent uniquement dans un contexte de démonstration.
Supposons que les deux serveurs reçoivent 6 demandes simultanées. Chaque demande effectue une opération
d’E/S. Le serveur sans code asynchrone doit placer en file d’attente la 6ème demande jusqu’à ce que l’un des
5 threads ait terminé le travail utilisant des E/S et écrit une réponse. Quand la 20ème demande arrive, le serveur
commence peut-être à ralentir, car la file d’attente devient trop longue.
Le serveur avec code asynchrone peut placer en file d’attente la 6ème demande, mais parce qu’il utilise async et
await , chacun de ses threads est libéré quand le travail utilisant des E/S démarre, et non quand il se termine.
Quand la 20ème demande arrive, la file d’attente des demandes entrantes est bien plus petite (ou est totalement
vide) et le serveur ne ralentit pas.
Bien qu’il s’agisse d’un exemple fictif, il fonctionne de manière très similaire dans le monde réel. En réalité, un
serveur est capable de gérer un nombre bien plus important de demandes à l’aide de async et await que s’il
dédiait un thread à chaque demande qu’il reçoit.
Qu’est-ce que cela signifie dans un scénario de client ?
Le plus gros avantage de l’utilisation de async et await pour une application cliente est l’augmentation de la
réactivité. Même si vous pouvez améliorer la réactivité d’une application en gérant manuellement des threads de
manière dynamique, c’est une opération coûteuse par rapport à la simple utilisation de async et await . Dans le
cas particulier d’un jeu mobile, il est essentiel d’affecter aussi peu que possible le thread d’interface utilisateur en
ce qui concerne les E/S.
Plus important encore, parce que le travail utilisant des E/S ne se sert pratiquement pas du processeur, en dédiant
un thread de processeur entier pour effectuer vaguement des tâches utiles, vous utilisez mal vos ressources.
Par ailleurs, la répartition du travail sur le thread d’interface utilisateur (par exemple, la mise à jour d’une interface
utilisateur) est très simple avec des méthodes async et n’engendre pas de travail supplémentaire (par exemple,
l’appel d’un délégué thread-safe).

Approfondissement : Task et Task<T> pour une opération utilisant le


processeur
Le code async utilisant le processeur est un peu différent du code async utilisant des E/S. Comme le travail est
effectué sur le processeur, il n’est pas possible de dédier un thread au calcul. L’utilisation de async et await est un
moyen d’interagir avec un thread en arrière-plan et de faire en sorte que l’appelant de la méthode async reste
réactif. Notez que cela ne protège en rien les données partagées. Si vous utilisez des données partagées, vous devez
quand même appliquer une stratégie de synchronisation appropriée.
Voici une vue générale d’un appel asynchrone utilisant le processeur :

public async Task<int> CalculateResult(InputData data)


{
// This queues up the work on the threadpool.
var expensiveResultTask = Task.Run(() => DoExpensiveCalculation(data));

// Note that at this point, you can do some other work concurrently,
// as CalculateResult() is still executing!

// Execution of CalculateResult is yielded here!


var result = await expensiveResultTask;

return result;
}

CalculateResult() s’exécute sur le thread sur lequel il a été appelé. Quand il appelle Task.Run , il place en file
d’attente l’opération coûteuse qui utilise le processeur, DoExpensiveCalculation() , sur le pool de threads et reçoit
un handle Task<int> . DoExpensiveCalculation() est finalement exécuté simultanément sur le prochain thread
disponible, probablement sur un autre cœur d’UC. Il est possible d’effectuer des tâches simultanées quand
DoExpensiveCalculation() est occupé sur un autre thread, car le thread qui a appelé CalculateResult() est encore
en cours d’exécution.
Une fois que await a été trouvé, l’exécution de CalculateResult() est cédée à son appelant, ce qui permet
d’effectuer d’autres tâches avec le thread actuel pendant que DoExpensiveCalculation() produit un résultat. Une fois
cette opération terminée, le résultat est placé en file d’attente pour s’exécuter sur le thread principal. Finalement, le
thread principal retourne à l’exécution de CalculateResult() , à partir duquel il obtient le résultat de
DoExpensiveCalculation() .

Pourquoi async est-il utile ici ?


async et await représentent la meilleure pratique de gestion des travaux utilisant le processeur de manière
intensive en cas d’impératifs de réactivité. Il existe plusieurs modèles d’utilisation d’async avec des tâches utilisant
le processeur. Notez que l’utilisation d’async représente un coût, même s’il est faible, et qu’elle n’est donc pas
recommandée pour les boucles serrées. C’est à vous de déterminer la façon dont vous écrivez votre code autour de
cette nouvelle fonctionnalité.

Voir aussi
Programmation asynchrone en C#
Programmation asynchrone avec async et attente (C)
Programmation asynchrone en F#
Programmation asynchrone avec Async et Await (Visual Basic)
Modèles de programmation asynchrone
18/07/2020 • 3 minutes to read • Edit Online

.NET propose trois modèles d’exécution d’opérations asynchrones :


Modèle asynchrone basé sur des tâches (TAP) , qui utilise une méthode unique pour représenter
l’initiation et l’achèvement d’une opération asynchrone. TAP a été introduit avec le .NET Framework 4. Il est
recommandé pour la programmation asynchrone dans .NET. Les mots clés async et await en C#,
ainsi que les opérateurs Async et Await en Visual Basic, ajoutent au modèle TAP la prise en charge des
langages. Pour plus d’informations, consultez Modèle asynchrone basé sur des tâches (TAP).
Le modèle asynchrone basé sur les événements (EAP) , qui est le modèle hérité basé sur les
événements pour fournir un comportement asynchrone. Il nécessite une méthode avec le suffixe Async ,
ainsi qu’un ou plusieurs événements, des types de délégués de gestionnaire d’événements et des types
dérivés de EventArg . Ce modèle a été introduit avec le .NET Framework 2.0. Il n’est plus recommandé pour
les nouveaux développements. Pour plus d'informations, consultez Modèle asynchrone basé sur des
événements (EAP).
Le modèle de programmation asynchrone (APM) , également appelé modèle IAsyncResult, qui est le
modèle hérité qui utilise l’interface IAsyncResult pour fournir un comportement asynchrone. Dans ce
modèle, les opérations synchrones nécessitent les méthodes Begin et End (par exemple, BeginWrite et
EndWrite pour implémenter une opération d’écriture asynchrone). Ce modèle n’est plus recommandé pour
un futur développement. Pour plus d’informations sur la programmation asynchrone, consultez Modèle de
programmation asynchrone (APM).

Comparaison des modèles


Pour comprendre rapidement la façon dont chacun des trois modèles modélise les opérations asynchrones,
prenons une méthode Read qui lit une quantité de données spécifiée dans une mémoire tampon fournie, en
commençant à l'offset spécifié :

public class MyClass


{
public int Read(byte [] buffer, int offset, int count);
}

Le modèle TAP de cette méthode exposerait l’unique méthode ReadAsync suivante :

public class MyClass


{
public Task<int> ReadAsync(byte [] buffer, int offset, int count);
}

Le modèle EAP exposerait l'ensemble de types et de membres suivant :

public class MyClass


{
public void ReadAsync(byte [] buffer, int offset, int count);
public event ReadCompletedEventHandler ReadCompleted;
}
Le modèle APM exposerait les méthodes BeginRead et EndRead :

public class MyClass


{
public IAsyncResult BeginRead(
byte [] buffer, int offset, int count,
AsyncCallback callback, object state);
public int EndRead(IAsyncResult asyncResult);
}

Voir aussi
Async en détail
Programmation asynchrone en C#
Programmation asynchrone en F#
Programmation asynchrone avec Async et Await (Visual Basic)
Programmation parallèle en .NET
18/07/2020 • 3 minutes to read • Edit Online

De nombreux ordinateurs personnels et stations de travail ont plusieurs cœurs de processeur qui permettent à
plusieurs threads de s’exécuter simultanément. Pour tirer parti du matériel, vous pouvez paralléliser votre code
pour distribuer le travail sur plusieurs processeurs.
Dans le passé, la parallélisation nécessitait un niveau peu élevé de manipulation de threads et de verrous. Visual
Studio et le .NET Framework améliorent la prise en charge de la programmation parallèle en fournissant une
exécution, des types de bibliothèques de classes et des outils de diagnostics. Ces fonctionnalités, qui ont été
introduites avec le .NET Framework 4, simplifient le développement parallèle. Vous pouvez écrire du code
parallèle efficace, à grains fins et évolutif dans un idiome naturel sans devoir utiliser directement des threads ou
le pool de threads.
L’illustration suivante fournit une vue d’ensemble générale de l’architecture de programmation parallèle dans le
.NET Framework :

Rubriques connexes
T EC H N O LO GY DESC RIP T IO N

Bibliothèque parallèle de tâches Fournit la documentation pour la classe


System.Threading.Tasks.Parallel, qui inclut des versions
parallèles de For et des boucles ForEach , et également
pour la classe System.Threading.Tasks.Task, qui représente la
meilleure façon d'exprimer des opérations asynchrones.

Parallel LINQ (PLINQ) Implémentation parallèle de LINQ to Objects qui améliore de


manière significative la performance dans de nombreux
scénarios.

Structures de données pour la programmation parallèle Fournit des liens vers la documentation pour les classes de
collection thread-safe, les types de synchronisation légers et
les types pour l’initialisation tardive.
T EC H N O LO GY DESC RIP T IO N

Outils de diagnostic parallèle Fournit des liens vers la documentation relative aux fenêtres
du débogueur Visual Studio pour les tâches et les piles
parallèles, et au Visualiseur concurrentiel.

Partitionneurs personnalisés pour PLINQ et TPL Décrit le fonctionnement des partitionneurs et comment
configurer les partitionneurs par défaut ou en créer.

Planificateurs de tâches Décrit le fonctionnement des planificateurs et comment les


planificateurs par défaut peuvent être configurés.

Expressions lambda dans PLINQ et TPL Fournit une vue d’ensemble d’expressions lambda en C# et
Visual Basic, et affiche comment elles sont utilisées dans
PLINQ et la bibliothèque parallèle de tâches.

Pour plus d’informations Fournit des liens vers des informations supplémentaires et
des exemples de ressources pour la programmation parallèle
dans .NET.

Voir aussi
Vue d’ensemble asynchrone
Threading managé
Bibliothèque parallèle de tâches
18/07/2020 • 3 minutes to read • Edit Online

La bibliothèque parallèle de tâches est un ensemble de types publics et d’API dans les espaces de noms
System.Threading et System.Threading.Tasks. L'objectif de la bibliothèque parallèle de tâches est d'accroître la
productivité des développeurs en simplifiant le processus d'ajout du parallélisme et de l'accès concurrentiel aux
applications. La bibliothèque parallèle de tâches met à l'échelle dynamiquement le degré d'accès concurrentiel
pour utiliser plus efficacement tous les processeurs disponibles. De plus, la bibliothèque parallèle de tâches gère
le partitionnement du travail, la planification de threads sur le ThreadPool, la prise en charge de l'annulation, la
gestion d'état et d'autres détails de bas niveau. L'utilisation de la bibliothèque parallèle de tâches vous permet de
maximiser les performances de votre code tout en vous concentrant sur le travail que votre programme doit
accomplir.
À compter de .NET Framework 4, la bibliothèque parallèle de tâches est la meilleure méthode pour écrire le code
multithread et parallèle. Toutefois, tout le code est pas approprié pour la parallélisation ; par exemple, si une
boucle exécute uniquement une petite quantité de travail sur chaque itération ou ne s'exécute que pour un
nombre limité d'itérations, la charge mémoire de la parallélisation peut ralentir l'exécution du code. En outre,
comme tout code multithread, la parallélisation rend l'exécution du programme plus complexe. Même si la
bibliothèque parallèle de tâches simplifie les scénarios multithread, il est recommandé de connaître les notions
fondamentales des concepts de threading, tels que les verrous, les interblocages et les conditions de concurrence
critique, afin de pouvoir utiliser efficacement la bibliothèque parallèle de tâches.

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Parallélisme des données Décrit comment créer des boucles parallèles for et
foreach ( For et For Each en Visual Basic).

Programmation asynchrone basée sur les tâches Décrit comment créer et exécuter implicitement des tâches à
l’aide de Parallel.Invoke ou explicitement en utilisant des
objets Task directement.

Dataflow Explique comment utiliser les composants de flux de données


dans la bibliothèque de flux de données de TPL pour effectuer
plusieurs opérations qui doivent communiquer entre elles ou
pour traiter les données lorsqu'elles sont disponibles.

Utilisation de la bibliothèque parallèle de tâches (TPL) avec Décrit comment utiliser la bibliothèque parallèle de tâches
d’autres modèles asynchrones avec d’autres modèles asynchrones dans .NET.

Pièges potentiels dans le parallélisme des données et des Décrit des pièges courants et la manière de les éviter.
tâches

Parallel LINQ (PLINQ) Décrit comment atteindre le parallélisme des données avec
les requêtes LINQ.

Programmation parallèle Nœud de niveau supérieur pour la programmation parallèle


.NET.
Voir aussi
Exemples de programmation parallèle avec .NET Core & .NET Standard
Parallélisme de données (bibliothèque parallèle de
tâches)
18/07/2020 • 6 minutes to read • Edit Online

Le parallélisme des données fait référence aux scénarios dans lesquels la même opération est exécutée de
manière simultanée (autrement dit, en parallèle) sur les éléments d’un tableau ou d’une collection source. Dans
les opérations en parallèle de données, la collection source est partitionnée afin que plusieurs threads puissent
fonctionner simultanément sur des segments différents.
La bibliothèque parallèle de tâches prend en charge le parallélisme des données via la classe
System.Threading.Tasks.Parallel. Cette classe fournit des implémentations parallèles, fondées sur une méthode,
des boucles for et foreach ( For et For Each en Visual Basic). Vous écrivez la logique de boucle d'une boucle
Parallel.For ou Parallel.ForEach de la même manière que pour une boucle séquentielle. Vous n’avez pas à créer de
threads ou d’éléments de travail de file d’attente. Dans les boucles simples, vous n'avez pas besoin d'acquérir de
verrous. La bibliothèque parallèle de tâches gère tous les travaux de bas niveau pour vous. Pour des informations
détaillées sur l’utilisation de Parallel.For et de Parallel.ForEach, téléchargez le document Modèles de
programmation parallèle : comprendre et appliquer les modèles parallèles avec .NET Framework 4. L'exemple de
code suivant montre une boucle foreach simple et son équivalent parallèle.

NOTE
Cette documentation utilise les expressions lambda pour définir les délégués de la bibliothèque parallèle de tâches. Si les
expressions lambda en C# ou Visual Basic ne vous sont pas familières, consultez la page Expressions lambda en PLINQ et
dans la bibliothèque parallèle de tâches.

// Sequential version
foreach (var item in sourceCollection)
{
Process(item);
}

// Parallel equivalent
Parallel.ForEach(sourceCollection, item => Process(item));

' Sequential version


For Each item In sourceCollection
Process(item)
Next

' Parallel equivalent


Parallel.ForEach(sourceCollection, Sub(item) Process(item))

Quand une boucle parallèle s'exécute, la bibliothèque parallèle de tâches partitionne la source de données afin
que la boucle puisse s'exécuter simultanément sur plusieurs parties. En arrière-plan, le planificateur de tâches
partitionne la tâche en fonction des ressources système et de la charge de travail. Quand cela lui est possible, le
planificateur redistribue le travail entre plusieurs threads et processeurs si la charge de travail est déséquilibrée.
NOTE
Vous pouvez également fournir votre propre partitionneur ou planificateur personnalisé. Pour plus d’informations, consultez
la page Partitionneurs personnalisés pour PLINQ et la bibliothèque parallèle de tâches (TPL) et Planificateurs de tâches.

Les méthodes Parallel.For et Parallel.ForEach ont plusieurs surcharges qui vous permettent d'arrêter ou de quitter
l'exécution d'une boucle, de surveiller l'état de la boucle sur d'autres threads, de maintenir l'état local de thread,
de finaliser des objets locaux de thread, de contrôler le degré d'accès concurrentiel, etc. Les types d'assistance qui
permettent ces fonctionnalités incluent ParallelLoopState, ParallelOptions, ParallelLoopResult, CancellationToken et
CancellationTokenSource.
Pour plus d’informations, consultez Patterns for Parallel Programming: Understanding and Applying Parallel
Patterns with the .NET Framework 4.
Le parallélisme des données avec syntaxe déclarative ou de requête est pris en charge par PLINQ. Pour plus
d’informations, consultez PLINQ (Parallel LINQ).

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Procédure : écrire une boucle Parallel.For simple Explique comment écrire une boucle For sur tout tableau ou
collection source IEnumerable<T> indexable.

Procédure : écrire une boucle Parallel.ForEach simple Explique comment écrire une boucle ForEach sur toute
collection source IEnumerable<T>.

Guide pratique : arrêt ou sortie d’une boucle Parallel.For Explique comment arrêter ou rompre une boucle parallèle afin
que tous les threads soient informés de l'action.

Procédure : écrire une boucle Parallel.For avec des variables Explique comment écrire une boucle For dans laquelle chaque
locales de thread thread maintient une variable privée qui n’est pas visible pour
les autres threads et comment synchroniser les résultats de
tous les threads quand la boucle se termine.

Procédure : écrire une boucle Parallel.ForEach avec des Explique comment écrire une boucle ForEach dans laquelle
variables locales de partition chaque thread maintient une variable privée qui n’est pas
visible pour les autres threads et comment synchroniser les
résultats de tous les threads quand la boucle se termine.

Procédure : annuler une boucle Parallel.For ou ForEach Explique comment annuler une boucle parallèle à l'aide d'un
System.Threading.CancellationToken.

Procédure : accélérer les petits corps de boucles Décrit une façon d'accélérer l'exécution quand un corps de
boucle est très petit.

Bibliothèque parallèle de tâches Fournit une vue d’ensemble de la bibliothèque parallèle de


tâches.

Programmation parallèle Présente la programmation parallèle dans le .NET Framework.

Voir aussi
Programmation parallèle
Procédure : écrire une boucle Parallel.For simple
18/07/2020 • 13 minutes to read • Edit Online

Cette rubrique contient deux exemples qui illustrent la méthode Parallel.For. Le premier utilise la surcharge de
méthode Parallel.For(Int64, Int64, Action<Int64>) et le second utilise la surcharge Parallel.For(Int32, Int32,
Action<Int32>), les deux surcharges les plus simples de la méthode Parallel.For. Vous pouvez utiliser ces deux
surcharges de la méthode Parallel.For quand vous n'avez pas besoin d'annuler la boucle, de sortir des itérations de
boucle ou de maintenir un état de thread local.

NOTE
Cette documentation utilise les expressions lambda pour définir les délégués de la bibliothèque parallèle de tâches. Si les
expressions lambda en C# ou Visual Basic ne vous sont pas familières, consultez la page Expressions lambda en PLINQ et
dans la bibliothèque parallèle de tâches.

Le premier exemple calcule la taille des fichiers dans un répertoire unique. Le deuxième calcule le produit de deux
matrices.

Exemple de taille de répertoire


Cet exemple est un utilitaire en ligne de commande simple qui calcule la taille totale des fichiers d'un répertoire. Il
attend un chemin d'accès de répertoire unique en tant qu'argument et indique le nombre et la taille totale des
fichiers contenus dans ce répertoire. Après avoir vérifié que le répertoire existe, il utilise la méthode Parallel.For
pour énumérer les fichiers dans le répertoire et déterminer leur taille. Chaque taille de fichier est ensuite ajoutée à
la variable totalSize . Notez que l'addition est effectuée en appelant la méthode Interlocked.Add, pour être
exécutée comme une opération atomique. Dans le cas contraire, plusieurs tâches pourraient essayer de mettre à
jour la variable totalSize simultanément.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
long totalSize = 0;

String[] args = Environment.GetCommandLineArgs();


if (args.Length == 1) {
Console.WriteLine("There are no command line arguments.");
return;
}
if (! Directory.Exists(args[1])) {
Console.WriteLine("The directory does not exist.");
return;
}

String[] files = Directory.GetFiles(args[1]);


Parallel.For(0, files.Length,
index => { FileInfo fi = new FileInfo(files[index]);
long size = fi.Length;
Interlocked.Add(ref totalSize, size);
} );
Console.WriteLine("Directory '{0}':", args[1]);
Console.WriteLine("{0:N0} files, {1:N0} bytes", files.Length, totalSize);
}
}
// The example displaysoutput like the following:
// Directory 'c:\windows\':
// 32 files, 6,587,222 bytes
Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim totalSize As Long = 0

Dim args() As String = Environment.GetCommandLineArgs()


If args.Length = 1 Then
Console.WriteLine("There are no command line arguments.")
Return
End If
If Not Directory.Exists(args(1))
Console.WriteLine("The directory does not exist.")
Return
End If

Dim files() As String = Directory.GetFiles(args(1))


Parallel.For(0, files.Length,
Sub(index As Integer)
Dim fi As New FileInfo(files(index))
Dim size As Long = fi.Length
Interlocked.Add(totalSize, size)
End Sub)
Console.WriteLine("Directory '{0}':", args(1))
Console.WriteLine("{0:N0} files, {1:N0} bytes", files.Length, totalSize)
End Sub
End Module
' The example displays output like the following:
' Directory 'c:\windows\':
' 32 files, 6,587,222 bytes

Exemple de matrice et de chronomètre


Cet exemple utilise la méthode Parallel.For pour calculer le produit de deux matrices. Il montre également
comment utiliser la classe System.Diagnostics.Stopwatch pour comparer les performances d'une boucle parallèle
avec une boucle non parallèle. Étant donné qu'il peut générer un important volume de sortie, l'exemple permet de
rediriger la sortie vers un fichier.

using System;
using System.Diagnostics;
using System.Threading.Tasks;

class MultiplyMatrices
{
#region Sequential_Loop
static void MultiplyMatricesSequential(double[,] matA, double[,] matB,
double[,] result)
{
int matACols = matA.GetLength(1);
int matBCols = matB.GetLength(1);
int matARows = matA.GetLength(0);

for (int i = 0; i < matARows; i++)


{
for (int j = 0; j < matBCols; j++)
{
double temp = 0;
for (int k = 0; k < matACols; k++)
{
temp += matA[i, k] * matB[k, j];
}
result[i, j] += temp;
}
}
}
#endregion

#region Parallel_Loop
static void MultiplyMatricesParallel(double[,] matA, double[,] matB, double[,] result)
{
int matACols = matA.GetLength(1);
int matBCols = matB.GetLength(1);
int matARows = matA.GetLength(0);

// A basic matrix multiplication.


// Parallelize the outer loop to partition the source array by rows.
Parallel.For(0, matARows, i =>
{
for (int j = 0; j < matBCols; j++)
{
double temp = 0;
for (int k = 0; k < matACols; k++)
{
temp += matA[i, k] * matB[k, j];
}
result[i, j] = temp;
}
}); // Parallel.For
}
#endregion

#region Main
static void Main(string[] args)
{
// Set up matrices. Use small values to better view
// result matrix. Increase the counts to see greater
// speedup in the parallel loop vs. the sequential loop.
int colCount = 180;
int rowCount = 2000;
int colCount2 = 270;
double[,] m1 = InitializeMatrix(rowCount, colCount);
double[,] m2 = InitializeMatrix(colCount, colCount2);
double[,] result = new double[rowCount, colCount2];

// First do the sequential version.


Console.Error.WriteLine("Executing sequential loop...");
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

MultiplyMatricesSequential(m1, m2, result);


stopwatch.Stop();
Console.Error.WriteLine("Sequential loop time in milliseconds: {0}",
stopwatch.ElapsedMilliseconds);

// For the skeptics.


OfferToPrint(rowCount, colCount2, result);

// Reset timer and results matrix.


stopwatch.Reset();
result = new double[rowCount, colCount2];

// Do the parallel loop.


Console.Error.WriteLine("Executing parallel loop...");
stopwatch.Start();
MultiplyMatricesParallel(m1, m2, result);
stopwatch.Stop();
Console.Error.WriteLine("Parallel loop time in milliseconds: {0}",
stopwatch.ElapsedMilliseconds);
OfferToPrint(rowCount, colCount2, result);

// Keep the console window open in debug mode.


// Keep the console window open in debug mode.
Console.Error.WriteLine("Press any key to exit.");
Console.ReadKey();
}
#endregion

#region Helper_Methods
static double[,] InitializeMatrix(int rows, int cols)
{
double[,] matrix = new double[rows, cols];

Random r = new Random();


for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
matrix[i, j] = r.Next(100);
}
}
return matrix;
}

private static void OfferToPrint(int rowCount, int colCount, double[,] matrix)


{
Console.Error.Write("Computation complete. Print results (y/n)? ");
char c = Console.ReadKey(true).KeyChar;
Console.Error.WriteLine(c);
if (Char.ToUpperInvariant(c) == 'Y')
{
if (! Console.IsOutputRedirected) Console.WindowWidth = 180;
Console.WriteLine();
for (int x = 0; x < rowCount; x++)
{
Console.WriteLine("ROW {0}: ", x);
for (int y = 0; y < colCount; y++)
{
Console.Write("{0:#.##} ", matrix[x, y]);
}
Console.WriteLine();
}
}
}
#endregion
}

Imports System.Diagnostics
Imports System.Threading.Tasks

Module MultiplyMatrices
#Region "Sequential_Loop"
Sub MultiplyMatricesSequential(ByVal matA As Double(,), ByVal matB As Double(,), ByVal result As
Double(,))
Dim matACols As Integer = matA.GetLength(1)
Dim matBCols As Integer = matB.GetLength(1)
Dim matARows As Integer = matA.GetLength(0)

For i As Integer = 0 To matARows - 1


For j As Integer = 0 To matBCols - 1
Dim temp As Double = 0
For k As Integer = 0 To matACols - 1
temp += matA(i, k) * matB(k, j)
Next
result(i, j) += temp
Next
Next
End Sub
#End Region
#Region "Parallel_Loop"
Private Sub MultiplyMatricesParallel(ByVal matA As Double(,), ByVal matB As Double(,), ByVal result As
Double(,))
Dim matACols As Integer = matA.GetLength(1)
Dim matBCols As Integer = matB.GetLength(1)
Dim matARows As Integer = matA.GetLength(0)

' A basic matrix multiplication.


' Parallelize the outer loop to partition the source array by rows.
Parallel.For(0, matARows, Sub(i)
For j As Integer = 0 To matBCols - 1
Dim temp As Double = 0
For k As Integer = 0 To matACols - 1
temp += matA(i, k) * matB(k, j)
Next
result(i, j) += temp
Next
End Sub)
End Sub
#End Region

#Region "Main"
Sub Main(ByVal args As String())
' Set up matrices. Use small values to better view
' result matrix. Increase the counts to see greater
' speedup in the parallel loop vs. the sequential loop.
Dim colCount As Integer = 180
Dim rowCount As Integer = 2000
Dim colCount2 As Integer = 270
Dim m1 As Double(,) = InitializeMatrix(rowCount, colCount)
Dim m2 As Double(,) = InitializeMatrix(colCount, colCount2)
Dim result As Double(,) = New Double(rowCount - 1, colCount2 - 1) {}

' First do the sequential version.


Console.Error.WriteLine("Executing sequential loop...")
Dim stopwatch As New Stopwatch()
stopwatch.Start()

MultiplyMatricesSequential(m1, m2, result)


stopwatch.[Stop]()
Console.Error.WriteLine("Sequential loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds)

' For the skeptics.


OfferToPrint(rowCount, colCount2, result)

' Reset timer and results matrix.


stopwatch.Reset()
result = New Double(rowCount - 1, colCount2 - 1) {}

' Do the parallel loop.


Console.Error.WriteLine("Executing parallel loop...")
stopwatch.Start()
MultiplyMatricesParallel(m1, m2, result)
stopwatch.[Stop]()
Console.Error.WriteLine("Parallel loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds)
OfferToPrint(rowCount, colCount2, result)

' Keep the console window open in debug mode.


Console.Error.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
#End Region

#Region "Helper_Methods"
Function InitializeMatrix(ByVal rows As Integer, ByVal cols As Integer) As Double(,)
Dim matrix As Double(,) = New Double(rows - 1, cols - 1) {}

Dim r As New Random()


For i As Integer = 0 To rows - 1
For i As Integer = 0 To rows - 1
For j As Integer = 0 To cols - 1
matrix(i, j) = r.[Next](100)
Next
Next
Return matrix
End Function

Sub OfferToPrint(ByVal rowCount As Integer, ByVal colCount As Integer, ByVal matrix As Double(,))
Console.Error.Write("Computation complete. Display results (y/n)? ")
Dim c As Char = Console.ReadKey(True).KeyChar
Console.Error.WriteLine(c)
If Char.ToUpperInvariant(c) = "Y"c Then
If Not Console.IsOutputRedirected Then Console.WindowWidth = 168
Console.WriteLine()
For x As Integer = 0 To rowCount - 1
Console.WriteLine("ROW {0}: ", x)
For y As Integer = 0 To colCount - 1
Console.Write("{0:#.##} ", matrix(x, y))
Next
Console.WriteLine()
Next
End If
End Sub
#End Region
End Module

Lors de la parallélisation du code, y compris des boucles, un objectif important consiste à utiliser les processeurs le
plus possible sans surparalléliser jusqu'au point où la surcharge de traitement en parallèle réduit les performances.
Dans cet exemple, seule la boucle externe est parallélisée, car peu de travail est effectué dans la boucle interne. La
combinaison d'une petite quantité de travail et des effets indésirables du cache peut entraîner une dégradation des
performances dans les boucles parallèles imbriquées. Par conséquent, paralléliser uniquement la boucle externe
est la meilleure façon d'optimiser les avantages offerts par l'accès concurrentiel sur la plupart des systèmes.

Délégué
Le troisième paramètre de cette surcharge de For est un délégué de type Action<int> en C# ou
Action(Of Integer) en Visual Basic. Un délégué Action , qu'il possède zéro, un ou seize paramètres de type,
retourne toujours void. En Visual Basic, le comportement d'une Action est défini avec un Sub . L’exemple utilise
une expression lambda pour créer le délégué, mais vous pouvez le créer d’autres façons. Pour plus d’informations,
consultez Expressions lambda en PLINQ et dans la bibliothèque parallèle de tâches.

Valeur d'itération
Le délégué accepte un seul paramètre d'entrée dont la valeur est l'itération actuelle. Cette valeur d'itération est
fournie par le runtime et sa valeur de départ est l'index du premier élément du segment (partition) de la source qui
est en cours de traitement sur le thread actuel.
Si vous souhaitez contrôler plus étroitement le niveau d'accès concurrentiel, utilisez l'une des surcharges qui
acceptent un paramètre d'entrée System.Threading.Tasks.ParallelOptions, telles que : Parallel.For(Int32, Int32,
ParallelOptions, Action<Int32,ParallelLoopState>).

Valeur de retour et gestion des exceptions


For retourne un objet System.Threading.Tasks.ParallelLoopResult quand tous les threads sont terminés. Cette
valeur de retour est utile quand vous arrêtez ou rompez l'itération de boucle manuellement, car le
ParallelLoopResult stocke des informations telles que la dernière itération qui s'est achevée. Si une ou plusieurs
exceptions se produisent sur l'un des threads, une System.AggregateException est levée.
Dans le code de cet exemple, la valeur de retour de For n'est pas utilisée.
Analyse et performances
Vous pouvez utiliser l'Assistant Performance pour afficher l'utilisation du processeur sur votre ordinateur. À des
fins de test, augmentez le nombre de colonnes et de lignes des matrices. Plus les matrices sont grandes, plus la
différence de performances est élevée entre les versions parallèles et séquentielles du calcul. Quand la matrice est
petite, la version séquentielle s'exécute plus rapidement en raison de la surcharge liée au paramétrage de la boucle
parallèle.
Les appels synchrones aux ressources partagées, telles que la console ou le système de fichiers, entraînent une
dégradation sensible des performances d’une boucle parallèle. Quand vous mesurez les performances, essayez
d'éviter les appels tels que Console.WriteLine dans la boucle.

Compiler le code
Copiez et collez ce code dans un projet Visual Studio.

Voir aussi
For
ForEach
Parallélisme des données
Programmation parallèle
Comment : écrire une boucle Parallel. ForEach simple
18/07/2020 • 5 minutes to read • Edit Online

Cet exemple montre comment utiliser une boucle Parallel.ForEach pour activer le parallélisme des données sur
n’importe quelle source de données System.Collections.IEnumerable ou
System.Collections.Generic.IEnumerable<T>.

NOTE
Cette documentation utilise des expressions lambda pour définir les délégués en PLINQ. Si vous n’êtes pas familiarisé avec les
expressions lambda en C# ou Visual Basic, consultez expressions lambda en PLINQ et tpl.

Exemple
Cet exemple part du principe que vous disposez de plusieurs fichiers .jpg dans un dossier
C:\Users\Public\Pictures\Sample Pictures, et crée un sous-dossier nommé Modified (Modifié). Lorsque vous
exécutez l’exemple, il fait pivoter chaque image .jpg dans Sample Pictures (Exemples d’images) et les enregistre
dans le sous-dossier Modified. Vous pouvez modifier les deux chemins d’accès en fonction des besoins.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Drawing;

public class Example


{
public static void Main()
{
// A simple source for demonstration purposes. Modify this path as necessary.
string[] files = Directory.GetFiles(@"C:\Users\Public\Pictures\Sample Pictures", "*.jpg");
string newDir = @"C:\Users\Public\Pictures\Sample Pictures\Modified";
Directory.CreateDirectory(newDir);

// Method signature: Parallel.ForEach(IEnumerable<TSource> source, Action<TSource> body)


Parallel.ForEach(files, (currentFile) =>
{
// The more computational work you do here, the greater
// the speedup compared to a sequential foreach loop.
string filename = Path.GetFileName(currentFile);
var bitmap = new Bitmap(currentFile);

bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
bitmap.Save(Path.Combine(newDir, filename));

// Peek behind the scenes to see how work is parallelized.


// But be aware: Thread contention for the Console slows down parallel
loops!!!

Console.WriteLine($"Processing {filename} on thread


{Thread.CurrentThread.ManagedThreadId}");
//close lambda expression and method invocation
});

// Keep the console window open in debug mode.


Console.WriteLine("Processing complete. Press any key to exit.");
Console.ReadKey();
}
}
Imports System.IO
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Drawing

Module ForEachDemo

Sub Main()
' A simple source for demonstration purposes. Modify this path as necessary.
Dim files As String() = Directory.GetFiles("C:\Users\Public\Pictures\Sample Pictures", "*.jpg")
Dim newDir As String = "C:\Users\Public\Pictures\Sample Pictures\Modified"
Directory.CreateDirectory(newDir)

Parallel.ForEach(files, Sub(currentFile)
' The more computational work you do here, the greater
' the speedup compared to a sequential foreach loop.
Dim filename As String = Path.GetFileName(currentFile)
Dim bitmap As New Bitmap(currentFile)

bitmap.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone)
bitmap.Save(Path.Combine(newDir, filename))

' Peek behind the scenes to see how work is parallelized.


' But be aware: Thread contention for the Console slows down parallel
loops!!!

Console.WriteLine($"Processing {filename} on thread


{Thread.CurrentThread.ManagedThreadId}")
'close lambda expression and method invocation
End Sub)

' Keep the console window open in debug mode.


Console.WriteLine("Processing complete. Press any key to exit.")
Console.ReadKey()
End Sub
End Module

Une boule Parallel.ForEach fonctionne comme une boucle Parallel.For. Les boucles partitionnent la collection source
et planifient le travail sur plusieurs threads en fonction de l’environnement système. Plus il y a de processeurs sur
le système, plus la méthode parallèle s’exécute rapidement. Pour certaines collections sources, une boucle
séquentielle peut être plus rapide, selon la taille de la source et le type de travail exécuté par la boucle. Pour plus
d’informations sur les performances, consultez pièges potentiels dans le parallélisme des données et des tâches.
Pour plus d’informations sur les boucles parallèles, consultez Comment : écrire une boucle Parallel. for simple.
Pour utiliser Parallel.ForEach avec une collection non générique, vous pouvez utiliser la méthode d’extension
Enumerable.Cast pour convertir la collection en une collection générique, comme indiqué dans l’exemple suivant :

Parallel.ForEach(nonGenericCollection.Cast<object>(),
currentElement =>
{
});

Parallel.ForEach(nonGenericCollection.Cast(Of Object), _
Sub(currentElement)
' ... work with currentElement
End Sub)

Vous pouvez également utiliser PLINQ (Parallel LINQ) pour paralléliser le traitement des sources de données
IEnumerable<T>. PLINQ vous permet d’utiliser la syntaxe de requête déclarative pour exprimer le comportement
de la boucle. Pour plus d’informations, consultez PLINQ (Parallel LINQ).

Compiler et exécuter le code


Vous pouvez compiler le code en tant qu’application de console pour .NET Framework ou en tant qu’application de
console pour .NET Core.
Dans Visual Studio, il existe des modèles d’application de console Visual Basic et C# pour Windows Desktop et .NET
Core.
À partir de la ligne de commande, vous pouvez utiliser les commandes CLI .NET Core (par exemple,
dotnet new console ou dotnet new console -lang vb ), ou vous pouvez créer le fichier et utiliser le compilateur de
ligne de commande pour une application .NET Framework.
Pour un projet .NET Core, vous devez référencer le package NuGet System.Drawing.Common . Dans Visual
Studio, utilisez le gestionnaire de package NuGet pour installer le package. Vous pouvez aussi ajouter une référence
au package dans votre fichier *.csproj ou *.vbproj :

<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="4.5.1" />
</ItemGroup>

Pour exécuter une application de console .NET Core depuis la ligne de commande, utilisez dotnet run depuis le
dossier qui contient votre application.
Pour exécuter votre application de console depuis Visual Studio, appuyez sur F5 .

Voir aussi
Parallélisme des données
Programmation parallèle
Parallel LINQ (PLINQ)
Procédure : écrire une boucle Parallel.For avec des
variables locales de thread
18/07/2020 • 6 minutes to read • Edit Online

Cet exemple montre comment utiliser des variables de thread local pour stocker et récupérer l’état de chaque
tâche créée par une boucle For. En utilisant des données de thread local, vous pouvez éviter la surcharge liée à la
synchronisation d’un grand nombre d’accès à un état partagé. Au lieu d’écrire dans une ressource partagée à
chaque itération, vous calculez et stockez la valeur jusqu’à ce que toutes les itérations de la tâche soient terminées.
Vous pouvez ensuite écrire une fois le résultat final dans la ressource partagée ou le transmettre à une autre
méthode.

Exemple
L'exemple suivant appelle la méthode For<TLocal>(Int32, Int32, Func<TLocal>,
Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) pour calculer la somme des valeurs d'un tableau
qui contient un million d'éléments. La valeur de chaque élément est égale à son index.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Test
{
static void Main()
{
int[] nums = Enumerable.Range(0, 1000000).ToArray();
long total = 0;

// Use type parameter to make subtotal a long, not an int


Parallel.For<long>(0, nums.Length, () => 0, (j, loop, subtotal) =>
{
subtotal += nums[j];
return subtotal;
},
(x) => Interlocked.Add(ref total, x)
);

Console.WriteLine("The total is {0:N0}", total);


Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
'How to: Write a Parallel.For Loop That Has Thread-Local Variables

Imports System.Threading
Imports System.Threading.Tasks

Module ForWithThreadLocal

Sub Main()
Dim nums As Integer() = Enumerable.Range(0, 1000000).ToArray()
Dim total As Long = 0

' Use type parameter to make subtotal a Long type. Function will overflow otherwise.
Parallel.For(Of Long)(0, nums.Length, Function() 0, Function(j, [loop], subtotal)
subtotal += nums(j)
Return subtotal
End Function, Function(x) Interlocked.Add(total,
x))

Console.WriteLine("The total is {0:N0}", total)


Console.WriteLine("Press any key to exit")
Console.ReadKey()
End Sub

End Module

Les deux premiers paramètres de chaque méthode For spécifient les valeurs d'itération initiale et finale. Dans cette
surcharge de la méthode, le troisième paramètre indique où vous initialisez votre état local. Dans ce contexte,
« état local » signifie une variable dont la durée de vie commence juste avant la première itération de la boucle sur
le thread actuel et se termine juste après la dernière itération.
Le type du troisième paramètre est un délégué Func<TResult> où TResult représente le type de la variable
destinée à stocker l'état de thread local. Son type est défini par l’argument de type générique fourni au moment de
l’appel de la méthode For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>,
Action<TLocal>) générique, en l’occurrence Int64. L’argument de type indique au compilateur le type de la variable
temporaire à utiliser pour stocker l’état de thread local. Dans cet exemple, l'expression () => 0 (ou Function() 0
dans Visual Basic) initialise la variable de thread local à zéro. Si l'argument de type générique est un type de
référence ou un type de valeur défini par l'utilisateur, l'expression ressemble à ceci :

() => new MyClass()

Function() new MyClass()

Le quatrième paramètre définit la logique de la boucle. Il doit correspondre à un délégué ou à une expression
lambda dont la signature est Func<int, ParallelLoopState, long, long> en C# ou
Func(Of Integer, ParallelLoopState, Long, Long) en Visual Basic. Le premier paramètre est la valeur du compteur
de boucle pour cette itération de la boucle. Le deuxième est un objet ParallelLoopState qui permet de quitter la
boucle ; cet objet est fourni par la classe Parallel à chaque occurrence de la boucle. Le troisième paramètre est la
variable de thread local. Le dernier paramètre est le type de retour. Dans ce cas, le type est Int64, car c’est ce type
que nous avons spécifié dans l’argument de type For. Cette variable est nommée subtotal et est retournée par
l'expression lambda. La valeur de retour est utilisée pour initialiser subtotal à chaque itération suivante de la
boucle. Vous pouvez également considérer ce dernier paramètre comme une valeur transmise à chaque itération,
puis communiquée au délégué localFinally quand la dernière itération est terminée.
Le cinquième paramètre définit la méthode qui est appelée une fois, après la fin de toutes les itérations sur un
thread particulier. Le type de l'argument d'entrée correspond de nouveau à l'argument de type de la méthode
For<TLocal>(Int32, Int32, Func<TLocal>, Func<Int32,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) et au type
retourné par l'expression lambda du corps. Dans cet exemple, la valeur est ajoutée à une variable à portée de
classe d'une façon thread-safe en appelant la méthode Interlocked.Add. L'utilisation d'une variable de thread local
nous a évité d'écrire dans cette variable de classe à chaque itération de la boucle.
Pour plus d’informations sur l’utilisation d’expressions lambda, consultez Expressions lambda en PLINQ et dans la
bibliothèque parallèle de tâches.

Voir aussi
Parallélisme des données
Programmation parallèle
Bibliothèque parallèle de tâches
Expressions lambda dans PLINQ et TPL
Guide pratique pour écrire une boucle
Parallel.ForEach avec des variables locales de
partition
18/07/2020 • 6 minutes to read • Edit Online

L’exemple suivant montre comment écrire une méthode ForEach qui utilise des variables locales de partition.
Quand une boucle ForEach s’exécute, elle divise sa collection source en plusieurs partitions. Chaque partition a sa
propre copie de la variable locale de partition. Une variable locale de partition est similaire à une variable locale de
thread, à la différence près que plusieurs partitions peuvent s’exécuter sur un thread unique.
Le code et les paramètres dans cet exemple ressemblent beaucoup à la méthode For correspondante. Pour plus
d’informations, voir Guide pratique : écrire une boucle Parallel.For avec des variables locales de thread.
Pour utiliser une variable locale de partition dans une boucle ForEach, vous devez appeler l’une des surcharges de
méthode qui accepte deux paramètres de type. Le premier paramètre de type, TSource , spécifie le type d’élément
source, tandis que le second, TLocal , spécifie le type de la variable locale de partition.

Exemple
L’exemple suivant appelle la surcharge Parallel.ForEach<TSource,TLocal>(IEnumerable<TSource>, Func<TLocal>,
Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) pour calculer la somme d’un tableau d’un million
d’éléments. Cette surcharge possède quatre paramètres :
source , qui représente la source de données. Il doit implémenter IEnumerable<T>. La source de données
dans notre exemple est l'objet IEnumerable<Int32> d'un million de membres retourné par la méthode
Enumerable.Range.
localInit ou fonction qui initialise la variable locale de partition. Cette fonction est appelée une fois pour
chaque partition dans laquelle l'opération Parallel.ForEach s'exécute. Notre exemple initialise la variable
locale de partition à zéro.
body, Func<T1,T2,T3,TResult> appelé par la boucle parallèle sur chaque itération de la boucle. Sa signature
est Func\<TSource, ParallelLoopState, TLocal, TLocal> . Vous fournissez le code pour le délégué, puis la
boucle transmet les paramètres d'entrée, qui sont :
l'élément actuel de l'interface IEnumerable<T> ;
une variable ParallelLoopState que vous pouvez utiliser dans le code de votre délégué pour examiner
l'état de la boucle ;
la variable locale de partition.
Votre délégué retourne la variable locale de partition, qui est ensuite transmise à la prochaine itération de la
boucle qui s’exécute dans la partition concernée. Chaque partition de boucle tient à jour une instance
distincte de cette variable.
Dans l’exemple, le délégué ajoute la valeur de chaque entier à la variable locale de partition, qui tient à jour
le total cumulé des valeurs des éléments entiers dans la partition concernée.
, délégué Action<TLocal> que la méthode Parallel.ForEach appelle quand les opérations de
localFinally
bouclage dans chaque partition sont terminées. La méthode Parallel.ForEach passe à votre délégué
Action<TLocal> la valeur finale de la variable locale de partition pour cette partition de boucle, et vous
fournissez le code qui prend en charge la combinaison du résultat de cette partition avec les résultats des
autres partitions. Ce délégué peut être appelé simultanément par plusieurs tâches. C’est la raison pour
laquelle l’exemple utilise la méthode Interlocked.Add(Int32, Int32) pour synchroniser l’accès à la variable
total . Comme le type de délégué est un délégué Action<T>, aucune valeur n'est retournée.

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Test
{
static void Main()
{
int[] nums = Enumerable.Range(0, 1000000).ToArray();
long total = 0;

// First type parameter is the type of the source elements


// Second type parameter is the type of the thread-local variable (partition subtotal)
Parallel.ForEach<int, long>(nums, // source collection
() => 0, // method to initialize the local variable
(j, loop, subtotal) => // method invoked by the loop on each iteration
{
subtotal += j; //modify local variable
return subtotal; // value to be passed to next iteration
},
// Method to be executed when each partition has completed.
// finalResult is the final value of subtotal for a particular partition.
(finalResult) => Interlocked.Add(ref total, finalResult)
);

Console.WriteLine("The total from Parallel.ForEach is {0:N0}", total);


}
}
// The example displays the following output:
// The total from Parallel.ForEach is 499,999,500,000
' How to: Write a Parallel.ForEach Loop That Has Thread-Local Variables

Imports System.Threading
Imports System.Threading.Tasks

Module ForEachThreadLocal
Sub Main()

Dim nums() As Integer = Enumerable.Range(0, 1000000).ToArray()


Dim total As Long = 0

' First type paramemter is the type of the source elements


' Second type parameter is the type of the thread-local variable (partition subtotal)
Parallel.ForEach(Of Integer, Long)(nums, Function() 0,
Function(elem, loopState, subtotal)
subtotal += elem
Return subtotal
End Function,
Sub(finalResult)
Interlocked.Add(total, finalResult)
End Sub)

Console.WriteLine("The result of Parallel.ForEach is {0:N0}", total)


End Sub
End Module
' The example displays the following output:
' The result of Parallel.ForEach is 499,999,500,000

Voir aussi
Parallélisme des données
Procédure : écrire une boucle Parallel.For avec des variables locales de thread
Expressions lambda dans PLINQ et TPL
Procédure : annuler une boucle Parallel.For ou
ForEach
18/07/2020 • 2 minutes to read • Edit Online

Les méthodes Parallel.For et Parallel.ForEach prennent en charge l'annulation via l'utilisation de jetons d'annulation.
Pour plus d'informations sur l'annulation en général, consultez Annulation. Dans une boucle parallèle, vous
fournissez CancellationToken à la méthode dans le paramètre ParallelOptions et vous joignez l’appel parallèle dans
un bloc try-catch.

Exemple
L'exemple suivant montre comment annuler un appel à Parallel.ForEach. Vous pouvez appliquer la même approche
à un appel Parallel.For.
namespace CancelParallelLoops
{
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static void Main()
{
int[] nums = Enumerable.Range(0, 10000000).ToArray();
CancellationTokenSource cts = new CancellationTokenSource();

// Use ParallelOptions instance to store the CancellationToken


ParallelOptions po = new ParallelOptions();
po.CancellationToken = cts.Token;
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
Console.WriteLine("Press any key to start. Press 'c' to cancel.");
Console.ReadKey();

// Run a task so that we can cancel from another thread.


Task.Factory.StartNew(() =>
{
if (Console.ReadKey().KeyChar == 'c')
cts.Cancel();
Console.WriteLine("press any key to exit");
});

try
{
Parallel.ForEach(nums, po, (num) =>
{
double d = Math.Sqrt(num);
Console.WriteLine("{0} on {1}", d, Thread.CurrentThread.ManagedThreadId);
po.CancellationToken.ThrowIfCancellationRequested();
});
}
catch (OperationCanceledException e)
{
Console.WriteLine(e.Message);
}
finally
{
cts.Dispose();
}

Console.ReadKey();
}
}
}
' How to: Cancel a Parallel.For or ForEach Loop

Imports System.Threading
Imports System.Threading.Tasks

Module CancelParallelLoops
Sub Main()
Dim nums() As Integer = Enumerable.Range(0, 10000000).ToArray()
Dim cts As New CancellationTokenSource

' Use ParallelOptions instance to store the CancellationToken


Dim po As New ParallelOptions
po.CancellationToken = cts.Token
po.MaxDegreeOfParallelism = System.Environment.ProcessorCount
Console.WriteLine("Press any key to start. Press 'c' to cancel.")
Console.ReadKey()

' Run a task so that we can cancel from another thread.


Dim t As Task = Task.Factory.StartNew(Sub()
If Console.ReadKey().KeyChar = "c"c Then
cts.Cancel()
End If
Console.WriteLine(vbCrLf & "Press any key to exit.")
End Sub)

Try

' The error "Exception is unhandled by user code" will appear if "Just My Code"
' is enabled. This error is benign. You can press F5 to continue, or disable Just My Code.
Parallel.ForEach(nums, po, Sub(num)
Dim d As Double = Math.Sqrt(num)
Console.CursorLeft = 0
Console.Write("{0:##.##} on {1}", d,
Thread.CurrentThread.ManagedThreadId)
po.CancellationToken.ThrowIfCancellationRequested()
End Sub)

Catch e As OperationCanceledException
Console.WriteLine(e.Message)
Finally
cts.Dispose()
End Try

Console.ReadKey()

End Sub
End Module

Si le jeton qui signale l’annulation est le même jeton que celui spécifié dans l’instance ParallelOptions, alors la
boucle parallèle lève une seule exception OperationCanceledException lors de l’annulation. Si un autre jeton
provoque l’annulation, la boucle lève une exception AggregateException avec une exception
OperationCanceledException comme InnerException.

Voir aussi
Parallélisme des données
Expressions lambda dans PLINQ et TPL
Procédure : gérer des exceptions dans des boucles
parallèles
18/07/2020 • 4 minutes to read • Edit Online

Les surcharges Parallel.For et Parallel.ForEach ne possèdent pas de mécanisme permettant de gérer les exceptions.
À cet égard, elles ressemblent à des for boucles normales et foreach ( For et For Each dans Visual Basic); une
exception non gérée entraîne l’arrêt de la boucle dès que toutes les itérations en cours se terminent.
Quand vous ajoutez votre propre logique de gestion des exceptions à des boucles parallèles, gérez le cas dans
lequel de telles exceptions peuvent être levées simultanément sur plusieurs threads et le cas dans lequel une
exception levée sur un thread provoque la levée d'une autre exception sur un autre thread. Vous pouvez gérer les
deux cas en encapsulant toutes les exceptions de la boucle dans un System.AggregateException. L'exemple suivant
montre une méthode possible.

NOTE
Quand l'option Uniquement mon code est activée, Visual Studio, dans certains cas, peut s'arrêter sur la ligne qui lève
l'exception et afficher un message d'erreur indiquant que l'exception n'est pas gérée par le code utilisateur. Cette erreur est
sans gravité. Vous pouvez appuyer sur F5 pour continuer et voir le comportement de gestion des exceptions qui est illustré
dans l'exemple ci-dessous. Pour empêcher Visual Studio de s'arrêter sur la première erreur, il suffit de désactiver la case à
cocher Uniquement mon code sous Outils, Options, Débogage, Général.

Exemple
Dans cet exemple, toutes les exceptions sont interceptées, puis encapsulées dans une System.AggregateException
qui est ensuite levée. L'appelant peut décider des exceptions à gérer.
class ExceptionDemo2
{
static void Main(string[] args)
{
// Create some random data to process in parallel.
// There is a good probability this data will cause some exceptions to be thrown.
byte[] data = new byte[5000];
Random r = new Random();
r.NextBytes(data);

try
{
ProcessDataInParallel(data);
}
catch (AggregateException ae)
{
var ignoredExceptions = new List<Exception>();
// This is where you can choose which exceptions to handle.
foreach (var ex in ae.Flatten().InnerExceptions)
{
if (ex is ArgumentException)
Console.WriteLine(ex.Message);
else
ignoredExceptions.Add(ex);
}
if (ignoredExceptions.Count > 0) throw new AggregateException(ignoredExceptions);
}

Console.WriteLine("Press any key to exit.");


Console.ReadKey();
}

private static void ProcessDataInParallel(byte[] data)


{
// Use ConcurrentQueue to enable safe enqueueing from multiple threads.
var exceptions = new ConcurrentQueue<Exception>();

// Execute the complete loop and capture all exceptions.


Parallel.ForEach(data, d =>
{
try
{
// Cause a few exceptions, but not too many.
if (d < 3)
throw new ArgumentException($"Value is {d}. Value must be greater than or equal to 3.");
else
Console.Write(d + " ");
}
// Store the exception and continue with the loop.
catch (Exception e)
{
exceptions.Enqueue(e);
}
});
Console.WriteLine();

// Throw the exceptions here after the loop completes.


if (exceptions.Count > 0) throw new AggregateException(exceptions);
}
}
' How to: Handle Exceptions in Parallel Loops

Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Threading.Tasks

Module ExceptionsInLoops

Sub Main()

' Create some random data to process in parallel.


' There is a good probability this data will cause some exceptions to be thrown.
Dim data(1000) As Byte
Dim r As New Random()
r.NextBytes(data)

Try
ProcessDataInParallel(data)
Catch ae As AggregateException
Dim ignoredExceptions As New List(Of Exception)
' This is where you can choose which exceptions to handle.
For Each ex As Exception In ae.Flatten().InnerExceptions
If (TypeOf (ex) Is ArgumentException) Then
Console.WriteLine(ex.Message)
Else
ignoredExceptions.Add(ex)
End If
Next
If ignoredExceptions.Count > 0 Then
Throw New AggregateException(ignoredExceptions)
End If
End Try
Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub
Sub ProcessDataInParallel(ByVal data As Byte())

' Use ConcurrentQueue to enable safe enqueueing from multiple threads.


Dim exceptions As New ConcurrentQueue(Of Exception)

' Execute the complete loop and capture all exceptions.


Parallel.ForEach(Of Byte)(data, Sub(d)
Try
' Cause a few exceptions, but not too many.
If d < 3 Then
Throw New ArgumentException($"Value is {d}. Value must be
greater than or equal to 3")
Else
Console.Write(d & " ")
End If
Catch ex As Exception
' Store the exception and continue with the loop.
exceptions.Enqueue(ex)
End Try
End Sub)
Console.WriteLine()
' Throw the exceptions here after the loop completes.
If exceptions.Count > 0 Then
Throw New AggregateException(exceptions)
End If
End Sub
End Module

Voir aussi
Parallélisme des données
Expressions lambda dans PLINQ et TPL
Procédure : accélérer les petits corps de boucles
18/07/2020 • 2 minutes to read • Edit Online

Une boucle Parallel.For dont le corps est court risque de s'exécuter plus lentement que la boucle séquentielle
équivalente, notamment la boucle for en C# et la boucle For en Visual Basic. La baisse des performances est
provoquée par les surcharges impliquées dans le partitionnement des données et par le coût de l'appel d'un
délégué sur chaque itération de boucle. Pour résoudre ces scénarios, la classe Partitioner offre la méthode
Partitioner.Create, ce qui vous permet de fournir une boucle séquentielle pour le corps du délégué, afin que celui-ci
soit appelé une seule fois par partition, au lieu d'une fois par itération. Pour plus d’informations, consultez
Partitionneurs personnalisés pour PLINQ et la bibliothèque parallèle de tâches (TPL).

Exemple
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static void Main()
{

// Source must be array or IList.


var source = Enumerable.Range(0, 100000).ToArray();

// Partition the entire source array.


var rangePartitioner = Partitioner.Create(0, source.Length);

double[] results = new double[source.Length];

// Loop over the partitions in parallel.


Parallel.ForEach(rangePartitioner, (range, loopState) =>
{
// Loop over each range element without a delegate invocation.
for (int i = range.Item1; i < range.Item2; i++)
{
results[i] = source[i] * Math.PI;
}
});

Console.WriteLine("Operation complete. Print results? y/n");


char input = Console.ReadKey().KeyChar;
if (input == 'y' || input == 'Y')
{
foreach(double d in results)
{
Console.Write("{0} ", d);
}
}
}
}
Imports System.Threading.Tasks
Imports System.Collections.Concurrent

Module PartitionDemo

Sub Main()
' Source must be array or IList.
Dim source = Enumerable.Range(0, 100000).ToArray()

' Partition the entire source array.


' Let the partitioner size the ranges.
Dim rangePartitioner = Partitioner.Create(0, source.Length)

Dim results(source.Length - 1) As Double

' Loop over the partitions in parallel. The Sub is invoked


' once per partition.
Parallel.ForEach(rangePartitioner, Sub(range, loopState)

' Loop over each range element without a delegate invocation.


For i As Integer = range.Item1 To range.Item2 - 1
results(i) = source(i) * Math.PI
Next
End Sub)
Console.WriteLine("Operation complete. Print results? y/n")
Dim input As Char = Console.ReadKey().KeyChar
If input = "y"c Or input = "Y"c Then
For Each d As Double In results
Console.Write("{0} ", d)
Next
End If

End Sub
End Module

L'approche illustrée dans cet exemple est utile quand la boucle exécute une quantité minimale de travail. À mesure
que le travail devient de plus en plus gourmand en ressources informatiques, vous obtiendrez probablement des
performances identiques ou meilleures en utilisant une boucle For ou ForEach avec le partitionneur par défaut.

Voir aussi
Parallélisme des données
Partitionneurs personnalisés pour PLINQ et TPL
Itérateurs (C#)
Itérateurs (Visual Basic)
Expressions lambda dans PLINQ et TPL
Procédure : Itérer les répertoires de fichiers avec la
classe parallèle
18/07/2020 • 7 minutes to read • Edit Online

Dans de nombreux cas, l’itération de fichiers est une opération facile à mettre en parallèle. La rubrique Guide
pratique : itérer les répertoires de fichiers avec PLINQ présente le moyen le plus simple d’effectuer cette tâche dans
de nombreux scénarios. Toutefois, des problèmes risquent de survenir si le code doit gérer les différents types
d’exceptions susceptibles de se produire avec l’accès au système de fichiers. L'exemple suivant montre une
approche du problème. Il utilise une itération de type pile pour parcourir tous les fichiers et tous les dossiers situés
sous un répertoire spécifié, et permet au code d’intercepter et de gérer différentes exceptions. Bien entendu, vous
pouvez gérer les exceptions comme vous le souhaitez.

Exemple
L’exemple suivant itère les répertoires de manière séquentielle, mais traite les fichiers en parallèle. C’est
probablement la meilleure approche en cas de rapport élevé entre les répertoires et les fichiers. Il est également
possible de paralléliser l’itération des répertoires et d’accéder à chaque fichier de manière séquentielle. Il n’est
probablement pas efficace de paralléliser les deux boucles, sauf si l’ordinateur ciblé comporte un grand nombre de
processeurs. Toutefois, comme toujours, vous devez tester votre application de manière approfondie pour
déterminer la meilleure approche.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static void Main()
{
try {
TraverseTreeParallelForEach(@"C:\Program Files", (f) =>
{
// Exceptions are no-ops.
try {
// Do nothing with the data except read it.
byte[] data = File.ReadAllBytes(f);
}
catch (FileNotFoundException) {}
catch (IOException) {}
catch (UnauthorizedAccessException) {}
catch (SecurityException) {}
// Display the filename.
Console.WriteLine(f);
});
}
catch (ArgumentException) {
Console.WriteLine(@"The directory 'C:\Program Files' does not exist.");
}

// Keep the console window open.


Console.ReadKey();
}
public static void TraverseTreeParallelForEach(string root, Action<string> action)
{
//Count of files traversed and timer for diagnostic output
int fileCount = 0;
var sw = Stopwatch.StartNew();

// Determine whether to parallelize file processing on each folder based on processor count.
int procCount = System.Environment.ProcessorCount;

// Data structure to hold names of subfolders to be examined for files.


Stack<string> dirs = new Stack<string>();

if (!Directory.Exists(root)) {
throw new ArgumentException();
}
dirs.Push(root);

while (dirs.Count > 0) {


string currentDir = dirs.Pop();
string[] subDirs = {};
string[] files = {};

try {
subDirs = Directory.GetDirectories(currentDir);
}
// Thrown if we do not have discovery permission on the directory.
catch (UnauthorizedAccessException e) {
Console.WriteLine(e.Message);
continue;
}
// Thrown if another process has deleted the directory after we retrieved its name.
catch (DirectoryNotFoundException e) {
Console.WriteLine(e.Message);
continue;
}

try {
files = Directory.GetFiles(currentDir);
}
catch (UnauthorizedAccessException e) {
Console.WriteLine(e.Message);
continue;
}
catch (DirectoryNotFoundException e) {
Console.WriteLine(e.Message);
continue;
}
catch (IOException e) {
Console.WriteLine(e.Message);
continue;
}

// Execute in parallel if there are enough files in the directory.


// Otherwise, execute sequentially.Files are opened and processed
// synchronously but this could be modified to perform async I/O.
try {
if (files.Length < procCount) {
foreach (var file in files) {
action(file);
fileCount++;
}
}
else {
Parallel.ForEach(files, () => 0, (file, loopState, localCount) =>
{ action(file);
return (int) ++localCount;
},
(c) => {
Interlocked.Add(ref fileCount, c);
Interlocked.Add(ref fileCount, c);
});
}
}
catch (AggregateException ae) {
ae.Handle((ex) => {
if (ex is UnauthorizedAccessException) {
// Here we just output a message and go on.
Console.WriteLine(ex.Message);
return true;
}
// Handle other exceptions here if necessary...

return false;
});
}

// Push the subdirectories onto the stack for traversal.


// This could also be done before handing the files.
foreach (string str in subDirs)
dirs.Push(str);
}

// For diagnostic purposes.


Console.WriteLine("Processed {0} files in {1} milliseconds", fileCount, sw.ElapsedMilliseconds);
}
}

Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.IO
Imports System.Security
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Sub Main()
Try
TraverseTreeParallelForEach("C:\Program Files",
Sub(f)
' Exceptions are No-ops.
Try
' Do nothing with the data except read it.
Dim data() As Byte = File.ReadAllBytes(f)
' In the event the file has been deleted.
Catch e As FileNotFoundException

' General I/O exception, especially if the file is in use.


Catch e As IOException

' Lack of adequate permissions.


Catch e As UnauthorizedAccessException

' Lack of adequate permissions.


Catch e As SecurityException

End Try
' Display the filename.
Console.WriteLine(f)
End Sub)
Catch e As ArgumentException
Console.WriteLine("The directory 'C:\Program Files' does not exist.")
End Try
' Keep the console window open.
Console.ReadKey()
End Sub

Public Sub TraverseTreeParallelForEach(ByVal root As String, ByVal action As Action(Of String))


Public Sub TraverseTreeParallelForEach(ByVal root As String, ByVal action As Action(Of String))
'Count of files traversed and timer for diagnostic output
Dim fileCount As Integer = 0
Dim sw As Stopwatch = Stopwatch.StartNew()

' Determine whether to parallelize file processing on each folder based on processor count.
Dim procCount As Integer = System.Environment.ProcessorCount

' Data structure to hold names of subfolders to be examined for files.


Dim dirs As New Stack(Of String)

If Not Directory.Exists(root) Then Throw New ArgumentException()

dirs.Push(root)

While (dirs.Count > 0)


Dim currentDir As String = dirs.Pop()
Dim subDirs() As String = Nothing
Dim files() As String = Nothing

Try
subDirs = Directory.GetDirectories(currentDir)
' Thrown if we do not have discovery permission on the directory.
Catch e As UnauthorizedAccessException
Console.WriteLine(e.Message)
Continue While
' Thrown if another process has deleted the directory after we retrieved its name.
Catch e As DirectoryNotFoundException
Console.WriteLine(e.Message)
Continue While
End Try

Try
files = Directory.GetFiles(currentDir)
Catch e As UnauthorizedAccessException
Console.WriteLine(e.Message)
Continue While
Catch e As DirectoryNotFoundException
Console.WriteLine(e.Message)
Continue While
Catch e As IOException
Console.WriteLine(e.Message)
Continue While
End Try

' Execute in parallel if there are enough files in the directory.


' Otherwise, execute sequentially.Files are opened and processed
' synchronously but this could be modified to perform async I/O.
Try
If files.Length < procCount Then
For Each file In files
action(file)
fileCount += 1
Next
Else
Parallel.ForEach(files, Function() 0, Function(file, loopState, localCount)
action(file)
localCount = localCount + 1
Return localCount
End Function,
Sub(c)
Interlocked.Add(fileCount, c)
End Sub)
End If
Catch ae As AggregateException
ae.Handle(Function(ex)

If TypeOf (ex) Is UnauthorizedAccessException Then


' Here we just output a message and go on.
Console.WriteLine(ex.Message)
Return True
End If
' Handle other exceptions here if necessary...

Return False
End Function)
End Try
' Push the subdirectories onto the stack for traversal.
' This could also be done before handing the files.
For Each str As String In subDirs
dirs.Push(str)
Next

' For diagnostic purposes.


Console.WriteLine("Processed {0} files in {1} milliseconds", fileCount, sw.ElapsedMilliseconds)
End While
End Sub
End Module

Dans cet exemple, les E/S des fichiers sont effectuées de façon synchrone. En cas de fichiers volumineux ou de
connexions réseau lentes, il peut être préférable d’accéder aux fichiers de façon asynchrone. Vous pouvez combiner
des techniques d’E/S asynchrones avec l’itération parallèle. Pour plus d’informations, consultez Bibliothèque
parallèle de tâches (TPL) et programmation asynchrone.
L’exemple utilise la variable fileCount locale pour maintenir un décompte du nombre total de fichiers traités. Étant
donné que plusieurs tâches sont susceptibles d’y accéder simultanément, la méthode Interlocked.Add est utilisée
pour synchroniser les accès.
Sachez que, si une exception est levée sur le thread principal, les threads lancés par la méthode ForEach risquent de
continuer à s’exécuter. Pour les arrêter, vous pouvez définir une variable booléenne dans vos gestionnaires
d’exceptions et vérifier sa valeur à chaque itération de la boucle parallèle. Si la valeur indique qu’une exception a
été levée, utilisez la variable ParallelLoopState pour arrêter ou sortir de la boucle. Pour plus d’informations,
consultez la page Guide pratique : arrêter ou sortir d’une boucle Parallel.For.

Voir aussi
Parallélisme des données
Programmation asynchrone basée sur les tâches
18/07/2020 • 59 minutes to read • Edit Online

La bibliothèque parallèle de tâches (TPL) est basée sur le concept de tâche, qui représente une opération
asynchrone. À certains égards, une tâche ressemble à un thread ou à un élément de travail ThreadPool, mais à un
niveau d’abstraction supérieur. Le terme parallélisme des tâches fait référence à une ou plusieurs tâches
indépendantes qui s’exécutent simultanément. Les tâches présentent deux grands avantages :
Une utilisation plus efficace et évolutive des ressources système.
En arrière-plan, les tâches sont mises en file d’attente dans le ThreadPool, amélioré au moyen d’algorithmes
qui déterminent le nombre de threads (et s’y ajustent) et qui fournissent l’équilibrage de charge afin
d’optimiser le débit. Cela rend les tâches relativement simples et vous permet d’en créer de nombreuses
pour un parallélisme affiné.
Davantage de contrôle par programmation qu'avec un thread ou un élément de travail.
Les tâches et l'infrastructure construites autour de ces algorithmes fournissent un ensemble riche d'API
prenant en charge l'attente, l'annulation, les continuations, la gestion fiable des exceptions, l'état détaillé, la
planification personnalisée, et bien plus encore.
Pour ces raisons, la bibliothèque parallèle de tâches est l'API privilégiée pour l'écriture du code multithread,
asynchrone et parallèle dans .NET Framework.

Création et exécution implicites de tâches


La méthode Parallel.Invoke offre un moyen pratique d'exécuter simultanément un nombre d'instructions
arbitraires. Pour cela, passez un délégué Action pour chaque élément de travail. La façon la plus facile de créer ces
délégués est d’utiliser des expressions lambda. L’expression lambda peut appeler une méthode nommée ou
fournir le code inline. L'exemple suivant montre un appel Invoke de base qui crée et démarre deux tâches qui
s'exécutent simultanément. La première tâche est représentée par une expression lambda qui appelle une
méthode nommée DoSomeWork et la seconde tâche est représentée par une expression lambda qui appelle une
méthode nommée DoSomeOtherWork .

NOTE
Cette documentation utilise les expressions lambda pour définir les délégués de la bibliothèque parallèle de tâches. Si les
expressions lambda en C# ou Visual Basic ne vous sont pas familières, consultez la page Expressions lambda en PLINQ et
dans la bibliothèque parallèle de tâches.

Parallel.Invoke(() => DoSomeWork(), () => DoSomeOtherWork());

Parallel.Invoke(Sub() DoSomeWork(), Sub() DoSomeOtherWork())

NOTE
Le nombre d'instances Task créées en arrière-plan par Invoke n'est pas nécessairement égal au nombre de délégués fournis.
La bibliothèque parallèle de tâches peut utiliser différentes optimisations, surtout avec un grand nombre de délégués.
Pour plus d’informations, consultez Comment : utiliser parallel_invoke pour exécuter des opérations parallèles.
Pour un plus grand contrôle de l’exécution de tâches ou pour retourner une valeur à partir de la tâche, vous devez
utiliser les objets Task de manière plus explicite.

Création et exécution explicites de tâches


Une tâche qui ne retourne pas de valeur est représentée par la classe System.Threading.Tasks.Task. Une tâche qui
retourne une valeur est représentée par la classe System.Threading.Tasks.Task<TResult>, qui hérite de Task. L’objet
de tâche gère les détails de l’infrastructure et fournit des méthodes et des propriétés accessibles depuis le thread
appelant pendant la durée de vie de la tâche. Par exemple, vous pouvez accéder à la propriété Status d'une tâche à
tout moment pour déterminer si son exécution a commencé, est terminée, a été annulée ou a levé une exception.
Le statut est représenté par une énumération TaskStatus.
Lorsque vous créez une tâche, vous lui donnez un délégué utilisateur qui encapsule le code que la tâche exécutera.
Le délégué peut être exprimé en tant que délégué nommé, méthode anonyme ou expression lambda. Les
expressions lambda peuvent contenir un appel à une méthode nommée, comme indiqué dans l’exemple suivant.
Notez que l’exemple inclut un appel à la méthode Task.Wait pour garantir que l’exécution de la tâche se termine
avant la fin de l’application en mode console.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
Thread.CurrentThread.Name = "Main";

// Create a task and supply a user delegate by using a lambda expression.


Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));
// Start the task.
taskA.Start();

// Output a message from the calling thread.


Console.WriteLine("Hello from thread '{0}'.",
Thread.CurrentThread.Name);
taskA.Wait();
}
}
// The example displays output like the following:
// Hello from thread 'Main'.
// Hello from taskA.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Thread.CurrentThread.Name = "Main"

' Create a task and supply a user delegate by using a lambda expression.
Dim taskA = New Task(Sub() Console.WriteLine("Hello from taskA."))
' Start the task.
taskA.Start()

' Output a message from the calling thread.


Console.WriteLine("Hello from thread '{0}'.",
Thread.CurrentThread.Name)
taskA.Wait()
End Sub
End Module
' The example displays output like the following:
' Hello from thread 'Main'.
' Hello from taskA.

Vous pouvez également utiliser les méthodes Task.Run pour créer et lancer une tâche dans une opération. Pour
gérer la tâche, les méthodes Run utilisent le planificateur de tâches par défaut, quel que soit le planificateur de
tâches associé au thread actuel. Les méthodes Run représentent la meilleure façon de créer et de lancer des tâches
lorsqu’un plus grand contrôle sur la création et la planification de la tâche n’est pas nécessaire.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
Thread.CurrentThread.Name = "Main";

// Define and run the task.


Task taskA = Task.Run( () => Console.WriteLine("Hello from taskA."));

// Output a message from the calling thread.


Console.WriteLine("Hello from thread '{0}'.",
Thread.CurrentThread.Name);
taskA.Wait();
}
}
// The example displays output like the following:
// Hello from thread 'Main'.
// Hello from taskA.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Thread.CurrentThread.Name = "Main"

Dim taskA As Task = Task.Run(Sub() Console.WriteLine("Hello from taskA."))

' Output a message from the calling thread.


Console.WriteLine("Hello from thread '{0}'.",
Thread.CurrentThread.Name)
taskA.Wait()
End Sub
End Module
' The example displays output like the following:
' Hello from thread 'Main'.
' Hello from taskA.

Vous pouvez également utiliser la méthode TaskFactory.StartNew pour créer et lancer une tâche dans une
opération. Utilisez cette méthode quand la création et la planification n’ont pas besoin d’être séparées et que vous
avez besoin d’options supplémentaires de création de tâches ou d’utiliser un planificateur spécifique, ou lorsque
vous devez obtenir l’état supplémentaire dans la tâche via sa propriété Task.AsyncState, comme indiqué dans
l’exemple suivant.
using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}

public class Example


{
public static void Main()
{
Task[] taskArray = new Task[10];
for (int i = 0; i < taskArray.Length; i++) {
taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
CustomData data = obj as CustomData;
if (data == null)
return;

data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
},
new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks}
);
}
Task.WaitAll(taskArray);
foreach (var task in taskArray) {
var data = task.AsyncState as CustomData;
if (data != null)
Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
data.Name, data.CreationTime, data.ThreadNum);
}
}
}
// The example displays output like the following:
// Task #0 created at 635116412924597583 on thread #3.
// Task #1 created at 635116412924607584 on thread #4.
// Task #3 created at 635116412924607584 on thread #4.
// Task #4 created at 635116412924607584 on thread #4.
// Task #2 created at 635116412924607584 on thread #3.
// Task #6 created at 635116412924607584 on thread #3.
// Task #5 created at 635116412924607584 on thread #4.
// Task #8 created at 635116412924607584 on thread #4.
// Task #7 created at 635116412924607584 on thread #3.
// Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
Public CreationTime As Long
Public Name As Integer
Public ThreadNum As Integer
End Class

Module Example
Public Sub Main()
Dim taskArray(9) As Task
For i As Integer = 0 To taskArray.Length - 1
taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
Dim data As CustomData = TryCast(obj, CustomData)
If data Is Nothing Then Return

data.ThreadNum = Thread.CurrentThread.ManagedThreadId
End Sub,
New CustomData With {.Name = i, .CreationTime =
DateTime.Now.Ticks})
Next
Task.WaitAll(taskArray)

For Each task In taskArray


Dim data = TryCast(task.AsyncState, CustomData)
If data IsNot Nothing Then
Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
data.Name, data.CreationTime, data.ThreadNum)
End If
Next
End Sub
End Module
' The example displays output like the following:
' Task #0 created at 635116451245250515, ran on thread #3, RanToCompletion
' Task #1 created at 635116451245270515, ran on thread #4, RanToCompletion
' Task #2 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #3 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #4 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #5 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #6 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #7 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #8 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #9 created at 635116451245270515, ran on thread #3, RanToCompletion

Task et Task<TResult> exposent tous deux une propriété Factory statique qui retourne une instance par défaut de
TaskFactory, afin que vous puissiez appeler la méthode en tant que Task.Factory.StartNew() . De plus, dans
l’exemple suivant, parce que les tâches sont de type System.Threading.Tasks.Task<TResult>, chacune d’elle a une
propriété Task<TResult>.Result publique contenant le résultat du calcul. Les tâches sont exécutées de façon
asynchrone et peuvent se terminer dans n’importe quel ordre. Si la propriété Result est accessible avant la fin du
calcul, la propriété bloque le thread appelant jusqu'à ce que la valeur soit disponible.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
Task<Double>[] taskArray = { Task<Double>.Factory.StartNew(() => DoComputation(1.0)),
Task<Double>.Factory.StartNew(() => DoComputation(100.0)),
Task<Double>.Factory.StartNew(() => DoComputation(1000.0)) };

var results = new Double[taskArray.Length];


Double sum = 0;

for (int i = 0; i < taskArray.Length; i++) {


results[i] = taskArray[i].Result;
Console.Write("{0:N1} {1}", results[i],
i == taskArray.Length - 1 ? "= " : "+ ");
sum += results[i];
}
Console.WriteLine("{0:N1}", sum);
}

private static Double DoComputation(Double start)


{
Double sum = 0;
for (var value = start; value <= start + 10; value += .1)
sum += value;

return sum;
}
}
// The example displays the following output:
// 606.0 + 10,605.0 + 100,495.0 = 111,706.0

Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim taskArray() = {Task(Of Double).Factory.StartNew(Function() DoComputation(1.0)),
Task(Of Double).Factory.StartNew(Function() DoComputation(100.0)),
Task(Of Double).Factory.StartNew(Function() DoComputation(1000.0))}

Dim results(taskArray.Length - 1) As Double


Dim sum As Double

For i As Integer = 0 To taskArray.Length - 1


results(i) = taskArray(i).Result
Console.Write("{0:N1} {1}", results(i),
If(i = taskArray.Length - 1, "= ", "+ "))
sum += results(i)
Next
Console.WriteLine("{0:N1}", sum)
End Sub

Private Function DoComputation(start As Double) As Double


Dim sum As Double
For value As Double = start To start + 10 Step .1
sum += value
Next
Return sum
End Function
End Module
' The example displays the following output:
' 606.0 + 10,605.0 + 100,495.0 = 111,706.0
Pour plus d’informations, consultez Comment : retourner une valeur à partir d’une tâche.
Lorsque vous utilisez une expression lambda pour créer le délégué d’une tâche, vous avez accès à toutes les
variables qui sont visibles à ce stade dans votre code source. Toutefois, dans certains cas, notamment dans les
boucles, une expression lambda ne capture pas la variable comme prévu. Elle capture uniquement la valeur finale,
pas la valeur qui change au cours de chaque itération. L'exemple de code suivant illustre le problème. Il passe un
compteur de boucles à une expression lambda qui instancie un objet CustomData et utilise le compteur de boucles
comme identificateur de l'objet. Comme le montre la sortie de l'exemple, chaque objet CustomData a un
identificateur identique.

using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}

public class Example


{
public static void Main()
{
// Create the task object by using an Action(Of Object) to pass in the loop
// counter. This produces an unexpected result.
Task[] taskArray = new Task[10];
for (int i = 0; i < taskArray.Length; i++) {
taskArray[i] = Task.Factory.StartNew( (Object obj) => {
var data = new CustomData() {Name = i, CreationTime =
DateTime.Now.Ticks};
data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Task #{0} created at {1} on thread #
{2}.",
data.Name, data.CreationTime,
data.ThreadNum);
},
i );
}
Task.WaitAll(taskArray);
}
}
// The example displays output like the following:
// Task #10 created at 635116418427727841 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427737842 on thread #4.
// Task #10 created at 635116418427727841 on thread #3.
// Task #10 created at 635116418427747843 on thread #3.
// Task #10 created at 635116418427747843 on thread #3.
// Task #10 created at 635116418427737842 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
Public CreationTime As Long
Public Name As Integer
Public ThreadNum As Integer
End Class

Module Example
Public Sub Main()
' Create the task object by using an Action(Of Object) to pass in the loop
' counter. This produces an unexpected result.
Dim taskArray(9) As Task
For i As Integer = 0 To taskArray.Length - 1
taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
Dim data As New CustomData With {.Name = i,
.CreationTime = DateTime.Now.Ticks}
data.ThreadNum = Thread.CurrentThread.ManagedThreadId
Console.WriteLine("Task #{0} created at {1} on thread #
{2}.",
data.Name, data.CreationTime,
data.ThreadNum)
End Sub,
i)
Next
Task.WaitAll(taskArray)
End Sub
End Module
' The example displays output like the following:
' Task #10 created at 635116418427727841 on thread #4.
' Task #10 created at 635116418427737842 on thread #4.
' Task #10 created at 635116418427737842 on thread #4.
' Task #10 created at 635116418427737842 on thread #4.
' Task #10 created at 635116418427737842 on thread #4.
' Task #10 created at 635116418427737842 on thread #4.
' Task #10 created at 635116418427727841 on thread #3.
' Task #10 created at 635116418427747843 on thread #3.
' Task #10 created at 635116418427747843 on thread #3.
' Task #10 created at 635116418427737842 on thread #4.

Vous pouvez accéder à la valeur de chaque itération en fournissant un objet d’état à une tâche via son
constructeur. L'exemple suivant modifie l'exemple précédent en utilisant le compteur de boucles lors de la
création de l'objet CustomData qui, à son tour, est passé à l'expression lambda. Comme le montre la sortie de
l'exemple, chaque objet CustomData possède maintenant un identificateur unique basé sur la valeur du compteur
de boucle au moment où l'objet a été instancié.
using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}

public class Example


{
public static void Main()
{
// Create the task object by using an Action(Of Object) to pass in custom data
// to the Task constructor. This is useful when you need to capture outer variables
// from within a loop.
Task[] taskArray = new Task[10];
for (int i = 0; i < taskArray.Length; i++) {
taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
CustomData data = obj as CustomData;
if (data == null)
return;

data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Task #{0} created at {1} on thread #
{2}.",
data.Name, data.CreationTime,
data.ThreadNum);
},
new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks}
);
}
Task.WaitAll(taskArray);
}
}
// The example displays output like the following:
// Task #0 created at 635116412924597583 on thread #3.
// Task #1 created at 635116412924607584 on thread #4.
// Task #3 created at 635116412924607584 on thread #4.
// Task #4 created at 635116412924607584 on thread #4.
// Task #2 created at 635116412924607584 on thread #3.
// Task #6 created at 635116412924607584 on thread #3.
// Task #5 created at 635116412924607584 on thread #4.
// Task #8 created at 635116412924607584 on thread #4.
// Task #7 created at 635116412924607584 on thread #3.
// Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
Public CreationTime As Long
Public Name As Integer
Public ThreadNum As Integer
End Class

Module Example
Public Sub Main()
' Create the task object by using an Action(Of Object) to pass in custom data
' to the Task constructor. This is useful when you need to capture outer variables
' from within a loop.
Dim taskArray(9) As Task
For i As Integer = 0 To taskArray.Length - 1
taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
Dim data As CustomData = TryCast(obj, CustomData)
If data Is Nothing Then Return

data.ThreadNum = Thread.CurrentThread.ManagedThreadId
Console.WriteLine("Task #{0} created at {1} on thread #
{2}.",
data.Name, data.CreationTime,
data.ThreadNum)
End Sub,
New CustomData With {.Name = i, .CreationTime =
DateTime.Now.Ticks})
Next
Task.WaitAll(taskArray)
End Sub
End Module
' The example displays output like the following:
' Task #0 created at 635116412924597583 on thread #3.
' Task #1 created at 635116412924607584 on thread #4.
' Task #3 created at 635116412924607584 on thread #4.
' Task #4 created at 635116412924607584 on thread #4.
' Task #2 created at 635116412924607584 on thread #3.
' Task #6 created at 635116412924607584 on thread #3.
' Task #5 created at 635116412924607584 on thread #4.
' Task #8 created at 635116412924607584 on thread #4.
' Task #7 created at 635116412924607584 on thread #3.
' Task #9 created at 635116412924607584 on thread #4.

Cet état est passé comme argument au délégué de tâche et est accessible dans l'objet de tâche à l'aide de la
propriété Task.AsyncState. L'exemple suivant est une variante de l'exemple précédent. Il utilise la propriété
AsyncState pour afficher des informations sur les objets CustomData passés à l’expression lambda.
using System;
using System.Threading;
using System.Threading.Tasks;

class CustomData
{
public long CreationTime;
public int Name;
public int ThreadNum;
}

public class Example


{
public static void Main()
{
Task[] taskArray = new Task[10];
for (int i = 0; i < taskArray.Length; i++) {
taskArray[i] = Task.Factory.StartNew( (Object obj ) => {
CustomData data = obj as CustomData;
if (data == null)
return;

data.ThreadNum = Thread.CurrentThread.ManagedThreadId;
},
new CustomData() {Name = i, CreationTime = DateTime.Now.Ticks}
);
}
Task.WaitAll(taskArray);
foreach (var task in taskArray) {
var data = task.AsyncState as CustomData;
if (data != null)
Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
data.Name, data.CreationTime, data.ThreadNum);
}
}
}
// The example displays output like the following:
// Task #0 created at 635116412924597583 on thread #3.
// Task #1 created at 635116412924607584 on thread #4.
// Task #3 created at 635116412924607584 on thread #4.
// Task #4 created at 635116412924607584 on thread #4.
// Task #2 created at 635116412924607584 on thread #3.
// Task #6 created at 635116412924607584 on thread #3.
// Task #5 created at 635116412924607584 on thread #4.
// Task #8 created at 635116412924607584 on thread #4.
// Task #7 created at 635116412924607584 on thread #3.
// Task #9 created at 635116412924607584 on thread #4.
Imports System.Threading
Imports System.Threading.Tasks

Class CustomData
Public CreationTime As Long
Public Name As Integer
Public ThreadNum As Integer
End Class

Module Example
Public Sub Main()
Dim taskArray(9) As Task
For i As Integer = 0 To taskArray.Length - 1
taskArray(i) = Task.Factory.StartNew(Sub(obj As Object)
Dim data As CustomData = TryCast(obj, CustomData)
If data Is Nothing Then Return

data.ThreadNum = Thread.CurrentThread.ManagedThreadId
End Sub,
New CustomData With {.Name = i, .CreationTime =
DateTime.Now.Ticks})
Next
Task.WaitAll(taskArray)

For Each task In taskArray


Dim data = TryCast(task.AsyncState, CustomData)
If data IsNot Nothing Then
Console.WriteLine("Task #{0} created at {1}, ran on thread #{2}.",
data.Name, data.CreationTime, data.ThreadNum)
End If
Next
End Sub
End Module
' The example displays output like the following:
' Task #0 created at 635116451245250515, ran on thread #3, RanToCompletion
' Task #1 created at 635116451245270515, ran on thread #4, RanToCompletion
' Task #2 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #3 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #4 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #5 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #6 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #7 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #8 created at 635116451245270515, ran on thread #3, RanToCompletion
' Task #9 created at 635116451245270515, ran on thread #3, RanToCompletion

ID de la tâche
Chaque tâche reçoit un ID d'entier qui l'identifie de façon unique dans un domaine d'application et peut être
accessible à l'aide de la propriété Task.Id. L’ID est utile pour la consultation des informations de tâche dans les
fenêtres Piles parallèles et Tâches du débogueur Visual Studio. L’ID est créé de manière différée, ce qui signifie
qu’il n’est pas créé tant que cela n’est pas demandé ; par conséquent, une tâche peut avoir un ID différent à chaque
exécution du programme. Pour plus d’informations sur l’affichage des ID de tâche dans le débogueur, consultez
Utilisation de la fenêtre Tâches et Utilisation de la fenêtre Piles parallèles.

Options de création de tâches


La plupart des API qui créent des tâches fournissent des surcharges qui acceptent un paramètre
TaskCreationOptions. En spécifiant l’une de ces options, vous indiquez au planificateur de tâches la manière de
planifier la tâche dans le pool de threads. Le tableau suivant répertorie les différentes options de création de
tâches.
VA L EUR DU PA RA M ÈT RE TA SKC REAT IO N O P T IO N S DESC RIP T IO N

None Valeur par défaut lorsqu'aucune option n'est spécifiée. Le


planificateur utilise ses méthodes heuristiques par défaut
pour planifier la tâche.

PreferFairness Spécifie que la tâche doit être planifiée afin que les tâches
créées précédemment soient susceptibles d’être exécutées
plus tôt et que les tâches créées ultérieurement soient
susceptibles d’être exécutées plus tard.

LongRunning Spécifie que la tâche représente une opération de longue


durée.

AttachedToParent Spécifie qu’une tâche doit être créée en tant qu’enfant attaché
à la tâche actuelle, s’il en existe une. Pour plus d'informations,
consultez Tâches enfants attachées et détachées.

DenyChildAttach Indique que si une tâche interne spécifie l'option


AttachedToParent , cette tâche ne sera pas une tâche enfant
attachée.

HideScheduler Spécifie que le planificateur de tâches pour les tâches créées


en appelant des méthodes comme TaskFactory.StartNew ou
Task<TResult>.ContinueWith à partir d'une tâche particulière
est le planificateur par défaut au lieu du planificateur sur
lequel cette tâche s'exécute.

Les options peuvent être combinées avec une opération de bits OR . L’exemple suivant montre une tâche avec les
options LongRunning et PreferFairness.

var task3 = new Task(() => MyLongRunningMethod(),


TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness);
task3.Start();

Dim task3 = New Task(Sub() MyLongRunningMethod(),


TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
task3.Start()

Tâches, threads et culture


Chaque thread possède une culture associée et une culture d'interface utilisateur, respectivement définies par les
propriétés Thread.CurrentCulture et Thread.CurrentUICulture. La culture d'un thread est utilisée dans des
opérations telles que la mise en forme, l'analyse, le tri et la comparaison de chaînes. La culture d'interface
utilisateur d'un thread est utilisée dans la recherche de ressources. En règle générale, sauf si vous spécifiez une
culture par défaut pour tous les threads dans un domaine d'application à l'aide des propriétés
CultureInfo.DefaultThreadCurrentCulture et CultureInfo.DefaultThreadCurrentUICulture, la culture par défaut et la
culture d'interface utilisateur d'un thread sont définies par la culture du système. Si vous définissez explicitement
la culture d'un thread et lancez un nouveau thread, ce dernier n'hérite pas de la culture du thread appelant ; au
lieu de cela, sa culture est la culture du système par défaut. Le modèle de programmation basé sur les tâches pour
les applications qui ciblent des versions du .NET Framework antérieures à .NET Framework 4.6 respecte cette
pratique.
IMPORTANT
Notez que la culture du thread appelant dans le cadre du contexte d’une tâche s’applique aux applications qui ciblent .NET
Framework 4.6, et non celles qui s’exécutent sous .NET Framework 4.6. Vous pouvez cibler une version particulière du .NET
Framework quand vous créez votre projet dans Visual Studio en sélectionnant une version dans la liste déroulante située en
haut de la boîte de dialogue Nouveau projet . Sinon, en dehors de Visual Studio, vous pouvez utiliser l’attribut
TargetFrameworkAttribute. Pour les applications qui ciblent des versions du .NET Framework antérieures à .NET
Framework 4.6, ou qui ne ciblent pas une version spécifique du .NET Framework, la culture d’une tâche demeure déterminée
par la culture du thread sur lequel la tâche s’exécute.

À compter des applications qui ciblent .NET Framework 4.6, la culture du thread appelant est héritée par chaque
tâche, même si la tâche s’exécute de façon asynchrone sur un thread de pool de threads.
L'exemple suivant illustre cette situation de façon simple. Il utilise l’attribut TargetFrameworkAttribute pour cibler
.NET Framework 4.6 et définit la culture actuelle de l’application sur Français (France) ou, si Français (France) est
déjà la culture actuelle, sur Anglais (États-Unis). Il appelle ensuite un délégué nommé formatDelegate qui
retourne des nombres mis en forme en tant que valeurs de devise dans la nouvelle culture. Que le délégué
exécute une tâche de manière synchrone ou asynchrone, il retourne le résultat attendu, car la culture du thread
appelant est héritée par la tâche asynchrone.

using System;
using System.Globalization;
using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;

[assembly:TargetFramework(".NETFramework,Version=v4.6")]

public class Example


{

public static void Main()


{
decimal[] values = { 163025412.32m, 18905365.59m };
string formatString = "C2";
Func<String> formatDelegate = () => { string output = String.Format("Formatting using the {0} culture
on thread {1}.\n",
CultureInfo.CurrentCulture.Name,

Thread.CurrentThread.ManagedThreadId);
foreach (var value in values)
output += String.Format("{0} ",
value.ToString(formatString));

output += Environment.NewLine;
return output;
};

Console.WriteLine("The example is running on thread {0}",


Thread.CurrentThread.ManagedThreadId);
// Make the current culture different from the system culture.
Console.WriteLine("The current culture is {0}",
CultureInfo.CurrentCulture.Name);
if (CultureInfo.CurrentCulture.Name == "fr-FR")
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
else
Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");

Console.WriteLine("Changed the current culture to {0}.\n",


CultureInfo.CurrentCulture.Name);

// Execute the delegate synchronously.


Console.WriteLine("Executing the delegate synchronously:");
Console.WriteLine("Executing the delegate synchronously:");
Console.WriteLine(formatDelegate());

// Call an async delegate to format the values using one format string.
Console.WriteLine("Executing a task asynchronously:");
var t1 = Task.Run(formatDelegate);
Console.WriteLine(t1.Result);

Console.WriteLine("Executing a task synchronously:");


var t2 = new Task<String>(formatDelegate);
t2.RunSynchronously();
Console.WriteLine(t2.Result);
}
}
// The example displays the following output:
// The example is running on thread 1
// The current culture is en-US
// Changed the current culture to fr-FR.
//
// Executing the delegate synchronously:
// Formatting using the fr-FR culture on thread 1.
// 163 025 412,32 € 18 905 365,59 €
//
// Executing a task asynchronously:
// Formatting using the fr-FR culture on thread 3.
// 163 025 412,32 € 18 905 365,59 €
//
// Executing a task synchronously:
// Formatting using the fr-FR culture on thread 1.
// 163 025 412,32 € 18 905 365,59 €
// If the TargetFrameworkAttribute statement is removed, the example
// displays the following output:
// The example is running on thread 1
// The current culture is en-US
// Changed the current culture to fr-FR.
//
// Executing the delegate synchronously:
// Formatting using the fr-FR culture on thread 1.
// 163 025 412,32 € 18 905 365,59 €
//
// Executing a task asynchronously:
// Formatting using the en-US culture on thread 3.
// $163,025,412.32 $18,905,365.59
//
// Executing a task synchronously:
// Formatting using the fr-FR culture on thread 1.
// 163 025 412,32 € 18 905 365,59 €

Imports System.Globalization
Imports System.Runtime.Versioning
Imports System.Threading
Imports System.Threading.Tasks

<Assembly: TargetFramework(".NETFramework,Version=v4.6")>

Module Example
Public Sub Main()
Dim values() As Decimal = {163025412.32d, 18905365.59d}
Dim formatString As String = "C2"
Dim formatDelegate As Func(Of String) = Function()
Dim output As String = String.Format("Formatting using
the {0} culture on thread {1}.",

CultureInfo.CurrentCulture.Name,

Thread.CurrentThread.ManagedThreadId)
output += Environment.NewLine
For Each value In values
For Each value In values
output += String.Format("{0} ",
value.ToString(formatString))
Next
output += Environment.NewLine
Return output
End Function

Console.WriteLine("The example is running on thread {0}",


Thread.CurrentThread.ManagedThreadId)
' Make the current culture different from the system culture.
Console.WriteLine("The current culture is {0}",
CultureInfo.CurrentCulture.Name)
If CultureInfo.CurrentCulture.Name = "fr-FR" Then
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US")
Else
Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR")
End If
Console.WriteLine("Changed the current culture to {0}.",
CultureInfo.CurrentCulture.Name)
Console.WriteLine()

' Execute the delegate synchronously.


Console.WriteLine("Executing the delegate synchronously:")
Console.WriteLine(formatDelegate())

' Call an async delegate to format the values using one format string.
Console.WriteLine("Executing a task asynchronously:")
Dim t1 = Task.Run(formatDelegate)
Console.WriteLine(t1.Result)

Console.WriteLine("Executing a task synchronously:")


Dim t2 = New Task(Of String)(formatDelegate)
t2.RunSynchronously()
Console.WriteLine(t2.Result)
End Sub
End Module
' The example displays the following output:
' The example is running on thread 1
' The current culture is en-US
' Changed the current culture to fr-FR.
'
' Executing the delegate synchronously:
' Formatting Imports the fr-FR culture on thread 1.
' 163 025 412,32 € 18 905 365,59 €
'
' Executing a task asynchronously:
' Formatting Imports the fr-FR culture on thread 3.
' 163 025 412,32 € 18 905 365,59 €
'
' Executing a task synchronously:
' Formatting Imports the fr-FR culture on thread 1.
' 163 025 412,32 € 18 905 365,59 €
' If the TargetFrameworkAttribute statement is removed, the example
' displays the following output:
' The example is running on thread 1
' The current culture is en-US
' Changed the current culture to fr-FR.
'
' Executing the delegate synchronously:
' Formatting using the fr-FR culture on thread 1.
' 163 025 412,32 € 18 905 365,59 €
'
' Executing a task asynchronously:
' Formatting using the en-US culture on thread 3.
' $163,025,412.32 $18,905,365.59
'
' Executing a task synchronously:
' Formatting using the fr-FR culture on thread 1.
' 163 025 412,32 € 18 905 365,59 €
' 163 025 412,32 € 18 905 365,59 €

Si vous utilisez Visual Studio, vous pouvez omettre l'attribut TargetFrameworkAttribute. Pour cela, vous devez
sélectionner le .NET Framework 4.6 comme cible au moment de la création du projet dans la boîte de dialogue
Nouveau projet .
Pour la sortie qui reflète le comportement des applications ciblant des versions du .NET Framework antérieures à
.NET Framework 4.6, supprimez l’attribut TargetFrameworkAttribute du code source. La sortie reflète les
conventions de mise en forme de la culture du système par défaut, pas de la culture du thread appelant.
Pour plus d'informations sur les tâches asynchrones et la culture, consultez la section « Culture et opérations
asynchrones basées sur les tâches » dans la rubrique CultureInfo.

Création de continuations de tâches


Les méthodes Task.ContinueWith et Task<TResult>.ContinueWith vous permettent de spécifier une tâche à
démarrer lorsque l’antécédent est terminé. Une référence à la tâche antécédente est passée au délégué de la tâche
de continuation afin qu’il puisse examiner l’état de la tâche antécédente et, en récupérant la valeur de la propriété
Task<TResult>.Result, utiliser la sortie de l’antécédent comme entrée pour la continuation.
Dans l'exemple suivant, la tâche getData est démarrée par un appel à la méthode TaskFactory.StartNew<TResult>
(Func<TResult>). La tâche processData démarre automatiquement lorsque getData est terminé, et displayData
commence lorsque processData est terminé. getData produit un tableau d'entiers, accessible à la tâche
processData via la propriété getData de la tâche Task<TResult>.Result. La tâche processData traite ce tableau et
retourne un résultat dont le type est déduit du type de retour de l'expression lambda passée à la méthode
Task<TResult>.ContinueWith<TNewResult>(Func<Task<TResult>,TNewResult>). La tâche displayData s'exécute
automatiquement lorsque processData est terminé, et l'objet Tuple<T1,T2,T3> retourné par l'expression lambda
processData est accessible à la tâche displayData via la propriété processData de la tâche Task<TResult>.Result.
La tâche displayData prend le résultat de la tâche processData et produit un résultat dont le type est déduit de
façon semblable et mis à disposition du programme dans la propriété Result.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var getData = Task.Factory.StartNew(() => {
Random rnd = new Random();
int[] values = new int[100];
for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
values[ctr] = rnd.Next();

return values;
} );
var processData = getData.ContinueWith((x) => {
int n = x.Result.Length;
long sum = 0;
double mean;

for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)


sum += x.Result[ctr];

mean = sum / (double) n;


return Tuple.Create(n, sum, mean);
} );
var displayData = processData.ContinueWith((x) => {
return String.Format("N={0:N0}, Total = {1:N0}, Mean =
{2:N2}",
x.Result.Item1, x.Result.Item2,
x.Result.Item3);
} );
Console.WriteLine(displayData.Result);
}
}
// The example displays output similar to the following:
// N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim getData = Task.Factory.StartNew(Function()
Dim rnd As New Random()
Dim values(99) As Integer
For ctr = 0 To values.GetUpperBound(0)
values(ctr) = rnd.Next()
Next
Return values
End Function)
Dim processData = getData.ContinueWith(Function(x)
Dim n As Integer = x.Result.Length
Dim sum As Long
Dim mean As Double

For ctr = 0 To x.Result.GetUpperBound(0)


sum += x.Result(ctr)
Next
mean = sum / n
Return Tuple.Create(n, sum, mean)
End Function)
Dim displayData = processData.ContinueWith(Function(x)
Return String.Format("N={0:N0}, Total = {1:N0}, Mean =
{2:N2}",
x.Result.Item1, x.Result.Item2,
x.Result.Item3)
End Function)
Console.WriteLine(displayData.Result)
End Sub
End Module
' The example displays output like the following:
' N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Comme Task.ContinueWith est une méthode d‘instance, vous pouvez concaténer les appels de méthode ensemble
au lieu d‘instancier un objet Task<TResult> pour chaque antécédent. L'exemple suivant est fonctionnellement
identique à l'exemple précédent, sauf qu'il chaîne ensemble les appels à la méthode Task.ContinueWith. Notez que
l'objet Task<TResult> retourné par la chaîne d'appels de méthode est la tâche de continuation finale.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var displayData = Task.Factory.StartNew(() => {
Random rnd = new Random();
int[] values = new int[100];
for (int ctr = 0; ctr <= values.GetUpperBound(0); ctr++)
values[ctr] = rnd.Next();

return values;
} ).
ContinueWith((x) => {
int n = x.Result.Length;
long sum = 0;
double mean;

for (int ctr = 0; ctr <= x.Result.GetUpperBound(0); ctr++)


sum += x.Result[ctr];

mean = sum / (double) n;


return Tuple.Create(n, sum, mean);
} ).
ContinueWith((x) => {
return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
x.Result.Item1, x.Result.Item2,
x.Result.Item3);
} );
Console.WriteLine(displayData.Result);
}
}
// The example displays output similar to the following:
// N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim displayData = Task.Factory.StartNew(Function()
Dim rnd As New Random()
Dim values(99) As Integer
For ctr = 0 To values.GetUpperBound(0)
values(ctr) = rnd.Next()
Next
Return values
End Function). _
ContinueWith(Function(x)
Dim n As Integer = x.Result.Length
Dim sum As Long
Dim mean As Double

For ctr = 0 To x.Result.GetUpperBound(0)


sum += x.Result(ctr)
Next
mean = sum / n
Return Tuple.Create(n, sum, mean)
End Function). _
ContinueWith(Function(x)
Return String.Format("N={0:N0}, Total = {1:N0}, Mean = {2:N2}",
x.Result.Item1, x.Result.Item2,
x.Result.Item3)
End Function)
Console.WriteLine(displayData.Result)
End Sub
End Module
' The example displays output like the following:
' N=100, Total = 110,081,653,682, Mean = 1,100,816,536.82

Les méthodes ContinueWhenAll et ContinueWhenAny vous permettent de continuer avec plusieurs tâches.
Pour plus d’informations, consultez Chaînage des tâches à l’aide de tâches de continuation.

Création de tâches enfants détachées


Lorsque le code utilisateur qui s'exécute dans une tâche crée une tâche sans spécifier l'option AttachedToParent, la
nouvelle tâche n'est pas synchronisée avec la tâche parent externe. Ce type de tâche non synchronisée est appelé
tâche imbriquée détachée ou tâche enfant détachée. L’exemple suivant montre une tâche qui crée une tâche enfant
détachée.

var outer = Task.Factory.StartNew(() =>


{
Console.WriteLine("Outer task beginning.");

var child = Task.Factory.StartNew(() =>


{
Thread.SpinWait(5000000);
Console.WriteLine("Detached task completed.");
});
});

outer.Wait();
Console.WriteLine("Outer task completed.");
// The example displays the following output:
// Outer task beginning.
// Outer task completed.
// Detached task completed.
Dim outer = Task.Factory.StartNew(Sub()
Console.WriteLine("Outer task beginning.")
Dim child = Task.Factory.StartNew(Sub()
Thread.SpinWait(5000000)
Console.WriteLine("Detached task
completed.")
End Sub)
End Sub)
outer.Wait()
Console.WriteLine("Outer task completed.")
' The example displays the following output:
' Outer task beginning.
' Outer task completed.
' Detached child completed.

Notez que la tâche parent n’attend pas que la tâche enfant détachée soit terminée.

Création de tâches enfants


Lorsque le code utilisateur qui s’exécute dans une tâche crée une tâche avec l’option AttachedToParent, la nouvelle
tâche est une tâche enfant attachée de la tâche d’origine, appelée tâche parent. Vous pouvez utiliser l’option
AttachedToParent pour exprimer le parallélisme des tâches structuré, car la tâche parent attend implicitement que
toutes les tâches enfants attachées soient terminées. L’exemple suivant affiche une tâche parent qui crée dix tâches
enfants attachées. Notez que l'exemple appelle la méthode Task.Wait pour attendre la fin de la tâche parente, il ne
doit pas explicitement attendre la fin des tâches enfants attachées.
using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Parent task beginning.");
for (int ctr = 0; ctr < 10; ctr++) {
int taskNo = ctr;
Task.Factory.StartNew((x) => {
Thread.SpinWait(5000000);
Console.WriteLine("Attached child #{0} completed.",
x);
},
taskNo, TaskCreationOptions.AttachedToParent);
}
});

parent.Wait();
Console.WriteLine("Parent task completed.");
}
}
// The example displays output like the following:
// Parent task beginning.
// Attached child #9 completed.
// Attached child #0 completed.
// Attached child #8 completed.
// Attached child #1 completed.
// Attached child #7 completed.
// Attached child #2 completed.
// Attached child #6 completed.
// Attached child #3 completed.
// Attached child #5 completed.
// Attached child #4 completed.
// Parent task completed.
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Parent task beginning.")
For ctr As Integer = 0 To 9
Dim taskNo As Integer = ctr
Task.Factory.StartNew(Sub(x)
Thread.SpinWait(5000000)
Console.WriteLine("Attached
child #{0} completed.",
x)
End Sub,
taskNo,
TaskCreationOptions.AttachedToParent)
Next
End Sub)
parent.Wait()
Console.WriteLine("Parent task completed.")
End Sub
End Module
' The example displays output like the following:
' Parent task beginning.
' Attached child #9 completed.
' Attached child #0 completed.
' Attached child #8 completed.
' Attached child #1 completed.
' Attached child #7 completed.
' Attached child #2 completed.
' Attached child #6 completed.
' Attached child #3 completed.
' Attached child #5 completed.
' Attached child #4 completed.
' Parent task completed.

Une tâche parent peut utiliser l’option TaskCreationOptions.DenyChildAttach pour empêcher d’autres tâches de
s’attacher à la tâche parent. Pour plus d'informations, consultez Tâches enfants attachées et détachées.

Attente de fin d’exécution de tâches


Les types System.Threading.Tasks.Task et System.Threading.Tasks.Task<TResult> fournissent plusieurs surcharges
des méthodes Task.Wait qui vous permettent d’attendre qu’une tâche soit terminée. De plus, les surcharges des
méthodes statiques Task.WaitAll et Task.WaitAny vous permettent d’attendre que certaines ou toutes les tâches
soient terminées.
En général, il est nécessaire d’attendre une tâche pour l’une des raisons suivantes :
Le thread principal dépend du résultat final calculé par une tâche.
Vous devez gérer les exceptions pouvant être levées depuis la tâche.
L'application peut se fermer avant que l'exécution de toutes les tâches ne soit terminée. Par exemple, les
applications de console s'arrêtent dès que l'intégralité du code synchrone dans Main (point d'entrée de
l'application) est exécutée.
L’exemple suivant affiche le modèle de base qui n’implique pas la gestion des exceptions.
Task[] tasks = new Task[3]
{
Task.Factory.StartNew(() => MethodA()),
Task.Factory.StartNew(() => MethodB()),
Task.Factory.StartNew(() => MethodC())
};

//Block until all tasks complete.


Task.WaitAll(tasks);

// Continue on this thread...

Dim tasks() =
{
Task.Factory.StartNew(Sub() MethodA()),
Task.Factory.StartNew(Sub() MethodB()),
Task.Factory.StartNew(Sub() MethodC())
}

' Block until all tasks complete.


Task.WaitAll(tasks)

' Continue on this thread...

Pour obtenir un exemple illustrant la gestion des exceptions, consultez Gestion des exceptions.
Certaines surcharges vous permettent de spécifier un délai d'attente, et d'autres prennent un CancellationToken
supplémentaire en tant que paramètre d'entrée, afin que l'attente puisse être annulée soit par programmation,
soit en réponse à l'entrée utilisateur.
Lorsque vous attendez une tâche, vous attendez implicitement tous les enfants de cette tâche créés à l'aide de
l'option TaskCreationOptions.AttachedToParent. Task.Wait est retourné immédiatement si la tâche est déjà
terminée. Toutes les exceptions déclenchées par une tâche seront levées par une méthode Task.Wait, même si la
méthode Task.Wait a été appelée une fois la tâche terminée.

Composition des tâches


Les classes Task et Task<TResult> fournissent plusieurs méthodes qui peuvent vous aider à composer plusieurs
tâches pour implémenter des modèles courants et améliorer l’utilisation des fonctionnalités de langage
asynchrones fournies par C#, Visual Basic et F#. Cette section décrit les méthodes WhenAll, WhenAny, Delay et
FromResult.
Task.WhenAll
La méthode Task.WhenAll attend de façon asynchrone que plusieurs objets Task ou Task<TResult> se terminent.
Elle fournit des versions surchargées qui vous permettent d’attendre des ensembles de tâches non-uniformes. Par
exemple, vous pouvez attendre que plusieurs objets Task et Task<TResult> soient terminés à partir d'un appel de
méthode.
Task.WhenAny
La méthode Task.WhenAny attend de façon asynchrone qu'un des objets Task ou Task<TResult> se termine. À
l’instar de la méthode Task.WhenAll, cette méthode fournit des versions surchargées qui vous permettent
d’attendre des jeux de tâches non-uniformes. La méthode WhenAny est spécialement utile dans les scénarios
suivants.
Opérations redondantes. Considérez un algorithme ou une opération pouvant être exécutée plusieurs
façons. Vous pouvez utiliser la méthode WhenAny pour sélectionner l'opération qui se termine en premier
et annuler les opérations restantes.
Opérations entrelacées. Vous pouvez démarrer plusieurs opérations qui doivent toutes se terminer et
utiliser la méthode WhenAny pour traiter les résultats à mesure que chaque opération se termine.
Lorsqu’une opération se termine, vous pouvez démarrer une ou plusieurs autres tâches.
Opérations limitées. Vous pouvez utiliser la méthode WhenAny pour étendre le scénario précédent en
limitant le nombre d'opérations simultanées.
Opérations expirées. Vous pouvez utiliser la méthode WhenAny pour sélectionner entre une ou plusieurs
tâches et une tâche qui se termine après une heure spécifique, telle qu’une tâche retournée par la méthode
Delay. La méthode Delay est décrite dans la section suivante.
Task.Delay
La méthode Task.Delay produit un objet Task qui se termine après le délai spécifié. Vous pouvez utiliser cette
méthode pour générer des boucles qui, occasionnellement, sondent les données, introduisent des délais, diffèrent
la gestion des entrées d'utilisateur pendant une durée prédéterminé, et ainsi de suite.
Task(T ).FromResult
En utilisant la méthode Task.FromResult, vous pouvez créer un objet Task<TResult> qui contient un résultat
précalculé. Cette méthode est utile lorsque vous exécutez une opération asynchrone qui retourne un objet
Task<TResult>, et que le résultat de cet objet Task<TResult> est déjà calculé. Pour obtenir un exemple qui utilise
FromResult pour récupérer les résultats des opérations de téléchargement asynchrones qui sont conservées dans
un cache, consultez Procédure : création de tâches précalculées.

Gestion des exceptions des tâches


Lorsqu’une tâche lève une ou plusieurs exceptions, les exceptions sont encapsulées dans une exception
AggregateException. Cette exception est propagée vers le thread joint à la tâche, qui est en général le thread qui
attend la fin de la tâche, ou le thread qui accède à la propriété Result. Ce comportement permet d'appliquer la
stratégie .NET Framework selon laquelle toutes les exceptions non gérées doivent par défaut détruire le processus.
Le code appelant peut gérer les exceptions en utilisant l’un des éléments suivants dans un bloc try / catch :
Méthode Wait
Méthode WaitAll
Méthode WaitAny
La propriété Result
Le thread joint peut également gérer des exceptions en accédant à la propriété Exception avant que la tâche ne
soit récupérée par le garbage collector. En accédant à cette propriété, vous empêchez l'exception non gérée de
déclencher le comportement de propagation de l'exception qui termine le processus lorsque l'objet est finalisé.
Pour plus d’informations sur les exceptions et les tâches, consultez Gestion des exceptions.

Annulation des tâches


La classe Task prend en charge l'annulation coopérative et s'intègre pleinement aux classes
System.Threading.CancellationTokenSource et System.Threading.CancellationToken, qui ont été introduites dans
.NET Framework 4. De nombreux constructeurs de la classe System.Threading.Tasks.Task prennent un objet
CancellationToken en tant que paramètre d'entrée. La plupart des surcharges StartNew et Run incluent également
un paramètre CancellationToken.
Vous pouvez créer le jeton et la requête d'annulation ultérieurement, à l'aide de la classe
CancellationTokenSource. Passez le jeton au Task en tant qu'argument et référencez ce même jeton dans votre
délégué d'utilisateur, qui répond à une requête d'annulation.
Pour plus d’informations, consultez Annulation de tâches et Comment : annuler une tâche et ses enfants.

Classe TaskFactory
La classe TaskFactory fournit des méthodes statiques qui encapsulent des modèles communs pour la création et le
lancement des tâches et des tâches de continuation.
Le modèle le plus commun est StartNew, qui crée et lance une tâche dans une instruction.
Lorsque vous créez des tâches de continuation à partir de plusieurs antécédents, utilisez la méthode
ContinueWhenAll ou ContinueWhenAny, ou leurs équivalents de la classe Task<TResult>. Pour plus
d’informations, consultez Chaînage des tâches à l’aide de tâches de continuation.
Pour encapsuler le modèle de programmation asynchrone BeginX et des méthodes EndX dans une
instance Task ou Task<TResult>, utilisez les méthodes FromAsync. Pour plus d’informations, consultez
Bibliothèque parallèle de tâches (TPL) et programmation asynchrone.
TaskFactory par défaut est accessible en tant que propriété statique dans la classe Task ou Task<TResult>. Vous
pouvez également instancier directement un TaskFactory et spécifier différentes options qui incluent une option
CancellationToken, TaskCreationOptions, TaskContinuationOptions ou TaskScheduler. Toutes les options spécifiées
lors de la création de la fabrique de tâches seront appliquées à toutes les tâches qu'elle crée, à moins que la tâche
Task ne soit créée à l'aide de l'énumération TaskCreationOptions, auquel cas les options de la tâche remplacent
celles de la fabrique de tâches.

Tâches sans délégués


Dans certains cas, vous pouvez utiliser un Task pour encapsuler une opération asynchrone exécutée par un
composant externe au lieu de votre propre délégué utilisateur. Si l’opération est basée sur le modèle de
programmation asynchrone Begin/End, vous pouvez utiliser les méthodes FromAsync. Si ce n’est pas le cas, vous
pouvez utiliser l’objet TaskCompletionSource<TResult> pour encapsuler l’opération dans une tâche et, de cette
façon, bénéficier de certains des avantages de programmabilité Task, comme par exemple, la prise en charge de la
propagation et des continuations d’exceptions. Pour plus d’informations, consultez
TaskCompletionSource<TResult>.

Planificateurs personnalisés
La plupart des développeurs d'applications ou de bibliothèques ne se soucient pas du processeur sur lequel
s'exécute la tâche, ni de la manière dont il synchronise son travail avec d'autres tâches ou de la façon dont il est
planifié sur le System.Threading.ThreadPool. Ils demandent simplement à ce qu'il s'exécute aussi efficacement que
possible sur l'ordinateur hôte. Si vous avez besoin d’un contrôle plus affiné sur les détails de la planification, la
bibliothèque parallèle de tâches vous permet de configurer des paramètres dans le planificateur de tâches par
défaut et vous permet même de fournir un planificateur personnalisé. Pour plus d’informations, consultez
TaskScheduler.

Structures de données associées


La bibliothèque parallèle de tâches possède plusieurs nouveaux types publics qui sont utiles dans les scénarios
parallèles et séquentiels. Ces derniers incluent plusieurs classes de collection thread-safe, rapides et évolutives
dans l'espace de noms System.Collections.Concurrent, et plusieurs nouveaux types de synchronisation, tels que
System.Threading.Semaphore et System.Threading.ManualResetEventSlim, qui sont plus efficaces que leurs
prédécesseurs pour certains genres de charges de travail. D'autres types nouveaux dans .NET Framework 4, tels
que System.Threading.Barrier et System.Threading.SpinLock, fournissent des fonctionnalités qui n'étaient pas
disponibles dans les versions précédentes. Pour plus d’informations, consultez Structures de données pour la
programmation parallèle.
Types de tâches personnalisés
Nous vous recommandons de ne pas hériter de System.Threading.Tasks.Task ou
System.Threading.Tasks.Task<TResult>. Nous vous recommandons d'utiliser plutôt la propriété AsyncState pour
associer d'autres données ou états à un objet Task ou Task<TResult>. Vous pouvez également utiliser des
méthodes d'extension pour étendre les fonctionnalités des classes Task et Task<TResult>. Pour plus
d’informations sur les méthodes d’extension, consultez Méthodes d’extension et Méthodes d’extension.
Si vous devez hériter de Task ou Task<TResult> , vous ne pouvez pas utiliser Run , ou les
System.Threading.Tasks.TaskFactory System.Threading.Tasks.TaskFactory<TResult> classes, ou
System.Threading.Tasks.TaskCompletionSource<TResult> pour créer des instances de votre type de tâche
personnalisée, car ces mécanismes créent uniquement des Task Task<TResult> objets et. En outre, vous ne pouvez
pas utiliser les mécanismes de continuation des tâches fournis par Task, Task<TResult>TaskFactory et
TaskFactory<TResult> pour créer des instances de votre type de tâche personnalisé parce que ces mécanismes
créent également uniquement des objets Task et Task<TResult>.

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Chaînage des tâches à l’aide de tâches de continuation Décrit le fonctionnement des continuations.

Tâches enfants attachées et détachées Décrit la différence entre les tâches enfants attachées et les
tâches enfants détachées.

Annulation de tâches Décrit la prise en charge de l'annulation intégrée dans l'objet


Task.

Gestion des exceptions Décrit comment les exceptions sur les threads simultanés
sont gérées.

Procédure : utiliser Parallel_Invoke pour exécuter des Explique comment utiliser Invoke.
opérations parallèles

Procédure : retourner une valeur à partir d’une tâche Décrit comment retourner des valeurs à partir de tâches.

Procédure : annuler une tâche et ses enfants Décrit comment annuler des tâches.

Procédure : créer des tâches précalculées Décrit comment utiliser la méthode Task.FromResult pour
récupérer les résultats d'opérations de téléchargement
asynchrones qui sont conservées dans un cache.

Procédure : parcourir un arbre binaire avec des tâches Décrit comment utiliser des tâches pour parcourir un arbre
parallèles binaire.

Procédure : désencapsuler une tâche imbriquée Montre également comment utiliser la méthode d'extension
Unwrap.

Parallélisme des données Décrit comment utiliser For et ForEach pour créer des boucles
parallèles sur des données.

Programmation parallèle Nœud de niveau supérieur pour la programmation parallèle


.NET Framework.
Voir aussi
Programmation parallèle
Exemples de programmation parallèle avec .NET Core & .NET Standard
Chaînage des tâches à l’aide de tâches de
continuation
18/07/2020 • 38 minutes to read • Edit Online

En programmation asynchrone, il est courant pour une opération asynchrone, une fois terminée, d’appeler une
deuxième opération et de lui passer des données. Habituellement, les continuations se faisaient à l’aide de
méthodes de rappel. Dans la bibliothèque parallèle de tâches, les mêmes fonctionnalités sont fournies par les
tâches de continuation. Une tâche de continuation (également appelée continuation) est une tâche asynchrone
appelée par une autre tâche, appelée antécédent, quand ce dernier est terminé.
Les continuations sont relativement faciles à utiliser, tout en étant puissantes et flexibles. Par exemple, vous
pouvez :
passer des données de l'antécédent à la continuation ;
spécifier les conditions précises sous lesquelles la continuation doit être ou non appelée ;
annuler une continuation avant qu'elle ne soit lancée ou pendant son exécution de manière coopérative ;
fournir des conseils sur la manière de planifier la continuation ;
appeler plusieurs continuations depuis le même antécédent ;
appeler une continuation quand certains ou tous les antécédents se terminent ;
chaîner des continuations les unes à la suite des autres à une longueur arbitraire ;
utiliser une continuation pour gérer des exceptions levées par l'antécédent.

À propos des continuations


Une continuation est une tâche qui est créée dans l'état WaitingForActivation . Elle est automatiquement activée à
la fin de la tâche ou des tâches de son antécédent. Appeler Task.Start sur une continuation dans le code utilisateur
lève une exception System.InvalidOperationException .
Une continuation est un Task et ne bloque pas le thread sur lequel elle a démarré. Appelez la méthode Task.Wait
pour bloquer jusqu'à ce que la tâche de continuation se termine.

Création d‘une continuation pour un seul antécédent


Vous créez une continuation qui s'exécute quand son antécédent est terminé en appelant la méthode
Task.ContinueWith . L’exemple suivant montre le modèle de base (pour plus de clarté, la gestion des exceptions a
été omise). Il exécute une tâche d'antécédent, taskA , qui retourne un objet DayOfWeek indiquant le jour actuel
de la semaine. Quand l’antécédent termine, la tâche de continuation, continuation , le récupère et affiche une
chaîne qui comprend son résultat.

NOTE
Les exemples en C# fournis dans cet article utilisent le modificateur async sur la méthode Main . Cette fonctionnalité est
disponible dans les versions 7.1 et ultérieures de C#. Les versions précédentes ont été générées CS5001 lors de la
compilation de cet exemple de code. Vous devez définir la version du langage sur C# 7.1 ou une version ultérieure. Pour
savoir comment configurer la version du langage, consultez l’article sur la configuration de la version du langage.
using System;
using System.Threading.Tasks;

public class Example


{
public static async Task Main()
{
// Execute the antecedent.
Task<DayOfWeek> taskA = Task.Run( () => DateTime.Today.DayOfWeek );

// Execute the continuation when the antecedent finishes.


await taskA.ContinueWith( antecedent => Console.WriteLine("Today is {0}.", antecedent.Result) );
}
}
// The example displays output like the following output:
// Today is Monday.

Imports System.Threading.Tasks

Module Example
Public Sub Main()
' Execute the antecedent.
Dim taskA As Task(Of DayOfWeek) = Task.Run(Function() DateTime.Today.DayOfWeek)

' Execute the continuation when the antecedent finishes.


Dim continuation As Task = taskA.ContinueWith(Sub(antecedent)
Console.WriteLine("Today is {0}.",
antecedent.Result)
End Sub)
continuation.Wait()
End Sub
End Module
' The example displays output like the following output:
' Today is Monday.

Création d‘une continuation pour plusieurs antécédents


Vous pouvez également créer une continuation qui s’exécute quand tout ou partie d’un groupe de tâches est
terminé. Pour exécuter une continuation quand toutes les tâches d'antécédent sont terminées, vous appelez la
méthode statique Shared ( Task.WhenAll en Visual Basic) ou la méthode TaskFactory.ContinueWhenAll d'instance.
Pour exécuter une continuation quand n'importe quelle tâche d'antécédent est terminée, vous appelez la méthode
statique Shared ( Task.WhenAny en Visual Basic) ou la méthode TaskFactory.ContinueWhenAny d'instance.
Sachez que les appels aux surcharges Task.WhenAll et Task.WhenAny ne bloquent pas le thread appelant.
Cependant, vous appelez généralement toutes les méthodes, sauf Task.WhenAll(IEnumerable<Task>) et
Task.WhenAll(Task[]) , pour extraire la propriété Task<TResult>.Result retournée, ce qui bloque le thread appelant.
L’exemple suivant appelle la méthode Task.WhenAll(IEnumerable<Task>) pour créer une tâche de continuation
qui reflète les résultats de ses dix tâches d’antécédent. Chaque tâche d’antécédent élève au carré une valeur
d’index allant de 1 à 10. Si les antécédents se terminent correctement (leur propriété Task.Status vaut
TaskStatus.RanToCompletion), la propriété Task<TResult>.Result de la continuation est un tableau de valeurs
Task<TResult>.Result retournées par chaque antécédent. L’exemple les ajoute pour calculer la somme des carrés
de tous les nombres compris entre 1 et 10.
using System.Collections.Generic;
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
List<Task<int>> tasks = new List<Task<int>>();
for (int ctr = 1; ctr <= 10; ctr++) {
int baseValue = ctr;
tasks.Add(Task.Factory.StartNew( (b) => { int i = (int) b;
return i * i; }, baseValue));
}
var continuation = Task.WhenAll(tasks);

long sum = 0;
for (int ctr = 0; ctr <= continuation.Result.Length - 1; ctr++) {
Console.Write("{0} {1} ", continuation.Result[ctr],
ctr == continuation.Result.Length - 1 ? "=" : "+");
sum += continuation.Result[ctr];
}
Console.WriteLine(sum);
}
}
// The example displays the following output:
// 1 + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100 = 385

Imports System.Collections.Generic
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim tasks As New List(Of Task(Of Integer))()
For ctr As Integer = 1 To 10
Dim baseValue As Integer = ctr
tasks.Add(Task.Factory.StartNew(Function(b)
Dim i As Integer = CInt(b)
Return i * i
End Function, baseValue))
Next
Dim continuation = Task.WhenAll(tasks)

Dim sum As Long = 0


For ctr As Integer = 0 To continuation.Result.Length - 1
Console.Write("{0} {1} ", continuation.Result(ctr),
If(ctr = continuation.Result.Length - 1, "=", "+"))
sum += continuation.Result(ctr)
Next
Console.WriteLine(sum)
End Sub
End Module
' The example displays the following output:
' 1 + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100 = 385

Options de continuation
Quand vous créez une continuation de tâche unique, vous pouvez utiliser une surcharge ContinueWith qui prend
une valeur d'énumération System.Threading.Tasks.TaskContinuationOptions pour spécifier les conditions sous
lesquelles la continuation doit se lancer. Par exemple, vous pouvez spécifier que la continuation doit être exécutée
uniquement si l‘antécédent se termine correctement ou uniquement s‘il se termine avec un état d‘erreur. Si cette
condition n’est pas remplie quand l’antécédent est prêt à appeler la continuation, la continuation passe
directement à l’état TaskStatus.Canceled et ne peut donc pas être lancée.
Plusieurs méthodes de continuation multitâche, telles que les surcharges de la méthode
TaskFactory.ContinueWhenAll , incluent également un paramètre
System.Threading.Tasks.TaskContinuationOptions . Seul un sous-ensemble des membres de l'énumération
System.Threading.Tasks.TaskContinuationOptions est valide, toutefois. Vous pouvez spécifier des valeurs
System.Threading.Tasks.TaskContinuationOptions qui ont des équivalents dans l'énumération
System.Threading.Tasks.TaskCreationOptions , telles que TaskContinuationOptions.AttachedToParent,
TaskContinuationOptions.LongRunninget TaskContinuationOptions.PreferFairness. Si vous spécifiez n'importe
laquelle des options NotOn ou OnlyOn avec une continuation multitâche, une exception
ArgumentOutOfRangeException est levée au moment de l'exécution.
Pour plus d'informations sur les options de continuation de tâche, consultez la rubrique TaskContinuationOptions
.

Passage de données à une continuation


La méthode Task.ContinueWith passe une référence à l'antécédent au délégué d'utilisateur de la continuation en
tant qu'argument. Si l'antécédent est un objet System.Threading.Tasks.Task<TResult> et que la tâche s'est
exécutée normalement jusqu'à son terme, la continuation peut accéder à la propriété Task<TResult>.Result de la
tâche.
La propriété Task<TResult>.Result se bloque jusqu'à ce que la tâche soit terminée. Toutefois, si la tâche a été
annulée ou a rencontré une erreur, toute tentative d'accéder à la propriété Result lève une exception
AggregateException . Vous pouvez éviter ce problème à l'aide de l'option OnlyOnRanToCompletion , comme
indiqué dans l'exemple suivant.

using System;
using System.Threading.Tasks;

public class Example


{
public static async Task Main()
{
var t = Task.Run( () => { DateTime dat = DateTime.Now;
if (dat == DateTime.MinValue)
throw new ArgumentException("The clock is not working.");

if (dat.Hour > 17)


return "evening";
else if (dat.Hour > 12)
return "afternoon";
else
return "morning"; });
await t.ContinueWith( (antecedent) => { Console.WriteLine("Good {0}!",
antecedent.Result);
Console.WriteLine("And how are you this fine {0}?",
antecedent.Result); },
TaskContinuationOptions.OnlyOnRanToCompletion);
}
}
// The example displays output like the following:
// Good afternoon!
// And how are you this fine afternoon?
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim t = Task.Run(Function()
Dim dat As DateTime = DateTime.Now
If dat = DateTime.MinValue Then
Throw New ArgumentException("The clock is not working.")
End If

If dat.Hour > 17 Then


Return "evening"
Else If dat.Hour > 12 Then
Return "afternoon"
Else
Return "morning"
End If
End Function)
Dim c = t.ContinueWith(Sub(antecedent)
Console.WriteLine("Good {0}!",
antecedent.Result)
Console.WriteLine("And how are you this fine {0}?",
antecedent.Result)
End Sub, TaskContinuationOptions.OnlyOnRanToCompletion)
c.Wait()
End Sub
End Module
' The example displays output like the following:
' Good afternoon!
' And how are you this fine afternoon?

Si vous voulez que la continuation s‘exécute même si l‘exécution de l‘antécédent n‘est pas correctement terminée,
vous devez vous attendre à des exceptions. Une approche consiste à tester la propriété Task.Status de l'antécédent
et à n'essayer d'accéder à la propriété Result que si l'état n'est pas Faulted ou Canceled. Vous pouvez également
examiner la propriété Exception de l'antécédent. Pour plus d’informations, consultez l’article Gestion des
exceptions. L'exemple suivant modifie l'exemple précédent de manière à n'accéder à la propriété
Task<TResult>.Result de l'antécédent que si son état est TaskStatus.RanToCompletion.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var t = Task.Run( () => { DateTime dat = DateTime.Now;
if (dat == DateTime.MinValue)
throw new ArgumentException("The clock is not working.");

if (dat.Hour > 17)


return "evening";
else if (dat.Hour > 12)
return "afternoon";
else
return "morning"; });
var c = t.ContinueWith( (antecedent) => { if (t.Status == TaskStatus.RanToCompletion) {
Console.WriteLine("Good {0}!",
antecedent.Result);
Console.WriteLine("And how are you this fine {0}?",
antecedent.Result);
}
else if (t.Status == TaskStatus.Faulted) {
Console.WriteLine(t.Exception.GetBaseException().Message);
}} );
}
}
// The example displays output like the following:
// Good afternoon!
// And how are you this fine afternoon?

Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim t = Task.Run(Function()
Dim dat As DateTime = DateTime.Now
If dat = DateTime.MinValue Then
Throw New ArgumentException("The clock is not working.")
End If

If dat.Hour > 17 Then


Return "evening"
Else If dat.Hour > 12 Then
Return "afternoon"
Else
Return "morning"
End If
End Function)
Dim c = t.ContinueWith(Sub(antecedent)
If t.Status = TaskStatus.RanToCompletion Then
Console.WriteLine("Good {0}!",
antecedent.Result)
Console.WriteLine("And how are you this fine {0}?",
antecedent.Result)
Else If t.Status = TaskStatus.Faulted Then
Console.WriteLine(t.Exception.GetBaseException().Message)
End If
End Sub)
End Sub
End Module
' The example displays output like the following:
' Good afternoon!
' And how are you this fine afternoon?
Annulation d'une continuation
La propriété Task.Status d'une continuation est définie sur TaskStatus.Canceled dans les situations suivantes :
Elle lève une exception OperationCanceledException en réponse à une demande d'annulation. Comme
pour toute tâche, si l’exception contient le même jeton qui a été passé à la continuation, il est traité comme
un accusé de réception de l’annulation coopérative.
La continuation récupère un System.Threading.CancellationToken dont la propriété
IsCancellationRequested a pour valeur true . Dans ce cas, la continuation ne démarre pas, et elle passe à
l'état TaskStatus.Canceled .
La continuation ne s'exécute jamais, car la condition définie par son argument TaskContinuationOptions
n'a pas été remplie. Par exemple, si un antécédent passe à l'état TaskStatus.Faulted , sa continuation qui a
reçu l'option TaskContinuationOptions.NotOnFaulted ne s'exécute pas, mais passe à l'état Canceled .
Si une tâche et sa continuation représentent deux parties de la même opération logique, vous pouvez passer le
même jeton d’annulation aux deux tâches, comme illustré dans l’exemple suivant. Ce dernier comprend un
antécédent qui génère une liste d'entiers divisibles par 33, transmise ensuite à la continuation. À son tour, la
continuation affiche la liste. L‘antécédent et la continuation marquent régulièrement des pauses de durée
aléatoire. En outre, un objet System.Threading.Timer est utilisé pour exécuter la méthode Elapsed après un délai
de cinq secondes. Cet exemple appelle la méthode CancellationTokenSource.Cancel, ce qui amène la tâche en
cours d’exécution à appeler la méthode CancellationToken.ThrowIfCancellationRequested. La durée des pauses
générées de manière aléatoire détermine si la méthode CancellationTokenSource.Cancel est appelée pendant
l'exécution de l'antécédent ou de sa continuation. Si l‘antécédent est annulé, la continuation ne démarre pas. Si
l‘antécédent n‘est pas annulé, le jeton peut toujours être utilisé pour annuler la continuation.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
Random rnd = new Random();
var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Timer timer = new Timer(Elapsed, cts, 5000, Timeout.Infinite);

var t = Task.Run( () => { List<int> product33 = new List<int>();


for (int ctr = 1; ctr < Int16.MaxValue; ctr++) {
if (token.IsCancellationRequested) {
Console.WriteLine("\nCancellation requested in antecedent...\n");
token.ThrowIfCancellationRequested();
}
if (ctr % 2000 == 0) {
int delay = rnd.Next(16,501);
Thread.Sleep(delay);
}

if (ctr % 33 == 0)
product33.Add(ctr);
}
return product33.ToArray();
}, token);

Task continuation = t.ContinueWith(antecedent => { Console.WriteLine("Multiples of 33:\n");


var arr = antecedent.Result;
for (int ctr = 0; ctr < arr.Length; ctr++)
{
if (token.IsCancellationRequested) {
if (token.IsCancellationRequested) {
Console.WriteLine("\nCancellation requested in
continuation...\n");
token.ThrowIfCancellationRequested();
}

if (ctr % 100 == 0) {
int delay = rnd.Next(16,251);
Thread.Sleep(delay);
}
Console.Write("{0:N0}{1}", arr[ctr],
ctr != arr.Length - 1 ? ", " : "");
if (Console.CursorLeft >= 74)
Console.WriteLine();
}
Console.WriteLine();
} , token);

try {
continuation.Wait();
}
catch (AggregateException e) {
foreach (Exception ie in e.InnerExceptions)
Console.WriteLine("{0}: {1}", ie.GetType().Name,
ie.Message);
}
finally {
cts.Dispose();
}

Console.WriteLine("\nAntecedent Status: {0}", t.Status);


Console.WriteLine("Continuation Status: {0}", continuation.Status);
}

private static void Elapsed(object state)


{
CancellationTokenSource cts = state as CancellationTokenSource;
if (cts == null) return;

cts.Cancel();
Console.WriteLine("\nCancellation request issued...\n");
}
}
// The example displays the following output:
// Multiples of 33:
//
// 33, 66, 99, 132, 165, 198, 231, 264, 297, 330, 363, 396, 429, 462, 495, 528,
// 561, 594, 627, 660, 693, 726, 759, 792, 825, 858, 891, 924, 957, 990, 1,023,
// 1,056, 1,089, 1,122, 1,155, 1,188, 1,221, 1,254, 1,287, 1,320, 1,353, 1,386,
// 1,419, 1,452, 1,485, 1,518, 1,551, 1,584, 1,617, 1,650, 1,683, 1,716, 1,749,
// 1,782, 1,815, 1,848, 1,881, 1,914, 1,947, 1,980, 2,013, 2,046, 2,079, 2,112,
// 2,145, 2,178, 2,211, 2,244, 2,277, 2,310, 2,343, 2,376, 2,409, 2,442, 2,475,
// 2,508, 2,541, 2,574, 2,607, 2,640, 2,673, 2,706, 2,739, 2,772, 2,805, 2,838,
// 2,871, 2,904, 2,937, 2,970, 3,003, 3,036, 3,069, 3,102, 3,135, 3,168, 3,201,
// 3,234, 3,267, 3,300, 3,333, 3,366, 3,399, 3,432, 3,465, 3,498, 3,531, 3,564,
// 3,597, 3,630, 3,663, 3,696, 3,729, 3,762, 3,795, 3,828, 3,861, 3,894, 3,927,
// 3,960, 3,993, 4,026, 4,059, 4,092, 4,125, 4,158, 4,191, 4,224, 4,257, 4,290,
// 4,323, 4,356, 4,389, 4,422, 4,455, 4,488, 4,521, 4,554, 4,587, 4,620, 4,653,
// 4,686, 4,719, 4,752, 4,785, 4,818, 4,851, 4,884, 4,917, 4,950, 4,983, 5,016,
// 5,049, 5,082, 5,115, 5,148, 5,181, 5,214, 5,247, 5,280, 5,313, 5,346, 5,379,
// 5,412, 5,445, 5,478, 5,511, 5,544, 5,577, 5,610, 5,643, 5,676, 5,709, 5,742,
// 5,775, 5,808, 5,841, 5,874, 5,907, 5,940, 5,973, 6,006, 6,039, 6,072, 6,105,
// 6,138, 6,171, 6,204, 6,237, 6,270, 6,303, 6,336, 6,369, 6,402, 6,435, 6,468,
// 6,501, 6,534, 6,567, 6,600, 6,633, 6,666, 6,699, 6,732, 6,765, 6,798, 6,831,
// 6,864, 6,897, 6,930, 6,963, 6,996, 7,029, 7,062, 7,095, 7,128, 7,161, 7,194,
// 7,227, 7,260, 7,293, 7,326, 7,359, 7,392, 7,425, 7,458, 7,491, 7,524, 7,557,
// 7,590, 7,623, 7,656, 7,689, 7,722, 7,755, 7,788, 7,821, 7,854, 7,887, 7,920,
// 7,953, 7,986, 8,019, 8,052, 8,085, 8,118, 8,151, 8,184, 8,217, 8,250, 8,283,
// 8,316, 8,349, 8,382, 8,415, 8,448, 8,481, 8,514, 8,547, 8,580, 8,613, 8,646,
// 8,679, 8,712, 8,745, 8,778, 8,811, 8,844, 8,877, 8,910, 8,943, 8,976, 9,009,
// 9,042, 9,075, 9,108, 9,141, 9,174, 9,207, 9,240, 9,273, 9,306, 9,339, 9,372,
// 9,405, 9,438, 9,471, 9,504, 9,537, 9,570, 9,603, 9,636, 9,669, 9,702, 9,735,
// 9,768, 9,801, 9,834, 9,867, 9,900, 9,933, 9,966, 9,999, 10,032, 10,065, 10,098,
// 10,131, 10,164, 10,197, 10,230, 10,263, 10,296, 10,329, 10,362, 10,395, 10,428,
// 10,461, 10,494, 10,527, 10,560, 10,593, 10,626, 10,659, 10,692, 10,725, 10,758,
// 10,791, 10,824, 10,857, 10,890, 10,923, 10,956, 10,989, 11,022, 11,055, 11,088,
// 11,121, 11,154, 11,187, 11,220, 11,253, 11,286, 11,319, 11,352, 11,385, 11,418,
// 11,451, 11,484, 11,517, 11,550, 11,583, 11,616, 11,649, 11,682, 11,715, 11,748,
// 11,781, 11,814, 11,847, 11,880, 11,913, 11,946, 11,979, 12,012, 12,045, 12,078,
// 12,111, 12,144, 12,177, 12,210, 12,243, 12,276, 12,309, 12,342, 12,375, 12,408,
// 12,441, 12,474, 12,507, 12,540, 12,573, 12,606, 12,639, 12,672, 12,705, 12,738,
// 12,771, 12,804, 12,837, 12,870, 12,903, 12,936, 12,969, 13,002, 13,035, 13,068,
// 13,101, 13,134, 13,167, 13,200, 13,233, 13,266,
// Cancellation requested in continuation...
//
//
// Cancellation request issued...
//
// TaskCanceledException: A task was canceled.
//
// Antecedent Status: RanToCompletion
// Continuation Status: Canceled

Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim rnd As New Random()
Dim lockObj As New Object()
Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
Dim timer As New Timer(AddressOf Elapsed, cts, 5000, Timeout.Infinite)

Dim t = Task.Run(Function()
Dim product33 As New List(Of Integer)()
For ctr As Integer = 1 To Int16.MaxValue
' Check for cancellation.
If token.IsCancellationRequested Then
Console.WriteLine("\nCancellation requested in antecedent...\n")
token.ThrowIfCancellationRequested()
End If
' Introduce a delay.
If ctr Mod 2000 = 0 Then
Dim delay As Integer
SyncLock lockObj
delay = rnd.Next(16, 501)
End SyncLock
Thread.Sleep(delay)
End If

' Determine if this is a multiple of 33.


If ctr Mod 33 = 0 Then product33.Add(ctr)
Next
Return product33.ToArray()
End Function, token)

Dim continuation = t.ContinueWith(Sub(antecedent)


Console.WriteLine("Multiples of 33:" + vbCrLf)
Dim arr = antecedent.Result
For ctr As Integer = 0 To arr.Length - 1
If token.IsCancellationRequested Then
Console.WriteLine("{0}Cancellation requested in
continuation...{0}",
vbCrLf)
token.ThrowIfCancellationRequested()
End If

If ctr Mod 100 = 0 Then


Dim delay As Integer
SyncLock lockObj
delay = rnd.Next(16, 251)
End SyncLock
Thread.Sleep(delay)
End If
Console.Write("{0:N0}{1}", arr(ctr),
If(ctr <> arr.Length - 1, ", ", ""))
If Console.CursorLeft >= 74 Then Console.WriteLine()
Next
Console.WriteLine()
End Sub, token)

Try
continuation.Wait()
Catch e As AggregateException
For Each ie In e.InnerExceptions
Console.WriteLine("{0}: {1}", ie.GetType().Name,
ie.Message)
Next
Finally
cts.Dispose()
End Try

Console.WriteLine(vbCrLf + "Antecedent Status: {0}", t.Status)


Console.WriteLine("Continuation Status: {0}", continuation.Status)
End Sub

Private Sub Elapsed(state As Object)


Dim cts As CancellationTokenSource = TryCast(state, CancellationTokenSource)
If cts Is Nothing Then return

cts.Cancel()
Console.WriteLine("{0}Cancellation request issued...{0}", vbCrLf)
End Sub
End Module
' The example displays output like the following:
' Multiples of 33:
'
' 33, 66, 99, 132, 165, 198, 231, 264, 297, 330, 363, 396, 429, 462, 495, 528,
' 561, 594, 627, 660, 693, 726, 759, 792, 825, 858, 891, 924, 957, 990, 1,023,
' 1,056, 1,089, 1,122, 1,155, 1,188, 1,221, 1,254, 1,287, 1,320, 1,353, 1,386,
' 1,419, 1,452, 1,485, 1,518, 1,551, 1,584, 1,617, 1,650, 1,683, 1,716, 1,749,
' 1,782, 1,815, 1,848, 1,881, 1,914, 1,947, 1,980, 2,013, 2,046, 2,079, 2,112,
' 2,145, 2,178, 2,211, 2,244, 2,277, 2,310, 2,343, 2,376, 2,409, 2,442, 2,475,
' 2,508, 2,541, 2,574, 2,607, 2,640, 2,673, 2,706, 2,739, 2,772, 2,805, 2,838,
' 2,871, 2,904, 2,937, 2,970, 3,003, 3,036, 3,069, 3,102, 3,135, 3,168, 3,201,
' 3,234, 3,267, 3,300, 3,333, 3,366, 3,399, 3,432, 3,465, 3,498, 3,531, 3,564,
' 3,597, 3,630, 3,663, 3,696, 3,729, 3,762, 3,795, 3,828, 3,861, 3,894, 3,927,
' 3,960, 3,993, 4,026, 4,059, 4,092, 4,125, 4,158, 4,191, 4,224, 4,257, 4,290,
' 4,323, 4,356, 4,389, 4,422, 4,455, 4,488, 4,521, 4,554, 4,587, 4,620, 4,653,
' 4,686, 4,719, 4,752, 4,785, 4,818, 4,851, 4,884, 4,917, 4,950, 4,983, 5,016,
' 5,049, 5,082, 5,115, 5,148, 5,181, 5,214, 5,247, 5,280, 5,313, 5,346, 5,379,
' 5,412, 5,445, 5,478, 5,511, 5,544, 5,577, 5,610, 5,643, 5,676, 5,709, 5,742,
' 5,775, 5,808, 5,841, 5,874, 5,907, 5,940, 5,973, 6,006, 6,039, 6,072, 6,105,
' 6,138, 6,171, 6,204, 6,237, 6,270, 6,303, 6,336, 6,369, 6,402, 6,435, 6,468,
' 6,501, 6,534, 6,567, 6,600, 6,633, 6,666, 6,699, 6,732, 6,765, 6,798, 6,831,
' 6,864, 6,897, 6,930, 6,963, 6,996, 7,029, 7,062, 7,095, 7,128, 7,161, 7,194,
' 7,227, 7,260, 7,293, 7,326, 7,359, 7,392, 7,425, 7,458, 7,491, 7,524, 7,557,
' 7,590, 7,623, 7,656, 7,689, 7,722, 7,755, 7,788, 7,821, 7,854, 7,887, 7,920,
' 7,953, 7,986, 8,019, 8,052, 8,085, 8,118, 8,151, 8,184, 8,217, 8,250, 8,283,
' 8,316, 8,349, 8,382, 8,415, 8,448, 8,481, 8,514, 8,547, 8,580, 8,613, 8,646,
' 8,679, 8,712, 8,745, 8,778, 8,811, 8,844, 8,877, 8,910, 8,943, 8,976, 9,009,
' 9,042, 9,075, 9,108, 9,141, 9,174, 9,207, 9,240, 9,273, 9,306, 9,339, 9,372,
' 9,405, 9,438, 9,471, 9,504, 9,537, 9,570, 9,603, 9,636, 9,669, 9,702, 9,735,
' 9,768, 9,801, 9,834, 9,867, 9,900, 9,933, 9,966, 9,999, 10,032, 10,065, 10,098,
' 10,131, 10,164, 10,197, 10,230, 10,263, 10,296, 10,329, 10,362, 10,395, 10,428,
' 10,461, 10,494, 10,527, 10,560, 10,593, 10,626, 10,659, 10,692, 10,725, 10,758,
' 10,791, 10,824, 10,857, 10,890, 10,923, 10,956, 10,989, 11,022, 11,055, 11,088,
' 11,121, 11,154, 11,187, 11,220, 11,253, 11,286, 11,319, 11,352, 11,385, 11,418,
' 11,451, 11,484, 11,517, 11,550, 11,583, 11,616, 11,649, 11,682, 11,715, 11,748,
' 11,781, 11,814, 11,847, 11,880, 11,913, 11,946, 11,979, 12,012, 12,045, 12,078,
' 12,111, 12,144, 12,177, 12,210, 12,243, 12,276, 12,309, 12,342, 12,375, 12,408,
' 12,441, 12,474, 12,507, 12,540, 12,573, 12,606, 12,639, 12,672, 12,705, 12,738,
' 12,771, 12,804, 12,837, 12,870, 12,903, 12,936, 12,969, 13,002, 13,035, 13,068,
' 13,101, 13,134, 13,167, 13,200, 13,233, 13,266,
' Cancellation requested in continuation...
'
'
' Cancellation request issued...
'
' TaskCanceledException: A task was canceled.
'
' Antecedent Status: RanToCompletion
' Continuation Status: Canceled

Vous pouvez également empêcher une continuation de s'exécuter si son antécédent est annulé en spécifiant
l'option TaskContinuationOptions.NotOnCanceled quand vous créez la continuation, ce qui vous évite de lui
fournir un jeton d'annulation. Voici un exemple simple.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.Cancel();

var t = Task.FromCanceled(token);
var continuation = t.ContinueWith( (antecedent) => {
Console.WriteLine("The continuation is running.");
} , TaskContinuationOptions.NotOnCanceled);
try {
t.Wait();
}
catch (AggregateException ae) {
foreach (var ie in ae.InnerExceptions)
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message);

Console.WriteLine();
}
finally {
cts.Dispose();
}

Console.WriteLine("Task {0}: {1:G}", t.Id, t.Status);


Console.WriteLine("Task {0}: {1:G}", continuation.Id,
continuation.Status);
}
}
// The example displays the following output:
// TaskCanceledException: A task was canceled.
//
// Task 1: Canceled
// Task 2: Canceled
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
cts.Cancel()

Dim t As Task = Task.FromCanceled(token)


Dim continuation As Task = t.ContinueWith(Sub(antecedent)
Console.WriteLine("The continuation is running.")
End Sub, TaskContinuationOptions.NotOnCanceled)
Try
t.Wait()
Catch e As AggregateException
For Each ie In e.InnerExceptions
Console.WriteLine("{0}: {1}", ie.GetType().Name, ie.Message)
Next
Console.WriteLine()
Finally
cts.Dispose()
End Try

Console.WriteLine("Task {0}: {1:G}", t.Id, t.Status)


Console.WriteLine("Task {0}: {1:G}", continuation.Id,
continuation.Status)
End Sub
End Module
' The example displays the following output:
' TaskCanceledException: A task was canceled.
'
' Task 1: Canceled
' Task 2: Canceled

Une fois la continuation à l'état Canceled , elle peut affecter les continuations suivantes, selon les
TaskContinuationOptions spécifiés pour ces continuations.
Les continuations supprimées ne seront pas lancées.

Continuations et tâches enfants


Une continuation ne s'exécute pas tant que l'antécédent et toutes ses tâches enfants attachées ne sont pas
terminés. La continuation n'attend pas la fin des tâches enfants détachées. Les deux exemples suivants illustrent
des tâches enfants attachées à un antécédent qui crée une continuation, et détachées de celui-ci. Dans l’exemple
suivant, la continuation ne s’exécute qu’une fois toutes les tâches enfants terminées. L’exécution de l’exemple à
plusieurs reprise génère systématiquement la même sortie. L’exemple lance l’antécédent en appelant la méthode
TaskFactory.StartNew, car par défaut la méthode Task.Run crée une tâche parente dont l’option de création de
tâche par défaut est TaskCreationOptions.DenyChildAttach.
using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var t = Task.Factory.StartNew( () => { Console.WriteLine("Running antecedent task {0}...",
Task.CurrentId);
Console.WriteLine("Launching attached child tasks...");
for (int ctr = 1; ctr <= 5; ctr++) {
int index = ctr;
Task.Factory.StartNew( (value) => {
Console.WriteLine(" Attached child
task #{0} running",
value);
Thread.Sleep(1000);
}, index,
TaskCreationOptions.AttachedToParent);
}
Console.WriteLine("Finished launching attached child tasks...");
});
var continuation = t.ContinueWith( (antecedent) => { Console.WriteLine("Executing continuation of Task
{0}",
antecedent.Id);
});
continuation.Wait();
}
}
// The example displays the following output:
// Running antecedent task 1...
// Launching attached child tasks...
// Finished launching attached child tasks...
// Attached child task #5 running
// Attached child task #1 running
// Attached child task #2 running
// Attached child task #3 running
// Attached child task #4 running
// Executing continuation of Task 1
Imports System.Threading
Imports System.Threading.Tasks

Public Module Example


Public Sub Main()
Dim t = Task.Factory.StartNew(Sub()
Console.WriteLine("Running antecedent task {0}...",
Task.CurrentId)
Console.WriteLine("Launching attached child tasks...")
For ctr As Integer = 1 To 5
Dim index As Integer = ctr
Task.Factory.StartNew(Sub(value)
Console.WriteLine(" Attached child
task #{0} running",
value)
Thread.Sleep(1000)
End Sub, index,
TaskCreationOptions.AttachedToParent)
Next
Console.WriteLine("Finished launching attached child tasks...")
End Sub)
Dim continuation = t.ContinueWith(Sub(antecedent)
Console.WriteLine("Executing continuation of Task {0}",
antecedent.Id)
End Sub)
continuation.Wait()
End Sub
End Module
' The example displays the following output:
' Running antecedent task 1...
' Launching attached child tasks...
' Finished launching attached child tasks...
' Attached child task #5 running
' Attached child task #1 running
' Attached child task #2 running
' Attached child task #3 running
' Attached child task #4 running
' Executing continuation of Task 1

Toutefois, si des tâches enfants sont détachées de l'antécédent, la continuation s'exécute dès que l'antécédent a
terminé, indépendamment de l'état des tâches enfants. Ainsi, différentes exécutions de l'exemple suivant peuvent
générer différentes sorties en fonction de la façon dont le Planificateur de tâches a géré chaque tâche enfant.
using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var t = Task.Factory.StartNew( () => { Console.WriteLine("Running antecedent task {0}...",
Task.CurrentId);
Console.WriteLine("Launching attached child tasks...");
for (int ctr = 1; ctr <= 5; ctr++) {
int index = ctr;
Task.Factory.StartNew( (value) => {
Console.WriteLine(" Attached child
task #{0} running",
value);
Thread.Sleep(1000);
}, index);
}
Console.WriteLine("Finished launching detached child tasks...");
}, TaskCreationOptions.DenyChildAttach);
var continuation = t.ContinueWith( (antecedent) => { Console.WriteLine("Executing continuation of Task
{0}",
antecedent.Id);
});
continuation.Wait();
}
}
// The example displays output like the following:
// Running antecedent task 1...
// Launching attached child tasks...
// Finished launching detached child tasks...
// Attached child task #1 running
// Attached child task #2 running
// Attached child task #5 running
// Attached child task #3 running
// Executing continuation of Task 1
// Attached child task #4 running
Imports System.Threading
Imports System.Threading.Tasks

Public Module Example


Public Sub Main()
Dim t = Task.Factory.StartNew(Sub()
Console.WriteLine("Running antecedent task {0}...",
Task.CurrentId)
Console.WriteLine("Launching attached child tasks...")
For ctr As Integer = 1 To 5
Dim index As Integer = ctr
Task.Factory.StartNew(Sub(value)
Console.WriteLine(" Attached child
task #{0} running",
value)
Thread.Sleep(1000)
End Sub, index)
Next
Console.WriteLine("Finished launching detached child tasks...")
End Sub, TaskCreationOptions.DenyChildAttach)
Dim continuation = t.ContinueWith(Sub(antecedent)
Console.WriteLine("Executing continuation of Task {0}",
antecedent.Id)
End Sub)
continuation.Wait()
End Sub
End Module
' The example displays output like the following:
' Running antecedent task 1...
' Launching attached child tasks...
' Finished launching detached child tasks...
' Attached child task #1 running
' Attached child task #2 running
' Attached child task #5 running
' Attached child task #3 running
' Executing continuation of Task 1
' Attached child task #4 running

L’état final de la tâche d’antécédent dépend de l’état final des tâches enfants attachées. L’état des tâches enfants
détachées n’affecte pas le parent. Pour plus d'informations, consultez Tâches enfants attachées et détachées.

Association d'un état à une continuation


Vous pouvez associer un état arbitraire à une continuation de tâche. La méthode ContinueWith fournit des
versions surchargées prenant chacune une valeur Object qui représente l'état de la continuation. Vous pouvez
ensuite accéder à cet objet d'état à l'aide de la propriété Task.AsyncState . Cet objet d'état est null si vous ne
fournissez pas de valeur.
L'état de continuation est utile quand vous convertissez du code existant qui recourt au modèle de
programmation asynchrone (APM) pour utiliser la bibliothèque parallèle de tâches. Dans l’APM, vous fournissez
généralement l’état de l’objet dans la méthode Begin Method, puis vous accédez à cet état à l’aide de la propriété
IAsyncResult.AsyncState. À l'aide de la méthode ContinueWith , vous pouvez conserver cet état quand vous
convertissez du code qui recourt à l'APM pour utiliser la bibliothèque parallèle de tâches.
L'état de continuation peut également être utile si vous employez des objets Task dans le débogueur Visual
Studio. Par exemple, dans la fenêtre Tâches parallèles , la colonne Tâche affiche la représentation sous forme de
chaîne de l'objet d'état pour chaque tâche. Pour plus d’informations sur la fenêtre Tâches parallèles , consultez
Utilisation de la fenêtre Tâches.
L'exemple suivant montre comment utiliser l'état de continuation. Il crée une chaîne de tâches de continuation.
Chaque tâche fournit l'heure actuelle, sous la forme d'un objet DateTime , pour le paramètre state de la
méthode ContinueWith . Chaque objet DateTime représente l'heure de création de la tâche de continuation.
Chaque tâche génère un deuxième objet DateTime qui représente l'heure à laquelle la tâche se termine. Une fois
toutes les tâches terminées, cet exemple affiche les heures de création et de fin de chaque tâche de continuation.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

// Demonstrates how to associate state with task continuations.


class ContinuationState
{
// Simluates a lengthy operation and returns the time at which
// the operation completed.
public static DateTime DoWork()
{
// Simulate work by suspending the current thread
// for two seconds.
Thread.Sleep(2000);

// Return the current time.


return DateTime.Now;
}

static void Main(string[] args)


{
// Start a root task that performs work.
Task<DateTime> t = Task<DateTime>.Run(delegate { return DoWork(); });

// Create a chain of continuation tasks, where each task is


// followed by another task that performs work.
List<Task<DateTime>> continuations = new List<Task<DateTime>>();
for (int i = 0; i < 5; i++)
{
// Provide the current time as the state of the continuation.
t = t.ContinueWith(delegate { return DoWork(); }, DateTime.Now);
continuations.Add(t);
}

// Wait for the last task in the chain to complete.


t.Wait();

// Print the creation time of each continuation (the state object)


// and the completion time (the result of that task) to the console.
foreach (var continuation in continuations)
{
DateTime start = (DateTime)continuation.AsyncState;
DateTime end = continuation.Result;

Console.WriteLine("Task was created at {0} and finished at {1}.",


start.TimeOfDay, end.TimeOfDay);
}
}
}

/* Sample output:
Task was created at 10:56:21.1561762 and finished at 10:56:25.1672062.
Task was created at 10:56:21.1610677 and finished at 10:56:27.1707646.
Task was created at 10:56:21.1610677 and finished at 10:56:29.1743230.
Task was created at 10:56:21.1610677 and finished at 10:56:31.1779883.
Task was created at 10:56:21.1610677 and finished at 10:56:33.1837083.
*/
Imports System.Collections.Generic
Imports System.Threading
Imports System.Threading.Tasks

' Demonstrates how to associate state with task continuations.


Public Module ContinuationState
' Simluates a lengthy operation and returns the time at which
' the operation completed.
Public Function DoWork() As Date
' Simulate work by suspending the current thread
' for two seconds.
Thread.Sleep(2000)

' Return the current time.


Return Date.Now
End Function

Public Sub Main()


' Start a root task that performs work.
Dim t As Task(Of Date) = Task(Of Date).Run(Function() DoWork())

' Create a chain of continuation tasks, where each task is


' followed by another task that performs work.
Dim continuations As New List(Of Task(Of DateTime))()
For i As Integer = 0 To 4
' Provide the current time as the state of the continuation.
t = t.ContinueWith(Function(antecedent, state) DoWork(), DateTime.Now)
continuations.Add(t)
Next

' Wait for the last task in the chain to complete.


t.Wait()

' Display the creation time of each continuation (the state object)
' and the completion time (the result of that task) to the console.
For Each continuation In continuations
Dim start As DateTime = CDate(continuation.AsyncState)
Dim [end] As DateTime = continuation.Result

Console.WriteLine("Task was created at {0} and finished at {1}.",


start.TimeOfDay, [end].TimeOfDay)
Next
End Sub
End Module
' The example displays output like the following:
' Task was created at 10:56:21.1561762 and finished at 10:56:25.1672062.
' Task was created at 10:56:21.1610677 and finished at 10:56:27.1707646.
' Task was created at 10:56:21.1610677 and finished at 10:56:29.1743230.
' Task was created at 10:56:21.1610677 and finished at 10:56:31.1779883.
' Task was created at 10:56:21.1610677 and finished at 10:56:33.1837083.

Gestion d'exceptions levées par des continuations


Une relation antécédent-continuation n'est pas une relation parent-enfant. Les exceptions levées par les
continuations ne sont pas propagées à l'antécédent. Vous pouvez ainsi gérer les exceptions levées par les
continuations de la même manière qu’avec une autre tâche, comme suit :
Vous pouvez utiliser la méthode Wait, WaitAll, ou WaitAny , ou son équivalent générique, pour attendre la
continuation. Vous pouvez attendre un antécédent et ses continuations dans la même instruction try ,
comme indiqué dans l'exemple suivant.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var task1 = Task<int>.Run( () => { Console.WriteLine("Executing task {0}",
Task.CurrentId);
return 54; });
var continuation = task1.ContinueWith( (antecedent) =>
{ Console.WriteLine("Executing continuation task {0}",
Task.CurrentId);
Console.WriteLine("Value from antecedent: {0}",
antecedent.Result);
throw new InvalidOperationException();
} );

try {
task1.Wait();
continuation.Wait();
}
catch (AggregateException ae) {
foreach (var ex in ae.InnerExceptions)
Console.WriteLine(ex.Message);
}
}
}
// The example displays the following output:
// Executing task 1
// Executing continuation task 2
// Value from antecedent: 54
// Operation is not valid due to the current state of the object.
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task(Of Integer).Run(Function()
Console.WriteLine("Executing task {0}",
Task.CurrentId)
Return 54
End Function)
Dim continuation = task1.ContinueWith(Sub(antecedent)
Console.WriteLine("Executing continuation task {0}",
Task.CurrentId)
Console.WriteLine("Value from antecedent: {0}",
antecedent.Result)
Throw New InvalidOperationException()
End Sub)

Try
task1.Wait()
continuation.Wait()
Catch ae As AggregateException
For Each ex In ae.InnerExceptions
Console.WriteLine(ex.Message)
Next
End Try
End Sub
End Module
' The example displays the following output:
' Executing task 1
' Executing continuation task 2
' Value from antecedent: 54
' Operation is not valid due to the current state of the object.

Vous pouvez utiliser une deuxième continuation pour observer la propriété Exception de la première
continuation. Dans l’exemple suivant, une tâche tente de lire un fichier inexistant. La continuation affiche
ensuite des informations sur l'exception dans la tâche d'antécédent.

using System;
using System.IO;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var t = Task.Run( () => { string s = File.ReadAllText(@"C:\NonexistentFile.txt");
return s;
});

var c = t.ContinueWith( (antecedent) =>


{ // Get the antecedent's exception information.
foreach (var ex in antecedent.Exception.InnerExceptions) {
if (ex is FileNotFoundException)
Console.WriteLine(ex.Message);
}
}, TaskContinuationOptions.OnlyOnFaulted);

c.Wait();
}
}
// The example displays the following output:
// Could not find file 'C:\NonexistentFile.txt'.
Imports System.IO
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim t = Task.Run(Function()
Dim s As String = File.ReadAllText("C:\NonexistentFile.txt")
Return s
End Function)

Dim c = t.ContinueWith(Sub(antecedent)
' Get the antecedent's exception information.
For Each ex In antecedent.Exception.InnerExceptions
If TypeOf ex Is FileNotFoundException
Console.WriteLine(ex.Message)
End If
Next
End Sub, TaskContinuationOptions.OnlyOnFaulted)

c.Wait()
End Sub
End Module
' The example displays the following output:
' Could not find file 'C:\NonexistentFile.txt'.

Ayant été lancée avec l'option TaskContinuationOptions.OnlyOnFaulted , la continuation ne s'exécute que si


une exception se produit dans l'antécédent et peut donc supposer que la propriété Exception de
l'antécédent n'a pas pour valeur null . Si la continuation s'exécute indépendamment du fait qu'une
exception soit levée ou non dans l'antécédent, elle doit vérifier si la propriété Exception de l'antécédent
n'est pas définie sur null avant de gérer l'exception, comme le montre le fragment de code suivant.

// Determine whether an exception occurred.


if (antecedent.Exception != null) {
foreach (var ex in antecedent.Exception.InnerExceptions) {
if (ex is FileNotFoundException)
Console.WriteLine(ex.Message);
}
}

' Determine whether an exception occurred.


If antecedent.Exception IsNot Nothing Then
' Get the antecedent's exception information.
For Each ex In antecedent.Exception.InnerExceptions
If TypeOf ex Is FileNotFoundException
Console.WriteLine(ex.Message)
End If
Next
End If

Pour plus d’informations, consultez l’article Gestion des exceptions.


Si la continuation est une tâche enfant attachée qui a été créée à l'aide de l'option
TaskContinuationOptions.AttachedToParent , ses exceptions sont de nouveau propagées par le parent au
thread appelant, comme dans le cas de tout autre enfant attaché. Pour plus d'informations, consultez
Tâches enfants attachées et détachées.

Voir aussi
Bibliothèque parallèle de tâches
Tâches enfants attachées et détachées
18/07/2020 • 14 minutes to read • Edit Online

Une tâche enfant (ou tâche imbriquée) est une instance System.Threading.Tasks.Task créée dans le délégué
utilisateur d’une autre tâche, appelée tâche parent. Une tâche enfant peut être détachée ou attachée. Une tâche
enfant détachée est une tâche qui s’exécute indépendamment de son parent. Une tâche enfant attachée est une
tâche imbriquée créée avec l’option TaskCreationOptions.AttachedToParent dont le parent ne l’empêche pas
explicitement ou par défaut d’être attachée. Une tâche peut créer autant de tâches enfants attachées et détachées
que le permettent les ressources système.
Le tableau suivant répertorie les principales différences entre les deux types de tâches enfants.

C AT EGO RY TÂ C H ES EN FA N T S DÉTA C H ÉES TÂ C H ES EN FA N T S AT TA C H ÉES

Le parent attend que les tâches Non Oui


enfants soient terminées.

Le parent propage les exceptions Non Oui


levées par les tâches enfants.

Le statut du parent dépend du statut Non Oui


de l'enfant.

Dans la plupart des scénarios, nous vous recommandons d’utiliser des tâches enfants détachées, car leurs
relations avec les autres tâches sont moins complexes. C'est pourquoi les tâches créées à l'intérieur de tâches
parentes sont détachées par défaut et vous devez spécifier explicitement l'option
TaskCreationOptions.AttachedToParent pour créer une tâche enfant attachée.

Tâches enfants détachées


Même si une tâche enfant est créée par une tâche parente, par défaut, elle est indépendante de celle-ci. Dans
l'exemple suivant, une tâche parente crée une tâche enfant simple. Si vous exécutez l'exemple de code plusieurs
fois, vous pouvez remarquer que la sortie de l'exemple diffère de celle indiquée et éventuellement d'une
exécution à l'autre. En effet, la tâche parente et les tâches enfants s'exécutent indépendamment les unes des
autres ; l'enfant est une tâche détachée. L'exemple attend seulement que la tâche parente se termine, et la tâche
enfant ne peut pas s'exécuter ou s'achever avant la fin de l'application console.
using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Outer task executing.");

var child = Task.Factory.StartNew(() => {


Console.WriteLine("Nested task starting.");
Thread.SpinWait(500000);
Console.WriteLine("Nested task completing.");
});
});

parent.Wait();
Console.WriteLine("Outer has completed.");
}
}
// The example produces output like the following:
// Outer task executing.
// Nested task starting.
// Outer has completed.
// Nested task completing.

Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Outer task executing.")
Dim child = Task.Factory.StartNew(Sub()

Console.WriteLine("Nested task starting.")


Thread.SpinWait(500000)

Console.WriteLine("Nested task completing.")


End Sub)
End Sub)
parent.Wait()
Console.WriteLine("Outer task has completed.")
End Sub
End Module
' The example produces output like the following:
' Outer task executing.
' Nested task starting.
' Outer task has completed.
' Nested task completing.

Si la tâche enfant est représentée par un objet Task<TResult> plutôt que par un objet Task, vous pouvez vous
assurer que la tâche parente attend la fin de la tâche enfant en accédant à la propriété Task<TResult>.Result de
celle-ci, même s'il s'agit d'une tâche enfant détachée. La propriété Result bloque jusqu’à ce que sa tâche se
termine, comme le montre l’exemple suivant.
using System;
using System.Threading;
using System.Threading.Tasks;

class Example
{
static void Main()
{
var outer = Task<int>.Factory.StartNew(() => {
Console.WriteLine("Outer task executing.");

var nested = Task<int>.Factory.StartNew(() => {


Console.WriteLine("Nested task starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Nested task completing.");
return 42;
});

// Parent will wait for this detached child.


return nested.Result;
});

Console.WriteLine("Outer has returned {0}.", outer.Result);


}
}
// The example displays the following output:
// Outer task executing.
// Nested task starting.
// Nested task completing.
// Outer has returned 42.

Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim parent = Task(Of Integer).Factory.StartNew(Function()
Console.WriteLine("Outer task executing.")
Dim child = Task(Of
Integer).Factory.StartNew(Function()

Console.WriteLine("Nested task starting.")

Thread.SpinWait(5000000)

Console.WriteLine("Nested task completing.")

Return 42
End
Function)
Return child.Result

End Function)
Console.WriteLine("Outer has returned {0}", parent.Result)
End Sub
End Module
' The example displays the following output:
' Outer task executing.
' Nested task starting.
' Detached task completing.
' Outer has returned 42
Tâches enfants attachées
Contrairement aux tâches enfants détachées, les tâches enfants attachées sont étroitement synchronisées avec le
parent. Vous pouvez convertir la tâche enfant détachée dans l'exemple précédent en tâche enfant attachée à
l'aide de l'option TaskCreationOptions.AttachedToParent dans l'instruction de création de tâche, comme illustré
dans l'exemple suivant. Dans ce code, la tâche enfant attachée se termine avant son parent. La sortie de l'exemple
est donc la même chaque fois que vous exécutez le code.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var parent = Task.Factory.StartNew(() => {
Console.WriteLine("Parent task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Attached child starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completing.");
}, TaskCreationOptions.AttachedToParent);
});
parent.Wait();
Console.WriteLine("Parent has completed.");
}
}
// The example displays the following output:
// Parent task executing.
// Attached child starting.
// Attached child completing.
// Parent has completed.

Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim parent = Task.Factory.StartNew(Sub()
Console.WriteLine("Parent task executing")
Dim child = Task.Factory.StartNew(Sub()

Console.WriteLine("Attached child starting.")

Thread.SpinWait(5000000)

Console.WriteLine("Attached child completing.")


End Sub,
TaskCreationOptions.AttachedToParent)
End Sub)
parent.Wait()
Console.WriteLine("Parent has completed.")
End Sub
End Module
' The example displays the following output:
' Parent task executing.
' Attached child starting.
' Attached child completing.
' Parent has completed.

Vous pouvez utiliser des tâches enfants attachées pour créer des graphiques étroitement synchronisés
d’opérations asynchrones.
Toutefois, une tâche enfant ne peut s’attacher à son parent que si celui-ci n’interdit pas les tâches enfants
attachées. Pour empêcher explicitement l’attachement de tâches enfants à une tâche parente, vous devez spécifier
l’option TaskCreationOptions.DenyChildAttach dans le constructeur de classe de la tâche parente ou la méthode
TaskFactory.StartNew. Pour une interdiction implicite, vous devez créer les tâches parentes en appelant la
méthode Task.Run. L'exemple suivant illustre ce comportement. Il est identique à l’exemple précédent, même si la
tâche parente est créée en appelant la méthode Task.Run(Action) plutôt que la méthode
TaskFactory.StartNew(Action). Étant donné que la tâche enfant n’est pas en mesure de s’attacher à son parent, la
sortie de l’exemple est imprévisible. Étant donné que les options de création de tâches par défaut pour les
surcharges Task.Run incluent TaskCreationOptions.DenyChildAttach, cet exemple est fonctionnellement
équivalent au premier exemple dans la section « Tâches enfants détachées ».

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var parent = Task.Run(() => {
Console.WriteLine("Parent task executing.");
var child = Task.Factory.StartNew(() => {
Console.WriteLine("Attached child starting.");
Thread.SpinWait(5000000);
Console.WriteLine("Attached child completing.");
}, TaskCreationOptions.AttachedToParent);
});
parent.Wait();
Console.WriteLine("Parent has completed.");
}
}
// The example displays output like the following:
// Parent task executing.
// Parent has completed.
// Attached child starting.

Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim parent = Task.Run(Sub()
Console.WriteLine("Parent task executing.")
Dim child = Task.Factory.StartNew(Sub()
Console.WriteLine("Attached child
starting.")
Thread.SpinWait(5000000)
Console.WriteLine("Attached child
completing.")
End Sub,
TaskCreationOptions.AttachedToParent)
End Sub)
parent.Wait()
Console.WriteLine("Parent has completed.")
End Sub
End Module
' The example displays output like the following:
' Parent task executing.
' Parent has completed.
' Attached child starting.
Exceptions dans les tâches enfants
Si une tâche enfant détachée lève une exception, celle-ci doit être observée ou gérée directement dans la tâche
parente, comme dans le cas de n’importe quelle tâche non imbriquée. Si une tâche enfant attachée lève une
exception, celle-ci est automatiquement propagée vers la tâche parente, puis vers le thread qui attend ou essaie
d’accéder à la propriété Task<TResult>.Result de la tâche. Ainsi, en utilisant des tâches enfants attachées, vous
pouvez gérer toutes les exceptions en un seul point dans l’appel à Task.Wait sur le thread appelant. Pour plus
d’informations, consultez l’article Gestion des exceptions.

Annulation et tâches enfants


L'annulation de tâche est coopérative. Autrement dit, pour être annulable, chaque tâche enfant attachée ou
détachée doit surveiller l'état du jeton d'annulation. Pour annuler un parent et tous ses enfants à l'aide d'une
demande d'annulation, vous passez le même jeton en tant qu'argument à toutes les tâches et fournissez dans
chaque tâche la logique pour répondre à la demande. Pour plus d’informations, consultez Annulation de tâches
et Comment : annuler une tâche et ses enfants.
Annulation d'un parent
Si un parent s’annule avant le démarrage de sa tâche enfant, celle-ci ne démarre jamais. Si un parent s’annule
une fois que sa tâche enfant a démarré, celle-ci s’exécute jusqu’à son terme sauf si elle possède sa propre logique
d’annulation. Pour plus d'informations, consultez Task Cancellation.
Annulation d’une tâche enfant détachée
Si une tâche enfant détachée s’annule à l’aide du jeton passé au parent et que celui-ci n’attend pas la tâche
enfant, aucune exception n’est propagée, car l’exception est traitée comme une annulation de coopération
bénigne. Ce comportement est identique à celui de toute tâche de niveau supérieur.
Annulation d’une tâche enfant attachée
Quand une tâche enfant attachée s’annule à l’aide du jeton passé à sa tâche parente, une TaskCanceledException
est propagée vers le thread intermédiaire à l’intérieur d’une AggregateException. Vous devez attendre la tâche
parente pour pouvoir gérer toutes les exceptions bénignes, en plus de toute exception d’erreur propagée vers un
graphique de tâches enfants attachées.
Pour plus d’informations, consultez l’article Gestion des exceptions.

Empêcher qu'une tâche enfant ne s'attache à son parent


Une exception non gérée levée par une tâche enfant est propagée vers la tâche parente. Vous pouvez vous baser
sur ce comportement pour observer toutes les exceptions de tâche enfant à partir d'une seule tâche racine au
lieu de parcourir une arborescence de tâches. Toutefois, la propagation d’exception peut être problématique
quand une tâche parente n’attend pas d’attachement de la part d’un autre code. Par exemple, imaginez une
application qui appelle un composant de bibliothèque tierce à partir d'un objet Task. Si ce composant crée
également un objet Task et spécifie TaskCreationOptions.AttachedToParent pour l’attacher à la tâche parente, les
exceptions non gérées qui se produisent dans la tâche enfant se propagent vers le parent. Cela peut entraîner un
comportement inattendu dans l'application principale.
Pour empêcher une tâche enfant de s'attacher à sa tâche parente, spécifiez l'option
TaskCreationOptions.DenyChildAttach quand vous créez l'objet Task ou Task<TResult> parent. Si une tâche
enfant tente de s’attacher à son parent alors que celui-ci spécifie l’option TaskCreationOptions.DenyChildAttach,
elle échoue et s’exécute comme si l’option TaskCreationOptions.AttachedToParent n’était pas spécifiée.
Vous pourriez également empêcher une tâche enfant de s'attacher à son parent quand la tâche enfant ne se
termine pas en temps voulu. Étant donné qu’une tâche parente ne se termine pas tant que toutes les tâches
enfants ne sont pas achevées, une tâche enfant à exécution longue peut entraîner des performances médiocres
de la part de l’application globale. Pour obtenir un exemple qui montre comment améliorer les performances de
l’application en empêchant une tâche de s’attacher à sa tâche parente, consultez Procédure : empêcher une tâche
enfant de s’attacher à son parent.

Voir aussi
Programmation parallèle
Parallélisme des données
Annulation de tâches
18/07/2020 • 5 minutes to read • Edit Online

Les classes System.Threading.Tasks.Task et System.Threading.Tasks.Task<TResult> prennent en charge


l'annulation via l'utilisation de jetons d'annulation dans .NET Framework. Pour plus d’informations, consultez
annulation dans les threads managés. Dans les classes de tâche, l'annulation implique une coopération entre le
délégué d'utilisateur, qui représente une opération annulable et le code qui a demandé l'annulation. Une
annulation réussie implique la demande du code appelant la méthode CancellationTokenSource.Cancel, et le
délégué d'utilisateur terminant l'opération dans le délai imparti. Vous pouvez terminer l'opération à l'aide de
l'une des options suivantes :
Par un retour du délégué. Cela suffit dans la plupart des scénarios ; toutefois, une instance de tâche
annulée de cette façon passe à l'état TaskStatus.RanToCompletion , et non à l'état TaskStatus.Canceled .
En levant une OperationCanceledException et en lui passant le jeton sur lequel l'annulation a été
demandée. La meilleure façon de faire cela est d'utiliser la méthode ThrowIfCancellationRequested . Une
tâche annulée de cette façon passe à l'état Canceled, ce que le code appelant peut utiliser pour vérifier
que la tâche a répondu à sa requête d'annulation.
L'exemple suivant montre le modèle de base d'annulation de tâche qui lève l'exception. Notez que le jeton est
passé au délégué utilisateur et à l'instance de tâche.
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
var tokenSource2 = new CancellationTokenSource();
CancellationToken ct = tokenSource2.Token;

var task = Task.Run(() =>


{
// Were we already canceled?
ct.ThrowIfCancellationRequested();

bool moreToDo = true;


while (moreToDo)
{
// Poll on this property if you have to do
// other cleanup before throwing.
if (ct.IsCancellationRequested)
{
// Clean up here, then...
ct.ThrowIfCancellationRequested();
}
}
}, tokenSource2.Token); // Pass same token to Task.Run.

tokenSource2.Cancel();

// Just continue on this thread, or await with try-catch:


try
{
await task;
}
catch (OperationCanceledException e)
{
Console.WriteLine($"{nameof(OperationCanceledException)} thrown with message: {e.Message}");
}
finally
{
tokenSource2.Dispose();
}

Console.ReadKey();
}
}
Imports System.Threading
Imports System.Threading.Tasks

Module Test
Sub Main()
Dim tokenSource2 As New CancellationTokenSource()
Dim ct As CancellationToken = tokenSource2.Token

Dim t2 = Task.Factory.StartNew(Sub()
' Were we already canceled?
ct.ThrowIfCancellationRequested()

Dim moreToDo As Boolean = True


While moreToDo = True
' Poll on this property if you have to do
' other cleanup before throwing.
If ct.IsCancellationRequested Then

' Clean up here, then...


ct.ThrowIfCancellationRequested()
End If

End While
End Sub _
, tokenSource2.Token) ' Pass same token to StartNew.

' Cancel the task.


tokenSource2.Cancel()

' Just continue on this thread, or Wait/WaitAll with try-catch:


Try
t2.Wait()

Catch e As AggregateException

For Each item In e.InnerExceptions


Console.WriteLine(e.Message & " " & item.Message)
Next
Finally
tokenSource2.Dispose()
End Try

Console.ReadKey()
End Sub
End Module

Pour obtenir un exemple plus complet, consultez Comment : annuler une tâche et ses enfants.
Lorsqu'une instance de tâche observe une OperationCanceledException levée par le code utilisateur, elle
compare le jeton de l'exception à son jeton associé (celui passé à l'API ayant créé la tâche). S'ils sont identiques
et si la propriété IsCancellationRequested du jeton retourne la valeur true, la tâche l'interprète comme une
acceptation d'annulation et passe à l'état Canceled. Si vous n'utilisez pas une méthode Wait ou WaitAll pour
attendre la tâche, la tâche définit uniquement son état sur Canceled.
Si vous attendez une tâche qui passe à l’état Canceled, une exception
System.Threading.Tasks.TaskCanceledException (enveloppée dans une exception AggregateException ) est levée.
Notez que cette exception indique une annulation réussie et non une défaillance. Ainsi, la propriété Exception de
la tâche retourne null .
Si la propriété IsCancellationRequested du jeton retourne la valeur false ou si le jeton de l'exception ne
correspond pas au jeton de la tâche, OperationCanceledException est traitée comme une exception normale,
entraînant ainsi le passage de la tâche à l'état Faulted. Notez également que la présence d'autres exceptions
entraînera le passage de la tâche à l'état Faulted. Vous pouvez obtenir l'état de la tâche terminée dans la
propriété Status .
Il est possible qu'une tâche puisse continuer à traiter certains éléments après la demande d'annulation.

Voir aussi
Annulation dans les threads managés
Procédure : annuler une tâche et ses enfants
Gestion des exceptions (bibliothèque parallèle de
tâches)
18/07/2020 • 20 minutes to read • Edit Online

Les exceptions non gérées levées par le code utilisateur s’exécutant à l’intérieur d’une tâche sont propagées
vers le thread appelant, sauf dans certains scénarios décrits plus loin dans cette rubrique. Les exceptions sont
propagées quand vous utilisez l’une des méthodes statiques ou d’instance Task.Wait et que vous les gérez en
incluant l’appel dans une instruction try / catch . Si une tâche est le parent de tâches enfants attachées ou si
vous attendez plusieurs tâches, plusieurs exceptions peuvent être levées.
Pour propager toutes les exceptions vers le thread appelant, l’infrastructure de la tâche les encapsule dans une
instance AggregateException . L’exception AggregateException possède une propriété InnerExceptions qu’il est
possible d’énumérer pour examiner toutes les exceptions d’origine levées et gérer (ou non) individuellement
chacune d’elles. Vous pouvez également gérer les exceptions d’origine à l’aide de la méthode
AggregateException.Handle.
Même si une seule exception est levée, elle est encapsulée dans une exception AggregateException , comme le
montre l’exemple suivant.

using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

try
{
task1.Wait();
}
catch (AggregateException ae)
{
foreach (var e in ae.InnerExceptions) {
// Handle the custom exception.
if (e is CustomException) {
Console.WriteLine(e.Message);
}
// Rethrow any other exception.
else {
throw;
}
}
}
}
}

public class CustomException : Exception


{
public CustomException(String message) : base(message)
{}
}
// The example displays the following output:
// This exception is expected!
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

Try
task1.Wait()
Catch ae As AggregateException
For Each ex In ae.InnerExceptions
' Handle the custom exception.
If TypeOf ex Is CustomException Then
Console.WriteLine(ex.Message)
' Rethrow any other exception.
Else
Throw
End If
Next
End Try
End Sub
End Module

Class CustomException : Inherits Exception


Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' This exception is expected!

Vous pouvez éviter une exception non gérée en interceptant l’exception AggregateException et en n’observant
aucune des exceptions internes. Toutefois, cela n’est pas recommandé car cela revient à intercepter le type
Exception de base dans des scénarios non parallèles. Intercepter une exception sans prendre de mesures
spécifiques de récupération peut laisser votre programme dans un état indéterminé.
Si vous ne voulez pas appeler la méthode Task.Wait afin d’attendre la fin d’une tâche, vous pouvez également
récupérer l’exception AggregateException à partir de la propriété Exception de la tâche, comme le montre
l’exemple suivant. Pour plus d’informations, consultez la section observation des exceptions à l’aide de la
propriété Task. exception dans cette rubrique.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

while(! task1.IsCompleted) {}

if (task1.Status == TaskStatus.Faulted) {
foreach (var e in task1.Exception.InnerExceptions) {
// Handle the custom exception.
if (e is CustomException) {
Console.WriteLine(e.Message);
}
// Rethrow any other exception.
else {
throw e;
}
}
}
}
}

public class CustomException : Exception


{
public CustomException(String message) : base(message)
{}
}
// The example displays the following output:
// This exception is expected!

Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

While Not task1.IsCompleted


End While

If task1.Status = TaskStatus.Faulted Then


For Each ex In task1.Exception.InnerExceptions
' Handle the custom exception.
If TypeOf ex Is CustomException Then
Console.WriteLine(ex.Message)
' Rethrow any other exception.
Else
Throw ex
End If
Next
End If
End Sub
End Module

Class CustomException : Inherits Exception


Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' This exception is expected!
Si vous n’attendez pas une tâche qui propage une exception ou accédez à sa propriété Exception , l’exception est
transmise d’après la stratégie de l’exception .NET lorsque la tâche est récupérée par le garbage collector.
Lorsque les exceptions sont autorisées à se propager vers le thread joint, il est possible qu’une tâche continue à
traiter des éléments après que l’exception a été levée.

NOTE
Quand l'option Uniquement mon code est activée, Visual Studio, dans certains cas, peut s'arrêter sur la ligne qui lève
l'exception et afficher un message d'erreur indiquant que l'exception n'est pas gérée par le code utilisateur. Cette erreur
est sans gravité. Vous pouvez appuyer sur F5 pour continuer et voir le comportement de gestion des exceptions qui est
illustré dans ces exemples. Pour empêcher Visual Studio de s’arrêter sur la première erreur, il suffit de désactiver la case à
cocher Autoriser uniquement mon code sous Outils, Options, Débogage, Général.

Tâches enfants attachées et exceptions AggregateException


imbriquées
Si une tâche a une tâche enfant attachée qui lève une exception, cette exception est encapsulée dans une
exception AggregateException avant d’être propagée vers la tâche parent, qui encapsule cette exception dans sa
propre exception AggregateException avant de la propager vers le thread appelant. Dans de tels cas, la
propriété InnerExceptions de l’exception AggregateException interceptée au niveau de la méthode Task.Wait,
WaitAny ou WaitAllcontient une ou plusieurs instances AggregateException, et non les exceptions d’origine
ayant provoqué l’erreur. Pour éviter d’avoir à effectuer une itération sur les exceptions AggregateException
imbriquées, vous pouvez utiliser la méthode Flatten pour supprimer toutes les exceptions AggregateException
imbriquées, afin que la propriété AggregateException.InnerExceptions contienne les exceptions d’origine. Dans
l’exemple suivant, les instances AggregateException imbriquées sont aplaties et gérées en une seule boucle.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var task1 = Task.Factory.StartNew(() => {
var child1 = Task.Factory.StartNew(() => {
var child2 = Task.Factory.StartNew(() => {
// This exception is nested inside three AggregateExceptions.
throw new CustomException("Attached child2 faulted.");
}, TaskCreationOptions.AttachedToParent);

// This exception is nested inside two AggregateExceptions.


throw new CustomException("Attached child1 faulted.");
}, TaskCreationOptions.AttachedToParent);
});

try {
task1.Wait();
}
catch (AggregateException ae) {
foreach (var e in ae.Flatten().InnerExceptions) {
if (e is CustomException) {
Console.WriteLine(e.Message);
}
else {
throw;
}
}
}
}
}

public class CustomException : Exception


{
public CustomException(String message) : base(message)
{}
}
// The example displays the following output:
// Attached child1 faulted.
// Attached child2 faulted.
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task.Factory.StartNew(Sub()
Dim child1 = Task.Factory.StartNew(Sub()
Dim child2 =
Task.Factory.StartNew(Sub()

Throw New CustomException("Attached child2 faulted.")

End Sub,

TaskCreationOptions.AttachedToParent)
Throw New
CustomException("Attached child1 faulted.")
End Sub,

TaskCreationOptions.AttachedToParent)
End Sub)

Try
task1.Wait()
Catch ae As AggregateException
For Each ex In ae.Flatten().InnerExceptions
If TypeOf ex Is CustomException Then
Console.WriteLine(ex.Message)
Else
Throw
End If
Next
End Try
End Sub
End Module

Class CustomException : Inherits Exception


Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' Attached child1 faulted.
' Attached child2 faulted.

Vous pouvez également utiliser la méthode AggregateException.Flatten pour lever à nouveau les exceptions
internes de plusieurs instances AggregateException levées par plusieurs tâches dans une seule instance
AggregateException, comme le montre l’exemple suivant.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
try {
ExecuteTasks();
}
catch (AggregateException ae) {
foreach (var e in ae.InnerExceptions) {
Console.WriteLine("{0}:\n {1}", e.GetType().Name, e.Message);
}
}
}

static void ExecuteTasks()


{
// Assume this is a user-entered String.
String path = @"C:\";
List<Task> tasks = new List<Task>();

tasks.Add(Task.Run(() => {
// This should throw an UnauthorizedAccessException.
return Directory.GetFiles(path, "*.txt",
SearchOption.AllDirectories);
}));

tasks.Add(Task.Run(() => {
if (path == @"C:\")
throw new ArgumentException("The system root is not a valid path.");
return new String[] { ".txt", ".dll", ".exe", ".bin", ".dat" };
}));

tasks.Add(Task.Run(() => {
throw new NotImplementedException("This operation has not been
implemented.");
}));

try {
Task.WaitAll(tasks.ToArray());
}
catch (AggregateException ae) {
throw ae.Flatten();
}
}
}
// The example displays the following output:
// UnauthorizedAccessException:
// Access to the path 'C:\Documents and Settings' is denied.
// ArgumentException:
// The system root is not a valid path.
// NotImplementedException:
// This operation has not been implemented.
Imports System.Collections.Generic
Imports System.IO
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Try
ExecuteTasks()
Catch ae As AggregateException
For Each e In ae.InnerExceptions
Console.WriteLine("{0}:{2} {1}", e.GetType().Name, e.Message,
vbCrLf)
Next
End Try
End Sub

Sub ExecuteTasks()
' Assume this is a user-entered String.
Dim path = "C:\"
Dim tasks As New List(Of Task)

tasks.Add(Task.Run(Function()
' This should throw an UnauthorizedAccessException.
Return Directory.GetFiles(path, "*.txt",
SearchOption.AllDirectories)
End Function))

tasks.Add(Task.Run(Function()
If path = "C:\" Then
Throw New ArgumentException("The system root is not a valid path.")
End If
Return {".txt", ".dll", ".exe", ".bin", ".dat"}
End Function))

tasks.Add(Task.Run(Sub()
Throw New NotImplementedException("This operation has not been
implemented.")
End Sub))

Try
Task.WaitAll(tasks.ToArray)
Catch ae As AggregateException
Throw ae.Flatten()
End Try
End Sub
End Module
' The example displays the following output:
' UnauthorizedAccessException:
' Access to the path 'C:\Documents and Settings' is denied.
' ArgumentException:
' The system root is not a valid path.
' NotImplementedException:
' This operation has not been implemented.

Exceptions des tâches enfants détachées


Par défaut, les tâches enfants sont créées détachées. Les exceptions levées depuis des tâches détachées doivent
être gérées ou levées à nouveau dans la tâche parent immédiate. Elles ne sont pas propagées vers le thread
appelant de la même façon que les tâches enfants attachées. Le parent le plus haut peut lever à nouveau
manuellement une exception à partir d’un enfant détaché pour l’encapsuler dans une exception
AggregateException et la propager vers le thread appelant.
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var task1 = Task.Run(() => {
var nested1 = Task.Run(() => {
throw new CustomException("Detached child task faulted.");
});

// Here the exception will be escalated back to the calling thread.


// We could use try/catch here to prevent that.
nested1.Wait();
});

try {
task1.Wait();
}
catch (AggregateException ae) {
foreach (var e in ae.Flatten().InnerExceptions) {
if (e is CustomException) {
Console.WriteLine(e.Message);
}
}
}
}
}

public class CustomException : Exception


{
public CustomException(String message) : base(message)
{}
}
// The example displays the following output:
// Detached child task faulted.
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task.Run(Sub()
Dim nestedTask1 = Task.Run(Sub()
Throw New CustomException("Detached child
task faulted.")
End Sub)
' Here the exception will be escalated back to joining thread.
' We could use try/catch here to prevent that.
nestedTask1.Wait()
End Sub)

Try
task1.Wait()
Catch ae As AggregateException
For Each ex In ae.Flatten().InnerExceptions
If TypeOf ex Is CustomException Then
' Recover from the exception. Here we just
' print the message for demonstration purposes.
Console.WriteLine(ex.Message)
End If
Next
End Try
End Sub
End Module

Class CustomException : Inherits Exception


Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' Detached child task faulted.

Même si vous utilisez une continuation pour observer une exception dans une tâche enfant, l’exception doit
toujours être observée par la tâche parent.

Exceptions indiquant une annulation coopérative


Lorsque le code utilisateur d’une tâche répond à une demande d’annulation, la procédure correcte consiste à
lever une exception OperationCanceledException qui passe le jeton d’annulation sur lequel la demande a été
communiquée. Avant d’essayer de propager l’exception, l’instance de tâche compare le jeton de l’exception à
celui qui lui a été passé lors de sa création. S’ils sont identiques, la tâche propage une exception
TaskCanceledException encapsulée dans l’exception AggregateException, et cette dernière peut être affichée au
moment d’examiner les exceptions internes. Toutefois, si le thread appelant n’est pas en attente sur la tâche,
cette exception spécifique n’est pas propagée. Pour plus d'informations, consultez Task Cancellation.
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

var task1 = Task.Factory.StartNew(() =>


{
CancellationToken ct = token;
while (someCondition)
{
// Do some work...
Thread.SpinWait(50000);
ct.ThrowIfCancellationRequested();
}
},
token);

// No waiting required.
tokenSource.Dispose();

Dim someCondition As Boolean = True


Dim tokenSource = New CancellationTokenSource()
Dim token = tokenSource.Token

Dim task1 = Task.Factory.StartNew(Sub()


Dim ct As CancellationToken = token
While someCondition = True
' Do some work...
Thread.SpinWait(500000)
ct.ThrowIfCancellationRequested()
End While
End Sub,
token)

Utilisation de la méthode Handle pour filtrer les exceptions internes


Vous pouvez utiliser la méthode AggregateException.Handle pour éliminer les exceptions que vous pouvez
traiter comme « gérées » sans utiliser d’autre logique. Dans le délégué utilisateur fourni à la méthode
AggregateException.Handle(Func<Exception,Boolean>) , vous pouvez examiner le type d’exception, sa propriété
Message ou toute autre information le concernant qui vous permettra de déterminer si l’exception est sans
gravité. Toutes les exceptions pour lesquelles le délégué retourne la valeur false sont levées à nouveau dans
une nouvelle instance AggregateException dès le retour de la méthode AggregateException.Handle.
L’exemple suivant est fonctionnellement équivalent au premier exemple de cette rubrique, qui examine chaque
exception de la collection AggregateException.InnerExceptions. À la place, ce gestionnaire d’exceptions appelle
l’objet de la méthode AggregateException.Handle pour chaque exception et lève à nouveau uniquement les
exceptions qui ne sont pas des instances CustomException .
using System;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var task1 = Task.Run( () => { throw new CustomException("This exception is expected!"); } );

try {
task1.Wait();
}
catch (AggregateException ae)
{
// Call the Handle method to handle the custom exception,
// otherwise rethrow the exception.
ae.Handle(ex => { if (ex is CustomException)
Console.WriteLine(ex.Message);
return ex is CustomException;
});
}
}
}

public class CustomException : Exception


{
public CustomException(String message) : base(message)
{}
}
// The example displays the following output:
// This exception is expected!

Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task.Run(Sub() Throw New CustomException("This exception is expected!"))

Try
task1.Wait()
Catch ae As AggregateException
' Call the Handle method to handle the custom exception,
' otherwise rethrow the exception.
ae.Handle(Function(e)
If TypeOf e Is CustomException Then
Console.WriteLine(e.Message)
End If
Return TypeOf e Is CustomException
End Function)
End Try
End Sub
End Module

Class CustomException : Inherits Exception


Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays the following output:
' This exception is expected!

L’exemple suivant est plus complet et utilise la méthode AggregateException.Handle pour fournir une gestion
spéciale pour une exception UnauthorizedAccessException lors de l’énumération des fichiers.
using System;
using System.IO;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
// This should throw an UnauthorizedAccessException.
try {
var files = GetAllFiles(@"C:\");
if (files != null)
foreach (var file in files)
Console.WriteLine(file);
}
catch (AggregateException ae) {
foreach (var ex in ae.InnerExceptions)
Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);
}
Console.WriteLine();

// This should throw an ArgumentException.


try {
foreach (var s in GetAllFiles(""))
Console.WriteLine(s);
}
catch (AggregateException ae) {
foreach (var ex in ae.InnerExceptions)
Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);
}
}

static string[] GetAllFiles(string path)


{
var task1 = Task.Run( () => Directory.GetFiles(path, "*.txt",
SearchOption.AllDirectories));

try {
return task1.Result;
}
catch (AggregateException ae) {
ae.Handle( x => { // Handle an UnauthorizedAccessException
if (x is UnauthorizedAccessException) {
Console.WriteLine("You do not have permission to access all folders in this
path.");
Console.WriteLine("See your network administrator or try another path.");
}
return x is UnauthorizedAccessException;
});
return Array.Empty<String>();
}
}
}
// The example displays the following output:
// You do not have permission to access all folders in this path.
// See your network administrator or try another path.
//
// ArgumentException: The path is not of a legal form.
Imports System.IO
Imports System.Threading.Tasks

Module Example
Public Sub Main()
' This should throw an UnauthorizedAccessException.
Try
Dim files = GetAllFiles("C:\")
If files IsNot Nothing Then
For Each file In files
Console.WriteLine(file)
Next
End If
Catch ae As AggregateException
For Each ex In ae.InnerExceptions
Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message)
Next
End Try
Console.WriteLine()

' This should throw an ArgumentException.


Try
For Each s In GetAllFiles("")
Console.WriteLine(s)
Next
Catch ae As AggregateException
For Each ex In ae.InnerExceptions
Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message)
Next
End Try
Console.WriteLine()
End Sub

Function GetAllFiles(ByVal path As String) As String()


Dim task1 = Task.Run(Function()
Return Directory.GetFiles(path, "*.txt",
SearchOption.AllDirectories)
End Function)
Try
Return task1.Result
Catch ae As AggregateException
ae.Handle(Function(x)
' Handle an UnauthorizedAccessException
If TypeOf x Is UnauthorizedAccessException Then
Console.WriteLine("You do not have permission to access all folders in this
path.")
Console.WriteLine("See your network administrator or try another path.")
End If
Return TypeOf x Is UnauthorizedAccessException
End Function)
End Try
Return Array.Empty(Of String)()
End Function
End Module
' The example displays the following output:
' You do not have permission to access all folders in this path.
' See your network administrator or try another path.
'
' ArgumentException: The path is not of a legal form.

Observation d’exceptions à l’aide de la propriété Task.Exception


Si une tâche se termine avec l’état TaskStatus.Faulted, il est possible d’examiner sa propriété Exception pour
découvrir l’exception spécifique qui a provoqué l’erreur. Un bon moyen d’observer la propriété Exception
consiste à utiliser une continuation qui s’exécute uniquement en cas d’erreur de la tâche antérieure, comme
indiqué dans l’exemple suivant.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static void Main()
{
var task1 = Task.Run(() =>
{ throw new CustomException("task1 faulted.");
}).ContinueWith( t => { Console.WriteLine("{0}: {1}",
t.Exception.InnerException.GetType().Name,
t.Exception.InnerException.Message);
}, TaskContinuationOptions.OnlyOnFaulted);
Thread.Sleep(500);
}
}

public class CustomException : Exception


{
public CustomException(String message) : base(message)
{}
}
// The example displays output like the following:
// CustomException: task1 faulted.

Imports System.Threading
Imports System.Threading.Tasks

Module Example
Public Sub Main()
Dim task1 = Task.Factory.StartNew(Sub()
Throw New CustomException("task1 faulted.")
End Sub).
ContinueWith(Sub(t)
Console.WriteLine("{0}: {1}",
t.Exception.InnerException.GetType().Name,
t.Exception.InnerException.Message)
End Sub, TaskContinuationOptions.OnlyOnFaulted)

Thread.Sleep(500)
End Sub
End Module

Class CustomException : Inherits Exception


Public Sub New(s As String)
MyBase.New(s)
End Sub
End Class
' The example displays output like the following:
' CustomException: task1 faulted.

Dans une application significative, le délégué de continuation peut consigner des informations détaillées sur
l’exception et éventuellement générer de nouvelles tâches pour récupérer à partir de l’exception. En cas de
défaillance d’une tâche, les expressions suivantes lèvent l’exception :
await task
task.Wait()
task.Result
task.GetAwaiter().GetResult()
Utilisez une try-catch instruction pour gérer et observer les exceptions levées. Vous pouvez également
observer l’exception en accédant à la Task.Exception propriété.

Événement UnobservedTaskException
Dans certains scénarios, tels que l’hébergement de plug-ins non approuvés, des exceptions sans gravité sont
courantes. Il peut s’avérer trop difficile de toutes les observer manuellement. Dans de tels cas, vous pouvez
gérer l’événement TaskScheduler.UnobservedTaskException. Il est possible d’utiliser l’instance
System.Threading.Tasks.UnobservedTaskExceptionEventArgs passée à votre gestionnaire pour empêcher la
propagation de l’exception non prise en charge vers le thread joint.

Voir aussi
Bibliothèque parallèle de tâches
Procédure : utiliser Parallel_Invoke pour exécuter des
opérations parallèles
18/07/2020 • 5 minutes to read • Edit Online

Cet exemple indique comment paralléliser des opérations à l'aide de Invoke dans la bibliothèque parallèle de
tâches. Trois opérations sont effectuées sur la source de données partagée. Les opérations peuvent être exécutées
en parallèle de manière simple, car aucune d’elles ne modifie la source.

NOTE
Cette documentation utilise les expressions lambda pour définir les délégués de la bibliothèque parallèle de tâches. Si vous
n’êtes pas familiarisé avec les expressions lambda en C# ou Visual Basic, consultez expressions lambda en PLINQ et tpl.

Exemple
namespace ParallelTasks
{
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net;

class ParallelInvoke
{
static void Main()
{
// Retrieve Goncharov's "Oblomov" from Gutenberg.org.
string[] words = CreateWordArray(@"http://www.gutenberg.org/files/54700/54700-0.txt");

#region ParallelTasks
// Perform three tasks in parallel on the source array
Parallel.Invoke(() =>
{
Console.WriteLine("Begin first task...");
GetLongestWord(words);
}, // close first Action

() =>
{
Console.WriteLine("Begin second task...");
GetMostCommonWords(words);
}, //close second Action

() =>
{
Console.WriteLine("Begin third task...");
GetCountForWord(words, "sleep");
} //close third Action
); //close parallel.invoke

Console.WriteLine("Returned from Parallel.Invoke");


#endregion

Console.WriteLine("Press any key to exit");


Console.ReadKey();
}

#region HelperMethods
private static void GetCountForWord(string[] words, string term)
{
var findWord = from word in words
where word.ToUpper().Contains(term.ToUpper())
select word;

Console.WriteLine($@"Task 3 -- The word ""{term}"" occurs {findWord.Count()} times.");


}

private static void GetMostCommonWords(string[] words)


{
var frequencyOrder = from word in words
where word.Length > 6
group word by word into g
orderby g.Count() descending
select g.Key;

var commonWords = frequencyOrder.Take(10);

StringBuilder sb = new StringBuilder();


sb.AppendLine("Task 2 -- The most common words are:");
foreach (var v in commonWords)
{
sb.AppendLine(" " + v);
}
Console.WriteLine(sb.ToString());
}

private static string GetLongestWord(string[] words)


{
var longestWord = (from w in words
orderby w.Length descending
select w).First();

Console.WriteLine($"Task 1 -- The longest word is {longestWord}.");


return longestWord;
}

// An http request performed synchronously for simplicity.


static string[] CreateWordArray(string uri)
{
Console.WriteLine($"Retrieving from {uri}");

// Download a web page the easy way.


string s = new WebClient().DownloadString(uri);

// Separate string into an array of words, removing some common punctuation.


return s.Split(
new char[] { ' ', '\u000A', ',', '.', ';', ':', '-', '_', '/' },
StringSplitOptions.RemoveEmptyEntries);
}
#endregion
}
}
// The example displays output like the following:
// Retrieving from http://www.gutenberg.org/files/54700/54700-0.txt
// Begin first task...
// Begin second task...
// Begin third task...
// Task 2 -- The most common words are:
// Oblomov
// himself
// Schtoltz
// Gutenberg
// Project
// another
// thought
// Oblomov's
// nothing
// replied
//
// Task 1 -- The longest word is incomprehensible.
// Task 3 -- The word "sleep" occurs 57 times.
// Returned from Parallel.Invoke
// Press any key to exit

Imports System.Net
Imports System.Threading.Tasks

Module ParallelTasks
Sub Main()
' Retrieve Goncharov's "Oblomov" from Gutenberg.org.
Dim words As String() = CreateWordArray("http://www.gutenberg.org/files/54700/54700-0.txt")

'#Region "ParallelTasks"
' Perform three tasks in parallel on the source array
Parallel.Invoke(Sub()
Console.WriteLine("Begin first task...")
GetLongestWord(words)
' close first Action
End Sub,
Sub()
Console.WriteLine("Begin second task...")
GetMostCommonWords(words)
'close second Action
End Sub,
Sub()
Console.WriteLine("Begin third task...")
GetCountForWord(words, "sleep")
'close third Action
End Sub)
'close parallel.invoke
Console.WriteLine("Returned from Parallel.Invoke")
'#End Region

Console.WriteLine("Press any key to exit")


Console.ReadKey()
End Sub

#Region "HelperMethods"
Sub GetCountForWord(ByVal words As String(), ByVal term As String)
Dim findWord = From word In words
Where word.ToUpper().Contains(term.ToUpper())
Select word

Console.WriteLine($"Task 3 -- The word ""{term}"" occurs {findWord.Count()} times.")


End Sub

Sub GetMostCommonWords(ByVal words As String())


Dim frequencyOrder = From word In words
Where word.Length > 6
Group By word
Into wordGroup = Group, Count()
Order By wordGroup.Count() Descending
Select wordGroup

Dim commonWords = From grp In frequencyOrder


Select grp
Take (10)

Dim s As String
s = "Task 2 -- The most common words are:" & vbCrLf
For Each v In commonWords
s = s & v(0) & vbCrLf
Next
Console.WriteLine(s)
End Sub

Function GetLongestWord(ByVal words As String()) As String


Dim longestWord = (From w In words
Order By w.Length Descending
Select w).First()

Console.WriteLine($"Task 1 -- The longest word is {longestWord}.")


Return longestWord
End Function

' An http request performed synchronously for simplicity.


Function CreateWordArray(ByVal uri As String) As String()
Console.WriteLine($"Retrieving from {uri}")

' Download a web page the easy way.


Dim s As String = New WebClient().DownloadString(uri)

' Separate string into an array of words, removing some common punctuation.
Return s.Split(New Char() {" "c, ControlChars.Lf, ","c, "."c, ";"c, ":"c,
"-"c, "_"c, "/"c}, StringSplitOptions.RemoveEmptyEntries)
End Function
#End Region
End Module
' The exmaple displays output like the following:
' Retrieving from http://www.gutenberg.org/files/54700/54700-0.txt
' Begin first task...
' Begin second task...
' Begin third task...
' Task 2 -- The most common words are:
' Oblomov
' himself
' Schtoltz
' Gutenberg
' Project
' another
' thought
' Oblomov's
' nothing
' replied
'
' Task 1 -- The longest word is incomprehensible.
' Task 3 -- The word "sleep" occurs 57 times.
' Returned from Parallel.Invoke
' Press any key to exit

Avec Invoke , vous exprimez simplement les actions que vous souhaitez exécuter simultanément, et le Runtime
gère tous les détails de planification de thread, y compris la mise à l’échelle automatique jusqu’au nombre de
cœurs sur l’ordinateur hôte.
Cet exemple parallélise les opérations et non les données. Vous pouvez également paralléliser les requêtes LINQ à
l'aide de PLINQ et exécuter les requêtes séquentiellement. Vous pouvez aussi paralléliser les données à l'aide de
PLINQ. Une autre option consiste à paralléliser à la fois les requêtes et les tâches. Bien que la surcharge résultante
puisse dégrader les performances sur les ordinateurs hôtes avec relativement peu de processeurs, elle s’adapte
mieux aux ordinateurs avec de nombreux processeurs.

Compiler le code
Copiez et collez l'exemple entier dans un projet Microsoft Visual Studio et appuyez sur F5 .
Voir aussi
Programmation parallèle
Procédure : annuler une tâche et ses enfants
Parallel LINQ (PLINQ)
Procédure : retourner une valeur à partir d’une tâche
18/07/2020 • 2 minutes to read • Edit Online

Cet exemple montre comment utiliser le type System.Threading.Tasks.Task<TResult> pour retourner une valeur à
partir de la propriété Result. Il nécessite que le répertoire C:\Users\Public\Pictures\Sample Pictures\ existe et qu'il
contienne des fichiers.

Exemple
using System;
using System.Linq;
using System.Threading.Tasks;

class Program
{
static void Main()
{
// Return a value type with a lambda expression
Task<int> task1 = Task<int>.Factory.StartNew(() => 1);
int i = task1.Result;

// Return a named reference type with a multi-line statement lambda.


Task<Test> task2 = Task<Test>.Factory.StartNew(() =>
{
string s = ".NET";
double d = 4.0;
return new Test { Name = s, Number = d };
});
Test test = task2.Result;

// Return an array produced by a PLINQ query


Task<string[]> task3 = Task<string[]>.Factory.StartNew(() =>
{
string path = @"C:\Users\Public\Pictures\Sample Pictures\";
string[] files = System.IO.Directory.GetFiles(path);

var result = (from file in files.AsParallel()


let info = new System.IO.FileInfo(file)
where info.Extension == ".jpg"
select file).ToArray();

return result;
});

foreach (var name in task3.Result)


Console.WriteLine(name);
}
class Test
{
public string Name { get; set; }
public double Number { get; set; }
}
}
Imports System.Threading.Tasks

Module Module1

Sub Main()
ReturnAValue()

Console.WriteLine("Press any key to exit.")


Console.ReadKey()

End Sub

Sub ReturnAValue()

' Return a value type with a lambda expression


Dim task1 = Task(Of Integer).Factory.StartNew(Function() 1)
Dim i As Integer = task1.Result

' Return a named reference type with a multi-line statement lambda.


Dim task2 As Task(Of Test) = Task.Factory.StartNew(Function()
Dim s As String = ".NET"
Dim d As Integer = 4
Return New Test With {.Name = s, .Number = d}
End Function)

Dim myTest As Test = task2.Result


Console.WriteLine(myTest.Name & ": " & myTest.Number)

' Return an array produced by a PLINQ query.


Dim task3 As Task(Of String()) = Task(Of String()).Factory.StartNew(Function()

Dim path =
"C:\Users\Public\Pictures\Sample Pictures\"
Dim files =
System.IO.Directory.GetFiles(path)

Dim result = (From file In


files.AsParallel()
Let info = New
System.IO.FileInfo(file)
Where
info.Extension = ".jpg"
Select
file).ToArray()
Return result
End Function)

For Each name As String In task3.Result


Console.WriteLine(name)
Next
End Sub

Class Test
Public Name As String
Public Number As Double
End Class
End Module

La propriété Result bloque le thread appelant jusqu'à ce que la tâche se termine.


Pour savoir comment passer le résultat d’une System.Threading.Tasks.Task<TResult> à une tâche de continuation,
consultez Chaînage des tâches à l’aide de tâches de continuation.

Voir aussi
Programmation asynchrone basée sur les tâches
Expressions lambda dans PLINQ et TPL
Procédure : annuler une tâche et ses enfants
18/07/2020 • 8 minutes to read • Edit Online

Ces exemples montrent comment effectuer les tâches suivantes :


1. Créer et lancer une tâche annulable
2. Passer un jeton d’annulation à votre délégué d’utilisateur et éventuellement à l’instance de tâche
3. Notifier et répondre à la demande d’annulation dans votre délégué d’utilisateur
4. Éventuellement notifier sur le thread appelant que la tâche a été annulée
Le thread appelant ne force pas l’arrêt de la tâche. Il signale seulement que son annulation a été demandée. Si la
tâche est déjà en cours d’exécution, il incombe au délégué d’utilisateur de notifier la demande et d’y répondre de
façon appropriée. Si l’annulation a été demandée avant l’exécution de la tâche, le délégué d’utilisateur n’est
jamais exécuté et l’objet de tâche passe à l’état Canceled.

Exemple
Cet exemple montre comment arrêter une Task et ses enfants en réponse à une demande d’annulation. Il montre
également que quand un délégué d’utilisateur se termine en levant une TaskCanceledException, le thread
appelant peut éventuellement utiliser la méthode Wait ou WaitAllpour attendre la fin des tâches. Dans ce cas,
vous devez utiliser un bloc try/catch pour gérer les exceptions sur le thread appelant.

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

public class Example


{
public static async Task Main()
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

// Store references to the tasks so that we can wait on them and


// observe their status after cancellation.
Task t;
var tasks = new ConcurrentBag<Task>();

Console.WriteLine("Press any key to begin tasks...");


Console.ReadKey(true);
Console.WriteLine("To terminate the example, press 'c' to cancel and exit...");
Console.WriteLine();

// Request cancellation of a single task when the token source is canceled.


// Pass the token to the user delegate, and also to the task so it can
// handle the exception correctly.
t = Task.Run(() => DoSomeWork(1, token), token);
Console.WriteLine("Task {0} executing", t.Id);
tasks.Add(t);

// Request cancellation of a task and its children. Note the token is passed
// to (1) the user delegate and (2) as the second argument to Task.Run, so
// that the task instance can correctly handle the OperationCanceledException.
t = Task.Run(() =>
{
{
// Create some cancelable child tasks.
Task tc;
for (int i = 3; i <= 10; i++)
{
// For each child task, pass the same token
// to each user delegate and to Task.Run.
tc = Task.Run(() => DoSomeWork(i, token), token);
Console.WriteLine("Task {0} executing", tc.Id);
tasks.Add(tc);
// Pass the same token again to do work on the parent task.
// All will be signaled by the call to tokenSource.Cancel below.
DoSomeWork(2, token);
}
}, token);

Console.WriteLine("Task {0} executing", t.Id);


tasks.Add(t);

// Request cancellation from the UI thread.


char ch = Console.ReadKey().KeyChar;
if (ch == 'c' || ch == 'C')
{
tokenSource.Cancel();
Console.WriteLine("\nTask cancellation requested.");

// Optional: Observe the change in the Status property on the task.


// It is not necessary to wait on tasks that have canceled. However,
// if you do wait, you must enclose the call in a try-catch block to
// catch the TaskCanceledExceptions that are thrown. If you do
// not wait, no exception is thrown if the token that was passed to the
// Task.Run method is the same token that requested the cancellation.
}

try
{
await Task.WhenAll(tasks.ToArray());
}
catch (OperationCanceledException)
{
Console.WriteLine($"\n{nameof(OperationCanceledException)} thrown\n");
}
finally
{
tokenSource.Dispose();
}

// Display status of all tasks.


foreach (var task in tasks)
Console.WriteLine("Task {0} status is now {1}", task.Id, task.Status);
}

static void DoSomeWork(int taskNum, CancellationToken ct)


{
// Was cancellation already requested?
if (ct.IsCancellationRequested)
{
Console.WriteLine("Task {0} was cancelled before it got started.",
taskNum);
ct.ThrowIfCancellationRequested();
}

int maxIterations = 100;

// NOTE!!! A "TaskCanceledException was unhandled


// by user code" error will be raised here if "Just My Code"
// is enabled on your computer. On Express editions JMC is
// enabled and cannot be disabled. The exception is benign.
// Just press F5 to continue executing your code.
for (int i = 0; i <= maxIterations; i++)
for (int i = 0; i <= maxIterations; i++)
{
// Do a bit of work. Not too much.
var sw = new SpinWait();
for (int j = 0; j <= 100; j++)
sw.SpinOnce();

if (ct.IsCancellationRequested)
{
Console.WriteLine("Task {0} cancelled", taskNum);
ct.ThrowIfCancellationRequested();
}
}
}
}
// The example displays output like the following:
// Press any key to begin tasks...
// To terminate the example, press 'c' to cancel and exit...
//
// Task 1 executing
// Task 2 executing
// Task 3 executing
// Task 4 executing
// Task 5 executing
// Task 6 executing
// Task 7 executing
// Task 8 executing
// c
// Task cancellation requested.
// Task 2 cancelled
// Task 7 cancelled
//
// OperationCanceledException thrown
//
// Task 2 status is now Canceled
// Task 1 status is now RanToCompletion
// Task 8 status is now Canceled
// Task 7 status is now Canceled
// Task 6 status is now RanToCompletion
// Task 5 status is now RanToCompletion
// Task 4 status is now RanToCompletion
// Task 3 status is now RanToCompletion

Imports System.Collections.Concurrent
Imports System.Threading
Imports System.Threading.Tasks

Module Example
Sub Main()
Dim tokenSource As New CancellationTokenSource()
Dim token As CancellationToken = tokenSource.Token

' Store references to the tasks so that we can wait on them and
' observe their status after cancellation.
Dim t As Task
Dim tasks As New ConcurrentBag(Of Task)()

Console.WriteLine("Press any key to begin tasks...")


Console.ReadKey(True)
Console.WriteLine("To terminate the example, press 'c' to cancel and exit...")
Console.WriteLine()

' Request cancellation of a single task when the token source is canceled.
' Pass the token to the user delegate, and also to the task so it can
' handle the exception correctly.
t = Task.Factory.StartNew(Sub() DoSomeWork(1, token), token)
Console.WriteLine("Task {0} executing", t.Id)
tasks.Add(t)
tasks.Add(t)

' Request cancellation of a task and its children. Note the token is passed
' to (1) the user delegate and (2) as the second argument to StartNew, so
' that the task instance can correctly handle the OperationCanceledException.
t = Task.Factory.StartNew(Sub()
' Create some cancelable child tasks.
Dim tc As Task
For i As Integer = 3 To 10
' For each child task, pass the same token
' to each user delegate and to StartNew.
tc = Task.Factory.StartNew(Sub(iteration) DoSomeWork(iteration,
token), i, token)
Console.WriteLine("Task {0} executing", tc.Id)
tasks.Add(tc)
' Pass the same token again to do work on the parent task.
' All will be signaled by the call to tokenSource.Cancel below.
DoSomeWork(2, token)
Next
End Sub,
token)

Console.WriteLine("Task {0} executing", t.Id)


tasks.Add(t)

' Request cancellation from the UI thread.


Dim ch As Char = Console.ReadKey().KeyChar
If ch = "c"c Or ch = "C"c Then
tokenSource.Cancel()
Console.WriteLine(vbCrLf + "Task cancellation requested.")

' Optional: Observe the change in the Status property on the task.
' It is not necessary to wait on tasks that have canceled. However,
' if you do wait, you must enclose the call in a try-catch block to
' catch the TaskCanceledExceptions that are thrown. If you do
' not wait, no exception is thrown if the token that was passed to the
' StartNew method is the same token that requested the cancellation.
End If

Try
Task.WaitAll(tasks.ToArray())
Catch e As AggregateException
Console.WriteLine()
Console.WriteLine("AggregateException thrown with the following inner exceptions:")
' Display information about each exception.
For Each v In e.InnerExceptions
If TypeOf v Is TaskCanceledException
Console.WriteLine(" TaskCanceledException: Task {0}",
DirectCast(v, TaskCanceledException).Task.Id)
Else
Console.WriteLine(" Exception: {0}", v.GetType().Name)
End If
Next
Console.WriteLine()
Finally
tokenSource.Dispose()
End Try

' Display status of all tasks.


For Each t In tasks
Console.WriteLine("Task {0} status is now {1}", t.Id, t.Status)
Next
End Sub

Sub DoSomeWork(ByVal taskNum As Integer, ByVal ct As CancellationToken)


' Was cancellation already requested?
If ct.IsCancellationRequested = True Then
Console.WriteLine("Task {0} was cancelled before it got started.",
taskNum)
ct.ThrowIfCancellationRequested()
ct.ThrowIfCancellationRequested()
End If

Dim maxIterations As Integer = 100

' NOTE!!! A "TaskCanceledException was unhandled


' by user code" error will be raised here if "Just My Code"
' is enabled on your computer. On Express editions JMC is
' enabled and cannot be disabled. The exception is benign.
' Just press F5 to continue executing your code.
For i As Integer = 0 To maxIterations
' Do a bit of work. Not too much.
Dim sw As New SpinWait()
For j As Integer = 0 To 100
sw.SpinOnce()
Next
If ct.IsCancellationRequested Then
Console.WriteLine("Task {0} cancelled", taskNum)
ct.ThrowIfCancellationRequested()
End If
Next
End Sub
End Module
' The example displays output like the following:
' Press any key to begin tasks...
' To terminate the example, press 'c' to cancel and exit...
'
' Task 1 executing
' Task 2 executing
' Task 3 executing
' Task 4 executing
' Task 5 executing
' Task 6 executing
' Task 7 executing
' Task 8 executing
' c
' Task cancellation requested.
' Task 2 cancelled
' Task 7 cancelled
'
' AggregateException thrown with the following inner exceptions:
' TaskCanceledException: Task 2
' TaskCanceledException: Task 8
' TaskCanceledException: Task 7
'
' Task 2 status is now Canceled
' Task 1 status is now RanToCompletion
' Task 8 status is now Canceled
' Task 7 status is now Canceled
' Task 6 status is now RanToCompletion
' Task 5 status is now RanToCompletion
' Task 4 status is now RanToCompletion
' Task 3 status is now RanToCompletion

La classe System.Threading.Tasks.Task est entièrement intégrée au modèle d’annulation qui est basé sur les types
System.Threading.CancellationTokenSource et System.Threading.CancellationToken. Pour plus d’informations,
consultez Annulation dans les threads managés et Annulation de tâches.

Voir aussi
System.Threading.CancellationTokenSource
System.Threading.CancellationToken
System.Threading.Tasks.Task
System.Threading.Tasks.Task<TResult>
Programmation asynchrone basée sur les tâches
Tâches enfants attachées et détachées
Expressions lambda dans PLINQ et TPL
Procédure : créer des tâches précalculées
18/07/2020 • 4 minutes to read • Edit Online

Ce document décrit comment utiliser la méthode Task.FromResult pour récupérer les résultats d’opérations de
téléchargement asynchrones qui sont conservées dans un cache. La méthode FromResult retourne un objet
Task<TResult> fini qui contient la valeur fournie en tant que sa propriété Result. Cette méthode est utile lorsque
vous exécutez une opération asynchrone qui retourne un objet Task<TResult>, et que le résultat de cet objet
Task<TResult> est déjà calculé.

Exemple
L’exemple suivant télécharge des chaînes à partir du web. Il définit la méthode DownloadStringAsync . Cette
méthode télécharge des chaînes à partir du web de façon asynchrone. Cet exemple utilise également un objet
ConcurrentDictionary<TKey,TValue> pour mettre en cache les résultats des opérations précédentes. Si l’adresse
d’entrée est conservée dans ce cache, DownloadStringAsync utilise la méthode FromResult pour produire un objet
Task<TResult> qui contient le contenu à cette adresse. Dans le cas contraire, DownloadStringAsync télécharge le
fichier à partir du web et ajoute le résultat dans le cache.

using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

// Demonstrates how to use Task<TResult>.FromResult to create a task


// that holds a pre-computed result.
class CachedDownloads
{
// Holds the results of download operations.
static ConcurrentDictionary<string, string> cachedDownloads =
new ConcurrentDictionary<string, string>();

// Asynchronously downloads the requested resource as a string.


public static Task<string> DownloadStringAsync(string address)
{
// First try to retrieve the content from cache.
string content;
if (cachedDownloads.TryGetValue(address, out content))
{
return Task.FromResult<string>(content);
}

// If the result was not in the cache, download the


// string and add it to the cache.
return Task.Run(async () =>
{
content = await new WebClient().DownloadStringTaskAsync(address);
cachedDownloads.TryAdd(address, content);
return content;
});
}

static void Main(string[] args)


{
// The URLs to download.
string[] urls = new string[]
{
"http://msdn.microsoft.com",
"http://msdn.microsoft.com",
"http://www.contoso.com",
"http://www.microsoft.com"
};

// Used to time download operations.


Stopwatch stopwatch = new Stopwatch();

// Compute the time required to download the URLs.


stopwatch.Start();
var downloads = from url in urls
select DownloadStringAsync(url);
Task.WhenAll(downloads).ContinueWith(results =>
{
stopwatch.Stop();

// Print the number of characters download and the elapsed time.


Console.WriteLine("Retrieved {0} characters. Elapsed time was {1} ms.",
results.Result.Sum(result => result.Length),
stopwatch.ElapsedMilliseconds);
})
.Wait();

// Perform the same operation a second time. The time required


// should be shorter because the results are held in the cache.
stopwatch.Restart();
downloads = from url in urls
select DownloadStringAsync(url);
Task.WhenAll(downloads).ContinueWith(results =>
{
stopwatch.Stop();

// Print the number of characters download and the elapsed time.


Console.WriteLine("Retrieved {0} characters. Elapsed time was {1} ms.",
results.Result.Sum(result => result.Length),
stopwatch.ElapsedMilliseconds);
})
.Wait();
}
}

/* Sample output:
Retrieved 27798 characters. Elapsed time was 1045 ms.
Retrieved 27798 characters. Elapsed time was 0 ms.
*/

Imports System.Collections.Concurrent
Imports System.Diagnostics
Imports System.Linq
Imports System.Net
Imports System.Threading.Tasks

' Demonstrates how to use Task<TResult>.FromResult to create a task


' that holds a pre-computed result.
Friend Class CachedDownloads
' Holds the results of download operations.
Private Shared cachedDownloads As New ConcurrentDictionary(Of String, String)()

' Asynchronously downloads the requested resource as a string.


Public Shared Function DownloadStringAsync(ByVal address As String) As Task(Of String)
' First try to retrieve the content from cache.
Dim content As String
If cachedDownloads.TryGetValue(address, content) Then
Return Task.FromResult(Of String)(content)
End If

' If the result was not in the cache, download the


' string and add it to the cache.
' string and add it to the cache.
Return Task.Run(async Function()
content = await New WebClient().DownloadStringTaskAsync(address)
cachedDownloads.TryAdd(address, content)
Return content
End Function)
End Function

Shared Sub Main(ByVal args() As String)


' The URLs to download.
Dim urls() As String = {"http://msdn.microsoft.com", "http://www.contoso.com",
"http://www.microsoft.com"}

' Used to time download operations.


Dim stopwatch As New Stopwatch()

' Compute the time required to download the URLs.


stopwatch.Start()
Dim downloads = From url In urls _
Select DownloadStringAsync(url)
Task.WhenAll(downloads).ContinueWith(Sub(results)
' Print the number of characters download and the elapsed
time.
stopwatch.Stop()
Console.WriteLine("Retrieved {0} characters. Elapsed time was
{1} ms.", results.Result.Sum(Function(result) result.Length), stopwatch.ElapsedMilliseconds)
End Sub).Wait()

' Perform the same operation a second time. The time required
' should be shorter because the results are held in the cache.
stopwatch.Restart()
downloads = From url In urls _
Select DownloadStringAsync(url)
Task.WhenAll(downloads).ContinueWith(Sub(results)
' Print the number of characters download and the elapsed
time.
stopwatch.Stop()
Console.WriteLine("Retrieved {0} characters. Elapsed time was
{1} ms.", results.Result.Sum(Function(result) result.Length), stopwatch.ElapsedMilliseconds)
End Sub).Wait()
End Sub
End Class

' Sample output:


'Retrieved 27798 characters. Elapsed time was 1045 ms.
'Retrieved 27798 characters. Elapsed time was 0 ms.
'

Cet exemple calcule le temps nécessaire pour télécharger plusieurs chaînes deux fois. Le deuxième ensemble
d’opérations de téléchargement doit prendre moins de temps que le premier, car les résultats sont conservés dans
le cache. La méthode FromResult permet à la méthode DownloadStringAsync de créer des objets Task<TResult> qui
contiennent ces résultats pré-calculés.

Voir aussi
Programmation asynchrone basée sur les tâches
Procédure : parcourir un arbre binaire avec des
tâches parallèles
18/07/2020 • 2 minutes to read • Edit Online

L’exemple suivant montre deux façons d’utiliser des tâches parallèles pour parcourir une arborescence de données.
La création de l’arborescence en soi est considérée comme un exercice.

Exemple
public class TreeWalk
{
static void Main()
{
Tree<MyClass> tree = new Tree<MyClass>();

// ...populate tree (left as an exercise)

// Define the Action to perform on each node.


Action<MyClass> myAction = x => Console.WriteLine("{0} : {1}", x.Name, x.Number);

// Traverse the tree with parallel tasks.


DoTree(tree, myAction);
}

public class MyClass


{
public string Name { get; set; }
public int Number { get; set; }
}
public class Tree<T>
{
public Tree<T> Left;
public Tree<T> Right;
public T Data;
}

// By using tasks explcitly.


public static void DoTree<T>(Tree<T> tree, Action<T> action)
{
if (tree == null) return;
var left = Task.Factory.StartNew(() => DoTree(tree.Left, action));
var right = Task.Factory.StartNew(() => DoTree(tree.Right, action));
action(tree.Data);

try
{
Task.WaitAll(left, right);
}
catch (AggregateException )
{
//handle exceptions here
}
}

// By using Parallel.Invoke
public static void DoTree2<T>(Tree<T> tree, Action<T> action)
{
if (tree == null) return;
Parallel.Invoke(
() => DoTree2(tree.Left, action),
() => DoTree2(tree.Right, action),
() => action(tree.Data)
);
}
}
Imports System.Threading.Tasks

Public Class TreeWalk

Shared Sub Main()

Dim tree As Tree(Of Person) = New Tree(Of Person)()

' ...populate tree (left as an exercise)

' Define the Action to perform on each node.


Dim myAction As Action(Of Person) = New Action(Of Person)(Sub(x)
Console.WriteLine("{0} : {1} ", x.Name,
x.Number)
End Sub)

' Traverse the tree with parallel tasks.


DoTree(tree, myAction)
End Sub

Public Class Person


Public Name As String
Public Number As Integer
End Class

Public Class Tree(Of T)


Public Left As Tree(Of T)
Public Right As Tree(Of T)
Public Data As T
End Class

' By using tasks explicitly.


Public Shared Sub DoTree(Of T)(ByVal myTree As Tree(Of T), ByVal a As Action(Of T))
If myTree Is Nothing Then
Return
End If
Dim left = Task.Factory.StartNew(Sub() DoTree(myTree.Left, a))
Dim right = Task.Factory.StartNew(Sub() DoTree(myTree.Right, a))
a(myTree.Data)

Try
Task.WaitAll(left, right)
Catch ae As AggregateException
'handle exceptions here
End Try
End Sub

' By using Parallel.Invoke


Public Shared Sub DoTree2(Of T)(ByVal myTree As Tree(Of T), ByVal myAct As Action(Of T))
If myTree Is Nothing Then
Return
End If
Parallel.Invoke(
Sub() DoTree2(myTree.Left, myAct),
Sub() DoTree2(myTree.Right, myAct),
Sub() myAct(myTree.Data)
)
End Sub
End Class

Les deux méthodes indiquées sont équivalentes d’un point de vue fonctionnel. Si vous utilisez la méthode
StartNew pour créer et exécuter les tâches, vous obtenez un handle à partir des tâches, qui peut être utilisé pour
attendre les tâches et gérer les exceptions.

Voir aussi
Bibliothèque parallèle de tâches
Procédure : désencapsuler une tâche imbriquée
18/07/2020 • 5 minutes to read • Edit Online

Vous pouvez retourner une tâche à partir d’une méthode, puis attendre ou poursuivre à partir de cette tâche,
comme indiqué dans l’exemple suivant :

static Task<string> DoWorkAsync()


{
return Task<String>.Factory.StartNew(() =>
{
//...
return "Work completed.";
});
}

static void StartTask()


{
Task<String> t = DoWorkAsync();
t.Wait();
Console.WriteLine(t.Result);
}

Shared Function DoWorkAsync() As Task(Of String)

Return Task(Of String).Run(Function()


'...
Return "Work completed."
End Function)
End Function

Shared Sub StartTask()

Dim t As Task(Of String) = DoWorkAsync()


t.Wait()
Console.WriteLine(t.Result)
End Sub

Dans l’exemple précédent, la propriété Result est de type string ( String en Visual Basic).
Toutefois, dans certains cas, il se peut que vous souhaitiez créer une tâche dans une autre tâche, puis retourner la
tâche imbriquée. Dans ce cas, le TResult de la tâche englobante est lui-même une tâche. Dans l’exemple suivant, la
propriété Result est une Task<Task<string>> en C# ou Task(Of Task(Of String)) en Visual Basic.

// Note the type of t and t2.


Task<Task<string>> t = Task.Factory.StartNew(() => DoWorkAsync());
Task<Task<string>> t2 = DoWorkAsync().ContinueWith((s) => DoMoreWorkAsync());

// Outputs: System.Threading.Tasks.Task`1[System.String]
Console.WriteLine(t.Result);
' Note the type of t and t2.
Dim t As Task(Of Task(Of String)) = Task.Run(Function() DoWorkAsync())
Dim t2 As Task(Of Task(Of String)) = DoWorkAsync().ContinueWith(Function(s) DoMoreWorkAsync())

' Outputs: System.Threading.Tasks.Task`1[System.String]


Console.WriteLine(t.Result)

Bien qu’il soit possible d’écrire du code pour désencapsuler la tâche externe et extraire la tâche d’origine et sa
propriété Result, ce code n’est pas facile à écrire, car vous devez gérer des exceptions et des demandes
d’annulation. Dans ce cas, nous vous recommandons d’utiliser l’une des méthodes d’extension Unwrap, comme
indiqué dans l’exemple suivant.

// Unwrap the inner task.


Task<string> t3 = DoWorkAsync().ContinueWith((s) => DoMoreWorkAsync()).Unwrap();

// Outputs "More work completed."


Console.WriteLine(t.Result);

' Unwrap the inner task.


Dim t3 As Task(Of String) = DoWorkAsync().ContinueWith(Function(s) DoMoreWorkAsync()).Unwrap()

' Outputs "More work completed."


Console.WriteLine(t.Result)

Les méthodes Unwrap peuvent être utilisées pour transformer toute Task<Task> ou Task<Task<TResult>> (
Task(Of Task) ou Task(Of Task(Of TResult)) en Visual Basic) en Task ou Task<TResult> ( Task(Of TResult) en
Visual Basic). La nouvelle tâche représente entièrement la tâche imbriquée interne et inclut l’état d’annulation ainsi
que toutes les exceptions.

Exemple
L'exemple suivant décrit comment utiliser la méthode d’extension Unwrap.

namespace Unwrap
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
// A program whose only use is to demonstrate Unwrap.
class Program
{
static void Main()
{
// An arbitrary threshold value.
byte threshold = 0x40;

// data is a Task<byte[]>
var data = Task<byte[]>.Factory.StartNew(() =>
{
return GetData();
});

// We want to return a task so that we can


// continue from it later in the program.
// Without Unwrap: stepTwo is a Task<Task<byte[]>>
// With Unwrap: stepTwo is a Task<byte[]>
// With Unwrap: stepTwo is a Task<byte[]>
var stepTwo = data.ContinueWith((antecedent) =>
{
return Task<byte>.Factory.StartNew( () => Compute(antecedent.Result));
})
.Unwrap();

// Without Unwrap: antecedent.Result = Task<byte>


// and the following method will not compile.
// With Unwrap: antecedent.Result = byte and
// we can work directly with the result of the Compute method.
var lastStep = stepTwo.ContinueWith( (antecedant) =>
{
if (antecedant.Result >= threshold)
{
return Task.Factory.StartNew( () => Console.WriteLine("Program complete. Final =
0x{0:x} threshold = 0x{1:x}", stepTwo.Result, threshold));
}
else
{
return DoSomeOtherAsyncronousWork(stepTwo.Result, threshold);
}
});

lastStep.Wait();
Console.WriteLine("Press any key");
Console.ReadKey();
}

#region Dummy_Methods
private static byte[] GetData()
{
Random rand = new Random();
byte[] bytes = new byte[64];
rand.NextBytes(bytes);
return bytes;
}

static Task DoSomeOtherAsyncronousWork(int i, byte b2)


{
return Task.Factory.StartNew(() =>
{
Thread.SpinWait(500000);
Console.WriteLine("Doing more work. Value was <= threshold");
});
}
static byte Compute(byte[] data)
{

byte final = 0;
foreach (byte item in data)
{
final ^= item;
Console.WriteLine("{0:x}", final);
}
Console.WriteLine("Done computing");
return final;
}
#endregion
}
}

'How to: Unwrap a Task


Imports System.Threading
Imports System.Threading.Tasks

Module UnwrapATask2
Sub Main()
' An arbitrary threshold value.
Dim threshold As Byte = &H40

' myData is a Task(Of Byte())

Dim myData As Task(Of Byte()) = Task.Factory.StartNew(Function()


Return GetData()
End Function)
' We want to return a task so that we can
' continue from it later in the program.
' Without Unwrap: stepTwo is a Task(Of Task(Of Byte))
' With Unwrap: stepTwo is a Task(Of Byte)

Dim stepTwo = myData.ContinueWith(Function(antecedent)


Return Task.Factory.StartNew(Function()
Return
Compute(antecedent.Result)
End Function)
End Function).Unwrap()

Dim lastStep = stepTwo.ContinueWith(Function(antecedent)


Console.WriteLine("Result = {0}", antecedent.Result)
If antecedent.Result >= threshold Then
Return Task.Factory.StartNew(Sub()

Console.WriteLine("Program complete. Final = &H{1:x} threshold = &H{1:x}",

stepTwo.Result, threshold)
End Sub)
Else
Return DoSomeOtherAsynchronousWork(stepTwo.Result,
threshold)
End If
End Function)
Try
lastStep.Wait()
Catch ae As AggregateException
For Each ex As Exception In ae.InnerExceptions
Console.WriteLine(ex.Message & ex.StackTrace & ex.GetBaseException.ToString())
Next
End Try

Console.WriteLine("Press any key")


Console.ReadKey()
End Sub

#Region "Dummy_Methods"
Function GetData() As Byte()
Dim rand As Random = New Random()
Dim bytes(64) As Byte
rand.NextBytes(bytes)
Return bytes
End Function

Function DoSomeOtherAsynchronousWork(ByVal i As Integer, ByVal b2 As Byte) As Task


Return Task.Factory.StartNew(Sub()
Thread.SpinWait(500000)
Console.WriteLine("Doing more work. Value was <= threshold.")
End Sub)
End Function

Function Compute(ByVal d As Byte()) As Byte


Dim final As Byte = 0
For Each item As Byte In d
final = final Xor item
Console.WriteLine("{0:x}", final)
Next
Console.WriteLine("Done computing")
Console.WriteLine("Done computing")
Return final
End Function
#End Region
End Module

Voir aussi
System.Threading.Tasks.TaskExtensions
Programmation asynchrone basée sur les tâches
Procédure : empêcher une tâche enfant de s’attacher
à son parent
18/07/2020 • 6 minutes to read • Edit Online

Ce document explique comment empêcher une tâche enfant de s’attacher à la tâche parente. Empêcher une tâche
enfant de s’attacher à son parent est utile quand vous appelez un composant écrit par un tiers, qui utilise
également des tâches. Par exemple, un composant tiers qui utilise l’option TaskCreationOptions.AttachedToParent
pour créer un objet Task ou Task<TResult> peut causer des problèmes dans votre code s’il est long ou s’il lève une
exception non gérée.

Exemple
L’exemple suivant compare les effets d’utiliser les options par défaut à ceux d’empêcher une tâche enfant de
s’attacher au parent. L’exemple crée un objet Task qui appelle une bibliothèque tierce qui utilise également un objet
Task. La bibliothèque tierce utilise l’option AttachedToParent pour créer l’objet Task. L’application utilise l’option
TaskCreationOptions.DenyChildAttach pour créer la tâche parente. Cette option ordonne au runtime de supprimer
la spécification AttachedToParent des tâches enfants.

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

// Defines functionality that is provided by a third-party.


// In a real-world scenario, this would likely be provided
// in a separate code file or assembly.
namespace Contoso
{
public class Widget
{
public Task Run()
{
// Create a long-running task that is attached to the
// parent in the task hierarchy.
return Task.Factory.StartNew(() =>
{
// Simulate a lengthy operation.
Thread.Sleep(5000);
}, TaskCreationOptions.AttachedToParent);
}
}
}

// Demonstrates how to prevent a child task from attaching to the parent.


class DenyChildAttach
{
static void RunWidget(Contoso.Widget widget,
TaskCreationOptions parentTaskOptions)
{
// Record the time required to run the parent
// and child tasks.
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

Console.WriteLine("Starting widget as a background task...");

// Run the widget task in the background.


Task<Task> runWidget = Task.Factory.StartNew(() =>
Task<Task> runWidget = Task.Factory.StartNew(() =>
{
Task widgetTask = widget.Run();

// Perform other work while the task runs...


Thread.Sleep(1000);

return widgetTask;
}, parentTaskOptions);

// Wait for the parent task to finish.


Console.WriteLine("Waiting for parent task to finish...");
runWidget.Wait();
Console.WriteLine("Parent task has finished. Elapsed time is {0} ms.",
stopwatch.ElapsedMilliseconds);

// Perform more work...


Console.WriteLine("Performing more work on the main thread...");
Thread.Sleep(2000);
Console.WriteLine("Elapsed time is {0} ms.", stopwatch.ElapsedMilliseconds);

// Wait for the child task to finish.


Console.WriteLine("Waiting for child task to finish...");
runWidget.Result.Wait();
Console.WriteLine("Child task has finished. Elapsed time is {0} ms.",
stopwatch.ElapsedMilliseconds);
}

static void Main(string[] args)


{
Contoso.Widget w = new Contoso.Widget();

// Perform the same operation two times. The first time, the operation
// is performed by using the default task creation options. The second
// time, the operation is performed by using the DenyChildAttach option
// in the parent task.

Console.WriteLine("Demonstrating parent/child tasks with default options...");


RunWidget(w, TaskCreationOptions.None);

Console.WriteLine();

Console.WriteLine("Demonstrating parent/child tasks with the DenyChildAttach option...");


RunWidget(w, TaskCreationOptions.DenyChildAttach);
}
}

/* Sample output:
Demonstrating parent/child tasks with default options...
Starting widget as a background task...
Waiting for parent task to finish...
Parent task has finished. Elapsed time is 5014 ms.
Performing more work on the main thread...
Elapsed time is 7019 ms.
Waiting for child task to finish...
Child task has finished. Elapsed time is 7019 ms.

Demonstrating parent/child tasks with the DenyChildAttach option...


Starting widget as a background task...
Waiting for parent task to finish...
Parent task has finished. Elapsed time is 1007 ms.
Performing more work on the main thread...
Elapsed time is 3015 ms.
Waiting for child task to finish...
Child task has finished. Elapsed time is 5015 ms.
*/

Imports System.Diagnostics
Imports System.Diagnostics
Imports System.Threading
Imports System.Threading.Tasks

' Defines functionality that is provided by a third-party.


' In a real-world scenario, this would likely be provided
' in a separate code file or assembly.
Namespace Contoso
Public Class Widget
Public Function Run() As Task
' Create a long-running task that is attached to the
' parent in the task hierarchy.
Return Task.Factory.StartNew(Sub() Thread.Sleep(5000), TaskCreationOptions.AttachedToParent)
' Simulate a lengthy operation.
End Function
End Class
End Namespace

' Demonstrates how to prevent a child task from attaching to the parent.
Friend Class DenyChildAttach
Private Shared Sub RunWidget(ByVal widget As Contoso.Widget, ByVal parentTaskOptions As
TaskCreationOptions)
' Record the time required to run the parent
' and child tasks.
Dim stopwatch As New Stopwatch()
stopwatch.Start()

Console.WriteLine("Starting widget as a background task...")

' Run the widget task in the background.


Dim runWidget As Task(Of Task) = Task.Factory.StartNew(Function()
' Perform other work while the task runs...
Dim widgetTask As Task = widget.Run()
Thread.Sleep(1000)
Return widgetTask
End Function, parentTaskOptions)

' Wait for the parent task to finish.


Console.WriteLine("Waiting for parent task to finish...")
runWidget.Wait()
Console.WriteLine("Parent task has finished. Elapsed time is {0} ms.", stopwatch.ElapsedMilliseconds)

' Perform more work...


Console.WriteLine("Performing more work on the main thread...")
Thread.Sleep(2000)
Console.WriteLine("Elapsed time is {0} ms.", stopwatch.ElapsedMilliseconds)

' Wait for the child task to finish.


Console.WriteLine("Waiting for child task to finish...")
runWidget.Result.Wait()
Console.WriteLine("Child task has finished. Elapsed time is {0} ms.", stopwatch.ElapsedMilliseconds)
End Sub

Shared Sub Main(ByVal args() As String)


Dim w As New Contoso.Widget()

' Perform the same operation two times. The first time, the operation
' is performed by using the default task creation options. The second
' time, the operation is performed by using the DenyChildAttach option
' in the parent task.

Console.WriteLine("Demonstrating parent/child tasks with default options...")


RunWidget(w, TaskCreationOptions.None)

Console.WriteLine()

Console.WriteLine("Demonstrating parent/child tasks with the DenyChildAttach option...")


RunWidget(w, TaskCreationOptions.DenyChildAttach)
End Sub
End Class
End Class

' Sample output:


'Demonstrating parent/child tasks with default options...
'Starting widget as a background task...
'Waiting for parent task to finish...
'Parent task has finished. Elapsed time is 5014 ms.
'Performing more work on the main thread...
'Elapsed time is 7019 ms.
'Waiting for child task to finish...
'Child task has finished. Elapsed time is 7019 ms.
'
'Demonstrating parent/child tasks with the DenyChildAttach option...
'Starting widget as a background task...
'Waiting for parent task to finish...
'Parent task has finished. Elapsed time is 1007 ms.
'Performing more work on the main thread...
'Elapsed time is 3015 ms.
'Waiting for child task to finish...
'Child task has finished. Elapsed time is 5015 ms.
'

Étant donné qu’une tâche parente ne se termine pas tant que toutes les tâches enfants ne sont pas achevées, une
tâche enfant à exécution longue peut entraîner des performances médiocres de la part de l’application globale.
Dans cet exemple, quand l’application utilise les options par défaut pour créer une tâche parente, la tâche enfant
doit se terminer avant la fin de la tâche parente. Quand l’application utilise l’option
TaskCreationOptions.DenyChildAttach, l’enfant n’est pas attachée au parent. Par conséquent, l’application peut
effectuer du travail supplémentaire dès que la tâche parente est terminée et avant qu’elle ne doive attendre la fin
de la tâche enfant.

Voir aussi
Programmation asynchrone basée sur les tâches
Flux de données (bibliothèque parallèle de tâches)
18/07/2020 • 68 minutes to read • Edit Online

La bibliothèque parallèle de tâches (TPL) fournit des composants de flux de données destinés à augmenter la
robustesse des applications prenant en charge l’accès concurrentiel. Ces composants de flux de données sont
regroupés sous le nom de bibliothèque de flux de données TPL. Ce modèle de flux de données favorise la
programmation basée sur les acteurs en fournissant une transmission de messages in-process pour les flux de
données à granularité grossière et les tâches de traitement "pipeline". Les composants de flux de données
reposent sur les types et l'infrastructure de planification de la bibliothèque parallèle de tâches, et s'intègrent à la
prise en charge des langages C#, Visual Basic et F# de la programmation asynchrone. Ces composants de flux
de données sont utiles quand vous avez plusieurs opérations qui doivent communiquer entre elles de façon
asynchrone ou quand vous voulez traiter les données à mesure qu'elles deviennent disponibles. Prenons par
exemple une application qui traite les données d'image d'une webcam. En utilisant le modèle de flux de
données, l'application peut traiter les trames d'images à mesure qu'elles deviennent disponibles. Si l’application
améliore les trames d’images, par exemple, en effectuant la correction de la lumière ou des yeux rouges, vous
pouvez créer un pipeline de composants de flux de données. Chaque étape du pipeline peut utiliser plusieurs
fonctionnalités de parallélisme à granularité grossière, telles que les fonctionnalités fournies par la
bibliothèque TPL qui permettent de transformer les images.
Ce document fournit une vue d'ensemble de la bibliothèque de flux de données TPL. Il décrit le modèle de
programmation et les types de blocs de flux de données prédéfinis, et explique comment configurer des blocs
de flux de données pour répondre aux besoins de vos applications.

NOTE
La bibliothèque de flux de données TPL (espace de noms System.Threading.Tasks.Dataflow) n'est pas distribuée avec .NET.
Pour installer l’espace de noms System.Threading.Tasks.Dataflow dans Visual Studio, ouvrez votre projet, choisissez Gérer
les packages NuGet dans le menu Projet , puis recherchez en ligne le package System.Threading.Tasks.Dataflow .
Vous pouvez également l’installer à l’aide de l’interface CLI .NET Core en exécutant
dotnet add package System.Threading.Tasks.Dataflow .

Modèle de programmation
La bibliothèque de flux de données TPL constitue une base pour la transmission de messages et la
parallélisation des applications nécessitant une utilisation importante du processeur et des E/S, et ayant un
débit élevé et une faible latence. Elle permet également de contrôler explicitement la manière dont les données
sont mises en mémoire tampon et se déplacent sur le système. Pour mieux comprendre le modèle de
programmation de flux de données, imaginez une application qui charge des images à partir du disque de
manière asynchrone et crée un composite de ces images. Les modèles de programmation traditionnels
requièrent généralement l'utilisation de rappels et d'objets de synchronisation, tels que des verrous, pour
coordonner les tâches et accéder aux données partagées. À l'aide du modèle de programmation de flux de
données, vous pouvez créer des objets de flux de données qui traitent les images à mesure qu'elles sont lues à
partir du disque. Sous le modèle de flux de données, vous déclarez la manière dont sont traitées les données
quand elles deviennent disponibles, ainsi que les éventuelles dépendances qui existent entre les données. Le
runtime gère les dépendances entre les données, ce qui vous évite d'avoir à synchroniser l'accès aux données
partagées. De plus, étant donné que le runtime planifie les tâches en fonction de l'arrivée asynchrone des
données, le flux de données peut améliorer la réactivité et le débit grâce à une gestion efficace des threads sous-
jacents. Vous trouverez un exemple d’utilisation du modèle de programmation de flux de données pour
implémenter le traitement d’image dans une application Windows Forms sur la page Procédure pas à pas :
utiliser les flux de données dans une application Windows Forms.
Sources et cibles
La bibliothèque de flux de données TPL comprend des blocs de flux de données, structures de données qui
mettent les données en mémoire tampon et les traitent. La bibliothèque parallèle de tâches définit trois types de
blocs de flux de données : les blocs sources, les blocs cibles et les blocs propagateurs. Un bloc source agit
comme une source de données et peut donc être lu. Un bloc cible joue le rôle de récepteur de données, il est
donc possible d'y écrire. Un bloc propagateur agit à la fois comme un bloc source et un bloc cible, et peut donc
faire l'objet d'une lecture et d'une écriture. La bibliothèque parallèle de tâches définit l'interface
System.Threading.Tasks.Dataflow.ISourceBlock<TOutput> pour représenter les sources,
System.Threading.Tasks.Dataflow.ITargetBlock<TInput> pour représenter les cibles, et
System.Threading.Tasks.Dataflow.IPropagatorBlock<TInput,TOutput> pour représenter les propagateurs.
IPropagatorBlock<TInput,TOutput> hérite des deux ISourceBlock<TOutput> et de ITargetBlock<TInput>.
La bibliothèque de flux de données TPL fournit plusieurs types de blocs de flux de données prédéfinis qui
implémentent les interfaces ISourceBlock<TOutput>, ITargetBlock<TInput> et
IPropagatorBlock<TInput,TOutput>. Ces types de blocs de flux de données sont décrits dans ce document dans
la section Types de blocs de flux de données prédéfinis.
Connexion des blocs
Vous pouvez aussi connecter des blocs de flux de données pour former des pipelines, qui sont des séquences
linéaires de blocs de flux de données, ou bien des réseaux, qui sont des graphiques de blocs de flux de données.
Un pipeline est une forme de réseau. Dans un pipeline ou un réseau, les sources propagent des données de
manière asynchrone vers les cibles à mesure que les données deviennent disponibles. La méthode
ISourceBlock<TOutput>.LinkTo lie un bloc de flux de données source à un bloc cible. Une source peut être liée à
zéro, une ou plusieurs cibles. Une cible peut être liée à zéro, une ou plusieurs sources. Vous pouvez ajouter des
blocs de flux de données à un pipeline ou en supprimer de manière simultanée. Les types de blocs de flux de
données prédéfinis gèrent tous les aspects liés à la sécurité des threads pour les liaisons et les annulations de
liaison.
Vous trouverez un exemple de connexion de blocs de flux de données permettant de former un pipeline de base
sur la page Procédure pas à pas : créer un pipeline de flux de données. Vous trouverez un exemple de connexion
de blocs de flux de données permettant de former un réseau plus complexe sur la page Procédure pas à pas :
utiliser les flux de données dans une application Windows Forms. Vous trouverez un exemple de dissociation
d’une cible et d’une source après que la source a envoyé un message à la cible sur la page Guide pratique :
dissocier des blocs de flux de données.
Filtrage
Quand vous appelez la méthode ISourceBlock<TOutput>.LinkTo pour lier une source à une cible, vous pouvez
fournir un délégué qui détermine si le bloc cible accepte ou rejette les messages en fonction de la valeur de ce
message. Ce mécanisme de filtrage permet de garantir qu'un bloc de flux de données recevra uniquement
certaines valeurs. Pour la plupart des types de blocs de flux de données prédéfinis, si un bloc source est
connecté à plusieurs blocs cibles, quand un bloc cible rejette un message, la source envoie le message à la cible
suivante. L'ordre dans lequel une source envoie des messages aux cibles est défini par la source et peut varier
en fonction du type de la source. La plupart des types de blocs sources arrêtent d'envoyer un message une fois
celui-ci accepté par une cible. La classe BroadcastBlock<T> est une exception. En effet, celle-ci envoie chaque
message à l'ensemble des cibles, même si celles-ci le rejettent. Vous trouverez un exemple d’utilisation du
filtrage permettant de traiter uniquement certains messages sur la page Procédure pas à pas : utiliser les flux de
données dans une application Windows Forms.
IMPORTANT
Étant donné que chaque type de bloc de flux de données source prédéfini garantit la propagation des messages dans
l'ordre où ils sont reçus, tous les messages doivent être lus depuis le bloc source avant que celui-ci ne puisse traiter le
message suivant. Par conséquent, quand vous utilisez le filtrage pour connecter plusieurs cibles à une source, assurez-
vous que chaque message soit reçu par au moins un bloc cible. Sinon, votre application peut se bloquer.

Transmission de messages
Le modèle de programmation de flux de données est lié au concept de transmission de messages, durant
laquelle les composants indépendants d’un programme communiquent entre eux à l’aide de messages. Pour
propager des messages entre composants d'application, vous pouvez appeler les méthodes Post et
DataflowBlock.SendAsync afin d’envoyer des messages aux blocs de flux de données cibles (Post agit de façon
synchrone et SendAsync de façon asynchrone), et les méthodes Receive, ReceiveAsync et TryReceive afin de
recevoir des messages provenant de blocs sources. Vous pouvez combiner ces méthodes à des pipelines ou
réseaux de flux de données en envoyant des données d'entrée au nœud principal (bloc cible) et en recevant des
données de sortie de la part du nœud terminal du pipeline ou des nœuds terminaux du réseau (un ou plusieurs
blocs sources). Vous pouvez également utiliser la méthode Choose pour lire la première des sources fournies
qui contient des données et effectuer des actions sur ces données.
Les blocs sources envoient des données aux blocs cibles en appelant la méthode
ITargetBlock<TInput>.OfferMessage. Le bloc cible peut répondre à un message envoyé de trois manières : il
peut accepter le message, le refuser ou le différer. Quand la cible accepte le message, la méthode OfferMessage
retourne Accepted. Quand la cible refuse le message, la méthode OfferMessage retourne Declined. Quand la
cible décide de ne plus recevoir de messages de la source, OfferMessage renvoie DecliningPermanently. Les
types de blocs sources prédéfinis n'envoient plus de messages aux cibles liées après réception d'une telle valeur
et sont automatiquement dissociés de ces cibles.
Quand un bloc cible diffère le message pour une utilisation ultérieure, la méthode OfferMessage retourne
Postponed. Un bloc cible qui diffère un message peut appeler par la suite la méthode
ISourceBlock<TOutput>.ReserveMessage pour tenter de réserver le message envoyé. À ce stade, soit le
message est toujours disponible et peut être utilisé par le bloc cible, soit le message a été utilisé par une autre
cible. Si, par la suite, le bloc cible demande le message ou n'en a plus besoin, il appellera respectivement la
méthode ISourceBlock<TOutput>.ConsumeMessage ou la méthode ReleaseReservation. La réservation de
messages est généralement utilisée par les types de blocs de flux de données qui fonctionnent en mode non
gourmand. Le mode non gourmand est expliqué plus loin dans ce document. Plutôt que de réserver un
message différé, un bloc cible peut également utiliser la méthode ISourceBlock<TOutput>.ConsumeMessage
pour tenter d'utiliser directement le message différé.
Achèvement des blocs de flux de données
Les blocs de flux de données prennent également en charge le concept d’achèvement. Un bloc de flux de
données qui est achevé n'effectue plus aucune tâche. Chaque bloc de flux de données est associé à un objet
System.Threading.Tasks.Task, appelé tâche d'achèvement, qui représente l'état d'achèvement du bloc. Étant
donné que vous pouvez attendre qu'un objet Task soit terminé, vous pouvez, à l'aide de tâches d'achèvement,
attendre l'achèvement d'un ou plusieurs nœuds terminaux d'un réseau de flux de données. L'interface
IDataflowBlock définit la méthode Complete qui informe le bloc de flux de données qu'une requête exige son
achèvement, ainsi que la propriété Completion qui retourne la tâche d'achèvement pour le bloc de flux de
données. ISourceBlock<TOutput> et ITargetBlock<TInput> héritent de l'interface IDataflowBlock.
Il existe deux façons de déterminer si un bloc de flux de données s'est terminé sans erreur, si une ou plusieurs
erreurs se sont produites ou s'il a été annulé. La première consiste à appeler la méthode Task.Wait sur la tâche
d'achèvement dans un bloc try - catch ( Try - Catch en Visual Basic). Dans l'exemple suivant, un
ActionBlock<TInput> objet lève une exception ArgumentOutOfRangeException si sa valeur d'entrée est
inférieure à zéro. L’exception AggregateException est levée quand cet exemple appelle Wait sur la tâche
d’achèvement. ArgumentOutOfRangeException est accessible via la propriété InnerExceptions de l'objet
AggregateException.

// Create an ActionBlock<int> object that prints its input


// and throws ArgumentOutOfRangeException if the input
// is less than zero.
var throwIfNegative = new ActionBlock<int>(n =>
{
Console.WriteLine("n = {0}", n);
if (n < 0)
{
throw new ArgumentOutOfRangeException();
}
});

// Post values to the block.


throwIfNegative.Post(0);
throwIfNegative.Post(-1);
throwIfNegative.Post(1);
throwIfNegative.Post(-2);
throwIfNegative.Complete();

// Wait for completion in a try/catch block.


try
{
throwIfNegative.Completion.Wait();
}
catch (AggregateException ae)
{
// If an unhandled exception occurs during dataflow processing, all
// exceptions are propagated through an AggregateException object.
ae.Handle(e =>
{
Console.WriteLine("Encountered {0}: {1}",
e.GetType().Name, e.Message);
return true;
});
}

/* Output:
n = 0
n = -1
Encountered ArgumentOutOfRangeException: Specified argument was out of the range
of valid values.
*/
' Create an ActionBlock<int> object that prints its input
' and throws ArgumentOutOfRangeException if the input
' is less than zero.
Dim throwIfNegative = New ActionBlock(Of Integer)(Sub(n)
Console.WriteLine("n = {0}", n)
If n < 0 Then
Throw New ArgumentOutOfRangeException()
End If
End Sub)

' Post values to the block.


throwIfNegative.Post(0)
throwIfNegative.Post(-1)
throwIfNegative.Post(1)
throwIfNegative.Post(-2)
throwIfNegative.Complete()

' Wait for completion in a try/catch block.


Try
throwIfNegative.Completion.Wait()
Catch ae As AggregateException
' If an unhandled exception occurs during dataflow processing, all
' exceptions are propagated through an AggregateException object.
ae.Handle(Function(e)
Console.WriteLine("Encountered {0}: {1}", e.GetType().Name, e.Message)
Return True
End Function)
End Try

' Output:
' n = 0
' n = -1
' Encountered ArgumentOutOfRangeException: Specified argument was out of the range
' of valid values.
'

Dans cet exemple, une exception n'est pas gérée dans le délégué d'un bloc de flux de données d'exécution. Nous
vous recommandons de gérer les exceptions dans le corps des blocs. Toutefois, si vous ne parvenez pas à le
faire, le bloc se comportera comme s'il avait été annulé et ne traitera pas les messages entrants.
Quand un bloc de flux de données est annulé de manière explicite, l'objet AggregateException contient
OperationCanceledException dans la propriété InnerExceptions. Pour plus d’informations sur l’annulation de flux
de données, consultez la section Permettre les annulations.
La deuxième méthode permettant de déterminer l’état d’achèvement d’un bloc de flux de données est d’utiliser
une continuation de la tâche d’achèvement, ou d’utiliser les fonctionnalités de langage asynchrones de C# et de
Visual Basic pour attendre la tâche d’achèvement de manière asynchrone. Le délégué que vous fournissez à la
méthode Task.ContinueWith prend un objet Task qui représente la tâche précédente. Dans le cas de la propriété
Completion, le délégué de la continuation prend la tâche d'achèvement. L’exemple suivant ressemble au
précédent, mais il utilise également la méthode ContinueWith pour créer une tâche de continuation qui imprime
l’état de l’opération globale de flux de données.
// Create an ActionBlock<int> object that prints its input
// and throws ArgumentOutOfRangeException if the input
// is less than zero.
var throwIfNegative = new ActionBlock<int>(n =>
{
Console.WriteLine("n = {0}", n);
if (n < 0)
{
throw new ArgumentOutOfRangeException();
}
});

// Create a continuation task that prints the overall


// task status to the console when the block finishes.
throwIfNegative.Completion.ContinueWith(task =>
{
Console.WriteLine("The status of the completion task is '{0}'.",
task.Status);
});

// Post values to the block.


throwIfNegative.Post(0);
throwIfNegative.Post(-1);
throwIfNegative.Post(1);
throwIfNegative.Post(-2);
throwIfNegative.Complete();

// Wait for completion in a try/catch block.


try
{
throwIfNegative.Completion.Wait();
}
catch (AggregateException ae)
{
// If an unhandled exception occurs during dataflow processing, all
// exceptions are propagated through an AggregateException object.
ae.Handle(e =>
{
Console.WriteLine("Encountered {0}: {1}",
e.GetType().Name, e.Message);
return true;
});
}

/* Output:
n = 0
n = -1
The status of the completion task is 'Faulted'.
Encountered ArgumentOutOfRangeException: Specified argument was out of the range
of valid values.
*/
' Create an ActionBlock<int> object that prints its input
' and throws ArgumentOutOfRangeException if the input
' is less than zero.
Dim throwIfNegative = New ActionBlock(Of Integer)(Sub(n)
Console.WriteLine("n = {0}", n)
If n < 0 Then
Throw New ArgumentOutOfRangeException()
End If
End Sub)

' Create a continuation task that prints the overall


' task status to the console when the block finishes.
throwIfNegative.Completion.ContinueWith(Sub(task) Console.WriteLine("The status of the completion task is
'{0}'.", task.Status))

' Post values to the block.


throwIfNegative.Post(0)
throwIfNegative.Post(-1)
throwIfNegative.Post(1)
throwIfNegative.Post(-2)
throwIfNegative.Complete()

' Wait for completion in a try/catch block.


Try
throwIfNegative.Completion.Wait()
Catch ae As AggregateException
' If an unhandled exception occurs during dataflow processing, all
' exceptions are propagated through an AggregateException object.
ae.Handle(Function(e)
Console.WriteLine("Encountered {0}: {1}", e.GetType().Name, e.Message)
Return True
End Function)
End Try

' Output:
' n = 0
' n = -1
' The status of the completion task is 'Faulted'.
' Encountered ArgumentOutOfRangeException: Specified argument was out of the range
' of valid values.
'

Vous pouvez également utiliser des propriétés telles que IsCanceled dans le corps de la tâche de continuation
pour obtenir des informations supplémentaires sur l'état d'achèvement d'un bloc de flux de données. Pour plus
d’informations sur les tâches de continuation et leur rôle dans les annulations et la gestion des erreurs,
consultez Chaînage des tâches à l’aide de tâches de continuation, Annulation de tâches et Gestion des
exceptions.

Types de blocs de flux de données prédéfinis


La bibliothèque de flux de données TPL fournit plusieurs types de blocs de flux de données prédéfinis. Ces types
sont répartis en trois catégories : blocs de mise en mémoire tampon, blocs d’exécution et blocs de
regroupement. Les sections suivantes décrivent les types de blocs qui composent ces catégories.
Blocs de mise en mémoire tampon
Les blocs de mise en mémoire tampon contiennent des données destinées aux consommateurs de données. La
bibliothèque de flux de données TPL fournit trois types de blocs de mise en mémoire tampon :
System.Threading.Tasks.Dataflow.BufferBlock<T>, System.Threading.Tasks.Dataflow.BroadcastBlock<T> et
System.Threading.Tasks.Dataflow.WriteOnceBlock<T>.
BufferBlock(T)
La classe BufferBlock<T> représente une structure de messagerie asynchrone à usage général. Cette classe
stocke une file d'attente de messages de type premier entré, premier sorti (FIFO). Plusieurs cibles peuvent lire
ces messages et plusieurs sources peuvent y écrire. Quand une cible reçoit un message d'un objet
BufferBlock<T>, ce message est supprimé de la file d'attente. Par conséquent, même si un objet BufferBlock<T>
peut avoir plusieurs cibles, seule une cible reçoit chaque message. La classe BufferBlock<T> est utile quand
vous voulez transmettre plusieurs messages à un autre composant et que ce composant doit recevoir chaque
message.
Dans l'exemple simple qui suit, plusieurs valeurs Int32 sont publiées sur un objet BufferBlock<T>, puis sont lues
depuis l'objet.

// Create a BufferBlock<int> object.


var bufferBlock = new BufferBlock<int>();

// Post several messages to the block.


for (int i = 0; i < 3; i++)
{
bufferBlock.Post(i);
}

// Receive the messages back from the block.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(bufferBlock.Receive());
}

/* Output:
0
1
2
*/

' Create a BufferBlock<int> object.


Dim bufferBlock = New BufferBlock(Of Integer)()

' Post several messages to the block.


For i As Integer = 0 To 2
bufferBlock.Post(i)
Next i

' Receive the messages back from the block.


For i As Integer = 0 To 2
Console.WriteLine(bufferBlock.Receive())
Next i

' Output:
' 0
' 1
' 2
'

Vous trouverez un exemple complet montrant comment écrire et lire des messages dans un objet
BufferBlock<T> sur la page Guide pratique : écrire et lire des messages dans un bloc de flux de données.
BroadcastBlock(T)
La classe BroadcastBlock<T> est utile quand vous devez transmettre plusieurs messages à un autre composant,
mais que ce composant nécessite uniquement la valeur la plus récente. Cette classe est également utile quand
vous voulez diffuser un message vers plusieurs composants.
Dans l'exemple simple qui suit, une valeur Double est publiée sur un objet BroadcastBlock<T>, puis est lue
plusieurs fois depuis l'objet. Étant donné que les valeurs ne sont pas supprimées des objets BroadcastBlock<T>
après leur lecture, la même valeur est disponible à chaque fois.
// Create a BroadcastBlock<double> object.
var broadcastBlock = new BroadcastBlock<double>(null);

// Post a message to the block.


broadcastBlock.Post(Math.PI);

// Receive the messages back from the block several times.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(broadcastBlock.Receive());
}

/* Output:
3.14159265358979
3.14159265358979
3.14159265358979
*/

' Create a BroadcastBlock<double> object.


Dim broadcastBlock = New BroadcastBlock(Of Double)(Nothing)

' Post a message to the block.


broadcastBlock.Post(Math.PI)

' Receive the messages back from the block several times.
For i As Integer = 0 To 2
Console.WriteLine(broadcastBlock.Receive())
Next i

' Output:
' 3.14159265358979
' 3.14159265358979
' 3.14159265358979
'

Vous trouverez un exemple complet montrant comment utiliser BroadcastBlock<T> pour diffuser un message
vers plusieurs blocs cibles sur la page Guide pratique : spécifier un Planificateur de tâches dans un bloc de flux
de données.
WriteOnceBlock(T)
La classe WriteOnceBlock<T> est similaire à la classe BroadcastBlock<T>, à ceci près qu'il n'est possible d'écrire
qu'une seule fois dans un objet WriteOnceBlock<T>. Vous pouvez assimiler WriteOnceBlock<T> au mot clé
readonly en C# (ReadOnly en Visual Basic). Toutefois, la différence se trouve dans le fait qu'un objet
WriteOnceBlock<T> devient immuable après avoir reçu une valeur, et non à la construction. Comme pour la
classe BroadcastBlock<T>, quand une cible reçoit un message d'un objet WriteOnceBlock<T>, le message n'est
pas supprimé de l'objet. Par conséquent, plusieurs cibles reçoivent une copie du message. La classe
WriteOnceBlock<T> est utile quand vous voulez propager uniquement le premier d'une liste de messages.
Dans l'exemple simple qui suit, plusieurs valeurs String sont publiées sur un objet WriteOnceBlock<T>, puis
sont lues depuis l'objet. Étant donné qu'il n'est possible d'écrire dans un objet WriteOnceBlock<T> qu'une seule
fois, une fois qu'un objet WriteOnceBlock<T> reçoit un message, il rejette les messages suivants.
// Create a WriteOnceBlock<string> object.
var writeOnceBlock = new WriteOnceBlock<string>(null);

// Post several messages to the block in parallel. The first


// message to be received is written to the block.
// Subsequent messages are discarded.
Parallel.Invoke(
() => writeOnceBlock.Post("Message 1"),
() => writeOnceBlock.Post("Message 2"),
() => writeOnceBlock.Post("Message 3"));

// Receive the message from the block.


Console.WriteLine(writeOnceBlock.Receive());

/* Sample output:
Message 2
*/

' Create a WriteOnceBlock<string> object.


Dim writeOnceBlock = New WriteOnceBlock(Of String)(Nothing)

' Post several messages to the block in parallel. The first


' message to be received is written to the block.
' Subsequent messages are discarded.
Parallel.Invoke(Function() writeOnceBlock.Post("Message 1"), Function() writeOnceBlock.Post("Message 2"),
Function() writeOnceBlock.Post("Message 3"))

' Receive the message from the block.


Console.WriteLine(writeOnceBlock.Receive())

' Sample output:


' Message 2
'

Vous trouverez un exemple complet montrant comment utiliser WriteOnceBlock<T> pour recevoir la valeur de
la première opération terminée sur la page Guide pratique : dissocier des blocs de flux de données.
Blocs d'exécution
Les blocs d'exécution appellent un délégué fourni par l'utilisateur pour chaque donnée reçue. La bibliothèque de
flux de données TPL fournit trois types de blocs d'exécution : ActionBlock<TInput>,
System.Threading.Tasks.Dataflow.TransformBlock<TInput,TOutput> et
System.Threading.Tasks.Dataflow.TransformManyBlock<TInput,TOutput>.
ActionBlock(T)
La classe ActionBlock<TInput> est un bloc cible qui appelle un délégué quand il reçoit des données. Imaginez
un objet ActionBlock<TInput> utilisé comme un délégué qui s'exécute de façon asynchrone quand des données
deviennent disponibles. Le délégué que vous fournissez à un objet ActionBlock<TInput> peut être de type
Action<T> ou System.Func<TInput, Task> . Quand vous utilisez un objet ActionBlock<TInput> avec Action<T>,
le traitement de chaque élément d'entrée est considéré comme terminé quand le délégué est retourné. Quand
vous utilisez un objet ActionBlock<TInput> avec System.Func<TInput, Task> , le traitement de chaque élément
d'entrée n'est considéré comme terminé que quand l'objet Task retourné est à l'état achevé. À l'aide de ces deux
mécanismes, vous pouvez utiliser ActionBlock<TInput> pour le traitement synchrone et asynchrone de chaque
élément d'entrée.
Dans l'exemple simple qui suit, plusieurs valeurs Int32 sont publiées sur un objet ActionBlock<TInput>. L'objet
ActionBlock<TInput> imprime ces valeurs dans la console. Cet exemple attribue ensuite au bloc l'état achevé,
puis attend que toutes les tâches de flux de données soient terminées.
// Create an ActionBlock<int> object that prints values
// to the console.
var actionBlock = new ActionBlock<int>(n => Console.WriteLine(n));

// Post several messages to the block.


for (int i = 0; i < 3; i++)
{
actionBlock.Post(i * 10);
}

// Set the block to the completed state and wait for all
// tasks to finish.
actionBlock.Complete();
actionBlock.Completion.Wait();

/* Output:
0
10
20
*/

' Create an ActionBlock<int> object that prints values


' to the console.
Dim actionBlock = New ActionBlock(Of Integer)(Function(n) WriteLine(n))

' Post several messages to the block.


For i As Integer = 0 To 2
actionBlock.Post(i * 10)
Next i

' Set the block to the completed state and wait for all
' tasks to finish.
actionBlock.Complete()
actionBlock.Completion.Wait()

' Output:
' 0
' 10
' 20
'

Vous trouverez des exemples complets montrant comment utiliser des délégués avec la classe
ActionBlock<TInput> sur la page Guide pratique : exécuter des actions lorsqu'un bloc de flux de données reçoit
des données.
TransformBlock(TInput, TOutput)
La classe TransformBlock<TInput,TOutput> est similaire à la classe ActionBlock<TInput>, à ceci près qu'elle joue
à la fois le rôle de source et le rôle de cible. Le délégué que vous passez à un objet
TransformBlock<TInput,TOutput> renvoie une valeur de type TOutput . Le délégué que vous fournissez à un
objet TransformBlock<TInput,TOutput> peut être de type System.Func<TInput, TOutput> ou
System.Func<TInput, Task<TOutput>> . Quand vous utilisez un objet TransformBlock<TInput,TOutput> avec
System.Func<TInput, TOutput> , le traitement de chaque élément d'entrée est considéré comme terminé quand le
délégué est retourné. Quand vous utilisez un objet TransformBlock<TInput,TOutput> utilisé avec
System.Func<TInput, Task<TOutput>> , le traitement de chaque élément d'entrée n'est considéré comme terminé
que quand l'objet Task<TResult> retourné est à l'état achevé. Comme pour ActionBlock<TInput>, ces deux
mécanismes vous permettent d'utiliser TransformBlock<TInput,TOutput> pour le traitement synchrone et
asynchrone de chaque élément d'entrée.
Dans l'exemple simple qui suit, l'objet TransformBlock<TInput,TOutput> créé calcule la racine carrée de son
entrée. L'objet TransformBlock<TInput,TOutput> prend des valeurs Int32 comme entrée et produit des valeurs
Double comme sortie.

// Create a TransformBlock<int, double> object that


// computes the square root of its input.
var transformBlock = new TransformBlock<int, double>(n => Math.Sqrt(n));

// Post several messages to the block.


transformBlock.Post(10);
transformBlock.Post(20);
transformBlock.Post(30);

// Read the output messages from the block.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(transformBlock.Receive());
}

/* Output:
3.16227766016838
4.47213595499958
5.47722557505166
*/

' Create a TransformBlock<int, double> object that


' computes the square root of its input.
Dim transformBlock = New TransformBlock(Of Integer, Double)(Function(n) Math.Sqrt(n))

' Post several messages to the block.


transformBlock.Post(10)
transformBlock.Post(20)
transformBlock.Post(30)

' Read the output messages from the block.


For i As Integer = 0 To 2
Console.WriteLine(transformBlock.Receive())
Next i

' Output:
' 3.16227766016838
' 4.47213595499958
' 5.47722557505166
'

Vous trouverez des exemples complets utilisant TransformBlock<TInput,TOutput> dans un réseau de blocs de
flux de données qui réalise un traitement d’image dans une application Windows Forms sur la page Procédure
pas à pas : utiliser un flux de données dans une application Windows Forms.
TransformManyBlock(TInput, TOutput)
La classe TransformManyBlock<TInput,TOutput> est similaire à la classe TransformBlock<TInput,TOutput>, à
ceci près que TransformManyBlock<TInput,TOutput> produit zéro, une ou plusieurs valeurs de sortie pour
chaque valeur d'entrée, au lieu d'une seule valeur de sortie pour chaque valeur d'entrée. Le délégué que vous
fournissez à un objet TransformManyBlock<TInput,TOutput> peut être de type
System.Func<TInput, IEnumerable<TOutput>> ou System.Func<TInput, Task<IEnumerable<TOutput>>> . Quand vous
utilisez un objet TransformManyBlock<TInput,TOutput> avec System.Func<TInput, IEnumerable<TOutput>> , le
traitement de chaque élément d'entrée est considéré comme terminé quand le délégué est retourné. Quand
vous utilisez un objet TransformManyBlock<TInput,TOutput> avec
System.Func<TInput, Task<IEnumerable<TOutput>>> , le traitement de chaque élément d'entrée n'est considéré
comme terminé que quand l'objet System.Threading.Tasks.Task<IEnumerable<TOutput>> retourné est à l'état
achevé.
Dans l'exemple simple qui suit, l'objet TransformManyBlock<TInput,TOutput> créé fractionne les chaînes en
séquences de caractères. L'objet TransformManyBlock<TInput,TOutput> prend des valeurs String comme entrée
et produit des valeurs Char comme sortie.

// Create a TransformManyBlock<string, char> object that splits


// a string into its individual characters.
var transformManyBlock = new TransformManyBlock<string, char>(
s => s.ToCharArray());

// Post two messages to the first block.


transformManyBlock.Post("Hello");
transformManyBlock.Post("World");

// Receive all output values from the block.


for (int i = 0; i < ("Hello" + "World").Length; i++)
{
Console.WriteLine(transformManyBlock.Receive());
}

/* Output:
H
e
l
l
o
W
o
r
l
d
*/

' Create a TransformManyBlock<string, char> object that splits


' a string into its individual characters.
Dim transformManyBlock = New TransformManyBlock(Of String, Char)(Function(s) s.ToCharArray())

' Post two messages to the first block.


transformManyBlock.Post("Hello")
transformManyBlock.Post("World")

' Receive all output values from the block.


For i As Integer = 0 To ("Hello" & "World").Length - 1
Console.WriteLine(transformManyBlock.Receive())
Next i

' Output:
' H
' e
' l
' l
' o
' W
' o
' r
' l
' d
'

Vous trouverez des exemples complets utilisant TransformManyBlock<TInput,TOutput> pour produire plusieurs
sorties indépendantes pour chaque entrée dans un pipeline de flux de données sur la page Procédure pas à
pas : créer un pipeline de flux de données.
Degré de parallélisme
Chaque objet ActionBlock<TInput>, TransformBlock<TInput,TOutput> et
TransformManyBlock<TInput,TOutput> met en mémoire tampon les messages entrants jusqu'à ce que le bloc
soit prêt à les traiter. Par défaut, ces classes de traitent les messages un par un, dans l'ordre dans lequel ils sont
reçus. Vous pouvez également spécifier le degré de parallélisme permettant aux objets ActionBlock<TInput>,
TransformBlock<TInput,TOutput> et TransformManyBlock<TInput,TOutput> de traiter plusieurs messages
simultanément. Pour plus d'informations sur l'exécution simultanée, consultez la section "Spécification du degré
de parallélisme" plus loin dans ce document. Vous trouverez un exemple dans lequel un degré de parallélisme
est défini pour permettre à un bloc de flux de données d’exécution de traiter plusieurs messages à la fois sur la
page Guide pratique : spécifier le degré de parallélisme dans un bloc de flux de données.
Récapitulation des types délégués
Le tableau suivant récapitule les types délégués que vous pouvez fournir aux objets ActionBlock<TInput>,
TransformBlock<TInput,TOutput> et TransformManyBlock<TInput,TOutput>. Le tableau indique également si le
type délégué fonctionne de façon synchrone ou asynchrone.

TYPE T Y P E DÉL ÉGUÉ SY N C H RO N E T Y P E DÉL ÉGUÉ A SY N C H RO N E

ActionBlock<TInput> System.Action System.Func<TInput, Task>

TransformBlock<TInput,TOutput> System.Func<TInput, TOutput> System.Func<TInput,


Task<TOutput>>

TransformManyBlock<TInput,TOutput System.Func<TInput, System.Func<TInput,


> IEnumerable<TOutput>> Task<IEnumerable<TOutput>>>

Vous pouvez également utiliser des expressions lambda quand vous utilisez des types de blocs d'exécution.
Vous trouverez un exemple qui montre comment utiliser une expression lambda avec un bloc d’exécution sur la
page Guide pratique : exécuter des actions lorsqu’un bloc de flux de données reçoit des données.
Blocs de regroupement
Les blocs de regroupement combinent les données d'une ou plusieurs sources, sous diverses contraintes. La
bibliothèque de flux de données TPL fournit trois types de blocs de regroupement : BatchBlock<T>,
JoinBlock<T1,T2> et BatchedJoinBlock<T1,T2>.
BatchBlock(T)
La classe BatchBlock<T> combine des jeux de données d'entrée (ou lots), dans des tableaux de données de
sortie. Vous spécifiez la taille de chaque lot quand vous créez un objet BatchBlock<T>. Quand l'objet
BatchBlock<T> reçoit le nombre spécifié d'éléments d'entrée, il propage de manière asynchrone un tableau
contenant ces éléments. Si un objet BatchBlock<T> est à l'état achevé, mais ne contient pas suffisamment
d'éléments pour former un lot, il propage un tableau final contenant les éléments d'entrée restants.
La classe BatchBlock<T> peut fonctionner en mode gourmand ou non gourmand. En mode gourmand, qui est
le mode par défaut, un objet BatchBlock<T> accepte tous les messages qui lui sont envoyés et propage un
tableau après avoir reçu le nombre spécifié d'éléments. En mode non gourmand, un objet BatchBlock<T>
diffère tous les messages entrants jusqu'à ce que suffisamment de sources aient envoyé au bloc un nombre de
messages permettant de former un lot. Le mode gourmand est généralement plus performant que le mode non
gourmand, car il nécessite une charge de traitement moindre. Toutefois, vous pouvez utiliser le mode non
gourmand quand vous devez coordonner la consommation de plusieurs sources de façon atomique. Pour
spécifier le mode non gourmand, définissez Greedy sur False dans le paramètre dataflowBlockOptions du
constructeur BatchBlock<T>.
Dans l'exemple simple qui suit, plusieurs valeurs Int32 sont publiées sur un objet BatchBlock<T> qui contient
un lot de dix éléments. Pour garantir que toutes les valeurs de BatchBlock<T> soient propagées, cet exemple
appelle la méthode Complete. La méthode Complete définit l'objet BatchBlock<T> sur l'état achevé. L'objet
BatchBlock<T> propage donc les éléments restants dans un dernier lot.
// Create a BatchBlock<int> object that holds ten
// elements per batch.
var batchBlock = new BatchBlock<int>(10);

// Post several values to the block.


for (int i = 0; i < 13; i++)
{
batchBlock.Post(i);
}
// Set the block to the completed state. This causes
// the block to propagate out any any remaining
// values as a final batch.
batchBlock.Complete();

// Print the sum of both batches.

Console.WriteLine("The sum of the elements in batch 1 is {0}.",


batchBlock.Receive().Sum());

Console.WriteLine("The sum of the elements in batch 2 is {0}.",


batchBlock.Receive().Sum());

/* Output:
The sum of the elements in batch 1 is 45.
The sum of the elements in batch 2 is 33.
*/

' Create a BatchBlock<int> object that holds ten


' elements per batch.
Dim batchBlock = New BatchBlock(Of Integer)(10)

' Post several values to the block.


For i As Integer = 0 To 12
batchBlock.Post(i)
Next i
' Set the block to the completed state. This causes
' the block to propagate out any any remaining
' values as a final batch.
batchBlock.Complete()

' Print the sum of both batches.

Console.WriteLine("The sum of the elements in batch 1 is {0}.", batchBlock.Receive().Sum())

Console.WriteLine("The sum of the elements in batch 2 is {0}.", batchBlock.Receive().Sum())

' Output:
' The sum of the elements in batch 1 is 45.
' The sum of the elements in batch 2 is 33.
'

Vous trouverez un exemple complet utilisant BatchBlock<T> pour améliorer l'efficacité des opérations
d'insertion en base de données sur la page Procédure pas à pas : utiliser BatchBlock et BatchedJoinBlock pour
améliorer l'efficacité.
JoinBlock(T1, T2,...)
Les classes JoinBlock<T1,T2> et JoinBlock<T1,T2,T3> collectent des éléments d'entrée et propagent les objets
System.Tuple<T1,T2> ou System.Tuple<T1,T2,T3> qui contiennent ces éléments. Les classes JoinBlock<T1,T2>
et JoinBlock<T1,T2,T3> n'héritent pas de ITargetBlock<TInput>. Au lieu de cela, elles fournissent les propriétés
Target1, Target2 et Target3 qui implémentent ITargetBlock<TInput>.
Comme BatchBlock<T>, JoinBlock<T1,T2> et JoinBlock<T1,T2,T3> peuvent fonctionner en mode gourmand ou
non gourmand. En mode gourmand, qui est le mode par défaut, un objet JoinBlock<T1,T2> ou
JoinBlock<T1,T2,T3> accepte tous les messages qui lui sont envoyés et propage un tuple chaque fois que l'une
de ses cibles reçoit au moins un message. En mode non gourmand, un objet JoinBlock<T1,T2> ou
JoinBlock<T1,T2,T3> diffère tous les messages entrants jusqu'à ce que toutes les cibles aient reçu les données
requises pour créer un tuple. À ce stade, le bloc s'engage dans un protocole de validation en deux phases pour
récupérer atomiquement tous les éléments requis à partir des sources. Ce report permet à une autre entité de
consommer les données pendant ce temps, pour que l'ensemble du système puisse progresser.
Dans l'exemple simple qui suit, un objet JoinBlock<T1,T2,T3> nécessite plusieurs données pour calculer une
valeur. Dans cet exemple, l'objet JoinBlock<T1,T2,T3> créé nécessite deux valeurs Int32 et une valeur Char pour
effectuer une opération arithmétique.

// Create a JoinBlock<int, int, char> object that requires


// two numbers and an operator.
var joinBlock = new JoinBlock<int, int, char>();

// Post two values to each target of the join.

joinBlock.Target1.Post(3);
joinBlock.Target1.Post(6);

joinBlock.Target2.Post(5);
joinBlock.Target2.Post(4);

joinBlock.Target3.Post('+');
joinBlock.Target3.Post('-');

// Receive each group of values and apply the operator part


// to the number parts.

for (int i = 0; i < 2; i++)


{
var data = joinBlock.Receive();
switch (data.Item3)
{
case '+':
Console.WriteLine("{0} + {1} = {2}",
data.Item1, data.Item2, data.Item1 + data.Item2);
break;
case '-':
Console.WriteLine("{0} - {1} = {2}",
data.Item1, data.Item2, data.Item1 - data.Item2);
break;
default:
Console.WriteLine("Unknown operator '{0}'.", data.Item3);
break;
}
}

/* Output:
3 + 5 = 8
6 - 4 = 2
*/
' Create a JoinBlock<int, int, char> object that requires
' two numbers and an operator.
Dim joinBlock = New JoinBlock(Of Integer, Integer, Char)()

' Post two values to each target of the join.

joinBlock.Target1.Post(3)
joinBlock.Target1.Post(6)

joinBlock.Target2.Post(5)
joinBlock.Target2.Post(4)

joinBlock.Target3.Post("+"c)
joinBlock.Target3.Post("-"c)

' Receive each group of values and apply the operator part
' to the number parts.

For i As Integer = 0 To 1
Dim data = joinBlock.Receive()
Select Case data.Item3
Case "+"c
Console.WriteLine("{0} + {1} = {2}", data.Item1, data.Item2, data.Item1 + data.Item2)
Case "-"c
Console.WriteLine("{0} - {1} = {2}", data.Item1, data.Item2, data.Item1 - data.Item2)
Case Else
Console.WriteLine("Unknown operator '{0}'.", data.Item3)
End Select
Next i

' Output:
' 3 + 5 = 8
' 6 - 4 = 2
'

Vous trouverez un exemple complet utilisant des objets JoinBlock<T1,T2> en mode non gourmand pour
partager une ressource de manière coopérative sur la page Guide pratique : utiliser JoinBlock pour lire des
données issues de plusieurs sources.
BatchedJoinBlock(T1, T2, ...)
Les classes BatchedJoinBlock<T1,T2> et BatchedJoinBlock<T1,T2,T3> collectent des lots d'éléments d'entrée et
propagent les objets System.Tuple(IList(T1), IList(T2)) ou System.Tuple(IList(T1), IList(T2), IList(T3))
qui contiennent ces éléments. BatchedJoinBlock<T1,T2> est un mélange entre BatchBlock<T> et
JoinBlock<T1,T2>. Vous spécifiez la taille de chaque lot quand vous créez un objet BatchedJoinBlock<T1,T2>.
BatchedJoinBlock<T1,T2> fournit également les propriétés Target1 et Target2 qui implémentent
ITargetBlock<TInput>. Quand le nombre spécifié d'éléments d'entrée est reçu par l'ensemble des cibles, l'objet
BatchedJoinBlock<T1,T2> propage de manière asynchrone un objet System.Tuple(IList(T1), IList(T2)) qui
contient ces éléments.
Dans l'exemple simple qui suit, l'objet BatchedJoinBlock<T1,T2> créé contient des résultats, des valeurs Int32 et
des erreurs qui sont des objets Exception. Dans cet exemple, plusieurs opérations sont effectuées. Les résultats
sont écrits dans la propriété Target1 et les erreurs dans la propriété Target2 de l'objet BatchedJoinBlock<T1,T2>.
Étant donné que le nombre d'opérations ayant réussi et ayant échoué n'est pas connu à l'avance, les objets
IList<T> permettent à chaque cible de recevoir zéro, une ou plusieurs valeurs.
// For demonstration, create a Func<int, int> that
// returns its argument, or throws ArgumentOutOfRangeException
// if the argument is less than zero.
Func<int, int> DoWork = n =>
{
if (n < 0)
throw new ArgumentOutOfRangeException();
return n;
};

// Create a BatchedJoinBlock<int, Exception> object that holds


// seven elements per batch.
var batchedJoinBlock = new BatchedJoinBlock<int, Exception>(7);

// Post several items to the block.


foreach (int i in new int[] { 5, 6, -7, -22, 13, 55, 0 })
{
try
{
// Post the result of the worker to the
// first target of the block.
batchedJoinBlock.Target1.Post(DoWork(i));
}
catch (ArgumentOutOfRangeException e)
{
// If an error occurred, post the Exception to the
// second target of the block.
batchedJoinBlock.Target2.Post(e);
}
}

// Read the results from the block.


var results = batchedJoinBlock.Receive();

// Print the results to the console.

// Print the results.


foreach (int n in results.Item1)
{
Console.WriteLine(n);
}
// Print failures.
foreach (Exception e in results.Item2)
{
Console.WriteLine(e.Message);
}

/* Output:
5
6
13
55
0
Specified argument was out of the range of valid values.
Specified argument was out of the range of valid values.
*/
' For demonstration, create a Func<int, int> that
' returns its argument, or throws ArgumentOutOfRangeException
' if the argument is less than zero.
Dim DoWork As Func(Of Integer, Integer) = Function(n)
If n < 0 Then
Throw New ArgumentOutOfRangeException()
End If
Return n
End Function

' Create a BatchedJoinBlock<int, Exception> object that holds


' seven elements per batch.
Dim batchedJoinBlock = New BatchedJoinBlock(Of Integer, Exception)(7)

' Post several items to the block.


For Each i As Integer In New Integer() {5, 6, -7, -22, 13, 55, 0}
Try
' Post the result of the worker to the
' first target of the block.
batchedJoinBlock.Target1.Post(DoWork(i))
Catch e As ArgumentOutOfRangeException
' If an error occurred, post the Exception to the
' second target of the block.
batchedJoinBlock.Target2.Post(e)
End Try
Next i

' Read the results from the block.


Dim results = batchedJoinBlock.Receive()

' Print the results to the console.

' Print the results.


For Each n As Integer In results.Item1
Console.WriteLine(n)
Next n
' Print failures.
For Each e As Exception In results.Item2
Console.WriteLine(e.Message)
Next e

' Output:
' 5
' 6
' 13
' 55
' 0
' Specified argument was out of the range of valid values.
' Specified argument was out of the range of valid values.
'

Vous trouverez un exemple complet utilisant BatchedJoinBlock<T1,T2> pour capturer les résultats et toutes les
exceptions qui se produisent quand le programme lit les données d'une base de données sur la page Procédure
pas à pas : utiliser BatchBlock et BatchedJoinBlock pour améliorer l'efficacité.

Configuration du comportement des blocs de flux de données


Vous pouvez activer des options supplémentaires en fournissant un objet
System.Threading.Tasks.Dataflow.DataflowBlockOptions au constructeur de types de blocs de flux de données.
Ces options permettent de contrôler le comportement, comme celui du planificateur qui gère la tâche sous-
jacente et le degré de parallélisme. DataflowBlockOptions possède également des types dérivés qui spécifient le
comportement spécifique à certains types de blocs de flux de données. Le tableau suivant récapitule les types
d'options qui sont associés à chaque type de bloc de flux de données.
T Y P E DE B LO C DE F L UX DE DO N N ÉES T Y P E DATA F LO W B LO C KO P T IO N S

BufferBlock<T> DataflowBlockOptions

BroadcastBlock<T> DataflowBlockOptions

WriteOnceBlock<T> DataflowBlockOptions

ActionBlock<TInput> ExecutionDataflowBlockOptions

TransformBlock<TInput,TOutput> ExecutionDataflowBlockOptions

TransformManyBlock<TInput,TOutput> ExecutionDataflowBlockOptions

BatchBlock<T> GroupingDataflowBlockOptions

JoinBlock<T1,T2> GroupingDataflowBlockOptions

BatchedJoinBlock<T1,T2> GroupingDataflowBlockOptions

Les sections suivantes fournissent des informations supplémentaires sur les types importants d'options de
blocs de flux de données qui sont disponibles via les classes
System.Threading.Tasks.Dataflow.DataflowBlockOptions,
System.Threading.Tasks.Dataflow.ExecutionDataflowBlockOptions et
System.Threading.Tasks.Dataflow.GroupingDataflowBlockOptions.
Spécification du planificateur de tâches
Chaque bloc de flux de données prédéfini utilise le mécanisme de planification des tâches TPL pour effectuer
des activités, telles que la propagation de données vers une cible, la réception de données à partir d'une source
et l'exécution de délégués définis par l'utilisateur quand des données deviennent disponibles. TaskScheduler est
une classe abstraite qui représente un planificateur de tâches qui place des tâches en attente dans des threads.
Le planificateur de tâches par défaut, Default, utilise la classe ThreadPool pour placer des tâches en file d’attente
et les exécuter. Vous pouvez remplacer le planificateur de tâches par défaut en définissant la propriété
TaskScheduler quand vous créez un objet de bloc de flux de données.
Quand un même planificateur de tâches gère plusieurs blocs de flux de données, il peut appliquer les mêmes
stratégies à chacune d'elles. Par exemple, si plusieurs blocs de flux de données sont configurés de manière à
cibler le planificateur exclusif du même objet ConcurrentExclusiveSchedulerPair, toutes les tâches qui sont
exécutées dans ces blocs seront sérialisées. De même, si ces blocs sont configurés de manière à cibler le
planificateur simultané du même objet ConcurrentExclusiveSchedulerPair et que le planificateur est configuré
avec un niveau d'accès concurrentiel maximal, toutes les tâches de ces blocs seront limitées au nombre
d'opérations simultanées. Vous trouverez un exemple utilisant la classe ConcurrentExclusiveSchedulerPair pour
permettre que des opérations de lecture s’effectuent en parallèle, tout en imposant que chaque opération
d’écriture soit réalisée de manière exclusive sur la page Guide pratique : spécifier un Planificateur de tâches
dans un bloc de flux de données. Pour plus d’informations sur les planificateurs de tâches dans la bibliothèque
parallèle de tâches, consultez la rubrique sur la classe TaskScheduler.
Spécification du degré de parallélisme
Par défaut, les trois types de blocs d'exécution fournis par la bibliothèque de flux de données TPL
(ActionBlock<TInput>, TransformBlock<TInput,TOutput> et TransformManyBlock<TInput,TOutput>) traitent les
messages un par un. Ces types de blocs de flux de données traitent également les messages dans l'ordre dans
lequel ils sont reçus. Pour permettre aux blocs de flux de données de traiter simultanément les messages,
définissez la propriété ExecutionDataflowBlockOptions.MaxDegreeOfParallelism au moment de créer l'objet de
bloc de flux de données.
La valeur par défaut de MaxDegreeOfParallelism est 1, ce qui signifie que le bloc de flux de données traitera les
messages un par un. En définissant cette propriété sur une valeur supérieure à 1, vous permettez au bloc de flux
de données de traiter plusieurs messages simultanément. Si vous définissez cette propriété sur
DataflowBlockOptions.Unbounded, vous permettez au planificateur de tâches sous-jacentes de gérer le degré
maximal de concurrence.

IMPORTANT
Quand vous spécifiez un degré maximal de parallélisme supérieur à 1, plusieurs messages sont traités simultanément. Il se
peut donc que les messages ne soient pas traités dans l’ordre dans lequel ils sont reçus. Toutefois, les messages sont
renvoyés du bloc dans le même ordre qu’ils ont été reçus.

Étant donné que la propriété MaxDegreeOfParallelism représente le degré maximal de parallélisme, le bloc de
flux de données peut s'exécuter avec un degré de parallélisme moindre spécifié par vos soins. Le bloc de flux de
données peut utiliser un degré de parallélisme moindre pour satisfaire ses exigences fonctionnelles ou en cas
de manque de ressources système disponibles. Un bloc de flux de données n'utilisera jamais un niveau de
parallélisme supérieur à celui que vous spécifiez.
La valeur de la propriété MaxDegreeOfParallelism est exclusive à chaque objet de bloc de flux de données. Par
exemple, si chacun des quatre objets de blocs de flux de données spécifie la valeur 1 comme degré maximal de
parallélisme, les quatre objets de bloc de flux de données peuvent être exécutés en parallèle.
Vous trouverez un exemple dans lequel est défini le degré maximal de parallélisme permettant à des opérations
de longue durée d’être exécutées en parallèle sur la page Guide pratique : spécifier le degré de parallélisme
dans un bloc de flux de données.
Spécification du nombre de messages par tâche
Les types de blocs de flux de données prédéfinis utilisent des tâches pour traiter plusieurs éléments d'entrée.
Cela aide à réduire le nombre d'objets de tâches requis pour traiter les données, ce qui permet aux applications
de s'exécuter plus efficacement. Toutefois, quand les tâches d'un ensemble de blocs de flux de données traitent
des données, il est possible que les tâches des autres blocs de flux de données doivent attendre d'être traitées et
placer leurs messages dans la file d'attente. Pour un meilleur respect de l'ordre des tâches de flux de données,
définissez la propriété MaxMessagesPerTask. Quand MaxMessagesPerTask a la valeur
DataflowBlockOptions.Unbounded, qui est la valeur par défaut, la tâche utilisée par un bloc de flux de données
traite tous les messages disponibles. Quand MaxMessagesPerTask est défini sur une valeur autre que
Unbounded, le bloc de flux de données traite au maximum le nombre défini de messages par objet Task. Même
si la configuration de la propriété MaxMessagesPerTask peut améliorer le respect de l'ordre des tâches, elle peut
aussi entraîner la création par le système de davantage de tâches que nécessaire, réduisant ainsi les
performances.
Permettre les annulations
La bibliothèque parallèle de tâches (TPL) fournit un mécanisme qui permet aux tâches de coordonner
l'annulation de manière coopérative. Pour permettre aux blocs de flux de données de participer à ce mécanisme
d'annulation, définissez la propriété CancellationToken. Quand l'objet CancellationToken est à l'état annulé, tous
les blocs de flux de données qui contrôlent ce jeton terminent l'exécution de l'élément actuel, mais ne
démarrent pas le traitement des éléments suivants. De plus, ces blocs de flux de données effacent les messages
mis en mémoire tampon, libèrent les connexions aux blocs sources et cibles, et passent à l'état annulé. Lors du
passage à l'état annulé, la propriété Status de la propriété Completion est définie sur Canceled, sauf si une
exception s'est produite lors du traitement. Dans ce cas, Status est défini sur Faulted.
Vous trouverez un exemple qui montre comment utiliser l’annulation dans une application Windows Forms sur
la page Guide pratique : annuler un bloc de flux de données. Pour plus d’informations sur les annulations dans
la bibliothèque parallèle de tâches, consultez la page Annulation de tâches.
Spécification des comportements gourmand et non gourmand
Plusieurs types de blocs de flux de données de regroupement peuvent fonctionner en mode gourmand ou en
mode non gourmand. Par défaut, les types de blocs de flux de données prédéfinis fonctionnent en mode
gourmand.
Pour les types de blocs de regroupement tels que JoinBlock<T1,T2>, le mode gourmand signifie que le bloc
accepte immédiatement les données, même si les données correspondantes avec lesquelles effectuer le
regroupement ne sont pas encore disponibles. Le mode non gourmand signifie que le bloc diffère tous les
messages entrants jusqu'à ce que chacune de ses cibles ait reçu un message, permettant ainsi le regroupement.
Si l'un des messages différés n'est plus disponible, le bloc de regroupement libère tous les messages différés et
redémarre le processus. Pour la classe BatchBlock<T>, les comportements gourmand et non gourmand sont
similaires, à ceci près qu'en mode non gourmand, un objet BatchBlock<T> diffère tous les messages entrants
jusqu'à ce que suffisamment de messages soient disponibles dans plusieurs sources distinctes pour former un
lot.
Pour spécifier le mode non gourmand pour un bloc de flux de données, définissez Greedy sur False . Vous
trouverez un exemple qui montre comment utiliser le mode non gourmand pour permettre à plusieurs blocs de
regroupement de partager une source de données plus efficacement sur la page Guide pratique : utiliser
JoinBlock pour lire des données issues de plusieurs sources.

Blocs de flux de données personnalisés


Même si la bibliothèque de flux de données TPL fournit de nombreux types de blocs prédéfinis, vous pouvez
créer d'autres types de blocs ayant un comportement personnalisé. Implémentez directement les interfaces
ISourceBlock<TOutput> ou ITargetBlock<TInput>, ou utilisez la méthode Encapsulate pour créer un bloc
complexe qui encapsule le comportement des types de blocs existants. Vous trouverez des exemples qui
montrent comment implémenter la fonctionnalité de bloc de flux de données personnalisé sur la page
Procédure pas à pas : créer un type de bloc de flux de données personnalisé.

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Procédure : Écrire et lire des messages dans un bloc de flux Montre comment écrire des messages dans un objet
de données BufferBlock<T> et les lire.

Procédure : implémenter un modèle de flux de données Explique comment utiliser le modèle de flux de données pour
producteur-consommateur implémenter un modèle producteur-consommateur, où le
producteur envoie des messages à un bloc de flux de
données et le consommateur lit les messages de ce bloc.

Procédure : Exécuter des actions quand un bloc de flux de Explique comment fournir des délégués aux types de blocs
données reçoit des données de flux de données d'exécution ActionBlock<TInput>,
TransformBlock<TInput,TOutput> et
TransformManyBlock<TInput,TOutput>.

Procédure pas à pas : création d’un pipeline de dataflow Explique comment créer un pipeline de flux de données qui
télécharge du texte à partir du web et effectue des
opérations sur ce texte.

Procédure : Dissocier des blocs de flux de données Montre comment utiliser la méthode LinkTo pour dissocier
un bloc cible de sa source après que celle-ci lui a envoyé un
message.
IN T IT UL É DESC RIP T IO N

Procédure pas à pas : utilisation d’un dataflow dans une Montre comment créer un réseau de blocs de flux de
application Windows Forms données qui effectuent un traitement des images dans une
application Windows Forms.

Procédure : annuler un bloc de dataflow Montre comment utiliser l'annulation dans une application
Windows Forms.

Procédure : Utiliser JoinBlock pour lire des données de Explique comment utiliser la classe JoinBlock<T1,T2> pour
plusieurs sources effectuer une opération quand des données sont disponibles
dans plusieurs sources, et comment utiliser le mode non
gourmand pour permettre à plusieurs blocs de
regroupement de partager une source de données plus
efficacement.

Procédure : Spécifier le degré de parallélisme dans un bloc de Explique comment définir la propriété
flux de données MaxDegreeOfParallelism pour permettre à un bloc de flux de
données d'exécution de traiter plusieurs messages à la fois.

Procédure : spécifier un planificateur de tâches dans un bloc Montre comment associer un planificateur de tâches
de dataflow spécifique quand vous utilisez un flux de données dans votre
application.

Procédure pas à pas : Utiliser BatchBlock et BatchedJoinBlock Explique comment utiliser la classe BatchBlock<T> pour
pour améliorer l’efficacité améliorer l'efficacité des opérations d'insertion de bases de
données, et comment utiliser la classe
BatchedJoinBlock<T1,T2> pour capturer les résultats et les
exceptions qui se produisent quand le programme lit les
données d'une base de données.

Procédure pas à pas : Créer un type de bloc de flux de Montre deux façons de créer un type de bloc de flux de
données personnalisé données qui implémente un comportement personnalisé.

Bibliothèque parallèle de tâches Présente la bibliothèque parallèle de tâches (TPL), qui


simplifie la programmation parallèle et simultanée dans les
applications .NET Framework.
Procédure : Écrire et lire des messages dans un bloc
de flux de données
18/07/2020 • 9 minutes to read • Edit Online

Ce document explique comment utiliser la bibliothèque de flux de données TPL pour écrire et lire des messages
sur un bloc de flux de données. La bibliothèque de flux de données TPL fournit à la fois des méthodes synchrones
et asynchrones pour lire et écrire des messages sur un bloc de flux de données. Ce document utilise la classe
System.Threading.Tasks.Dataflow.BufferBlock<T>. La classe BufferBlock<T> met en mémoire tampon des
messages et fonctionne à la fois comme source et comme cible de message.

NOTE
La bibliothèque de flux de données TPL (espace de noms System.Threading.Tasks.Dataflow) n'est pas distribuée avec .NET.
Pour installer l’espace de noms System.Threading.Tasks.Dataflow dans Visual Studio, ouvrez votre projet, choisissez Gérer les
packages NuGet dans le menu Projet , puis recherchez en ligne le package System.Threading.Tasks.Dataflow . Vous
pouvez également l’installer à l’aide de l’interface CLI .NET Core en exécutant
dotnet add package System.Threading.Tasks.Dataflow .

Écriture et lecture d’un bloc de flux de données de façon synchrone


L'exemple suivant utilise la méthode Post pour écrire sur un bloc de flux de données BufferBlock<T> et la méthode
Receive pour lire à partir du même objet.

// Create a BufferBlock<int> object.


var bufferBlock = new BufferBlock<int>();

// Post several messages to the block.


for (int i = 0; i < 3; i++)
{
bufferBlock.Post(i);
}

// Receive the messages back from the block.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(bufferBlock.Receive());
}

/* Output:
0
1
2
*/
' Create a BufferBlock<int> object.
Dim bufferBlock = New BufferBlock(Of Integer)()

' Post several messages to the block.


For i As Integer = 0 To 2
bufferBlock.Post(i)
Next i

' Receive the messages back from the block.


For i As Integer = 0 To 2
Console.WriteLine(bufferBlock.Receive())
Next i

' Output:
' 0
' 1
' 2
'

Vous pouvez également utiliser la méthode TryReceive pour lire à partir d'un bloc de flux de données, comme
illustré dans l'exemple suivant. La méthode TryReceive ne bloque pas le thread actuel et est utile lorsque vous
interrogez occasionnellement les données.

// Post more messages to the block.


for (int i = 0; i < 3; i++)
{
bufferBlock.Post(i);
}

// Receive the messages back from the block.


int value;
while (bufferBlock.TryReceive(out value))
{
Console.WriteLine(value);
}

/* Output:
0
1
2
*/

' Post more messages to the block.


For i As Integer = 0 To 2
bufferBlock.Post(i)
Next i

' Receive the messages back from the block.


Dim value As Integer
Do While bufferBlock.TryReceive(value)
Console.WriteLine(value)
Loop

' Output:
' 0
' 1
' 2
'

Comme la méthode Post agit de manière synchrone, l’objet BufferBlock<T> dans les exemples précédents reçoit
toutes les données avant que la deuxième boucle lise les données. L'exemple suivant étend le premier exemple en
utilisant Invoke pour lire et écrire simultanément sur le bloc de messages. Comme Invoke effectue des actions
simultanément, les valeurs ne sont pas écrites dans l'objet BufferBlock<T> dans un ordre spécifique.

// Write to and read from the message block concurrently.


var post01 = Task.Run(() =>
{
bufferBlock.Post(0);
bufferBlock.Post(1);
});
var receive = Task.Run(() =>
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine(bufferBlock.Receive());
}
});
var post2 = Task.Run(() =>
{
bufferBlock.Post(2);
});
Task.WaitAll(post01, receive, post2);

/* Sample output:
2
0
1
*/

' Write to and read from the message block concurrently.


Dim post01 = Task.Run(Sub()
bufferBlock.Post(0)
bufferBlock.Post(1)
End Sub)
Dim receive = Task.Run(Sub()
For i As Integer = 0 To 2
Console.WriteLine(bufferBlock.Receive())
Next i
End Sub)
Dim post2 = Task.Run(Sub() bufferBlock.Post(2))
Task.WaitAll(post01, receive, post2)

' Sample output:


' 2
' 0
' 1
'

Écriture et lecture d'un bloc de flux de données de façon asynchrone


L'exemple suivant utilise la méthode SendAsync pour l'écriture asynchrone sur un objet BufferBlock<T> et la
méthode ReceiveAsync pour lire de façon asynchrone à partir du même objet. Cet exemple utilise des opérateurs
async et await (Async et Await en Visual Basic) pour envoyer façon asynchrone des données et pour lire des
données dans le bloc cible. La méthode SendAsync est utile lorsque vous devez autoriser un bloc de flux de
données à reporter des messages. La méthode ReceiveAsync est utile lorsque vous souhaitez traiter des données
lorsque ces données deviennent disponibles. Pour plus d’informations sur la façon dont les messages se
propagent à travers les blocs de messages, consultez la section Transmission des Messages dans un flux de
données.
// Post more messages to the block asynchronously.
for (int i = 0; i < 3; i++)
{
await bufferBlock.SendAsync(i);
}

// Asynchronously receive the messages back from the block.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(await bufferBlock.ReceiveAsync());
}

/* Output:
0
1
2
*/

' Post more messages to the block asynchronously.


For i As Integer = 0 To 2
await bufferBlock.SendAsync(i)
Next i

' Asynchronously receive the messages back from the block.


For i As Integer = 0 To 2
Console.WriteLine(await bufferBlock.ReceiveAsync())
Next i

' Output:
' 0
' 1
' 2
'

Un exemple complet
L'exemple suivant présente le code complet pour ce document.

using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

// Demonstrates a how to write to and read from a dataflow block.


class DataflowReadWrite
{
// Demonstrates asynchronous dataflow operations.
static async Task AsyncSendReceive(BufferBlock<int> bufferBlock)
{
// Post more messages to the block asynchronously.
for (int i = 0; i < 3; i++)
{
await bufferBlock.SendAsync(i);
}

// Asynchronously receive the messages back from the block.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(await bufferBlock.ReceiveAsync());
}

/* Output:
0
1
2
2
*/
}

static void Main(string[] args)


{
// Create a BufferBlock<int> object.
var bufferBlock = new BufferBlock<int>();

// Post several messages to the block.


for (int i = 0; i < 3; i++)
{
bufferBlock.Post(i);
}

// Receive the messages back from the block.


for (int i = 0; i < 3; i++)
{
Console.WriteLine(bufferBlock.Receive());
}

/* Output:
0
1
2
*/

// Post more messages to the block.


for (int i = 0; i < 3; i++)
{
bufferBlock.Post(i);
}

// Receive the messages back from the block.


int value;
while (bufferBlock.TryReceive(out value))
{
Console.WriteLine(value);
}

/* Output:
0
1
2
*/

// Write to and read from the message block concurrently.


var post01 = Task.Run(() =>
{
bufferBlock.Post(0);
bufferBlock.Post(1);
});
var receive = Task.Run(() =>
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine(bufferBlock.Receive());
}
});
var post2 = Task.Run(() =>
{
bufferBlock.Post(2);
});
Task.WaitAll(post01, receive, post2);

/* Sample output:
2
0
1
*/

// Demonstrate asynchronous dataflow operations.


AsyncSendReceive(bufferBlock).Wait();
}
}

Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

' Demonstrates a how to write to and read from a dataflow block.


Friend Class DataflowReadWrite
' Demonstrates asynchronous dataflow operations.
Private Shared async Function AsyncSendReceive(ByVal bufferBlock As BufferBlock(Of Integer)) As Task
' Post more messages to the block asynchronously.
For i As Integer = 0 To 2
await bufferBlock.SendAsync(i)
Next i

' Asynchronously receive the messages back from the block.


For i As Integer = 0 To 2
Console.WriteLine(await bufferBlock.ReceiveAsync())
Next i

' Output:
' 0
' 1
' 2
'
End Function

Shared Sub Main(ByVal args() As String)


' Create a BufferBlock<int> object.
Dim bufferBlock = New BufferBlock(Of Integer)()

' Post several messages to the block.


For i As Integer = 0 To 2
bufferBlock.Post(i)
Next i

' Receive the messages back from the block.


For i As Integer = 0 To 2
Console.WriteLine(bufferBlock.Receive())
Next i

' Output:
' 0
' 1
' 2
'

' Post more messages to the block.


For i As Integer = 0 To 2
bufferBlock.Post(i)
Next i

' Receive the messages back from the block.


Dim value As Integer
Do While bufferBlock.TryReceive(value)
Console.WriteLine(value)
Loop

' Output:
' 0
' 1
' 2
'
' Write to and read from the message block concurrently.
Dim post01 = Task.Run(Sub()
bufferBlock.Post(0)
bufferBlock.Post(1)
End Sub)
Dim receive = Task.Run(Sub()
For i As Integer = 0 To 2
Console.WriteLine(bufferBlock.Receive())
Next i
End Sub)
Dim post2 = Task.Run(Sub() bufferBlock.Post(2))
Task.WaitAll(post01, receive, post2)

' Sample output:


' 2
' 0
' 1
'

' Demonstrate asynchronous dataflow operations.


AsyncSendReceive(bufferBlock).Wait()
End Sub

End Class

Étapes suivantes
Cet exemple montre comment lire et écrire directement sur un bloc de messages. Vous pouvez aussi connecter des
blocs de flux de données pour former des pipelines, qui sont des séquences linéaires de blocs de flux de données,
ou des réseaux, qui sont des graphiques des blocs de flux de données. Dans un pipeline ou un réseau, les sources
propagent des données de manière asynchrone vers les cibles à mesure que les données deviennent disponibles.
Pour obtenir un exemple de création d’un pipeline de flux de données, consultez Procédure pas à pas : création
d’un pipeline de flux de données. Pour obtenir un exemple de création d’un réseau de flux de données plus
complexe, consultez Procédure pas à pas : utilisation de flux de données dans une application Windows Forms.

Voir aussi
Dataflow
Procédure : implémenter un modèle de flux de
données producteur-consommateur
18/07/2020 • 7 minutes to read • Edit Online

Ce document décrit comment utiliser la bibliothèque de flux de données TPL pour implémenter un modèle
producteur-consommateur. Dans ce modèle, le producteur envoie des messages à un bloc de message et le
consommateur lit les messages de ce bloc.

NOTE
La bibliothèque de flux de données TPL (espace de noms System.Threading.Tasks.Dataflow) n'est pas distribuée avec .NET.
Pour installer l’espace de noms System.Threading.Tasks.Dataflow dans Visual Studio, ouvrez votre projet, choisissez Gérer les
packages NuGet dans le menu Projet , puis recherchez en ligne le package System.Threading.Tasks.Dataflow . Vous
pouvez également l’installer à l’aide de l’interface CLI .NET Core en exécutant
dotnet add package System.Threading.Tasks.Dataflow .

Exemple
L’exemple suivant montre un modèle producteur-consommateur de base qui utilise des flux de données. La
méthode Produce écrit des tableaux contenant les octets aléatoires de données dans un objet
System.Threading.Tasks.Dataflow.ITargetBlock<TInput>, et la méthode Consume lit les octets à partir d’un objet
System.Threading.Tasks.Dataflow.ISourceBlock<TOutput>. En utilisant les interfaces ISourceBlock<TOutput> et
ITargetBlock<TInput>, et non leurs types dérivés, vous pouvez écrire du code réutilisable sur une variété de types
de blocs de flux de données. Cet exemple utilise la classe BufferBlock<T>. Dans la mesure où la classe
BufferBlock<T> agit à la fois en tant que source et cible, le producteur et le consommateur peuvent utiliser un objet
partagé pour transférer les données.
La méthode Produce appelle la méthode Post dans une boucle pour écrire de façon synchrone des données vers le
bloc cible. Dès que la méthode Produce a écrit toutes les données dans le bloc cible, celle-ci appelle la méthode
Complete pour indiquer que le bloc n’aura jamais aucune donnée supplémentaire disponible. La méthode Consume
utilise les opérateurs async et await (Async et Await en Visual Basic) pour calculer de façon asynchrone le nombre
total d’octets provenant de l’objet ISourceBlock<TOutput>. Pour agir de façon asynchrone, la méthode Consume
appelle la méthode OutputAvailableAsync pour recevoir une notification lorsque le bloc source possède des
données disponibles et lorsque le bloc source n’aura jamais aucune donnée supplémentaire disponible.

using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

// Demonstrates a basic producer and consumer pattern that uses dataflow.


class DataflowProducerConsumer
{
// Demonstrates the production end of the producer and consumer pattern.
static void Produce(ITargetBlock<byte[]> target)
{
// Create a Random object to generate random data.
Random rand = new Random();

// In a loop, fill a buffer with random data and


// post the buffer to the target block.
for (int i = 0; i < 100; i++)
{
{
// Create an array to hold random byte data.
byte[] buffer = new byte[1024];

// Fill the buffer with random bytes.


rand.NextBytes(buffer);

// Post the result to the message block.


target.Post(buffer);
}

// Set the target to the completed state to signal to the consumer


// that no more data will be available.
target.Complete();
}

// Demonstrates the consumption end of the producer and consumer pattern.


static async Task<int> ConsumeAsync(ISourceBlock<byte[]> source)
{
// Initialize a counter to track the number of bytes that are processed.
int bytesProcessed = 0;

// Read from the source buffer until the source buffer has no
// available output data.
while (await source.OutputAvailableAsync())
{
byte[] data = source.Receive();

// Increment the count of bytes received.


bytesProcessed += data.Length;
}

return bytesProcessed;
}

static void Main(string[] args)


{
// Create a BufferBlock<byte[]> object. This object serves as the
// target block for the producer and the source block for the consumer.
var buffer = new BufferBlock<byte[]>();

// Start the consumer. The Consume method runs asynchronously.


var consumer = ConsumeAsync(buffer);

// Post source data to the dataflow block.


Produce(buffer);

// Wait for the consumer to process all data.


consumer.Wait();

// Print the count of bytes processed to the console.


Console.WriteLine("Processed {0} bytes.", consumer.Result);
}
}

/* Output:
Processed 102400 bytes.
*/

Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

' Demonstrates a basic producer and consumer pattern that uses dataflow.
Friend Class DataflowProducerConsumer
' Demonstrates the production end of the producer and consumer pattern.
Private Shared Sub Produce(ByVal target As ITargetBlock(Of Byte()))
' Create a Random object to generate random data.
Dim rand As New Random()
' In a loop, fill a buffer with random data and
' post the buffer to the target block.
For i As Integer = 0 To 99
' Create an array to hold random byte data.
Dim buffer(1023) As Byte

' Fill the buffer with random bytes.


rand.NextBytes(buffer)

' Post the result to the message block.


target.Post(buffer)
Next i

' Set the target to the completed state to signal to the consumer
' that no more data will be available.
target.Complete()
End Sub

' Demonstrates the consumption end of the producer and consumer pattern.
Private Shared async Function ConsumeAsync(ByVal source As ISourceBlock(Of Byte())) As Task(Of Integer)
' Initialize a counter to track the number of bytes that are processed.
Dim bytesProcessed As Integer = 0

' Read from the source buffer until the source buffer has no
' available output data.
Do While await source.OutputAvailableAsync()
Dim data() As Byte = source.Receive()

' Increment the count of bytes received.


bytesProcessed += data.Length
Loop

Return bytesProcessed
End Function

Shared Sub Main(ByVal args() As String)


' Create a BufferBlock<byte[]> object. This object serves as the
' target block for the producer and the source block for the consumer.
Dim buffer = New BufferBlock(Of Byte())()

' Start the consumer. The Consume method runs asynchronously.


Dim consumer = ConsumeAsync(buffer)

' Post source data to the dataflow block.


Produce(buffer)

' Wait for the consumer to process all data.


consumer.Wait()

' Print the count of bytes processed to the console.


Console.WriteLine("Processed {0} bytes.", consumer.Result)
End Sub
End Class

' Output:
'Processed 102400 bytes.
'

Programmation fiable
L’exemple précédent utilise un seul consommateur pour traiter les données sources. Si votre application dispose de
plusieurs consommateurs, utilisez la méthode TryReceive pour lire des données à partir du bloc source, comme
indiqué dans l’exemple suivant.
// Demonstrates the consumption end of the producer and consumer pattern.
static async Task<int> ConsumeAsync(IReceivableSourceBlock<byte[]> source)
{
// Initialize a counter to track the number of bytes that are processed.
int bytesProcessed = 0;

// Read from the source buffer until the source buffer has no
// available output data.
while (await source.OutputAvailableAsync())
{
byte[] data;
while (source.TryReceive(out data))
{
// Increment the count of bytes received.
bytesProcessed += data.Length;
}
}

return bytesProcessed;
}

' Demonstrates the consumption end of the producer and consumer pattern.
Private Shared async Function ConsumeAsync(ByVal source As IReceivableSourceBlock(Of Byte())) As Task(Of
Integer)
' Initialize a counter to track the number of bytes that are processed.
Dim bytesProcessed As Integer = 0

' Read from the source buffer until the source buffer has no
' available output data.
Do While await source.OutputAvailableAsync()
Dim data() As Byte
Do While source.TryReceive(data)
' Increment the count of bytes received.
bytesProcessed += data.Length
Loop
Loop

Return bytesProcessed
End Function

La méthode TryReceive retourne False si donnée n’est disponible. Lorsque plusieurs consommateurs doivent
accéder simultanément au bloc source, ce mécanisme garantit que les données sont toujours disponibles après
l’appel à OutputAvailableAsync.

Voir aussi
Dataflow
Procédure : Exécuter des actions quand un bloc de
flux de données reçoit des données
18/07/2020 • 9 minutes to read • Edit Online

Les types de Bloc de flux de données d’exécution appellent un délégué fourni par l’utilisateur lorsqu’ils reçoivent
des données. Les classes System.Threading.Tasks.Dataflow.ActionBlock<TInput>,
System.Threading.Tasks.Dataflow.TransformBlock<TInput,TOutput>, et
System.Threading.Tasks.Dataflow.TransformManyBlock<TInput,TOutput> sont des types de bloc de flux de
données d'exécution. Vous pouvez utiliser le mot clé delegate ( Sub en Visual Basic), Action<T>, Func<T,TResult>
ou une expression lambda quand vous fournissez une fonction de travail dans un bloc de flux de données
d’exécution. Ce document explique comment utiliser Func<T,TResult> et les expressions lambda pour effectuer
l’action dans des blocs d’exécution.

NOTE
La bibliothèque de flux de données TPL (espace de noms System.Threading.Tasks.Dataflow) n'est pas distribuée avec .NET.
Pour installer l’espace de noms System.Threading.Tasks.Dataflow dans Visual Studio, ouvrez votre projet, choisissez Gérer
les packages NuGet dans le menu Projet , puis recherchez en ligne le package System.Threading.Tasks.Dataflow .
Vous pouvez également l’installer à l’aide de l’interface CLI .NET Core en exécutant
dotnet add package System.Threading.Tasks.Dataflow .

Exemple
L'exemple suivant utilise le flux de données pour lire un fichier de disque et calcule le nombre d'octets qui sont
égaux à zéro dans ce fichier. Il utilise TransformBlock<TInput,TOutput> pour lire le fichier et calculer le nombre
d'octets nuls, et ActionBlock<TInput> pour imprimer le nombre d'octets nuls sur la console. L'objet
TransformBlock<TInput,TOutput> spécifie un objet Func<T,TResult> pour effectuer le travail lorsque les blocs
reçoivent les données. L'objet ActionBlock<TInput> utilise une expression lambda pour afficher sur la console le
nombre d'octets nuls qui sont lus.

using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to provide delegates to exectution dataflow blocks.


class DataflowExecutionBlocks
{
// Computes the number of zero bytes that the provided file
// contains.
static int CountBytes(string path)
{
byte[] buffer = new byte[1024];
int totalZeroBytesRead = 0;
using (var fileStream = File.OpenRead(path))
{
int bytesRead = 0;
do
{
bytesRead = fileStream.Read(buffer, 0, buffer.Length);
totalZeroBytesRead += buffer.Count(b => b == 0);
} while (bytesRead > 0);
}

return totalZeroBytesRead;
}

static void Main(string[] args)


{
// Create a temporary file on disk.
string tempFile = Path.GetTempFileName();

// Write random data to the temporary file.


using (var fileStream = File.OpenWrite(tempFile))
{
Random rand = new Random();
byte[] buffer = new byte[1024];
for (int i = 0; i < 512; i++)
{
rand.NextBytes(buffer);
fileStream.Write(buffer, 0, buffer.Length);
}
}

// Create an ActionBlock<int> object that prints to the console


// the number of bytes read.
var printResult = new ActionBlock<int>(zeroBytesRead =>
{
Console.WriteLine("{0} contains {1} zero bytes.",
Path.GetFileName(tempFile), zeroBytesRead);
});

// Create a TransformBlock<string, int> object that calls the


// CountBytes function and returns its result.
var countBytes = new TransformBlock<string, int>(
new Func<string, int>(CountBytes));

// Link the TransformBlock<string, int> object to the


// ActionBlock<int> object.
countBytes.LinkTo(printResult);

// Create a continuation task that completes the ActionBlock<int>


// object when the TransformBlock<string, int> finishes.
countBytes.Completion.ContinueWith(delegate { printResult.Complete(); });

// Post the path to the temporary file to the


// TransformBlock<string, int> object.
countBytes.Post(tempFile);

// Requests completion of the TransformBlock<string, int> object.


countBytes.Complete();

// Wait for the ActionBlock<int> object to print the message.


printResult.Completion.Wait();

// Delete the temporary file.


File.Delete(tempFile);
}
}

/* Sample output:
tmp4FBE.tmp contains 2081 zero bytes.
*/

Imports System.IO
Imports System.Linq
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to provide delegates to exectution dataflow blocks.


' Demonstrates how to provide delegates to exectution dataflow blocks.
Friend Class DataflowExecutionBlocks
' Computes the number of zero bytes that the provided file
' contains.
Private Shared Function CountBytes(ByVal path As String) As Integer
Dim buffer(1023) As Byte
Dim totalZeroBytesRead As Integer = 0
Using fileStream = File.OpenRead(path)
Dim bytesRead As Integer = 0
Do
bytesRead = fileStream.Read(buffer, 0, buffer.Length)
totalZeroBytesRead += buffer.Count(Function(b) b = 0)
Loop While bytesRead > 0
End Using

Return totalZeroBytesRead
End Function

Shared Sub Main(ByVal args() As String)


' Create a temporary file on disk.
Dim tempFile As String = Path.GetTempFileName()

' Write random data to the temporary file.


Using fileStream = File.OpenWrite(tempFile)
Dim rand As New Random()
Dim buffer(1023) As Byte
For i As Integer = 0 To 511
rand.NextBytes(buffer)
fileStream.Write(buffer, 0, buffer.Length)
Next i
End Using

' Create an ActionBlock<int> object that prints to the console


' the number of bytes read.
Dim printResult = New ActionBlock(Of Integer)(Sub(zeroBytesRead) Console.WriteLine("{0} contains {1}
zero bytes.", Path.GetFileName(tempFile), zeroBytesRead))

' Create a TransformBlock<string, int> object that calls the


' CountBytes function and returns its result.
Dim countBytes = New TransformBlock(Of String, Integer)(New Func(Of String, Integer)(AddressOf
DataflowExecutionBlocks.CountBytes))

' Link the TransformBlock<string, int> object to the


' ActionBlock<int> object.
countBytes.LinkTo(printResult)

' Create a continuation task that completes the ActionBlock<int>


' object when the TransformBlock<string, int> finishes.
countBytes.Completion.ContinueWith(Sub() printResult.Complete())

' Post the path to the temporary file to the


' TransformBlock<string, int> object.
countBytes.Post(tempFile)

' Requests completion of the TransformBlock<string, int> object.


countBytes.Complete()

' Wait for the ActionBlock<int> object to print the message.


printResult.Completion.Wait()

' Delete the temporary file.


File.Delete(tempFile)
End Sub
End Class

' Sample output:


'tmp4FBE.tmp contains 2081 zero bytes.
'
Bien que vous puissiez spécifier une expression lambda à un objet TransformBlock<TInput,TOutput>, cet exemple
utilise Func<T,TResult> pour permettre à l'autre code d'utiliser la méthode CountBytes . L’objet
ActionBlock<TInput> utilise une expression lambda car le travail à effectuer est spécifique à cette tâche et n’est
pas susceptible d’être utile depuis un autre code. Pour plus d’informations sur la façon dont les expressions
lambda fonctionnent dans la bibliothèque de tâches en parallèle, consultez Expressions lambda en PLINQ et dans
la bibliothèque parallèle de tâches.
La section Résumé des types délégués dans le document de flux de données résume les types délégués que vous
pouvez fournir aux ActionBlock<TInput> TransformBlock<TInput,TOutput> objets, et
TransformManyBlock<TInput,TOutput> . La table indique également si le type délégué fonctionne de façon
synchrone ou asynchrone.

Programmation fiable
Cet exemple fournit un délégué de type Func<T,TResult> à l'objet TransformBlock<TInput,TOutput> pour effectuer
la tâche du bloc de flux de données de façon synchrone. Pour permettre au bloc de flux de données de se
comporter de façon asynchrone, fournissez un délégué de type Func<TResult> au bloc de flux de données.
Lorsqu’un bloc de flux de données se comporte de façon asynchrone, la tâche du bloc de flux de données se
termine uniquement lorsque l’objet Task<TResult> retourné s’achève. L’exemple suivant modifie la méthode
CountBytes et utilise les opérateurs async et await (Async et Await en Visual Basic) pour calculer de façon
asynchrone le nombre total d’octets qui sont égaux à zéro dans le fichier fourni. La méthode ReadAsync effectue
de façon asynchrone des opérations de lecture de fichiers.

// Asynchronously computes the number of zero bytes that the provided file
// contains.
static async Task<int> CountBytesAsync(string path)
{
byte[] buffer = new byte[1024];
int totalZeroBytesRead = 0;
using (var fileStream = new FileStream(
path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x1000, true))
{
int bytesRead = 0;
do
{
// Asynchronously read from the file stream.
bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length);
totalZeroBytesRead += buffer.Count(b => b == 0);
} while (bytesRead > 0);
}

return totalZeroBytesRead;
}
' Asynchronously computes the number of zero bytes that the provided file
' contains.
Private Shared async Function CountBytesAsync(ByVal path As String) As Task(Of Integer)
Dim buffer(1023) As Byte
Dim totalZeroBytesRead As Integer = 0
Using fileStream = New FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, &H1000, True)
Dim bytesRead As Integer = 0
Do
' Asynchronously read from the file stream.
bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length)
totalZeroBytesRead += buffer.Count(Function(b) b = 0)
Loop While bytesRead > 0
End Using

Return totalZeroBytesRead
End Function

Utilisez également des expressions lambda asynchrones pour effectuer une action dans un bloc de flux de
données d’exécution. L’exemple suivant modifie l’objet TransformBlock<TInput,TOutput> utilisé dans l’exemple
précédent pour qu’il utilise une expression lambda pour effectuer de manière asynchrone le travail.

// Create a TransformBlock<string, int> object that calls the


// CountBytes function and returns its result.
var countBytesAsync = new TransformBlock<string, int>(async path =>
{
byte[] buffer = new byte[1024];
int totalZeroBytesRead = 0;
using (var fileStream = new FileStream(
path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x1000, true))
{
int bytesRead = 0;
do
{
// Asynchronously read from the file stream.
bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length);
totalZeroBytesRead += buffer.Count(b => b == 0);
} while (bytesRead > 0);
}

return totalZeroBytesRead;
});

' Create a TransformBlock<string, int> object that calls the


' CountBytes function and returns its result.
Dim countBytesAsync = New TransformBlock(Of String, Integer)(async Function(path)
' Asynchronously read from the file stream.
Dim buffer(1023) As Byte
Dim totalZeroBytesRead As Integer = 0
Using fileStream = New FileStream(path,
FileMode.Open, FileAccess.Read, FileShare.Read, &H1000, True)
Dim bytesRead As Integer = 0
Do
bytesRead = await
fileStream.ReadAsync(buffer, 0, buffer.Length)
totalZeroBytesRead +=
buffer.Count(Function(b) b = 0)
Loop While bytesRead > 0
End Using
Return totalZeroBytesRead
End Function)
Voir aussi
Dataflow
Procédure pas à pas : création d’un pipeline de
dataflow
18/07/2020 • 19 minutes to read • Edit Online

Bien que vous puissiez utiliser les méthodes DataflowBlock.Receive, DataflowBlock.ReceiveAsync et


DataflowBlock.TryReceive pour recevoir des messages des blocs sources, vous pouvez également connecter des
blocs de messages pour former un pipeline de flux de données. Un pipeline de flux de données est une série de
composants, ou de blocs de flux de données, qui effectuent chacun une tâche spécifique qui contribue à un plus
grand objectif. Chaque bloc de flux de données d'un pipeline de flux de données effectue un travail lorsqu'il reçoit
un message d'un autre bloc de flux de données. Ce processus s'apparente à une chaîne de montage en
construction automobile. Comme chaque véhicule passe via la ligne de montage, un poste assemble le châssis, le
suivant installe le moteur, et ainsi de suite. Étant donné qu'une ligne d'assemblage permet à plusieurs véhicules
d'être assemblés en même temps, cela fournit une productivité supérieure à l'assemblage un par un des
véhicules.
Ce document montre un pipeline de flux de données qui télécharge le livre L’Illiade d’Homère à partir d’un site
web et recherche dans le texte les mots dont l’inversion des caractères permet d’obtenir un autre mot. La
formation du pipeline de flux de données dans ce document comprend les étapes suivantes :
1. Créez des blocs de flux de données qui participent au pipeline.
2. Connectez chaque bloc de flux de données au bloc suivant dans le pipeline. Chaque bloc reçoit comme
entrée la sortie du bloc précédent du pipeline.
3. Pour chaque bloc de flux de données, créez une tâche de continuation qui définit le bloc suivant à l’état
arrêté après que le bloc précédent ait terminé.
4. Publiez les données au début du pipeline.
5. Marquez le début du pipeline comme terminé.
6. Attendez que le pipeline termine tous les travaux.

Prérequis
Lisez la rubrique Flux de données avant de démarrer cette procédure pas à pas.

Création d'une application console


Dans Visual Studio, créez un projet Application console en Visual C# ou en Visual Basic. Installez le package
NuGet System.Threading.Tasks.Dataflow.

NOTE
La bibliothèque de flux de données TPL (espace de noms System.Threading.Tasks.Dataflow) n'est pas distribuée avec .NET.
Pour installer l’espace de noms System.Threading.Tasks.Dataflow dans Visual Studio, ouvrez votre projet, choisissez Gérer
les packages NuGet dans le menu Projet , puis recherchez en ligne le package System.Threading.Tasks.Dataflow .
Vous pouvez également l’installer à l’aide de l’interface CLI .NET Core en exécutant
dotnet add package System.Threading.Tasks.Dataflow .

Ajoutez le code suivant à votre projet pour créer l'application de base.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to create a basic dataflow pipeline.


// This program downloads the book "The Iliad of Homer" by Homer from the Web
// and finds all reversed words that appear in that book.
static class Program
{
static void Main()
{
}
}

Imports System.Net.Http
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to create a basic dataflow pipeline.


' This program downloads the book "The Iliad of Homer" by Homer from the Web
' and finds all reversed words that appear in that book.
Module DataflowReversedWords

Sub Main()
End Sub

End Module

Création des blocs de flux de données


Ajoutez le code suivant à la méthode Main pour créer des blocs de flux de données qui participent au pipeline. Le
tableau suivant résume le rôle de chaque membre du pipeline.
//
// Create the members of the pipeline.
//

// Downloads the requested resource as a string.


var downloadString = new TransformBlock<string, string>(async uri =>
{
Console.WriteLine("Downloading '{0}'...", uri);

return await new HttpClient(new HttpClientHandler{ AutomaticDecompression =


System.Net.DecompressionMethods.GZip }).GetStringAsync(uri);
});

// Separates the specified text into an array of words.


var createWordList = new TransformBlock<string, string[]>(text =>
{
Console.WriteLine("Creating word list...");

// Remove common punctuation by replacing all non-letter characters


// with a space character.
char[] tokens = text.Select(c => char.IsLetter(c) ? c : ' ').ToArray();
text = new string(tokens);

// Separate the text into an array of words.


return text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
});

// Removes short words and duplicates.


var filterWordList = new TransformBlock<string[], string[]>(words =>
{
Console.WriteLine("Filtering word list...");

return words
.Where(word => word.Length > 3)
.Distinct()
.ToArray();
});

// Finds all words in the specified collection whose reverse also


// exists in the collection.
var findReversedWords = new TransformManyBlock<string[], string>(words =>
{
Console.WriteLine("Finding reversed words...");

var wordsSet = new HashSet<string>(words);

return from word in words.AsParallel()


let reverse = new string(word.Reverse().ToArray())
where word != reverse && wordsSet.Contains(reverse)
select word;
});

// Prints the provided reversed words to the console.


var printReversedWords = new ActionBlock<string>(reversedWord =>
{
Console.WriteLine("Found reversed words {0}/{1}",
reversedWord, new string(reversedWord.Reverse().ToArray()));
});
'
' Create the members of the pipeline.
'

' Downloads the requested resource as a string.


Dim downloadString = New TransformBlock(Of String, String)(
Async Function(uri)
Console.WriteLine("Downloading '{0}'...", uri)

Return Await New HttpClient().GetStringAsync(uri)


End Function)

' Separates the specified text into an array of words.


Dim createWordList = New TransformBlock(Of String, String())(
Function(text)
Console.WriteLine("Creating word list...")

' Remove common punctuation by replacing all non-letter characters


' with a space character.
Dim tokens() As Char = text.Select(Function(c) If(Char.IsLetter(c), c, " "c)).ToArray()
text = New String(tokens)

' Separate the text into an array of words.


Return text.Split(New Char() {" "c}, StringSplitOptions.RemoveEmptyEntries)
End Function)

' Removes short words and duplicates.


Dim filterWordList = New TransformBlock(Of String(), String())(
Function(words)
Console.WriteLine("Filtering word list...")

Return words.Where(Function(word) word.Length > 3).Distinct().ToArray()


End Function)

' Finds all words in the specified collection whose reverse also
' exists in the collection.
Dim findReversedWords = New TransformManyBlock(Of String(), String)(
Function(words)

Dim wordsSet = New HashSet(Of String)(words)

Return From word In words.AsParallel()


Let reverse = New String(word.Reverse().ToArray())
Where word <> reverse AndAlso wordsSet.Contains(reverse)
Select word
End Function)

' Prints the provided reversed words to the console.


Dim printReversedWords = New ActionBlock(Of String)(
Sub(reversedWord)
Console.WriteLine("Found reversed words {0}/{1}", reversedWord, New
String(reversedWord.Reverse().ToArray()))
End Sub)

M EM B RE TYPE DESC RIP T IO N

downloadString TransformBlock<TInput,TOutput> Télécharge le texte du livre depuis le


Web.

createWordList TransformBlock<TInput,TOutput> Sépare le texte du livre dans un tableau


de mots.
M EM B RE TYPE DESC RIP T IO N

filterWordList TransformBlock<TInput,TOutput> Supprime les mots courts et les


doublons du tableau de mots.

findReversedWords TransformManyBlock<TInput,TOutput> Recherche tous les mots dans la


collection filtrée de tableau de mots
dont le changement se produit
également dans le tableau de mots.

printReversedWords ActionBlock<TInput> Affiche les mots et les mots inversés


correspondants dans la console.

Bien que vous puissiez combiner plusieurs étapes de cet exemple dans le pipeline de flux de données en une
étape, l’exemple illustre le concept de composition de plusieurs tâches distinctes de flux de données pour
effectuer une plus grande tâche. L'exemple utilise TransformBlock<TInput,TOutput> pour permettre à chaque
membre du pipeline d'exécuter une opération sur les données d'entrée et d'envoyer les résultats à l'étape
suivante dans le pipeline. Le membre findReversedWords du pipeline est un objet
TransformManyBlock<TInput,TOutput> car il génère des sorties multiples indépendantes pour chaque entrée. La
queue du pipeline, printReversedWords , est un objet ActionBlock<TInput> car elle exécute une action sur son
entrée, et ne produit aucun résultat.

Formation du pipeline
Ajoutez le code suivant pour adapter chaque bloc au bloc suivant dans le pipeline.
Lorsque vous appelez la méthode LinkTo pour adapter un bloc source de flux de données à un bloc cible de flux
de données, le bloc source de flux de données se propage au bloc cible lorsque les données sont disponibles. Si
vous spécifiez également DataflowLinkOptions avec PropagateCompletion défini sur true, la réussite ou l’échec
de l’achèvement d’un bloc dans le pipeline entraînera l’achèvement du bloc suivant dans le pipeline.

//
// Connect the dataflow blocks to form a pipeline.
//

var linkOptions = new DataflowLinkOptions { PropagateCompletion = true };

downloadString.LinkTo(createWordList, linkOptions);
createWordList.LinkTo(filterWordList, linkOptions);
filterWordList.LinkTo(findReversedWords, linkOptions);
findReversedWords.LinkTo(printReversedWords, linkOptions);

'
' Connect the dataflow blocks to form a pipeline.
'

Dim linkOptions = New DataflowLinkOptions With {.PropagateCompletion = True}

downloadString.LinkTo(createWordList, linkOptions)
createWordList.LinkTo(filterWordList, linkOptions)
filterWordList.LinkTo(findReversedWords, linkOptions)
findReversedWords.LinkTo(printReversedWords, linkOptions)

Publication des données du pipeline


Ajoutez le code suivant pour publier l'URL du livre de l'Iliade d'Homère au début du pipeline de flux de données.
// Process "The Iliad of Homer" by Homer.
downloadString.Post("http://www.gutenberg.org/cache/epub/16452/pg16452.txt");

' Process "The Iliad of Homer" by Homer.


downloadString.Post("http://www.gutenberg.org/cache/epub/16452/pg16452.txt")

Cet exemple utilise DataflowBlock.Post pour envoyer des données de façon synchrone au début du pipeline.
Utilisez la méthode DataflowBlock.SendAsync lorsque vous devez envoyer de manière asynchrone des données à
un nœud de flux de données.

Fermeture de l'activité du pipeline


Ajoutez le code suivant pour indiquer que le début du pipeline est terminé. Le début du pipeline propage son
achèvement lorsqu’il a traité tous les messages mis en mémoire tampon.

// Mark the head of the pipeline as complete.


downloadString.Complete();

' Mark the head of the pipeline as complete.


downloadString.Complete()

Cet exemple envoie une URL via le pipeline de flux de données à traiter. Si vous devez envoyer plusieurs entrées
par un pipeline, appelez la méthode IDataflowBlock.Complete après avoir soumis toutes les entrées. Vous pouvez
omettre cette étape si votre application n'a pas de points bien définis à partir desquels les données ne sont plus
disponibles ou si l'application n'a pas à attendre que le pipeline se termine.

Attente de la fermeture du pipeline


Ajoutez le code suivant pour attendre que le pipeline se termine. L’opération globale se termine lorsque la fin du
pipeline est atteinte.

// Wait for the last block in the pipeline to process all messages.
printReversedWords.Completion.Wait();

' Wait for the last block in the pipeline to process all messages.
printReversedWords.Completion.Wait()

Vous pouvez attendre la fin de flux de données de tous les threads ou de plusieurs threads simultanément.

Exemple complet
L'exemple suivant présente le code complet pour cette visite.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to create a basic dataflow pipeline.


// This program downloads the book "The Iliad of Homer" by Homer from the Web
// and finds all reversed words that appear in that book.
static class DataflowReversedWords
{
static void Main()
{
//
// Create the members of the pipeline.
//

// Downloads the requested resource as a string.


var downloadString = new TransformBlock<string, string>(async uri =>
{
Console.WriteLine("Downloading '{0}'...", uri);

return await new HttpClient(new HttpClientHandler{ AutomaticDecompression =


System.Net.DecompressionMethods.GZip }).GetStringAsync(uri);
});

// Separates the specified text into an array of words.


var createWordList = new TransformBlock<string, string[]>(text =>
{
Console.WriteLine("Creating word list...");

// Remove common punctuation by replacing all non-letter characters


// with a space character.
char[] tokens = text.Select(c => char.IsLetter(c) ? c : ' ').ToArray();
text = new string(tokens);

// Separate the text into an array of words.


return text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
});

// Removes short words and duplicates.


var filterWordList = new TransformBlock<string[], string[]>(words =>
{
Console.WriteLine("Filtering word list...");

return words
.Where(word => word.Length > 3)
.Distinct()
.ToArray();
});

// Finds all words in the specified collection whose reverse also


// exists in the collection.
var findReversedWords = new TransformManyBlock<string[], string>(words =>
{
Console.WriteLine("Finding reversed words...");

var wordsSet = new HashSet<string>(words);

return from word in words.AsParallel()


let reverse = new string(word.Reverse().ToArray())
where word != reverse && wordsSet.Contains(reverse)
select word;
});

// Prints the provided reversed words to the console.


var printReversedWords = new ActionBlock<string>(reversedWord =>
{
Console.WriteLine("Found reversed words {0}/{1}",
reversedWord, new string(reversedWord.Reverse().ToArray()));
});

//
// Connect the dataflow blocks to form a pipeline.
//

var linkOptions = new DataflowLinkOptions { PropagateCompletion = true };


downloadString.LinkTo(createWordList, linkOptions);
createWordList.LinkTo(filterWordList, linkOptions);
filterWordList.LinkTo(findReversedWords, linkOptions);
findReversedWords.LinkTo(printReversedWords, linkOptions);

// Process "The Iliad of Homer" by Homer.


downloadString.Post("http://www.gutenberg.org/cache/epub/16452/pg16452.txt");

// Mark the head of the pipeline as complete.


downloadString.Complete();

// Wait for the last block in the pipeline to process all messages.
printReversedWords.Completion.Wait();
}
}
/* Sample output:
Downloading 'http://www.gutenberg.org/cache/epub/16452/pg16452.txt'...
Creating word list...
Filtering word list...
Finding reversed words...
Found reversed words doom/mood
Found reversed words draw/ward
Found reversed words aera/area
Found reversed words seat/taes
Found reversed words live/evil
Found reversed words port/trop
Found reversed words sleek/keels
Found reversed words area/aera
Found reversed words tops/spot
Found reversed words evil/live
Found reversed words mood/doom
Found reversed words speed/deeps
Found reversed words moor/room
Found reversed words trop/port
Found reversed words spot/tops
Found reversed words spots/stops
Found reversed words stops/spots
Found reversed words reed/deer
Found reversed words keels/sleek
Found reversed words deeps/speed
Found reversed words deer/reed
Found reversed words taes/seat
Found reversed words room/moor
Found reversed words ward/draw
*/

Imports System.Net.Http
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to create a basic dataflow pipeline.


' This program downloads the book "The Iliad of Homer" by Homer from the Web
' and finds all reversed words that appear in that book.
Module DataflowReversedWords

Sub Main()
'
' Create the members of the pipeline.
'

' Downloads the requested resource as a string.


Dim downloadString = New TransformBlock(Of String, String)(
Async Function(uri)
Console.WriteLine("Downloading '{0}'...", uri)

Return Await New HttpClient().GetStringAsync(uri)


End Function)
' Separates the specified text into an array of words.
Dim createWordList = New TransformBlock(Of String, String())(
Function(text)
Console.WriteLine("Creating word list...")

' Remove common punctuation by replacing all non-letter characters


' with a space character.
Dim tokens() As Char = text.Select(Function(c) If(Char.IsLetter(c), c, " "c)).ToArray()
text = New String(tokens)

' Separate the text into an array of words.


Return text.Split(New Char() {" "c}, StringSplitOptions.RemoveEmptyEntries)
End Function)

' Removes short words and duplicates.


Dim filterWordList = New TransformBlock(Of String(), String())(
Function(words)
Console.WriteLine("Filtering word list...")

Return words.Where(Function(word) word.Length > 3).Distinct().ToArray()


End Function)

' Finds all words in the specified collection whose reverse also
' exists in the collection.
Dim findReversedWords = New TransformManyBlock(Of String(), String)(
Function(words)

Dim wordsSet = New HashSet(Of String)(words)

Return From word In words.AsParallel()


Let reverse = New String(word.Reverse().ToArray())
Where word <> reverse AndAlso wordsSet.Contains(reverse)
Select word
End Function)

' Prints the provided reversed words to the console.


Dim printReversedWords = New ActionBlock(Of String)(
Sub(reversedWord)
Console.WriteLine("Found reversed words {0}/{1}", reversedWord, New
String(reversedWord.Reverse().ToArray()))
End Sub)

'
' Connect the dataflow blocks to form a pipeline.
'

Dim linkOptions = New DataflowLinkOptions With {.PropagateCompletion = True}

downloadString.LinkTo(createWordList, linkOptions)
createWordList.LinkTo(filterWordList, linkOptions)
filterWordList.LinkTo(findReversedWords, linkOptions)
findReversedWords.LinkTo(printReversedWords, linkOptions)

' Process "The Iliad of Homer" by Homer.


downloadString.Post("http://www.gutenberg.org/cache/epub/16452/pg16452.txt")

' Mark the head of the pipeline as complete.


downloadString.Complete()

' Wait for the last block in the pipeline to process all messages.
printReversedWords.Completion.Wait()
End Sub

End Module

' Sample output:


'Downloading 'http://www.gutenberg.org/cache/epub/16452/pg16452.txt'...
'Creating word list...
'Filtering word list...
'Filtering word list...
'Finding reversed words...
'Found reversed words aera/area
'Found reversed words doom/mood
'Found reversed words draw/ward
'Found reversed words live/evil
'Found reversed words seat/taes
'Found reversed words area/aera
'Found reversed words port/trop
'Found reversed words sleek/keels
'Found reversed words tops/spot
'Found reversed words evil/live
'Found reversed words speed/deeps
'Found reversed words mood/doom
'Found reversed words moor/room
'Found reversed words spot/tops
'Found reversed words spots/stops
'Found reversed words trop/port
'Found reversed words stops/spots
'Found reversed words reed/deer
'Found reversed words deeps/speed
'Found reversed words deer/reed
'Found reversed words taes/seat
'Found reversed words keels/sleek
'Found reversed words room/moor
'Found reversed words ward/draw

Étapes suivantes
Cet exemple envoie une URL à traiter via le pipeline de flux de données. Si vous devez envoyer plusieurs valeurs
d'entrée via le pipeline, vous pouvez introduire un forme de parallélisme dans votre application similaire à la
façon dont des parties peuvent parcourir une fabrique d'automobiles. Lorsque le premier membre du pipeline
envoie son résultat au deuxième membre, il peut traiter un autre élément en parallèle alors que le deuxième
membre traite le premier résultat.
Le parallélisme qui est effectué à l’aide de pipelines de flux de données s’appelle le parallélisme de granularité
grossière parce qu’il comprend généralement moins de tâches, mais plus grosses. Vous pouvez également utiliser
le parallélisme de granularité fine de plus petites tâches de courte durée dans un pipeline de flux de données.
Dans cet exemple, le membre findReversedWords du pipeline utilise PLINQ pour traiter plusieurs éléments dans la
liste des travaux en parallèle. L'utilisation du parallélisme de granularité fine dans un pipeline de granularité
grossière peut améliorer le débit global.
Vous pouvez également adapter un bloc de flux de données source à plusieurs blocs cibles pour créer un réseau
de flux de données. La version surchargée de la méthode LinkTo accepte un objet Predicate<T> qui définit si le
bloc cible reçoit les messages en fonction de sa valeur. La plupart des types de bloc de flux de données qui
agissent comme sources envoient des messages à toutes les blocs cibles connectés, dans l'ordre dans lequel ils
ont été connectés, jusqu'à ce que l'un des blocs reçoive ce message. En utilisant ce mécanisme de filtrage, vous
pouvez créer des systèmes de blocs de flux de données connectés qui dirigent certaines données via un seul tracé
et d’autres données via un autre tracé. Pour obtenir un exemple qui utilise le filtrage afin de créer un réseau de
flux de données, consultez Procédure pas à pas : utilisation de flux de données dans une application Windows
Forms.

Voir aussi
Dataflow
Procédure : Dissocier des blocs de flux de données
18/07/2020 • 6 minutes to read • Edit Online

Ce document explique comment dissocier un bloc de flux de données cible de sa source.

NOTE
La bibliothèque de flux de données TPL (espace de noms System.Threading.Tasks.Dataflow) n'est pas distribuée avec .NET.
Pour installer l’espace de noms System.Threading.Tasks.Dataflow dans Visual Studio, ouvrez votre projet, choisissez Gérer
les packages NuGet dans le menu Projet , puis recherchez en ligne le package System.Threading.Tasks.Dataflow .
Vous pouvez également l’installer à l’aide de l’interface CLI .NET Core en exécutant
dotnet add package System.Threading.Tasks.Dataflow .

Exemple
L’exemple suivant crée trois objets TransformBlock<TInput,TOutput>, dont chacun appelle la méthode
TrySolution pour calculer une valeur. Cet exemple n’a besoin que du résultat du premier appel à TrySolution
pour se terminer.

using System;
using System.Threading;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to unlink dataflow blocks.


class DataflowReceiveAny
{
// Receives the value from the first provided source that has
// a message.
public static T ReceiveFromAny<T>(params ISourceBlock<T>[] sources)
{
// Create a WriteOnceBlock<T> object and link it to each source block.
var writeOnceBlock = new WriteOnceBlock<T>(e => e);
foreach (var source in sources)
{
// Setting MaxMessages to one instructs
// the source block to unlink from the WriteOnceBlock<T> object
// after offering the WriteOnceBlock<T> object one message.
source.LinkTo(writeOnceBlock, new DataflowLinkOptions { MaxMessages = 1 });
}
// Return the first value that is offered to the WriteOnceBlock object.
return writeOnceBlock.Receive();
}

// Demonstrates a function that takes several seconds to produce a result.


static int TrySolution(int n, CancellationToken ct)
{
// Simulate a lengthy operation that completes within three seconds
// or when the provided CancellationToken object is cancelled.
SpinWait.SpinUntil(() => ct.IsCancellationRequested,
new Random().Next(3000));

// Return a value.
return n + 42;
}

static void Main(string[] args)


{
// Create a shared CancellationTokenSource object to enable the
// Create a shared CancellationTokenSource object to enable the
// TrySolution method to be cancelled.
var cts = new CancellationTokenSource();

// Create three TransformBlock<int, int> objects.


// Each TransformBlock<int, int> object calls the TrySolution method.
Func<int, int> action = n => TrySolution(n, cts.Token);
var trySolution1 = new TransformBlock<int, int>(action);
var trySolution2 = new TransformBlock<int, int>(action);
var trySolution3 = new TransformBlock<int, int>(action);

// Post data to each TransformBlock<int, int> object.


trySolution1.Post(11);
trySolution2.Post(21);
trySolution3.Post(31);

// Call the ReceiveFromAny<T> method to receive the result from the


// first TransformBlock<int, int> object to finish.
int result = ReceiveFromAny(trySolution1, trySolution2, trySolution3);

// Cancel all calls to TrySolution that are still active.


cts.Cancel();

// Print the result to the console.


Console.WriteLine("The solution is {0}.", result);

cts.Dispose();
}
}

/* Sample output:
The solution is 53.
*/
Imports System.Threading
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to unlink dataflow blocks.


Friend Class DataflowReceiveAny
' Receives the value from the first provided source that has
' a message.
Public Shared Function ReceiveFromAny(Of T)(ParamArray ByVal sources() As ISourceBlock(Of T)) As T
' Create a WriteOnceBlock<T> object and link it to each source block.
Dim writeOnceBlock = New WriteOnceBlock(Of T)(Function(e) e)
For Each source In sources
' Setting MaxMessages to one instructs
' the source block to unlink from the WriteOnceBlock<T> object
' after offering the WriteOnceBlock<T> object one message.
source.LinkTo(writeOnceBlock, New DataflowLinkOptions With {.MaxMessages = 1})
Next source
' Return the first value that is offered to the WriteOnceBlock object.
Return writeOnceBlock.Receive()
End Function

' Demonstrates a function that takes several seconds to produce a result.


Private Shared Function TrySolution(ByVal n As Integer, ByVal ct As CancellationToken) As Integer
' Simulate a lengthy operation that completes within three seconds
' or when the provided CancellationToken object is cancelled.
SpinWait.SpinUntil(Function() ct.IsCancellationRequested, New Random().Next(3000))

' Return a value.


Return n + 42
End Function

Shared Sub Main(ByVal args() As String)


' Create a shared CancellationTokenSource object to enable the
' TrySolution method to be cancelled.
Dim cts = New CancellationTokenSource()

' Create three TransformBlock<int, int> objects.


' Each TransformBlock<int, int> object calls the TrySolution method.
Dim action As Func(Of Integer, Integer) = Function(n) TrySolution(n, cts.Token)
Dim trySolution1 = New TransformBlock(Of Integer, Integer)(action)
Dim trySolution2 = New TransformBlock(Of Integer, Integer)(action)
Dim trySolution3 = New TransformBlock(Of Integer, Integer)(action)

' Post data to each TransformBlock<int, int> object.


trySolution1.Post(11)
trySolution2.Post(21)
trySolution3.Post(31)

' Call the ReceiveFromAny<T> method to receive the result from the
' first TransformBlock<int, int> object to finish.
Dim result As Integer = ReceiveFromAny(trySolution1, trySolution2, trySolution3)

' Cancel all calls to TrySolution that are still active.


cts.Cancel()

' Print the result to the console.


Console.WriteLine("The solution is {0}.", result)

cts.Dispose()
End Sub
End Class

' Sample output:


'The solution is 53.
'

Pour recevoir la valeur du premier objet TransformBlock<TInput,TOutput> qui se termine, cet exemple définit la
méthode ReceiveFromAny(T) . La méthode ReceiveFromAny(T) accepte un tableau d’objets ISourceBlock<TOutput>
et lie chacun de ces objets à un objet WriteOnceBlock<T>. Si vous utilisez la méthode LinkTo pour lier un bloc
source de flux de données à un bloc cible, le premier propage les messages auprès du second au fil de l’arrivée
des données. Étant donné que la classe WriteOnceBlock<T> accepte uniquement le premier message proposé, la
méthode ReceiveFromAny(T) produit ses résultats en appelant la méthode Receive. Cela génère le premier
message proposé à l’objet WriteOnceBlock<T>. La méthode LinkTo a une version surchargée acceptant un objet
DataflowLinkOptions avec une propriété MaxMessages qui, définie à la valeur 1 , impose au bloc source de se
dissocier du bloc cible dès que ce dernier a reçu un message provenant du bloc source. Il est important que l’objet
WriteOnceBlock<T> se dissocie de ses sources, car la relation qu’il entretient avec le tableau de sources n’est plus
nécessaire dès lors qu’il a reçu un messageWriteOnceBlock<T>WriteOnceBlock<T>.
Pour permettre aux appels restants à TrySolution de se terminer dès que l’un d’eux a calculé une valeur, la
méthode TrySolution prend un objet CancellationToken qui est annulé après que l’appel à ReceiveFromAny(T) a
produit une sortie. La méthode SpinUntil produit une sortie quand cet objet CancellationToken est annulé.

Voir aussi
Dataflow
Procédure pas à pas : utilisation d’un dataflow dans
une application Windows Forms
18/07/2020 • 22 minutes to read • Edit Online

Ce document montre comment créer un réseau de blocs de flux de données qui effectuent un traitement des
images dans une application Windows Forms.
Dans cet exemple, on charge des fichiers image à partir du dossier spécifié, on crée une image composite, et on
affiche le résultat. L’exemple utilise le modèle de flux de données pour acheminer les images via le réseau. Dans
le modèle de flux de données, les composants indépendants d’un programme communiquent entre eux en
envoyant des messages. Lorsqu’un composant reçoit un message, il effectue une action, puis transmet le
résultat à un autre composant. Comparez cela avec le modèle de flux de contrôle, dans lequel une application
utilise des structures de contrôle (par exemple, des instructions conditionnelles, des boucles, etc.) pour contrôler
l’ordre des opérations dans un programme.

Prérequis
Lisez la rubrique Flux de données avant de démarrer cette procédure pas à pas.

NOTE
La bibliothèque de flux de données TPL (espace de noms System.Threading.Tasks.Dataflow) n'est pas distribuée avec .NET.
Pour installer l’espace de noms System.Threading.Tasks.Dataflow dans Visual Studio, ouvrez votre projet, choisissez Gérer
les packages NuGet dans le menu Projet , puis recherchez en ligne le package System.Threading.Tasks.Dataflow .
Vous pouvez également l’installer à l’aide de l’interface CLI .NET Core en exécutant
dotnet add package System.Threading.Tasks.Dataflow .

Sections
Cette procédure pas à pas contient les sections suivantes :
Création de l’application Windows Forms
Création du réseau de flux de données
Connexion du réseau de flux de données à l’interface utilisateur
Exemple complet

Création de l’application Windows Forms


Cette section décrit comment créer l’application Windows Forms de base et ajouter des contrôles au formulaire
principal.
Pour créer une Application Windows Forms
1. Dans Visual Studio, créez un projet Application Windows Forms en Visual C# ou en Visual Basic. Dans
ce document, le projet est nommé CompositeImages .
2. Dans le concepteur de formulaires pour le formulaire principal, Form1.cs (Form1.vb pour Visual Basic),
ajoutez un contrôle ToolStrip.
3. Ajoutez un contrôle ToolStripButton au contrôle ToolStrip. Définissez la propriété DisplayStyle sur Text et
la propriété Text sur Choisir un dossier .
4. Ajoutez un deuxième contrôle ToolStripButton au contrôle ToolStrip. Définissez la propriété DisplayStyle
sur Text, la propriété Text sur Annuler et la propriété Enabled sur False .
5. Ajoutez un objet PictureBox au formulaire principal. Attribuez à la propriété Dock la valeur Fill.

Création du réseau de flux de données


Cette section décrit comment créer le réseau de flux de données qui effectue le traitement des images.
Pour créer le réseau de flux de données
1. Dans votre projet, ajoutez une référence à System.Threading.Tasks.Dataflow.dll.
2. Vérifiez que Form1.cs (Form1.vb pour Visual Basic) contient les instructions using suivantes ( Using en
Visual Basic) :

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;

3. Ajoutez les membres de données suivants à la classe Form1 :

// The head of the dataflow network.


ITargetBlock<string> headBlock = null;

// Enables the user interface to signal cancellation to the network.


CancellationTokenSource cancellationTokenSource;

4. Ajoutez la méthode suivante, CreateImageProcessingNetwork , à la classe Form1 . Cette méthode crée le


réseau de traitement des images.

// Creates the image processing dataflow network and returns the


// head node of the network.
ITargetBlock<string> CreateImageProcessingNetwork()
{
//
// Create the dataflow blocks that form the network.
//

// Create a dataflow block that takes a folder path as input


// and returns a collection of Bitmap objects.
var loadBitmaps = new TransformBlock<string, IEnumerable<Bitmap>>(path =>
{
try
{
return LoadBitmaps(path);
}
catch (OperationCanceledException)
{
// Handle cancellation by passing the empty collection
// to the next stage of the network.
return Enumerable.Empty<Bitmap>();
}
});

// Create a dataflow block that takes a collection of Bitmap objects


// and returns a single composite bitmap.
var createCompositeBitmap = new TransformBlock<IEnumerable<Bitmap>, Bitmap>(bitmaps =>
{
try
{
return CreateCompositeBitmap(bitmaps);
}
catch (OperationCanceledException)
{
// Handle cancellation by passing null to the next stage
// of the network.
return null;
}
});

// Create a dataflow block that displays the provided bitmap on the form.
var displayCompositeBitmap = new ActionBlock<Bitmap>(bitmap =>
{
// Display the bitmap.
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.Image = bitmap;

// Enable the user to select another folder.


toolStripButton1.Enabled = true;
toolStripButton2.Enabled = false;
Cursor = DefaultCursor;
},
// Specify a task scheduler from the current synchronization context
// so that the action runs on the UI thread.
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

// Create a dataflow block that responds to a cancellation request by


// displaying an image to indicate that the operation is cancelled and
// enables the user to select another folder.
var operationCancelled = new ActionBlock<object>(delegate
{
// Display the error image to indicate that the operation
// was cancelled.
pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox1.Image = pictureBox1.ErrorImage;

// Enable the user to select another folder.


toolStripButton1.Enabled = true;
toolStripButton2.Enabled = false;
Cursor = DefaultCursor;
},
// Specify a task scheduler from the current synchronization context
// so that the action runs on the UI thread.
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

//
// Connect the network.
//

// Link loadBitmaps to createCompositeBitmap.


// The provided predicate ensures that createCompositeBitmap accepts the
// collection of bitmaps only if that collection has at least one member.
loadBitmaps.LinkTo(createCompositeBitmap, bitmaps => bitmaps.Count() > 0);
// Also link loadBitmaps to operationCancelled.
// When createCompositeBitmap rejects the message, loadBitmaps
// offers the message to operationCancelled.
// operationCancelled accepts all messages because we do not provide a
// predicate.
loadBitmaps.LinkTo(operationCancelled);

// Link createCompositeBitmap to displayCompositeBitmap.


// The provided predicate ensures that displayCompositeBitmap accepts the
// bitmap only if it is non-null.
createCompositeBitmap.LinkTo(displayCompositeBitmap, bitmap => bitmap != null);

// Also link createCompositeBitmap to operationCancelled.


// When displayCompositeBitmap rejects the message, createCompositeBitmap
// offers the message to operationCancelled.
// operationCancelled accepts all messages because we do not provide a
// predicate.
createCompositeBitmap.LinkTo(operationCancelled);

// Return the head of the network.


return loadBitmaps;
}

5. Implémentez la méthode LoadBitmaps .

// Loads all bitmap files that exist at the provided path.


IEnumerable<Bitmap> LoadBitmaps(string path)
{
List<Bitmap> bitmaps = new List<Bitmap>();

// Load a variety of image types.


foreach (string bitmapType in
new string[] { "*.bmp", "*.gif", "*.jpg", "*.png", "*.tif" })
{
// Load each bitmap for the current extension.
foreach (string fileName in Directory.GetFiles(path, bitmapType))
{
// Throw OperationCanceledException if cancellation is requested.
cancellationTokenSource.Token.ThrowIfCancellationRequested();

try
{
// Add the Bitmap object to the collection.
bitmaps.Add(new Bitmap(fileName));
}
catch (Exception)
{
// TODO: A complete application might handle the error.
}
}
}
return bitmaps;
}

6. Implémentez la méthode CreateCompositeBitmap .

// Creates a composite bitmap from the provided collection of Bitmap objects.


// This method computes the average color of each pixel among all bitmaps
// to create the composite image.
Bitmap CreateCompositeBitmap(IEnumerable<Bitmap> bitmaps)
{
Bitmap[] bitmapArray = bitmaps.ToArray();

// Compute the maximum width and height components of all


// bitmaps in the collection.
Rectangle largest = new Rectangle();
foreach (var bitmap in bitmapArray)
{
if (bitmap.Width > largest.Width)
largest.Width = bitmap.Width;
if (bitmap.Height > largest.Height)
largest.Height = bitmap.Height;
}

// Create a 32-bit Bitmap object with the greatest dimensions.


Bitmap result = new Bitmap(largest.Width, largest.Height,
PixelFormat.Format32bppArgb);

// Lock the result Bitmap.


var resultBitmapData = result.LockBits(
new Rectangle(new Point(), result.Size), ImageLockMode.WriteOnly,
result.PixelFormat);

// Lock each source bitmap to create a parallel list of BitmapData objects.


var bitmapDataList = (from bitmap in bitmapArray
select bitmap.LockBits(
new Rectangle(new Point(), bitmap.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
.ToList();

// Compute each column in parallel.


Parallel.For(0, largest.Width, new ParallelOptions
{
CancellationToken = cancellationTokenSource.Token
},
i =>
{
// Compute each row.
for (int j = 0; j < largest.Height; j++)
{
// Counts the number of bitmaps whose dimensions
// contain the current location.
int count = 0;

// The sum of all alpha, red, green, and blue components.


int a = 0, r = 0, g = 0, b = 0;

// For each bitmap, compute the sum of all color components.


foreach (var bitmapData in bitmapDataList)
{
// Ensure that we stay within the bounds of the image.
if (bitmapData.Width > i && bitmapData.Height > j)
{
unsafe
{
byte* row = (byte*)(bitmapData.Scan0 + (j * bitmapData.Stride));
byte* pix = (byte*)(row + (4 * i));
a += *pix; pix++;
r += *pix; pix++;
g += *pix; pix++;
b += *pix;
}
count++;
}
}

//prevent divide by zero in bottom right pixelless corner


if (count == 0)
break;

unsafe
{
// Compute the average of each color component.
a /= count;
a /= count;
r /= count;
g /= count;
b /= count;

// Set the result pixel.


byte* row = (byte*)(resultBitmapData.Scan0 + (j * resultBitmapData.Stride));
byte* pix = (byte*)(row + (4 * i));
*pix = (byte)a; pix++;
*pix = (byte)r; pix++;
*pix = (byte)g; pix++;
*pix = (byte)b;
}
}
});

// Unlock the source bitmaps.


for (int i = 0; i < bitmapArray.Length; i++)
{
bitmapArray[i].UnlockBits(bitmapDataList[i]);
}

// Unlock the result bitmap.


result.UnlockBits(resultBitmapData);

// Return the result.


return result;
}

NOTE
La version C# de la méthode CreateCompositeBitmap utilise des pointeurs pour permettre un traitement
efficace des objets System.Drawing.Bitmap. Par conséquent, vous devez activer l’option Autoriser les blocs de
code unsafe dans votre projet pour pouvoir utiliser le mot-clé unsafe. Pour plus d’informations sur l’activation
de code unsafe dans un projet Visual C#, consultez Générer, page du Concepteur de projets (C#).

Le tableau ci-dessous décrit les membres du réseau.

M EM B RE TYPE DESC RIP T IO N

loadBitmaps TransformBlock<TInput,TOutput> Prend un chemin d’accès à un dossier


en entrée, et produit une collection
d’objets Bitmap en sortie.

createCompositeBitmap TransformBlock<TInput,TOutput> Prend une collection d’objets Bitmap


en entrée, et produit une bitmap
composite en sortie.

displayCompositeBitmap ActionBlock<TInput> Affiche l’image bitmap composite sur


le formulaire.

operationCancelled ActionBlock<TInput> Affiche une image pour indiquer que


l’opération est annulée et permet à
l’utilisateur de sélectionner un autre
dossier.

Pour connecter les blocs de flux de données afin de former un réseau, cet exemple utilise la méthode LinkTo.
LinkTo Celle-ci contient une version surchargée qui prend un objet Predicate<T> définissant si le bloc cible
accepte ou rejette un message. Ce mécanisme de filtrage permet aux blocs de messages de ne recevoir que
certaines valeurs. Dans cet exemple, le réseau peut créer une branche de deux manières. La branche principale
charge les images à partir du disque, crée l’image composite et affiche cette image sur le formulaire. L’autre
branche annule l’opération en cours. Les objets Predicate<T> permettent aux blocs de flux de données de la
branche principale de basculer vers l’autre branche en rejetant certains messages. Par exemple, si l’utilisateur
annule l’opération, le bloc de flux de données createCompositeBitmap produit null ( Nothing en Visual Basic)
comme sortie. Le bloc de flux de données displayCompositeBitmap rejette les valeurs d’entrée null . Par
conséquent, le message est proposé aux operationCancelled . Le bloc de flux de données operationCancelled
accepte tous les messages. Par conséquent, il affiche une image pour indiquer que l’opération est annulée.
L’illustration suivante montre le réseau de traitement des images :

Étant donné que les blocs de flux de données displayCompositeBitmap et operationCancelled agissent sur
l’interface utilisateur, il est important que ces actions se produisent sur le thread de l’interface utilisateur. Pour
cela, lors de la construction, chacun de ces objets fournit un objet ExecutionDataflowBlockOptions dont la
propriété TaskScheduler est définie sur TaskScheduler.FromCurrentSynchronizationContext. La méthode
TaskScheduler.FromCurrentSynchronizationContext crée un objet TaskScheduler qui effectue le travail dans le
contexte actuel de synchronisation. Étant donné que la méthode CreateImageProcessingNetwork est appelée à
partir du gestionnaire du bouton Choisir le dossier , qui est exécuté sur le thread de l’interface utilisateur, les
actions des blocs de flux de données displayCompositeBitmap et operationCancelled sont également exécutées
sur le thread de l’interface utilisateur.
Cet exemple utilise un jeton d’annulation partagé au lieu de définir la propriété CancellationToken, car cette
dernière annule définitivement l’exécution du bloc de flux de donnéesCancellationToken. Cet exemple explique
comment un jeton d’annulation permet de réutiliser un réseau de flux de données plusieurs fois, même lorsque
l’utilisateur annule une ou plusieurs opérations. Vous trouverez un exemple utilisant CancellationToken pour
annuler définitivement l’exécution d’un bloc de flux de données sur la page Guide pratique : annuler un bloc de
flux de données.

Connexion du réseau de flux de données à l’interface utilisateur


Cette section décrit comment connecter le réseau de flux de données à l’interface utilisateur. La création de
l’image composite et l’annulation de l’opération sont lancées à partir des boutons Choisir le dossier et
Annuler . Lorsque l’utilisateur clique sur l’un de ces boutons, l’action appropriée est lancée de manière
asynchrone.
Connecter le réseau de flux de données à l’interface utilisateur
1. Dans le concepteur de formulaires du formulaire principal, créez un gestionnaire d'événements pour
l'événement Click du bouton Annuler .
2. Implémentez l’événement Click pour le bouton Choisir un dossier .
// Event handler for the Choose Folder button.
private void toolStripButton1_Click(object sender, EventArgs e)
{
// Create a FolderBrowserDialog object to enable the user to
// select a folder.
FolderBrowserDialog dlg = new FolderBrowserDialog
{
ShowNewFolderButton = false
};

// Set the selected path to the common Sample Pictures folder


// if it exists.
string initialDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures),
"Sample Pictures");
if (Directory.Exists(initialDirectory))
{
dlg.SelectedPath = initialDirectory;
}

// Show the dialog and process the dataflow network.


if (dlg.ShowDialog() == DialogResult.OK)
{
// Create a new CancellationTokenSource object to enable
// cancellation.
cancellationTokenSource = new CancellationTokenSource();

// Create the image processing network if needed.


headBlock ??= CreateImageProcessingNetwork();

// Post the selected path to the network.


headBlock.Post(dlg.SelectedPath);

// Enable the Cancel button and disable the Choose Folder button.
toolStripButton1.Enabled = false;
toolStripButton2.Enabled = true;

// Show a wait cursor.


Cursor = Cursors.WaitCursor;
}
}

3. Dans le concepteur de formulaires du formulaire principal, créez un gestionnaire d'événements pour


l’événement Click du bouton Annuler .
4. Implémentez l’événement Click pour le bouton Annuler .

// Event handler for the Cancel button.


private void toolStripButton2_Click(object sender, EventArgs e)
{
// Signal the request for cancellation. The current component of
// the dataflow network will respond to the cancellation request.
cancellationTokenSource.Cancel();
}

Exemple complet
L'exemple suivant présente le code complet pour cette visite.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;

namespace CompositeImages
{
public partial class Form1 : Form
{
// The head of the dataflow network.
ITargetBlock<string> headBlock = null;

// Enables the user interface to signal cancellation to the network.


CancellationTokenSource cancellationTokenSource;

public Form1()
{
InitializeComponent();
}

// Creates the image processing dataflow network and returns the


// head node of the network.
ITargetBlock<string> CreateImageProcessingNetwork()
{
//
// Create the dataflow blocks that form the network.
//

// Create a dataflow block that takes a folder path as input


// and returns a collection of Bitmap objects.
var loadBitmaps = new TransformBlock<string, IEnumerable<Bitmap>>(path =>
{
try
{
return LoadBitmaps(path);
}
catch (OperationCanceledException)
{
// Handle cancellation by passing the empty collection
// to the next stage of the network.
return Enumerable.Empty<Bitmap>();
}
});

// Create a dataflow block that takes a collection of Bitmap objects


// and returns a single composite bitmap.
var createCompositeBitmap = new TransformBlock<IEnumerable<Bitmap>, Bitmap>(bitmaps =>
{
try
{
return CreateCompositeBitmap(bitmaps);
}
catch (OperationCanceledException)
{
// Handle cancellation by passing null to the next stage
// of the network.
return null;
}
});

// Create a dataflow block that displays the provided bitmap on the form.
var displayCompositeBitmap = new ActionBlock<Bitmap>(bitmap =>
{
// Display the bitmap.
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBox1.Image = bitmap;
// Enable the user to select another folder.
toolStripButton1.Enabled = true;
toolStripButton2.Enabled = false;
Cursor = DefaultCursor;
},
// Specify a task scheduler from the current synchronization context
// so that the action runs on the UI thread.
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

// Create a dataflow block that responds to a cancellation request by


// displaying an image to indicate that the operation is cancelled and
// enables the user to select another folder.
var operationCancelled = new ActionBlock<object>(delegate
{
// Display the error image to indicate that the operation
// was cancelled.
pictureBox1.SizeMode = PictureBoxSizeMode.CenterImage;
pictureBox1.Image = pictureBox1.ErrorImage;

// Enable the user to select another folder.


toolStripButton1.Enabled = true;
toolStripButton2.Enabled = false;
Cursor = DefaultCursor;
},
// Specify a task scheduler from the current synchronization context
// so that the action runs on the UI thread.
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

//
// Connect the network.
//

// Link loadBitmaps to createCompositeBitmap.


// The provided predicate ensures that createCompositeBitmap accepts the
// collection of bitmaps only if that collection has at least one member.
loadBitmaps.LinkTo(createCompositeBitmap, bitmaps => bitmaps.Count() > 0);

// Also link loadBitmaps to operationCancelled.


// When createCompositeBitmap rejects the message, loadBitmaps
// offers the message to operationCancelled.
// operationCancelled accepts all messages because we do not provide a
// predicate.
loadBitmaps.LinkTo(operationCancelled);

// Link createCompositeBitmap to displayCompositeBitmap.


// The provided predicate ensures that displayCompositeBitmap accepts the
// bitmap only if it is non-null.
createCompositeBitmap.LinkTo(displayCompositeBitmap, bitmap => bitmap != null);

// Also link createCompositeBitmap to operationCancelled.


// When displayCompositeBitmap rejects the message, createCompositeBitmap
// offers the message to operationCancelled.
// operationCancelled accepts all messages because we do not provide a
// predicate.
createCompositeBitmap.LinkTo(operationCancelled);

// Return the head of the network.


return loadBitmaps;
}

// Loads all bitmap files that exist at the provided path.


IEnumerable<Bitmap> LoadBitmaps(string path)
{
{
List<Bitmap> bitmaps = new List<Bitmap>();

// Load a variety of image types.


foreach (string bitmapType in
new string[] { "*.bmp", "*.gif", "*.jpg", "*.png", "*.tif" })
{
// Load each bitmap for the current extension.
foreach (string fileName in Directory.GetFiles(path, bitmapType))
{
// Throw OperationCanceledException if cancellation is requested.
cancellationTokenSource.Token.ThrowIfCancellationRequested();

try
{
// Add the Bitmap object to the collection.
bitmaps.Add(new Bitmap(fileName));
}
catch (Exception)
{
// TODO: A complete application might handle the error.
}
}
}
return bitmaps;
}

// Creates a composite bitmap from the provided collection of Bitmap objects.


// This method computes the average color of each pixel among all bitmaps
// to create the composite image.
Bitmap CreateCompositeBitmap(IEnumerable<Bitmap> bitmaps)
{
Bitmap[] bitmapArray = bitmaps.ToArray();

// Compute the maximum width and height components of all


// bitmaps in the collection.
Rectangle largest = new Rectangle();
foreach (var bitmap in bitmapArray)
{
if (bitmap.Width > largest.Width)
largest.Width = bitmap.Width;
if (bitmap.Height > largest.Height)
largest.Height = bitmap.Height;
}

// Create a 32-bit Bitmap object with the greatest dimensions.


Bitmap result = new Bitmap(largest.Width, largest.Height,
PixelFormat.Format32bppArgb);

// Lock the result Bitmap.


var resultBitmapData = result.LockBits(
new Rectangle(new Point(), result.Size), ImageLockMode.WriteOnly,
result.PixelFormat);

// Lock each source bitmap to create a parallel list of BitmapData objects.


var bitmapDataList = (from bitmap in bitmapArray
select bitmap.LockBits(
new Rectangle(new Point(), bitmap.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb))
.ToList();

// Compute each column in parallel.


Parallel.For(0, largest.Width, new ParallelOptions
{
CancellationToken = cancellationTokenSource.Token
},
i =>
{
// Compute each row.
for (int j = 0; j < largest.Height; j++)
for (int j = 0; j < largest.Height; j++)
{
// Counts the number of bitmaps whose dimensions
// contain the current location.
int count = 0;

// The sum of all alpha, red, green, and blue components.


int a = 0, r = 0, g = 0, b = 0;

// For each bitmap, compute the sum of all color components.


foreach (var bitmapData in bitmapDataList)
{
// Ensure that we stay within the bounds of the image.
if (bitmapData.Width > i && bitmapData.Height > j)
{
unsafe
{
byte* row = (byte*)(bitmapData.Scan0 + (j * bitmapData.Stride));
byte* pix = (byte*)(row + (4 * i));
a += *pix; pix++;
r += *pix; pix++;
g += *pix; pix++;
b += *pix;
}
count++;
}
}

//prevent divide by zero in bottom right pixelless corner


if (count == 0)
break;

unsafe
{
// Compute the average of each color component.
a /= count;
r /= count;
g /= count;
b /= count;

// Set the result pixel.


byte* row = (byte*)(resultBitmapData.Scan0 + (j * resultBitmapData.Stride));
byte* pix = (byte*)(row + (4 * i));
*pix = (byte)a; pix++;
*pix = (byte)r; pix++;
*pix = (byte)g; pix++;
*pix = (byte)b;
}
}
});

// Unlock the source bitmaps.


for (int i = 0; i < bitmapArray.Length; i++)
{
bitmapArray[i].UnlockBits(bitmapDataList[i]);
}

// Unlock the result bitmap.


result.UnlockBits(resultBitmapData);

// Return the result.


return result;
}

// Event handler for the Choose Folder button.


private void toolStripButton1_Click(object sender, EventArgs e)
{
// Create a FolderBrowserDialog object to enable the user to
// select a folder.
FolderBrowserDialog dlg = new FolderBrowserDialog
FolderBrowserDialog dlg = new FolderBrowserDialog
{
ShowNewFolderButton = false
};

// Set the selected path to the common Sample Pictures folder


// if it exists.
string initialDirectory = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures),
"Sample Pictures");
if (Directory.Exists(initialDirectory))
{
dlg.SelectedPath = initialDirectory;
}

// Show the dialog and process the dataflow network.


if (dlg.ShowDialog() == DialogResult.OK)
{
// Create a new CancellationTokenSource object to enable
// cancellation.
cancellationTokenSource = new CancellationTokenSource();

// Create the image processing network if needed.


headBlock ??= CreateImageProcessingNetwork();

// Post the selected path to the network.


headBlock.Post(dlg.SelectedPath);

// Enable the Cancel button and disable the Choose Folder button.
toolStripButton1.Enabled = false;
toolStripButton2.Enabled = true;

// Show a wait cursor.


Cursor = Cursors.WaitCursor;
}
}

// Event handler for the Cancel button.


private void toolStripButton2_Click(object sender, EventArgs e)
{
// Signal the request for cancellation. The current component of
// the dataflow network will respond to the cancellation request.
cancellationTokenSource.Cancel();
}

~Form1()
{
cancellationTokenSource.Dispose();
}
}
}

L’illustration suivante montre une sortie type du dossier \Échantillons d'images.


Voir aussi
Dataflow
Procédure : annuler un bloc de dataflow
18/07/2020 • 18 minutes to read • Edit Online

Ce document montre comment activer l’annulation dans votre application. Cet exemple utilise des Windows
Forms pour montrer où les éléments de travail sont actifs dans un pipeline de flux de données, ainsi que les effets
de l’annulation.

NOTE
La bibliothèque de flux de données TPL (espace de noms System.Threading.Tasks.Dataflow) n'est pas distribuée avec .NET.
Pour installer l’espace de noms System.Threading.Tasks.Dataflow dans Visual Studio, ouvrez votre projet, choisissez Gérer
les packages NuGet dans le menu Projet , puis recherchez en ligne le package System.Threading.Tasks.Dataflow .
Vous pouvez également l’installer à l’aide de l’interface CLI .NET Core en exécutant
dotnet add package System.Threading.Tasks.Dataflow .

Pour créer une Application Windows Forms


1. Créez un projet C# ou Application Windows Forms Visual Basic. Dans les étapes suivantes, le projet est
nommé CancellationWinForms .
2. Dans le concepteur de formulaires pour le formulaire principal, Form1.cs (Form1.vb pour Visual Basic),
ajoutez un contrôle ToolStrip.
3. Ajoutez un contrôle ToolStripButton au contrôle ToolStrip. Définissez la propriété DisplayStyle sur Text et la
propriété Text sur Ajouter des éléments de travail .
4. Ajoutez un deuxième contrôle ToolStripButton au contrôle ToolStrip. Définissez la propriété DisplayStyle sur
Text, la propriété Text sur Annuler et la propriété Enabled sur False .
5. Ajoutez quatre objets ToolStripProgressBar au contrôle ToolStrip.

Création du pipeline de flux de données


Cette section décrit comment créer le pipeline de flux de données qui traite les éléments de travail et met à jour
les barres de progression.
Pour créer le pipeline de flux de données
1. Dans votre projet, ajoutez une référence à System.Threading.Tasks.Dataflow.dll.
2. Vérifiez que Form1.cs (Form1.vb pour Visual Basic) contient les instructions using suivantes ( Imports en
Visual Basic).

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

3. Ajouter la classe WorkItem comme un type interne de la classe Form1 .

// A placeholder type that performs work.


class WorkItem
{
// Performs work for the provided number of milliseconds.
public void DoWork(int milliseconds)
{
// For demonstration, suspend the current thread.
Thread.Sleep(milliseconds);
}
}

' A placeholder type that performs work.


Private Class WorkItem
' Performs work for the provided number of milliseconds.
Public Sub DoWork(ByVal milliseconds As Integer)
' For demonstration, suspend the current thread.
Thread.Sleep(milliseconds)
End Sub
End Class

4. Ajoutez les membres de données suivants à la classe Form1 .

// Enables the user interface to signal cancellation.


CancellationTokenSource cancellationSource;

// The first node in the dataflow pipeline.


TransformBlock<WorkItem, WorkItem> startWork;

// The second, and final, node in the dataflow pipeline.


ActionBlock<WorkItem> completeWork;

// Increments the value of the provided progress bar.


ActionBlock<ToolStripProgressBar> incrementProgress;

// Decrements the value of the provided progress bar.


ActionBlock<ToolStripProgressBar> decrementProgress;

// Enables progress bar actions to run on the UI thread.


TaskScheduler uiTaskScheduler;
' Enables the user interface to signal cancellation.
Private cancellationSource As CancellationTokenSource

' The first node in the dataflow pipeline.


Private startWork As TransformBlock(Of WorkItem, WorkItem)

' The second, and final, node in the dataflow pipeline.


Private completeWork As ActionBlock(Of WorkItem)

' Increments the value of the provided progress bar.


Private incrementProgress As ActionBlock(Of ToolStripProgressBar)

' Decrements the value of the provided progress bar.


Private decrementProgress As ActionBlock(Of ToolStripProgressBar)

' Enables progress bar actions to run on the UI thread.


Private uiTaskScheduler As TaskScheduler

5. Ajoutez la méthode suivante, CreatePipeline , à la classe Form1 .

// Creates the blocks that participate in the dataflow pipeline.


private void CreatePipeline()
{
// Create the cancellation source.
cancellationSource = new CancellationTokenSource();

// Create the first node in the pipeline.


startWork = new TransformBlock<WorkItem, WorkItem>(workItem =>
{
// Perform some work.
workItem.DoWork(250);

// Decrement the progress bar that tracks the count of


// active work items in this stage of the pipeline.
decrementProgress.Post(toolStripProgressBar1);

// Increment the progress bar that tracks the count of


// active work items in the next stage of the pipeline.
incrementProgress.Post(toolStripProgressBar2);

// Send the work item to the next stage of the pipeline.


return workItem;
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token
});

// Create the second, and final, node in the pipeline.


completeWork = new ActionBlock<WorkItem>(workItem =>
{
// Perform some work.
workItem.DoWork(1000);

// Decrement the progress bar that tracks the count of


// active work items in this stage of the pipeline.
decrementProgress.Post(toolStripProgressBar2);

// Increment the progress bar that tracks the overall


// count of completed work items.
incrementProgress.Post(toolStripProgressBar3);
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token,
MaxDegreeOfParallelism = 2
});

// Connect the two nodes of the pipeline. When the first node completes,
// set the second node also to the completed state.
startWork.LinkTo(
completeWork, new DataflowLinkOptions { PropagateCompletion = true });

// Create the dataflow action blocks that increment and decrement


// progress bars.
// These blocks use the task scheduler that is associated with
// the UI thread.

incrementProgress = new ActionBlock<ToolStripProgressBar>(


progressBar => progressBar.Value++,
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token,
TaskScheduler = uiTaskScheduler
});

decrementProgress = new ActionBlock<ToolStripProgressBar>(


progressBar => progressBar.Value--,
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token,
TaskScheduler = uiTaskScheduler
});
}
' Creates the blocks that participate in the dataflow pipeline.
Private Sub CreatePipeline()
' Create the cancellation source.
cancellationSource = New CancellationTokenSource()

' Create the first node in the pipeline.


startWork = New TransformBlock(Of WorkItem, WorkItem)(Function(workItem)
' Perform some work.
' Decrement the progress bar that tracks
the count of
' active work items in this stage of the
pipeline.
' Increment the progress bar that tracks
the count of
' active work items in the next stage of
the pipeline.
' Send the work item to the next stage of
the pipeline.
workItem.DoWork(250)

decrementProgress.Post(toolStripProgressBar1)

incrementProgress.Post(toolStripProgressBar2)
Return workItem
End Function,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token})

' Create the second, and final, node in the pipeline.


completeWork = New ActionBlock(Of WorkItem)(Sub(workItem)
' Perform some work.
' Decrement the progress bar that tracks the count
of
' active work items in this stage of the pipeline.
' Increment the progress bar that tracks the
overall
' count of completed work items.
workItem.DoWork(1000)
decrementProgress.Post(toolStripProgressBar2)
incrementProgress.Post(toolStripProgressBar3)
End Sub,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
.MaxDegreeOfParallelism = 2})

' Connect the two nodes of the pipeline. When the first node completes,
' set the second node also to the completed state.
startWork.LinkTo(
completeWork, New DataflowLinkOptions With {.PropagateCompletion = true})

' Create the dataflow action blocks that increment and decrement
' progress bars.
' These blocks use the task scheduler that is associated with
' the UI thread.

incrementProgress = New ActionBlock(Of ToolStripProgressBar)(


Sub(progressBar) progressBar.Value += 1,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
.TaskScheduler = uiTaskScheduler})

decrementProgress = New ActionBlock(Of ToolStripProgressBar)(


Sub(progressBar) progressBar.Value -= 1,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
.TaskScheduler = uiTaskScheduler})

End Sub
Étant donné que les blocs de flux de données incrementProgress et decrementProgress agissent sur l’interface
utilisateur, il est important que ces actions se produisent sur le thread de l’interface utilisateur. Pour cela, lors de la
construction, chacun de ces objets fournit un objet ExecutionDataflowBlockOptions dont la propriété
TaskScheduler est définie sur TaskScheduler.FromCurrentSynchronizationContext. La méthode
TaskScheduler.FromCurrentSynchronizationContext crée un objet TaskScheduler qui effectue le travail dans le
contexte actuel de synchronisation. Étant donné que le constructeur Form1 est appelé depuis le thread de
l’interface utilisateur, les actions des blocs de flux de données incrementProgress et decrementProgress
fonctionnent aussi sur le thread de l’interface utilisateur.
Cet exemple définit la propriété CancellationToken lors de la construction des membres du pipeline. Étant donné
que la propriété CancellationToken annule définitivement l’exécution du bloc de flux de données, l’ensemble du
pipeline doit être recréé si l’utilisateur annule l’opération et souhaite par la suite ajouter d’autres éléments de
travail au pipeline. Pour obtenir un exemple illustrant une autre méthode d’annulation d’un bloc de flux de
données de sorte que les autres travaux puissent être effectués après l’annulation d’une opération, consultez
Walkthrough: Using Dataflow in a Windows Forms Application (Procédure pas à pas : utilisation d’un flux de
données dans une application Windows Forms).

Connexion du pipeline de flux de données à l’interface utilisateur


Cette section décrit comment connecter le pipeline de flux de données à l’interface utilisateur. La création du
pipeline et l’ajout d’éléments de travail au pipeline sont contrôlés par le gestionnaire d’événements pour le
bouton Add Work Items (Ajouter des éléments de travail). L’annulation est lancée par le bouton Annuler .
Lorsque l’utilisateur clique sur un de ces boutons, l’action appropriée est lancée de manière asynchrone.
Pour connecter le pipeline de flux de données à l’interface utilisateur
1. Dans le concepteur de formulaires du formulaire principal, créez un gestionnaire d'événements pour
l'événement Click du bouton Ajouter des éléments de travail .
2. Implémentez l’événement Click pour le bouton Ajouter des éléments de travail .

// Event handler for the Add Work Items button.


private void toolStripButton1_Click(object sender, EventArgs e)
{
// The Cancel button is disabled when the pipeline is not active.
// Therefore, create the pipeline and enable the Cancel button
// if the Cancel button is disabled.
if (!toolStripButton2.Enabled)
{
CreatePipeline();

// Enable the Cancel button.


toolStripButton2.Enabled = true;
}

// Post several work items to the head of the pipeline.


for (int i = 0; i < 5; i++)
{
toolStripProgressBar1.Value++;
startWork.Post(new WorkItem());
}
}
' Event handler for the Add Work Items button.
Private Sub toolStripButton1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles
toolStripButton1.Click
' The Cancel button is disabled when the pipeline is not active.
' Therefore, create the pipeline and enable the Cancel button
' if the Cancel button is disabled.
If Not toolStripButton2.Enabled Then
CreatePipeline()

' Enable the Cancel button.


toolStripButton2.Enabled = True
End If

' Post several work items to the head of the pipeline.


For i As Integer = 0 To 4
toolStripProgressBar1.Value += 1
startWork.Post(New WorkItem())
Next i
End Sub

3. Dans le concepteur de formulaires du formulaire principal, créez un gestionnaire d'événements pour le


gestionnaire d’événements Click du bouton Annuler .
4. Implémentez le gestionnaire d’événements Click pour le bouton Annuler .

// Event handler for the Cancel button.


private async void toolStripButton2_Click(object sender, EventArgs e)
{
// Disable both buttons.
toolStripButton1.Enabled = false;
toolStripButton2.Enabled = false;

// Trigger cancellation.
cancellationSource.Cancel();

try
{
// Asynchronously wait for the pipeline to complete processing and for
// the progress bars to update.
await Task.WhenAll(
completeWork.Completion,
incrementProgress.Completion,
decrementProgress.Completion);
}
catch (OperationCanceledException)
{
}

// Increment the progress bar that tracks the number of cancelled


// work items by the number of active work items.
toolStripProgressBar4.Value += toolStripProgressBar1.Value;
toolStripProgressBar4.Value += toolStripProgressBar2.Value;

// Reset the progress bars that track the number of active work items.
toolStripProgressBar1.Value = 0;
toolStripProgressBar2.Value = 0;

// Enable the Add Work Items button.


toolStripButton1.Enabled = true;
}
' Event handler for the Cancel button.
Private Async Sub toolStripButton2_Click(ByVal sender As Object, ByVal e As EventArgs) Handles
toolStripButton2.Click
' Disable both buttons.
toolStripButton1.Enabled = False
toolStripButton2.Enabled = False

' Trigger cancellation.


cancellationSource.Cancel()

Try
' Asynchronously wait for the pipeline to complete processing and for
' the progress bars to update.
Await Task.WhenAll(completeWork.Completion, incrementProgress.Completion,
decrementProgress.Completion)
Catch e1 As OperationCanceledException
End Try

' Increment the progress bar that tracks the number of cancelled
' work items by the number of active work items.
toolStripProgressBar4.Value += toolStripProgressBar1.Value
toolStripProgressBar4.Value += toolStripProgressBar2.Value

' Reset the progress bars that track the number of active work items.
toolStripProgressBar1.Value = 0
toolStripProgressBar2.Value = 0

' Enable the Add Work Items button.


toolStripButton1.Enabled = True
End Sub

Exemple
L’exemple suivant montre le code complet pour Form1.cs (Form1.vb pour Visual Basic).

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;

namespace CancellationWinForms
{
public partial class Form1 : Form
{
// A placeholder type that performs work.
class WorkItem
{
// Performs work for the provided number of milliseconds.
public void DoWork(int milliseconds)
{
// For demonstration, suspend the current thread.
Thread.Sleep(milliseconds);
}
}

// Enables the user interface to signal cancellation.


CancellationTokenSource cancellationSource;

// The first node in the dataflow pipeline.


TransformBlock<WorkItem, WorkItem> startWork;

// The second, and final, node in the dataflow pipeline.


ActionBlock<WorkItem> completeWork;
// Increments the value of the provided progress bar.
ActionBlock<ToolStripProgressBar> incrementProgress;

// Decrements the value of the provided progress bar.


ActionBlock<ToolStripProgressBar> decrementProgress;

// Enables progress bar actions to run on the UI thread.


TaskScheduler uiTaskScheduler;

public Form1()
{
InitializeComponent();

// Create the UI task scheduler from the current sychronization


// context.
uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}

// Creates the blocks that participate in the dataflow pipeline.


private void CreatePipeline()
{
// Create the cancellation source.
cancellationSource = new CancellationTokenSource();

// Create the first node in the pipeline.


startWork = new TransformBlock<WorkItem, WorkItem>(workItem =>
{
// Perform some work.
workItem.DoWork(250);

// Decrement the progress bar that tracks the count of


// active work items in this stage of the pipeline.
decrementProgress.Post(toolStripProgressBar1);

// Increment the progress bar that tracks the count of


// active work items in the next stage of the pipeline.
incrementProgress.Post(toolStripProgressBar2);

// Send the work item to the next stage of the pipeline.


return workItem;
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token
});

// Create the second, and final, node in the pipeline.


completeWork = new ActionBlock<WorkItem>(workItem =>
{
// Perform some work.
workItem.DoWork(1000);

// Decrement the progress bar that tracks the count of


// active work items in this stage of the pipeline.
decrementProgress.Post(toolStripProgressBar2);

// Increment the progress bar that tracks the overall


// count of completed work items.
incrementProgress.Post(toolStripProgressBar3);
},
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token,
MaxDegreeOfParallelism = 2
});

// Connect the two nodes of the pipeline. When the first node completes,
// set the second node also to the completed state.
startWork.LinkTo(
startWork.LinkTo(
completeWork, new DataflowLinkOptions { PropagateCompletion = true });

// Create the dataflow action blocks that increment and decrement


// progress bars.
// These blocks use the task scheduler that is associated with
// the UI thread.

incrementProgress = new ActionBlock<ToolStripProgressBar>(


progressBar => progressBar.Value++,
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token,
TaskScheduler = uiTaskScheduler
});

decrementProgress = new ActionBlock<ToolStripProgressBar>(


progressBar => progressBar.Value--,
new ExecutionDataflowBlockOptions
{
CancellationToken = cancellationSource.Token,
TaskScheduler = uiTaskScheduler
});
}

// Event handler for the Add Work Items button.


private void toolStripButton1_Click(object sender, EventArgs e)
{
// The Cancel button is disabled when the pipeline is not active.
// Therefore, create the pipeline and enable the Cancel button
// if the Cancel button is disabled.
if (!toolStripButton2.Enabled)
{
CreatePipeline();

// Enable the Cancel button.


toolStripButton2.Enabled = true;
}

// Post several work items to the head of the pipeline.


for (int i = 0; i < 5; i++)
{
toolStripProgressBar1.Value++;
startWork.Post(new WorkItem());
}
}

// Event handler for the Cancel button.


private async void toolStripButton2_Click(object sender, EventArgs e)
{
// Disable both buttons.
toolStripButton1.Enabled = false;
toolStripButton2.Enabled = false;

// Trigger cancellation.
cancellationSource.Cancel();

try
{
// Asynchronously wait for the pipeline to complete processing and for
// the progress bars to update.
await Task.WhenAll(
completeWork.Completion,
incrementProgress.Completion,
decrementProgress.Completion);
}
catch (OperationCanceledException)
{
}
// Increment the progress bar that tracks the number of cancelled
// work items by the number of active work items.
toolStripProgressBar4.Value += toolStripProgressBar1.Value;
toolStripProgressBar4.Value += toolStripProgressBar2.Value;

// Reset the progress bars that track the number of active work items.
toolStripProgressBar1.Value = 0;
toolStripProgressBar2.Value = 0;

// Enable the Add Work Items button.


toolStripButton1.Enabled = true;
}

~Form1()
{
cancellationSource.Dispose();
}
}
}

Imports System.Threading
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

Namespace CancellationWinForms
Partial Public Class Form1
Inherits Form
' A placeholder type that performs work.
Private Class WorkItem
' Performs work for the provided number of milliseconds.
Public Sub DoWork(ByVal milliseconds As Integer)
' For demonstration, suspend the current thread.
Thread.Sleep(milliseconds)
End Sub
End Class

' Enables the user interface to signal cancellation.


Private cancellationSource As CancellationTokenSource

' The first node in the dataflow pipeline.


Private startWork As TransformBlock(Of WorkItem, WorkItem)

' The second, and final, node in the dataflow pipeline.


Private completeWork As ActionBlock(Of WorkItem)

' Increments the value of the provided progress bar.


Private incrementProgress As ActionBlock(Of ToolStripProgressBar)

' Decrements the value of the provided progress bar.


Private decrementProgress As ActionBlock(Of ToolStripProgressBar)

' Enables progress bar actions to run on the UI thread.


Private uiTaskScheduler As TaskScheduler

Public Sub New()


InitializeComponent()

' Create the UI task scheduler from the current sychronization


' context.
uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
End Sub

' Creates the blocks that participate in the dataflow pipeline.


Private Sub CreatePipeline()
' Create the cancellation source.
cancellationSource = New CancellationTokenSource()
cancellationSource = New CancellationTokenSource()

' Create the first node in the pipeline.


startWork = New TransformBlock(Of WorkItem, WorkItem)(Function(workItem)
' Perform some work.
' Decrement the progress bar that
tracks the count of
' active work items in this stage of
the pipeline.
' Increment the progress bar that
tracks the count of
' active work items in the next stage
of the pipeline.
' Send the work item to the next stage
of the pipeline.
workItem.DoWork(250)

decrementProgress.Post(toolStripProgressBar1)

incrementProgress.Post(toolStripProgressBar2)
Return workItem
End Function,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token})

' Create the second, and final, node in the pipeline.


completeWork = New ActionBlock(Of WorkItem)(Sub(workItem)
' Perform some work.
' Decrement the progress bar that tracks the
count of
' active work items in this stage of the
pipeline.
' Increment the progress bar that tracks the
overall
' count of completed work items.
workItem.DoWork(1000)
decrementProgress.Post(toolStripProgressBar2)
incrementProgress.Post(toolStripProgressBar3)
End Sub,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
.MaxDegreeOfParallelism = 2})

' Connect the two nodes of the pipeline. When the first node completes,
' set the second node also to the completed state.
startWork.LinkTo(
completeWork, New DataflowLinkOptions With {.PropagateCompletion = true})

' Create the dataflow action blocks that increment and decrement
' progress bars.
' These blocks use the task scheduler that is associated with
' the UI thread.

incrementProgress = New ActionBlock(Of ToolStripProgressBar)(


Sub(progressBar) progressBar.Value += 1,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
.TaskScheduler = uiTaskScheduler})

decrementProgress = New ActionBlock(Of ToolStripProgressBar)(


Sub(progressBar) progressBar.Value -= 1,
New ExecutionDataflowBlockOptions With {.CancellationToken = cancellationSource.Token,
.TaskScheduler = uiTaskScheduler})

End Sub

' Event handler for the Add Work Items button.


Private Sub toolStripButton1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles
toolStripButton1.Click
' The Cancel button is disabled when the pipeline is not active.
' Therefore, create the pipeline and enable the Cancel button
' if the Cancel button is disabled.
If Not toolStripButton2.Enabled Then
If Not toolStripButton2.Enabled Then
CreatePipeline()

' Enable the Cancel button.


toolStripButton2.Enabled = True
End If

' Post several work items to the head of the pipeline.


For i As Integer = 0 To 4
toolStripProgressBar1.Value += 1
startWork.Post(New WorkItem())
Next i
End Sub

' Event handler for the Cancel button.


Private Async Sub toolStripButton2_Click(ByVal sender As Object, ByVal e As EventArgs) Handles
toolStripButton2.Click
' Disable both buttons.
toolStripButton1.Enabled = False
toolStripButton2.Enabled = False

' Trigger cancellation.


cancellationSource.Cancel()

Try
' Asynchronously wait for the pipeline to complete processing and for
' the progress bars to update.
Await Task.WhenAll(completeWork.Completion, incrementProgress.Completion,
decrementProgress.Completion)
Catch e1 As OperationCanceledException
End Try

' Increment the progress bar that tracks the number of cancelled
' work items by the number of active work items.
toolStripProgressBar4.Value += toolStripProgressBar1.Value
toolStripProgressBar4.Value += toolStripProgressBar2.Value

' Reset the progress bars that track the number of active work items.
toolStripProgressBar1.Value = 0
toolStripProgressBar2.Value = 0

' Enable the Add Work Items button.


toolStripButton1.Enabled = True
End Sub

Protected Overrides Sub Finalize()


cancellationSource.Dispose()
MyBase.Finalize()
End Sub
End Class
End Namespace

L'illustration suivante présente l'application en cours d'exécution.

Voir aussi
Dataflow
Procédure pas à pas : Créer un type de bloc de flux
de données personnalisé
18/07/2020 • 22 minutes to read • Edit Online

Bien que la bibliothèque de flux de données TPL fournisse plusieurs types de blocs de flux de données offrant une
large gamme de fonctionnalités, vous pouvez aussi créer des types de blocs personnalisés. Ce document décrit
comment créer un type de bloc de flux de données qui implémente un comportement personnalisé.

Prérequis
Consultez Flux de données avant de lire ce document.

NOTE
La bibliothèque de flux de données TPL (espace de noms System.Threading.Tasks.Dataflow) n'est pas distribuée avec .NET.
Pour installer l’espace de noms System.Threading.Tasks.Dataflow dans Visual Studio, ouvrez votre projet, choisissez Gérer les
packages NuGet dans le menu Projet , puis recherchez en ligne le package System.Threading.Tasks.Dataflow . Vous
pouvez également l’installer à l’aide de l’interface CLI .NET Core en exécutant
dotnet add package System.Threading.Tasks.Dataflow .

Définition du bloc de flux de données d’une fenêtre coulissante


Imaginez une application de flux de données qui exige que les valeurs d’entrée soient mises en mémoire tampon,
puis affichées sous la forme d’une fenêtre coulissante. Par exemple, pour les valeurs d’entrée {0, 1, 2, 3, 4, 5} et une
taille de fenêtre de 3, un bloc de flux de données de fenêtre coulissante génère les tableaux de sortie {0, 1, 2}, {1, 2,
3}, {2, 3, 4,} et {3, 4, 5}. Les sections suivantes décrivent deux façons de créer un type de bloc de flux de données qui
implémente ce comportement personnalisé. La première technique utilise la méthode Encapsulate pour combiner
la fonctionnalité d’un objet ISourceBlock<TOutput> et d’un objet ITargetBlock<TInput> dans un bloc propagateur.
La seconde technique définit une classe dérivée de IPropagatorBlock<TInput,TOutput> et combine la
fonctionnalité existante pour générer le comportement personnalisé.

Utilisation de la méthode d’encapsulage pour définir le bloc de flux


d’une fenêtre coulissante
L’exemple suivant utilise la méthode Encapsulate pour créer un bloc propagateur à partir d’une source et d’une
cible. Un bloc propagateur permet à un bloc source et à un bloc cible d’agir en tant que destinataire et expéditeur
des données.
Cette technique est utile lorsque vous avez besoin d’une fonctionnalité de flux de données personnalisée, mais pas
d’un type qui fournit d’autres méthodes, propriétés ou champs.
// Creates a IPropagatorBlock<T, T[]> object propagates data in a
// sliding window fashion.
public static IPropagatorBlock<T, T[]> CreateSlidingWindow<T>(int windowSize)
{
// Create a queue to hold messages.
var queue = new Queue<T>();

// The source part of the propagator holds arrays of size windowSize


// and propagates data out to any connected targets.
var source = new BufferBlock<T[]>();

// The target part receives data and adds them to the queue.
var target = new ActionBlock<T>(item =>
{
// Add the item to the queue.
queue.Enqueue(item);
// Remove the oldest item when the queue size exceeds the window size.
if (queue.Count > windowSize)
queue.Dequeue();
// Post the data in the queue to the source block when the queue size
// equals the window size.
if (queue.Count == windowSize)
source.Post(queue.ToArray());
});

// When the target is set to the completed state, propagate out any
// remaining data and set the source to the completed state.
target.Completion.ContinueWith(delegate
{
if (queue.Count > 0 && queue.Count < windowSize)
source.Post(queue.ToArray());
source.Complete();
});

// Return a IPropagatorBlock<T, T[]> object that encapsulates the


// target and source blocks.
return DataflowBlock.Encapsulate(target, source);
}
' Creates a IPropagatorBlock<T, T[]> object propagates data in a
' sliding window fashion.
Public Shared Function CreateSlidingWindow(Of T)(ByVal windowSize As Integer) As IPropagatorBlock(Of T, T())
' Create a queue to hold messages.
Dim queue = New Queue(Of T)()

' The source part of the propagator holds arrays of size windowSize
' and propagates data out to any connected targets.
Dim source = New BufferBlock(Of T())()

' The target part receives data and adds them to the queue.
Dim target = New ActionBlock(Of T)(Sub(item)
' Add the item to the queue.
' Remove the oldest item when the queue size exceeds the window
size.
' Post the data in the queue to the source block when the queue
size
' equals the window size.
queue.Enqueue(item)
If queue.Count > windowSize Then
queue.Dequeue()
End If
If queue.Count = windowSize Then
source.Post(queue.ToArray())
End If
End Sub)

' When the target is set to the completed state, propagate out any
' remaining data and set the source to the completed state.
target.Completion.ContinueWith(Sub()
If queue.Count > 0 AndAlso queue.Count < windowSize Then
source.Post(queue.ToArray())
End If
source.Complete()
End Sub)

' Return a IPropagatorBlock<T, T[]> object that encapsulates the


' target and source blocks.
Return DataflowBlock.Encapsulate(target, source)
End Function

Dérivation d’IPropagatorBlock pour définir le bloc de flux de données


d’une fenêtre coulissante
L’exemple suivant montre la classe SlidingWindowBlock . Cette classe est dérivée
IPropagatorBlock<TInput,TOutput> pour pouvoir agir à la fois comme une source et une cible de données.
Comme dans l’exemple précédent, la classe SlidingWindowBlock s’appuie sur des types de blocs de flux de données
existants. Cependant, la classe SlidingWindowBlock implémente également les méthodes requises par les interfaces
ISourceBlock<TOutput>, ITargetBlock<TInput> et IDataflowBlock. Toutes ces méthodes transfèrent le travail aux
membres du type de bloc de flux de données prédéfini. Par exemple, la méthode Post transmet le travail au
membre de données m_target , qui est également un objet ITargetBlock<TInput>.
Cette technique est utile lorsque vous avez besoin d’une fonctionnalité de flux de données personnalisée, mais
également d’un type qui fournit d’autres méthodes, propriétés ou champs. Par exemple, la classe
SlidingWindowBlock est également dérivée de IReceivableSourceBlock<TOutput> afin de fournir les méthodes
TryReceive et TryReceiveAll. La classe SlidingWindowBlock apporte également une extensibilité en fournissant la
propriété WindowSize , qui récupère le nombre d’éléments dans la fenêtre coulissante.

// Propagates data in a sliding window fashion.


public class SlidingWindowBlock<T> : IPropagatorBlock<T, T[]>,
IReceivableSourceBlock<T[]>
IReceivableSourceBlock<T[]>
{
// The size of the window.
private readonly int m_windowSize;
// The target part of the block.
private readonly ITargetBlock<T> m_target;
// The source part of the block.
private readonly IReceivableSourceBlock<T[]> m_source;

// Constructs a SlidingWindowBlock object.


public SlidingWindowBlock(int windowSize)
{
// Create a queue to hold messages.
var queue = new Queue<T>();

// The source part of the propagator holds arrays of size windowSize


// and propagates data out to any connected targets.
var source = new BufferBlock<T[]>();

// The target part receives data and adds them to the queue.
var target = new ActionBlock<T>(item =>
{
// Add the item to the queue.
queue.Enqueue(item);
// Remove the oldest item when the queue size exceeds the window size.
if (queue.Count > windowSize)
queue.Dequeue();
// Post the data in the queue to the source block when the queue size
// equals the window size.
if (queue.Count == windowSize)
source.Post(queue.ToArray());
});

// When the target is set to the completed state, propagate out any
// remaining data and set the source to the completed state.
target.Completion.ContinueWith(delegate
{
if (queue.Count > 0 && queue.Count < windowSize)
source.Post(queue.ToArray());
source.Complete();
});

m_windowSize = windowSize;
m_target = target;
m_source = source;
}

// Retrieves the size of the window.


public int WindowSize { get { return m_windowSize; } }

#region IReceivableSourceBlock<TOutput> members

// Attempts to synchronously receive an item from the source.


public bool TryReceive(Predicate<T[]> filter, out T[] item)
{
return m_source.TryReceive(filter, out item);
}

// Attempts to remove all available elements from the source into a new
// array that is returned.
public bool TryReceiveAll(out IList<T[]> items)
{
return m_source.TryReceiveAll(out items);
}

#endregion

#region ISourceBlock<TOutput> members

// Links this dataflow block to the provided target.


// Links this dataflow block to the provided target.
public IDisposable LinkTo(ITargetBlock<T[]> target, DataflowLinkOptions linkOptions)
{
return m_source.LinkTo(target, linkOptions);
}

// Called by a target to reserve a message previously offered by a source


// but not yet consumed by this target.
bool ISourceBlock<T[]>.ReserveMessage(DataflowMessageHeader messageHeader,
ITargetBlock<T[]> target)
{
return m_source.ReserveMessage(messageHeader, target);
}

// Called by a target to consume a previously offered message from a source.


T[] ISourceBlock<T[]>.ConsumeMessage(DataflowMessageHeader messageHeader,
ITargetBlock<T[]> target, out bool messageConsumed)
{
return m_source.ConsumeMessage(messageHeader,
target, out messageConsumed);
}

// Called by a target to release a previously reserved message from a source.


void ISourceBlock<T[]>.ReleaseReservation(DataflowMessageHeader messageHeader,
ITargetBlock<T[]> target)
{
m_source.ReleaseReservation(messageHeader, target);
}

#endregion

#region ITargetBlock<TInput> members

// Asynchronously passes a message to the target block, giving the target the
// opportunity to consume the message.
DataflowMessageStatus ITargetBlock<T>.OfferMessage(DataflowMessageHeader messageHeader,
T messageValue, ISourceBlock<T> source, bool consumeToAccept)
{
return m_target.OfferMessage(messageHeader,
messageValue, source, consumeToAccept);
}

#endregion

#region IDataflowBlock members

// Gets a Task that represents the completion of this dataflow block.


public Task Completion { get { return m_source.Completion; } }

// Signals to this target block that it should not accept any more messages,
// nor consume postponed messages.
public void Complete()
{
m_target.Complete();
}

public void Fault(Exception error)


{
m_target.Fault(error);
}

#endregion
}

' Propagates data in a sliding window fashion.


Public Class SlidingWindowBlock(Of T)
Implements IPropagatorBlock(Of T, T()), IReceivableSourceBlock(Of T())
' The size of the window.
' The size of the window.
Private ReadOnly m_windowSize As Integer
' The target part of the block.
Private ReadOnly m_target As ITargetBlock(Of T)
' The source part of the block.
Private ReadOnly m_source As IReceivableSourceBlock(Of T())

' Constructs a SlidingWindowBlock object.


Public Sub New(ByVal windowSize As Integer)
' Create a queue to hold messages.
Dim queue = New Queue(Of T)()

' The source part of the propagator holds arrays of size windowSize
' and propagates data out to any connected targets.
Dim source = New BufferBlock(Of T())()

' The target part receives data and adds them to the queue.
Dim target = New ActionBlock(Of T)(Sub(item)
' Add the item to the queue.
' Remove the oldest item when the queue size exceeds the
window size.
' Post the data in the queue to the source block when the
queue size
' equals the window size.
queue.Enqueue(item)
If queue.Count > windowSize Then
queue.Dequeue()
End If
If queue.Count = windowSize Then
source.Post(queue.ToArray())
End If
End Sub)

' When the target is set to the completed state, propagate out any
' remaining data and set the source to the completed state.
target.Completion.ContinueWith(Sub()
If queue.Count > 0 AndAlso queue.Count < windowSize Then
source.Post(queue.ToArray())
End If
source.Complete()
End Sub)

m_windowSize = windowSize
m_target = target
m_source = source
End Sub

' Retrieves the size of the window.


Public ReadOnly Property WindowSize() As Integer
Get
Return m_windowSize
End Get
End Property

'#Region "IReceivableSourceBlock<TOutput> members"

' Attempts to synchronously receive an item from the source.


Public Function TryReceive(ByVal filter As Predicate(Of T()), <System.Runtime.InteropServices.Out()>
ByRef item() As T) As Boolean Implements IReceivableSourceBlock(Of T()).TryReceive
Return m_source.TryReceive(filter, item)
End Function

' Attempts to remove all available elements from the source into a new
' array that is returned.
Public Function TryReceiveAll(<System.Runtime.InteropServices.Out()> ByRef items As IList(Of T())) As
Boolean Implements IReceivableSourceBlock(Of T()).TryReceiveAll
Return m_source.TryReceiveAll(items)
End Function

'#End Region
'#End Region

#Region "ISourceBlock<TOutput> members"

' Links this dataflow block to the provided target.


Public Function LinkTo(ByVal target As ITargetBlock(Of T()), ByVal linkOptions As DataflowLinkOptions)
As IDisposable Implements ISourceBlock(Of T()).LinkTo
Return m_source.LinkTo(target, linkOptions)
End Function

' Called by a target to reserve a message previously offered by a source


' but not yet consumed by this target.
Private Function ReserveMessage(ByVal messageHeader As DataflowMessageHeader, ByVal target As
ITargetBlock(Of T())) As Boolean Implements ISourceBlock(Of T()).ReserveMessage
Return m_source.ReserveMessage(messageHeader, target)
End Function

' Called by a target to consume a previously offered message from a source.


Private Function ConsumeMessage(ByVal messageHeader As DataflowMessageHeader, ByVal target As
ITargetBlock(Of T()), ByRef messageConsumed As Boolean) As T() Implements ISourceBlock(Of T()).ConsumeMessage
Return m_source.ConsumeMessage(messageHeader, target, messageConsumed)
End Function

' Called by a target to release a previously reserved message from a source.


Private Sub ReleaseReservation(ByVal messageHeader As DataflowMessageHeader, ByVal target As
ITargetBlock(Of T())) Implements ISourceBlock(Of T()).ReleaseReservation
m_source.ReleaseReservation(messageHeader, target)
End Sub

#End Region

#Region "ITargetBlock<TInput> members"

' Asynchronously passes a message to the target block, giving the target the
' opportunity to consume the message.
Private Function OfferMessage(ByVal messageHeader As DataflowMessageHeader, ByVal messageValue As T,
ByVal source As ISourceBlock(Of T), ByVal consumeToAccept As Boolean) As DataflowMessageStatus Implements
ITargetBlock(Of T).OfferMessage
Return m_target.OfferMessage(messageHeader, messageValue, source, consumeToAccept)
End Function

#End Region

#Region "IDataflowBlock members"

' Gets a Task that represents the completion of this dataflow block.
Public ReadOnly Property Completion() As Task Implements IDataflowBlock.Completion
Get
Return m_source.Completion
End Get
End Property

' Signals to this target block that it should not accept any more messages,
' nor consume postponed messages.
Public Sub Complete() Implements IDataflowBlock.Complete
m_target.Complete()
End Sub

Public Sub Fault(ByVal [error] As Exception) Implements IDataflowBlock.Fault


m_target.Fault([error])
End Sub

#End Region
End Class

Exemple complet
L'exemple suivant présente le code complet pour cette visite. Il montre également comment utiliser les deux blocs
de fenêtre coulissante dans une méthode qui écrit dans le bloc, lit à partir de celui-ci, puis imprime les résultats
dans la console.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to create a custom dataflow block type.


class Program
{
// Creates a IPropagatorBlock<T, T[]> object propagates data in a
// sliding window fashion.
public static IPropagatorBlock<T, T[]> CreateSlidingWindow<T>(int windowSize)
{
// Create a queue to hold messages.
var queue = new Queue<T>();

// The source part of the propagator holds arrays of size windowSize


// and propagates data out to any connected targets.
var source = new BufferBlock<T[]>();

// The target part receives data and adds them to the queue.
var target = new ActionBlock<T>(item =>
{
// Add the item to the queue.
queue.Enqueue(item);
// Remove the oldest item when the queue size exceeds the window size.
if (queue.Count > windowSize)
queue.Dequeue();
// Post the data in the queue to the source block when the queue size
// equals the window size.
if (queue.Count == windowSize)
source.Post(queue.ToArray());
});

// When the target is set to the completed state, propagate out any
// remaining data and set the source to the completed state.
target.Completion.ContinueWith(delegate
{
if (queue.Count > 0 && queue.Count < windowSize)
source.Post(queue.ToArray());
source.Complete();
});

// Return a IPropagatorBlock<T, T[]> object that encapsulates the


// target and source blocks.
return DataflowBlock.Encapsulate(target, source);
}

// Propagates data in a sliding window fashion.


public class SlidingWindowBlock<T> : IPropagatorBlock<T, T[]>,
IReceivableSourceBlock<T[]>
{
// The size of the window.
private readonly int m_windowSize;
// The target part of the block.
private readonly ITargetBlock<T> m_target;
// The source part of the block.
private readonly IReceivableSourceBlock<T[]> m_source;

// Constructs a SlidingWindowBlock object.


public SlidingWindowBlock(int windowSize)
{
// Create a queue to hold messages.
var queue = new Queue<T>();

// The source part of the propagator holds arrays of size windowSize


// and propagates data out to any connected targets.
var source = new BufferBlock<T[]>();

// The target part receives data and adds them to the queue.
var target = new ActionBlock<T>(item =>
{
// Add the item to the queue.
queue.Enqueue(item);
// Remove the oldest item when the queue size exceeds the window size.
if (queue.Count > windowSize)
queue.Dequeue();
// Post the data in the queue to the source block when the queue size
// equals the window size.
if (queue.Count == windowSize)
source.Post(queue.ToArray());
});

// When the target is set to the completed state, propagate out any
// remaining data and set the source to the completed state.
target.Completion.ContinueWith(delegate
{
if (queue.Count > 0 && queue.Count < windowSize)
source.Post(queue.ToArray());
source.Complete();
});

m_windowSize = windowSize;
m_target = target;
m_source = source;
}

// Retrieves the size of the window.


public int WindowSize { get { return m_windowSize; } }

#region IReceivableSourceBlock<TOutput> members

// Attempts to synchronously receive an item from the source.


public bool TryReceive(Predicate<T[]> filter, out T[] item)
{
return m_source.TryReceive(filter, out item);
}

// Attempts to remove all available elements from the source into a new
// array that is returned.
public bool TryReceiveAll(out IList<T[]> items)
{
return m_source.TryReceiveAll(out items);
}

#endregion

#region ISourceBlock<TOutput> members

// Links this dataflow block to the provided target.


public IDisposable LinkTo(ITargetBlock<T[]> target, DataflowLinkOptions linkOptions)
{
return m_source.LinkTo(target, linkOptions);
}

// Called by a target to reserve a message previously offered by a source


// but not yet consumed by this target.
bool ISourceBlock<T[]>.ReserveMessage(DataflowMessageHeader messageHeader,
ITargetBlock<T[]> target)
{
return m_source.ReserveMessage(messageHeader, target);
}
// Called by a target to consume a previously offered message from a source.
T[] ISourceBlock<T[]>.ConsumeMessage(DataflowMessageHeader messageHeader,
ITargetBlock<T[]> target, out bool messageConsumed)
{
return m_source.ConsumeMessage(messageHeader,
target, out messageConsumed);
}

// Called by a target to release a previously reserved message from a source.


void ISourceBlock<T[]>.ReleaseReservation(DataflowMessageHeader messageHeader,
ITargetBlock<T[]> target)
{
m_source.ReleaseReservation(messageHeader, target);
}

#endregion

#region ITargetBlock<TInput> members

// Asynchronously passes a message to the target block, giving the target the
// opportunity to consume the message.
DataflowMessageStatus ITargetBlock<T>.OfferMessage(DataflowMessageHeader messageHeader,
T messageValue, ISourceBlock<T> source, bool consumeToAccept)
{
return m_target.OfferMessage(messageHeader,
messageValue, source, consumeToAccept);
}

#endregion

#region IDataflowBlock members

// Gets a Task that represents the completion of this dataflow block.


public Task Completion { get { return m_source.Completion; } }

// Signals to this target block that it should not accept any more messages,
// nor consume postponed messages.
public void Complete()
{
m_target.Complete();
}

public void Fault(Exception error)


{
m_target.Fault(error);
}

#endregion
}

// Demonstrates usage of the sliding window block by sending the provided


// values to the provided propagator block and printing the output of
// that block to the console.
static void DemonstrateSlidingWindow<T>(IPropagatorBlock<T, T[]> slidingWindow,
IEnumerable<T> values)
{
// Create an action block that prints arrays of data to the console.
string windowComma = string.Empty;
var printWindow = new ActionBlock<T[]>(window =>
{
Console.Write(windowComma);
Console.Write("{");

string comma = string.Empty;


foreach (T item in window)
{
Console.Write(comma);
Console.Write(item);
Console.Write(item);
comma = ",";
}
Console.Write("}");

windowComma = ", ";


});

// Link the printer block to the sliding window block.


slidingWindow.LinkTo(printWindow);

// Set the printer block to the completed state when the sliding window
// block completes.
slidingWindow.Completion.ContinueWith(delegate { printWindow.Complete(); });

// Print an additional newline to the console when the printer block completes.
var completion = printWindow.Completion.ContinueWith(delegate { Console.WriteLine(); });

// Post the provided values to the sliding window block and then wait
// for the sliding window block to complete.
foreach (T value in values)
{
slidingWindow.Post(value);
}
slidingWindow.Complete();

// Wait for the printer to complete and perform its final action.
completion.Wait();
}

static void Main(string[] args)


{

Console.Write("Using the DataflowBlockExtensions.Encapsulate method ");


Console.WriteLine("(T=int, windowSize=3):");
DemonstrateSlidingWindow(CreateSlidingWindow<int>(3), Enumerable.Range(0, 10));

Console.WriteLine();

var slidingWindow = new SlidingWindowBlock<char>(4);

Console.Write("Using SlidingWindowBlock<T> ");


Console.WriteLine("(T=char, windowSize={0}):", slidingWindow.WindowSize);
DemonstrateSlidingWindow(slidingWindow, from n in Enumerable.Range(65, 10)
select (char)n);
}
}

/* Output:
Using the DataflowBlockExtensions.Encapsulate method (T=int, windowSize=3):
{0,1,2}, {1,2,3}, {2,3,4}, {3,4,5}, {4,5,6}, {5,6,7}, {6,7,8}, {7,8,9}

Using SlidingWindowBlock<T> (T=char, windowSize=4):


{A,B,C,D}, {B,C,D,E}, {C,D,E,F}, {D,E,F,G}, {E,F,G,H}, {F,G,H,I}, {G,H,I,J}
*/

Imports System.Collections.Generic
Imports System.Linq
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to create a custom dataflow block type.


Friend Class Program
' Creates a IPropagatorBlock<T, T[]> object propagates data in a
' sliding window fashion.
Public Shared Function CreateSlidingWindow(Of T)(ByVal windowSize As Integer) As IPropagatorBlock(Of T,
T())
' Create a queue to hold messages.
Dim queue = New Queue(Of T)()

' The source part of the propagator holds arrays of size windowSize
' and propagates data out to any connected targets.
Dim source = New BufferBlock(Of T())()

' The target part receives data and adds them to the queue.
Dim target = New ActionBlock(Of T)(Sub(item)
' Add the item to the queue.
' Remove the oldest item when the queue size exceeds the window
size.
' Post the data in the queue to the source block when the queue
size
' equals the window size.
queue.Enqueue(item)
If queue.Count > windowSize Then
queue.Dequeue()
End If
If queue.Count = windowSize Then
source.Post(queue.ToArray())
End If
End Sub)

' When the target is set to the completed state, propagate out any
' remaining data and set the source to the completed state.
target.Completion.ContinueWith(Sub()
If queue.Count > 0 AndAlso queue.Count < windowSize Then
source.Post(queue.ToArray())
End If
source.Complete()
End Sub)

' Return a IPropagatorBlock<T, T[]> object that encapsulates the


' target and source blocks.
Return DataflowBlock.Encapsulate(target, source)
End Function

' Propagates data in a sliding window fashion.


Public Class SlidingWindowBlock(Of T)
Implements IPropagatorBlock(Of T, T()), IReceivableSourceBlock(Of T())
' The size of the window.
Private ReadOnly m_windowSize As Integer
' The target part of the block.
Private ReadOnly m_target As ITargetBlock(Of T)
' The source part of the block.
Private ReadOnly m_source As IReceivableSourceBlock(Of T())

' Constructs a SlidingWindowBlock object.


Public Sub New(ByVal windowSize As Integer)
' Create a queue to hold messages.
Dim queue = New Queue(Of T)()

' The source part of the propagator holds arrays of size windowSize
' and propagates data out to any connected targets.
Dim source = New BufferBlock(Of T())()

' The target part receives data and adds them to the queue.
Dim target = New ActionBlock(Of T)(Sub(item)
' Add the item to the queue.
' Remove the oldest item when the queue size exceeds the
window size.
' Post the data in the queue to the source block when the
queue size
' equals the window size.
queue.Enqueue(item)
If queue.Count > windowSize Then
queue.Dequeue()
End If
If queue.Count = windowSize Then
If queue.Count = windowSize Then
source.Post(queue.ToArray())
End If
End Sub)

' When the target is set to the completed state, propagate out any
' remaining data and set the source to the completed state.
target.Completion.ContinueWith(Sub()
If queue.Count > 0 AndAlso queue.Count < windowSize Then
source.Post(queue.ToArray())
End If
source.Complete()
End Sub)

m_windowSize = windowSize
m_target = target
m_source = source
End Sub

' Retrieves the size of the window.


Public ReadOnly Property WindowSize() As Integer
Get
Return m_windowSize
End Get
End Property

'#Region "IReceivableSourceBlock<TOutput> members"

' Attempts to synchronously receive an item from the source.


Public Function TryReceive(ByVal filter As Predicate(Of T()), <System.Runtime.InteropServices.Out()>
ByRef item() As T) As Boolean Implements IReceivableSourceBlock(Of T()).TryReceive
Return m_source.TryReceive(filter, item)
End Function

' Attempts to remove all available elements from the source into a new
' array that is returned.
Public Function TryReceiveAll(<System.Runtime.InteropServices.Out()> ByRef items As IList(Of T())) As
Boolean Implements IReceivableSourceBlock(Of T()).TryReceiveAll
Return m_source.TryReceiveAll(items)
End Function

'#End Region

#Region "ISourceBlock<TOutput> members"

' Links this dataflow block to the provided target.


Public Function LinkTo(ByVal target As ITargetBlock(Of T()), ByVal linkOptions As DataflowLinkOptions)
As IDisposable Implements ISourceBlock(Of T()).LinkTo
Return m_source.LinkTo(target, linkOptions)
End Function

' Called by a target to reserve a message previously offered by a source


' but not yet consumed by this target.
Private Function ReserveMessage(ByVal messageHeader As DataflowMessageHeader, ByVal target As
ITargetBlock(Of T())) As Boolean Implements ISourceBlock(Of T()).ReserveMessage
Return m_source.ReserveMessage(messageHeader, target)
End Function

' Called by a target to consume a previously offered message from a source.


Private Function ConsumeMessage(ByVal messageHeader As DataflowMessageHeader, ByVal target As
ITargetBlock(Of T()), ByRef messageConsumed As Boolean) As T() Implements ISourceBlock(Of T()).ConsumeMessage
Return m_source.ConsumeMessage(messageHeader, target, messageConsumed)
End Function

' Called by a target to release a previously reserved message from a source.


Private Sub ReleaseReservation(ByVal messageHeader As DataflowMessageHeader, ByVal target As
ITargetBlock(Of T())) Implements ISourceBlock(Of T()).ReleaseReservation
m_source.ReleaseReservation(messageHeader, target)
End Sub
#End Region

#Region "ITargetBlock<TInput> members"

' Asynchronously passes a message to the target block, giving the target the
' opportunity to consume the message.
Private Function OfferMessage(ByVal messageHeader As DataflowMessageHeader, ByVal messageValue As T,
ByVal source As ISourceBlock(Of T), ByVal consumeToAccept As Boolean) As DataflowMessageStatus Implements
ITargetBlock(Of T).OfferMessage
Return m_target.OfferMessage(messageHeader, messageValue, source, consumeToAccept)
End Function

#End Region

#Region "IDataflowBlock members"

' Gets a Task that represents the completion of this dataflow block.
Public ReadOnly Property Completion() As Task Implements IDataflowBlock.Completion
Get
Return m_source.Completion
End Get
End Property

' Signals to this target block that it should not accept any more messages,
' nor consume postponed messages.
Public Sub Complete() Implements IDataflowBlock.Complete
m_target.Complete()
End Sub

Public Sub Fault(ByVal [error] As Exception) Implements IDataflowBlock.Fault


m_target.Fault([error])
End Sub

#End Region
End Class

' Demonstrates usage of the sliding window block by sending the provided
' values to the provided propagator block and printing the output of
' that block to the console.
Private Shared Sub DemonstrateSlidingWindow(Of T)(ByVal slidingWindow As IPropagatorBlock(Of T, T()),
ByVal values As IEnumerable(Of T))
' Create an action block that prints arrays of data to the console.
Dim windowComma As String = String.Empty
Dim printWindow = New ActionBlock(Of T())(Sub(window)
Console.Write(windowComma)
Console.Write("{")
Dim comma As String = String.Empty
For Each item As T In window
Console.Write(comma)
Console.Write(item)
comma = ","
Next item
Console.Write("}")
windowComma = ", "
End Sub)

' Link the printer block to the sliding window block.


slidingWindow.LinkTo(printWindow)

' Set the printer block to the completed state when the sliding window
' block completes.
slidingWindow.Completion.ContinueWith(Sub() printWindow.Complete())

' Print an additional newline to the console when the printer block completes.
Dim completion = printWindow.Completion.ContinueWith(Sub() Console.WriteLine())

' Post the provided values to the sliding window block and then wait
' for the sliding window block to complete.
For Each value As T In values
For Each value As T In values
slidingWindow.Post(value)
Next value
slidingWindow.Complete()

' Wait for the printer to complete and perform its final action.
completion.Wait()
End Sub

Shared Sub Main(ByVal args() As String)

Console.Write("Using the DataflowBlockExtensions.Encapsulate method ")


Console.WriteLine("(T=int, windowSize=3):")
DemonstrateSlidingWindow(CreateSlidingWindow(Of Integer)(3), Enumerable.Range(0, 10))

Console.WriteLine()

Dim slidingWindow = New SlidingWindowBlock(Of Char)(4)

Console.Write("Using SlidingWindowBlock<T> ")


Console.WriteLine("(T=char, windowSize={0}):", slidingWindow.WindowSize)
DemonstrateSlidingWindow(slidingWindow, _
From n In Enumerable.Range(65, 10) _
Select ChrW(n))
End Sub
End Class

' Output:
'Using the DataflowBlockExtensions.Encapsulate method (T=int, windowSize=3):
'{0,1,2}, {1,2,3}, {2,3,4}, {3,4,5}, {4,5,6}, {5,6,7}, {6,7,8}, {7,8,9}
'
'Using SlidingWindowBlock<T> (T=char, windowSize=4):
'{A,B,C,D}, {B,C,D,E}, {C,D,E,F}, {D,E,F,G}, {E,F,G,H}, {F,G,H,I}, {G,H,I,J}
'

Voir aussi
Dataflow
Procédure : Utiliser JoinBlock pour lire des données
de plusieurs sources
18/07/2020 • 10 minutes to read • Edit Online

Ce document explique comment utiliser la classe JoinBlock<T1,T2> pour effectuer une opération lorsque des
données sont disponibles à partir de plusieurs sources. Il présente aussi comment utiliser le mode non gourmand
pour permettre à plusieurs blocs de jointure de partager plus efficacement une source de données.

NOTE
La bibliothèque de flux de données TPL (espace de noms System.Threading.Tasks.Dataflow) n'est pas distribuée avec .NET.
Pour installer l’espace de noms System.Threading.Tasks.Dataflow dans Visual Studio, ouvrez votre projet, choisissez Gérer
les packages NuGet dans le menu Projet , puis recherchez en ligne le package System.Threading.Tasks.Dataflow .
Vous pouvez également l’installer à l’aide de l’interface CLI .NET Core en exécutant
dotnet add package System.Threading.Tasks.Dataflow .

Exemple
L'exemple suivant définit trois types de ressources, NetworkResource , FileResource et MemoryResource , et effectue
des opérations lorsque les ressources sont disponibles. Cet exemple nécessite une paire NetworkResource et
MemoryResource pour effectuer la première opération et une paire FileResource et MemoryResource pour effectuer
la seconde opération. Pour permettre aux opérations de se produire lorsque toutes les ressources requises sont
disponibles, cet exemple utilise la classe JoinBlock<T1,T2>. Lorsqu'un objet JoinBlock<T1,T2> reçoit les données
de toutes les sources, il envoie ces données à la cible, qui dans cet exemple est un objet ActionBlock<TInput>. Les
objets JoinBlock<T1,T2> lisent à partir d'un pool partagé d'objets MemoryResource .

using System;
using System.Threading;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to use non-greedy join blocks to distribute


// resources among a dataflow network.
class Program
{
// Represents a resource. A derived class might represent
// a limited resource such as a memory, network, or I/O
// device.
abstract class Resource
{
}

// Represents a memory resource. For brevity, the details of


// this class are omitted.
class MemoryResource : Resource
{
}

// Represents a network resource. For brevity, the details of


// this class are omitted.
class NetworkResource : Resource
{
}

// Represents a file resource. For brevity, the details of


// this class are omitted.
// this class are omitted.
class FileResource : Resource
{
}

static void Main(string[] args)


{
// Create three BufferBlock<T> objects. Each object holds a different
// type of resource.
var networkResources = new BufferBlock<NetworkResource>();
var fileResources = new BufferBlock<FileResource>();
var memoryResources = new BufferBlock<MemoryResource>();

// Create two non-greedy JoinBlock<T1, T2> objects.


// The first join works with network and memory resources;
// the second pool works with file and memory resources.

var joinNetworkAndMemoryResources =
new JoinBlock<NetworkResource, MemoryResource>(
new GroupingDataflowBlockOptions
{
Greedy = false
});

var joinFileAndMemoryResources =
new JoinBlock<FileResource, MemoryResource>(
new GroupingDataflowBlockOptions
{
Greedy = false
});

// Create two ActionBlock<T> objects.


// The first block acts on a network resource and a memory resource.
// The second block acts on a file resource and a memory resource.

var networkMemoryAction =
new ActionBlock<Tuple<NetworkResource, MemoryResource>>(
data =>
{
// Perform some action on the resources.

// Print a message.
Console.WriteLine("Network worker: using resources...");

// Simulate a lengthy operation that uses the resources.


Thread.Sleep(new Random().Next(500, 2000));

// Print a message.
Console.WriteLine("Network worker: finished using resources...");

// Release the resources back to their respective pools.


networkResources.Post(data.Item1);
memoryResources.Post(data.Item2);
});

var fileMemoryAction =
new ActionBlock<Tuple<FileResource, MemoryResource>>(
data =>
{
// Perform some action on the resources.

// Print a message.
Console.WriteLine("File worker: using resources...");

// Simulate a lengthy operation that uses the resources.


Thread.Sleep(new Random().Next(500, 2000));

// Print a message.
Console.WriteLine("File worker: finished using resources...");
// Release the resources back to their respective pools.
fileResources.Post(data.Item1);
memoryResources.Post(data.Item2);
});

// Link the resource pools to the JoinBlock<T1, T2> objects.


// Because these join blocks operate in non-greedy mode, they do not
// take the resource from a pool until all resources are available from
// all pools.

networkResources.LinkTo(joinNetworkAndMemoryResources.Target1);
memoryResources.LinkTo(joinNetworkAndMemoryResources.Target2);

fileResources.LinkTo(joinFileAndMemoryResources.Target1);
memoryResources.LinkTo(joinFileAndMemoryResources.Target2);

// Link the JoinBlock<T1, T2> objects to the ActionBlock<T> objects.

joinNetworkAndMemoryResources.LinkTo(networkMemoryAction);
joinFileAndMemoryResources.LinkTo(fileMemoryAction);

// Populate the resource pools. In this example, network and


// file resources are more abundant than memory resources.

networkResources.Post(new NetworkResource());
networkResources.Post(new NetworkResource());
networkResources.Post(new NetworkResource());

memoryResources.Post(new MemoryResource());

fileResources.Post(new FileResource());
fileResources.Post(new FileResource());
fileResources.Post(new FileResource());

// Allow data to flow through the network for several seconds.


Thread.Sleep(10000);
}
}

/* Sample output:
File worker: using resources...
File worker: finished using resources...
Network worker: using resources...
Network worker: finished using resources...
File worker: using resources...
File worker: finished using resources...
Network worker: using resources...
Network worker: finished using resources...
File worker: using resources...
File worker: finished using resources...
File worker: using resources...
File worker: finished using resources...
Network worker: using resources...
Network worker: finished using resources...
Network worker: using resources...
Network worker: finished using resources...
File worker: using resources...
*/

Imports System.Threading
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to use non-greedy join blocks to distribute


' resources among a dataflow network.
Friend Class Program
' Represents a resource. A derived class might represent
' a limited resource such as a memory, network, or I/O
' a limited resource such as a memory, network, or I/O
' device.
Private MustInherit Class Resource
End Class

' Represents a memory resource. For brevity, the details of


' this class are omitted.
Private Class MemoryResource
Inherits Resource
End Class

' Represents a network resource. For brevity, the details of


' this class are omitted.
Private Class NetworkResource
Inherits Resource
End Class

' Represents a file resource. For brevity, the details of


' this class are omitted.
Private Class FileResource
Inherits Resource
End Class

Shared Sub Main(ByVal args() As String)


' Create three BufferBlock<T> objects. Each object holds a different
' type of resource.
Dim networkResources = New BufferBlock(Of NetworkResource)()
Dim fileResources = New BufferBlock(Of FileResource)()
Dim memoryResources = New BufferBlock(Of MemoryResource)()

' Create two non-greedy JoinBlock<T1, T2> objects.


' The first join works with network and memory resources;
' the second pool works with file and memory resources.

Dim joinNetworkAndMemoryResources = New JoinBlock(Of NetworkResource, MemoryResource)(New


GroupingDataflowBlockOptions With {.Greedy = False})

Dim joinFileAndMemoryResources = New JoinBlock(Of FileResource, MemoryResource)(New


GroupingDataflowBlockOptions With {.Greedy = False})

' Create two ActionBlock<T> objects.


' The first block acts on a network resource and a memory resource.
' The second block acts on a file resource and a memory resource.

Dim networkMemoryAction = New ActionBlock(Of Tuple(Of NetworkResource, MemoryResource))(Sub(data)


' Perform
some action on the resources.
' Print a
message.
'
Simulate a lengthy operation that uses the resources.
' Print a
message.
' Release
the resources back to their respective pools.

Console.WriteLine("Network worker: using resources...")

Thread.Sleep(New Random().Next(500, 2000))

Console.WriteLine("Network worker: finished using resources...")

networkResources.Post(data.Item1)

memoryResources.Post(data.Item2)
End Sub)

Dim fileMemoryAction = New ActionBlock(Of Tuple(Of FileResource, MemoryResource))(Sub(data)


' Perform some
action on the resources.
' Print a
message.
' Simulate a
lengthy operation that uses the resources.
' Print a
message.
' Release the
resources back to their respective pools.

Console.WriteLine("File worker: using resources...")

Thread.Sleep(New Random().Next(500, 2000))

Console.WriteLine("File worker: finished using resources...")

fileResources.Post(data.Item1)

memoryResources.Post(data.Item2)
End Sub)

' Link the resource pools to the JoinBlock<T1, T2> objects.


' Because these join blocks operate in non-greedy mode, they do not
' take the resource from a pool until all resources are available from
' all pools.

networkResources.LinkTo(joinNetworkAndMemoryResources.Target1)
memoryResources.LinkTo(joinNetworkAndMemoryResources.Target2)

fileResources.LinkTo(joinFileAndMemoryResources.Target1)
memoryResources.LinkTo(joinFileAndMemoryResources.Target2)

' Link the JoinBlock<T1, T2> objects to the ActionBlock<T> objects.

joinNetworkAndMemoryResources.LinkTo(networkMemoryAction)
joinFileAndMemoryResources.LinkTo(fileMemoryAction)

' Populate the resource pools. In this example, network and


' file resources are more abundant than memory resources.

networkResources.Post(New NetworkResource())
networkResources.Post(New NetworkResource())
networkResources.Post(New NetworkResource())

memoryResources.Post(New MemoryResource())

fileResources.Post(New FileResource())
fileResources.Post(New FileResource())
fileResources.Post(New FileResource())

' Allow data to flow through the network for several seconds.
Thread.Sleep(10000)

End Sub

End Class

' Sample output:


'File worker: using resources...
'File worker: finished using resources...
'Network worker: using resources...
'Network worker: finished using resources...
'File worker: using resources...
'File worker: finished using resources...
'Network worker: using resources...
'Network worker: finished using resources...
'File worker: using resources...
'File worker: finished using resources...
'File worker: using resources...
'File worker: using resources...
'File worker: finished using resources...
'Network worker: using resources...
'Network worker: finished using resources...
'Network worker: using resources...
'Network worker: finished using resources...
'File worker: using resources...
'

Pour permettre l'utilisation efficace du pool partagé des objets MemoryResource , cet exemple spécifie un objet
GroupingDataflowBlockOptions qui contient le jeu de propriétés Greedy à False pour créer les objets
JoinBlock<T1,T2> qui agissent en mode non gourmand. Un bloc de jointure non-gourmand remet les messages
entrants jusqu'à ce qu'il y en ait un disponible à partir de chaque source. Si n'importe quel message remis à plus
tard a été reçu par un autre bloc, le bloc de jointure redémarre le processus. Le mode non gourmand permet aux
blocs de jointure qui partagent un ou plusieurs blocs sources d'avancer quand les autres blocs attendent des
données. Dans cet exemple, si un objet MemoryResource est ajouté au pool memoryResources , le premier bloc de
jointure peut progresser pour recevoir sa deuxième source de données. Si cet exemple visait à utiliser le mode
gourmand, qui est la valeur par défaut, un bloc de jointure peut prendre l'objet MemoryResource et attendre que la
deuxième ressource soit disponible. Toutefois, si l'autre bloc de jointure a sa deuxième source de données
disponible, il ne peut pas avancer car l'objet MemoryResource a été pris par l'autre bloc de jointure.

Programmation fiable
L'utilisation de jointures non gourmandes peut également vous aider à empêcher tout interblocage dans votre
application. Dans une application logicielle, un blocage se produit lorsque au moins deux processus comportent
chacun une ressource et attendent mutuellement qu’un autre processus en libère une autre. Examinez la requête
qui définit deux objets JoinBlock<T1,T2>. Les deux objets lisent chacun des données de deux blocs sources
partagés. En mode gourmand, si un bloc de jointure lit depuis la première source et le deuxième bloc de jointure
lit depuis la seconde source, l'application peut être interbloquée car les deux blocs de jointure attendent
mutuellement l'autre pour libérer sa ressource. En mode non gourmand, chaque bloc de jointure lit uniquement à
partir de ses sources lorsque toutes les données sont disponibles, éliminant par conséquent, le risque
d'interblocage.

Voir aussi
Dataflow
Procédure : Spécifier le degré de parallélisme dans
un bloc de flux de données
18/07/2020 • 6 minutes to read • Edit Online

Ce document décrit comment définir la propriété ExecutionDataflowBlockOptions.MaxDegreeOfParallelism pour


permettre à un bloc de flux de données d'exécution de traiter plusieurs messages à la fois. Cela est utile quand
vous avez un bloc de flux de données qui effectue un calcul de longue durée et qui peut tirer parti du traitement
des messages en parallèle. Cet exemple utilise la classe System.Threading.Tasks.Dataflow.ActionBlock<TInput>
pour effectuer plusieurs opérations de flux de données simultanément ; toutefois, vous pouvez spécifier le degré
maximum de parallélisme dans les types de bloc d'exécution prédéfinis fournis par la bibliothèque de flux de
données TPL, ActionBlock<TInput>, System.Threading.Tasks.Dataflow.TransformBlock<TInput,TOutput> et
System.Threading.Tasks.Dataflow.TransformManyBlock<TInput,TOutput>.

NOTE
La bibliothèque de flux de données TPL (espace de noms System.Threading.Tasks.Dataflow) n'est pas distribuée avec .NET.
Pour installer l’espace de noms System.Threading.Tasks.Dataflow dans Visual Studio, ouvrez votre projet, choisissez Gérer
les packages NuGet dans le menu Projet , puis recherchez en ligne le package System.Threading.Tasks.Dataflow .
Vous pouvez également l’installer à l’aide de l’interface CLI .NET Core en exécutant
dotnet add package System.Threading.Tasks.Dataflow .

Exemple
L'exemple suivant effectue deux calculs de flux de données et affiche le temps écoulé nécessaire pour chaque
calcul. Le premier calcul spécifie un degré maximum de parallélisme de 1, qui est la valeur par défaut. Un degré
maximum de parallélisme égal à 1 amène le bloc de flux de données à traiter les messages en série. Le deuxième
calcul ressemble à la première, à ceci près qu'il spécifie un degré maximum de parallélisme égal au nombre de
processeurs disponibles. Cela permet au bloc de flux de données d'effectuer plusieurs opérations en parallèle.

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to specify the maximum degree of parallelism


// when using dataflow.
class Program
{
// Performs several computations by using dataflow and returns the elapsed
// time required to perform the computations.
static TimeSpan TimeDataflowComputations(int maxDegreeOfParallelism,
int messageCount)
{
// Create an ActionBlock<int> that performs some work.
var workerBlock = new ActionBlock<int>(
// Simulate work by suspending the current thread.
millisecondsTimeout => Thread.Sleep(millisecondsTimeout),
// Specify a maximum degree of parallelism.
new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = maxDegreeOfParallelism
});

// Compute the time that it takes for several messages to


// Compute the time that it takes for several messages to
// flow through the dataflow block.

Stopwatch stopwatch = new Stopwatch();


stopwatch.Start();

for (int i = 0; i < messageCount; i++)


{
workerBlock.Post(1000);
}
workerBlock.Complete();

// Wait for all messages to propagate through the network.


workerBlock.Completion.Wait();

// Stop the timer and return the elapsed number of milliseconds.


stopwatch.Stop();
return stopwatch.Elapsed;
}
static void Main(string[] args)
{
int processorCount = Environment.ProcessorCount;
int messageCount = processorCount;

// Print the number of processors on this computer.


Console.WriteLine("Processor count = {0}.", processorCount);

TimeSpan elapsed;

// Perform two dataflow computations and print the elapsed


// time required for each.

// This call specifies a maximum degree of parallelism of 1.


// This causes the dataflow block to process messages serially.
elapsed = TimeDataflowComputations(1, messageCount);
Console.WriteLine("Degree of parallelism = {0}; message count = {1}; " +
"elapsed time = {2}ms.", 1, messageCount, (int)elapsed.TotalMilliseconds);

// Perform the computations again. This time, specify the number of


// processors as the maximum degree of parallelism. This causes
// multiple messages to be processed in parallel.
elapsed = TimeDataflowComputations(processorCount, messageCount);
Console.WriteLine("Degree of parallelism = {0}; message count = {1}; " +
"elapsed time = {2}ms.", processorCount, messageCount, (int)elapsed.TotalMilliseconds);
}
}

/* Sample output:
Processor count = 4.
Degree of parallelism = 1; message count = 4; elapsed time = 4032ms.
Degree of parallelism = 4; message count = 4; elapsed time = 1001ms.
*/

Imports System.Diagnostics
Imports System.Threading
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to specify the maximum degree of parallelism


' when using dataflow.
Friend Class Program
' Performs several computations by using dataflow and returns the elapsed
' time required to perform the computations.
Private Shared Function TimeDataflowComputations(ByVal maxDegreeOfParallelism As Integer, ByVal
messageCount As Integer) As TimeSpan
' Create an ActionBlock<int> that performs some work.
Dim workerBlock = New ActionBlock(Of Integer)(Function(millisecondsTimeout)
Pause(millisecondsTimeout), New ExecutionDataflowBlockOptions() With {.MaxDegreeOfParallelism =
maxDegreeOfParallelism})
maxDegreeOfParallelism})
' Simulate work by suspending the current thread.
' Specify a maximum degree of parallelism.

' Compute the time that it takes for several messages to


' flow through the dataflow block.

Dim stopwatch As New Stopwatch()


stopwatch.Start()

For i As Integer = 0 To messageCount - 1


workerBlock.Post(1000)
Next i
workerBlock.Complete()

' Wait for all messages to propagate through the network.


workerBlock.Completion.Wait()

' Stop the timer and return the elapsed number of milliseconds.
stopwatch.Stop()
Return stopwatch.Elapsed
End Function

Private Shared Function Pause(ByVal obj As Object)


Thread.Sleep(obj)
Return Nothing
End Function
Shared Sub Main(ByVal args() As String)
Dim processorCount As Integer = Environment.ProcessorCount
Dim messageCount As Integer = processorCount

' Print the number of processors on this computer.


Console.WriteLine("Processor count = {0}.", processorCount)

Dim elapsed As TimeSpan

' Perform two dataflow computations and print the elapsed


' time required for each.

' This call specifies a maximum degree of parallelism of 1.


' This causes the dataflow block to process messages serially.
elapsed = TimeDataflowComputations(1, messageCount)
Console.WriteLine("Degree of parallelism = {0}; message count = {1}; " & "elapsed time = {2}ms.", 1,
messageCount, CInt(Fix(elapsed.TotalMilliseconds)))

' Perform the computations again. This time, specify the number of
' processors as the maximum degree of parallelism. This causes
' multiple messages to be processed in parallel.
elapsed = TimeDataflowComputations(processorCount, messageCount)
Console.WriteLine("Degree of parallelism = {0}; message count = {1}; " & "elapsed time = {2}ms.",
processorCount, messageCount, CInt(Fix(elapsed.TotalMilliseconds)))
End Sub
End Class

' Sample output:


'Processor count = 4.
'Degree of parallelism = 1; message count = 4; elapsed time = 4032ms.
'Degree of parallelism = 4; message count = 4; elapsed time = 1001ms.
'

Programmation fiable
Par défaut, chaque bloc de flux de données prédéfini propage les messages dans l'ordre dans lequel ils sont reçus.
Bien que plusieurs messages soient traités simultanément quand vous spécifiez un degré maximum de
parallélisme supérieur 1, ils sont toujours propagés dans l'ordre dans lequel ils sont reçus.
Étant donné que la propriété MaxDegreeOfParallelism représente le degré maximal de parallélisme, le bloc de flux
de données peut s'exécuter avec un degré de parallélisme moindre spécifié par vos soins. Le bloc de flux de
données peut utiliser un degré de parallélisme moindre pour satisfaire ses exigences fonctionnelles ou pour
prendre en compte un manque de ressources système disponibles. Un bloc de flux de données ne choisit jamais
un degré de parallélisme supérieur à celui que vous spécifiez.

Voir aussi
Dataflow
Procédure : spécifier un planificateur de tâches dans
un bloc de dataflow
18/07/2020 • 14 minutes to read • Edit Online

Ce document montre comment associer un planificateur de tâches spécifique lorsque vous utilisez le flux de
données dans votre application. L’exemple utilise la classe
System.Threading.Tasks.ConcurrentExclusiveSchedulerPair dans une application Windows Forms pour indiquer
lorsque les tâches de lecture sont actives et lorsqu’une tâche d’écriture est active. Il utilise également la méthode
TaskScheduler.FromCurrentSynchronizationContext pour permettre à un bloc de flux de données de s'exécuter sur
le thread de l'interface utilisateur.

NOTE
La bibliothèque de flux de données TPL (espace de noms System.Threading.Tasks.Dataflow) n'est pas distribuée avec .NET.
Pour installer l’espace de noms System.Threading.Tasks.Dataflow dans Visual Studio, ouvrez votre projet, choisissez Gérer
les packages NuGet dans le menu Projet , puis recherchez en ligne le package System.Threading.Tasks.Dataflow .
Vous pouvez également l’installer à l’aide de l’interface CLI .NET Core en exécutant
dotnet add package System.Threading.Tasks.Dataflow .

Pour créer une Application Windows Forms


1. Créez un projet Application Windows Forms en Visual C# ou Visual Basic. Dans les étapes suivantes, le
projet est nommé WriterReadersWinForms .
2. Dans le concepteur de formulaires pour le formulaire principal, Form1.cs (Form1.vb pour Visual Basic),
ajoutez quatre contrôles CheckBox. Définissez la propriété Text sur Lecteur 1 pour checkBox1 , Lecteur 2
pour checkBox2 , Lecteur 3 pour checkBox3 , et Enregistreur pour checkBox4 . Définissez la propriété
Enabled pour chaque contrôle sur False .
3. Ajoutez un contrôle Timer au formulaire. Attribuez à la propriété Interval la valeur 2500 .

Ajout de fonctionnalités de flux de données


Cette section décrit comment créer des blocs de flux de données participant à l'application et comment associer
chacun d'entre eux à un planificateur de tâches.
Pour ajouter des fonctionnalités de flux de données à l'application
1. Dans votre projet, ajoutez une référence à System.Threading.Tasks.Dataflow.dll.
2. Vérifiez que Form1.cs (Form1.vb pour Visual Basic) contient les instructions using suivantes ( Imports en
Visual Basic).

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

3. Ajoutez des données membre BroadcastBlock<T> à la classe Form1 .

// Broadcasts values to an ActionBlock<int> object that is associated


// with each check box.
BroadcastBlock<int> broadcaster = new BroadcastBlock<int>(null);

' Broadcasts values to an ActionBlock<int> object that is associated


' with each check box.
Private broadcaster As New BroadcastBlock(Of Integer)(Nothing)

4. Dans le constructeur Form1 , après l'appel à InitializeComponent , créez un objet ActionBlock<TInput> qui
fait basculer l'état des objets CheckBox.

// Create an ActionBlock<CheckBox> object that toggles the state


// of CheckBox objects.
// Specifying the current synchronization context enables the
// action to run on the user-interface thread.
var toggleCheckBox = new ActionBlock<CheckBox>(checkBox =>
{
checkBox.Checked = !checkBox.Checked;
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

' Create an ActionBlock<CheckBox> object that toggles the state


' of CheckBox objects.
' Specifying the current synchronization context enables the
' action to run on the user-interface thread.
Dim toggleCheckBox = New ActionBlock(Of CheckBox)(Sub(checkBox) checkBox.Checked = Not
checkBox.Checked, New ExecutionDataflowBlockOptions With {.TaskScheduler =
TaskScheduler.FromCurrentSynchronizationContext()})

5. Dans le constructeur Form1 , créez un objet ConcurrentExclusiveSchedulerPair et quatre objets


ActionBlock<TInput>, un objet ActionBlock<TInput> pour chaque objet CheckBox. Pour chaque objet
ActionBlock<TInput>, spécifiez un objet ExecutionDataflowBlockOptions qui contient le jeu de propriétés
TaskScheduler affectées à la propriété ConcurrentScheduler pour les lecteurs, la propriété
ExclusiveScheduler pour l'enregistreur.
// Create a ConcurrentExclusiveSchedulerPair object.
// Readers will run on the concurrent part of the scheduler pair.
// The writer will run on the exclusive part of the scheduler pair.
var taskSchedulerPair = new ConcurrentExclusiveSchedulerPair();

// Create an ActionBlock<int> object for each reader CheckBox object.


// Each ActionBlock<int> object represents an action that can read
// from a resource in parallel to other readers.
// Specifying the concurrent part of the scheduler pair enables the
// reader to run in parallel to other actions that are managed by
// that scheduler.
var readerActions =
from checkBox in new CheckBox[] {checkBox1, checkBox2, checkBox3}
select new ActionBlock<int>(milliseconds =>
{
// Toggle the check box to the checked state.
toggleCheckBox.Post(checkBox);

// Perform the read action. For demonstration, suspend the current


// thread to simulate a lengthy read operation.
Thread.Sleep(milliseconds);

// Toggle the check box to the unchecked state.


toggleCheckBox.Post(checkBox);
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = taskSchedulerPair.ConcurrentScheduler
});

// Create an ActionBlock<int> object for the writer CheckBox object.


// This ActionBlock<int> object represents an action that writes to
// a resource, but cannot run in parallel to readers.
// Specifying the exclusive part of the scheduler pair enables the
// writer to run in exclusively with respect to other actions that are
// managed by the scheduler pair.
var writerAction = new ActionBlock<int>(milliseconds =>
{
// Toggle the check box to the checked state.
toggleCheckBox.Post(checkBox4);

// Perform the write action. For demonstration, suspend the current


// thread to simulate a lengthy write operation.
Thread.Sleep(milliseconds);

// Toggle the check box to the unchecked state.


toggleCheckBox.Post(checkBox4);
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = taskSchedulerPair.ExclusiveScheduler
});

// Link the broadcaster to each reader and writer block.


// The BroadcastBlock<T> class propagates values that it
// receives to all connected targets.
foreach (var readerAction in readerActions)
{
broadcaster.LinkTo(readerAction);
}
broadcaster.LinkTo(writerAction);
' Create a ConcurrentExclusiveSchedulerPair object.
' Readers will run on the concurrent part of the scheduler pair.
' The writer will run on the exclusive part of the scheduler pair.
Dim taskSchedulerPair = New ConcurrentExclusiveSchedulerPair()

' Create an ActionBlock<int> object for each reader CheckBox object.


' Each ActionBlock<int> object represents an action that can read
' from a resource in parallel to other readers.
' Specifying the concurrent part of the scheduler pair enables the
' reader to run in parallel to other actions that are managed by
' that scheduler.
Dim readerActions = From checkBox In New CheckBox() {checkBox1, checkBox2, checkBox3} _
Select New ActionBlock(Of Integer)(Sub(milliseconds)
' Toggle the check box to the checked state.
' Perform the read action. For demonstration, suspend the
current
' thread to simulate a lengthy read operation.
' Toggle the check box to the unchecked state.
toggleCheckBox.Post(checkBox)
Thread.Sleep(milliseconds)
toggleCheckBox.Post(checkBox)
End Sub, New ExecutionDataflowBlockOptions With
{.TaskScheduler = taskSchedulerPair.ConcurrentScheduler})

' Create an ActionBlock<int> object for the writer CheckBox object.


' This ActionBlock<int> object represents an action that writes to
' a resource, but cannot run in parallel to readers.
' Specifying the exclusive part of the scheduler pair enables the
' writer to run in exclusively with respect to other actions that are
' managed by the scheduler pair.
Dim writerAction = New ActionBlock(Of Integer)(Sub(milliseconds)
' Toggle the check box to the checked state.
' Perform the write action. For demonstration,
suspend the current
' thread to simulate a lengthy write operation.
' Toggle the check box to the unchecked state.
toggleCheckBox.Post(checkBox4)
Thread.Sleep(milliseconds)
toggleCheckBox.Post(checkBox4)
End Sub, New ExecutionDataflowBlockOptions With
{.TaskScheduler = taskSchedulerPair.ExclusiveScheduler})

' Link the broadcaster to each reader and writer block.


' The BroadcastBlock<T> class propagates values that it
' receives to all connected targets.
For Each readerAction In readerActions
broadcaster.LinkTo(readerAction)
Next readerAction
broadcaster.LinkTo(writerAction)

6. Dans le constructeur Form1 , démarrez l'objet Timer.

// Start the timer.


timer1.Start();

' Start the timer.


timer1.Start()

7. Dans le concepteur de formulaires pour le formulaire principal, créez un gestionnaire d'événements pour
l'événement Tick pour la minuterie.
8. Implémentez l'événement Tick pour la minuterie.
// Event handler for the timer.
private void timer1_Tick(object sender, EventArgs e)
{
// Post a value to the broadcaster. The broadcaster
// sends this message to each target.
broadcaster.Post(1000);
}

' Event handler for the timer.


Private Sub timer1_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles timer1.Tick
' Post a value to the broadcaster. The broadcaster
' sends this message to each target.
broadcaster.Post(1000)
End Sub

Étant donné que le bloc de flux de données toggleCheckBox agit sur l'interface utilisateur, il est important que
cette action se produisent sur le thread de l'interface utilisateur. Pour ce faire, lors de la construction, cet objet est
un objet ExecutionDataflowBlockOptions qui contient la propriété TaskScheduler définie sur
TaskScheduler.FromCurrentSynchronizationContext. La méthode FromCurrentSynchronizationContext crée un
objet TaskScheduler qui effectue le travail dans le contexte actuel de synchronisation. Étant donné que le
constructeur Form1 est appelé depuis le thread de l'interface utilisateur, l'action du bloc de flux de données
toggleCheckBox fonctionne aussi sur le thread de l'interface utilisateur.

Cet exemple utilise également une classe ConcurrentExclusiveSchedulerPair pour permettre à des blocs de flux de
données d'agir simultanément, et à un autre bloc de flux de données d'agir de façon exclusive par rapport à tous
les autres blocs de flux de données qui s'exécutent sur le même objet ConcurrentExclusiveSchedulerPair. Cette
technique est utile lorsque plusieurs blocs de flux de données partagent une ressource et que certains requièrent
un accès exclusif à cette ressource, car elle élimine le besoin de synchroniser manuellement l'accès à cette
ressource. L'élimination de la synchronisation manuelle peut rendre le code plus efficace.

Exemple
L’exemple suivant montre le code complet pour Form1.cs (Form1.vb pour Visual Basic).

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;

namespace WriterReadersWinForms
{
public partial class Form1 : Form
{
// Broadcasts values to an ActionBlock<int> object that is associated
// with each check box.
BroadcastBlock<int> broadcaster = new BroadcastBlock<int>(null);

public Form1()
{
InitializeComponent();

// Create an ActionBlock<CheckBox> object that toggles the state


// of CheckBox objects.
// Specifying the current synchronization context enables the
// action to run on the user-interface thread.
var toggleCheckBox = new ActionBlock<CheckBox>(checkBox =>
{
checkBox.Checked = !checkBox.Checked;
checkBox.Checked = !checkBox.Checked;
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
});

// Create a ConcurrentExclusiveSchedulerPair object.


// Readers will run on the concurrent part of the scheduler pair.
// The writer will run on the exclusive part of the scheduler pair.
var taskSchedulerPair = new ConcurrentExclusiveSchedulerPair();

// Create an ActionBlock<int> object for each reader CheckBox object.


// Each ActionBlock<int> object represents an action that can read
// from a resource in parallel to other readers.
// Specifying the concurrent part of the scheduler pair enables the
// reader to run in parallel to other actions that are managed by
// that scheduler.
var readerActions =
from checkBox in new CheckBox[] {checkBox1, checkBox2, checkBox3}
select new ActionBlock<int>(milliseconds =>
{
// Toggle the check box to the checked state.
toggleCheckBox.Post(checkBox);

// Perform the read action. For demonstration, suspend the current


// thread to simulate a lengthy read operation.
Thread.Sleep(milliseconds);

// Toggle the check box to the unchecked state.


toggleCheckBox.Post(checkBox);
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = taskSchedulerPair.ConcurrentScheduler
});

// Create an ActionBlock<int> object for the writer CheckBox object.


// This ActionBlock<int> object represents an action that writes to
// a resource, but cannot run in parallel to readers.
// Specifying the exclusive part of the scheduler pair enables the
// writer to run in exclusively with respect to other actions that are
// managed by the scheduler pair.
var writerAction = new ActionBlock<int>(milliseconds =>
{
// Toggle the check box to the checked state.
toggleCheckBox.Post(checkBox4);

// Perform the write action. For demonstration, suspend the current


// thread to simulate a lengthy write operation.
Thread.Sleep(milliseconds);

// Toggle the check box to the unchecked state.


toggleCheckBox.Post(checkBox4);
},
new ExecutionDataflowBlockOptions
{
TaskScheduler = taskSchedulerPair.ExclusiveScheduler
});

// Link the broadcaster to each reader and writer block.


// The BroadcastBlock<T> class propagates values that it
// receives to all connected targets.
foreach (var readerAction in readerActions)
{
broadcaster.LinkTo(readerAction);
}
broadcaster.LinkTo(writerAction);

// Start the timer.


// Start the timer.
timer1.Start();
}

// Event handler for the timer.


private void timer1_Tick(object sender, EventArgs e)
{
// Post a value to the broadcaster. The broadcaster
// sends this message to each target.
broadcaster.Post(1000);
}
}
}

Imports System.Threading
Imports System.Threading.Tasks
Imports System.Threading.Tasks.Dataflow

Namespace WriterReadersWinForms
Partial Public Class Form1
Inherits Form
' Broadcasts values to an ActionBlock<int> object that is associated
' with each check box.
Private broadcaster As New BroadcastBlock(Of Integer)(Nothing)

Public Sub New()


InitializeComponent()

' Create an ActionBlock<CheckBox> object that toggles the state


' of CheckBox objects.
' Specifying the current synchronization context enables the
' action to run on the user-interface thread.
Dim toggleCheckBox = New ActionBlock(Of CheckBox)(Sub(checkBox) checkBox.Checked = Not
checkBox.Checked, New ExecutionDataflowBlockOptions With {.TaskScheduler =
TaskScheduler.FromCurrentSynchronizationContext()})

' Create a ConcurrentExclusiveSchedulerPair object.


' Readers will run on the concurrent part of the scheduler pair.
' The writer will run on the exclusive part of the scheduler pair.
Dim taskSchedulerPair = New ConcurrentExclusiveSchedulerPair()

' Create an ActionBlock<int> object for each reader CheckBox object.


' Each ActionBlock<int> object represents an action that can read
' from a resource in parallel to other readers.
' Specifying the concurrent part of the scheduler pair enables the
' reader to run in parallel to other actions that are managed by
' that scheduler.
Dim readerActions = From checkBox In New CheckBox() {checkBox1, checkBox2, checkBox3} _
Select New ActionBlock(Of Integer)(Sub(milliseconds)
' Toggle the check box to the checked state.
' Perform the read action. For demonstration, suspend
the current
' thread to simulate a lengthy read operation.
' Toggle the check box to the unchecked state.
toggleCheckBox.Post(checkBox)
Thread.Sleep(milliseconds)
toggleCheckBox.Post(checkBox)
End Sub, New ExecutionDataflowBlockOptions
With {.TaskScheduler = taskSchedulerPair.ConcurrentScheduler})

' Create an ActionBlock<int> object for the writer CheckBox object.


' This ActionBlock<int> object represents an action that writes to
' a resource, but cannot run in parallel to readers.
' Specifying the exclusive part of the scheduler pair enables the
' writer to run in exclusively with respect to other actions that are
' managed by the scheduler pair.
Dim writerAction = New ActionBlock(Of Integer)(Sub(milliseconds)
Dim writerAction = New ActionBlock(Of Integer)(Sub(milliseconds)
' Toggle the check box to the checked state.
' Perform the write action. For demonstration,
suspend the current
' thread to simulate a lengthy write
operation.
' Toggle the check box to the unchecked state.
toggleCheckBox.Post(checkBox4)
Thread.Sleep(milliseconds)
toggleCheckBox.Post(checkBox4)
End Sub, New ExecutionDataflowBlockOptions With
{.TaskScheduler = taskSchedulerPair.ExclusiveScheduler})

' Link the broadcaster to each reader and writer block.


' The BroadcastBlock<T> class propagates values that it
' receives to all connected targets.
For Each readerAction In readerActions
broadcaster.LinkTo(readerAction)
Next readerAction
broadcaster.LinkTo(writerAction)

' Start the timer.


timer1.Start()
End Sub

' Event handler for the timer.


Private Sub timer1_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles timer1.Tick
' Post a value to the broadcaster. The broadcaster
' sends this message to each target.
broadcaster.Post(1000)
End Sub
End Class
End Namespace

Voir aussi
Dataflow
Procédure pas à pas : Utiliser BatchBlock et
BatchedJoinBlock pour améliorer l’efficacité
18/07/2020 • 34 minutes to read • Edit Online

La bibliothèque de flux de données TPL comporte les classes System.Threading.Tasks.Dataflow.BatchBlock<T> et


System.Threading.Tasks.Dataflow.BatchedJoinBlock<T1,T2>, qui permettent de recevoir et de mettre en mémoire
tampon des données provenant d’une ou plusieurs sources, puis de les propager sous la forme d’une seule et
même collection. Ce mécanisme de traitement par lot est utile pour collecter des données provenant d’une ou
plusieurs sources, puis pour traiter par lot plusieurs éléments de données. Prenons par exemple une application
qui utilise un flux de données pour insérer des enregistrements dans une base de données. Cette opération est
plus efficace si plusieurs éléments sont insérés en même temps, plutôt qu’un à la fois successivement. Ce
document explique comment utiliser la classe BatchBlock<T> afin d’améliorer l’efficacité de ces opérations
d’insertion en base de données. Il montre également comment se servir de la classe BatchedJoinBlock<T1,T2>
pour capturer les résultats et toutes les exceptions qui se produisent quand le programme lit des données à partir
d’une base de données.

NOTE
La bibliothèque de flux de données TPL (espace de noms System.Threading.Tasks.Dataflow) n'est pas distribuée avec .NET.
Pour installer l’espace de noms System.Threading.Tasks.Dataflow dans Visual Studio, ouvrez votre projet, choisissez Gérer
les packages NuGet dans le menu Projet , puis recherchez en ligne le package System.Threading.Tasks.Dataflow .
Vous pouvez également l’installer à l’aide de l’interface CLI .NET Core en exécutant
dotnet add package System.Threading.Tasks.Dataflow .

Prérequis
1. Lisez la section Blocs de jointure du document Flux de données avant de commencer cette procédure pas à
pas.
2. Vérifiez que vous disposez d’une copie de la base de données Northwind, Northwind.sdf, sur votre
ordinateur. Ce fichier se trouve généralement dans le dossier %Program Files%\Microsoft SQL Server
Compact Edition\v3.5\Samples\.

IMPORTANT
Dans certaines versions de Windows, il n’est pas possible de se connecter à Northwind.sdf si Visual Studio est en
mode non administrateur. Pour vous connecter à Northwind.sdf, démarrez Visual Studio ou une invite de
commandes développeur pour Visual Studio en mode Exécuter en tant qu’administrateur .

Cette procédure pas à pas contient les sections suivantes :


Créer l'application console
Définir la classe Employee
Définir des opérations de base de données Employee
Ajout de données d’employés à la base de données sans utilisation de la mise en mémoire tampon
Utiliser la mise en mémoire tampon pour ajouter des données sur les employés à la base de données
Utiliser la jointure en mémoire tampon pour lire des données sur les employés à partir de la base de
données
Exemple complet

Créer l'application console


1. Dans Visual Studio, créez un projet d' application console Visual C# ou Visual Basic. Dans ce document,
le projet est nommé DataflowBatchDatabase .
2. Dans votre projet, ajoutez une référence à System.Data.SqlServerCe.dll et une autre à
System.Threading.Tasks.Dataflow.dll.
3. Vérifiez que Form1.cs (Form1.vb pour Visual Basic) contient les instructions using suivantes ( Imports en
Visual Basic).

using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks.Dataflow;

Imports System.Collections.Generic
Imports System.Data.SqlServerCe
Imports System.Diagnostics
Imports System.IO
Imports System.Threading.Tasks.Dataflow

4. Ajoutez les membres de données suivants à la classe Program .

// The number of employees to add to the database.


// TODO: Change this value to experiment with different numbers of
// employees to insert into the database.
static readonly int insertCount = 256;

// The size of a single batch of employees to add to the database.


// TODO: Change this value to experiment with different batch sizes.
static readonly int insertBatchSize = 96;

// The source database file.


// TODO: Change this value if Northwind.sdf is at a different location
// on your computer.
static readonly string sourceDatabase =
@"C:\Program Files\Microsoft SQL Server Compact Edition\v3.5\Samples\Northwind.sdf";

// TODO: Change this value if you require a different temporary location.


static readonly string scratchDatabase =
@"C:\Temp\Northwind.sdf";
' The number of employees to add to the database.
' TODO: Change this value to experiment with different numbers of
' employees to insert into the database.
Private Shared ReadOnly insertCount As Integer = 256

' The size of a single batch of employees to add to the database.


' TODO: Change this value to experiment with different batch sizes.
Private Shared ReadOnly insertBatchSize As Integer = 96

' The source database file.


' TODO: Change this value if Northwind.sdf is at a different location
' on your computer.
Private Shared ReadOnly sourceDatabase As String = "C:\Program Files\Microsoft SQL Server Compact
Edition\v3.5\Samples\Northwind.sdf"

' TODO: Change this value if you require a different temporary location.
Private Shared ReadOnly scratchDatabase As String = "C:\Temp\Northwind.sdf"

Définir la classe Employee


Ajoutez la classe Employee à la classe Program .

// Describes an employee. Each property maps to a


// column in the Employees table in the Northwind database.
// For brevity, the Employee class does not contain
// all columns from the Employees table.
class Employee
{
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }

// A random number generator that helps tp generate


// Employee property values.
static Random rand = new Random(42);

// Possible random first names.


static readonly string[] firstNames = { "Tom", "Mike", "Ruth", "Bob", "John" };
// Possible random last names.
static readonly string[] lastNames = { "Jones", "Smith", "Johnson", "Walker" };

// Creates an Employee object that contains random


// property values.
public static Employee Random()
{
return new Employee
{
EmployeeID = -1,
LastName = lastNames[rand.Next() % lastNames.Length],
FirstName = firstNames[rand.Next() % firstNames.Length]
};
}
}
' Describes an employee. Each property maps to a
' column in the Employees table in the Northwind database.
' For brevity, the Employee class does not contain
' all columns from the Employees table.
Private Class Employee
Public Property EmployeeID() As Integer
Public Property LastName() As String
Public Property FirstName() As String

' A random number generator that helps tp generate


' Employee property values.
Private Shared rand As New Random(42)

' Possible random first names.


Private Shared ReadOnly firstNames() As String = {"Tom", "Mike", "Ruth", "Bob", "John"}
' Possible random last names.
Private Shared ReadOnly lastNames() As String = {"Jones", "Smith", "Johnson", "Walker"}

' Creates an Employee object that contains random


' property values.
Public Shared Function Random() As Employee
Return New Employee With {.EmployeeID = -1, .LastName = lastNames(rand.Next() Mod lastNames.Length),
.FirstName = firstNames(rand.Next() Mod firstNames.Length)}
End Function
End Class

La classe Employee contient trois propriétés : EmployeeID , LastName et FirstName . Elles correspondent aux
colonnes Employee ID , Last Name et First Name de la table Employees dans la base de données Northwind. Pour
cette démonstration, la classe Employee définit également la méthode Random , ce qui crée un objet Employee
ayant des valeurs aléatoires pour propriétés.

Définir des opérations de base de données Employee


Ajoutez les méthodes InsertEmployees , GetEmployeeCount et GetEmployeeID à la classe Program .

// Adds new employee records to the database.


static void InsertEmployees(Employee[] employees, string connectionString)
{
using (SqlCeConnection connection =
new SqlCeConnection(connectionString))
{
try
{
// Create the SQL command.
SqlCeCommand command = new SqlCeCommand(
"INSERT INTO Employees ([Last Name], [First Name])" +
"VALUES (@lastName, @firstName)",
connection);

connection.Open();
for (int i = 0; i < employees.Length; i++)
{
// Set parameters.
command.Parameters.Clear();
command.Parameters.Add("@lastName", employees[i].LastName);
command.Parameters.Add("@firstName", employees[i].FirstName);

// Execute the command.


command.ExecuteNonQuery();
}
}
finally
{
connection.Close();
connection.Close();
}
}
}

// Retrieves the number of entries in the Employees table in


// the Northwind database.
static int GetEmployeeCount(string connectionString)
{
int result = 0;
using (SqlCeConnection sqlConnection =
new SqlCeConnection(connectionString))
{
SqlCeCommand sqlCommand = new SqlCeCommand(
"SELECT COUNT(*) FROM Employees", sqlConnection);

sqlConnection.Open();
try
{
result = (int)sqlCommand.ExecuteScalar();
}
finally
{
sqlConnection.Close();
}
}
return result;
}

// Retrieves the ID of the first employee that has the provided name.
static int GetEmployeeID(string lastName, string firstName,
string connectionString)
{
using (SqlCeConnection connection =
new SqlCeConnection(connectionString))
{
SqlCeCommand command = new SqlCeCommand(
string.Format(
"SELECT [Employee ID] FROM Employees " +
"WHERE [Last Name] = '{0}' AND [First Name] = '{1}'",
lastName, firstName),
connection);

connection.Open();
try
{
return (int)command.ExecuteScalar();
}
finally
{
connection.Close();
}
}
}
' Adds new employee records to the database.
Private Shared Sub InsertEmployees(ByVal employees() As Employee, ByVal connectionString As String)
Using connection As New SqlCeConnection(connectionString)
Try
' Create the SQL command.
Dim command As New SqlCeCommand("INSERT INTO Employees ([Last Name], [First Name])" & "VALUES
(@lastName, @firstName)", connection)

connection.Open()
For i As Integer = 0 To employees.Length - 1
' Set parameters.
command.Parameters.Clear()
command.Parameters.Add("@lastName", employees(i).LastName)
command.Parameters.Add("@firstName", employees(i).FirstName)

' Execute the command.


command.ExecuteNonQuery()
Next i
Finally
connection.Close()
End Try
End Using
End Sub

' Retrieves the number of entries in the Employees table in


' the Northwind database.
Private Shared Function GetEmployeeCount(ByVal connectionString As String) As Integer
Dim result As Integer = 0
Using sqlConnection As New SqlCeConnection(connectionString)
Dim sqlCommand As New SqlCeCommand("SELECT COUNT(*) FROM Employees", sqlConnection)

sqlConnection.Open()
Try
result = CInt(Fix(sqlCommand.ExecuteScalar()))
Finally
sqlConnection.Close()
End Try
End Using
Return result
End Function

' Retrieves the ID of the first employee that has the provided name.
Private Shared Function GetEmployeeID(ByVal lastName As String, ByVal firstName As String, ByVal
connectionString As String) As Integer
Using connection As New SqlCeConnection(connectionString)
Dim command As New SqlCeCommand(String.Format("SELECT [Employee ID] FROM Employees " & "WHERE [Last
Name] = '{0}' AND [First Name] = '{1}'", lastName, firstName), connection)

connection.Open()
Try
Return CInt(Fix(command.ExecuteScalar()))
Finally
connection.Close()
End Try
End Using
End Function

La méthode InsertEmployees ajoute de nouveaux enregistrements d’employés à la base de données. La méthode


GetEmployeeCount récupère le nombre d’entrées de la table Employees . La méthode GetEmployeeID extrait
l’identificateur du premier employé possédant le nom indiqué. Chacune de ces méthodes prend une chaîne de
connexion à la base de données Northwind et utilise des fonctionnalités de l’espace de noms
System.Data.SqlServerCe pour communiquer avec la base de données.

Ajouter des données sur les employés à la base de données sans


utiliser la mise en mémoire tampon
Ajoutez les méthodes AddEmployees et PostRandomEmployees à la classe Program .

// Posts random Employee data to the provided target block.


static void PostRandomEmployees(ITargetBlock<Employee> target, int count)
{
Console.WriteLine("Adding {0} entries to Employee table...", count);

for (int i = 0; i < count; i++)


{
target.Post(Employee.Random());
}
}

// Adds random employee data to the database by using dataflow.


static void AddEmployees(string connectionString, int count)
{
// Create an ActionBlock<Employee> object that adds a single
// employee entry to the database.
var insertEmployee = new ActionBlock<Employee>(e =>
InsertEmployees(new Employee[] { e }, connectionString));

// Post several random Employee objects to the dataflow block.


PostRandomEmployees(insertEmployee, count);

// Set the dataflow block to the completed state and wait for
// all insert operations to complete.
insertEmployee.Complete();
insertEmployee.Completion.Wait();
}

' Posts random Employee data to the provided target block.


Private Shared Sub PostRandomEmployees(ByVal target As ITargetBlock(Of Employee), ByVal count As Integer)
Console.WriteLine("Adding {0} entries to Employee table...", count)

For i As Integer = 0 To count - 1


target.Post(Employee.Random())
Next i
End Sub

' Adds random employee data to the database by using dataflow.


Private Shared Sub AddEmployees(ByVal connectionString As String, ByVal count As Integer)
' Create an ActionBlock<Employee> object that adds a single
' employee entry to the database.
Dim insertEmployee = New ActionBlock(Of Employee)(Sub(e) InsertEmployees(New Employee() {e},
connectionString))

' Post several random Employee objects to the dataflow block.


PostRandomEmployees(insertEmployee, count)

' Set the dataflow block to the completed state and wait for
' all insert operations to complete.
insertEmployee.Complete()
insertEmployee.Completion.Wait()
End Sub

La méthode AddEmployees ajoute des données aléatoires sur les employés à la base de données à l’aide d’un flux
de données. Elle crée un objet ActionBlock<TInput> qui appelle la méthode InsertEmployees pour ajouter une
entrée d’employé à la base de données. La méthode AddEmployees appelle ensuite la méthode
PostRandomEmployees pour publier plusieurs objets Employee sur l’objet ActionBlock<TInput>. La méthode
AddEmployees attend alors la fin de toutes les opérations d’insertion.
Utiliser la mise en mémoire tampon pour ajouter des données sur les
employés à la base de données
Ajoutez la méthode AddEmployeesBatched à la classe Program .

// Adds random employee data to the database by using dataflow.


// This method is similar to AddEmployees except that it uses batching
// to add multiple employees to the database at a time.
static void AddEmployeesBatched(string connectionString, int batchSize,
int count)
{
// Create a BatchBlock<Employee> that holds several Employee objects and
// then propagates them out as an array.
var batchEmployees = new BatchBlock<Employee>(batchSize);

// Create an ActionBlock<Employee[]> object that adds multiple


// employee entries to the database.
var insertEmployees = new ActionBlock<Employee[]>(a =>
InsertEmployees(a, connectionString));

// Link the batch block to the action block.


batchEmployees.LinkTo(insertEmployees);

// When the batch block completes, set the action block also to complete.
batchEmployees.Completion.ContinueWith(delegate { insertEmployees.Complete(); });

// Post several random Employee objects to the batch block.


PostRandomEmployees(batchEmployees, count);

// Set the batch block to the completed state and wait for
// all insert operations to complete.
batchEmployees.Complete();
insertEmployees.Completion.Wait();
}

' Adds random employee data to the database by using dataflow.


' This method is similar to AddEmployees except that it uses batching
' to add multiple employees to the database at a time.
Private Shared Sub AddEmployeesBatched(ByVal connectionString As String, ByVal batchSize As Integer, ByVal
count As Integer)
' Create a BatchBlock<Employee> that holds several Employee objects and
' then propagates them out as an array.
Dim batchEmployees = New BatchBlock(Of Employee)(batchSize)

' Create an ActionBlock<Employee[]> object that adds multiple


' employee entries to the database.
Dim insertEmployees = New ActionBlock(Of Employee())(Sub(a) Program.InsertEmployees(a, connectionString))

' Link the batch block to the action block.


batchEmployees.LinkTo(insertEmployees)

' When the batch block completes, set the action block also to complete.
batchEmployees.Completion.ContinueWith(Sub() insertEmployees.Complete())

' Post several random Employee objects to the batch block.


PostRandomEmployees(batchEmployees, count)

' Set the batch block to the completed state and wait for
' all insert operations to complete.
batchEmployees.Complete()
insertEmployees.Completion.Wait()
End Sub
Cette méthode s’apparente à AddEmployees , sauf qu’elle utilise également la classe BatchBlock<T> pour mettre en
mémoire tampon plusieurs objets Employee avant de les envoyer vers l’objet ActionBlock<TInput>. Dans la
mesure où la classe BatchBlock<T> propage plusieurs éléments sous forme de collection, l’objet
ActionBlock<TInput> est modifié pour fonctionner sur un tableau d’objets Employee . Comme la méthode
AddEmployees , AddEmployeesBatched appelle la méthode PostRandomEmployees pour publier plusieurs objets
Employee , AddEmployeesBatched mais cette fois sur l’objet BatchBlock<T>. Elle AddEmployeesBatched attend
également la fin de toutes les opérations d’insertion.

Utiliser la jointure en mémoire tampon pour lire des données sur les
employés à partir de la base de données
Ajoutez la méthode GetRandomEmployees à la classe Program .

// Displays information about several random employees to the console.


static void GetRandomEmployees(string connectionString, int batchSize,
int count)
{
// Create a BatchedJoinBlock<Employee, Exception> object that holds
// both employee and exception data.
var selectEmployees = new BatchedJoinBlock<Employee, Exception>(batchSize);

// Holds the total number of exceptions that occurred.


int totalErrors = 0;

// Create an action block that prints employee and error information


// to the console.
var printEmployees =
new ActionBlock<Tuple<IList<Employee>, IList<Exception>>>(data =>
{
// Print information about the employees in this batch.
Console.WriteLine("Received a batch...");
foreach (Employee e in data.Item1)
{
Console.WriteLine("Last={0} First={1} ID={2}",
e.LastName, e.FirstName, e.EmployeeID);
}

// Print the error count for this batch.


Console.WriteLine("There were {0} errors in this batch...",
data.Item2.Count);

// Update total error count.


totalErrors += data.Item2.Count;
});

// Link the batched join block to the action block.


selectEmployees.LinkTo(printEmployees);

// When the batched join block completes, set the action block also to complete.
selectEmployees.Completion.ContinueWith(delegate { printEmployees.Complete(); });

// Try to retrieve the ID for several random employees.


Console.WriteLine("Selecting random entries from Employees table...");
for (int i = 0; i < count; i++)
{
try
{
// Create a random employee.
Employee e = Employee.Random();

// Try to retrieve the ID for the employee from the database.


e.EmployeeID = GetEmployeeID(e.LastName, e.FirstName, connectionString);

// Post the Employee object to the Employee target of


// Post the Employee object to the Employee target of
// the batched join block.
selectEmployees.Target1.Post(e);
}
catch (NullReferenceException e)
{
// GetEmployeeID throws NullReferenceException when there is
// no such employee with the given name. When this happens,
// post the Exception object to the Exception target of
// the batched join block.
selectEmployees.Target2.Post(e);
}
}

// Set the batched join block to the completed state and wait for
// all retrieval operations to complete.
selectEmployees.Complete();
printEmployees.Completion.Wait();

// Print the total error count.


Console.WriteLine("Finished. There were {0} total errors.", totalErrors);
}

' Displays information about several random employees to the console.


Private Shared Sub GetRandomEmployees(ByVal connectionString As String, ByVal batchSize As Integer, ByVal
count As Integer)
' Create a BatchedJoinBlock<Employee, Exception> object that holds
' both employee and exception data.
Dim selectEmployees = New BatchedJoinBlock(Of Employee, Exception)(batchSize)

' Holds the total number of exceptions that occurred.


Dim totalErrors As Integer = 0

' Create an action block that prints employee and error information
' to the console.
Dim printEmployees = New ActionBlock(Of Tuple(Of IList(Of Employee), IList(Of Exception)))(Sub(data)
' Print
information about the employees in this batch.
' Print
the error count for this batch.
' Update
total error count.

Console.WriteLine("Received a batch...")
For Each e
As Employee In data.Item1

Console.WriteLine("Last={0} First={1} ID={2}", e.LastName, e.FirstName, e.EmployeeID)


Next e

Console.WriteLine("There were {0} errors in this batch...", data.Item2.Count)

totalErrors += data.Item2.Count
End Sub)

' Link the batched join block to the action block.


selectEmployees.LinkTo(printEmployees)

' When the batched join block completes, set the action block also to complete.
selectEmployees.Completion.ContinueWith(Sub() printEmployees.Complete())

' Try to retrieve the ID for several random employees.


Console.WriteLine("Selecting random entries from Employees table...")
For i As Integer = 0 To count - 1
Try
' Create a random employee.
Dim e As Employee = Employee.Random()
' Try to retrieve the ID for the employee from the database.
e.EmployeeID = GetEmployeeID(e.LastName, e.FirstName, connectionString)

' Post the Employee object to the Employee target of


' the batched join block.
selectEmployees.Target1.Post(e)
Catch e As NullReferenceException
' GetEmployeeID throws NullReferenceException when there is
' no such employee with the given name. When this happens,
' post the Exception object to the Exception target of
' the batched join block.
selectEmployees.Target2.Post(e)
End Try
Next i

' Set the batched join block to the completed state and wait for
' all retrieval operations to complete.
selectEmployees.Complete()
printEmployees.Completion.Wait()

' Print the total error count.


Console.WriteLine("Finished. There were {0} total errors.", totalErrors)
End Sub

Cette méthode imprime des informations sur des employés pris au hasard dans la console. Elle crée plusieurs
objets Employee aléatoires et appelle la méthode GetEmployeeID pour récupérer l’identificateur unique de chacun
d’entre eux. Étant donné que la méthode GetEmployeeID lève une exception si aucun employé ne correspond au
prénom et au nom donnés, la méthode GetRandomEmployees utilise la classe BatchedJoinBlock<T1,T2> pour
stocker des objets Employee pour les appels à GetEmployeeID qui ont abouti et des objets System.Exception pour
ceux qui ont échoué. L’objet ActionBlock<TInput> de cet exemple fonctionne sur un objet Tuple<T1,T2> contenant
une liste d’objets Employee et une liste d’objets Exception. L’objet BatchedJoinBlock<T1,T2> propage ces données
lorsque le nombre total d’objets Exception et Employee reçus est égal à la taille du lot.

Exemple complet
L'exemple suivant montre le code complet. La méthode Main compare le temps nécessaire pour effectuer des
insertions en base de données par lot et sans mise en lots. Elle montre également l’utilisation d’une jointure en
mémoire tampon pour lire des données sur les employés à partir de la base de données, et signaler les erreurs.

using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks.Dataflow;

// Demonstrates how to use batched dataflow blocks to improve


// the performance of database operations.
namespace DataflowBatchDatabase
{
class Program
{
// The number of employees to add to the database.
// TODO: Change this value to experiment with different numbers of
// employees to insert into the database.
static readonly int insertCount = 256;

// The size of a single batch of employees to add to the database.


// TODO: Change this value to experiment with different batch sizes.
static readonly int insertBatchSize = 96;

// The source database file.


// TODO: Change this value if Northwind.sdf is at a different location
// TODO: Change this value if Northwind.sdf is at a different location
// on your computer.
static readonly string sourceDatabase =
@"C:\Program Files\Microsoft SQL Server Compact Edition\v3.5\Samples\Northwind.sdf";

// TODO: Change this value if you require a different temporary location.


static readonly string scratchDatabase =
@"C:\Temp\Northwind.sdf";

// Describes an employee. Each property maps to a


// column in the Employees table in the Northwind database.
// For brevity, the Employee class does not contain
// all columns from the Employees table.
class Employee
{
public int EmployeeID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }

// A random number generator that helps tp generate


// Employee property values.
static Random rand = new Random(42);

// Possible random first names.


static readonly string[] firstNames = { "Tom", "Mike", "Ruth", "Bob", "John" };
// Possible random last names.
static readonly string[] lastNames = { "Jones", "Smith", "Johnson", "Walker" };

// Creates an Employee object that contains random


// property values.
public static Employee Random()
{
return new Employee
{
EmployeeID = -1,
LastName = lastNames[rand.Next() % lastNames.Length],
FirstName = firstNames[rand.Next() % firstNames.Length]
};
}
}

// Adds new employee records to the database.


static void InsertEmployees(Employee[] employees, string connectionString)
{
using (SqlCeConnection connection =
new SqlCeConnection(connectionString))
{
try
{
// Create the SQL command.
SqlCeCommand command = new SqlCeCommand(
"INSERT INTO Employees ([Last Name], [First Name])" +
"VALUES (@lastName, @firstName)",
connection);

connection.Open();
for (int i = 0; i < employees.Length; i++)
{
// Set parameters.
command.Parameters.Clear();
command.Parameters.Add("@lastName", employees[i].LastName);
command.Parameters.Add("@firstName", employees[i].FirstName);

// Execute the command.


command.ExecuteNonQuery();
}
}
finally
{
connection.Close();
connection.Close();
}
}
}

// Retrieves the number of entries in the Employees table in


// the Northwind database.
static int GetEmployeeCount(string connectionString)
{
int result = 0;
using (SqlCeConnection sqlConnection =
new SqlCeConnection(connectionString))
{
SqlCeCommand sqlCommand = new SqlCeCommand(
"SELECT COUNT(*) FROM Employees", sqlConnection);

sqlConnection.Open();
try
{
result = (int)sqlCommand.ExecuteScalar();
}
finally
{
sqlConnection.Close();
}
}
return result;
}

// Retrieves the ID of the first employee that has the provided name.
static int GetEmployeeID(string lastName, string firstName,
string connectionString)
{
using (SqlCeConnection connection =
new SqlCeConnection(connectionString))
{
SqlCeCommand command = new SqlCeCommand(
string.Format(
"SELECT [Employee ID] FROM Employees " +
"WHERE [Last Name] = '{0}' AND [First Name] = '{1}'",
lastName, firstName),
connection);

connection.Open();
try
{
return (int)command.ExecuteScalar();
}
finally
{
connection.Close();
}
}
}

// Posts random Employee data to the provided target block.


static void PostRandomEmployees(ITargetBlock<Employee> target, int count)
{
Console.WriteLine("Adding {0} entries to Employee table...", count);

for (int i = 0; i < count; i++)


{
target.Post(Employee.Random());
}
}

// Adds random employee data to the database by using dataflow.


static void AddEmployees(string connectionString, int count)
{
// Create an ActionBlock<Employee> object that adds a single
// Create an ActionBlock<Employee> object that adds a single
// employee entry to the database.
var insertEmployee = new ActionBlock<Employee>(e =>
InsertEmployees(new Employee[] { e }, connectionString));

// Post several random Employee objects to the dataflow block.


PostRandomEmployees(insertEmployee, count);

// Set the dataflow block to the completed state and wait for
// all insert operations to complete.
insertEmployee.Complete();
insertEmployee.Completion.Wait();
}

// Adds random employee data to the database by using dataflow.


// This method is similar to AddEmployees except that it uses batching
// to add multiple employees to the database at a time.
static void AddEmployeesBatched(string connectionString, int batchSize,
int count)
{
// Create a BatchBlock<Employee> that holds several Employee objects and
// then propagates them out as an array.
var batchEmployees = new BatchBlock<Employee>(batchSize);

// Create an ActionBlock<Employee[]> object that adds multiple


// employee entries to the database.
var insertEmployees = new ActionBlock<Employee[]>(a =>
InsertEmployees(a, connectionString));

// Link the batch block to the action block.


batchEmployees.LinkTo(insertEmployees);

// When the batch block completes, set the action block also to complete.
batchEmployees.Completion.ContinueWith(delegate { insertEmployees.Complete(); });

// Post several random Employee objects to the batch block.


PostRandomEmployees(batchEmployees, count);

// Set the batch block to the completed state and wait for
// all insert operations to complete.
batchEmployees.Complete();
insertEmployees.Completion.Wait();
}

// Displays information about several random employees to the console.


static void GetRandomEmployees(string connectionString, int batchSize,
int count)
{
// Create a BatchedJoinBlock<Employee, Exception> object that holds
// both employee and exception data.
var selectEmployees = new BatchedJoinBlock<Employee, Exception>(batchSize);

// Holds the total number of exceptions that occurred.


int totalErrors = 0;

// Create an action block that prints employee and error information


// to the console.
var printEmployees =
new ActionBlock<Tuple<IList<Employee>, IList<Exception>>>(data =>
{
// Print information about the employees in this batch.
Console.WriteLine("Received a batch...");
foreach (Employee e in data.Item1)
{
Console.WriteLine("Last={0} First={1} ID={2}",
e.LastName, e.FirstName, e.EmployeeID);
}

// Print the error count for this batch.


Console.WriteLine("There were {0} errors in this batch...",
Console.WriteLine("There were {0} errors in this batch...",
data.Item2.Count);

// Update total error count.


totalErrors += data.Item2.Count;
});

// Link the batched join block to the action block.


selectEmployees.LinkTo(printEmployees);

// When the batched join block completes, set the action block also to complete.
selectEmployees.Completion.ContinueWith(delegate { printEmployees.Complete(); });

// Try to retrieve the ID for several random employees.


Console.WriteLine("Selecting random entries from Employees table...");
for (int i = 0; i < count; i++)
{
try
{
// Create a random employee.
Employee e = Employee.Random();

// Try to retrieve the ID for the employee from the database.


e.EmployeeID = GetEmployeeID(e.LastName, e.FirstName, connectionString);

// Post the Employee object to the Employee target of


// the batched join block.
selectEmployees.Target1.Post(e);
}
catch (NullReferenceException e)
{
// GetEmployeeID throws NullReferenceException when there is
// no such employee with the given name. When this happens,
// post the Exception object to the Exception target of
// the batched join block.
selectEmployees.Target2.Post(e);
}
}

// Set the batched join block to the completed state and wait for
// all retrieval operations to complete.
selectEmployees.Complete();
printEmployees.Completion.Wait();

// Print the total error count.


Console.WriteLine("Finished. There were {0} total errors.", totalErrors);
}

static void Main(string[] args)


{
// Create a connection string for accessing the database.
// The connection string refers to the temporary database location.
string connectionString = string.Format(@"Data Source={0}",
scratchDatabase);

// Create a Stopwatch object to time database insert operations.


Stopwatch stopwatch = new Stopwatch();

// Start with a clean database file by copying the source database to


// the temporary location.
File.Copy(sourceDatabase, scratchDatabase, true);

// Demonstrate multiple insert operations without batching.


Console.WriteLine("Demonstrating non-batched database insert operations...");
Console.WriteLine("Original size of Employee table: {0}.",
GetEmployeeCount(connectionString));
stopwatch.Start();
AddEmployees(connectionString, insertCount);
stopwatch.Stop();
Console.WriteLine("New size of Employee table: {0}; elapsed insert time: {1} ms.",
GetEmployeeCount(connectionString), stopwatch.ElapsedMilliseconds);

Console.WriteLine();

// Start again with a clean database file.


File.Copy(sourceDatabase, scratchDatabase, true);

// Demonstrate multiple insert operations, this time with batching.


Console.WriteLine("Demonstrating batched database insert operations...");
Console.WriteLine("Original size of Employee table: {0}.",
GetEmployeeCount(connectionString));
stopwatch.Restart();
AddEmployeesBatched(connectionString, insertBatchSize, insertCount);
stopwatch.Stop();
Console.WriteLine("New size of Employee table: {0}; elapsed insert time: {1} ms.",
GetEmployeeCount(connectionString), stopwatch.ElapsedMilliseconds);

Console.WriteLine();

// Start again with a clean database file.


File.Copy(sourceDatabase, scratchDatabase, true);

// Demonstrate multiple retrieval operations with error reporting.


Console.WriteLine("Demonstrating batched join database select operations...");
// Add a small number of employees to the database.
AddEmployeesBatched(connectionString, insertBatchSize, 16);
// Query for random employees.
GetRandomEmployees(connectionString, insertBatchSize, 10);
}
}
}
/* Sample output:
Demonstrating non-batched database insert operations...
Original size of Employee table: 15.
Adding 256 entries to Employee table...
New size of Employee table: 271; elapsed insert time: 11035 ms.

Demonstrating batched database insert operations...


Original size of Employee table: 15.
Adding 256 entries to Employee table...
New size of Employee table: 271; elapsed insert time: 197 ms.

Demonstrating batched join database insert operations...


Adding 16 entries to Employee table...
Selecting items from Employee table...
Received a batch...
Last=Jones First=Tom ID=21
Last=Jones First=John ID=24
Last=Smith First=Tom ID=26
Last=Jones First=Tom ID=21
There were 4 errors in this batch...
Received a batch...
Last=Smith First=Tom ID=26
Last=Jones First=Mike ID=28
There were 0 errors in this batch...
Finished. There were 4 total errors.
*/

Imports System.Collections.Generic
Imports System.Data.SqlServerCe
Imports System.Diagnostics
Imports System.IO
Imports System.Threading.Tasks.Dataflow

' Demonstrates how to use batched dataflow blocks to improve


' the performance of database operations.
' the performance of database operations.
Namespace DataflowBatchDatabase
Friend Class Program
' The number of employees to add to the database.
' TODO: Change this value to experiment with different numbers of
' employees to insert into the database.
Private Shared ReadOnly insertCount As Integer = 256

' The size of a single batch of employees to add to the database.


' TODO: Change this value to experiment with different batch sizes.
Private Shared ReadOnly insertBatchSize As Integer = 96

' The source database file.


' TODO: Change this value if Northwind.sdf is at a different location
' on your computer.
Private Shared ReadOnly sourceDatabase As String = "C:\Program Files\Microsoft SQL Server Compact
Edition\v3.5\Samples\Northwind.sdf"

' TODO: Change this value if you require a different temporary location.
Private Shared ReadOnly scratchDatabase As String = "C:\Temp\Northwind.sdf"

' Describes an employee. Each property maps to a


' column in the Employees table in the Northwind database.
' For brevity, the Employee class does not contain
' all columns from the Employees table.
Private Class Employee
Public Property EmployeeID() As Integer
Public Property LastName() As String
Public Property FirstName() As String

' A random number generator that helps tp generate


' Employee property values.
Private Shared rand As New Random(42)

' Possible random first names.


Private Shared ReadOnly firstNames() As String = {"Tom", "Mike", "Ruth", "Bob", "John"}
' Possible random last names.
Private Shared ReadOnly lastNames() As String = {"Jones", "Smith", "Johnson", "Walker"}

' Creates an Employee object that contains random


' property values.
Public Shared Function Random() As Employee
Return New Employee With {.EmployeeID = -1, .LastName = lastNames(rand.Next() Mod
lastNames.Length), .FirstName = firstNames(rand.Next() Mod firstNames.Length)}
End Function
End Class

' Adds new employee records to the database.


Private Shared Sub InsertEmployees(ByVal employees() As Employee, ByVal connectionString As String)
Using connection As New SqlCeConnection(connectionString)
Try
' Create the SQL command.
Dim command As New SqlCeCommand("INSERT INTO Employees ([Last Name], [First Name])" &
"VALUES (@lastName, @firstName)", connection)

connection.Open()
For i As Integer = 0 To employees.Length - 1
' Set parameters.
command.Parameters.Clear()
command.Parameters.Add("@lastName", employees(i).LastName)
command.Parameters.Add("@firstName", employees(i).FirstName)

' Execute the command.


command.ExecuteNonQuery()
Next i
Finally
connection.Close()
End Try
End Using
End Sub

' Retrieves the number of entries in the Employees table in


' the Northwind database.
Private Shared Function GetEmployeeCount(ByVal connectionString As String) As Integer
Dim result As Integer = 0
Using sqlConnection As New SqlCeConnection(connectionString)
Dim sqlCommand As New SqlCeCommand("SELECT COUNT(*) FROM Employees", sqlConnection)

sqlConnection.Open()
Try
result = CInt(Fix(sqlCommand.ExecuteScalar()))
Finally
sqlConnection.Close()
End Try
End Using
Return result
End Function

' Retrieves the ID of the first employee that has the provided name.
Private Shared Function GetEmployeeID(ByVal lastName As String, ByVal firstName As String, ByVal
connectionString As String) As Integer
Using connection As New SqlCeConnection(connectionString)
Dim command As New SqlCeCommand(String.Format("SELECT [Employee ID] FROM Employees " & "WHERE
[Last Name] = '{0}' AND [First Name] = '{1}'", lastName, firstName), connection)

connection.Open()
Try
Return CInt(Fix(command.ExecuteScalar()))
Finally
connection.Close()
End Try
End Using
End Function

' Posts random Employee data to the provided target block.


Private Shared Sub PostRandomEmployees(ByVal target As ITargetBlock(Of Employee), ByVal count As
Integer)
Console.WriteLine("Adding {0} entries to Employee table...", count)

For i As Integer = 0 To count - 1


target.Post(Employee.Random())
Next i
End Sub

' Adds random employee data to the database by using dataflow.


Private Shared Sub AddEmployees(ByVal connectionString As String, ByVal count As Integer)
' Create an ActionBlock<Employee> object that adds a single
' employee entry to the database.
Dim insertEmployee = New ActionBlock(Of Employee)(Sub(e) InsertEmployees(New Employee() {e},
connectionString))

' Post several random Employee objects to the dataflow block.


PostRandomEmployees(insertEmployee, count)

' Set the dataflow block to the completed state and wait for
' all insert operations to complete.
insertEmployee.Complete()
insertEmployee.Completion.Wait()
End Sub

' Adds random employee data to the database by using dataflow.


' This method is similar to AddEmployees except that it uses batching
' to add multiple employees to the database at a time.
Private Shared Sub AddEmployeesBatched(ByVal connectionString As String, ByVal batchSize As Integer,
ByVal count As Integer)
' Create a BatchBlock<Employee> that holds several Employee objects and
' then propagates them out as an array.
Dim batchEmployees = New BatchBlock(Of Employee)(batchSize)
' Create an ActionBlock<Employee[]> object that adds multiple
' employee entries to the database.
Dim insertEmployees = New ActionBlock(Of Employee())(Sub(a) Program.InsertEmployees(a,
connectionString))

' Link the batch block to the action block.


batchEmployees.LinkTo(insertEmployees)

' When the batch block completes, set the action block also to complete.
batchEmployees.Completion.ContinueWith(Sub() insertEmployees.Complete())

' Post several random Employee objects to the batch block.


PostRandomEmployees(batchEmployees, count)

' Set the batch block to the completed state and wait for
' all insert operations to complete.
batchEmployees.Complete()
insertEmployees.Completion.Wait()
End Sub

' Displays information about several random employees to the console.


Private Shared Sub GetRandomEmployees(ByVal connectionString As String, ByVal batchSize As Integer,
ByVal count As Integer)
' Create a BatchedJoinBlock<Employee, Exception> object that holds
' both employee and exception data.
Dim selectEmployees = New BatchedJoinBlock(Of Employee, Exception)(batchSize)

' Holds the total number of exceptions that occurred.


Dim totalErrors As Integer = 0

' Create an action block that prints employee and error information
' to the console.
Dim printEmployees = New ActionBlock(Of Tuple(Of IList(Of Employee), IList(Of Exception)))
(Sub(data)
'
Print information about the employees in this batch.
'
Print the error count for this batch.
'
Update total error count.

Console.WriteLine("Received a batch...")

For Each e As Employee In data.Item1

Console.WriteLine("Last={0} First={1} ID={2}", e.LastName, e.FirstName, e.EmployeeID)

Next e

Console.WriteLine("There were {0} errors in this batch...", data.Item2.Count)

totalErrors += data.Item2.Count
End
Sub)

' Link the batched join block to the action block.


selectEmployees.LinkTo(printEmployees)

' When the batched join block completes, set the action block also to complete.
selectEmployees.Completion.ContinueWith(Sub() printEmployees.Complete())

' Try to retrieve the ID for several random employees.


Console.WriteLine("Selecting random entries from Employees table...")
For i As Integer = 0 To count - 1
Try
' Create a random employee.
Dim e As Employee = Employee.Random()
' Try to retrieve the ID for the employee from the database.
e.EmployeeID = GetEmployeeID(e.LastName, e.FirstName, connectionString)

' Post the Employee object to the Employee target of


' the batched join block.
selectEmployees.Target1.Post(e)
Catch e As NullReferenceException
' GetEmployeeID throws NullReferenceException when there is
' no such employee with the given name. When this happens,
' post the Exception object to the Exception target of
' the batched join block.
selectEmployees.Target2.Post(e)
End Try
Next i

' Set the batched join block to the completed state and wait for
' all retrieval operations to complete.
selectEmployees.Complete()
printEmployees.Completion.Wait()

' Print the total error count.


Console.WriteLine("Finished. There were {0} total errors.", totalErrors)
End Sub

Shared Sub Main(ByVal args() As String)


' Create a connection string for accessing the database.
' The connection string refers to the temporary database location.
Dim connectionString As String = String.Format("Data Source={0}", scratchDatabase)

' Create a Stopwatch object to time database insert operations.


Dim stopwatch As New Stopwatch()

' Start with a clean database file by copying the source database to
' the temporary location.
File.Copy(sourceDatabase, scratchDatabase, True)

' Demonstrate multiple insert operations without batching.


Console.WriteLine("Demonstrating non-batched database insert operations...")
Console.WriteLine("Original size of Employee table: {0}.", GetEmployeeCount(connectionString))
stopwatch.Start()
AddEmployees(connectionString, insertCount)
stopwatch.Stop()
Console.WriteLine("New size of Employee table: {0}; elapsed insert time: {1} ms.",
GetEmployeeCount(connectionString), stopwatch.ElapsedMilliseconds)

Console.WriteLine()

' Start again with a clean database file.


File.Copy(sourceDatabase, scratchDatabase, True)

' Demonstrate multiple insert operations, this time with batching.


Console.WriteLine("Demonstrating batched database insert operations...")
Console.WriteLine("Original size of Employee table: {0}.", GetEmployeeCount(connectionString))
stopwatch.Restart()
AddEmployeesBatched(connectionString, insertBatchSize, insertCount)
stopwatch.Stop()
Console.WriteLine("New size of Employee table: {0}; elapsed insert time: {1} ms.",
GetEmployeeCount(connectionString), stopwatch.ElapsedMilliseconds)

Console.WriteLine()

' Start again with a clean database file.


File.Copy(sourceDatabase, scratchDatabase, True)

' Demonstrate multiple retrieval operations with error reporting.


Console.WriteLine("Demonstrating batched join database select operations...")
' Add a small number of employees to the database.
AddEmployeesBatched(connectionString, insertBatchSize, 16)
' Query for random employees.
' Query for random employees.
GetRandomEmployees(connectionString, insertBatchSize, 10)
End Sub
End Class
End Namespace
' Sample output:
'Demonstrating non-batched database insert operations...
'Original size of Employee table: 15.
'Adding 256 entries to Employee table...
'New size of Employee table: 271; elapsed insert time: 11035 ms.
'
'Demonstrating batched database insert operations...
'Original size of Employee table: 15.
'Adding 256 entries to Employee table...
'New size of Employee table: 271; elapsed insert time: 197 ms.
'
'Demonstrating batched join database insert operations...
'Adding 16 entries to Employee table...
'Selecting items from Employee table...
'Received a batch...
'Last=Jones First=Tom ID=21
'Last=Jones First=John ID=24
'Last=Smith First=Tom ID=26
'Last=Jones First=Tom ID=21
'There were 4 errors in this batch...
'Received a batch...
'Last=Smith First=Tom ID=26
'Last=Jones First=Mike ID=28
'There were 0 errors in this batch...
'Finished. There were 4 total errors.
'

Voir aussi
Dataflow
Utilisation de la bibliothèque parallèle de tâches (TPL)
avec d’autres modèles asynchrones
18/07/2020 • 2 minutes to read • Edit Online

La bibliothèque parallèle de tâches peut être utilisée de différentes manières avec des modèles traditionnels .NET
Framework de programmation asynchrone.

Dans cette section


Bibliothèque parallèle de tâches (TPL) et programmation asynchrone .NET Framework
Décrit comment utiliser des objets Task conjointement avec le modèle de programmation asynchrone (APM) et le
modèle asynchrone basé sur les événements (EAP).
Procédure : inclure dans un wrapper des modèles EAP dans une tâche
Explique comment utiliser des objets Task pour encapsuler des modèles EAP.

Voir aussi
Bibliothèque parallèle de tâches
Bibliothèque parallèle de tâches (TPL) et
programmation asynchrone .NET Framework
18/07/2020 • 23 minutes to read • Edit Online

Le .NET Framework fournit les deux modèles standard suivants pour l’exécution d’opérations asynchrones liées
aux E/S et orientées calculs :
Le modèle de programmation asynchrone, dans lequel les opérations asynchrones sont représentées par
une paire de méthodes Begin/End telles que FileStream.BeginRead et Stream.EndRead.
Le modèle asynchrone basé sur des événements, dans lequel les opérations asynchrones sont
représentées par une paire méthode/événement et nommées OperationNameAsync et OperationName
Completed, par exemple, WebClient.DownloadStringAsync et WebClient.DownloadStringCompleted. (Le
modèle asynchrone basé sur des événements a été introduit avec le .NET Framework version 2.0).
La bibliothèque parallèle de tâches peut être utilisée de plusieurs façons conjointement à l'un ou l'autre des
modèles asynchrones. Vous pouvez exposer à la fois les opérations de modèle de programmation asynchrone et
de modèle asynchrone basé sur des événements en tant que Tâches aux consommateurs de la bibliothèque, ou
bien exposer les modèles de programmation asynchrone et utiliser des objets Task pour les implémenter en
interne. Dans les deux scénarios, en utilisant des objets Task, vous pouvez simplifier le code et bénéficier des
fonctionnalités utiles suivantes :
Enregistrer des rappels sous la forme de continuations de tâches, à tout moment après le lancement de la
tâche.
Coordonner plusieurs opérations qui s'exécutent en réponse à une méthode Begin_ , à l'aide des
méthodes ContinueWhenAll et ContinueWhenAny, ou des méthodes WaitAll ou WaitAny.
Encapsuler des opérations asynchrones liées aux E/S et orientées calcul dans le même objet Task.
Surveiller l'état de l'objet Task.
Marshaler le statut d'une opération sur un objet Task à l'aide de TaskCompletionSource<TResult>.

Encapsulation d’opérations de modèle de programmation asynchrone


dans une tâche
Les classes System.Threading.Tasks.TaskFactory et System.Threading.Tasks.TaskFactory<TResult> fournissent
plusieurs surcharges des méthodes TaskFactory.FromAsync et TaskFactory<TResult>.FromAsync qui vous
permettent d'encapsuler une paire de méthodes de modèle de programmation asynchrone Begin/End dans une
instance Task ou Task<TResult>. Les différentes surcharges s'adaptent à toutes les paires de méthode Begin/End
possédant de zéro à trois paramètres d'entrée.
Pour les paires ayant des méthodes End qui retournent une valeur ( Function en Visual Basic), utilisez les
méthodes de TaskFactory<TResult> qui créent un Task<TResult>. Pour les méthodes End qui retournent void (
Sub en Visual Basic), utilisez les méthodes de TaskFactory qui créent un Task.

Pour les rares cas dans lesquels la méthode Begin a plus de trois paramètres ou contient les paramètres ref ou
out , les surcharges FromAsync supplémentaires qui encapsulent uniquement la méthode End sont fournies.

L'exemple suivant montre la signature de la surcharge FromAsync qui correspond aux méthodes
FileStream.BeginRead et FileStream.EndRead. Cette surcharge prend trois paramètres d'entrée, comme suit.
public Task<TResult> FromAsync<TArg1, TArg2, TArg3>(
Func<TArg1, TArg2, TArg3, AsyncCallback, object, IAsyncResult> beginMethod, //BeginRead
Func<IAsyncResult, TResult> endMethod, //EndRead
TArg1 arg1, // the byte[] buffer
TArg2 arg2, // the offset in arg1 at which to start writing data
TArg3 arg3, // the maximum number of bytes to read
object state // optional state information
)

Public Function FromAsync(Of TArg1, TArg2, TArg3)(


ByVal beginMethod As Func(Of TArg1, TArg2, TArg3, AsyncCallback, Object, IAsyncResult),
ByVal endMethod As Func(Of IAsyncResult, TResult),
ByVal dataBuffer As TArg1,
ByVal byteOffsetToStartAt As TArg2,
ByVal maxBytesToRead As TArg3,
ByVal stateInfo As Object)

Le premier paramètre est un délégué Func<T1,T2,T3,T4,T5,TResult> qui correspond à la signature de la méthode


FileStream.BeginRead. Le deuxième paramètre est un délégué Func<T,TResult> qui prend un IAsyncResult et
retourne un TResult . Étant donné que EndRead retourne un entier, le compilateur déduit le type de TResult en
tant que Int32 et le type de la tâche en tant que Task. Les quatre derniers paramètres sont identiques à ceux de la
méthode FileStream.BeginRead :
Tampon dans lequel stocker les données de fichiers.
Dans le tampon, offset à partir duquel commencer l'écriture des données.
Quantité maximale de données à lire dans le fichier.
Objet facultatif qui stocke les données d'état définies par l'utilisateur à passer au rappel.
Utilisation de ContinueWith pour les fonctionnalités de rappel
Si vous devez accéder aux données d'un fichier, et non juste au nombre d'octets, la méthode FromAsync n'est pas
suffisante. Utilisez plutôt Task, dont la propriété Result contient les données de fichier. Pour cela, ajoutez une
continuation à la tâche d’origine. La continuation exécute le travail généralement effectué par le délégué
AsyncCallback. Elle est appelée quand l'antécédent est terminé et que le tampon de données est rempli. (L'objet
FileStream doit être fermé avant le retour.)
L'exemple suivant montre comment retourner un Task qui encapsule la paire BeginRead/EndRead de la classe
FileStream.
const int MAX_FILE_SIZE = 14000000;
public static Task<string> GetFileStringAsync(string path)
{
FileInfo fi = new FileInfo(path);
byte[] data = null;
data = new byte[fi.Length];

FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, data.Length, true);

//Task<int> returns the number of bytes read


Task<int> task = Task<int>.Factory.FromAsync(
fs.BeginRead, fs.EndRead, data, 0, data.Length, null);

// It is possible to do other work here while waiting


// for the antecedent task to complete.
// ...

// Add the continuation, which returns a Task<string>.


return task.ContinueWith((antecedent) =>
{
fs.Close();

// Result = "number of bytes read" (if we need it.)


if (antecedent.Result < 100)
{
return "Data is too small to bother with.";
}
else
{
// If we did not receive the entire file, the end of the
// data buffer will contain garbage.
if (antecedent.Result < data.Length)
Array.Resize(ref data, antecedent.Result);

// Will be returned in the Result property of the Task<string>


// at some future point after the asynchronous file I/O operation completes.
return new UTF8Encoding().GetString(data);
}
});
}
Const MAX_FILE_SIZE As Integer = 14000000
Shared Function GetFileStringAsync(ByVal path As String) As Task(Of String)
Dim fi As New FileInfo(path)
Dim data(fi.Length - 1) As Byte

Dim fs As FileStream = New FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, data.Length,


True)

' Task(Of Integer) returns the number of bytes read


Dim myTask As Task(Of Integer) = Task(Of Integer).Factory.FromAsync(
AddressOf fs.BeginRead, AddressOf fs.EndRead, data, 0, data.Length, Nothing)

' It is possible to do other work here while waiting


' for the antecedent task to complete.
' ...

' Add the continuation, which returns a Task<string>.


Return myTask.ContinueWith(Function(antecedent)
fs.Close()
If (antecedent.Result < 100) Then
Return "Data is too small to bother with."
End If
' If we did not receive the entire file, the end of the
' data buffer will contain garbage.
If (antecedent.Result < data.Length) Then
Array.Resize(data, antecedent.Result)
End If

' Will be returned in the Result property of the Task<string>


' at some future point after the asynchronous file I/O operation
completes.
Return New UTF8Encoding().GetString(data)
End Function)

End Function

La méthode peut ensuite être appelée, comme suit.

Task<string> t = GetFileStringAsync(path);

// Do some other work:


// ...

try
{
Console.WriteLine(t.Result.Substring(0, 500));
}
catch (AggregateException ae)
{
Console.WriteLine(ae.InnerException.Message);
}

Dim myTask As Task(Of String) = GetFileStringAsync(path)

' Do some other work


' ...

Try
Console.WriteLine(myTask.Result.Substring(0, 500))
Catch ex As AggregateException
Console.WriteLine(ex.InnerException.Message)
End Try
Fourniture de données d'état personnalisées
Dans les opérations IAsyncResult typiques, si votre délégué AsyncCallback a besoin de certaines données d'état
personnalisées, vous devez les passer via le dernier paramètre de la méthode Begin , afin que les données
puissent être empaquetées dans l'objet IAsyncResult qui est finalement passé à la méthode de rappel. Cela n'est
en général pas obligatoire quand les méthodes FromAsync sont utilisées. Si les données personnalisées sont
connues de la continuation, elles peuvent être capturées directement dans le délégué de continuation. L'exemple
suivant ressemble au précédent, mais au lieu d'examiner la propriété Result de la tâche antérieure, la
continuation examine les données d'état personnalisées directement accessibles au délégué utilisateur de la
continuation.

public Task<string> GetFileStringAsync2(string path)


{
FileInfo fi = new FileInfo(path);
byte[] data = new byte[fi.Length];
MyCustomState state = GetCustomState();
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, data.Length, true);
// We still pass null for the last parameter because
// the state variable is visible to the continuation delegate.
Task<int> task = Task<int>.Factory.FromAsync(
fs.BeginRead, fs.EndRead, data, 0, data.Length, null);

return task.ContinueWith((antecedent) =>


{
// It is safe to close the filestream now.
fs.Close();

// Capture custom state data directly in the user delegate.


// No need to pass it through the FromAsync method.
if (state.StateData.Contains("New York, New York"))
{
return "Start spreading the news!";
}
else
{
// If we did not receive the entire file, the end of the
// data buffer will contain garbage.
if (antecedent.Result < data.Length)
Array.Resize(ref data, antecedent.Result);

// Will be returned in the Result property of the Task<string>


// at some future point after the asynchronous file I/O operation completes.
return new UTF8Encoding().GetString(data);
}
});
}
Public Function GetFileStringAsync2(ByVal path As String) As Task(Of String)
Dim fi = New FileInfo(path)
Dim data(fi.Length - 1) As Byte
Dim state As New MyCustomState()

Dim fs As New FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, data.Length, True)


' We still pass null for the last parameter because
' the state variable is visible to the continuation delegate.
Dim myTask As Task(Of Integer) = Task(Of Integer).Factory.FromAsync(
AddressOf fs.BeginRead, AddressOf fs.EndRead, data, 0, data.Length, Nothing)

Return myTask.ContinueWith(Function(antecedent)
fs.Close()
' Capture custom state data directly in the user delegate.
' No need to pass it through the FromAsync method.
If (state.StateData.Contains("New York, New York")) Then
Return "Start spreading the news!"
End If

' If we did not receive the entire file, the end of the
' data buffer will contain garbage.
If (antecedent.Result < data.Length) Then
Array.Resize(data, antecedent.Result)
End If
'/ Will be returned in the Result property of the Task<string>
'/ at some future point after the asynchronous file I/O operation
completes.
Return New UTF8Encoding().GetString(data)
End Function)

End Function

Synchronisation de plusieurs tâches FromAsync


Les méthodes ContinueWhenAll et ContinueWhenAny statiques offrent une flexibilité supplémentaire en cas
d'utilisation avec les méthodes FromAsync . L'exemple suivant montre comment initialiser plusieurs opérations
d'E/S asynchrones et attendre qu'elles soient toutes terminées avant d'exécuter la continuation.
public Task<string> GetMultiFileData(string[] filesToRead)
{
FileStream fs;
Task<string>[] tasks = new Task<string>[filesToRead.Length];
byte[] fileData = null;
for (int i = 0; i < filesToRead.Length; i++)
{
fileData = new byte[0x1000];
fs = new FileStream(filesToRead[i], FileMode.Open, FileAccess.Read, FileShare.Read, fileData.Length,
true);

// By adding the continuation here, the


// Result of each task will be a string.
tasks[i] = Task<int>.Factory.FromAsync(
fs.BeginRead, fs.EndRead, fileData, 0, fileData.Length, null)
.ContinueWith((antecedent) =>
{
fs.Close();

// If we did not receive the entire file, the end of the


// data buffer will contain garbage.
if (antecedent.Result < fileData.Length)
Array.Resize(ref fileData, antecedent.Result);

// Will be returned in the Result property of the Task<string>


// at some future point after the asynchronous file I/O operation completes.
return new UTF8Encoding().GetString(fileData);
});
}

// Wait for all tasks to complete.


return Task<string>.Factory.ContinueWhenAll(tasks, (data) =>
{
// Propagate all exceptions and mark all faulted tasks as observed.
Task.WaitAll(data);

// Combine the results from all tasks.


StringBuilder sb = new StringBuilder();
foreach (var t in data)
{
sb.Append(t.Result);
}
// Final result to be returned eventually on the calling thread.
return sb.ToString();
});
}
Public Function GetMultiFileData(ByVal filesToRead As String()) As Task(Of String)
Dim fs As FileStream
Dim tasks(filesToRead.Length - 1) As Task(Of String)
Dim fileData() As Byte = Nothing
For i As Integer = 0 To filesToRead.Length
fileData(&H1000) = New Byte()
fs = New FileStream(filesToRead(i), FileMode.Open, FileAccess.Read, FileShare.Read, fileData.Length,
True)

' By adding the continuation here, the


' Result of each task will be a string.
tasks(i) = Task(Of Integer).Factory.FromAsync(AddressOf fs.BeginRead,
AddressOf fs.EndRead,
fileData,
0,
fileData.Length,
Nothing).
ContinueWith(Function(antecedent)
fs.Close()
'If we did not receive the entire file,
the end of the
' data buffer will contain garbage.
If (antecedent.Result < fileData.Length)
Then
ReDim Preserve
fileData(antecedent.Result)
End If

'Will be returned in the Result property


of the Task<string>
' at some future point after the
asynchronous file I/O operation completes.
Return New
UTF8Encoding().GetString(fileData)
End Function)
Next

Return Task(Of String).Factory.ContinueWhenAll(tasks, Function(data)

' Propagate all exceptions and mark all faulted


tasks as observed.
Task.WaitAll(data)

' Combine the results from all tasks.


Dim sb As New StringBuilder()
For Each t As Task(Of String) In data
sb.Append(t.Result)
Next
' Final result to be returned eventually on the
calling thread.
Return sb.ToString()
End Function)
End Function

Tâches FromAsync pour la méthode End uniquement


Pour les rares cas dans lesquels la méthode Begin nécessite plus de trois paramètres d'entrée, ou a des
paramètres ref ou out , vous pouvez utiliser les surcharges FromAsync , par exemple,
TaskFactory<TResult>.FromAsync(IAsyncResult, Func<IAsyncResult,TResult>), qui représentent uniquement la
méthode End . Ces méthodes peuvent également être utilisées pour tous les scénarios dans lesquels vous est
passé un IAsyncResult que vous voulez encapsuler dans une tâche.
static Task<String> ReturnTaskFromAsyncResult()
{
IAsyncResult ar = DoSomethingAsynchronously();
Task<String> t = Task<string>.Factory.FromAsync(ar, _ =>
{
return (string)ar.AsyncState;
});

return t;
}

Shared Function ReturnTaskFromAsyncResult() As Task(Of String)


Dim ar As IAsyncResult = DoSomethingAsynchronously()
Dim t As Task(Of String) = Task(Of String).Factory.FromAsync(ar, Function(res) CStr(res.AsyncState))
Return t
End Function

Lancement et annulation de tâches FromAsync


La tâche retournée par une méthode FromAsync a l’état WaitingForActivation et sera lancée à un moment donné
par le système après la création de la tâche. Si vous essayez d’appeler Start pour une telle tâche, une exception
sera levée.
Vous ne pouvez pas annuler une tâche FromAsync , car les API .NET Framework sous-jacentes ne prennent
actuellement pas en charge l’annulation d’opérations d’E/S réseau ou de fichier en cours. Vous pouvez ajouter des
fonctionnalités d’annulation à une méthode qui encapsule un appel FromAsync , mais vous pouvez répondre
uniquement à l’annulation avant que FromAsync soit appelé ou terminé (par exemple, dans une tâche de
continuation).
Certaines classes prenant en charge le modèle asynchrone basé sur des événements, comme WebClient,
prennent en charge l'annulation et vous pouvez intégrer ces fonctionnalités d'annulation native à l'aide de jetons
d'annulation.

Exposition d’opérations complexes de modèle asynchrone basé sur des


événements en tant que tâches
La bibliothèque parallèle de tâches ne fournit pas de méthodes conçues spécifiquement pour encapsuler une
opération asynchrone basée sur des événements de la même façon que la famille FromAsync de méthodes
encapsule le modèle IAsyncResult. Toutefois, la bibliothèque parallèle de tâches fournit la classe
System.Threading.Tasks.TaskCompletionSource<TResult>, qui peut être utilisée pour représenter tout ensemble
d'opérations arbitraire tel qu'un Task<TResult>. Les opérations peuvent être synchrones ou asynchrones et
peuvent être liées aux E/S ou orientées calcul, ou les deux.
L'exemple suivant montre comment utiliser un TaskCompletionSource<TResult> pour exposer un ensemble
d'opérations WebClient asynchrones à du code client en tant que Task<TResult> de base. Cette méthode vous
permet d'entrer un tableau d'URL web et un terme ou nom à rechercher, puis retourne le nombre d'occurrences
de ce terme ou nom sur chaque site.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

public class SimpleWebExample


{
public Task<string[]> GetWordCountsSimplified(string[] urls, string name,
public Task<string[]> GetWordCountsSimplified(string[] urls, string name,
CancellationToken token)
{
TaskCompletionSource<string[]> tcs = new TaskCompletionSource<string[]>();
WebClient[] webClients = new WebClient[urls.Length];
object m_lock = new object();
int count = 0;
List<string> results = new List<string>();

// If the user cancels the CancellationToken, then we can use the


// WebClient's ability to cancel its own async operations.
token.Register(() =>
{
foreach (var wc in webClients)
{
if (wc != null)
wc.CancelAsync();
}
});

for (int i = 0; i < urls.Length; i++)


{
webClients[i] = new WebClient();

#region callback
// Specify the callback for the DownloadStringCompleted
// event that will be raised by this WebClient instance.
webClients[i].DownloadStringCompleted += (obj, args) =>
{

// Argument validation and exception handling omitted for brevity.

// Split the string into an array of words,


// then count the number of elements that match
// the search term.
string[] words = args.Result.Split(' ');
string NAME = name.ToUpper();
int nameCount = (from word in words.AsParallel()
where word.ToUpper().Contains(NAME)
select word)
.Count();

// Associate the results with the url, and add new string to the array that
// the underlying Task object will return in its Result property.
lock (m_lock)
{
results.Add(String.Format("{0} has {1} instances of {2}", args.UserState, nameCount, name));

// If this is the last async operation to complete,


// then set the Result property on the underlying Task.
count++;
if (count == urls.Length)
{
tcs.TrySetResult(results.ToArray());
}
}
};
#endregion

// Call DownloadStringAsync for each URL.


Uri address = null;
address = new Uri(urls[i]);
webClients[i].DownloadStringAsync(address, address);
} // end for

// Return the underlying Task. The client code


// waits on the Result property, and handles exceptions
// in the try-catch block there.
return tcs.Task;
}
}
}

Imports System.Collections.Generic
Imports System.Net
Imports System.Threading
Imports System.Threading.Tasks

Public Class SimpleWebExample


Dim tcs As New TaskCompletionSource(Of String())
Dim token As CancellationToken
Dim results As New List(Of String)
Dim m_lock As New Object()
Dim count As Integer
Dim addresses() As String
Dim nameToSearch As String

Public Function GetWordCountsSimplified(ByVal urls() As String, ByVal str As String,


ByVal token As CancellationToken) As Task(Of String())
addresses = urls
nameToSearch = str

Dim webClients(urls.Length - 1) As WebClient

' If the user cancels the CancellationToken, then we can use the
' WebClient's ability to cancel its own async operations.
token.Register(Sub()
For Each wc As WebClient In webClients
If wc IsNot Nothing Then
wc.CancelAsync()
End If
Next
End Sub)

For i As Integer = 0 To urls.Length - 1


webClients(i) = New WebClient()

' Specify the callback for the DownloadStringCompleted


' event that will be raised by this WebClient instance.
AddHandler webClients(i).DownloadStringCompleted, AddressOf WebEventHandler

Dim address As New Uri(urls(i))


' Pass the address, and also use it for the userToken
' to identify the page when the delegate is invoked.
webClients(i).DownloadStringAsync(address, address)
Next

' Return the underlying Task. The client code


' waits on the Result property, and handles exceptions
' in the try-catch block there.
Return tcs.Task
End Function

Public Sub WebEventHandler(ByVal sender As Object, ByVal args As DownloadStringCompletedEventArgs)

If args.Cancelled = True Then


tcs.TrySetCanceled()
Return
ElseIf args.Error IsNot Nothing Then
tcs.TrySetException(args.Error)
Return
Else
' Split the string into an array of words,
' then count the number of elements that match
' the search term.
Dim words() As String = args.Result.Split(" "c)

Dim name As String = nameToSearch.ToUpper()


Dim name As String = nameToSearch.ToUpper()
Dim nameCount = (From word In words.AsParallel()
Where word.ToUpper().Contains(name)
Select word).Count()

' Associate the results with the url, and add new string to the array that
' the underlying Task object will return in its Result property.
SyncLock (m_lock)
results.Add(String.Format("{0} has {1} instances of {2}", args.UserState, nameCount,
nameToSearch))
count = count + 1
If (count = addresses.Length) Then
tcs.TrySetResult(results.ToArray())
End If
End SyncLock
End If
End Sub
End Class

Pour obtenir un exemple plus complet incluant une gestion supplémentaire des exceptions et indiquant comment
appeler la méthode depuis le code client, consultez Guide pratique pour exposer des modèles EAP dans une tâche.
Souvenez-vous que toute tâche créée par un TaskCompletionSource<TResult> sera lancée par ce
TaskCompletionSource et que le code utilisateur ne doit donc pas appeler la méthode Start dans cette tâche.

Implémentation du modèle de programmation asynchrone à l’aide de


tâches
Dans certains scénarios, il peut être souhaitable d’exposer directement le modèle IAsyncResult à l’aide de paires
de méthodes Begin/End dans une API. Vous pouvez, par exemple, maintenir la cohérence avec les API existantes
ou posséder des outils automatisés qui nécessitent ce modèle. Dans de tels cas, vous pouvez utiliser des tâches
pour simplifier l'implémentation en interne du modèle de programmation asynchrone.
L’exemple suivant montre comment utiliser des tâches pour implémenter une paire de méthodes de modèle de
programmation asynchrone Begin/End pour une méthode orientée calcul de longue durée.
class Calculator
{
public IAsyncResult BeginCalculate(int decimalPlaces, AsyncCallback ac, object state)
{
Console.WriteLine("Calling BeginCalculate on thread {0}", Thread.CurrentThread.ManagedThreadId);
Task<string> f = Task<string>.Factory.StartNew(_ => Compute(decimalPlaces), state);
if (ac != null) f.ContinueWith((res) => ac(f));
return f;
}

public string Compute(int numPlaces)


{
Console.WriteLine("Calling compute on thread {0}", Thread.CurrentThread.ManagedThreadId);

// Simulating some heavy work.


Thread.SpinWait(500000000);

// Actual implemenation left as exercise for the reader.


// Several examples are available on the Web.
return "3.14159265358979323846264338327950288";
}

public string EndCalculate(IAsyncResult ar)


{
Console.WriteLine("Calling EndCalculate on thread {0}", Thread.CurrentThread.ManagedThreadId);
return ((Task<string>)ar).Result;
}
}

public class CalculatorClient


{
static int decimalPlaces = 12;
public static void Main()
{
Calculator calc = new Calculator();
int places = 35;

AsyncCallback callBack = new AsyncCallback(PrintResult);


IAsyncResult ar = calc.BeginCalculate(places, callBack, calc);

// Do some work on this thread while the calulator is busy.


Console.WriteLine("Working...");
Thread.SpinWait(500000);
Console.ReadLine();
}

public static void PrintResult(IAsyncResult result)


{
Calculator c = (Calculator)result.AsyncState;
string piString = c.EndCalculate(result);
Console.WriteLine("Calling PrintResult on thread {0}; result = {1}",
Thread.CurrentThread.ManagedThreadId, piString);
}
}
Class Calculator
Public Function BeginCalculate(ByVal decimalPlaces As Integer, ByVal ac As AsyncCallback, ByVal state As
Object) As IAsyncResult
Console.WriteLine("Calling BeginCalculate on thread {0}", Thread.CurrentThread.ManagedThreadId)
Dim myTask = Task(Of String).Factory.StartNew(Function(obj) Compute(decimalPlaces), state)
myTask.ContinueWith(Sub(antedecent) ac(myTask))

End Function
Private Function Compute(ByVal decimalPlaces As Integer)
Console.WriteLine("Calling compute on thread {0}", Thread.CurrentThread.ManagedThreadId)

' Simulating some heavy work.


Thread.SpinWait(500000000)

' Actual implemenation left as exercise for the reader.


' Several examples are available on the Web.
Return "3.14159265358979323846264338327950288"
End Function

Public Function EndCalculate(ByVal ar As IAsyncResult) As String


Console.WriteLine("Calling EndCalculate on thread {0}", Thread.CurrentThread.ManagedThreadId)
Return CType(ar, Task(Of String)).Result
End Function
End Class

Class CalculatorClient
Shared decimalPlaces As Integer
Shared Sub Main()
Dim calc As New Calculator
Dim places As Integer = 35
Dim callback As New AsyncCallback(AddressOf PrintResult)
Dim ar As IAsyncResult = calc.BeginCalculate(places, callback, calc)

' Do some work on this thread while the calulator is busy.


Console.WriteLine("Working...")
Thread.SpinWait(500000)
Console.ReadLine()
End Sub

Public Shared Sub PrintResult(ByVal result As IAsyncResult)


Dim c As Calculator = CType(result.AsyncState, Calculator)
Dim piString As String = c.EndCalculate(result)
Console.WriteLine("Calling PrintResult on thread {0}; result = {1}",
Thread.CurrentThread.ManagedThreadId, piString)
End Sub

End Class

Utilisation de l'exemple de code StreamExtensions


Le fichier StreamExtensions.cs , dans le référentiel .NET standard Parallel Extensions extras , contient plusieurs
implémentations de référence qui utilisent Task des objets pour les e/s de fichier et de réseau asynchrones.

Voir aussi
Bibliothèque parallèle de tâches
Procédure : inclure dans un wrapper des modèles
EAP dans une tâche
18/07/2020 • 5 minutes to read • Edit Online

L’exemple suivant montre comment exposer une séquence arbitraire d’opérations EAP (modèle asynchrone basé
sur des événements) comme une seule tâche à l’aide d’un TaskCompletionSource<TResult>. L’exemple montre
également comment utiliser un CancellationToken pour appeler les méthodes d’annulation intégrées sur les objets
WebClient.

Exemple
class WebDataDownloader
{

static void Main()


{
WebDataDownloader downloader = new WebDataDownloader();
string[] addresses = { "http://www.msnbc.com", "http://www.yahoo.com",
"http://www.nytimes.com", "http://www.washingtonpost.com",
"http://www.latimes.com", "http://www.newsday.com" };
CancellationTokenSource cts = new CancellationTokenSource();

// Create a UI thread from which to cancel the entire operation


Task.Factory.StartNew(() =>
{
Console.WriteLine("Press c to cancel");
if (Console.ReadKey().KeyChar == 'c')
cts.Cancel();
});

// Using a neutral search term that is sure to get some hits.


Task<string[]> webTask = downloader.GetWordCounts(addresses, "the", cts.Token);

// Do some other work here unless the method has already completed.
if (!webTask.IsCompleted)
{
// Simulate some work.
Thread.SpinWait(5000000);
}

string[] results = null;


try
{
results = webTask.Result;
}
catch (AggregateException e)
{
foreach (var ex in e.InnerExceptions)
{
OperationCanceledException oce = ex as OperationCanceledException;
if (oce != null)
{
if (oce.CancellationToken == cts.Token)
{
Console.WriteLine("Operation canceled by user.");
}
}
else
{
Console.WriteLine(ex.Message);
}
}
}
finally
{
cts.Dispose();
}
if (results != null)
{
foreach (var item in results)
Console.WriteLine(item);
}
Console.ReadKey();
}

Task<string[]> GetWordCounts(string[] urls, string name, CancellationToken token)


{
TaskCompletionSource<string[]> tcs = new TaskCompletionSource<string[]>();
WebClient[] webClients = new WebClient[urls.Length];

// If the user cancels the CancellationToken, then we can use the


// WebClient's ability to cancel its own async operations.
token.Register(() =>
{
foreach (var wc in webClients)
{
if (wc != null)
wc.CancelAsync();
}
});

object m_lock = new object();


int count = 0;
List<string> results = new List<string>();
for (int i = 0; i < urls.Length; i++)
{
webClients[i] = new WebClient();

#region callback
// Specify the callback for the DownloadStringCompleted
// event that will be raised by this WebClient instance.
webClients[i].DownloadStringCompleted += (obj, args) =>
{
if (args.Cancelled == true)
{
tcs.TrySetCanceled();
return;
}
else if (args.Error != null)
{
// Pass through to the underlying Task
// any exceptions thrown by the WebClient
// during the asynchronous operation.
tcs.TrySetException(args.Error);
return;
}
else
{
// Split the string into an array of words,
// then count the number of elements that match
// the search term.
string[] words = null;
words = args.Result.Split(' ');
string NAME = name.ToUpper();
int nameCount = (from word in words.AsParallel()
where word.ToUpper().Contains(NAME)
select word)
.Count();
.Count();

// Associate the results with the url, and add new string to the array that
// the underlying Task object will return in its Result property.
results.Add(String.Format("{0} has {1} instances of {2}", args.UserState, nameCount,
name));
}

// If this is the last async operation to complete,


// then set the Result property on the underlying Task.
lock (m_lock)
{
count++;
if (count == urls.Length)
{
tcs.TrySetResult(results.ToArray());
}
}
};
#endregion

// Call DownloadStringAsync for each URL.


Uri address = null;
try
{
address = new Uri(urls[i]);
// Pass the address, and also use it for the userToken
// to identify the page when the delegate is invoked.
webClients[i].DownloadStringAsync(address, address);
}

catch (UriFormatException ex)


{
// Abandon the entire operation if one url is malformed.
// Other actions are possible here.
tcs.TrySetException(ex);
return tcs.Task;
}
}

// Return the underlying Task. The client code


// waits on the Result property, and handles exceptions
// in the try-catch block there.
return tcs.Task;
}

Class WebDataDownLoader

Dim tcs As New TaskCompletionSource(Of String())


Dim nameToSearch As String
Dim token As CancellationToken
Dim results As New List(Of String)
Dim m_lock As Object
Dim count As Integer
Dim addresses() As String

Shared Sub Main()

Dim downloader As New WebDataDownLoader()


downloader.addresses = {"http://www.msnbc.com", "http://www.yahoo.com", _
"http://www.nytimes.com", "http://www.washingtonpost.com", _
"http://www.latimes.com", "http://www.newsday.com"}
Dim cts As New CancellationTokenSource()

' Create a UI thread from which to cancel the entire operation


Task.Factory.StartNew(Sub()
Console.WriteLine("Press c to cancel")
If Console.ReadKey().KeyChar = "c"c Then
If Console.ReadKey().KeyChar = "c"c Then
cts.Cancel()
End If
End Sub)

' Using a neutral search term that is sure to get some hits on English web sites.
' Please substitute your favorite search term.
downloader.nameToSearch = "the"
Dim webTask As Task(Of String()) = downloader.GetWordCounts(downloader.addresses,
downloader.nameToSearch, cts.Token)

' Do some other work here unless the method has already completed.
If (webTask.IsCompleted = False) Then
' Simulate some work
Thread.SpinWait(5000000)
End If

Dim results As String() = Nothing


Try
results = webTask.Result
Catch ae As AggregateException
For Each ex As Exception In ae.InnerExceptions
If (TypeOf (ex) Is OperationCanceledException) Then
Dim oce As OperationCanceledException = CType(ex, OperationCanceledException)
If oce.CancellationToken = cts.Token Then
Console.WriteLine("Operation canceled by user.")
End If
Else
Console.WriteLine(ex.Message)
End If

Next
Finally
cts.Dispose()
End Try

If (Not results Is Nothing) Then


For Each item As String In results
Console.WriteLine(item)
Next
End If

Console.WriteLine("Press any key to exit")


Console.ReadKey()
End Sub

Public Function GetWordCounts(ByVal urls() As String, ByVal str As String, ByVal token As
CancellationToken) As Task(Of String())

Dim webClients() As WebClient


ReDim webClients(urls.Length)
m_lock = New Object()

' If the user cancels the CancellationToken, then we can use the
' WebClient's ability to cancel its own async operations.
token.Register(Sub()
For Each wc As WebClient In webClients
If Not wc Is Nothing Then
wc.CancelAsync()
End If
Next
End Sub)

For i As Integer = 0 To urls.Length - 1


webClients(i) = New WebClient()

' Specify the callback for the DownloadStringCompleted


' event that will be raised by this WebClient instance.
AddHandler webClients(i).DownloadStringCompleted, AddressOf WebEventHandler
AddHandler webClients(i).DownloadStringCompleted, AddressOf WebEventHandler

Dim address As Uri = Nothing


Try
address = New Uri(urls(i))
' Pass the address, and also use it for the userToken
' to identify the page when the delegate is invoked.
webClients(i).DownloadStringAsync(address, address)
Catch ex As UriFormatException
tcs.TrySetException(ex)
Return tcs.Task
End Try

Next

' Return the underlying Task. The client code


' waits on the Result property, and handles exceptions
' in the try-catch block there.
Return tcs.Task
End Function

Public Sub WebEventHandler(ByVal sender As Object, ByVal args As DownloadStringCompletedEventArgs)

If args.Cancelled = True Then


tcs.TrySetCanceled()
Return
ElseIf Not args.Error Is Nothing Then
tcs.TrySetException(args.Error)
Return
Else
' Split the string into an array of words,
' then count the number of elements that match
' the search term.
Dim words() As String = args.Result.Split(" "c)
Dim NAME As String = nameToSearch.ToUpper()
Dim nameCount = (From word In words.AsParallel()
Where word.ToUpper().Contains(NAME)
Select word).Count()

' Associate the results with the url, and add new string to the array that
' the underlying Task object will return in its Result property.
results.Add(String.Format("{0} has {1} instances of {2}", args.UserState, nameCount,
nameToSearch))
End If

SyncLock (m_lock)
count = count + 1
If (count = addresses.Length) Then
tcs.TrySetResult(results.ToArray())
End If
End SyncLock
End Sub

Voir aussi
Bibliothèque parallèle de tâches (TPL) et programmation asynchrone .NET Framework
Pièges potentiels dans le parallélisme des données et
des tâches
18/07/2020 • 14 minutes to read • Edit Online

Dans de nombreux cas, Parallel.For et Parallel.ForEach permettent une amélioration significative des performances
par rapport à des boucles séquentielles ordinaires. Toutefois, le travail de la parallélisation de la boucle présente
une certaine complexité pouvant entraîner des problèmes qui, dans du code séquentiel, ne sont pas si courants ou
ne surviennent pas du tout. Cette rubrique répertorie les pratiques à éviter lorsque vous écrivez des boucles
parallèles.

Ne partez pas du principe qu’une boucle parallèle est toujours plus


rapide
Dans certains cas, elle peut s’exécuter plus lentement qu’une boucle séquentielle équivalente. La règle empirique
de base veut que les boucles parallèles ayant peu d’itérations et des délégués utilisateurs rapides ne soient pas
susceptibles d’apporter une grande accélération. Toutefois, étant donné que de nombreux facteurs sont impliqués
dans les performances, nous vous recommandons de toujours mesurer les résultats réels.

Éviter d’écrire à des emplacements de mémoire partagés


Dans du code séquentiel, il n’est pas rare de lire des variables statiques ou d’écrire dans ces dernières ou dans des
champs de classe. Toutefois, l’accès simultané de plusieurs threads à de telles variables entraîne un fort risque
d’engorgement. Bien que vous puissiez utiliser des verrous pour synchroniser l’accès à la variable, le coût de
synchronisation peut nuire aux performances. Par conséquent, nous vous recommandons d’éviter, ou au moins de
limiter autant que possible l’accès à un état partagé dans une boucle parallèle. La meilleure façon de procéder
consiste à utiliser les surcharges de Parallel.For et Parallel.ForEach qui utilisent une variable
System.Threading.ThreadLocal<T> pour stocker l’état local du thread pendant l’exécution de la boucle. Pour plus
d’informations, consultez Guide pratique pour écrire une boucle Parallel.For avec des variables locales de thread et
Guide pratique pour écrire une boucle Parallel.ForEach avec des variables locales de partition.

Éviter la surparallélisation
L’utilisation de boucles parallèles entraîne des coûts de surcharge liés au partitionnement de la collection source et
à la synchronisation des threads de travail. Les avantages de la parallélisation sont également limités par le
nombre de processeurs de l’ordinateur. L’exécution de plusieurs threads liés au calcul sur un seul processeur ne
permet aucune accélération. Par conséquent, vous devez veiller à ne pas surparalléliser une boucle.
Les boucles imbriquées sont le scénario le plus courant dans lequel une surparallélisation peut se produire. Dans
la plupart des cas, il est préférable de paralléliser uniquement la boucle externe, sauf si une ou plusieurs conditions
suivantes s’appliquent :
La boucle interne est réputée être très longue.
Vous effectuez un calcul coûteux sur chaque commande. (l’opération montrée dans l’exemple n’est pas
coûteuse)
Le système cible est connu pour avoir suffisamment de processeurs pour gérer le nombre de threads
produits en parallélisant la requête sur cust.Orders .
Dans tous les cas, le test et la mesure sont la meilleure façon de déterminer la forme de requête optimale.
Éviter de faire appel aux méthodes qui ne sont pas thread-safe
L’écriture dans des méthodes d’instance qui ne sont pas thread-safe à partir d’une boucle parallèle peut entraîner
une corruption des données qui peut être détectée ou non dans votre programme. Cela peut également entraîner
des exceptions. Dans l’exemple suivant, plusieurs threads tenteraient d’appeler simultanément la méthode
FileStream.WriteByte, qui n’est pas prise en charge par la classe.

FileStream fs = File.OpenWrite(path);
byte[] bytes = new Byte[10000000];
// ...
Parallel.For(0, bytes.Length, (i) => fs.WriteByte(bytes[i]));

Dim fs As FileStream = File.OpenWrite(filepath)


Dim bytes() As Byte
ReDim bytes(1000000)
' ...init byte array
Parallel.For(0, bytes.Length, Sub(n) fs.WriteByte(bytes(n)))

Limiter les appels aux méthodes qui ne sont pas thread-safe


La plupart des méthodes statiques du .NET Framework sont thread-safe et peuvent être appelées à partir de
plusieurs threads simultanément. Toutefois, même dans ces cas, la synchronisation impliquée peut entraîner un
ralentissement significatif de la requête.

NOTE
Vous pouvez le tester vous-même en insérant des appels à WriteLine dans vos requêtes. Bien que cette méthode soit utilisée
dans les exemples de documentation destinés à la démonstration, ne l’utilisez pas dans les boucles parallèles, sauf si
nécessaire.

Tenir compte des problèmes d’affinité de thread


Certaines technologies, par exemple, les composants STA (Single-Threaded Apartment), Windows Forms et
Windows Presentation Foundation (WPF) imposent des restrictions d’affinité de thread qui requièrent l’exécution
de code sur un thread spécifique. Par exemple, dans Windows Forms et WPF, un contrôle est uniquement
accessible sur le thread sur lequel il a été créé. Cela signifie, par exemple, que vous ne pouvez pas mettre à jour un
contrôle de liste à partir d’une boucle parallèle, sauf si vous configurez le planificateur de threads de sorte qu’il
planifie le travail uniquement sur le thread d’interface utilisateur. Pour plus d’informations, consultez Spécification
d’un contexte de synchronisation.

Être vigilant lors de l’attente dans des délégués appelés par


Parallel.Invoke
Dans certaines circonstances, la bibliothèque parallèle de tâches intègre une tâche, ce qui signifie qu’elle s’exécute
sur la tâche du thread en cours d’exécution. (Pour plus d’informations, consultez planificateurs de tâches.) Cette
optimisation des performances peut entraîner un blocage dans certains cas. Par exemple, deux tâches peuvent
exécuter le même code de délégué, qui signale la survenue d’un événement et attend l’autre tâche à signaler. Si la
seconde tâche est incluse sur le même thread que la première et que cette dernière passe à l’état En attente, la
seconde tâche ne pourra jamais signaler son événement. Pour éviter une telle situation, vous pouvez spécifier un
délai d’expiration sur l’opération d’attente, ou utiliser les constructeurs de thread explicites pour s’assurer qu’une
tâche ne peut pas bloquer l’autre.
Ne pas supposer que les itérations de ForEach, For et ForAll s’exécutent
toujours en parallèle
Il est important de garder à l’esprit que les itérations individuelles dans une boucle For, ForEach ou ForAll peuvent,
mais ne doivent pas forcément, s’exécuter en parallèle. Par conséquent, vous devez éviter d’écrire du code dont
l’exactitude dépend de l’exécution parallèle d’itérations ou de l’exécution d’itérations dans un ordre particulier. Par
exemple, ce code est susceptible d’interbloquer :

ManualResetEventSlim mre = new ManualResetEventSlim();


Enumerable.Range(0, Environment.ProcessorCount * 100)
.AsParallel()
.ForAll((j) =>
{
if (j == Environment.ProcessorCount)
{
Console.WriteLine("Set on {0} with value of {1}",
Thread.CurrentThread.ManagedThreadId, j);
mre.Set();
}
else
{
Console.WriteLine("Waiting on {0} with value of {1}",
Thread.CurrentThread.ManagedThreadId, j);
mre.Wait();
}
}); //deadlocks

Dim mres = New ManualResetEventSlim()


Enumerable.Range(0, Environment.ProcessorCount * 100) _
.AsParallel() _
.ForAll(Sub(j)

If j = Environment.ProcessorCount Then
Console.WriteLine("Set on {0} with value of {1}",
Thread.CurrentThread.ManagedThreadId, j)
mres.Set()
Else
Console.WriteLine("Waiting on {0} with value of {1}",
Thread.CurrentThread.ManagedThreadId, j)
mres.Wait()
End If
End Sub) ' deadlocks

Dans cet exemple, une itération définit un événement que toutes les autres itérations attendent. Aucune des
itérations en attente ne peut s’achever tant que l’itération de définition d’événement n’est pas terminée. Toutefois, il
est possible que les itérations en attente bloquent tous les threads utilisés pour exécuter la boucle parallèle, avant
que l’itération de définition d’événement ait eu une chance de s’exécuter. Cela provoque un interblocage :
l’itération de définition d’événement ne s’exécute jamais et les itérations en attente ne s’activent pas non plus.
En particulier, une itération de boucle parallèle ne doit jamais attendre une autre itération de la boucle pour
progresser. Si la boucle parallèle décide de planifier les itérations de manière séquentielle, mais dans l’ordre
inverse, un interblocage se produit.

Éviter d’exécuter des boucles parallèles sur le thread d’interface


utilisateur
Il est important de maintenir la réactivité de l’interface utilisateur de l’application (IU). Si une opération contient
suffisamment de travail pour assurer la parallélisation, cette opération ne doit pas être exécutée sur le thread
d’interface utilisateur. Au lieu de cela, elle doit décharger cette opération de sorte qu’elle s’exécute sur un thread
d’arrière-plan. Par exemple, si vous souhaitez utiliser une boucle parallèle pour calculer des données qui doivent
ensuite être restituées dans un contrôle d’interface utilisateur, vous devez envisager l’exécution de la boucle dans
une instance de tâche plutôt que directement dans un gestionnaire d’événements de l’interface utilisateur. Vous ne
devez marshaler la mise à jour de l’interface utilisateur vers le thread de l’interface utilisateur qu’une fois le calcul
principal terminé.
Si vous exécutez des boucles parallèles sur le thread d’interface utilisateur, veillez à éviter la mise à jour des
contrôles d’interface utilisateur à partir de la boucle. Toute tentative de mise à jour des contrôles d’interface
utilisateur à partir d’une boucle parallèle qui s’exécute sur le thread d’interface utilisateur peut entraîner une
altération de l’état, des exceptions, des reports de mise à jour et même des interblocages, selon la manière dont la
mise à jour de l’interface utilisateur est appelée. Dans l’exemple suivant, la boucle parallèle bloque le thread
d’interface utilisateur sur lequel elle s’exécute jusqu’à ce que toutes les itérations soient terminées. Toutefois, si une
itération de la boucle s’exécute sur un thread d’arrière-plan (comme For peut le faire), l’appel à Invoke entraîne
l’envoi d’un message au thread d’interface utilisateur et bloque en attendant que ce message soit traité. Étant
donné que le thread d’interface utilisateur est bloqué lors de l’exécution de For, le message ne peut jamais être
traité et le thread d’interface utilisateur est soumis à des interblocages.

private void button1_Click(object sender, EventArgs e)


{
Parallel.For(0, N, i =>
{
// do work for i
button1.Invoke((Action)delegate { DisplayProgress(i); });
});
}

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

Dim iterations As Integer = 20


Parallel.For(0, iterations, Sub(x)
Button1.Invoke(Sub()
DisplayProgress(x)
End Sub)
End Sub)
End Sub

L’exemple suivant montre comment éviter l’interblocage, en exécutant la boucle à l’intérieur d’une instance de
tâche. Le thread d’interface utilisateur n’est pas bloqué par la boucle, et le message peut être traité.

private void button1_Click(object sender, EventArgs e)


{
Task.Factory.StartNew(() =>
Parallel.For(0, N, i =>
{
// do work for i
button1.Invoke((Action)delegate { DisplayProgress(i); });
})
);
}
Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

Dim iterations As Integer = 20


Task.Factory.StartNew(Sub() Parallel.For(0, iterations, Sub(x)
Button1.Invoke(Sub()
DisplayProgress(x)
End Sub)
End Sub))
End Sub

Voir aussi
Programmation parallèle
Pièges potentiels avec PLINQ
Modèles de programmation parallèle : présentation et application des modèles parallèles avec le .NET
Framework 4 (page éventuellement en anglais)
Introduction à PLINQ
18/07/2020 • 21 minutes to read • Edit Online

Parallel LINQ (PLINQ) est une implémentation parallèle du modèle LINQ (Language-Integrated Query) .
PLINQ implémente le jeu complet d’opérateurs de requête standard LINQ comme méthodes d’extension pour
l’espace de noms System.Linq et inclut des opérateurs supplémentaires pour les opérations parallèles. PLINQ
combine la simplicité et la lisibilité de la syntaxe LINQ à la puissance de la programmation parallèle.

TIP
Si vous n’êtes pas familiarisé avec LINQ, il comprend un modèle unifié pour interroger une source de données
énumérable de façon sécurisée. LINQ to Objects est le nom des requêtes LINQ exécutées sur les collections en
mémoire telles que List<T> et les tableaux. Cet article suppose que vous avez une connaissance de base de LINQ.
Pour plus d’informations, consultez LINQ (Language-Integrated Query).

Qu’est-ce qu’une requête parallèle ?


Une requête PLINQ ressemble à bien des égards à une requête LINQ to Objects non parallèle. Les requêtes
PLINQ, tout comme les requêtes LINQ séquentielles, fonctionnent sur n’importe quelle IEnumerable source
de données ou mémoire IEnumerable<T> , et ont une exécution différée, ce qui signifie qu’elles ne
commencent pas à s’exécuter tant que la requête n’est pas énumérée. La principale différence est que PLINQ
essaie d’utiliser pleinement tous les processeurs sur le système. Cela s’effectue par le partitionnement de la
source de données en segments et l’exécution de la requête sur chaque segment sur des threads de travail
distincts en parallèle sur plusieurs processeurs. Dans de nombreux cas, l’exécution parallèle signifie une
exécution beaucoup plus rapide de la requête.
L’exécution parallèle permet à PLINQ d’améliorer de manière significative les performances du code hérité
pour certains types de requêtes, souvent par le simple ajout de l’opération de requête AsParallel à la source
de données. Toutefois, le parallélisme peut présenter ses propres complexités et toutes les opérations de
requête ne s’exécutent pas plus rapidement dans PLINQ. En fait, la parallélisation ralentit réellement certaines
requêtes. Par conséquent, vous devez comprendre comment des problèmes, tels que ceux liés à l’ordre,
affectent les requêtes parallèles. Pour plus d’informations, consultez Fonctionnement de l’accélération dans
PLINQ.

NOTE
Cette documentation utilise des expressions lambda pour définir les délégués en PLINQ. Si les expressions lambda en
C# ou Visual Basic ne vous sont pas familières, consultez la page Expressions lambda en PLINQ et dans la bibliothèque
parallèle de tâches.

Le reste de cet article donne une vue d’ensemble des principales classes PLINQ et explique comment créer
des requêtes PLINQ. Chaque section contient des liens vers des exemples de code et des informations plus
détaillées.

Classe ParallelEnumerable
La classe System.Linq.ParallelEnumerable expose presque toutes les fonctionnalités de PLINQ. Celle-ci et le
reste des types d’espaces de noms System.Linq sont compilés dans l’assembly System.Core.dll. Les projets C#
et Visual Basic par défaut de Visual Studio font tous deux référence à l’assembly et importent l’espace de
noms.
ParallelEnumerable inclut les implémentations de tous les opérateurs de requête standard pris en charge par
LINQ to Objects, bien qu’il ne tente pas de paralléliser chacun d’eux. Si vous n’êtes pas familiarisé avec LINQ,
consultez Introduction à LINQ (C#) et Introduction à LINQ (Visual Basic).
Outre les opérateurs de requête standard, la classe ParallelEnumerable contient un ensemble de méthodes
qui activent des comportements spécifiques à l’exécution parallèle. Ces méthodes spécifiques de PLINQ sont
répertoriées dans le tableau suivant.

O P ÉRAT EUR PA RA L L EL EN UM ERA B L E DESC RIP T IO N

AsParallel Point d’entrée de PLINQ. Indique que le reste de la requête


doit être parallélisé, si possible.

AsSequential Indique que le reste de la requête doit être exécuté de


manière séquentielle, comme requête LINQ non parallèle.

AsOrdered Indique que PLINQ doit conserver l’ordre de la séquence


source pour le reste de la requête, ou jusqu’à ce que l’ordre
soit modifié, par exemple à l’aide d’une clause orderby
(Order By en Visual Basic).

AsUnordered Indique que PLINQ ne doit pas conserver l’ordre de la


séquence source pour le reste de la requête.

WithCancellation Indique que PLINQ doit régulièrement surveiller l’état du


jeton d’annulation fourni et annuler l’exécution si cela est
demandé.

WithDegreeOfParallelism Spécifie le nombre maximal de processeurs que PLINQ doit


utiliser pour paralléliser la requête.

WithMergeOptions Fournit une indication sur la manière dont PLINQ doit, si


possible, fusionner les résultats parallèles en une seule
séquence sur le thread utilisé.

WithExecutionMode Indique si PLINQ doit paralléliser la requête même si le


comportement par défaut consisterait à l’exécuter de
manière séquentielle.

ForAll Méthode d’énumération multithread qui, contrairement à


l’itération sur les résultats de la requête, permet leur
traitement en parallèle sans nécessiter la fusion préalable
dans le thread utilisé.

Aggregate surcharge Surcharge propre à PLINQ qui permet l’agrégation


intermédiaire sur des partitions locales des threads,et
fonction d’agrégation finale permettant de combiner les
résultats de toutes les partitions.

Modèle Opt-in
Lorsque vous écrivez une requête, utilisez PLINQ en appelant la méthode d’extension
ParallelEnumerable.AsParallel sur la source de données, comme illustré dans l’exemple suivant.
var source = Enumerable.Range(1, 10000);

// Opt in to PLINQ with AsParallel.


var evenNums = from num in source.AsParallel()
where num % 2 == 0
select num;
Console.WriteLine("{0} even numbers out of {1} total",
evenNums.Count(), source.Count());
// The example displays the following output:
// 5000 even numbers out of 10000 total

Dim source = Enumerable.Range(1, 10000)

' Opt in to PLINQ with AsParallel


Dim evenNums = From num In source.AsParallel()
Where num Mod 2 = 0
Select num
Console.WriteLine("{0} even numbers out of {1} total",
evenNums.Count(), source.Count())
' The example displays the following output:
' 5000 even numbers out of 10000 total

La méthode d’extension AsParallel lie les opérateurs de requête suivants, dans ce cas, where et select , aux
implémentations System.Linq.ParallelEnumerable.

Modes d’exécution
Par défaut, PLINQ est conservateur. Au moment de l’exécution, l’infrastructure PLINQ analyse la structure
globale de la requête. Si la requête est susceptible de produire des accélérations par parallélisation, PLINQ
partitionne la séquence source en tâches pouvant être exécutées simultanément. Si la parallélisation d’une
requête présente un risque, PLINQ exécute uniquement la requête de manière séquentielle. Si PLINQ a le
choix entre un algorithme parallèle potentiellement coûteux ou un algorithme séquentiel abordable, il choisit
l’algorithme séquentiel par défaut. Vous pouvez utiliser la méthode WithExecutionMode et l’énumération
System.Linq.ParallelExecutionMode pour indiquer à PLINQ de sélectionner l’algorithme parallèle. Cela est
utile lorsque vous savez suite à des tests ou des mesures qu’une requête spécifique s’exécute plus
rapidement en parallèle. Pour plus d’informations, consultez How to: Specify the Execution Mode in PLINQ
(Guide pratique pour spécifier le mode d’exécution dans PLINQ).

Degré de parallélisme
Par défaut, PLINQ utilise tous les processeurs de l’ordinateur hôte. Vous pouvez demander à PLINQ de ne pas
utiliser plus d’un nombre spécifié de processeurs à l’aide de la méthode WithDegreeOfParallelism. Cela est
utile lorsque vous souhaitez vous assurer que les autres processus en cours d’exécution sur l’ordinateur
reçoivent une certaine quantité de temps CPU. L’extrait suivant limite la requête à l’utilisation de deux
processeurs maximum.

var query = from item in source.AsParallel().WithDegreeOfParallelism(2)


where Compute(item) > 42
select item;

Dim query = From item In source.AsParallel().WithDegreeOfParallelism(2)


Where Compute(item) > 42
Select item

Si une requête effectue une quantité importante de travaux non liés au calcul, comme des E/S de fichier, il
peut être utile de spécifier un degré de parallélisme supérieur au nombre de cœurs de l’ordinateur.

Comparatif des requêtes parallèles ordonnées et non ordonnées


Dans certaines requêtes, un opérateur de requête doit produire des résultats qui conservent l’ordre de la
séquence source. PLINQ fournit l’opérateur AsOrdered à cet effet. AsOrdered est différent de AsSequential.
Une séquence AsOrdered est toujours traitée en parallèle, mais ses résultats sont mis en mémoire tampon et
triés. Étant donné que la conservation de l’ordre implique généralement un travail supplémentaire, une
séquence AsOrdered peut être traitée plus lentement que la séquence AsUnordered par défaut. Le fait qu’une
opération parallèle ordonnée de manière spécifique soit plus rapide qu’une version séquentielle de
l’opération dépend de nombreux facteurs.
L’exemple de code suivant montre comment utiliser la conservation de l’ordre.

var evenNums = from num in numbers.AsParallel().AsOrdered()


where num % 2 == 0
select num;

Dim evenNums = From num In numbers.AsParallel().AsOrdered()


Where num Mod 2 = 0
Select num

Pour plus d’informations, consultez Order Preservation in PLINQ (Conservation de l’ordre dans PLINQ).

Requêtes parallèles et requêtes séquentielles


Certaines opérations requièrent que la source de données soit proposée de manière séquentielle. Les
opérateurs de requête ParallelEnumerable basculent automatiquement en mode séquentiel lorsque cela est
nécessaire. Pour les opérateurs de requête et les délégués d’utilisateurs définis par l’utilisateur qui
nécessitent une exécution séquentielle, PLINQ fournit la méthode AsSequential. Lorsque vous utilisez
AsSequential, tous les opérateurs suivants dans la requête sont exécutés séquentiellement jusqu'à ce que
AsParallel soit à nouveau appelé. Pour plus d’informations, voir Comment : combiner des requêtes LINQ
parallèles et séquentielles.

Options de fusion des résultats de requête


Quand une requête PLINQ s’exécute en parallèle, les résultats issus de chaque thread de travail doivent être
refusionnés sur le thread principal pour être utilisés par une boucle foreach ( For Each en Visual Basic), ou
insérés dans une liste ou un tableau. Dans certains cas, il peut être utile de spécifier un type particulier
d’opération de fusion, par exemple, pour commencer à générer des résultats plus rapidement. Pour cela,
PLINQ prend en charge la méthode WithMergeOptions et l’énumération ParallelMergeOptions. Pour plus
d’informations, consultez l’article Merge Options in PLINQ (Options de fusion de PLINQ).

Opérateur ForAll
Dans les requêtes LINQ séquentielles, l’exécution est différée jusqu’à ce que la requête soit énumérée dans
une foreach For Each boucle (en Visual Basic) ou en appelant une méthode telle que ToList , ToArray ou
ToDictionary . Dans PLINQ, vous pouvez également utiliser foreach pour exécuter la requête et itérer dans
les résultats. Toutefois, foreach lui-même ne s’exécute pas en parallèle, et par conséquent, requiert que les
résultats de toutes les tâches parallèles soient refusionnés dans le thread sur lequel la boucle s’exécute. Dans
PLINQ, vous pouvez utiliser foreach lorsque vous devez conserver l’ordre final des résultats de requête, et
également chaque fois que vous traitez des résultats en série, par exemple, lorsque vous appelez
Console.WriteLine pour chaque élément. Pour une exécution plus rapide des requêtes lorsque la
conservation de l’ordre n’est pas nécessaire et lorsque le traitement des résultats peut lui-même être
parallélisé, utilisez la méthode ForAll pour exécuter une requête PLINQ. ForAll n’effectue pas cette dernière
étape de fusion. L'exemple de code suivant montre comment utiliser la méthode ForAll.
System.Collections.Concurrent.ConcurrentBag<T> est utilisée ici, car elle est optimisée pour l’ajout simultané
de plusieurs threads sans tentative de suppression d’éléments.

var nums = Enumerable.Range(10, 10000);


var query = from num in nums.AsParallel()
where num % 10 == 0
select num;

// Process the results as each thread completes


// and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
// which can safely accept concurrent add operations
query.ForAll(e => concurrentBag.Add(Compute(e)));

Dim nums = Enumerable.Range(10, 10000)


Dim query = From num In nums.AsParallel()
Where num Mod 10 = 0
Select num

' Process the results as each thread completes


' and add them to a System.Collections.Concurrent.ConcurrentBag(Of Int)
' which can safely accept concurrent add operations
query.ForAll(Sub(e) concurrentBag.Add(Compute(e)))

L’illustration suivante montre la différence entre foreach et ForAll en ce qui concerne l’exécution des
requêtes.

Annulation
PLINQ est intégré aux types d’annulation dans .NET Framework 4. (Pour plus d’informations, consultez
annulation dans les threads managés.) Par conséquent, contrairement aux requêtes LINQ to Objects
séquentielles, les requêtes PLINQ peuvent être annulées. Pour créer une requête PLINQ annulable, utilisez
l’opérateur WithCancellation sur la requête et fournissez une instance CancellationToken comme argument.
Lorsque la propriété IsCancellationRequested sur le jeton est définie sur true, PLINQ le remarque, arrête le
traitement sur tous les threads et lève une OperationCanceledException.
Il est possible qu’une requête PLINQ continue de traiter certains éléments après la définition du jeton
d’annulation.
Pour une plus grande réactivité, vous pouvez également répondre aux demandes d’annulation dans les
délégués d’utilisateur de longue durée. Pour plus d’informations, consultez How to: Cancel a PLINQ Query
(Guide pratique pour annuler une requête PLINQ).

Exceptions
Lorsqu’une requête PLINQ s’exécute, plusieurs exceptions peuvent être générées simultanément à partir de
plusieurs threads. En outre, le code destiné à traiter l’exception peut se trouver sur un thread différent de celui
du code ayant généré l’exception. PLINQ utilise le type AggregateException afin d’encapsuler toutes les
exceptions levées par une requête et de les marshaler à sur le thread appelant. Le thread appelant ne requiert
qu’un seul bloc try-catch. Toutefois, vous pouvez itérer sur toutes les exceptions encapsulées dans
AggregateException et intercepter celles à partir desquelles vous pouvez effectuer une récupération en toute
sécurité. Dans de rares cas, certaines exceptions qui ne sont pas encapsulées dans AggregateException
peuvent être levées, et les exceptions ThreadAbortException ne sont pas non plus incluses dans un wrapper.
Lorsque les exceptions sont autorisées à se propager vers le thread lié, il est possible qu'une requête puisse
continuer à traiter des éléments après que l'exception ait été levée.
Pour plus d’informations, consultez How to: Handle Exceptions in a PLINQ Query (Comment : traiter des
exceptions dans une requête PLINQ).

Partitionneurs personnalisés
Dans certains cas, vous pouvez améliorer les performances des requêtes en écrivant un partitionneur
personnalisé qui tire parti de certaines caractéristiques de la source de données. Dans la requête, le
partitionneur personnalisé lui-même est l’objet énumérable interrogé.

int[] arr = new int[9999];


Partitioner<int> partitioner = new MyArrayPartitioner<int>(arr);
var query = partitioner.AsParallel().Select(x => SomeFunction(x));

Dim arr(10000) As Integer


Dim partitioner As Partitioner(Of Integer) = New MyArrayPartitioner(Of Integer)(arr)
Dim query = partitioner.AsParallel().Select(Function(x) SomeFunction(x))

PLINQ prend en charge un nombre fixe de partitions (bien que les données puissent être réaffectées de
manière dynamique à ces partitions pendant l’exécution pour l’équilibrage de charge.). For et ForEach
prennent en charge uniquement le partitionnement dynamique, ce qui signifie que le nombre de partitions
change en cours d’exécution. Pour plus d’informations, consultez Partitionneurs personnalisés pour PLINQ et
la bibliothèque parallèle de tâches (TPL).

Mesure des performances de PLINQ


Dans de nombreux cas, une requête peut être parallélisée, mais la surcharge liée à la configuration de la
requête parallèle annule le gain obtenu en termes de performances. Si une requête n’effectue pas beaucoup
de calculs ou si la source de données est petite, une requête PLINQ peut être plus lente qu’une requête LINQ
to Objects séquentielle. Vous pouvez utiliser l’outil d’analyse des performances parallèles de Visual Studio
Team Server pour comparer les performances de diverses requêtes, localiser des goulots d’étranglement et
déterminer si votre requête s’exécute en parallèle ou de manière séquentielle. Pour plus d’informations,
consultez Visualiseur concurrentiel et How to: Measure PLINQ Query Performance (Comment : mesurer les
performances des requêtes PLINQ).
Voir aussi
Parallel LINQ (PLINQ)
Fonctionnement de l'accélération dans PLINQ
Fonctionnement de l'accélération dans PLINQ
18/07/2020 • 12 minutes to read • Edit Online

Le principal objectif de PLINQ est d’accélérer l’exécution de requêtes LINQ to Objects en exécutant parallèlement
les délégués de requête sur des ordinateurs multicœurs. PLINQ accomplit les meilleures performances lorsque
le traitement de chaque élément dans une collection source est indépendant, sans aucun état partagé parmi les
délégués individuels. Ces opérations sont courantes dans LINQ to Objects et PLINQ et sont souvent appelées
« délicieusement parallèles », car elles se prêtent facilement à la planification sur plusieurs threads. Toutefois,
toutes les requêtes ne sont pas entièrement constituées d’opérations délicieusement parallèles ; dans la plupart
des cas, une requête concerne certains opérateurs qui ne peuvent pas être parallélisés, ou qui ralentissent
l’exécution parallèle. Et même dans le cas de requêtes qui sont entièrement délicieusement parallèles, PLINQ
doit encore partitionner la source de données et planifier le travail sur les threads et, généralement, fusionner
les résultats lorsque la requête est terminée. Toutes ces opérations ajoutent au coût de calcul de la
parallélisation ; ces coûts d’ajout de parallélisation sont appelés surcharge. Pour optimiser les performances
dans une requête PLINQ, l’objectif est d’augmenter les parties délicieusement parallèles et de réduire au
minimum les parties qui nécessitent une surcharge. Cet article fournit des informations qui vous aideront à
écrire des requêtes PLINQ aussi efficaces que possible, tout en produisant des résultats corrects.

Facteurs ayant un impact sur les performances des requêtes


Les sections suivantes répertorient certains des facteurs plus importants ayant un impact sur les performances
des requêtes parallèles. Il s’agit d’instructions générales qui ne sont en soi pas suffisantes pour prédire les
performances des requêtes dans tous les cas. Comme toujours, il est important de mesurer les performances
réelles de requêtes spécifiques sur des ordinateurs avec une plage de configurations et de charges
représentatives.
1. Coût de calcul du travail global.
Pour atteindre l’accélération, une requête PLINQ doit avoir suffisamment de travail délicieusement
parallèle pour compenser la surcharge. Le travail peut être exprimé comme le coût de calcul de chaque
délégué multiplié par le nombre d’éléments dans la collection source. En supposant qu’une opération
peut être parallélisée, plus elle est coûteuse en matière de calcul, plus la possibilité d’accélération est
grande. Par exemple, si une fonction s’exécute en une milliseconde, une requête séquentielle de 1 000
éléments prendra une seconde, tandis qu’une requête parallèle sur un ordinateur doté de quatre cœurs
ne prendra probablement que 250 millisecondes. Il en résulte une accélération de 750 millisecondes. Si
la fonction mettait une seconde à s’exécuter pour chaque élément, l’accélération sera de 750 secondes. Si
le délégué est très coûteux, PLINQ peut offrir une accélération significative avec uniquement quelques
éléments dans la collection source. À l’inverse, de petites collections source avec des délégués triviaux ne
sont généralement pas bons candidats pour PLINQ.
Dans l’exemple suivant, queryA est probablement un bon candidat pour PLINQ, en supposant que sa
fonction Select implique de nombreuses tâches. queryB n’est probablement pas un bon candidat, car il
n’y a pas suffisamment de travail dans l’instruction Select, et la surcharge de parallélisation compensera
la plus grande partie ou la totalité de l’accélération.
Dim queryA = From num In numberList.AsParallel()
Select ExpensiveFunction(num); 'good for PLINQ

Dim queryB = From num In numberList.AsParallel()


Where num Mod 2 > 0
Select num; 'not as good for PLINQ

var queryA = from num in numberList.AsParallel()


select ExpensiveFunction(num); //good for PLINQ

var queryB = from num in numberList.AsParallel()


where num % 2 > 0
select num; //not as good for PLINQ

2. Nombre de cœurs logiques sur le système (degré de parallélisme).


Ce point est un corollaire évident de la section précédente : lLes requêtes délicieusement parallèles
s’exécutent plus rapidement sur des ordinateurs au nombre de cœurs plus élevé, car le travail peut être
divisé entre davantage de threads simultanés. L’accélération globale dépend du pourcentage de travail
global parallèle de la requête. Toutefois, ne supposez pas que toutes les requêtes s’exécuteront deux fois
plus vite sur un ordinateur à huit cœurs que sur un ordinateur à quatre cœurs. Lors du paramétrage des
requêtes pour des performances optimales, il est important de mesurer les résultats réels sur des
ordinateurs avec des nombres de cœurs différents. Ce point est lié au point numéro 1 : des jeux de
données plus volumineux sont nécessaires pour tirer parti de ressources de calcul plus importantes.
3. Le nombre et le type d’opérations.
PLINQ fournit l’opérateur AsOrdered pour les situations dans lesquelles il est nécessaire de conserver
l’ordre des éléments dans la séquence source. Un coût est associé à la commande, mais il est
généralement modeste. Les opérations GroupBy et Join entraînent également une surcharge. PLINQ offre
les meilleures performances lorsqu’elle est autorisée à traiter les éléments de la collection source dans
n’importe quel ordre et à les passer à l’opérateur suivant dès qu’ils sont prêts. Pour plus d’informations,
consultez Order Preservation in PLINQ (Conservation de l’ordre dans PLINQ).
4. Formulaire d'exécution des requêtes.
Si vous stockez les résultats d’une requête en appelant ToArray ou ToList, les résultats de tous les threads
parallèles doivent être fusionnés dans la structure de données unique. Ceci implique un coût de calcul
inévitable. De même, si vous itérez les résultats à l’aide d’une boucle foreach (For Each en Visual Basic),
les résultats des threads de travail doivent être sérialisés sur le thread de l’énumérateur. Mais si vous
souhaitez simplement effectuer une action en fonction du résultat de chaque thread, vous pouvez utiliser
la méthode ForAll pour exécuter ce travail sur plusieurs threads.
5. Le type des options de fusion.
PLINQ peut être configuré pour mettre en mémoire tampon sa sortie et en produire des segments ou la
totalité après avoir produit le jeu de résultats entier, ou enfin transmettre en continu des résultats
individuels à mesure qu'ils sont produits. Les anciens résultats dans le délai d'exécution général réduit et
les derniers résultats dans la latence réduite entre les éléments produits. Tandis que les options de fusion
n'ont pas toujours une incidence majeure sur les performances globales de la requête, elles peuvent
affecter les performances perçues dans la mesure où elles contrôlent la durée d'attente d'un utilisateur
avant consultation des résultats. Pour plus d’informations, consultez l’article Merge Options in PLINQ
(Options de fusion de PLINQ).
6. Types de partitionnement.
Dans certains cas, une requête PLINQ sur une collection source indexable peut entraîner une charge de
travail déséquilibrée. Lorsque cela se produit, vous pouvez peut-être augmenter les performances des
requêtes en créant un partitionneur personnalisé. Pour plus d’informations, consultez Partitionneurs
personnalisés pour PLINQ et la bibliothèque parallèle de tâches (TPL).

Lorsque PLINQ choisit le mode séquentiel


PLINQ essaiera toujours exécuter une requête au moins aussi rapidement que si elle était exécutée de manière
séquentielle. Bien que PLINQ ne tienne compte ni du coût de calcul des délégués de l’utilisateur, ni de la taille la
source d’entrée, il recherche certaines « formes » de requêtes. Plus précisément, il recherche des opérateurs de
requêtes ou des combinaisons d’opérateurs qui font qu’une requête s’exécute plus lentement en mode parallèle.
Lorsqu’il trouve de telles formes, par défaut PLINQ revient au mode séquentiel.
Toutefois, après avoir mesuré les performances d’une requête spécifique, vous pouvez déterminer qu’elle
s’exécute plus rapidement en mode parallèle. Dans ce cas, vous pouvez utiliser l’indicateur
ParallelExecutionMode.ForceParallelism via la méthode WithExecutionMode pour indiquer à PLINQ de
paralléliser la requête. Pour plus d’informations, consultez How to: Specify the Execution Mode in PLINQ (Guide
pratique pour spécifier le mode d’exécution dans PLINQ).
La liste suivante décrit les formes de requêtes que PLINQ exécutera en mode séquentiel par défaut :
Requêtes qui contiennent une instruction Select, Where indexée, SelectMany indexée ou ElementAt après
un opérateur de tri ou de filtrage qui a supprimé ou réorganisé les indices d’origine.
Requêtes qui contiennent un SkipWhile opérateur Take, TakeWhile, ignorer et où les index de la séquence
source ne sont pas dans l’ordre d’origine.
Requêtes qui contiennent Zip ou SequenceEquals, sauf si une des sources de données a un index
ordonné à l’origine et la source de données indexable (autrement dit, un tableau ou un IList(T)).
Requêtes qui contiennent Concat, sauf si elle est appliquée aux sources de données indexables.
Requêtes qui contiennent Reverse, sauf si elle est appliquée aux sources de données indexables.

Voir aussi
Parallel LINQ (PLINQ)
Conservation de l'ordre en PLINQ
18/07/2020 • 10 minutes to read • Edit Online

Dans PLINQ, l’objectif est d’augmenter les performances tout en préservant l’exactitude. Une requête doit
s’exécuter aussi rapidement que possible, mais toujours générer des résultats corrects. Dans certains cas,
l’exactitude requiert que l’ordre de la séquence source soit conservé ; toutefois, le classement peut coûter cher en
calcul. Par conséquent, par défaut, PLINQ ne conserve pas l’ordre de la séquence source. À cet égard, PLINQ est
similaire à LINQ to SQL, mais pas à LINQ to Objects, qui conserve le classement.
Pour remplacer le comportement par défaut, vous pouvez activer la conservation de l’ordre à l’aide de l’opérateur
AsOrdered sur la séquence source. Vous pouvez désactiver la préservation de l’ordre plus loin dans la requête à
l’aide de la méthode AsUnordered. Ces deux méthodes permettent de traiter la requête en fonction des
paramètres heuristiques, qui déterminent s’il faut exécuter la requête en parallèle ou de manière séquentielle.
Pour plus d’informations, consultez Fonctionnement de l’accélération dans PLINQ.
L’exemple suivant montre une requête parallèle non classée qui filtre tous les éléments qui correspondent à une
condition, sans essayer de classer les résultats de quelque manière que ce soit.

var cityQuery = (from city in cities.AsParallel()


where city.Population > 10000
select city)
.Take(1000);

Dim cityQuery = From city In cities.AsParallel()


Where City.Population > 10000
Take (1000)

Cette requête ne produit pas nécessairement les 1 000 premières villes de la séquence source qui remplissent la
condition, mais plutôt un ensemble de 1 000 villes qui remplissent la condition. Les opérateurs de requête PLINQ
partitionnent la séquence source en plusieurs sous-séquences qui sont traitées comme des tâches simultanées. Si
la conservation de l’ordre n’est pas spécifiée, les résultats de chaque partition sont remis à l’étape suivante de la
requête dans un ordre arbitraire. Par ailleurs, une partition peut donner un sous-ensemble de ses résultats avant
de continuer à traiter les éléments restants. L’ordre résultant peut être différent à chaque fois. Ceci ne peut pas être
contrôlé par votre application, car cela dépend de la manière dont le système d’exploitation planifie les threads.
L’exemple suivant remplace le comportement par défaut à l’aide de l’opérateur AsOrdered sur la séquence source.
Cela garantit que la méthode Take retourne les 1 000 premières villes de la séquence source qui remplissent la
condition.

var orderedCities = (from city in cities.AsParallel().AsOrdered()


where city.Population > 10000
select city)
.Take(1000);

Dim orderedCities = From city In cities.AsParallel().AsOrdered()


Where City.Population > 10000
Take (1000)

Toutefois, cette requête ne s’exécute probablement pas aussi rapidement que la version non classée, car elle doit
surveiller l’ordre d’origine dans toutes les partitions et s’assurer que le classement est cohérent au moment de la
fusion. Par conséquent, nous vous recommandons de n’utiliser AsOrdered que si c’est nécessaire, et uniquement
pour les parties de la requête qui l’exigent. Lorsque la conservation de l’ordre n’est plus nécessaire, utilisez
AsUnordered pour la désactiver. L’exemple suivant obtient ce résultat en composant deux requêtes.

var orderedCities2 = (from city in cities.AsParallel().AsOrdered()


where city.Population > 10000
select city)
.Take(1000);

var finalResult = from city in orderedCities2.AsUnordered()


join p in people.AsParallel() on city.Name equals p.CityName into details
from c in details
select new { Name = city.Name, Pop = city.Population, Mayor = c.Mayor };

foreach (var city in finalResult) { /*...*/ }

Dim orderedCities2 = From city In cities.AsParallel().AsOrdered()


Where city.Population > 10000
Select city
Take (1000)

Dim finalResult = From city In orderedCities2.AsUnordered()


Join p In people.AsParallel() On city.Name Equals p.CityName
Select New With {.Name = city.Name, .Pop = city.Population, .Mayor = city.Mayor}

For Each city In finalResult


Console.WriteLine(city.Name & ":" & city.Pop & ":" & city.Mayor)
Next

Notez que PLINQ conserve le classement d’une séquence produite par des opérateurs imposant un ordre pour le
reste de la requête. En d’autres termes, les opérateurs tels que OrderBy et ThenBy sont traités comme s’ils étaient
suivis d’un appel à AsOrdered.

Opérateurs de requête et de classement


Les opérateurs de requête suivants présentent la conservation de l’ordre dans toutes les opérations suivantes
d’une requête, ou jusqu'à ce que AsUnordered soit appelée :
OrderBy
OrderByDescending
ThenBy
ThenByDescending
dans certains cas, les opérateurs de requête PLINQ suivants peuvent exiger des séquences source classées pour
produire des résultats corrects :
Reverse
SequenceEqual
TakeWhile
SkipWhile
Zip
certains opérateurs de requête PLINQ se comportent différemment, selon que leur séquence source est classée ou
non classée. Le tableau suivant répertorie ces opérateurs.

RÉSULTAT LO RSQ UE L A SÉQ UEN C E RÉSULTAT DE L A SÉQ UEN C E SO URC E


O P ÉRAT EUR SO URC E EST C L A SSÉE N ’EST PA S C L A SSÉE

Aggregate Sortie non déterministe pour les Sortie non déterministe pour les
opérations non associatives ou non opérations non associatives ou non
commutatives commutatives

All Non applicable Non applicable

Any Non applicable Non applicable

AsEnumerable Non applicable Non applicable

Average Sortie non déterministe pour les Sortie non déterministe pour les
opérations non associatives ou non opérations non associatives ou non
commutatives commutatives

Cast Résultats classés Résultats non classés

Concat Résultats classés Résultats non classés

Count Non applicable Non applicable

DefaultIfEmpty Non applicable Non applicable

Distinct Résultats classés Résultats non classés

ElementAt Retourner un élément spécifié Élément arbitraire

ElementAtOrDefault Retourner un élément spécifié Élément arbitraire

Except Résultats non classés Résultats non classés

First Retourner un élément spécifié Élément arbitraire

FirstOrDefault Retourner un élément spécifié Élément arbitraire

ForAll Exécute de façon non déterministe en Exécute de façon non déterministe en


parallèle parallèle

GroupBy Résultats classés Résultats non classés

GroupJoin Résultats classés Résultats non classés

Intersect Résultats classés Résultats non classés

Join Résultats classés Résultats non classés

Last Retourner un élément spécifié Élément arbitraire

LastOrDefault Retourner un élément spécifié Élément arbitraire


RÉSULTAT LO RSQ UE L A SÉQ UEN C E RÉSULTAT DE L A SÉQ UEN C E SO URC E
O P ÉRAT EUR SO URC E EST C L A SSÉE N ’EST PA S C L A SSÉE

LongCount Non applicable Non applicable

Min Non applicable Non applicable

OrderBy Réorganise la séquence Démarre une nouvelle section classée

OrderByDescending Réorganise la séquence Démarre une nouvelle section classée

Range Non applicable (même valeur par Non applicable


défaut que AsParallel)

Repeat Non applicable (même valeur par Non applicable


défaut que AsParallel)

Reverse Inverse Sans effet

Select Résultats classés Résultats non classés

Select (indexé) Résultats classés Résultats non classés.

SelectMany Résultats classés. Résultats non classés

SelectMany (indexé) Résultats classés. Résultats non classés.

SequenceEqual Comparaison classée Comparaison non classée

Single Non applicable Non applicable

SingleOrDefault Non applicable Non applicable

Skip Ignore les n premiers éléments Ignore les n éléments

SkipWhile Résultats classés. Non déterministe. Exécute SkipWhile


dans l’ordre arbitraire actuel

Sum Sortie non déterministe pour les Sortie non déterministe pour les
opérations non associatives ou non opérations non associatives ou non
commutatives commutatives

Take Prend les premiers éléments n Prend tous les éléments n

TakeWhile Résultats classés Non déterministe. Exécute TakeWhile


dans l’ordre arbitraire actuel

ThenBy Complète OrderBy Complète OrderBy

ThenByDescending Complète OrderBy Complète OrderBy

ToArray Résultats classés Résultats non classés


RÉSULTAT LO RSQ UE L A SÉQ UEN C E RÉSULTAT DE L A SÉQ UEN C E SO URC E
O P ÉRAT EUR SO URC E EST C L A SSÉE N ’EST PA S C L A SSÉE

ToDictionary Non applicable Non applicable

ToList Résultats classés Résultats non classés

ToLookup Résultats classés Résultats non classés

Union Résultats classés Résultats non classés

Where Résultats classés Résultats non classés

Where (indexé) Résultats classés Résultats non classés

Zip Résultats classés Résultats non classés

Les résultats non classés ne sont pas mélangés de manière active ; aucune logique de classement spéciale ne leur
est appliquée. Dans certains cas, une requête non classée peut conserver le classement de la séquence source.
Pour les requêtes utilisant l’opérateur Select indexé, PLINQ garantit que les éléments de sortie arriveront dans
l’ordre des indices croissant, mais n’offre aucune garantie quant aux index qui seront affectés aux éléments
respectifs.

Voir aussi
Parallel LINQ (PLINQ)
Programmation parallèle
Options de fusion en PLINQ
18/07/2020 • 7 minutes to read • Edit Online

Quand une requête s’exécute en parallèle, PLINQ partitionne la séquence source pour que plusieurs threads
puissent fonctionner simultanément sur différentes parties, généralement sur des threads distincts. Si les résultats
doivent être utilisés sur un thread, par exemple, dans une boucle foreach ( For Each en Visual Basic), les résultats
de chaque thread doivent être fusionnés de nouveau en une séquence. Le type de fusion que PLINQ exécute
dépend des opérateurs présents dans la requête. Par exemple, les opérateurs qui imposent un nouvel ordre des
résultats doivent mettre en mémoire tampon tous les éléments de tous les threads. Du point de vue du thread
utilisateur (qui est également celui de l’utilisateur de l’application), une requête entièrement mise en mémoire
tampon peut s’exécuter pendant un certain temps avant qu’elle ne génère son premier résultat. D’autres
opérateurs, par défaut, sont partiellement mis en mémoire tampon. Ils transmettent leurs résultats par lots.
L’opérateur ForAll n’est pas mis en mémoire tampon par défaut. Il transmet immédiatement tous les éléments à
partir de tous les threads.
À l’aide de la méthode WithMergeOptions, comme indiqué dans l’exemple suivant, vous pouvez fournir un
indicateur à PLINQ spécifiant le type de fusion à exécuter.

var scanLines = from n in nums.AsParallel()


.WithMergeOptions(ParallelMergeOptions.NotBuffered)
where n % 2 == 0
select ExpensiveFunc(n);

Dim scanlines = From n In nums.AsParallel().WithMergeOptions(ParallelMergeOptions.NotBuffered)


Where n Mod 2 = 0
Select ExpensiveFunc(n)

Pour obtenir un exemple complet, consultez Comment : spécifier des options de fusion en PLINQ.
Si la requête ne peut pas prendre en charge l’option demandée, cette dernière sera simplement ignorée. Dans la
plupart des cas, il n’est pas nécessaire de spécifier une option de fusion pour une requête PLINQ. Toutefois, dans
certains cas, après avoir effectué des tests et des mesures, vous pouvez trouver qu’une requête s’exécute mieux
dans un mode non défini par défaut. Cette option est souvent utilisée pour forcer un opérateur de fusion de blocs
à diffuser ses résultats en continu afin de fournir une interface utilisateur plus réactive.

ParallelMergeOptions
L’énumération ParallelMergeOptions inclut les options suivantes qui spécifient, pour les formes de requête prises
en charge, la manière dont la sortie finale de la requête est transmise quand les résultats sont utilisés sur un
thread :
Not Buffered

Avec l’option NotBuffered, chaque élément traité est retourné à partir de chaque thread dès qu’il est
généré. Ce comportement revient à « diffuser en continu » la sortie. Si l’opérateur AsOrdered est présent
dans la requête, NotBuffered conserve l’ordre des éléments sources. Bien que NotBuffered commence à
produire des résultats dès qu’ils sont disponibles, la durée totale de production de tous les résultats peut
toujours être plus longue que l’utilisation de l’une des autres options de fusion.
Auto Buffered
Avec l’option AutoBuffered, la requête regroupe des éléments dans une mémoire tampon, puis transmet
régulièrement tout le contenu de cette mémoire simultanément au thread utilisateur. Cette option revient à
transmettre les données sources dans des « blocs » au lieu d’utiliser le comportement de « diffusion en
continu » de NotBuffered . AutoBuffered peut nécessiter plus de temps que NotBuffered pour rendre le
premier élément disponible sur le thread utilisateur. La taille de la mémoire tampon et le comportement
exact de transmission ne sont pas configurables et peuvent varier en fonction de différents facteurs liés à la
requête.
FullyBuffered

Avec l’option FullyBuffered, la sortie de la requête entière est mise en mémoire tampon avant que l’un des
éléments ne soit transmis. Cette option peut nécessiter plus de temps pour que le premier élément soit
disponible sur le thread utilisateur, mais les résultats complets peuvent toujours être générés plus
rapidement qu’avec les autres options.

Opérateurs de requête prenant en charge les options de fusion


Le tableau suivant répertorie les opérateurs qui prennent en charge tous les modes d’options de fusion, qui sont
soumis aux restrictions spécifiées.

O P ÉRAT EUR REST RIC T IO N S

AsEnumerable None

Cast None

Concat Requêtes non ordonnées qui ont uniquement une source de


type Tableau ou Liste.

DefaultIfEmpty None

OfType None

Reverse Requêtes non ordonnées qui ont uniquement une source de


type Tableau ou Liste.

Select None

SelectMany None

Skip None

Take None

Where None

Tous les autres opérateurs de requête PLINQ peuvent ignorer les options de fusion fournis par l’utilisateur.
Certains opérateurs de requête, tels que Reverse et OrderBy, ne peuvent pas transmettre d’éléments tant qu’ils
n’ont pas tous été générés et réorganisés. Par conséquent, si vous utilisez ParallelMergeOptions dans une requête
qui contient également un opérateur tel que Reverse, le comportement de fusion ne sera appliqué dans la requête
qu’une fois que l’opérateur aura généré ses résultats.
La capacité de certains opérateurs à gérer les options de fusion varie selon le type de la séquence source et selon
que l’opérateur AsOrdered a été utilisé antérieurement dans la requête ou non. ForAll est toujours NotBuffered ; il
transmet immédiatement ses éléments. OrderBy est toujours FullyBuffered ; il doit trier l’intégralité de la liste
avant toute transmission.

Voir aussi
Parallel LINQ (PLINQ)
Procédure : spécifier des options de fusion avec PLINQ
Pièges potentiels avec PLINQ
18/07/2020 • 11 minutes to read • Edit Online

Dans de nombreux cas, PLINQ permet d’améliorer les performances de manière significative par rapport à des
requêtes LINQ to Objects séquentielles. Toutefois, le travail de parallélisation de l’exécution de la requête présente
une certaine complexité pouvant entraîner des problèmes qui, dans du code séquentiel, ne sont pas si courants ou
ne surviennent pas du tout. Cette rubrique répertorie des pratiques à éviter lorsque vous écrivez des requêtes
PLINQ.

Ne partez pas du principe que Parallel est toujours plus rapide


Parfois, la parallélisation entraîne l’exécution plus lente d’une requête PLINQ que son LINQ to Objects équivalent.
La règle empirique de base veut que les requêtes ayant peu d’éléments source et des délégués utilisateurs rapides
ne sont pas susceptibles d’apporter une grande accélération. Toutefois, étant donné que de nombreux facteurs sont
impliqués dans les performances, nous vous recommandons de mesurer les résultats réels avant de décider si vous
utiliserez PLINW. Pour plus d’informations, consultez Fonctionnement de l’accélération dans PLINQ.

Éviter d’écrire dans des emplacements de mémoire partagée


Dans du code séquentiel, il n’est pas rare de lire des variables statiques ou d’écrire dans ces dernières ou dans des
champs de classe. Toutefois, l’accès simultané de plusieurs threads à de telles variables entraîne un fort risque
d’engorgement. Bien que vous puissiez utiliser des verrous pour synchroniser l’accès à la variable, le coût de
synchronisation peut nuire aux performances. Par conséquent, nous vous recommandons d’éviter, ou au moins de
limiter autant que possible l’accès à un état partagé dans une requête PLINQ.

Éviter les surparallélisations


À l’aide de la AsParallel méthode, vous encourez les frais généraux liés au partitionnement de la collection source
et à la synchronisation des threads de travail. Les avantages de la parallélisation sont également limités par le
nombre de processeurs de l’ordinateur. L’exécution de plusieurs threads liés au calcul sur un seul processeur ne
permet aucune accélération. Par conséquent, vous devez veiller à ne pas surparalléliser une requête.
Les requêtes imbriquées sont le scénario le plus courant dans lequel une surparallélisation peut se produire,
comme le montre l’extrait suivant.

var q = from cust in customers.AsParallel()


from order in cust.Orders.AsParallel()
where order.OrderDate > date
select new { cust, order };

Dim q = From cust In customers.AsParallel()


From order In cust.Orders.AsParallel()
Where order.OrderDate > aDate
Select New With {cust, order}

Dans ce cas, il est préférable de paralléliser uniquement la source de données externe (clients), sauf si une ou
plusieurs conditions suivantes s’appliquent :
La source de données interne (cust.Orders) est connue pour être très longue.
Vous effectuez un calcul coûteux sur chaque commande. (l’opération montrée dans l’exemple n’est pas
coûteuse)
Le système cible est connu pour avoir suffisamment de processeurs pour gérer le nombre de threads
produits en parallélisant la requête sur cust.Orders .
Dans tous les cas, le test et la mesure sont la meilleure façon de déterminer la forme de requête optimale. Pour plus
d’informations, consultez Comment : mesurer les performances des requêtes PLINQ.

Éviter les appels à des méthodes non thread-safe


L’écriture dans des méthodes d’instance qui ne sont pas thread-safe à partir d’une requête PLINQ peut entraîner
une corruption des données qui peut être détectée ou non dans votre programme. Cela peut également entraîner
des exceptions. Dans l’exemple suivant, plusieurs threads tenteraient d’appeler simultanément la méthode
FileStream.Write , qui n’est pas prise en charge par la classe.

Dim fs As FileStream = File.OpenWrite(…)


a.AsParallel().Where(...).OrderBy(...).Select(...).ForAll(Sub(x) fs.Write(x))

FileStream fs = File.OpenWrite(...);
a.AsParallel().Where(...).OrderBy(...).Select(...).ForAll(x => fs.Write(x));

Limiter les appels aux méthodes thread-safe


La plupart des méthodes statiques du .NET Framework sont thread-safe et peuvent être appelées à partir de
plusieurs threads simultanément. Toutefois, même dans ces cas, la synchronisation impliquée peut entraîner un
ralentissement significatif de la requête.

NOTE
Vous pouvez le tester vous-même en insérant des appels à WriteLine dans vos requêtes. Bien que cette méthode soit utilisée
dans les exemples de documentation destinés à la démonstration, ne l’utilisez pas dans les requêtes PLINQ.

Éviter les opérations de tri inutiles


Lorsque PLINQ exécute une requête en parallèle, il divise la séquence source en partitions qui peuvent être traitées
simultanément sur plusieurs threads. Par défaut, l’ordre dans lequel les partitions sont traitées et les résultats sont
remis n’est pas prévisible (à l’exception des opérateurs tels que OrderBy ). Vous pouvez demander à PLINQ de
conserver le classement de toute séquence source, mais cela a un impact négatif sur les performances. Si possible,
la meilleure pratique consiste à structurer les requêtes afin qu’elles ne reposent pas sur la conservation de l’ordre.
Pour plus d’informations, consultez Order Preservation in PLINQ (Conservation de l’ordre dans PLINQ).

Préférer ForAll à ForEach quand c’est possible


Bien que PLINQ exécute une requête sur plusieurs threads, si vous consommez les résultats dans une boucle
foreach ( For Each en Visual Basic), les résultats de la requête doivent être fusionnés dans un thread et consultés
de façon séquentielle par l’énumérateur. Dans certains cas, c’est inévitable ; toutefois, si possible, utilisez la méthode
ForAll pour activer chaque thread afin qu’il sorte ses propres résultats, par exemple, en écrivant dans une
collection thread-safe, telle que System.Collections.Concurrent.ConcurrentBag<T>.
Le même problème s’applique à Parallel.ForEach . En d’autres termes, source.AsParallel().Where().ForAll(...) doit
être fortement préféré à Parallel.ForEach(source.AsParallel().Where(), ...) .
Tenez compte des problèmes d’affinité de thread
Certaines technologies, par exemple, les composants STA (Single-Threaded Apartment), Windows Forms et
Windows Presentation Foundation (WPF) imposent des restrictions d’affinité de thread qui requièrent l’exécution
de code sur un thread spécifique. Par exemple, dans Windows Forms et WPF, un contrôle est uniquement accessible
sur le thread sur lequel il a été créé. Si vous essayez d’accéder à l’état partagé d’un contrôle Windows Forms dans
une requête PLINQ, une exception est levée si vous exécutez dans le débogueur. (Ce paramètre peut être désactivé.)
Toutefois, si votre requête est consommée sur le thread d’interface utilisateur, vous pouvez accéder au contrôle à
partir de la foreach boucle qui énumère les résultats de la requête, car ce code s’exécute sur un seul thread.

Ne partez pas du principe que les itérations de ForEach, for et ForAll


s’exécutent toujours en parallèle
Il est important de garder à l’esprit que les itérations individuelles dans une Parallel.For Parallel.ForEach boucle, ou
ForAll peuvent, mais n’ont pas besoin de s’exécuter en parallèle. Par conséquent, vous devez éviter d’écrire du code
dont l’exactitude dépend de l’exécution parallèle d’itérations ou de l’exécution d’itérations dans un ordre particulier.
Par exemple, ce code est susceptible d’interbloquer :

Dim mre = New ManualResetEventSlim()


Enumerable.Range(0, Environment.ProcessorCount * 100).AsParallel().ForAll(Sub(j)
If j = Environment.ProcessorCount Then
Console.WriteLine("Set on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j)
mre.Set()
Else
Console.WriteLine("Waiting on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j)
mre.Wait()
End If
End Sub) ' deadlocks

ManualResetEventSlim mre = new ManualResetEventSlim();


Enumerable.Range(0, Environment.ProcessorCount * 100).AsParallel().ForAll((j) =>
{
if (j == Environment.ProcessorCount)
{
Console.WriteLine("Set on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j);
mre.Set();
}
else
{
Console.WriteLine("Waiting on {0} with value of {1}", Thread.CurrentThread.ManagedThreadId, j);
mre.Wait();
}
}); //deadlocks

Dans cet exemple, une itération définit un événement que toutes les autres itérations attendent. Aucune des
itérations en attente ne peut s’achever tant que l’itération de définition d’événement n’est pas terminée. Toutefois, il
est possible que les itérations en attente bloquent tous les threads utilisés pour exécuter la boucle parallèle, avant
que l’itération de définition d’événement ait eu une chance de s’exécuter. Cela provoque un interblocage : l’itération
de définition d’événement ne s’exécute jamais et les itérations en attente ne s’activent pas non plus.
En particulier, une itération de boucle parallèle ne doit jamais attendre une autre itération de la boucle pour
progresser. Si la boucle parallèle décide de planifier les itérations de manière séquentielle, mais dans l’ordre
inverse, un interblocage se produit.

Voir aussi
Parallel LINQ (PLINQ)
Procédure : créer et exécuter une requête PLINQ
simple
18/07/2020 • 3 minutes to read • Edit Online

L’exemple de cet article montre comment créer une requête LINQ (Parallel Language Integrated Query) simple en
utilisant la ParallelEnumerable.AsParallel méthode d’extension sur la séquence source et en exécutant la requête à
l’aide de la ForAll méthode.

NOTE
Cette documentation utilise des expressions lambda pour définir les délégués en PLINQ. Si les expressions lambda en C# ou
Visual Basic ne vous sont pas familières, consultez la page Expressions lambda en PLINQ et dans la bibliothèque parallèle de
tâches.

Exemple
using System;
using System.Linq;

public class Example


{
public static void Main()
{
var source = Enumerable.Range(100, 20000);

// Result sequence might be out of order.


var parallelQuery = from num in source.AsParallel()
where num % 10 == 0
select num;

// Process result sequence in parallel


parallelQuery.ForAll((e) => DoSomething(e));

// Or use foreach to merge results first.


foreach (var n in parallelQuery) {
Console.WriteLine(n);
}

// You can also use ToArray, ToList, etc as with LINQ to Objects.
var parallelQuery2 = (from num in source.AsParallel()
where num % 10 == 0
select num).ToArray();

// Method syntax is also supported


var parallelQuery3 = source.AsParallel().Where(n => n % 10 == 0).Select(n => n);

Console.WriteLine("\nPress any key to exit...");


Console.ReadLine();
}

static void DoSomething(int i) { }


}
Imports System.Linq

Module Example
Public Sub Main()
Dim source = Enumerable.Range(100, 20000)

' Result sequence might be out of order.


Dim parallelQuery = From num In source.AsParallel()
Where num Mod 10 = 0
Select num

' Process result sequence in parallel


parallelQuery.ForAll(Sub(e)
DoSomething(e)
End Sub)

' Or use For Each to merge results first


' as in this example, Where results must
' be serialized sequentially through static Console method.
For Each n In parallelQuery
Console.Write("{0} ", n)
Next

' You can also use ToArray, ToList, etc, as with LINQ to Objects.
Dim parallelQuery2 = (From num In source.AsParallel()
Where num Mod 10 = 0
Select num).ToArray()

' Method syntax is also supported


Dim parallelQuery3 = source.AsParallel().Where(Function(n)
Return (n Mod 10) = 0
End Function).Select(Function(n)
Return n
End Function)

For Each i As Integer In parallelQuery3


Console.Write("{0} ", i)
Next

Console.WriteLine()
Console.WriteLine("Press any key to exit...")
Console.ReadLine()
End Sub

' A toy function to demonstrate syntax. Typically you need a more


' computationally expensive method to see speedup over sequential queries.
Sub DoSomething(ByVal i As Integer)
Console.Write("{0:###.## }", Math.Sqrt(i))
End Sub
End Module

Cet exemple illustre le modèle de base pour la création et l’exécution de toute requête Parallel LINQ lorsque l’ordre
de la séquence de résultat n’est pas important. Les requêtes non ordonnées sont généralement plus rapides que les
requêtes classées. La requête partitionne la source en tâches qui sont exécutées de façon asynchrone sur plusieurs
threads. L'ordre dans lequel chaque tâche se termine ne dépend pas uniquement de la quantité de travail impliquée
pour traiter les éléments de la partition, mais également de facteurs externes tels que la façon dont le système
d'exploitation planifie chaque thread. Cet exemple, destiné à illustrer l'utilisation, peut ne pas s'exécuter plus
rapidement que la requête LINQ to Objects séquentielle équivalente. Pour plus d’informations sur l’accélération,
consultez Fonctionnement de l’accélération dans PLINQ. Pour plus d'informations sur la conservation du
classement des éléments d'une requête, consultez Comment : contrôler l'ordre dans une requête PLINQ.

Voir aussi
Parallel LINQ (PLINQ)
Procédure : contrôler l’ordre dans une requête PLINQ
18/07/2020 • 4 minutes to read • Edit Online

Ces exemples montrent comment contrôler le classement d’une requête PLINQ à l’aide de la méthode d’extension
AsOrdered.

WARNING
Ces exemples, principalement destinés à illustrer l'utilisation, peuvent ou non s'exécuter plus rapidement que les requêtes
LINQ to Objects séquentielle équivalentes.

Exemple
L’exemple suivant conserve l’ordre de la séquence source. Cela est parfois nécessaire, par exemple si certains
opérateurs de requête nécessitent une séquence source classée pour produire des résultats corrects.

var source = Enumerable.Range(9, 10000);

// Source is ordered; let's preserve it.


var parallelQuery = from num in source.AsParallel().AsOrdered()
where num % 3 == 0
select num;

// Use foreach to preserve order at execution time.


foreach (var v in parallelQuery)
Console.Write("{0} ", v);

// Some operators expect an ordered source sequence.


var lowValues = parallelQuery.Take(10);

Sub OrderedQuery()

Dim source = Enumerable.Range(9, 10000)

' Source is ordered let's preserve it.


Dim parallelQuery = From num In source.AsParallel().AsOrdered()
Where num Mod 3 = 0
Select num

' Use For Each to preserve order at execution time.


For Each item In parallelQuery
Console.Write("{0} ", item)
Next

' Some operators expect an ordered source sequence.


Dim lowValues = parallelQuery.Take(10)

End Sub

Exemple
L’exemple suivant montre certains opérateurs de requête dont la séquence source est probablement prévue pour
être classée. Ces opérateurs fonctionneront sur les séquences non classées, mais ils peuvent produire des résultats
inattendus.
// Paste into PLINQDataSample class.
static void SimpleOrdering()
{

var customers = GetCustomers();

// Take the first 20, preserving the original order


var firstTwentyCustomers = customers
.AsParallel()
.AsOrdered()
.Take(20);

foreach (var c in firstTwentyCustomers)


Console.Write("{0} ", c.CustomerID);

// All elements in reverse order.


var reverseOrder = customers
.AsParallel()
.AsOrdered()
.Reverse();

foreach (var v in reverseOrder)


Console.Write("{0} ", v.CustomerID);

// Get the element at a specified index.


var cust = customers.AsParallel()
.AsOrdered()
.ElementAt(48);

Console.WriteLine("Element #48 is: {0}", cust.CustomerID);


}
' Paste into PLINQDataSample class
Shared Sub SimpleOrdering()
Dim customers As List(Of Customer) = GetCustomers().ToList()

' Take the first 20, preserving the original order

Dim firstTwentyCustomers = customers _


.AsParallel() _
.AsOrdered() _
.Take(20)

Console.WriteLine("Take the first 20 in original order")


For Each c As Customer In firstTwentyCustomers
Console.Write(c.CustomerID & " ")
Next

' All elements in reverse order.


Dim reverseOrder = customers _
.AsParallel() _
.AsOrdered() _
.Reverse()

Console.WriteLine(vbCrLf & "Take all elements in reverse order")


For Each c As Customer In reverseOrder
Console.Write("{0} ", c.CustomerID)
Next
' Get the element at a specified index.
Dim cust = customers.AsParallel() _
.AsOrdered() _
.ElementAt(48)

Console.WriteLine("Element #48 is: " & cust.CustomerID)

End Sub

Pour exécuter cette méthode, collez-la dans la classe PLINQDataSample du projet Exemple de données PLINQ, puis
appuyez sur F5.

Exemple
L’exemple suivant montre comment conserver le classement pour la première partie d’une requête, comment
supprimer le classement afin d’augmenter les performances d’une clause join, puis comment réappliquer le
classement à la séquence de résultat finale.
// Paste into PLINQDataSample class.
static void OrderedThenUnordered()
{

var orders = GetOrders();


var orderDetails = GetOrderDetails();

var q2 = orders.AsParallel()
.Where(o => o.OrderDate < DateTime.Parse("07/04/1997"))
.Select(o => o)
.OrderBy(o => o.CustomerID) // Preserve original ordering for Take operation.
.Take(20)
.AsUnordered() // Remove ordering constraint to make join faster.
.Join(
orderDetails.AsParallel(),
ord => ord.OrderID,
od => od.OrderID,
(ord, od) =>
new
{
ID = ord.OrderID,
Customer = ord.CustomerID,
Product = od.ProductID
}
)
.OrderBy(i => i.Product); // Apply new ordering to final result sequence.

foreach (var v in q2)


Console.WriteLine("{0} {1} {2}", v.ID, v.Customer, v.Product);
}

' Paste into PLINQDataSample class


Sub OrderedThenUnordered()
Dim Orders As IEnumerable(Of Order) = GetOrders()
Dim orderDetails As IEnumerable(Of OrderDetail) = GetOrderDetails()

' Sometimes it's easier to create a query


' by composing two subqueries
Dim query1 = From ord In Orders.AsParallel()
Where ord.OrderDate < DateTime.Parse("07/04/1997")
Select ord
Order By ord.CustomerID
Take 20

Dim query2 = From ord In query1.AsUnordered()


Join od In orderDetails.AsParallel() On ord.OrderID Equals od.OrderID
Order By od.ProductID
Select New With {ord.OrderID, ord.CustomerID, od.ProductID}

For Each item In query2


Console.WriteLine("{0} {1} {2}", item.OrderID, item.CustomerID, item.ProductID)
Next
End Sub

Pour exécuter cette méthode, collez-la dans la classe PLINQDataSample du projet Exemple de données PLINQ, puis
appuyez sur F5.

Voir aussi
ParallelEnumerable
Parallel LINQ (PLINQ)
Procédure : combiner des requêtes LINQ parallèles et
séquentielles
18/07/2020 • 2 minutes to read • Edit Online

Cet exemple montre comment utiliser la méthode AsSequential pour indiquer à PLINQ de traiter de manière
séquentielle tous les opérateurs suivants dans la requête. Bien que le traitement séquentiel soit souvent plus lent
que parallèle, il est parfois nécessaire de produire des résultats corrects.

NOTE
Cet exemple est destiné à illustrer l’utilisation et peut ne pas s’exécuter plus rapidement que la requête LINQ to Objects
séquentielle équivalente. Pour plus d’informations sur l’accélération, consultez Fonctionnement de l’accélération dans PLINQ.

Exemple
L’exemple suivant montre un scénario dans lequel AsSequential est obligatoire, pour conserver le classement qui a
été établi dans une clause précédente de la requête.

// Paste into PLINQDataSample class.


static void SequentialDemo()
{
var orders = GetOrders();
var query = (from ord in orders.AsParallel()
orderby ord.CustomerID
select new
{
Details = ord.OrderID,
Date = ord.OrderDate,
Shipped = ord.ShippedDate
}).
AsSequential().Take(5);
}

' Paste into PLINQDataSample class


Shared Sub SequentialDemo()

Dim orders = GetOrders()


Dim query = From ord In orders.AsParallel()
Order By ord.CustomerID
Select New With
{
ord.OrderID,
ord.OrderDate,
ord.ShippedDate
}

Dim query2 = query.AsSequential().Take(5)

For Each item In query2


Console.WriteLine("{0}, {1}, {2}", item.OrderDate, item.OrderID, item.ShippedDate)
Next
End Sub
Compilation du code
Pour compiler et exécuter ce code, collez-le dans le projet d' exemple de données PLINQ , ajoutez une ligne pour
appeler la méthode à partir de Main et appuyez sur F5 .

Voir aussi
Parallel LINQ (PLINQ)
Procédure : gérer des exceptions dans une requête
PLINQ
18/07/2020 • 6 minutes to read • Edit Online

Le premier exemple de cette rubrique montre comment gérer l’exception System.AggregateException qui peut être
levée à partir d’une requête PLINQ au cours de son exécution. Le deuxième exemple montre comment placer des
blocs try-catch dans des délégués, le plus près possible de l’emplacement où l’exception sera levée. Vous pouvez
ainsi les intercepter dès qu’ils se produisent et éventuellement poursuivre l’exécution de la requête. Lorsque les
exceptions sont autorisées à se propager vers le thread lié, il est possible qu'une requête puisse continuer à traiter
des éléments après que l'exception ait été levée.
Dans certains cas, quand PLINQ revient à l’exécution séquentielle et qu’une exception se produit, cette dernière
peut être propagée directement, et non encapsulée dans une exception AggregateException. En outre, les
exceptions ThreadAbortException sont toujours propagées directement.

NOTE
Quand l'option «Uniquement mon code» est activée, Visual Studio peut s'arrêter sur la ligne qui lève l'exception et afficher
un message d'erreur « exception non gérée par le code utilisateur ». Cette erreur est sans gravité. Vous pouvez appuyer sur
F5 pour continuer et voir le comportement de gestion des exceptions qui est illustré dans les exemples ci-dessous. Pour
empêcher Visual Studio de s'arrêter sur la première erreur, il suffit de désactiver la case à cocher Uniquement mon code sous
Outils, Options, Débogage, Général.
Cet exemple, destiné à illustrer l'utilisation, peut ne pas s'exécuter plus rapidement que la requête LINQ to Objects
séquentielle équivalente. Pour plus d’informations sur l’accélération, consultez Fonctionnement de l’accélération dans PLINQ.

Exemple
Cet exemple montre comment placer les blocs try-catch autour du code qui exécute la requête pour intercepter
toute exception System.AggregateException levée.
// Paste into PLINQDataSample class.
static void PLINQExceptions_1()
{
// Using the raw string array here. See PLINQ Data Sample.
string[] customers = GetCustomersAsStrings().ToArray();

// First, we must simulate some currupt input.


customers[54] = "###";

var parallelQuery = from cust in customers.AsParallel()


let fields = cust.Split(',')
where fields[3].StartsWith("C") //throw indexoutofrange
select new { city = fields[3], thread = Thread.CurrentThread.ManagedThreadId };
try
{
// We use ForAll although it doesn't really improve performance
// since all output is serialized through the Console.
parallelQuery.ForAll(e => Console.WriteLine("City: {0}, Thread:{1}", e.city, e.thread));
}

// In this design, we stop query processing when the exception occurs.


catch (AggregateException e)
{
foreach (var ex in e.InnerExceptions)
{
Console.WriteLine(ex.Message);
if (ex is IndexOutOfRangeException)
Console.WriteLine("The data source is corrupt. Query stopped.");
}
}
}

' Paste into PLINQDataSample class


Shared Sub PLINQExceptions_1()

' Using the raw string array here. See PLINQ Data Sample.
Dim customers As String() = GetCustomersAsStrings().ToArray()

' First, we must simulate some currupt input.


customers(20) = "###"

'throws indexoutofrange
Dim query = From cust In customers.AsParallel() _
Let fields = cust.Split(","c) _
Where fields(3).StartsWith("C") _
Select fields
Try
' We use ForAll although it doesn't really improve performance
' since all output is serialized through the Console.
query.ForAll(Sub(e)
Console.WriteLine("City: {0}, Thread:{1}")
End Sub)
Catch e As AggregateException

' In this design, we stop query processing when the exception occurs.
For Each ex In e.InnerExceptions
Console.WriteLine(ex.Message)
If TypeOf ex Is IndexOutOfRangeException Then
Console.WriteLine("The data source is corrupt. Query stopped.")
End If
Next
End Try
End Sub

Dans cet exemple, la requête ne peut pas continuer une fois l’exception levée. Au moment où votre code
d’application intercepte l’exception, PLINQ a déjà arrêté la requête sur tous les threads.

Exemple
L’exemple suivant montre comment placer un bloc try-catch dans un délégué pour pouvoir intercepter une
exception et poursuivre l’exécution des requêtes.

// Paste into PLINQDataSample class.


static void PLINQExceptions_2()
{

var customers = GetCustomersAsStrings().ToArray();


// Using the raw string array here.
// First, we must simulate some currupt input
customers[54] = "###";

// Create a delegate with a lambda expression.


// Assume that in this app, we expect malformed data
// occasionally and by design we just report it and continue.
Func<string[], string, bool> isTrue = (f, c) =>
{
try
{
string s = f[3];
return s.StartsWith(c);
}
catch (IndexOutOfRangeException e)
{
Console.WriteLine("Malformed cust: {0}", f);
return false;
}
};

// Using the raw string array here


var parallelQuery = from cust in customers.AsParallel()
let fields = cust.Split(',')
where isTrue(fields, "C") //use a named delegate with a try-catch
select new { city = fields[3] };
try
{
// We use ForAll although it doesn't really improve performance
// since all output must be serialized through the Console.
parallelQuery.ForAll(e => Console.WriteLine(e.city));
}

// IndexOutOfRangeException will not bubble up


// because we handle it where it is thrown.
catch (AggregateException e)
{
foreach (var ex in e.InnerExceptions)
Console.WriteLine(ex.Message);
}
}
' Paste into PLINQDataSample class
Shared Sub PLINQExceptions_2()

Dim customers() = GetCustomersAsStrings().ToArray()


' Using the raw string array here.
' First, we must simulate some currupt input
customers(20) = "###"

' Create a delegate with a lambda expression.


' Assume that in this app, we expect malformed data
' occasionally and by design we just report it and continue.
Dim isTrue As Func(Of String(), String, Boolean) = Function(f, c)

Try

Dim s As String = f(3)


Return s.StartsWith(c)

Catch e As IndexOutOfRangeException

Console.WriteLine("Malformed cust: {0}", f)


Return False
End Try
End Function

' Using the raw string array here


Dim query = From cust In customers.AsParallel()
Let fields = cust.Split(","c)
Where isTrue(fields, "C")
Select New With {.City = fields(3)}
Try
' We use ForAll although it doesn't really improve performance
' since all output must be serialized through the Console.
query.ForAll(Sub(e) Console.WriteLine(e.city))

' IndexOutOfRangeException will not bubble up


' because we handle it where it is thrown.
Catch e As AggregateException
For Each ex In e.InnerExceptions
Console.WriteLine(ex.Message)
Next
End Try
End Sub

Compilation du code
Pour compiler et exécuter ces exemples, copiez-les dans l’exemple de données PLINQ et appelez la méthode à
partir de Main.

Programmation fiable
N’interceptez pas d’exceptions, sauf si vous savez comment les gérer pour ne pas endommager l’état de votre
programme.

Voir aussi
ParallelEnumerable
Parallel LINQ (PLINQ)
Procédure : annuler une requête PLINQ
18/07/2020 • 8 minutes to read • Edit Online

Les exemples suivants montrent deux façons de modifier une requête PLINQ. Le premier exemple montre
comment annuler une requête principalement composée de parcours de données. Le deuxième exemple montre
comment annuler une requête contenant une fonction d’utilisateur dont les calculs sont onéreux.

NOTE
Quand l'option «Uniquement mon code» est activée, Visual Studio peut s'arrêter sur la ligne qui lève l'exception et afficher un
message d'erreur « exception non gérée par le code utilisateur ». Cette erreur est sans gravité. Vous pouvez appuyer sur F5
pour continuer et voir le comportement de gestion des exceptions qui est illustré dans les exemples ci-dessous. Pour
empêcher Visual Studio de s'arrêter sur la première erreur, il suffit de désactiver la case à cocher Uniquement mon code sous
Outils, Options, Débogage, Général.
Cet exemple, destiné à illustrer l'utilisation, peut ne pas s'exécuter plus rapidement que la requête LINQ to Objects
séquentielle équivalente. Pour plus d’informations sur l’accélération, consultez Fonctionnement de l’accélération dans PLINQ.

Exemple
namespace PLINQCancellation_1
{
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;

class Program
{
static void Main(string[] args)
{
int[] source = Enumerable.Range(1, 10000000).ToArray();
var cts = new CancellationTokenSource();

// Start a new asynchronous task that will cancel the


// operation from another thread. Typically you would call
// Cancel() in response to a button click or some other
// user interface event.
Task.Factory.StartNew(() =>
{
UserClicksTheCancelButton(cts);
});

int[] results = null;


try
{
results = (from num in source.AsParallel().WithCancellation(cts.Token)
where num % 3 == 0
orderby num descending
select num).ToArray();
}
catch (OperationCanceledException e)
{
WriteLine(e.Message);
}
catch (AggregateException ae)
{
{
if (ae.InnerExceptions != null)
{
foreach (Exception e in ae.InnerExceptions)
WriteLine(e.Message);
}
}
finally
{
cts.Dispose();
}

if (results != null)
{
foreach (var v in results)
WriteLine(v);
}
WriteLine();
ReadKey();
}

static void UserClicksTheCancelButton(CancellationTokenSource cts)


{
// Wait between 150 and 500 ms, then cancel.
// Adjust these values if necessary to make
// cancellation fire while query is still executing.
Random rand = new Random();
Thread.Sleep(rand.Next(150, 500));
cts.Cancel();
}
}
}
Class Program
Private Shared Sub Main(ByVal args As String())
Dim source As Integer() = Enumerable.Range(1, 10000000).ToArray()
Dim cs As New CancellationTokenSource()

' Start a new asynchronous task that will cancel the


' operation from another thread. Typically you would call
' Cancel() in response to a button click or some other
' user interface event.
Task.Factory.StartNew(Sub()
UserClicksTheCancelButton(cs)
End Sub)

Dim results As Integer() = Nothing


Try

results = (From num In source.AsParallel().WithCancellation(cs.Token) _


Where num Mod 3 = 0 _
Order By num Descending _
Select num).ToArray()
Catch e As OperationCanceledException

Console.WriteLine(e.Message)
Catch ae As AggregateException

If ae.InnerExceptions IsNot Nothing Then


For Each e As Exception In ae.InnerExceptions
Console.WriteLine(e.Message)
Next
End If
Finally
cs.Dispose()
End Try

If results IsNot Nothing Then


For Each item In results
Console.WriteLine(item)
Next
End If
Console.WriteLine()

Console.ReadKey()
End Sub

Private Shared Sub UserClicksTheCancelButton(ByVal cs As CancellationTokenSource)


' Wait between 150 and 500 ms, then cancel.
' Adjust these values if necessary to make
' cancellation fire while query is still executing.
Dim rand As New Random()
Thread.Sleep(rand.[Next](150, 350))
cs.Cancel()
End Sub
End Class

L’infrastructure PLINQ ne restaure pas un seul OperationCanceledException dans un System.AggregateException ;


le OperationCanceledException doit être géré dans un bloc catch séparé. Si un ou plusieurs délégués utilisateurs
lèvent une OperationCanceledException(externalCT) (à l’aide d’un System.Threading.CancellationToken externe),
mais pas d’autre exception et que la requête était définie en tant que AsParallel().WithCancellation(externalCT) ,
PLINQ émettra un seul OperationCanceledException (externalCT) plutôt qu’un System.AggregateException.
Toutefois, si un délégué utilisateur lève une OperationCanceledExceptionet qu’un autre délégué lève un autre type
d’exception, les deux exceptions seront restaurées dans un AggregateException.
Les recommandations générales pour l’annulation sont les suivantes :
1. Si vous effectuez l’annulation d’un délégué utilisateur, vous devez informer PLINQ de l’extérieur
CancellationToken et lever une OperationCanceledException (externalCT).
2. Si l’annulation se produit et qu’aucune autre exception n’est levée, alors gérez un
OperationCanceledException plutôt qu’un AggregateException .

Exemple
L’exemple suivant montre comment gérer l’annulation lorsque vous avez une fonction de calcul onéreux dans le
code utilisateur.

namespace PLINQCancellation_2
{
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using static System.Console;

class Program
{
static void Main(string[] args)
{
int[] source = Enumerable.Range(1, 10000000).ToArray();
var cts = new CancellationTokenSource();

// Start a new asynchronous task that will cancel the


// operation from another thread. Typically you would call
// Cancel() in response to a button click or some other
// user interface event.
Task.Factory.StartNew(() =>
{
UserClicksTheCancelButton(cts);
});

double[] results = null;


try
{
results = (from num in source.AsParallel().WithCancellation(cts.Token)
where num % 3 == 0
select Function(num, cts.Token)).ToArray();
}
catch (OperationCanceledException e)
{
WriteLine(e.Message);
}
catch (AggregateException ae)
{
if (ae.InnerExceptions != null)
{
foreach (Exception e in ae.InnerExceptions)
WriteLine(e.Message);
}
}
finally
{
cts.Dispose();
}

if (results != null)
{
foreach (var v in results)
WriteLine(v);
}
WriteLine();
ReadKey();
ReadKey();
}

// A toy method to simulate work.


static double Function(int n, CancellationToken ct)
{
// If work is expected to take longer than 1 ms
// then try to check cancellation status more
// often within that work.
for (int i = 0; i < 5; i++)
{
// Work hard for approx 1 millisecond.
Thread.SpinWait(50000);

// Check for cancellation request.


ct.ThrowIfCancellationRequested();
}
// Anything will do for our purposes.
return Math.Sqrt(n);
}

static void UserClicksTheCancelButton(CancellationTokenSource cts)


{
// Wait between 150 and 500 ms, then cancel.
// Adjust these values if necessary to make
// cancellation fire while query is still executing.
Random rand = new Random();
Thread.Sleep(rand.Next(150, 500));
WriteLine("Press 'c' to cancel");
if (ReadKey().KeyChar == 'c')
cts.Cancel();
}
}
}

Class Program2
Private Shared Sub Main(ByVal args As String())

Dim source As Integer() = Enumerable.Range(1, 10000000).ToArray()


Dim cs As New CancellationTokenSource()

' Start a new asynchronous task that will cancel the


' operation from another thread. Typically you would call
' Cancel() in response to a button click or some other
' user interface event.
Task.Factory.StartNew(Sub()

UserClicksTheCancelButton(cs)
End Sub)

Dim results As Double() = Nothing


Try

results = (From num In source.AsParallel().WithCancellation(cs.Token) _


Where num Mod 3 = 0 _
Select [Function](num, cs.Token)).ToArray()
Catch e As OperationCanceledException

Console.WriteLine(e.Message)
Catch ae As AggregateException
If ae.InnerExceptions IsNot Nothing Then
For Each e As Exception In ae.InnerExceptions
Console.WriteLine(e.Message)
Next
End If
Finally
Finally
cs.Dispose()
End Try

If results IsNot Nothing Then


For Each item In results
Console.WriteLine(item)
Next
End If
Console.WriteLine()

Console.ReadKey()
End Sub

' A toy method to simulate work.


Private Shared Function [Function](ByVal n As Integer, ByVal ct As CancellationToken) As Double
' If work is expected to take longer than 1 ms
' then try to check cancellation status more
' often within that work.
For i As Integer = 0 To 4
' Work hard for approx 1 millisecond.
Thread.SpinWait(50000)

' Check for cancellation request.


If ct.IsCancellationRequested Then
Throw New OperationCanceledException(ct)
End If
Next
' Anything will do for our purposes.
Return Math.Sqrt(n)
End Function

Private Shared Sub UserClicksTheCancelButton(ByVal cs As CancellationTokenSource)


' Wait between 150 and 500 ms, then cancel.
' Adjust these values if necessary to make
' cancellation fire while query is still executing.
Dim rand As New Random()
Thread.Sleep(rand.[Next](150, 350))
Console.WriteLine("Press 'c' to cancel")
If Console.ReadKey().KeyChar = "c"c Then
cs.Cancel()

End If
End Sub
End Class

Lorsque vous gérez l’annulation dans le code utilisateur, vous n’avez pas à utiliser WithCancellation dans la
définition de requête. Toutefois, nous vous recommandons d’utiliser WithCancellation , car n' WithCancellation a
aucun effet sur les performances des requêtes et permet de gérer l’annulation par les opérateurs de requête et
votre code utilisateur.
Pour garantir la réactivité du système, nous vous recommandons de vérifier les annulations toutes les
millisecondes ; toutefois, une période jusqu'à 10 millisecondes est considérée comme acceptable. Cette fréquence
ne doit pas avoir d’impact négatif sur le niveau de performance de votre code.
Lorsqu’un énumérateur est supprimé, par exemple lorsque le code s’arrête sur une boucle foreach (for each in
Visual Basic) qui itère au sein des résultats de la requête, la requête est annulée, mais aucune exception n’est levée.

Voir aussi
ParallelEnumerable
Parallel LINQ (PLINQ)
Annulation dans les threads managés
Procédure : écrire une fonction d’agrégation PLINQ
personnalisée
18/07/2020 • 3 minutes to read • Edit Online

Cet exemple montre comment utiliser la méthode Aggregate pour appliquer une fonction d’agrégation
personnalisée à une séquence source.

WARNING
Cet exemple, destiné à illustrer l'utilisation, peut ne pas s'exécuter plus rapidement que la requête LINQ to Objects
séquentielle équivalente. Pour plus d’informations sur l’accélération, consultez Fonctionnement de l’accélération dans PLINQ.

Exemple
L’exemple suivant calcule l’écart type d’une séquence d’entiers.
namespace PLINQAggregation
{
using System;
using System.Linq;

class aggregation
{
static void Main(string[] args)
{

// Create a data source for demonstration purposes.


int[] source = new int[100000];
Random rand = new Random();
for (int x = 0; x < source.Length; x++)
{
// Should result in a mean of approximately 15.0.
source[x] = rand.Next(10, 20);
}

// Standard deviation calculation requires that we first


// calculate the mean average. Average is a predefined
// aggregation operator, along with Max, Min and Count.
double mean = source.AsParallel().Average();

// We use the overload that is unique to ParallelEnumerable. The


// third Func parameter combines the results from each thread.
double standardDev = source.AsParallel().Aggregate(
// initialize subtotal. Use decimal point to tell
// the compiler this is a type double. Can also use: 0d.
0.0,

// do this on each thread


(subtotal, item) => subtotal + Math.Pow((item - mean), 2),

// aggregate results after all threads are done.


(total, thisThread) => total + thisThread,

// perform standard deviation calc on the aggregated result.


(finalSum) => Math.Sqrt((finalSum / (source.Length - 1)))
);
Console.WriteLine("Mean value is = {0}", mean);
Console.WriteLine("Standard deviation is {0}", standardDev);
Console.ReadLine();
}
}
}
Class aggregation
Private Shared Sub Main(ByVal args As String())

' Create a data source for demonstration purposes.


Dim source As Integer() = New Integer(99999) {}
Dim rand As New Random()
For x As Integer = 0 To source.Length - 1
' Should result in a mean of approximately 15.0.
source(x) = rand.[Next](10, 20)
Next

' Standard deviation calculation requires that we first


' calculate the mean average. Average is a predefined
' aggregation operator, along with Max, Min and Count.
Dim mean As Double = source.AsParallel().Average()

' We use the overload that is unique to ParallelEnumerable. The


' third Func parameter combines the results from each thread.
' initialize subtotal. Use decimal point to tell
' the compiler this is a type double. Can also use: 0d.

' do this on each thread

' aggregate results after all threads are done.

' perform standard deviation calc on the aggregated result.


Dim standardDev As Double = source.AsParallel().Aggregate(0.0R, Function(subtotal, item) subtotal +
Math.Pow((item - mean), 2), Function(total, thisThread) total + thisThread, Function(finalSum)
Math.Sqrt((finalSum / (source.Length - 1))))
Console.WriteLine("Mean value is = {0}", mean)
Console.WriteLine("Standard deviation is {0}", standardDev)

Console.ReadLine()
End Sub
End Class

Cet exemple utilise une surcharge de l’opérateur de requête standard d’agrégation propre à PLINQ. Cette surcharge
accepte un System.Func<T1,T2,TResult> supplémentaire en tant que troisième paramètre d’entrée. Ce délégué
combine les résultats de tous les threads avant d’effectuer le calcul final à partir des résultats agrégés. Dans cet
exemple, nous regroupons les sommes de tous les threads.
Notez que quand un corps d’expression lambda se compose d’une expression unique, la valeur de retour du
délégué System.Func<T,TResult> correspond à la valeur de l’expression.

Voir aussi
ParallelEnumerable
Parallel LINQ (PLINQ)
Procédure : spécifier le mode d’exécution avec PLINQ
18/07/2020 • 2 minutes to read • Edit Online

Cet exemple montre comment forcer PLINQ à contourner ses paramètres heuristiques et à paralléliser une
requête quelle que soit sa forme.

NOTE
Cet exemple est destiné à illustrer l’utilisation et peut ne pas s’exécuter plus rapidement que la requête LINQ to Objects
séquentielle équivalente. Pour plus d’informations sur l’accélération, consultez Fonctionnement de l’accélération dans PLINQ.

Exemple
// Paste into PLINQDataSample class.
static void ForceParallel()
{
var customers = GetCustomers();
var parallelQuery = (from cust in customers.AsParallel()
.WithExecutionMode(ParallelExecutionMode.ForceParallelism)
where cust.City == "Berlin"
select cust.CustomerName)
.ToList();
}

Private Shared Sub ForceParallel()


Dim customers = GetCustomers()
Dim parallelQuery = (From cust In
customers.AsParallel().WithExecutionMode(ParallelExecutionMode.ForceParallelism) _
Where cust.City = "Berlin" _
Select cust.CustomerName).ToList()
End Sub

PLINQ est conçu pour exploiter les opportunités de parallélisation. Toutefois, toutes les requêtes ne bénéficient pas
de l’exécution parallèle. Par exemple, lorsqu’une requête contient un délégué d’utilisateur unique qui ne fonctionne
que peu, la requête s’exécute généralement plus rapidement. L’exécution séquentielle est plus rapide, car la
surcharge impliquée dans l’activation de la parallélisation d’exécution est plus coûteuse que l’accélération obtenue.
Par conséquent, PLINQ ne pas parallélise pas automatiquement chaque requête. Il examine d’abord la forme de la
requête et les différents opérateurs qui la composent. En fonction de cette analyse, en mode d’exécution par défaut
PLINQ peut décider d’exécuter de manière séquentielle une partie de la requête ou sa totalité. Toutefois, dans
certains cas, vous pouvez en savoir plus sur votre requête que PLINQ n’est en mesure de déterminer à partir de
son analyse. Par exemple, vous savez peut-être qu’un délégué est onéreux et que la requête bénéficiera de la
parallélisation. Dans ce cas, vous pouvez utiliser la méthode WithExecutionMode et spécifier la valeur
ForceParallelism pour indiquer à PLINQ de toujours exécuter la requête en parallèle.

Compilation du code
Coupez et collez ce code dans l’échantillon de données PLINQ et appelez la méthode à partir de Main .

Voir aussi
AsSequential
Parallel LINQ (PLINQ)
Procédure : spécifier des options de fusion avec
PLINQ
18/07/2020 • 2 minutes to read • Edit Online

Cet exemple montre comment spécifier les options de fusion qui seront appliquées à tous les opérateurs suivants
dans une requête PLINQ. Il n’est pas nécessaire de définir explicitement les options de fusion, mais cela peut
améliorer les performances. Pour plus d’informations sur les options de fusion, consultez Options de fusion en
PLINQ.

WARNING
Cet exemple, destiné à illustrer l'utilisation, peut ne pas s'exécuter plus rapidement que la requête LINQ to Objects
séquentielle équivalente. Pour plus d’informations sur l’accélération, consultez Fonctionnement de l’accélération dans PLINQ.

Exemple
L’exemple suivant montre le comportement des options de fusion dans un scénario de base qui a une source non
ordonnée et qui applique une fonction coûteuse à chaque élément.
namespace MergeOptions
{
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;

class Program
{
static void Main(string[] args)
{

var nums = Enumerable.Range(1, 10000);

// Replace NotBuffered with AutoBuffered


// or FullyBuffered to compare behavior.
var scanLines = from n in nums.AsParallel()
.WithMergeOptions(ParallelMergeOptions.NotBuffered)
where n % 2 == 0
select ExpensiveFunc(n);

Stopwatch sw = Stopwatch.StartNew();
foreach (var line in scanLines)
{
Console.WriteLine(line);
}

Console.WriteLine("Elapsed time: {0} ms. Press any key to exit.",


sw.ElapsedMilliseconds);
Console.ReadKey();
}

// A function that demonstrates what a fly


// sees when it watches television :-)
static string ExpensiveFunc(int i)
{
Thread.SpinWait(2000000);
return String.Format("{0} *****************************************", i);
}
}
}
Class MergeOptions2

Sub DoMergeOptions()

Dim nums = Enumerable.Range(1, 10000)

' Replace NotBuffered with AutoBuffered


' or FullyBuffered to compare behavior.
Dim scanLines = From n In nums.AsParallel().WithMergeOptions(ParallelMergeOptions.NotBuffered)
Where n Mod 2 = 0
Select ExpensiveFunc(n)

Dim sw = System.Diagnostics.Stopwatch.StartNew()
For Each line In scanLines
Console.WriteLine(line)
Next

Console.WriteLine("Elapsed time: {0} ms. Press any key to exit.")


Console.ReadKey()

End Sub
' A function that demonstrates what a fly
' sees when it watches television :-)
Function ExpensiveFunc(ByVal i As Integer) As String
System.Threading.Thread.SpinWait(2000000)
Return String.Format("{0} *****************************************", i)
End Function
End Class

Dans les cas où l’option AutoBuffered entraînerait une latence indésirable avant la transmission du premier
élément, essayez l’option NotBuffered pour transmettre des éléments de résultat plus rapidement et plus
facilement.

Voir aussi
ParallelMergeOptions
Parallel LINQ (PLINQ)
Procédure : itérer les répertoires de fichiers avec
PLINQ
18/07/2020 • 5 minutes to read • Edit Online

Cet article présente deux façons de paralléliser les opérations sur les répertoires de fichiers. La première requête
utilise la méthode GetFiles pour renseigner un tableau de noms de fichiers dans un répertoire et tous ses sous-
répertoires. Cette méthode peut introduire une latence au début de l’opération, car elle ne retourne pas jusqu’à ce
que l’intégralité du tableau soit remplie. Toutefois, une fois que le tableau est rempli, PLINQ peut rapidement le
traiter en parallèle.
La deuxième requête utilise les EnumerateDirectories méthodes et statiques EnumerateFiles , qui commencent à
retourner immédiatement les résultats. Cette approche peut être plus rapide lorsque vous effectuez une itération
sur de grandes arborescences de répertoires, mais le temps de traitement comparé au premier exemple dépend de
nombreux facteurs.

NOTE
Ces exemples sont destinés à illustrer l’utilisation et peuvent ne pas s’exécuter plus rapidement que la requête LINQ to
Objects séquentielle équivalente. Pour plus d’informations sur l’accélération, consultez Fonctionnement de l’accélération dans
PLINQ.

Exemple GetFiles
Cet exemple montre comment itérer au sein des répertoires de fichiers dans des scénarios simples lorsque vous
avez accès à tous les répertoires de l’arborescence, que les tailles de fichier ne sont pas volumineuses et que les
délais d’accès ne sont pas significatifs. Cette approche implique une période de latence au début, le temps que le
tableau de noms de fichiers soit créé.
struct FileResult
{
public string Text;
public string FileName;
}
// Use Directory.GetFiles to get the source sequence of file names.
public static void FileIteration_1(string path)
{
var sw = Stopwatch.StartNew();
int count = 0;
string[] files = null;
try
{
files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine("You do not have permission to access one or more folders in this directory tree.");
return;
}

catch (FileNotFoundException)
{
Console.WriteLine("The specified directory {0} was not found.", path);
}

var fileContents = from file in files.AsParallel()


let extension = Path.GetExtension(file)
where extension == ".txt" || extension == ".htm"
let text = File.ReadAllText(file)
select new FileResult { Text = text , FileName = file }; //Or ReadAllBytes, ReadAllLines, etc.

try
{
foreach (var item in fileContents)
{
Console.WriteLine(Path.GetFileName(item.FileName) + ":" + item.Text.Length);
count++;
}
}
catch (AggregateException ae)
{
ae.Handle((ex) =>
{
if (ex is UnauthorizedAccessException)
{
Console.WriteLine(ex.Message);
return true;
}
return false;
});
}

Console.WriteLine("FileIteration_1 processed {0} files in {1} milliseconds", count,


sw.ElapsedMilliseconds);
}

Exemple EnumerateFiles
Cet exemple montre comment itérer au sein des répertoires de fichiers dans des scénarios simples lorsque vous
avez accès à tous les répertoires de l’arborescence, que les tailles de fichier ne sont pas volumineuses et que les
délais d’accès ne sont pas significatifs. Cette méthode commence à générer des résultats plus rapidement que
l’exemple précédent.
struct FileResult
{
public string Text;
public string FileName;
}

// Use Directory.EnumerateDirectories and EnumerateFiles to get the source sequence of file names.
public static void FileIteration_2(string path) //225512 ms
{
var count = 0;
var sw = Stopwatch.StartNew();
var fileNames = from dir in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)
select dir;

var fileContents = from file in fileNames.AsParallel() // Use AsOrdered to preserve source ordering
let extension = Path.GetExtension(file)
where extension == ".txt" || extension == ".htm"
let Text = File.ReadAllText(file)
select new { Text, FileName = file }; //Or ReadAllBytes, ReadAllLines, etc.
try
{
foreach (var item in fileContents)
{
Console.WriteLine(Path.GetFileName(item.FileName) + ":" + item.Text.Length);
count++;
}
}
catch (AggregateException ae)
{
ae.Handle((ex) =>
{
if (ex is UnauthorizedAccessException)
{
Console.WriteLine(ex.Message);
return true;
}
return false;
});
}

Console.WriteLine("FileIteration_2 processed {0} files in {1} milliseconds", count,


sw.ElapsedMilliseconds);
}

Si vous utilisez GetFiles, assurez-vous de disposer des autorisations suffisantes sur tous les répertoires de
l’arborescence. Dans le cas contraire, une exception est levée et aucun résultat n’est retourné. Quand vous utilisez la
méthode EnumerateDirectories dans une requête PLINQ, la gestion des exceptions d’E/S de façon normale
permettant de poursuivre l’itération devient problématique. Si votre code doit gérer les E/S ou les exceptions
d’accès non autorisé, vous devez envisager l’approche décrite dans Comment : itérer les répertoires de fichiers
avec la classe parallèle.
Si la latence des E/S s’avère un problème, par exemple avec des E/S de fichiers sur un réseau, optez plutôt pour
l’une des techniques d’E/S asynchrones décrites dans Bibliothèque parallèle de tâches et programmation
asynchrone .NET traditionnelle et dans ce billet de blog.

Voir aussi
Parallel LINQ (PLINQ)
Procédure : mesurer les performances de requêtes
PLINQ
18/07/2020 • 2 minutes to read • Edit Online

Cet exemple montre comment utiliser la Stopwatch classe pour mesurer le temps nécessaire à l’exécution d’une
requête PLINQ.

Exemple
Cet exemple utilise une boucle foreach vide ( For Each en Visual Basic) pour mesurer le temps nécessaire à
l’exécution de la requête. Dans le code réel, la boucle contient généralement des étapes de traitement
supplémentaires qui s’ajoutent à la durée d’exécution totale de la requête. Notez que le chronomètre n’est pas
démarré avant la boucle, car c’est à ce moment-là que l’exécution de la requête commence. Si vous avez besoin de
mesures plus poussées, vous pouvez utiliser la propriété ElapsedTicks au lieu de ElapsedMilliseconds .

using System;
using System.Diagnostics;
using System.Linq;

class Example
{
static void Main()
{
var source = Enumerable.Range(0, 3000000);

var queryToMeasure = from num in source.AsParallel()


where num % 3 == 0
select Math.Sqrt(num);

Console.WriteLine("Measuring...");

// The query does not run until it is enumerated.


// Therefore, start the timer here.
Stopwatch sw = Stopwatch.StartNew();

// For pure query cost, enumerate and do nothing else.


foreach (var n in queryToMeasure) { }

sw.Stop();
long elapsed = sw.ElapsedMilliseconds; // or sw.ElapsedTicks
Console.WriteLine("Total query time: {0} ms", elapsed);

Console.WriteLine("Press any key to exit.");


Console.ReadKey();
}
}
Module Example
Sub Main()
Dim source = Enumerable.Range(0, 3000000)
' Define parallel and non-parallel queries.
Dim queryToMeasure = From num In source.AsParallel()
Where num Mod 3 = 0
Select Math.Sqrt(num)

Console.WriteLine("Measuring...")

' The query does not run until it is enumerated.


' Therefore, start the timer here.
Dim sw = System.Diagnostics.Stopwatch.StartNew()

' For pure query cost, enumerate and do nothing else.


For Each n As Double In queryToMeasure
Next

sw.Stop()
Dim elapsed As Long = sw.ElapsedMilliseconds ' or sw.ElapsedTicks
Console.WriteLine("Total query time: {0} ms.", elapsed)

Console.WriteLine("Press any key to exit.")


Console.ReadKey()
End Sub
End Module

La durée totale d’exécution est une mesure utile lorsque vous expérimentez des implémentations de requêtes,
mais n’indique pas toujours l’histoire entière. Pour obtenir une vue plus détaillée et plus riche de l’interaction des
threads de requête les uns avec les autres et avec d’autres processus en cours d’exécution, utilisez le visualiseur
concurrentiel.

Voir aussi
Parallel LINQ (PLINQ)
Données PLINQ, exemple
18/07/2020 • 20 minutes to read • Edit Online

Cet exemple contient des exemples de données au format .csv ainsi que des méthodes qui les transforment en
collections en mémoires de clients, de produits, de commandes et de détails des commandes. Pour expérimenter
davantage avec PLINQ, vous pouvez coller des exemples de code d’autres rubriques dans le code de cette
rubrique et l’appeler à partir de la méthode Main . Vous pouvez également utiliser ces données avec vos propres
requêtes PLINQ.
Les données représentent un sous-ensemble de la base de données Northwind. Cinquante (50) enregistrements
de clients sont inclus, mais pas tous les champs. Un sous-ensemble de lignes dans les commandes et les données
Order_Detail correspondantes pour chaque client sont inclus. Tous les produits sont inclus.

NOTE
Le jeu de données n’est pas assez volumineux pour faire la démonstration que PLINQ est plus rapide que LINQ to Objects
pour les requêtes qui contiennent uniquement des clauses where et select de base. Afin d’observer l’augmentation de
vitesse pour les petits jeux de données comme celui-là, utilisez des requêtes qui contiennent des opérations onéreuses en
calcul sur chaque élément du jeu de données.

Pour installer cet exemple


1. Créez un projet d'application console Visual Basic ou Visual C#.
2. Remplacez les contenu de Module1.vb ou de Program.cs en utilisant le code qui suit ces étapes.
3. Dans le menu Projet , cliquez sur Ajouter un nouvel élément . Sélectionnez Fichier texte , puis cliquez
sur OK . Copiez les données dans cette rubrique et collez-les dans le nouveau fichier texte. Sur le menu
Fichier , cliquez sur Enregistrer , nommez le fichier Plinqdata.csv, puis enregistrez-le dans le dossier qui
contient vos fichiers de code source.
4. Appuyez sur F5 pour vérifier que le projet est créé et exécuté correctement. La sortie mentionnée plus bas
devrait s’afficher dans la fenêtre de console.

Customer count: 50
Product count: 77
Order count: 190
Order Details count: 483
Press any key to exit.

// This class contains a subset of data from the Northwind database


// in the form of string arrays. The methods such as GetCustomers, GetOrders, and so on
// transform the strings into object arrays that you can query in an object-oriented way.
// Many of the code examples in the PLINQ How-to topics are designed to be pasted into
// the PLINQDataSample class and invoked from the Main method.
partial class PLINQDataSample
{

public static void Main()


{
////Call methods here.
TestDataSource();

Console.WriteLine("Press any key to exit.");


Console.ReadKey();
Console.ReadKey();
}

static void TestDataSource()


{
Console.WriteLine("Customer count: {0}", GetCustomers().Count());
Console.WriteLine("Product count: {0}", GetProducts().Count());
Console.WriteLine("Order count: {0}", GetOrders().Count());
Console.WriteLine("Order Details count: {0}", GetOrderDetails().Count());
}

#region DataClasses
public class Order
{
private Lazy<OrderDetail[]> _orderDetails;
public Order()
{
_orderDetails = new Lazy<OrderDetail[]>(() => GetOrderDetailsForOrder(OrderID));
}
public int OrderID { get; set; }
public string CustomerID { get; set; }
public DateTime OrderDate { get; set; }
public DateTime ShippedDate { get; set; }
public OrderDetail[] OrderDetails { get { return _orderDetails.Value; } }
}

public class Customer


{
private Lazy<Order[]> _orders;
public Customer()
{
_orders = new Lazy<Order[]>(() => GetOrdersForCustomer(CustomerID));
}
public string CustomerID { get; set; }
public string CustomerName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
public Order[] Orders
{
get
{
return _orders.Value;
}
}
}

public class Product


{
public string ProductName { get; set; }
public int ProductID { get; set; }
public double UnitPrice { get; set; }
}

public class OrderDetail


{
public int OrderID { get; set; }
public int ProductID { get; set; }
public double UnitPrice { get; set; }
public double Quantity { get; set; }
public double Discount { get; set; }
}
#endregion

public static IEnumerable<string> GetCustomersAsStrings()


{
return System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("CUSTOMERS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END CUSTOMERS") == false);
.TakeWhile((line) => line.StartsWith("END CUSTOMERS") == false);
}

public static IEnumerable<Customer> GetCustomers()


{
var customers = System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("CUSTOMERS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END CUSTOMERS") == false);
return (from line in customers
let fields = line.Split(',')
let custID = fields[0].Trim()
select new Customer()
{
CustomerID = custID,
CustomerName = fields[1].Trim(),
Address = fields[2].Trim(),
City = fields[3].Trim(),
PostalCode = fields[4].Trim()
});
}

public static Order[] GetOrdersForCustomer(string id)


{
// Assumes we copied the file correctly!
var orders = System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("ORDERS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END ORDERS") == false);
var orderStrings = from line in orders
let fields = line.Split(',')
where fields[1].CompareTo(id) == 0
select new Order()
{
OrderID = Convert.ToInt32(fields[0]),
CustomerID = fields[1].Trim(),
OrderDate = DateTime.Parse(fields[2]),
ShippedDate = DateTime.Parse(fields[3])
};
return orderStrings.ToArray();
}

// "10248, VINET, 7/4/1996 12:00:00 AM, 7/16/1996 12:00:00 AM


public static IEnumerable<Order> GetOrders()
{
// Assumes we copied the file correctly!
var orders = System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("ORDERS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END ORDERS") == false);
return from line in orders
let fields = line.Split(',')

select new Order()


{
OrderID = Convert.ToInt32(fields[0]),
CustomerID = fields[1].Trim(),
OrderDate = DateTime.Parse(fields[2]),
ShippedDate = DateTime.Parse(fields[3])
};
}

public static IEnumerable<Product> GetProducts()


{
// Assumes we copied the file correctly!
var products = System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("PRODUCTS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END PRODUCTS") == false);
return from line in products
return from line in products
let fields = line.Split(',')
select new Product()
{
ProductID = Convert.ToInt32(fields[0]),
ProductName = fields[1].Trim(),
UnitPrice = Convert.ToDouble(fields[2])
};
}

public static IEnumerable<OrderDetail> GetOrderDetails()


{
// Assumes we copied the file correctly!
var orderDetails = System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("ORDER DETAILS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END ORDER DETAILS") == false);

return from line in orderDetails


let fields = line.Split(',')
select new OrderDetail()
{
OrderID = Convert.ToInt32(fields[0]),
ProductID = Convert.ToInt32(fields[1]),
UnitPrice = Convert.ToDouble(fields[2]),
Quantity = Convert.ToDouble(fields[3]),
Discount = Convert.ToDouble(fields[4])
};
}

public static OrderDetail[] GetOrderDetailsForOrder(int id)


{
// Assumes we copied the file correctly!
var orderDetails = System.IO.File.ReadAllLines(@"..\..\plinqdata.csv")
.SkipWhile((line) => line.StartsWith("ORDER DETAILS") == false)
.Skip(1)
.TakeWhile((line) => line.StartsWith("END ORDER DETAILS") == false);

var orderDetailStrings = from line in orderDetails


let fields = line.Split(',')
let ordID = Convert.ToInt32(fields[0])
where ordID == id
select new OrderDetail()
{
OrderID = ordID,
ProductID = Convert.ToInt32(fields[1]),
UnitPrice = Convert.ToDouble(fields[2]),
Quantity = Convert.ToDouble(fields[3]),
Discount = Convert.ToDouble(fields[4])
};

return orderDetailStrings.ToArray();
}
}

' This class contains a subset of data from the Northwind database
' in the form of string arrays. The methods such as GetCustomers, GetOrders, and so on
' transform the strings into object arrays that you can query in an object-oriented way.
' Many of the code examples in the PLINQ How-to topics are designed to be pasted into
' the PLINQDataSample class and invoked from the Main method.
' IMPORTANT: This data set is not large enough for meaningful comparisons of PLINQ vs. LINQ performance.
Class PLINQDataSample
Shared Sub Main(ByVal args As String())

'Call methods here.


TestDataSource()

Console.WriteLine("Press any key to exit.")


Console.WriteLine("Press any key to exit.")
Console.ReadKey()
End Sub

Shared Sub TestDataSource()


Console.WriteLine("Customer count: {0}", GetCustomers().Count())
Console.WriteLine("Product count: {0}", GetProducts().Count())
Console.WriteLine("Order count: {0}", GetOrders().Count())
Console.WriteLine("Order Details count: {0}", GetOrderDetails().Count())
End Sub

#Region "DataClasses"
Class Order
Public Sub New()
_OrderDetails = New Lazy(Of OrderDetail())(Function() GetOrderDetailsForOrder(OrderID))
End Sub
Private _OrderID As Integer
Public Property OrderID() As Integer
Get
Return _OrderID
End Get
Set(ByVal value As Integer)
_OrderID = value
End Set
End Property
Private _CustomerID As String
Public Property CustomerID() As String
Get
Return _CustomerID
End Get
Set(ByVal value As String)
_CustomerID = value
End Set
End Property
Private _OrderDate As DateTime
Public Property OrderDate() As DateTime
Get
Return _OrderDate
End Get
Set(ByVal value As DateTime)
_OrderDate = value
End Set
End Property
Private _ShippedDate As DateTime
Public Property ShippedDate() As DateTime
Get
Return _ShippedDate
End Get
Set(ByVal value As DateTime)
_ShippedDate = value
End Set
End Property
Private _OrderDetails As Lazy(Of OrderDetail())
Public ReadOnly Property OrderDetails As OrderDetail()
Get
Return _OrderDetails.Value
End Get
End Property
End Class

Class Customer

Private _Orders As Lazy(Of Order())


Public Sub New()
_Orders = New Lazy(Of Order())(Function() GetOrdersForCustomer(_CustomerID))
End Sub

Private _CustomerID As String


Public Property CustomerID() As String
Get
Get
Return _CustomerID
End Get
Set(ByVal value As String)
_CustomerID = value
End Set
End Property
Private _CustomerName As String
Public Property CustomerName() As String
Get
Return _CustomerName
End Get
Set(ByVal value As String)
_CustomerName = value
End Set
End Property
Private _Address As String
Public Property Address() As String
Get
Return _Address
End Get
Set(ByVal value As String)
_Address = value
End Set
End Property
Private _City As String
Public Property City() As String
Get
Return _City
End Get
Set(ByVal value As String)
_City = value
End Set
End Property
Private _PostalCode As String
Public Property PostalCode() As String
Get
Return _PostalCode
End Get
Set(ByVal value As String)
_PostalCode = value
End Set
End Property
Public ReadOnly Property Orders() As Order()
Get
Return _Orders.Value
End Get
End Property
End Class

Class Product
Private _ProductName As String
Public Property ProductName() As String
Get
Return _ProductName
End Get
Set(ByVal value As String)
_ProductName = value
End Set
End Property
Private _ProductID As Integer
Public Property ProductID() As Integer
Get
Return _ProductID
End Get
Set(ByVal value As Integer)
_ProductID = value
End Set
End Property
Private _UnitPrice As Double
Public Property UnitPrice() As Double
Get
Return _UnitPrice
End Get
Set(ByVal value As Double)
_UnitPrice = value
End Set
End Property
End Class

Class OrderDetail
Private _OrderID As Integer
Public Property OrderID() As Integer
Get
Return _OrderID
End Get
Set(ByVal value As Integer)
_OrderID = value
End Set
End Property
Private _ProductID As Integer
Public Property ProductID() As Integer
Get
Return _ProductID
End Get
Set(ByVal value As Integer)
_ProductID = value
End Set
End Property
Private _UnitPrice As Double
Public Property UnitPrice() As Double
Get
Return _UnitPrice
End Get
Set(ByVal value As Double)
_UnitPrice = value
End Set
End Property
Private _Quantity As Double
Public Property Quantity() As Double
Get
Return _Quantity
End Get
Set(ByVal value As Double)
_Quantity = value
End Set
End Property
Private _Discount As Double
Public Property Discount() As Double
Get
Return _Discount
End Get
Set(ByVal value As Double)
_Discount = value
End Set
End Property

End Class
#End Region

Shared Function GetCustomersAsStrings() As IEnumerable(Of String)

Return System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("CUSTOMERS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END CUSTOMERS") = False)
End Function
End Function
Shared Function GetCustomers() As IEnumerable(Of Customer)

Dim customers = System.IO.File.ReadAllLines("..\..\plinqdata.csv").


SkipWhile(Function(line) line.StartsWith("CUSTOMERS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END CUSTOMERS") = False)

Return From line In customers


Let fields = line.Split(","c)
Select New Customer With {
.CustomerID = fields(0).Trim(),
.CustomerName = fields(1).Trim(),
.Address = fields(2).Trim(),
.City = fields(3).Trim(),
.PostalCode = fields(4).Trim()}
End Function

Shared Function GetOrders() As IEnumerable(Of Order)

Dim orders = System.IO.File.ReadAllLines("..\..\plinqdata.csv").


SkipWhile(Function(line) line.StartsWith("ORDERS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END ORDERS") = False)

Return From line In orders


Let fields = line.Split(","c)
Select New Order With {
.OrderID = CType(fields(0).Trim(), Integer),
.CustomerID = fields(1).Trim(),
.OrderDate = DateTime.Parse(fields(2)),
.ShippedDate = DateTime.Parse(fields(3))}

End Function

Shared Function GetOrdersForCustomer(ByVal id As String) As Order()

Dim orders = System.IO.File.ReadAllLines("..\..\plinqdata.csv").


SkipWhile(Function(line) line.StartsWith("ORDERS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END ORDERS") = False)

Dim orderStrings = From line In orders


Let fields = line.Split(","c)
Let custID = fields(1).Trim()
Where custID = id
Select New Order With {
.OrderID = CType(fields(0).Trim(), Integer),
.CustomerID = custID,
.OrderDate = DateTime.Parse(fields(2)),
.ShippedDate = DateTime.Parse(fields(3))}

Return orderStrings.ToArray()

End Function
Shared Function GetProducts() As IEnumerable(Of Product)

Dim products = System.IO.File.ReadAllLines("..\..\plinqdata.csv").


SkipWhile(Function(line) line.StartsWith("PRODUCTS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END PRODUCTS") = False)

Return From line In products


Let fields = line.Split(","c)
Select New Product With {
.ProductID = CType(fields(0), Integer),
.ProductName = fields(1).Trim(),
.UnitPrice = CType(fields(2), Double)}
End Function
Shared Function GetOrderDetailsForOrder(ByVal orderID As Integer) As OrderDetail()
Dim orderDetails = System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("ORDER DETAILS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END ORDER DETAILS") = False)

Dim ordDetailStrings = From line In orderDetails


Let fields = line.Split(","c)
Let ordID = Convert.ToInt32(fields(0))
Where ordID = orderID
Select New OrderDetail With {
.OrderID = ordID,
.ProductID = CType(fields(1), Integer),
.UnitPrice = CType(fields(2), Double),
.Quantity = CType(fields(3), Double),
.Discount = CType(fields(4), Double)}
Return ordDetailStrings.ToArray()

End Function

Shared Function GetOrderDetails() As IEnumerable(Of OrderDetail)


Dim orderDetails = System.IO.File.ReadAllLines("..\..\plinqdata.csv").
SkipWhile(Function(line) line.StartsWith("ORDER DETAILS") = False).
Skip(1).
TakeWhile(Function(line) line.StartsWith("END ORDER DETAILS") = False)

Return From line In orderDetails


Let fields = line.Split(","c)
Select New OrderDetail With {
.OrderID = CType(fields(0), Integer),
.ProductID = CType(fields(1), Integer),
.UnitPrice = CType(fields(2), Double),
.Quantity = CType(fields(3), Double),
.Discount = CType(fields(4), Double)}
End Function

End Class

Données
CUSTOMERS
ALFKI,Alfreds Futterkiste,Obere Str. 57,Berlin,12209
ANATR,Ana Trujillo Emparedados y helados,Avda. de la Constitución 2222,México D.F.,05021
ANTON,Antonio Moreno Taquería,Mataderos 2312,México D.F.,05023
AROUT,Around the Horn,120 Hanover Sq.,London,WA1 1DP
BERGS,Berglunds snabbköp,Berguvsvägen 8,Luleå,S-958 22
BLAUS,Blauer See Delikatessen,Forsterstr. 57,Mannheim,68306
BLONP,Blondesddsl père et fils,24, place Kléber,Strasbourg,67000
BOLID,Bólido Comidas preparadas,C/ Araquil, 67,Madrid,28023
BONAP,Bon app',12, rue des Bouchers,Marseille,13008
BOTTM,Bottom-Dollar Markets,23 Tsawassen Blvd.,Tsawassen,T2F 8M4
BSBEV,B's Beverages,Fauntleroy Circus,London,EC2 5NT
CACTU,Cactus Comidas para llevar,Cerrito 333,Buenos Aires,1010
CENTC,Centro comercial Moctezuma,Sierras de Granada 9993,México D.F.,05022
CHOPS,Chop-suey Chinese,Hauptstr. 29,Bern,3012
COMMI,Comércio Mineiro,Av. dos Lusíadas, 23,Sao Paulo,05432-043
CONSH,Consolidated Holdings,Berkeley Gardens 12 Brewery,London,WX1 6LT
DRACD,Drachenblut Delikatessen,Walserweg 21,Aachen,52066
DUMON,Du monde entier,67, rue des Cinquante Otages,Nantes,44000
EASTC,Eastern Connection,35 King George,London,WX3 6FW
ERNSH,Ernst Handel,Kirchgasse 6,Graz,8010
FAMIA,Familia Arquibaldo,Rua Orós, 92,Sao Paulo,05442-030
FISSA,FISSA Fabrica Inter. Salchichas S.A.,C/ Moralzarzal, 86,Madrid,28034
FOLIG,Folies gourmandes,184, chaussée de Tournai,Lille,59000
FOLKO,Folk och fä HB,Åkergatan 24,Bräcke,S-844 67
FOLKO,Folk och fä HB,Åkergatan 24,Bräcke,S-844 67
FRANK,Frankenversand,Berliner Platz 43,München,80805
FRANR,France restauration,54, rue Royale,Nantes,44000
FRANS,Franchi S.p.A.,Via Monte Bianco 34,Torino,10100
FURIB,Furia Bacalhau e Frutos do Mar,Jardim das rosas n. 32,Lisboa,1675
GALED,Galería del gastrónomo,Rambla de Cataluña, 23,Barcelona,08022
GODOS,Godos Cocina Típica,C/ Romero, 33,Sevilla,41101
GOURL,Gourmet Lanchonetes,Av. Brasil, 442,Campinas,04876-786
GREAL,Great Lakes Food Market,2732 Baker Blvd.,Eugene,97403
GROSR,GROSELLA-Restaurante,5ª Ave. Los Palos Grandes,Caracas,1081
HANAR,Hanari Carnes,Rua do Paço, 67,Rio de Janeiro,05454-876
HILAA,HILARION-Abastos,Carrera 22 con Ave. Carlos Soublette #8-35,San Cristóbal,5022
HUNGC,Hungry Coyote Import Store,City Center Plaza 516 Main St.,Elgin,97827
HUNGO,Hungry Owl All-Night Grocers,8 Johnstown Road,Cork,
ISLAT,Island Trading,Garden House Crowther Way,Cowes,PO31 7PJ
KOENE,Königlich Essen,Maubelstr. 90,Brandenburg,14776
LACOR,La corne d'abondance,67, avenue de l'Europe,Versailles,78000
LAMAI,La maison d'Asie,1 rue Alsace-Lorraine,Toulouse,31000
LAUGB,Laughing Bacchus Wine Cellars,1900 Oak St.,Vancouver,V3F 2K1
LAZYK,Lazy K Kountry Store,12 Orchestra Terrace,Walla Walla,99362
LEHMS,Lehmanns Marktstand,Magazinweg 7,Frankfurt a.M.,60528
LETSS,Let's Stop N Shop,87 Polk St. Suite 5,San Francisco,94117
LILAS,LILA-Supermercado,Carrera 52 con Ave. Bolívar #65-98 Llano Largo,Barquisimeto,3508
LINOD,LINO-Delicateses,Ave. 5 de Mayo Porlamar,I. de Margarita,4980
LONEP,Lonesome Pine Restaurant,89 Chiaroscuro Rd.,Portland,97219
MAGAA,Magazzini Alimentari Riuniti,Via Ludovico il Moro 22,Bergamo,24100
MAISD,Maison Dewey,Rue Joseph-Bens 532,Bruxelles,B-1180
END CUSTOMERS

ORDERS
10250,HANAR,7/8/1996 12:00:00 AM,7/12/1996 12:00:00 AM
10253,HANAR,7/10/1996 12:00:00 AM,7/16/1996 12:00:00 AM
10254,CHOPS,7/11/1996 12:00:00 AM,7/23/1996 12:00:00 AM
10257,HILAA,7/16/1996 12:00:00 AM,7/22/1996 12:00:00 AM
10258,ERNSH,7/17/1996 12:00:00 AM,7/23/1996 12:00:00 AM
10263,ERNSH,7/23/1996 12:00:00 AM,7/31/1996 12:00:00 AM
10264,FOLKO,7/24/1996 12:00:00 AM,8/23/1996 12:00:00 AM
10265,BLONP,7/25/1996 12:00:00 AM,8/12/1996 12:00:00 AM
10267,FRANK,7/29/1996 12:00:00 AM,8/6/1996 12:00:00 AM
10275,MAGAA,8/7/1996 12:00:00 AM,8/9/1996 12:00:00 AM
10278,BERGS,8/12/1996 12:00:00 AM,8/16/1996 12:00:00 AM
10279,LEHMS,8/13/1996 12:00:00 AM,8/16/1996 12:00:00 AM
10280,BERGS,8/14/1996 12:00:00 AM,9/12/1996 12:00:00 AM
10283,LILAS,8/16/1996 12:00:00 AM,8/23/1996 12:00:00 AM
10284,LEHMS,8/19/1996 12:00:00 AM,8/27/1996 12:00:00 AM
10289,BSBEV,8/26/1996 12:00:00 AM,8/28/1996 12:00:00 AM
10290,COMMI,8/27/1996 12:00:00 AM,9/3/1996 12:00:00 AM
10296,LILAS,9/3/1996 12:00:00 AM,9/11/1996 12:00:00 AM
10297,BLONP,9/4/1996 12:00:00 AM,9/10/1996 12:00:00 AM
10298,HUNGO,9/5/1996 12:00:00 AM,9/11/1996 12:00:00 AM
10300,MAGAA,9/9/1996 12:00:00 AM,9/18/1996 12:00:00 AM
10303,GODOS,9/11/1996 12:00:00 AM,9/18/1996 12:00:00 AM
10307,LONEP,9/17/1996 12:00:00 AM,9/25/1996 12:00:00 AM
10309,HUNGO,9/19/1996 12:00:00 AM,10/23/1996 12:00:00 AM
10315,ISLAT,9/26/1996 12:00:00 AM,10/3/1996 12:00:00 AM
10317,LONEP,9/30/1996 12:00:00 AM,10/10/1996 12:00:00 AM
10318,ISLAT,10/1/1996 12:00:00 AM,10/4/1996 12:00:00 AM
10321,ISLAT,10/3/1996 12:00:00 AM,10/11/1996 12:00:00 AM
10323,KOENE,10/7/1996 12:00:00 AM,10/14/1996 12:00:00 AM
10325,KOENE,10/9/1996 12:00:00 AM,10/14/1996 12:00:00 AM
10327,FOLKO,10/11/1996 12:00:00 AM,10/14/1996 12:00:00 AM
10328,FURIB,10/14/1996 12:00:00 AM,10/17/1996 12:00:00 AM
10330,LILAS,10/16/1996 12:00:00 AM,10/28/1996 12:00:00 AM
10331,BONAP,10/16/1996 12:00:00 AM,10/21/1996 12:00:00 AM
10335,HUNGO,10/22/1996 12:00:00 AM,10/24/1996 12:00:00 AM
10337,FRANK,10/24/1996 12:00:00 AM,10/29/1996 12:00:00 AM
10340,BONAP,10/29/1996 12:00:00 AM,11/8/1996 12:00:00 AM
10342,FRANK,10/30/1996 12:00:00 AM,11/4/1996 12:00:00 AM
10343,LEHMS,10/31/1996 12:00:00 AM,11/6/1996 12:00:00 AM
10347,FAMIA,11/6/1996 12:00:00 AM,11/8/1996 12:00:00 AM
10347,FAMIA,11/6/1996 12:00:00 AM,11/8/1996 12:00:00 AM
10350,LAMAI,11/11/1996 12:00:00 AM,12/3/1996 12:00:00 AM
10351,ERNSH,11/11/1996 12:00:00 AM,11/20/1996 12:00:00 AM
10352,FURIB,11/12/1996 12:00:00 AM,11/18/1996 12:00:00 AM
10355,AROUT,11/15/1996 12:00:00 AM,11/20/1996 12:00:00 AM
10357,LILAS,11/19/1996 12:00:00 AM,12/2/1996 12:00:00 AM
10358,LAMAI,11/20/1996 12:00:00 AM,11/27/1996 12:00:00 AM
10360,BLONP,11/22/1996 12:00:00 AM,12/2/1996 12:00:00 AM
10362,BONAP,11/25/1996 12:00:00 AM,11/28/1996 12:00:00 AM
10363,DRACD,11/26/1996 12:00:00 AM,12/4/1996 12:00:00 AM
10364,EASTC,11/26/1996 12:00:00 AM,12/4/1996 12:00:00 AM
10365,ANTON,11/27/1996 12:00:00 AM,12/2/1996 12:00:00 AM
10366,GALED,11/28/1996 12:00:00 AM,12/30/1996 12:00:00 AM
10368,ERNSH,11/29/1996 12:00:00 AM,12/2/1996 12:00:00 AM
10370,CHOPS,12/3/1996 12:00:00 AM,12/27/1996 12:00:00 AM
10371,LAMAI,12/3/1996 12:00:00 AM,12/24/1996 12:00:00 AM
10373,HUNGO,12/5/1996 12:00:00 AM,12/11/1996 12:00:00 AM
10375,HUNGC,12/6/1996 12:00:00 AM,12/9/1996 12:00:00 AM
10378,FOLKO,12/10/1996 12:00:00 AM,12/19/1996 12:00:00 AM
10380,HUNGO,12/12/1996 12:00:00 AM,1/16/1997 12:00:00 AM
10381,LILAS,12/12/1996 12:00:00 AM,12/13/1996 12:00:00 AM
10382,ERNSH,12/13/1996 12:00:00 AM,12/16/1996 12:00:00 AM
10383,AROUT,12/16/1996 12:00:00 AM,12/18/1996 12:00:00 AM
10384,BERGS,12/16/1996 12:00:00 AM,12/20/1996 12:00:00 AM
10386,FAMIA,12/18/1996 12:00:00 AM,12/25/1996 12:00:00 AM
10389,BOTTM,12/20/1996 12:00:00 AM,12/24/1996 12:00:00 AM
10391,DRACD,12/23/1996 12:00:00 AM,12/31/1996 12:00:00 AM
10394,HUNGC,12/25/1996 12:00:00 AM,1/3/1997 12:00:00 AM
10395,HILAA,12/26/1996 12:00:00 AM,1/3/1997 12:00:00 AM
10396,FRANK,12/27/1996 12:00:00 AM,1/6/1997 12:00:00 AM
10400,EASTC,1/1/1997 12:00:00 AM,1/16/1997 12:00:00 AM
10404,MAGAA,1/3/1997 12:00:00 AM,1/8/1997 12:00:00 AM
10405,LINOD,1/6/1997 12:00:00 AM,1/22/1997 12:00:00 AM
10408,FOLIG,1/8/1997 12:00:00 AM,1/14/1997 12:00:00 AM
10410,BOTTM,1/10/1997 12:00:00 AM,1/15/1997 12:00:00 AM
10411,BOTTM,1/10/1997 12:00:00 AM,1/21/1997 12:00:00 AM
10413,LAMAI,1/14/1997 12:00:00 AM,1/16/1997 12:00:00 AM
10414,FAMIA,1/14/1997 12:00:00 AM,1/17/1997 12:00:00 AM
10415,HUNGC,1/15/1997 12:00:00 AM,1/24/1997 12:00:00 AM
10422,FRANS,1/22/1997 12:00:00 AM,1/31/1997 12:00:00 AM
10423,GOURL,1/23/1997 12:00:00 AM,2/24/1997 12:00:00 AM
10425,LAMAI,1/24/1997 12:00:00 AM,2/14/1997 12:00:00 AM
10426,GALED,1/27/1997 12:00:00 AM,2/6/1997 12:00:00 AM
10431,BOTTM,1/30/1997 12:00:00 AM,2/7/1997 12:00:00 AM
10434,FOLKO,2/3/1997 12:00:00 AM,2/13/1997 12:00:00 AM
10436,BLONP,2/5/1997 12:00:00 AM,2/11/1997 12:00:00 AM
10444,BERGS,2/12/1997 12:00:00 AM,2/21/1997 12:00:00 AM
10445,BERGS,2/13/1997 12:00:00 AM,2/20/1997 12:00:00 AM
10449,BLONP,2/18/1997 12:00:00 AM,2/27/1997 12:00:00 AM
10453,AROUT,2/21/1997 12:00:00 AM,2/26/1997 12:00:00 AM
10456,KOENE,2/25/1997 12:00:00 AM,2/28/1997 12:00:00 AM
10457,KOENE,2/25/1997 12:00:00 AM,3/3/1997 12:00:00 AM
10460,FOLKO,2/28/1997 12:00:00 AM,3/3/1997 12:00:00 AM
10464,FURIB,3/4/1997 12:00:00 AM,3/14/1997 12:00:00 AM
10466,COMMI,3/6/1997 12:00:00 AM,3/13/1997 12:00:00 AM
10467,MAGAA,3/6/1997 12:00:00 AM,3/11/1997 12:00:00 AM
10468,KOENE,3/7/1997 12:00:00 AM,3/12/1997 12:00:00 AM
10470,BONAP,3/11/1997 12:00:00 AM,3/14/1997 12:00:00 AM
10471,BSBEV,3/11/1997 12:00:00 AM,3/18/1997 12:00:00 AM
10473,ISLAT,3/13/1997 12:00:00 AM,3/21/1997 12:00:00 AM
10476,HILAA,3/17/1997 12:00:00 AM,3/24/1997 12:00:00 AM
10480,FOLIG,3/20/1997 12:00:00 AM,3/24/1997 12:00:00 AM
10484,BSBEV,3/24/1997 12:00:00 AM,4/1/1997 12:00:00 AM
10485,LINOD,3/25/1997 12:00:00 AM,3/31/1997 12:00:00 AM
10486,HILAA,3/26/1997 12:00:00 AM,4/2/1997 12:00:00 AM
10488,FRANK,3/27/1997 12:00:00 AM,4/2/1997 12:00:00 AM
10490,HILAA,3/31/1997 12:00:00 AM,4/3/1997 12:00:00 AM
10491,FURIB,3/31/1997 12:00:00 AM,4/8/1997 12:00:00 AM
10492,BOTTM,4/1/1997 12:00:00 AM,4/11/1997 12:00:00 AM
10494,COMMI,4/2/1997 12:00:00 AM,4/9/1997 12:00:00 AM
10494,COMMI,4/2/1997 12:00:00 AM,4/9/1997 12:00:00 AM
10497,LEHMS,4/4/1997 12:00:00 AM,4/7/1997 12:00:00 AM
10501,BLAUS,4/9/1997 12:00:00 AM,4/16/1997 12:00:00 AM
10507,ANTON,4/15/1997 12:00:00 AM,4/22/1997 12:00:00 AM
10509,BLAUS,4/17/1997 12:00:00 AM,4/29/1997 12:00:00 AM
10511,BONAP,4/18/1997 12:00:00 AM,4/21/1997 12:00:00 AM
10512,FAMIA,4/21/1997 12:00:00 AM,4/24/1997 12:00:00 AM
10519,CHOPS,4/28/1997 12:00:00 AM,5/1/1997 12:00:00 AM
10521,CACTU,4/29/1997 12:00:00 AM,5/2/1997 12:00:00 AM
10522,LEHMS,4/30/1997 12:00:00 AM,5/6/1997 12:00:00 AM
10528,GREAL,5/6/1997 12:00:00 AM,5/9/1997 12:00:00 AM
10529,MAISD,5/7/1997 12:00:00 AM,5/9/1997 12:00:00 AM
10532,EASTC,5/9/1997 12:00:00 AM,5/12/1997 12:00:00 AM
10535,ANTON,5/13/1997 12:00:00 AM,5/21/1997 12:00:00 AM
10538,BSBEV,5/15/1997 12:00:00 AM,5/16/1997 12:00:00 AM
10539,BSBEV,5/16/1997 12:00:00 AM,5/23/1997 12:00:00 AM
10541,HANAR,5/19/1997 12:00:00 AM,5/29/1997 12:00:00 AM
10544,LONEP,5/21/1997 12:00:00 AM,5/30/1997 12:00:00 AM
10550,GODOS,5/28/1997 12:00:00 AM,6/6/1997 12:00:00 AM
10551,FURIB,5/28/1997 12:00:00 AM,6/6/1997 12:00:00 AM
10558,AROUT,6/4/1997 12:00:00 AM,6/10/1997 12:00:00 AM
10568,GALED,6/13/1997 12:00:00 AM,7/9/1997 12:00:00 AM
10573,ANTON,6/19/1997 12:00:00 AM,6/20/1997 12:00:00 AM
10581,FAMIA,6/26/1997 12:00:00 AM,7/2/1997 12:00:00 AM
10582,BLAUS,6/27/1997 12:00:00 AM,7/14/1997 12:00:00 AM
10589,GREAL,7/4/1997 12:00:00 AM,7/14/1997 12:00:00 AM
10600,HUNGC,7/16/1997 12:00:00 AM,7/21/1997 12:00:00 AM
10614,BLAUS,7/29/1997 12:00:00 AM,8/1/1997 12:00:00 AM
10616,GREAL,7/31/1997 12:00:00 AM,8/5/1997 12:00:00 AM
10617,GREAL,7/31/1997 12:00:00 AM,8/4/1997 12:00:00 AM
10621,ISLAT,8/5/1997 12:00:00 AM,8/11/1997 12:00:00 AM
10629,GODOS,8/12/1997 12:00:00 AM,8/20/1997 12:00:00 AM
10634,FOLIG,8/15/1997 12:00:00 AM,8/21/1997 12:00:00 AM
10635,MAGAA,8/18/1997 12:00:00 AM,8/21/1997 12:00:00 AM
10638,LINOD,8/20/1997 12:00:00 AM,9/1/1997 12:00:00 AM
10643,ALFKI,8/25/1997 12:00:00 AM,9/2/1997 12:00:00 AM
10645,HANAR,8/26/1997 12:00:00 AM,9/2/1997 12:00:00 AM
10649,MAISD,8/28/1997 12:00:00 AM,8/29/1997 12:00:00 AM
10652,GOURL,9/1/1997 12:00:00 AM,9/8/1997 12:00:00 AM
10656,GREAL,9/4/1997 12:00:00 AM,9/10/1997 12:00:00 AM
10660,HUNGC,9/8/1997 12:00:00 AM,10/15/1997 12:00:00 AM
10662,LONEP,9/9/1997 12:00:00 AM,9/18/1997 12:00:00 AM
10665,LONEP,9/11/1997 12:00:00 AM,9/17/1997 12:00:00 AM
10677,ANTON,9/22/1997 12:00:00 AM,9/26/1997 12:00:00 AM
10685,GOURL,9/29/1997 12:00:00 AM,10/3/1997 12:00:00 AM
10690,HANAR,10/2/1997 12:00:00 AM,10/3/1997 12:00:00 AM
10692,ALFKI,10/3/1997 12:00:00 AM,10/13/1997 12:00:00 AM
10697,LINOD,10/8/1997 12:00:00 AM,10/14/1997 12:00:00 AM
10702,ALFKI,10/13/1997 12:00:00 AM,10/21/1997 12:00:00 AM
10707,AROUT,10/16/1997 12:00:00 AM,10/23/1997 12:00:00 AM
10709,GOURL,10/17/1997 12:00:00 AM,11/20/1997 12:00:00 AM
10710,FRANS,10/20/1997 12:00:00 AM,10/23/1997 12:00:00 AM
10726,EASTC,11/3/1997 12:00:00 AM,12/5/1997 12:00:00 AM
10729,LINOD,11/4/1997 12:00:00 AM,11/14/1997 12:00:00 AM
10731,CHOPS,11/6/1997 12:00:00 AM,11/14/1997 12:00:00 AM
10734,GOURL,11/7/1997 12:00:00 AM,11/12/1997 12:00:00 AM
10746,CHOPS,11/19/1997 12:00:00 AM,11/21/1997 12:00:00 AM
10753,FRANS,11/25/1997 12:00:00 AM,11/27/1997 12:00:00 AM
10760,MAISD,12/1/1997 12:00:00 AM,12/10/1997 12:00:00 AM
10763,FOLIG,12/3/1997 12:00:00 AM,12/8/1997 12:00:00 AM
10782,CACTU,12/17/1997 12:00:00 AM,12/22/1997 12:00:00 AM
10789,FOLIG,12/22/1997 12:00:00 AM,12/31/1997 12:00:00 AM
10797,DRACD,12/25/1997 12:00:00 AM,1/5/1998 12:00:00 AM
10807,FRANS,12/31/1997 12:00:00 AM,1/30/1998 12:00:00 AM
10819,CACTU,1/7/1998 12:00:00 AM,1/16/1998 12:00:00 AM
10825,DRACD,1/9/1998 12:00:00 AM,1/14/1998 12:00:00 AM
10835,ALFKI,1/15/1998 12:00:00 AM,1/21/1998 12:00:00 AM
10853,BLAUS,1/27/1998 12:00:00 AM,2/3/1998 12:00:00 AM
10872,GODOS,2/5/1998 12:00:00 AM,2/9/1998 12:00:00 AM
10874,GODOS,2/6/1998 12:00:00 AM,2/11/1998 12:00:00 AM
10874,GODOS,2/6/1998 12:00:00 AM,2/11/1998 12:00:00 AM
10881,CACTU,2/11/1998 12:00:00 AM,2/18/1998 12:00:00 AM
10887,GALED,2/13/1998 12:00:00 AM,2/16/1998 12:00:00 AM
10892,MAISD,2/17/1998 12:00:00 AM,2/19/1998 12:00:00 AM
10896,MAISD,2/19/1998 12:00:00 AM,2/27/1998 12:00:00 AM
10928,GALED,3/5/1998 12:00:00 AM,3/18/1998 12:00:00 AM
10937,CACTU,3/10/1998 12:00:00 AM,3/13/1998 12:00:00 AM
10952,ALFKI,3/16/1998 12:00:00 AM,3/24/1998 12:00:00 AM
10969,COMMI,3/23/1998 12:00:00 AM,3/30/1998 12:00:00 AM
10987,EASTC,3/31/1998 12:00:00 AM,4/6/1998 12:00:00 AM
11026,FRANS,4/15/1998 12:00:00 AM,4/28/1998 12:00:00 AM
11036,DRACD,4/20/1998 12:00:00 AM,4/22/1998 12:00:00 AM
11042,COMMI,4/22/1998 12:00:00 AM,5/1/1998 12:00:00 AM
END ORDERS

ORDER DETAILS
10250,41,7.7000,10,0
10250,51,42.4000,35,0.15
10250,65,16.8000,15,0.15
10253,31,10.0000,20,0
10253,39,14.4000,42,0
10253,49,16.0000,40,0
10254,24,3.6000,15,0.15
10254,55,19.2000,21,0.15
10254,74,8.0000,21,0
10257,27,35.1000,25,0
10257,39,14.4000,6,0
10257,77,10.4000,15,0
10258,2,15.2000,50,0.2
10258,5,17.0000,65,0.2
10258,32,25.6000,6,0.2
10263,16,13.9000,60,0.25
10263,24,3.6000,28,0
10263,30,20.7000,60,0.25
10263,74,8.0000,36,0.25
10264,2,15.2000,35,0
10264,41,7.7000,25,0.15
10265,17,31.2000,30,0
10265,70,12.0000,20,0
10267,40,14.7000,50,0
10267,59,44.0000,70,0.15
10267,76,14.4000,15,0.15
10275,24,3.6000,12,0.05
10275,59,44.0000,6,0.05
10278,44,15.5000,16,0
10278,59,44.0000,15,0
10278,63,35.1000,8,0
10278,73,12.0000,25,0
10279,17,31.2000,15,0.25
10280,24,3.6000,12,0
10280,55,19.2000,20,0
10280,75,6.2000,30,0
10283,15,12.4000,20,0
10283,19,7.3000,18,0
10283,60,27.2000,35,0
10283,72,27.8000,3,0
10284,27,35.1000,15,0.25
10284,44,15.5000,21,0
10284,60,27.2000,20,0.25
10284,67,11.2000,5,0.25
10289,3,8.0000,30,0
10289,64,26.6000,9,0
10290,5,17.0000,20,0
10290,29,99.0000,15,0
10290,49,16.0000,15,0
10290,77,10.4000,10,0
10296,11,16.8000,12,0
10296,16,13.9000,30,0
10296,69,28.8000,15,0
10297,39,14.4000,60,0
10297,72,27.8000,20,0
10298,2,15.2000,40,0
10298,36,15.2000,40,0.25
10298,59,44.0000,30,0.25
10298,62,39.4000,15,0
10300,66,13.6000,30,0
10300,68,10.0000,20,0
10303,40,14.7000,40,0.1
10303,65,16.8000,30,0.1
10303,68,10.0000,15,0.1
10307,62,39.4000,10,0
10307,68,10.0000,3,0
10309,4,17.6000,20,0
10309,6,20.0000,30,0
10309,42,11.2000,2,0
10309,43,36.8000,20,0
10309,71,17.2000,3,0
10315,34,11.2000,14,0
10315,70,12.0000,30,0
10317,1,14.4000,20,0
10318,41,7.7000,20,0
10318,76,14.4000,6,0
10321,35,14.4000,10,0
10323,15,12.4000,5,0
10323,25,11.2000,4,0
10323,39,14.4000,4,0
10325,6,20.0000,6,0
10325,13,4.8000,12,0
10325,14,18.6000,9,0
10325,31,10.0000,4,0
10325,72,27.8000,40,0
10327,2,15.2000,25,0.2
10327,11,16.8000,50,0.2
10327,30,20.7000,35,0.2
10327,58,10.6000,30,0.2
10328,59,44.0000,9,0
10328,65,16.8000,40,0
10328,68,10.0000,10,0
10330,26,24.9000,50,0.15
10330,72,27.8000,25,0.15
10331,54,5.9000,15,0
10335,2,15.2000,7,0.2
10335,31,10.0000,25,0.2
10335,32,25.6000,6,0.2
10335,51,42.4000,48,0.2
10337,23,7.2000,40,0
10337,26,24.9000,24,0
10337,36,15.2000,20,0
10337,37,20.8000,28,0
10337,72,27.8000,25,0
10340,18,50.0000,20,0.05
10340,41,7.7000,12,0.05
10340,43,36.8000,40,0.05
10342,2,15.2000,24,0.2
10342,31,10.0000,56,0.2
10342,36,15.2000,40,0.2
10342,55,19.2000,40,0.2
10343,64,26.6000,50,0
10343,68,10.0000,4,0.05
10343,76,14.4000,15,0
10347,25,11.2000,10,0
10347,39,14.4000,50,0.15
10347,40,14.7000,4,0
10347,75,6.2000,6,0.15
10350,50,13.0000,15,0.1
10350,69,28.8000,18,0.1
10351,38,210.8000,20,0.05
10351,41,7.7000,13,0
10351,44,15.5000,77,0.05
10351,65,16.8000,10,0.05
10352,24,3.6000,10,0
10352,54,5.9000,20,0.15
10355,24,3.6000,25,0
10355,57,15.6000,25,0
10357,10,24.8000,30,0.2
10357,26,24.9000,16,0
10357,60,27.2000,8,0.2
10358,24,3.6000,10,0.05
10358,34,11.2000,10,0.05
10358,36,15.2000,20,0.05
10360,28,36.4000,30,0
10360,29,99.0000,35,0
10360,38,210.8000,10,0
10360,49,16.0000,35,0
10360,54,5.9000,28,0
10362,25,11.2000,50,0
10362,51,42.4000,20,0
10362,54,5.9000,24,0
10363,31,10.0000,20,0
10363,75,6.2000,12,0
10363,76,14.4000,12,0
10364,69,28.8000,30,0
10364,71,17.2000,5,0
10365,11,16.8000,24,0
10366,65,16.8000,5,0
10366,77,10.4000,5,0
10368,21,8.0000,5,0.1
10368,28,36.4000,13,0.1
10368,57,15.6000,25,0
10368,64,26.6000,35,0.1
10370,1,14.4000,15,0.15
10370,64,26.6000,30,0
10370,74,8.0000,20,0.15
10371,36,15.2000,6,0.2
10373,58,10.6000,80,0.2
10373,71,17.2000,50,0.2
10375,14,18.6000,15,0
10375,54,5.9000,10,0
10378,71,17.2000,6,0
10380,30,20.7000,18,0.1
10380,53,26.2000,20,0.1
10380,60,27.2000,6,0.1
10380,70,12.0000,30,0
10381,74,8.0000,14,0
10382,5,17.0000,32,0
10382,18,50.0000,9,0
10382,29,99.0000,14,0
10382,33,2.0000,60,0
10382,74,8.0000,50,0
10383,13,4.8000,20,0
10383,50,13.0000,15,0
10383,56,30.4000,20,0
10384,20,64.8000,28,0
10384,60,27.2000,15,0
10386,24,3.6000,15,0
10386,34,11.2000,10,0
10389,10,24.8000,16,0
10389,55,19.2000,15,0
10389,62,39.4000,20,0
10389,70,12.0000,30,0
10391,13,4.8000,18,0
10394,13,4.8000,10,0
10394,62,39.4000,10,0
10395,46,9.6000,28,0.1
10395,53,26.2000,70,0.1
10395,69,28.8000,8,0
10396,23,7.2000,40,0
10396,23,7.2000,40,0
10396,71,17.2000,60,0
10396,72,27.8000,21,0
10400,29,99.0000,21,0
10400,35,14.4000,35,0
10400,49,16.0000,30,0
10404,26,24.9000,30,0.05
10404,42,11.2000,40,0.05
10404,49,16.0000,30,0.05
10405,3,8.0000,50,0
10408,37,20.8000,10,0
10408,54,5.9000,6,0
10408,62,39.4000,35,0
10410,33,2.0000,49,0
10410,59,44.0000,16,0
10411,41,7.7000,25,0.2
10411,44,15.5000,40,0.2
10411,59,44.0000,9,0.2
10413,1,14.4000,24,0
10413,62,39.4000,40,0
10413,76,14.4000,14,0
10414,19,7.3000,18,0.05
10414,33,2.0000,50,0
10415,17,31.2000,2,0
10415,33,2.0000,20,0
10422,26,24.9000,2,0
10423,31,10.0000,14,0
10423,59,44.0000,20,0
10425,55,19.2000,10,0.25
10425,76,14.4000,20,0.25
10426,56,30.4000,5,0
10426,64,26.6000,7,0
10431,17,31.2000,50,0.25
10431,40,14.7000,50,0.25
10431,47,7.6000,30,0.25
10434,11,16.8000,6,0
10434,76,14.4000,18,0.15
10436,46,9.6000,5,0
10436,56,30.4000,40,0.1
10436,64,26.6000,30,0.1
10436,75,6.2000,24,0.1
10444,17,31.2000,10,0
10444,26,24.9000,15,0
10444,35,14.4000,8,0
10444,41,7.7000,30,0
10445,39,14.4000,6,0
10445,54,5.9000,15,0
10449,10,24.8000,14,0
10449,52,5.6000,20,0
10449,62,39.4000,35,0
10453,48,10.2000,15,0.1
10453,70,12.0000,25,0.1
10456,21,8.0000,40,0.15
10456,49,16.0000,21,0.15
10457,59,44.0000,36,0
10460,68,10.0000,21,0.25
10460,75,6.2000,4,0.25
10464,4,17.6000,16,0.2
10464,43,36.8000,3,0
10464,56,30.4000,30,0.2
10464,60,27.2000,20,0
10466,11,16.8000,10,0
10466,46,9.6000,5,0
10467,24,3.6000,28,0
10467,25,11.2000,12,0
10468,30,20.7000,8,0
10468,43,36.8000,15,0
10470,18,50.0000,30,0
10470,23,7.2000,15,0
10470,64,26.6000,8,0
10470,64,26.6000,8,0
10471,7,24.0000,30,0
10471,56,30.4000,20,0
10473,33,2.0000,12,0
10473,71,17.2000,12,0
10476,55,19.2000,2,0.05
10476,70,12.0000,12,0
10480,47,7.6000,30,0
10480,59,44.0000,12,0
10484,21,8.0000,14,0
10484,40,14.7000,10,0
10484,51,42.4000,3,0
10485,2,15.2000,20,0.1
10485,3,8.0000,20,0.1
10485,55,19.2000,30,0.1
10485,70,12.0000,60,0.1
10486,11,16.8000,5,0
10486,51,42.4000,25,0
10486,74,8.0000,16,0
10488,59,44.0000,30,0
10488,73,12.0000,20,0.2
10490,59,44.0000,60,0
10490,68,10.0000,30,0
10490,75,6.2000,36,0
10491,44,15.5000,15,0.15
10491,77,10.4000,7,0.15
10492,25,11.2000,60,0.05
10492,42,11.2000,20,0.05
10494,56,30.4000,30,0
10497,56,30.4000,14,0
10497,72,27.8000,25,0
10497,77,10.4000,25,0
10501,54,7.4500,20,0
10507,43,46.0000,15,0.15
10507,48,12.7500,15,0.15
10509,28,45.6000,3,0
10511,4,22.0000,50,0.15
10511,7,30.0000,50,0.15
10511,8,40.0000,10,0.15
10512,24,4.5000,10,0.15
10512,46,12.0000,9,0.15
10512,47,9.5000,6,0.15
10512,60,34.0000,12,0.15
10519,10,31.0000,16,0.05
10519,56,38.0000,40,0
10519,60,34.0000,10,0.05
10521,35,18.0000,3,0
10521,41,9.6500,10,0
10521,68,12.5000,6,0
10522,1,18.0000,40,0.2
10522,8,40.0000,24,0
10522,30,25.8900,20,0.2
10522,40,18.4000,25,0.2
10528,11,21.0000,3,0
10528,33,2.5000,8,0.2
10528,72,34.8000,9,0
10529,55,24.0000,14,0
10529,68,12.5000,20,0
10529,69,36.0000,10,0
10532,30,25.8900,15,0
10532,66,17.0000,24,0
10535,11,21.0000,50,0.1
10535,40,18.4000,10,0.1
10535,57,19.5000,5,0.1
10535,59,55.0000,15,0.1
10538,70,15.0000,7,0
10538,72,34.8000,1,0
10539,13,6.0000,8,0
10539,21,10.0000,15,0
10539,33,2.5000,15,0
10539,33,2.5000,15,0
10539,49,20.0000,6,0
10541,24,4.5000,35,0.1
10541,38,263.5000,4,0.1
10541,65,21.0500,36,0.1
10541,71,21.5000,9,0.1
10544,28,45.6000,7,0
10544,67,14.0000,7,0
10550,17,39.0000,8,0.1
10550,19,9.2000,10,0
10550,21,10.0000,6,0.1
10550,61,28.5000,10,0.1
10551,16,17.4500,40,0.15
10551,35,18.0000,20,0.15
10551,44,19.4500,40,0
10558,47,9.5000,25,0
10558,51,53.0000,20,0
10558,52,7.0000,30,0
10558,53,32.8000,18,0
10558,73,15.0000,3,0
10568,10,31.0000,5,0
10573,17,39.0000,18,0
10573,34,14.0000,40,0
10573,53,32.8000,25,0
10581,75,7.7500,50,0.2
10582,57,19.5000,4,0
10582,76,18.0000,14,0
10589,35,18.0000,4,0
10600,54,7.4500,4,0
10600,73,15.0000,30,0
10614,11,21.0000,14,0
10614,21,10.0000,8,0
10614,39,18.0000,5,0
10616,38,263.5000,15,0.05
10616,56,38.0000,14,0
10616,70,15.0000,15,0.05
10616,71,21.5000,15,0.05
10617,59,55.0000,30,0.15
10621,19,9.2000,5,0
10621,23,9.0000,10,0
10621,70,15.0000,20,0
10621,71,21.5000,15,0
10629,29,123.7900,20,0
10629,64,33.2500,9,0
10634,7,30.0000,35,0
10634,18,62.5000,50,0
10634,51,53.0000,15,0
10634,75,7.7500,2,0
10635,4,22.0000,10,0.1
10635,5,21.3500,15,0.1
10635,22,21.0000,40,0
10638,45,9.5000,20,0
10638,65,21.0500,21,0
10638,72,34.8000,60,0
10643,28,45.6000,15,0.25
10643,39,18.0000,21,0.25
10643,46,12.0000,2,0.25
10645,18,62.5000,20,0
10645,36,19.0000,15,0
10649,28,45.6000,20,0
10649,72,34.8000,15,0
10652,30,25.8900,2,0.25
10652,42,14.0000,20,0
10656,14,23.2500,3,0.1
10656,44,19.4500,28,0.1
10656,47,9.5000,6,0.1
10660,20,81.0000,21,0
10662,68,12.5000,10,0
10665,51,53.0000,20,0
10665,59,55.0000,1,0
10665,59,55.0000,1,0
10665,76,18.0000,10,0
10677,26,31.2300,30,0.15
10677,33,2.5000,8,0.15
10685,10,31.0000,20,0
10685,41,9.6500,4,0
10685,47,9.5000,15,0
10690,56,38.0000,20,0.25
10690,77,13.0000,30,0.25
10692,63,43.9000,20,0
10697,19,9.2000,7,0.25
10697,35,18.0000,9,0.25
10697,58,13.2500,30,0.25
10697,70,15.0000,30,0.25
10702,3,10.0000,6,0
10702,76,18.0000,15,0
10707,55,24.0000,21,0
10707,57,19.5000,40,0
10707,70,15.0000,28,0.15
10709,8,40.0000,40,0
10709,51,53.0000,28,0
10709,60,34.0000,10,0
10710,19,9.2000,5,0
10710,47,9.5000,5,0
10726,4,22.0000,25,0
10726,11,21.0000,5,0
10729,1,18.0000,50,0
10729,21,10.0000,30,0
10729,50,16.2500,40,0
10731,21,10.0000,40,0.05
10731,51,53.0000,30,0.05
10734,6,25.0000,30,0
10734,30,25.8900,15,0
10734,76,18.0000,20,0
10746,13,6.0000,6,0
10746,42,14.0000,28,0
10746,62,49.3000,9,0
10746,69,36.0000,40,0
10753,45,9.5000,4,0
10753,74,10.0000,5,0
10760,25,14.0000,12,0.25
10760,27,43.9000,40,0
10760,43,46.0000,30,0.25
10763,21,10.0000,40,0
10763,22,21.0000,6,0
10763,24,4.5000,20,0
10782,31,12.5000,1,0
10789,18,62.5000,30,0
10789,35,18.0000,15,0
10789,63,43.9000,30,0
10789,68,12.5000,18,0
10797,11,21.0000,20,0
10807,40,18.4000,1,0
10819,43,46.0000,7,0
10819,75,7.7500,20,0
10825,26,31.2300,12,0
10825,53,32.8000,20,0
10835,59,55.0000,15,0
10835,77,13.0000,2,0.2
10853,18,62.5000,10,0
10872,55,24.0000,10,0.05
10872,62,49.3000,20,0.05
10872,64,33.2500,15,0.05
10872,65,21.0500,21,0.05
10874,10,31.0000,10,0
10881,73,15.0000,10,0
10887,25,14.0000,5,0
10892,59,55.0000,40,0.05
10896,45,9.5000,15,0
10896,56,38.0000,16,0
10896,56,38.0000,16,0
10928,47,9.5000,5,0
10928,76,18.0000,5,0
10937,28,45.6000,8,0
10937,34,14.0000,20,0
10952,6,25.0000,16,0.05
10952,28,45.6000,2,0
10969,46,12.0000,9,0
10987,7,30.0000,60,0
10987,43,46.0000,6,0
10987,72,34.8000,20,0
11026,18,62.5000,8,0
11026,51,53.0000,10,0
11036,13,6.0000,7,0
11036,59,55.0000,30,0
11042,44,19.4500,15,0
11042,61,28.5000,4,0
END ORDER DETAILS

PRODUCTS
1,Chai,18.0000
2,Chang,19.0000
3,Aniseed Syrup,10.0000
4,Chef Anton's Cajun Seasoning,22.0000
5,Chef Anton's Gumbo Mix,21.3500
6,Grandma's Boysenberry Spread,25.0000
7,Uncle Bob's Organic Dried Pears,30.0000
8,Northwoods Cranberry Sauce,40.0000
9,Mishi Kobe Niku,97.0000
10,Ikura,31.0000
11,Queso Cabrales,21.0000
12,Queso Manchego La Pastora,38.0000
13,Konbu,6.0000
14,Tofu,23.2500
15,Genen Shouyu,15.5000
16,Pavlova,17.4500
17,Alice Mutton,39.0000
18,Carnarvon Tigers,62.5000
19,Teatime Chocolate Biscuits,9.2000
20,Sir Rodney's Marmalade,81.0000
21,Sir Rodney's Scones,10.0000
22,Gustaf's Knäckebröd,21.0000
23,Tunnbröd,9.0000
24,Guaraná Fantástica,4.5000
25,NuNuCa Nuß-Nougat-Creme,14.0000
26,Gumbär Gummibärchen,31.2300
27,Schoggi Schokolade,43.9000
28,Rössle Sauerkraut,45.6000
29,Thüringer Rostbratwurst,123.7900
30,Nord-Ost Matjeshering,25.8900
31,Gorgonzola Telino,12.5000
32,Mascarpone Fabioli,32.0000
33,Geitost,2.5000
34,Sasquatch Ale,14.0000
35,Steeleye Stout,18.0000
36,Inlagd Sill,19.0000
37,Gravad lax,26.0000
38,Côte de Blaye,263.5000
39,Chartreuse verte,18.0000
40,Boston Crab Meat,18.4000
41,Jack's New England Clam Chowder,9.6500
42,Singaporean Hokkien Fried Mee,14.0000
43,Ipoh Coffee,46.0000
44,Gula Malacca,19.4500
45,Rogede sild,9.5000
46,Spegesild,12.0000
47,Zaanse koeken,9.5000
48,Chocolade,12.7500
49,Maxilaku,20.0000
50,Valkoinen suklaa,16.2500
51,Manjimup Dried Apples,53.0000
52,Filo Mix,7.0000
53,Perth Pasties,32.8000
54,Tourtière,7.4500
55,Pâté chinois,24.0000
56,Gnocchi di nonna Alice,38.0000
57,Ravioli Angelo,19.5000
58,Escargots de Bourgogne,13.2500
59,Raclette Courdavault,55.0000
60,Camembert Pierrot,34.0000
61,Sirop d'érable,28.5000
62,Tarte au sucre,49.3000
63,Vegie-spread,43.9000
64,Wimmers gute Semmelknödel,33.2500
65,Louisiana Fiery Hot Pepper Sauce,21.0500
66,Louisiana Hot Spiced Okra,17.0000
67,Laughing Lumberjack Lager,14.0000
68,Scottish Longbreads,12.5000
69,Gudbrandsdalsost,36.0000
70,Outback Lager,15.0000
71,Flotemysost,21.5000
72,Mozzarella di Giovanni,34.8000
73,Röd Kaviar,15.0000
74,Longlife Tofu,10.0000
75,Rhönbräu Klosterbier,7.7500
76,Lakkalikööri,18.0000
77,Original Frankfurter grüne Soße,13.0000
END PRODUCTS

Voir aussi
Parallel LINQ (PLINQ)
Structures de données pour la programmation
parallèle
18/07/2020 • 7 minutes to read • Edit Online

La version 4 de .NET Framework introduit de nouveaux types très utiles pour la programmation parallèle,
notamment un ensemble de classes de collections simultanées, des primitives de synchronisation légères et des
types pour l’initialisation tardive. Vous pouvez utiliser ces types avec n’importe quel code d’application
multithread, y compris la bibliothèque parallèle de tâches et PLINQ.

Classes de collections simultanées


Les classes de collections de l’espace de noms System.Collections.Concurrent fournissent des opérations d’ajout et
de suppression thread-safe qui évitent autant que possible les verrous et, là où ils se révèlent nécessaires, utilisent
un verrouillage de granularité fine. Avec une classe de collections simultanées, contrairement aux collections
introduites dans les versions 1.0 et 2.0 de .NET Framework, il n’est pas nécessaire que le code utilisateur prenne
des verrous pour accéder aux éléments. Les classes de collections simultanées peuvent améliorer
considérablement les performances des types comme System.Collections.ArrayList et
System.Collections.Generic.List<T> (avec verrouillage implémenté par l’utilisateur) dans le cas où plusieurs
threads ajoutent et suppriment des éléments d’une collection.
Le tableau suivant liste les classes de collections simultanées :

TYPE DESC RIP T IO N

System.Collections.Concurrent.BlockingCollection<T> Fournit des fonctions bloquantes et englobantes pour les


collections thread-safe qui implémentent
System.Collections.Concurrent.IProducerConsumerCollection<
T>. Les threads producteurs se bloquent si aucun
emplacement n’est disponible ou que la collection est pleine.
Les threads consommateurs se bloquent si la collection est
vide. Ce type prend également en charge l’accès non bloquant
par les producteurs et les consommateurs.
BlockingCollection<T> peut être utilisé comme classe de base
ou comme magasin de stockage pour assurer le blocage et la
liaison des classes de collection qui prennent en charge
IEnumerable<T>.

System.Collections.Concurrent.ConcurrentBag<T> Implémentation de conteneur thread-safe qui effectue des


opérations Add et Get évolutives.

System.Collections.Concurrent.ConcurrentDictionary<TKey,TV Type dictionnaire simultané et évolutif.


alue>

System.Collections.Concurrent.ConcurrentQueue<T> File d’attente FIFO simultanée et évolutive.

System.Collections.Concurrent.ConcurrentStack<T> Pile LIFO simultanée et évolutive.

Pour plus d’informations, consultez Collections thread-safe.

Primitives de synchronisation
Les nouvelles primitives de synchronisation de l’espace de noms System.Threading affinent la concurrence et
améliorent les performances en évitant les coûteux mécanismes de verrouillage du code multithread hérité.
Certains des nouveaux types, par exemple, System.Threading.Barrier et System.Threading.CountdownEvent, ont
pas d’équivalents dans les versions antérieures de .NET Framework.
Le tableau suivant liste les nouveaux types de synchronisation :

TYPE DESC RIP T IO N

System.Threading.Barrier Permet à plusieurs threads de fonctionner en parallèle sur un


algorithme en fournissant un point auquel chaque tâche peut
signaler son arrivée, puis se bloquer jusqu'à ce qu’une partie
ou la totalité des tâches soient arrivées. Pour plus
d’informations, voir Cloisonnement.

System.Threading.CountdownEvent Simplifie les scénarios de duplication et de jointure en


fournissant un mécanisme facile de réunion. Pour plus
d'informations, consultez la page CountdownEvent.

System.Threading.ManualResetEventSlim Primitive de synchronisation similaire à


System.Threading.ManualResetEvent. ManualResetEventSlim
est léger mais n’est utilisable que pour la communication
intraprocessus.

System.Threading.SemaphoreSlim Primitive de synchronisation qui limite le nombre de threads


pouvant accéder simultanément à une ressource ou à un pool
de ressources. Pour plus d’informations, consultez la page
Semaphore et SemaphoreSlim.

System.Threading.SpinLock Primitive de verrou mutex obligeant le thread qui essaie


d’acquérir le verrou à attendre dans une boucle ou à rester en
attente active pendant un certain temps avant de transmettre
son quantum. Dans les scénarios où l’attente du verrou est
censée être courte, SpinLock offre de meilleures performances
que les autres types de verrouillage. Pour plus d'informations,
consultez la page SpinLock.

System.Threading.SpinWait Type petit et léger qui restera en attente active pendant un


certain temps et mettra le thread dans un état d’attente si le
nombre est dépassé. Pour plus d'informations, consultez la
page SpinWait.

Pour plus d'informations, consultez les pages suivantes :


Comment : utiliser le verrouillage SpinLock pour une synchronisation de bas niveau
Comment : synchroniser des opérations simultanées avec un cloisonnement.

Classes d’initialisation tardive


Avec l’initialisation tardive, la mémoire d’un objet n’est pas allouée tant qu’elle n’est pas nécessaire. L’initialisation
tardive peut améliorer les performances en répartissant uniformément les allocations d’objets sur toute la durée
de vie d’un programme. Vous pouvez l’activer sur n’importe quel type personnalisé en incluant le type Lazy<T>
dans un wrapper.
Le tableau suivant liste les nouveaux types d’initialisation tardive :
TYPE DESC RIP T IO N

System.Lazy<T> Assure une initialisation tardive légère et thread-safe.

System.Threading.ThreadLocal<T> Fournit une valeur initialisée tardivement thread par thread,


chacun appelant de façon tardive la fonction d’initialisation.

System.Threading.LazyInitializer Fournit des méthodes statiques qui évitent d’avoir à allouer


une instance dédiée d’initialisation tardive. Utilise plutôt des
références pour vérifier que les cibles ont été initialisées
lorsqu’elles sont consultées.

Pour plus d’informations, consultez Initialisation tardive.

Agréger des exceptions


Le type System.AggregateException permet de capturer plusieurs exceptions levées simultanément sur des
threads distincts, et de les retourner au thread de jonction comme une seule exception. Les types
System.Threading.Tasks.Task et System.Threading.Tasks.Parallel ainsi que PLINQ utilisent beaucoup
AggregateException pour cela. Pour plus d’informations, consultez Gestion des exceptions et Comment gérer des
exceptions dans une requête PLINQ.

Voir aussi
System.Collections.Concurrent
System.Threading
Programmation parallèle
Outils de diagnostic parallèles
18/07/2020 • 2 minutes to read • Edit Online

Visual Studio fournit une prise en charge complète des applications de débogage et de profilage multithread.

Débogage
L’outil de débogage de Visual Studio ajoute de nouvelles fenêtres pour le débogage des applications parallèles.
Pour plus d'informations, voir les rubriques suivantes :
Utilisation de la fenêtre Piles parallèles
Utilisation de la fenêtre Tâches
Procédure pas à pas : débogage d’une application parallèle.

Profilage
Les vues de rapport du visualiseur concurrentiel permettent de visualiser la façon dont les threads d’un
programme parallèle interagissent entre eux et avec les threads d’autres processus sur le système. Pour plus
d’informations, consultez Visualiseur concurrentiel.

Voir aussi
Programmation parallèle
Partitionneurs personnalisés pour PLINQ et la
bibliothèque parallèle de tâches (TPL)
18/07/2020 • 20 minutes to read • Edit Online

Pour paralléliser une opération sur une source de données, l’une des étapes essentielles consiste à partitionner
la source en plusieurs sections accessibles simultanément par plusieurs threads. PLINQ et la bibliothèque
parallèle de tâches (TPL) fournissent des partitionneurs par défaut qui fonctionnent de façon transparente
lorsque vous écrivez une requête parallèle ou une boucle ForEach. Pour des scénarios plus élaborés, vous
pouvez incorporer votre propre partitionneur.

Types de partitionnement
Il existe de nombreuses façons de partitionner une source de données. Dans les approches les plus efficaces,
plusieurs threads coopèrent pour traiter la séquence source d’origine, au lieu de séparer physiquement la source
en plusieurs sous-séquences. Pour des tableaux et d’autres sources indexées telles que des collections IList où la
longueur est connue à l’avance, le partitionnement par plage de valeurs est le type de partitionnement le plus
simple. Chaque thread reçoit des index de début et de fin uniques pour pouvoir traiter sa plage de la source,
sans remplacer ni être remplacé par un autre thread. La seule surcharge impliquée dans le partitionnement par
plage de valeurs est le travail initial consistant à créer les plages : aucune synchronisation supplémentaire n’est
requise par la suite. Par conséquent, cette méthode offre de bonnes performances tant que la charge de travail
est répartie uniformément. L’inconvénient du partitionnement par plage de valeurs réside dans le fait que si un
thread se termine plus tôt, il ne peut pas aider les autres threads à terminer leur travail.
Pour les listes liées ou d’autres collections dont la longueur n’est pas connue, vous pouvez utiliser le
partitionnement par segments. Dans le partitionnement par segments, chaque thread ou tâche d’une boucle
parallèle ou d’une requête consomme un certain nombre d’éléments de la source dans un segment, les traite,
puis revient pour extraire des éléments supplémentaires. Le partitionneur s’assure que tous les éléments sont
distribués et qu’il n’y a pas de doublons. Un segment peut être de n’importe quelle taille. Par exemple, le
partitionneur présenté dans la section Guide pratique pour implémenter des partitions dynamiques crée des
segments qui ne contiennent qu’un seul élément. Tant que les segments ne sont pas trop volumineux, ce type de
partitionnement effectue, par nature, un équilibrage de charge car l’affectation des éléments aux threads n’est
pas prédéfinie. Cependant, le partitionneur déclenche la surcharge de synchronisation chaque fois que le thread
a besoin d’un autre segment. Le niveau de synchronisation effectué dans ce cas est inversement proportionnel à
la taille des segments.
En règle générale, le partitionnement par plage de valeurs n’est plus rapide que lorsque la durée d’exécution du
délégué est faible à modérée, que la source comporte un grand nombre d’éléments, et que le travail total de
chaque partition est à peu près équivalent. Le partitionnement par segments est donc généralement plus rapide
dans la plupart des cas. Sur les sources comportant un petit nombre d’éléments ou avec des durées d’exécution
plus longues pour le délégué, les performances du partitionnement par plage de valeurs et du partitionnement
par segments sont équivalentes.
Les partitionneurs TPL prennent également en charge un nombre dynamique de partitions. Cela signifie qu’ils
peuvent créer des partitions à la volée, par exemple lorsque la boucle ForEach génère une nouvelle tâche. Cette
fonctionnalité permet la mise à l’échelle du partitionneur par rapport à la boucle elle-même. Par nature, les
partitionneurs dynamiques effectuent également un équilibrage de charge. Lorsque vous créez un partitionneur
personnalisé, vous devez faire en sortie que le partitionnement dynamique soit utilisable par une boucle
ForEach.
Configuration des partitionneurs d’équilibrage de charge pour PLINQ
Certaines surcharges de la méthode Partitioner.Create vous permettent de créer un partitionneur pour un
tableau ou une source IList et de spécifier s’il doit tenter d’équilibrer la charge de travail entre les threads.
Lorsque le partitionneur est configuré pour l’équilibrage de charge, le partitionnement par segments est utilisé,
et les éléments sont transmis, à la demande, par petits segments à chaque partition. Cette approche garantit que
toutes les partitions disposent d’éléments à traiter jusqu'à ce que la boucle ou la requête soit terminée. Une
surcharge supplémentaire peut être utilisée pour fournir le partitionnement d’équilibrage de charge de
n’importe quelle source IEnumerable.
En général, l’équilibrage de charge requiert que les partitions sollicitent fréquemment le partitionneur pour
obtenir des éléments. En revanche, un partitionneur qui effectue un partitionnement statique peut affecter les
éléments à chaque partitionneur en même temps à l’aide d’un partitionnement par plage de valeurs ou par
segments. Cette méthode nécessite moins de surcharge que l’équilibrage de charge, mais elle peut prendre plus
de temps à s’exécuter si un thread se retrouve avec beaucoup plus de travail que les autres. Par défaut, lorsqu’il
reçoit un objet IList ou un tableau, PLINQ utilise toujours le partitionnement par plage de valeurs sans
équilibrage de charge. Pour activer l’équilibrage de charge pour PLINQ, utilisez la méthode Partitioner.Create ,
comme indiqué dans l’exemple suivant.

// Static partitioning requires indexable source. Load balancing


// can use any IEnumerable.
var nums = Enumerable.Range(0, 100000000).ToArray();

// Create a load-balancing partitioner. Or specify false for static partitioning.


Partitioner<int> customPartitioner = Partitioner.Create(nums, true);

// The partitioner is the query's data source.


var q = from x in customPartitioner.AsParallel()
select x * Math.PI;

q.ForAll((x) =>
{
ProcessData(x);
});

' Static number of partitions requires indexable source.


Dim nums = Enumerable.Range(0, 100000000).ToArray()

' Create a load-balancing partitioner. Or specify false For Shared partitioning.


Dim customPartitioner = Partitioner.Create(nums, True)

' The partitioner is the query's data source.


Dim q = From x In customPartitioner.AsParallel()
Select x * Math.PI

q.ForAll(Sub(x) ProcessData(x))

La meilleure méthode pour déterminer si vous devez utiliser l’équilibrage de charge dans un scénario donné
consiste à faire des essais et à mesurer la durée des opérations avec des charges et des configurations
d’ordinateur représentatives. Par exemple, le partitionnement statique peut accélérer considérablement les
opérations sur un ordinateur multicœur doté de quelques cœurs, mais il peut entraîner des ralentissements sur
les ordinateurs qui disposent de nombreux cœurs.
Le tableau ci-dessous répertorie les surcharges disponibles avec la méthode Create. L’utilisation de ces
partitionneurs ne se limite pas à PLINQ ou à Task. Ils peuvent également être utilisés avec n’importe quelle
construction parallèle personnalisée.
SURC H A RGE UT IL ISER L 'ÉQ UIL IB RA GE DE C H A RGE

Create<TSource>(IEnumerable<TSource>) Toujours

Create<TSource>(TSource[], Boolean) Lorsque l’argument booléen est spécifié comme true

Create<TSource>(IList<TSource>, Boolean) Lorsque l’argument booléen est spécifié comme true

Create(Int32, Int32) Jamais

Create(Int32, Int32, Int32) Jamais

Create(Int64, Int64) Jamais

Create(Int64, Int64, Int64) Jamais

Configuration de partitionneurs de plages statiques pour Parallel.ForEach


Dans une boucle For, le corps de la boucle est transmis à la méthode en tant que délégué. Le coût d’un appel à
ce délégué est équivalent à celui d’un appel à une méthode virtuelle. Dans certains scénarios, le corps d’une
boucle parallèle peut être suffisamment petit de sorte que le coût d’un appel au délégué sur chaque itération de
la boucle devient important. Dans ce cas, vous pouvez utiliser une des surcharges Create pour créer un objet
IEnumerable<T> de partitions par plages de valeurs sur les éléments sources. Vous pouvez ensuite transmettre
cette collection de plages de valeurs à une méthode ForEach dont le corps se compose d’une boucle for
standard. L’avantage de cette approche est que le coût d’un appel au délégué n’est généré qu’une seule fois par
plage au lieu d’une seule fois par élément. L'exemple suivant illustre le modèle de base.
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static void Main()
{

// Source must be array or IList.


var source = Enumerable.Range(0, 100000).ToArray();

// Partition the entire source array.


var rangePartitioner = Partitioner.Create(0, source.Length);

double[] results = new double[source.Length];

// Loop over the partitions in parallel.


Parallel.ForEach(rangePartitioner, (range, loopState) =>
{
// Loop over each range element without a delegate invocation.
for (int i = range.Item1; i < range.Item2; i++)
{
results[i] = source[i] * Math.PI;
}
});

Console.WriteLine("Operation complete. Print results? y/n");


char input = Console.ReadKey().KeyChar;
if (input == 'y' || input == 'Y')
{
foreach(double d in results)
{
Console.Write("{0} ", d);
}
}
}
}
Imports System.Threading.Tasks
Imports System.Collections.Concurrent

Module PartitionDemo

Sub Main()
' Source must be array or IList.
Dim source = Enumerable.Range(0, 100000).ToArray()

' Partition the entire source array.


' Let the partitioner size the ranges.
Dim rangePartitioner = Partitioner.Create(0, source.Length)

Dim results(source.Length - 1) As Double

' Loop over the partitions in parallel. The Sub is invoked


' once per partition.
Parallel.ForEach(rangePartitioner, Sub(range, loopState)

' Loop over each range element without a delegate invocation.


For i As Integer = range.Item1 To range.Item2 - 1
results(i) = source(i) * Math.PI
Next
End Sub)
Console.WriteLine("Operation complete. Print results? y/n")
Dim input As Char = Console.ReadKey().KeyChar
If input = "y"c Or input = "Y"c Then
For Each d As Double In results
Console.Write("{0} ", d)
Next
End If

End Sub
End Module

Chaque thread de la boucle reçoit son propre objet Tuple<T1,T2>, qui contient les valeurs d’index de début et de
fin dans la sous-plage spécifiée. La boucle for interne utilise les valeurs fromInclusive et toExclusive pour
parcourir le tableau ou IList directement.
Une des surcharges Create vous permet de spécifier la taille des partitions ainsi que leur nombre. Cette
surcharge peut être utilisée dans des scénarios où le travail par élément est si faible que même un appel à une
méthode virtuelle par élément a un impact perceptible sur les performances.

Partitionneurs personnalisés
Dans certains scénarios, il peut être utile ou même obligatoire d’implémenter votre propre partitionneur. Par
exemple, vous pouvez partionner une classe de collection personnalisée plus efficacement qu’avec les
partitionneurs par défaut, selon votre connaissance de la structure interne de la classe. Ou vous pouvez créer
des partitions de plages de valeurs de tailles différentes en fonction du temps nécessaire pour traiter les
éléments situés en différents emplacements de la collection source.
Pour créer un partitionneur personnalisé de base, dérivez une classe de
System.Collections.Concurrent.Partitioner<TSource> puis remplacez les méthodes virtuelles, comme décrit dans
le tableau suivant.
GetPartitions Cette méthode est appelée une fois par le thread principal et
retourne un objet IList(IEnumerator(TSource)). Chaque
thread de travail dans la boucle ou la requête peut appeler
GetEnumerator sur la liste pour récupérer un objet
IEnumerator<T> sur une partition distincte.

SupportsDynamicPartitions Retournez true si vous implémentez GetDynamicPartitions,


et false dans le cas contraire.

GetDynamicPartitions Si SupportsDynamicPartitions est true , cette méthode


peut éventuellement être appelée à la place de GetPartitions.

Si les résultats doivent pouvoir être triés ou que vous avez besoin d’un accès indexé aux éléments, dérivez
System.Collections.Concurrent.OrderablePartitioner<TSource> et remplacez ses méthodes virtuelles, comme
décrit dans le tableau suivant.

GetPartitions Cette méthode est appelée une fois par le thread principal et
retourne un objet IList(IEnumerator(TSource)) . Chaque
thread de travail dans la boucle ou la requête peut appeler
GetEnumerator sur la liste pour récupérer un objet
IEnumerator<T> sur une partition distincte.

SupportsDynamicPartitions Retournez true si vous implémentez GetDynamicPartitions,


et false dans le cas contraire.

GetDynamicPartitions En règle générale, cette méthode appelle simplement


GetOrderableDynamicPartitions.

GetOrderableDynamicPartitions Si SupportsDynamicPartitions est true , cette méthode


peut éventuellement être appelée à la place de GetPartitions.

Le tableau suivant fournit des détails supplémentaires sur la façon dont les trois types de partitionneurs
d’équilibrage de charge implémentent la classe OrderablePartitioner<TSource>.

IL IST / TA B L EA U SA N S IL IST / TA B L EA U AVEC


M ÉT H O DE/ P RO P RIÉT É ÉQ UIL IB RA GE DE C H A RGE ÉQ UIL IB RA GE DE C H A RGE IEN UM ERA B L E

GetOrderablePartitions Utilise le partitionnement Utilise le partitionnement Utilise le partitionnement


par plages de valeurs par segments, optimisé par segments en créant un
pour les listes, pour la nombre statique de
valeur partitionCount partitions.
spécifiée

OrderablePartitioner<TSour Lève une exception non Utilise le partitionnement Utilise le partitionnement


ce>.GetOrderableDynamicP prise en charge par segments, optimisé par segments en créant un
artitions pour les listes, pour les nombre dynamique de
partitions dynamiques partitions.

KeysOrderedInEachPartition Retourne true . Retourne true . Retourne true .

KeysOrderedAcrossPartition Retourne true . Retourne false . Retourne false .


s
IL IST / TA B L EA U SA N S IL IST / TA B L EA U AVEC
M ÉT H O DE/ P RO P RIÉT É ÉQ UIL IB RA GE DE C H A RGE ÉQ UIL IB RA GE DE C H A RGE IEN UM ERA B L E

KeysNormalized Retourne true . Retourne true . Retourne true .

SupportsDynamicPartitions Retourne false . Retourne true . Retourne true .

Partitions dynamiques
Si vous envisagez d’utiliser le partitionneur dans une méthode ForEach, vous devez être en mesure de retourner
un nombre dynamique de partitions. Cela signifie que le partitionneur peut fournir un énumérateur pour une
nouvelle partition, à la demande et à tout moment pendant l’exécution de la boucle. En fait, chaque fois que la
boucle ajoute une nouvelle tâche parallèle, elle demande une nouvelle partition pour cette tâche. Si vous avez
besoin de pouvoir classer les données, effectuez une dérivation
System.Collections.Concurrent.OrderablePartitioner<TSource> afin d’affecter un index unique à chaque élément
de chaque partition.
Pour plus d’informations et consulter un exemple, voir Guide pratique pour implémenter des partitions
dynamiques.
Contrat pour les partitionneurs
Lorsque vous implémentez un partitionneur personnalisé, suivez ces instructions pour garantir une interaction
correcte avec PLINQ et ForEach dans la bibliothèque parallèle de tâches (TPL) :
Si GetPartitions est appelé avec un argument de zéro ou moins pour partitionsCount , levez
ArgumentOutOfRangeException. Bien que PLINQ et TPL ne fourniront jamais une valeur partitionCount
égale à 0, nous vous recommandons néanmoins de vous prémunir contre ce risque.
GetPartitions et GetOrderablePartitions devraient toujours renvoyer un nombre de partitions équivalant à
partitionsCount . Si le partitionneur manque de données et ne peut pas créer autant de partitions que
demandé, la méthode devrait retourner un énumérateur vide pour chacune des partitions restantes.
Sinon, PLINQ et TPL lèveront une InvalidOperationException.
GetPartitions, GetOrderablePartitions, GetDynamicPartitions, et GetOrderableDynamicPartitions ne
devraient jamais retourner null ( Nothing en Visual Basic). Si tel est le cas, PLINQ/TPL lèveront une
InvalidOperationException.
Les méthodes qui retournent des partitions devraient toujours renvoyer des partitions capables
d’énumérer totalement et de manière unique la source de données. Il ne devrait y avoir aucune
duplication dans la source de données ou les éléments ignorés, sauf si cela est spécifiquement requis par
la conception du partitionneur. Si cette règle n’est pas suivie, l’ordre de sortie peut être brouillé.
Les accesseurs booléens suivants doivent toujours retourner correctement les valeurs ci-dessous afin que
l’ordre de sortie ne soit pas brouillé :
KeysOrderedInEachPartition : chaque partition retourne des éléments en augmentant les index
clés.
KeysOrderedAcrossPartitions : pour toutes les partitions retournées, les index clés dans la partition
i sont plus élevés que les index clés de la partition i-1.
KeysNormalized : tous les index clés augmentent de monotone, sans écarts, à partir de zéro.
Tous les index doivent être uniques. Il ne doit pas y avoir de doublons. Si cette règle n’est pas suivie,
l’ordre de sortie peut être brouillé.
Tous les index doivent être non négatifs. Si cette règle n’est pas suivie, PLINQ/TPL peuvent lever des
exceptions.

Voir aussi
Programmation parallèle
Procédure : implémenter des partitions dynamiques
Procédure : implémenter un partitionneur pour un partitionnement statique
Procédure : implémenter des partitions dynamiques
18/07/2020 • 4 minutes to read • Edit Online

L’exemple suivant montre comment implémenter un


System.Collections.Concurrent.OrderablePartitioner<TSource> personnalisé qui implémente le partitionnement
dynamique et peut être utilisée à partir de certaines surcharges ForEach et à partir de PLINQ.

Exemple
À chaque fois qu’une partition appelle MoveNext sur l’énumérateur, celui-ci fournit à la partition un élément de
liste. Dans le cas de PLINQ et ForEach, la partition est une instance Task. Étant donné que les requêtes arrivent
simultanément sur plusieurs threads, l’accès à l’index actuel est synchronisé.

//
// An orderable dynamic partitioner for lists
//
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using System.Numerics;

class OrderableListPartitioner<TSource> : OrderablePartitioner<TSource>


{
private readonly IList<TSource> m_input;

// Must override to return true.


public override bool SupportsDynamicPartitions => true;

public OrderableListPartitioner(IList<TSource> input) : base(true, false, true) =>


m_input = input;

public override IList<IEnumerator<KeyValuePair<long, TSource>>> GetOrderablePartitions(int


partitionCount)
{
var dynamicPartitions = GetOrderableDynamicPartitions();
var partitions =
new IEnumerator<KeyValuePair<long, TSource>>[partitionCount];

for (int i = 0; i < partitionCount; i++)


{
partitions[i] = dynamicPartitions.GetEnumerator();
}
return partitions;
}

public override IEnumerable<KeyValuePair<long, TSource>> GetOrderableDynamicPartitions() =>


new ListDynamicPartitions(m_input);

private class ListDynamicPartitions : IEnumerable<KeyValuePair<long, TSource>>


{
private IList<TSource> m_input;
private int m_pos = 0;
internal ListDynamicPartitions(IList<TSource> input) =>
m_input = input;

public IEnumerator<KeyValuePair<long, TSource>> GetEnumerator()


{
while (true)
{
// Each task gets the next item in the list. The index is
// incremented in a thread-safe manner to avoid races.
int elemIndex = Interlocked.Increment(ref m_pos) - 1;

if (elemIndex >= m_input.Count)


{
yield break;
}

yield return new KeyValuePair<long, TSource>(


elemIndex, m_input[elemIndex]);
}
}

IEnumerator IEnumerable.GetEnumerator() =>


((IEnumerable<KeyValuePair<long, TSource>>)this).GetEnumerator();
}
}

class ConsumerClass
{
static void Main()
{
var nums = Enumerable.Range(0, 10000).ToArray();
OrderableListPartitioner<int> partitioner = new OrderableListPartitioner<int>(nums);

// Use with Parallel.ForEach


Parallel.ForEach(partitioner, (i) => Console.WriteLine(i));

// Use with PLINQ


var query = from num in partitioner.AsParallel()
where num % 2 == 0
select num;

foreach (var v in query)


Console.WriteLine(v);
}
}

Imports System.Threading
Imports System.Threading.Tasks
Imports System.Collections.Concurrent

Module Module1
Public Class OrderableListPartitioner(Of TSource)
Inherits OrderablePartitioner(Of TSource)

Private ReadOnly m_input As IList(Of TSource)

Public Sub New(ByVal input As IList(Of TSource))


MyBase.New(True, False, True)
m_input = input
End Sub

' Must override to return true.


Public Overrides ReadOnly Property SupportsDynamicPartitions As Boolean
Get
Return True
End Get
End Property

Public Overrides Function GetOrderablePartitions(ByVal partitionCount As Integer) As IList(Of


IEnumerator(Of KeyValuePair(Of Long, TSource)))
Dim dynamicPartitions = GetOrderableDynamicPartitions()
Dim partitions(partitionCount - 1) As IEnumerator(Of KeyValuePair(Of Long, TSource))

For i = 0 To partitionCount - 1
partitions(i) = dynamicPartitions.GetEnumerator()
Next

Return partitions
End Function

Public Overrides Function GetOrderableDynamicPartitions() As IEnumerable(Of KeyValuePair(Of Long,


TSource))
Return New ListDynamicPartitions(m_input)
End Function

Private Class ListDynamicPartitions


Implements IEnumerable(Of KeyValuePair(Of Long, TSource))

Private m_input As IList(Of TSource)

Friend Sub New(ByVal input As IList(Of TSource))


m_input = input
End Sub

Public Function GetEnumerator() As IEnumerator(Of KeyValuePair(Of Long, TSource)) Implements


IEnumerable(Of KeyValuePair(Of Long, TSource)).GetEnumerator
Return New ListDynamicPartitionsEnumerator(m_input)
End Function

Public Function GetEnumerator1() As IEnumerator Implements IEnumerable.GetEnumerator


Return CType(Me, IEnumerable).GetEnumerator()
End Function
End Class

Private Class ListDynamicPartitionsEnumerator


Implements IEnumerator(Of KeyValuePair(Of Long, TSource))

Private m_input As IList(Of TSource)


Shared m_pos As Integer = 0
Private m_current As KeyValuePair(Of Long, TSource)

Public Sub New(ByVal input As IList(Of TSource))


m_input = input
m_pos = 0
Me.disposedValue = False
End Sub

Public ReadOnly Property Current As KeyValuePair(Of Long, TSource) Implements IEnumerator(Of


KeyValuePair(Of Long, TSource)).Current
Get
Return m_current
End Get
End Property

Public ReadOnly Property Current1 As Object Implements IEnumerator.Current


Get
Return Me.Current
End Get
End Property

Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext


Dim elemIndex = Interlocked.Increment(m_pos) - 1
If elemIndex >= m_input.Count Then
Return False
End If
End If

m_current = New KeyValuePair(Of Long, TSource)(elemIndex, m_input(elemIndex))


Return True
End Function

Public Sub Reset() Implements IEnumerator.Reset


m_pos = 0
End Sub

Private disposedValue As Boolean ' To detect redundant calls

Protected Overridable Sub Dispose(ByVal disposing As Boolean)


If Not Me.disposedValue Then
m_input = Nothing
m_current = Nothing
End If
Me.disposedValue = True
End Sub

Public Sub Dispose() Implements IDisposable.Dispose


Dispose(True)
GC.SuppressFinalize(Me)
End Sub

End Class

End Class

Class ConsumerClass

Shared Sub Main()

Console.BufferHeight = 20000
Dim nums = Enumerable.Range(0, 2000).ToArray()

Dim partitioner = New OrderableListPartitioner(Of Integer)(nums)

' Use with Parallel.ForEach


Parallel.ForEach(partitioner, Sub(i) Console.Write("{0}:{1} ", i,
Thread.CurrentThread.ManagedThreadId))

Console.WriteLine("PLINQ -----------------------------------")

' create a new partitioner, since Enumerators are not reusable.


Dim partitioner2 = New OrderableListPartitioner(Of Integer)(nums)
' Use with PLINQ
Dim query = From num In partitioner2.AsParallel()
Where num Mod 8 = 0
Select num

For Each v In query


Console.Write("{0} ", v)
Next

Console.WriteLine("press any key")


Console.ReadKey()
End Sub
End Class

End Module

Il s’agit d’un exemple de partitionnement par segments, chaque segment se composant d’un seul élément. En
fournissant plus d’éléments à la fois, vous pourriez réduire le conflit sur le verrou et atteindre, en théorie, des
performances plus rapides. Toutefois, à un moment donné, des segments plus volumineux pourraient nécessiter
une logique supplémentaire d’équilibrage de charge afin de conserver tous les threads occupés jusqu'à ce que
tout le travail soit effectué.

Voir aussi
Partitionneurs personnalisés pour PLINQ et TPL
Procédure : implémenter un partitionneur pour un partitionnement statique
Procédure : implémenter un partitionneur pour un
partitionnement statique
18/07/2020 • 4 minutes to read • Edit Online

L’exemple suivant montre une façon d’implémenter un partitionneur personnalisé simple pour PLINQ qui exécute
le partitionnement statique. Étant donné que le partitionneur ne prend pas en charge les partitions dynamiques, il
n’est pas utilisable à partir de Parallel.ForEach. Ce partitionneur particulier peut fournir une accélération par
rapport au partitionneur de plage par défaut pour les sources de données pour lequel chaque élément requiert
une quantité croissante de temps de traitement.

Exemple
// A static range partitioner for sources that require
// a linear increase in processing time for each succeeding element.
// The range sizes are calculated based on the rate of increase
// with the first partition getting the most elements and the
// last partition getting the least.
class MyPartitioner : Partitioner<int>
{
int[] source;
double rateOfIncrease = 0;

public MyPartitioner(int[] source, double rate)


{
this.source = source;
rateOfIncrease = rate;
}

public override IEnumerable<int> GetDynamicPartitions()


{
throw new NotImplementedException();
}

// Not consumable from Parallel.ForEach.


public override bool SupportsDynamicPartitions
{
get
{
return false;
}
}

public override IList<IEnumerator<int>> GetPartitions(int partitionCount)


{
List<IEnumerator<int>> _list = new List<IEnumerator<int>>();
int end = 0;
int start = 0;
int[] nums = CalculatePartitions(partitionCount, source.Length);

for (int i = 0; i < nums.Length; i++)


{
start = nums[i];
if (i < nums.Length - 1)
end = nums[i + 1];
else
end = source.Length;

_list.Add(GetItemsForPartition(start, end));
// For demonstratation.
Console.WriteLine("start = {0} b (end) = {1}", start, end);
}
return (IList<IEnumerator<int>>)_list;
}
/*
*
*
* B
// Model increasing workloads as a right triangle / |
divided into equal areas along vertical lines. / | |
Each partition is taller and skinnier / | |
than the last. / | | |
/ | | |
/ | | |
/ | | | |
/ | | | |
A /______|____|___|__| C
*/
private int[] CalculatePartitions(int partitionCount, int sourceLength)
{
// Corresponds to the opposite side of angle A, which corresponds
// to an index into the source array.
int[] partitionLimits = new int[partitionCount];
partitionLimits[0] = 0;

// Represent total work as rectangle of source length times "most expensive element"
// Note: RateOfIncrease can be factored out of equation.
double totalWork = sourceLength * (sourceLength * rateOfIncrease);
// Divide by two to get the triangle whose slope goes from zero on the left to "most"
// on the right. Then divide by number of partitions to get area of each partition.
totalWork /= 2;
double partitionArea = totalWork / partitionCount;

// Draw the next partitionLimit on the vertical coordinate that gives


// an area of partitionArea * currentPartition.
for (int i = 1; i < partitionLimits.Length; i++)
{
double area = partitionArea * i;

// Solve for base given the area and the slope of the hypotenuse.
partitionLimits[i] = (int)Math.Floor(Math.Sqrt((2 * area) / rateOfIncrease));
}
return partitionLimits;
}

IEnumerator<int> GetItemsForPartition(int start, int end)


{
// For demonstration purpsoes. Each thread receives its own enumerator.
Console.WriteLine("called on thread {0}", Thread.CurrentThread.ManagedThreadId);
for (int i = start; i < end; i++)
yield return source[i];
}
}

class Consumer
{
public static void Main2()
{
var source = Enumerable.Range(0, 10000).ToArray();

Stopwatch sw = Stopwatch.StartNew();
MyPartitioner partitioner = new MyPartitioner(source, .5);

var query = from n in partitioner.AsParallel()


select ProcessData(n);

foreach (var v in query) { }


Console.WriteLine("Processing time with custom partitioner {0}", sw.ElapsedMilliseconds);
Console.WriteLine("Processing time with custom partitioner {0}", sw.ElapsedMilliseconds);

var source2 = Enumerable.Range(0, 10000).ToArray();

sw = Stopwatch.StartNew();

var query2 = from n in source2.AsParallel()


select ProcessData(n);

foreach (var v in query2) { }


Console.WriteLine("Processing time with default partitioner {0}", sw.ElapsedMilliseconds);
}

// Consistent processing time for measurement purposes.


static int ProcessData(int i)
{
Thread.SpinWait(i * 1000);
return i;
}
}

Les partitions dans cet exemple sont basées sur l’hypothèse d’une augmentation linéaire du temps de traitement
pour chaque élément. Dans le monde réel, il peut être difficile de prédire le temps de traitement de cette façon. Si
vous utilisez un partitionneur statique avec une source de données spécifique, vous pouvez optimiser la formule
de partitionnement pour la source, ajouter une logique d’équilibrage de charge ou utiliser une méthode de
partitionnement par segments, comme illustré dans Guide pratique pour implémenter des partitions dynamiques.

Voir aussi
Partitionneurs personnalisés pour PLINQ et TPL
Expressions lambda en PLINQ et dans la
bibliothèque parallèle de tâches
18/07/2020 • 5 minutes to read • Edit Online

La bibliothèque parallèle de tâches (TPL) contient de nombreuses méthodes qui utilisent l’une des familles
System.Func<TResult> ou System.Action de délégués comme paramètres d’entrée. Vous utilisez ces
délégués pour transmettre votre logique de programme personnalisée à la boucle, la tâche ou la requête
parallèle. Les exemples de code de la bibliothèque parallèle de tâches, ainsi que PLINQ, utilisent des
expressions lambda pour créer des instances de ces délégués comme blocs de code inline. Cette rubrique
est une brève présentation de Func et Action et vous montre comment utiliser des expressions lambda dans
la bibliothèque parallèle de tâches et PLINQ.

NOTE
Pour plus d’informations sur les délégués en général, consultez délégués et délégués. Pour plus d’informations sur les
expressions lambda en C# et Visual Basic, consultez Expressions lambda et Expressions lambda.

Func (délégué)
Un délégué Func encapsule une méthode qui renvoie une valeur. Dans une Func signature, le dernier
paramètre de type, ou le plus à droite, spécifie toujours le type de retour. L’une des causes courantes
d’erreurs de compilation consiste à tenter de transmettre deux paramètres d’entrée à un
System.Func<T,TResult>. En fait, ce type ne prend qu’un paramètre d’entrée. .Net définit 17 versions de
Func : System.Func<TResult> , System.Func<T,TResult> , System.Func<T1,T2,TResult> , et ainsi de suite
System.Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult> .

Action (délégué)
Un System.Action délégué encapsule une méthode (Sub dans Visual Basic) qui ne retourne pas de valeur.
Dans une Action signature de type, les paramètres de type représentent uniquement des paramètres
d’entrée. À l’instar de Func , .net définit 17 versions de Action , à partir d’une version qui n’a pas de
paramètres de type jusqu’à une version avec 16 paramètres de type.

Exemple
L’exemple suivant pour la méthode Parallel.ForEach<TSource,TLocal>(IEnumerable<TSource>,
Func<TLocal>, Func<TSource,ParallelLoopState,TLocal,TLocal>, Action<TLocal>) montre comment
exprimer les délégués Func et Action à l’aide d’expressions lambda.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

class ForEachWithThreadLocal
{
// Demonstrated features:
// Parallel.ForEach()
// Thread-local state
// Expected results:
// This example sums up the elements of an int[] in parallel.
// Each thread maintains a local sum. When a thread is initialized, that local sum is set to
0.
// On every iteration the current element is added to the local sum.
// When a thread is done, it safely adds its local sum to the global sum.
// After the loop is complete, the global sum is printed out.
// Documentation:
// http://msdn.microsoft.com/library/dd990270(VS.100).aspx
static void Main()
{
// The sum of these elements is 40.
int[] input = { 4, 1, 6, 2, 9, 5, 10, 3 };
int sum = 0;

try
{
Parallel.ForEach(
input, // source collection
() => 0, // thread local initializer
(n, loopState, localSum) => // body
{
localSum += n;
Console.WriteLine("Thread={0}, n={1}, localSum={2}",
Thread.CurrentThread.ManagedThreadId, n, localSum);
return localSum;
},
(localSum) => Interlocked.Add(ref sum, localSum) // thread local aggregator
);

Console.WriteLine("\nSum={0}", sum);
}
// No exception is expected in this example, but if one is still thrown from a task,
// it will be wrapped in AggregateException and propagated to the main thread.
catch (AggregateException e)
{
Console.WriteLine("Parallel.ForEach has thrown an exception. THIS WAS NOT EXPECTED.\n{0}",
e);
}
}
}
Imports System.Threading
Imports System.Threading.Tasks

Module ForEachDemo

' Demonstrated features:


' Parallel.ForEach()
' Thread-local state
' Expected results:
' This example sums up the elements of an int[] in parallel.
' Each thread maintains a local sum. When a thread is initialized, that local sum is set to 0.
' On every iteration the current element is added to the local sum.
' When a thread is done, it safely adds its local sum to the global sum.
' After the loop is complete, the global sum is printed out.
' Documentation:
' http://msdn.microsoft.com/library/dd990270(VS.100).aspx
Private Sub ForEachDemo()
' The sum of these elements is 40.
Dim input As Integer() = {4, 1, 6, 2, 9, 5, _
10, 3}
Dim sum As Integer = 0

Try
' source collection
Parallel.ForEach(input,
Function()
' thread local initializer
Return 0
End Function,
Function(n, loopState, localSum)
' body
localSum += n
Console.WriteLine("Thread={0}, n={1}, localSum={2}",
Thread.CurrentThread.ManagedThreadId, n, localSum)
Return localSum
End Function,
Sub(localSum)
' thread local aggregator
Interlocked.Add(sum, localSum)
End Sub)

Console.WriteLine(vbLf & "Sum={0}", sum)


Catch e As AggregateException
' No exception is expected in this example, but if one is still thrown from a task,
' it will be wrapped in AggregateException and propagated to the main thread.
Console.WriteLine("Parallel.ForEach has thrown an exception. THIS WAS NOT EXPECTED." & vbLf
& "{0}", e)
End Try
End Sub

End Module

Voir aussi
Programmation parallèle
Pour obtenir des informations supplémentaires
(programmation parallèle)
18/07/2020 • 2 minutes to read • Edit Online

Les ressources suivantes contiennent des informations supplémentaires sur la programmation parallèle dans .NET :
Le document Patterns for Parallel Programming: Understanding and Applying Parallel Patterns with the .NET
Framework 4 décrit les modèles parallèles courants et les bonnes pratiques pour développer des
composants parallèles avec ces modèles.
Le manuel Design Patterns for Decomposition and Coordination on Multicore Architectures décrit les
modèles pour la programmation parallèle qui utilisent la prise en charge de la programmation parallèle
introduite dans .NET Framework 4.
Le blog sur la programmation parallèle avec .NET contient de nombreux articles détaillés sur la
programmation parallèle dans .NET.
Les exemples de programmation parallèle avec la page .NET standard de & .net Core contiennent de
nombreux exemples qui illustrent des techniques de programmation parallèles intermédiaires et avancées.

Voir aussi
Parallel Computing Developer Center
Programmation parallèle en Visual C++
Threading managé
18/07/2020 • 3 minutes to read • Edit Online

Que votre développement s’applique à des ordinateurs avec un ou plusieurs processeurs, votre application doit
fournir l’interaction la plus réactive avec l’utilisateur, même si l’application effectue actuellement d’autres
opérations. Utiliser plusieurs threads d’exécution est l’une des manières les plus efficaces pour maintenir la
réactivité de votre application vis-à-vis de l’utilisateur, tout en exploitant le processeur entre voire pendant des
événements utilisateur. Bien que cette section présente les concepts de base du threading, elle se concentre sur les
concepts de threading managé et son utilisation.

NOTE
À compter de .NET Framework 4, la programmation multithread est considérablement simplifiée avec les classes
System.Threading.Tasks.Parallel et System.Threading.Tasks.Task, Parallel LINQ (PLINQ), de nouvelles classes de collections
simultanées dans l’espace de noms System.Collections.Concurrent et un nouveau modèle de programmation basé sur le
concept de tâches au lieu de threads. Pour plus d’informations, consultez programmation parallèle.

Dans cette section


Concepts de base du threading managé
Fournit une vue d’ensemble des threads managés et explique quand utiliser plusieurs threads.
Utilisation des threads et du threading
Explique comment créer, démarrer, suspendre, reprendre et abandonner des threads.
Meilleures pratiques pour le threading managé
Décrit les niveaux de synchronisation, comment éviter les interblocages et les conditions de concurrence, et
d’autres problèmes liés aux threads.
Objets et fonctionnalités de Threading
Décrit les classes managées que vous pouvez utiliser pour synchroniser les activités de threads et les données
d’objets ouvertes sur différents threads, et fournit une vue d’ensemble des threads du pool.

Informations de référence
System.Threading
Contient des classes pour l’utilisation et la synchronisation de threads managés.
System.Collections.Concurrent
Contient des classes de collection qui peuvent être utilisées en toute sécurité avec plusieurs threads.
System.Threading.Tasks
Contient des classes pour la création et la planification de tâches de traitement simultanées.

Sections connexes
Domaines d'application
Fournit une vue d'ensemble des domaines d'application et de leur utilisation dans le Common Language
Infrastructure.
E/s de fichier asynchrones
Décrit les opérations élémentaires des E/S asynchrones et leurs avantages en termes de performances.
Modèle asynchrone basé sur les tâches (TAP, Task-based Asynchronous Pattern)
Offre une vue d’ensemble du modèle recommandé pour la programmation asynchrone dans .NET.
Appel de méthodes synchrones de manière asynchrone
Explique comment appeler des méthodes sur les threads d’un pool à l’aide des fonctionnalités intégrées des
délégués.
Programmation parallèle
Décrit les bibliothèques de programmation parallèle qui simplifient l’utilisation de plusieurs threads dans les
applications.
Parallel LINQ (PLINQ)
Décrit un système permettant d’exécuter des requêtes en parallèle afin de tirer parti de plusieurs processeurs.
Types liés à la mémoire et l’étendue
18/07/2020 • 3 minutes to read • Edit Online

À compter de .NET Core 2,1, .NET comprend un certain nombre de types interdépendants qui représentent une
région contiguë fortement typée de mémoire arbitraire. notamment :
System.Span<T>, un type utilisé pour accéder à une zone contiguë de mémoire. Une instance Span<T> peut
être sauvegardée par un tableau de type T , un objet String, une mémoire tampon allouée avec stackalloc,
ou un pointeur vers une mémoire non managée. Comme elle doit être allouée sur la pile, elle comporte
plusieurs restrictions. Par exemple, un champ dans une classe ne peut pas être de type Span<T>, et l’étendue
ne peut pas être utilisée dans des opérations asynchrones.
System.ReadOnlySpan<T>, une version immuable de la structure Span<T>.
System.Memory<T>, une zone contiguë de mémoire qui est allouée sur le segment managé plutôt que sur
la pile. Une instance Memory<T> peut être sauvegardée par un tableau de type T ou un String. Comme elle
peut être stockée sur le tas managé, Memory<T> ne présente aucune limite pour Span<T>.
System.ReadOnlyMemory<T>, une version immuable de la structure Memory<T>.
System.Buffers.MemoryPool<T>, qui alloue des blocs de mémoire fortement typés d’un pool de mémoire à
un propriétaire. Les instances IMemoryOwner<T> peuvent être louées à partir du pool en appelant
MemoryPool<T>.Rent puis replacées dans le pool en appelant MemoryPool<T>.Dispose().
System.Buffers.IMemoryOwner<T>, qui représente le propriétaire d’un bloc de mémoire et gère la durée de
vie.
MemoryManager<T>, une classe de base abstraite qui peut être utilisée pour remplacer l’implémentation de
Memory<T> afin de sauvegarder Memory<T> par des types supplémentaires, par exemple des descripteurs
sécurisés. MemoryManager<T> s’applique à des scénarios avancés.
ArraySegment<T>, un wrapper pour un certain nombre d’éléments de tableau commençant à un index
particulier.
System.MemoryExtensions, une collection de méthodes d’extension pour la conversion de chaînes, de
tableaux et de segments de tableau en blocs Memory<T>.

NOTE
Pour les frameworks antérieurs, Span<T> et Memory<T> sont disponibles dans le package System.Memory NuGet.

Pour plus d'informations, consultez l'espace de noms System.Buffers.

Utilisation de la mémoire et de l’étendue


Étant donné que les types liés à la mémoire et à l’étendue servent généralement à stocker les données dans un
pipeline de traitement, il est important que les développeurs suivent un ensemble de meilleures pratiques lorsqu’ils
utilisent Span<T>, Memory<T> et des types connexes. Ces meilleures pratiques sont documentées dans <T> <T>
les instructions relatives à l’utilisation de la mémoire et de l’étendue.

Voir aussi
System.Memory<T>
System.ReadOnlyMemory<T>
System.Span<T>
System.ReadOnlySpan<T>
System.Buffers
Instructions d’utilisation de la mémoire<T> et de
l’étendue<T>
18/07/2020 • 27 minutes to read • Edit Online

.NET Core inclut un nombre de types qui représentent une région contiguë arbitraire de mémoire. .NET Core 2.0 a
introduit Span<T> et ReadOnlySpan<T>, qui sont des mémoires tampons légères pouvant être sauvegardées par
de la mémoire managée ou non managée. Ces types pouvant uniquement être stockés sur la pile, ils sont ne sont
pas adaptés à plusieurs scénarios, notamment les appels de méthode asynchrone. .NET Core 2.1 ajoute un certain
nombre de types supplémentaires, notamment Memory<T>, ReadOnlyMemory<T>, IMemoryOwner<T> et
MemoryPool<T>. Comme Span<T>, Memory<T> et ses types associés peuvent être sauvegardés par la mémoire
managée et non managée. Contrairement à Span<T>, Memory<T> peut être stockée sur le tas managé.
Span<T> et Memory<T> sont des mémoires tampons de données structurées qui peuvent être utilisées dans les
pipelines. Autrement dit, elles sont conçues afin que certaines données ou leur totalité puissent être transmises
efficacement à des composants du pipeline qui puissent les traiter et, éventuellement, modifier la mémoire
tampon. Étant donné que Memory<T> et ses types associés sont accessibles par plusieurs composants ou par
plusieurs threads, il est important que les développeurs suivent des instructions d’utilisation standard pour
produire un code robuste.

Gestion des propriétaires, des consommateurs et de la durée de vie


Les mémoires tampons pouvant passer d’une API à l’autre et étant parfois accessibles depuis plusieurs threads, il
est important de tenir compte de la gestion de la durée de vie. Il y a trois concepts fondamentaux :
Propriété . Le propriétaire d’une instance de la mémoire tampon est responsable de la gestion de la durée
de vie, notamment de la destruction de la mémoire tampon lorsqu’elle n’est plus utilisée. Toutes les
mémoires tampons ont un propriétaire unique. En règle générale, le propriétaire est le composant qui a créé
la mémoire tampon ou l’a reçue à partir d’une fabrique. La propriété peut également être transférée ;
Component-A peut abandonner le contrôle de la mémoire tampon à Component-B , à la suite de quoi
Component-A ne peut plus utiliser la mémoire tampon, et **Component-B ** devient responsable de sa
destruction lorsqu’elle n’est plus utilisée.
Consommation . Le consommateur d’une instance de la mémoire tampon est autorisé à utiliser l’instance
de la mémoire tampon en la lisant et, éventuellement, en écrivant dedans. Les mémoires tampons peuvent
avoir un consommateur à la fois, sauf si un mécanisme de synchronisation externe est disponible. Le
consommateur actif d’une mémoire tampon n’est pas nécessairement le propriétaire de la mémoire
tampon.
Bail . Le bail est la durée pendant laquelle un composant particulier est autorisé à être le consommateur de
la mémoire tampon.
L'exemple de pseudo-code suivant illustre ces trois concepts. Il inclut une méthode Main qui instancie une
mémoire tampon Memory<T> de type Char et appelle la méthode WriteInt32ToBuffer pour écrire la
représentation sous forme de chaîne d’un entier dans la mémoire tampon, puis la méthode
DisplayBufferToConsole pour afficher la valeur de la mémoire tampon.
using System;

class Program
{
// Write 'value' as a human-readable string to the output buffer.
void WriteInt32ToBuffer(int value, Buffer buffer);

// Display the contents of the buffer to the console.


void DisplayBufferToConsole(Buffer buffer);

// Application code
static void Main()
{
var buffer = CreateBuffer();
try
{
int value = Int32.Parse(Console.ReadLine());
WriteInt32ToBuffer(value, buffer);
DisplayBufferToConsole(buffer);
}
finally
{
buffer.Destroy();
}
}
}

La méthode Main crée la mémoire tampon (dans ce cas une instance Span<T>) et est donc son propriétaire. Pour
cette raison, Main est responsable de la destruction de la mémoire tampon lorsqu’elle n’est plus utilisée. Elle est
effectuée en appelant la méthode Span<T>.Clear() de la mémoire tampon. (Ici, la méthode Clear() efface
effectivement la mémoire de la mémoire tampon ; la structure Span<T> n’a pas vraiment de méthode de
destruction de la mémoire tampon.)
La mémoire tampon a deux consommateurs, à savoir WriteInt32ToBuffer et DisplayBufferToConsole . Il n'y a qu’un
seul consommateur à la fois (d’abord WriteInt32ToBuffer , puis DisplayBufferToConsole ), et aucune des
consommateurs ne possède la mémoire tampon. Notez également que « consommateur » dans ce contexte
n’implique pas une vue en lecture seule de la mémoire tampon ; comme WriteInt32ToBuffer , les consommateurs
peuvent modifier le contenu de la mémoire tampon s’ils disposent d’une vue en lecture/écriture de cette dernière.
La méthode WriteInt32ToBuffer a un bail pour la mémoire tampon (peut consommer) entre le début de l’appel de
méthode et le moment du retour de la méthode. De même, DisplayBufferToConsole a un bail pour la mémoire
tampon pendant son exécution et en est libéré lorsque la méthode se déroule. (Il n’existe aucune API pour la
gestion de bail ; un « bail » est un concept.)

Mémoire <T> et modèle propriétaire/consommateur


Comme mentionné dans la section Propriétaires, consommateurs et gestion de la durée de vie, une mémoire
tampon a toujours un propriétaire. .NET Core prend en charge deux modèles de propriété :
Un modèle qui prend en charge la propriété unique. Une mémoire tampon a un propriétaire unique pour
toute sa durée de vie.
Un modèle qui prend en charge le transfert de propriété. La propriété d’une mémoire tampon peut être
transférée de son propriétaire d’origine (son créateur) à un autre composant, qui devient alors responsable
de la gestion de la durée de vie de la mémoire tampon. Ce propriétaire peut à son tour transférer la
propriété à un autre composant, et ainsi de suite.
Vous utilisez l’interface System.Buffers.IMemoryOwner<T> pour gérer explicitement la propriété d’une mémoire
tampon. IMemoryOwner<T> prend en charge les deux modèles de propriété. Le composant qui a une référence
IMemoryOwner<T> possède la mémoire tampon. L’exemple suivant utilise une IMemoryOwner<T> instance pour
refléter la propriété d’une Memory<T> mémoire tampon.

using System;
using System.Buffers;

class Example
{
static void Main()
{
IMemoryOwner<char> owner = MemoryPool<char>.Shared.Rent();

Console.Write("Enter a number: ");


try {
var value = Int32.Parse(Console.ReadLine());

var memory = owner.Memory;

WriteInt32ToBuffer(value, memory);

DisplayBufferToConsole(owner.Memory.Slice(0, value.ToString().Length));
}
catch (FormatException) {
Console.WriteLine("You did not enter a valid number.");
}
catch (OverflowException) {
Console.WriteLine($"You entered a number less than {Int32.MinValue:N0} or greater than
{Int32.MaxValue:N0}.");
}
finally {
owner?.Dispose();
}
}

static void WriteInt32ToBuffer(int value, Memory<char> buffer)


{
var strValue = value.ToString();

var span = buffer.Span;


for (int ctr = 0; ctr < strValue.Length; ctr++)
span[ctr] = strValue[ctr];
}

static void DisplayBufferToConsole(Memory<char> buffer) =>


Console.WriteLine($"Contents of the buffer: '{buffer}'");
}

Nous pouvons également écrire cet exemple avec using :


using System;
using System.Buffers;

class Example
{
static void Main()
{
using (IMemoryOwner<char> owner = MemoryPool<char>.Shared.Rent())
{
Console.Write("Enter a number: ");
try {
var value = Int32.Parse(Console.ReadLine());

var memory = owner.Memory;


WriteInt32ToBuffer(value, memory);
DisplayBufferToConsole(memory.Slice(0, value.ToString().Length));
}
catch (FormatException) {
Console.WriteLine("You did not enter a valid number.");
}
catch (OverflowException) {
Console.WriteLine($"You entered a number less than {Int32.MinValue:N0} or greater than
{Int32.MaxValue:N0}.");
}
}
}

static void WriteInt32ToBuffer(int value, Memory<char> buffer)


{
var strValue = value.ToString();

var span = buffer.Slice(0, strValue.Length).Span;


strValue.AsSpan().CopyTo(span);
}

static void DisplayBufferToConsole(Memory<char> buffer) =>


Console.WriteLine($"Contents of the buffer: '{buffer}'");
}

Dans ce code :
La méthode Main conserve la référence à l’instance IMemoryOwner<T>, donc la méthode Main est
propriétaire de la mémoire tampon.
Les méthodes WriteInt32ToBuffer et DisplayBufferToConsole acceptent Memory<T> comme API publique.
Par conséquent, il y a des consommateurs de la mémoire tampon. Et ils ne la consomment qu’un par un.
Bien que la méthode WriteInt32ToBuffer soit destinée à écrire une valeur dans la mémoire tampon, ce n’est pas le
cas pour la méthode DisplayBufferToConsole . Pour refléter cette modification, un argument de type
ReadOnlyMemory<T> peut avoir été accepté. Pour plus d’informations sur ReadOnlyMemory<T> , consultez #2
de règles : utilisez ReadOnlySpan <T> ou ReadOnlyMemory <T> si la mémoire tampon doit être en lecture seule.
Instances de mémoire « propriétaires » <T>
Vous pouvez créer une instance Memory<T> sans utiliser IMemoryOwner<T>. Dans ce cas, la propriété de la
mémoire tampon est implicite plutôt qu’explicite et seul le modèle de propriétaire unique est pris en charge. Pour
ce faire, vous pouvez :
appeler directement l’un des constructeurs Memory<T> en passant à un T[] , comme dans l’exemple
suivant ;
appeler la méthode d'extension String.AsMemory pour produire une instance ReadOnlyMemory<char> .
using System;

class Example
{
static void Main()
{
Memory<char> memory = new char[64];

Console.Write("Enter a number: ");


var value = Int32.Parse(Console.ReadLine());

WriteInt32ToBuffer(value, memory);
DisplayBufferToConsole(memory);
}

static void WriteInt32ToBuffer(int value, Memory<char> buffer)


{
var strValue = value.ToString();
strValue.AsSpan().CopyTo(buffer.Slice(0, strValue.Length).Span);
}

static void DisplayBufferToConsole(Memory<char> buffer) =>


Console.WriteLine($"Contents of the buffer: '{buffer}'");
}

La méthode qui crée initialement l’instance Memory<T> est le propriétaire implicite de la mémoire tampon. La
propriété ne peut pas être transférée à n’importe quel autre composant, car il n’y a pas d’instance
IMemoryOwner<T> pour faciliter le transfert. (Comme alternative, vous pouvez également imaginer que le
récupérateur de mémoire du runtime possède la mémoire tampon et que toutes les méthodes ne fassent que
consommer la mémoire tampon.)

Indications relatives à l'utilisation


Un bloc de mémoire étant une propriété, mais destiné à être transmis vers plusieurs composants dont certains
peuvent fonctionner simultanément sur un bloc de mémoire particulier, il est important de définir des instructions
pour l’utilisation de Memory<T> et de Span<T>. Des instructions sont nécessaires pour les raisons suivantes :
Il est possible qu’un composant conserve une référence à un bloc de mémoire une fois que son propriétaire
l’a libéré.
Il est possible qu’un composant fonctionne sur une mémoire tampon en même temps qu’un autre
composant et que ce processus endommage les données de la mémoire tampon.
Alors que l’allocation par pile de Span<T> optimise les performances et fait de Span<T> le type préféré de
fonctionnement sur un bloc de mémoire, elle soumet également Span<T> à certaines restrictions majeures.
Il est important de savoir quand utiliser un Span<T> et quand utiliser Memory<T>.
Voici nos recommandations quant à l’utilisation réussie de Memory<T> et de ses types associés. Instructions qui
s’appliquent à Memory<T> et Span<T> s’appliquent également à ReadOnlyMemory<T> et ReadOnlySpan<T>
sauf indication contraire.
#1 de règle : pour une API synchrone, utilisez span <T> au lieu de <T> la mémoire comme
paramètre, si possible.
Span<T> est plus polyvalente que Memory<T> et peut représenter une plus grande variété de mémoires tampons
contiguës. Span<T> offre également de meilleures performances que Memory<T>. Enfin, vous pouvez utiliser la
Memory<T>.Span propriété pour convertir une Memory<T> instance en Span<T> , bien que la <T> conversion
d’étendue à mémoire <T> ne soit pas possible. Par conséquent, si vos appelants ont une instance Memory<T>, ils
pourront de toute façon appeler vos méthodes avec des paramètres Span<T>.
L’utilisation d’un paramètre de type Span<T> au lieu d’un paramètre de type Memory<T> vous aide également à
écrire une implémentation correcte de la méthode de consommation. Des vérifications automatiques au moment
de la compilation vous permettent de garantir que vous ne tentez pas d’accéder à la mémoire tampon au-delà de
votre bail de méthode (nous y reviendrons plus tard).
Vous devrez parfois utiliser un paramètre Memory<T> au lieu d’un paramètre Span<T>, même si vous êtes
entièrement synchrone. Il est possible qu’une API dont vous dépendez n’accepte que des arguments Memory<T>.
C’est bien, mais tenez compte des compromis impliqués par l’utilisation synchrone de Memory<T>.
#2 de règle : utilisez ReadOnlySpan <T> ou ReadOnlyMemor y <T> si la mémoire tampon doit être
en lecture seule.
Dans les exemples précédents, la méthode DisplayBufferToConsole lit uniquement à partir de la mémoire tampon ;
elle ne modifie pas le contenu de la mémoire tampon. La signature de méthode doit être modifiée comme suit.

void DisplayBufferToConsole(ReadOnlyMemory<char> buffer);

En fait, si nous combinons cette règle et la règle 1, nous pouvons faire encore mieux et réécrire la signature de
méthode comme suit :

void DisplayBufferToConsole(ReadOnlySpan<char> buffer);

La méthode DisplayBufferToConsole fonctionne désormais avec pratiquement chaque type de mémoire tampon
imaginable : T[] , stockage alloué avec stackalloc, et ainsi de suite. Vous pouvez même y passer directement une
chaîne String !
#3 de règle : Si votre méthode accepte <T> la mémoire et retourne void , vous ne devez pas utiliser
l’instance de mémoire <T> après le retour de la méthode.
Ceci est lié au concept de « bail » mentionné précédemment. Un bail de méthode avec renvoi d’annulation sur
l’instance Memory<T> commence lorsqu’on entre dans la méthode et se termine lorsqu’on la quitte. Prenons
l’exemple suivant, qui appelle Log dans une boucle basée sur l’entrée à partir de la console.
using System;
using System.Buffers;

public class Example


{
// implementation provided by third party
static extern void Log(ReadOnlyMemory<char> message);

// user code
public static void Main()
{
using (var owner = MemoryPool<char>.Shared.Rent())
{
var memory = owner.Memory;
var span = memory.Span;
while (true)
{
int value = Int32.Parse(Console.ReadLine());
if (value < 0)
return;

int numCharsWritten = ToBuffer(value, span);


Log(memory.Slice(0, numCharsWritten));
}
}
}

private static int ToBuffer(int value, Span<char> span)


{
string strValue = value.ToString();
int length = strValue.Length;
strValue.AsSpan().CopyTo(span.Slice(0, length));
return length;
}
}

Si Log est une méthode parfaitement synchrone, ce code se comporte comme prévu, car il n’y a qu’un seul
consommateur actif de l’instance de la mémoire à un moment donné. Mais imaginez plutôt que Log a cette
implémentation.

// !!! INCORRECT IMPLEMENTATION !!!


static void Log(ReadOnlyMemory<char> message)
{
// Run in background so that we don't block the main thread while performing IO.
Task.Run(() =>
{
StreamWriter sw = File.AppendText(@".\input-numbers.dat");
sw.WriteLine(message);
});
}

Dans cette implémentation, Log enfreint son bail, car il tente toujours d’utiliser l’instance Memory<T> en arrière-
plan après le retour de la méthode d’origine. La méthode Main pourrait muter la mémoire tampon pendant que
Log tente de la lire, ce qui pourrait entraîner une altération des données.

Il y a de nombreuses manières de résoudre ce problème :


La méthode Log peut retourner une Task au lieu de void , comme le fait l’implémentation suivante de la
méthode Log .
// An acceptable implementation.
static Task Log(ReadOnlyMemory<char> message)
{
// Run in the background so that we don't block the main thread while performing IO.
return Task.Run(() => {
StreamWriter sw = File.AppendText(@".\input-numbers.dat");
sw.WriteLine(message);
sw.Flush();
});
}

À la place, Log peut plutôt être implémenté comme suit :

// An acceptable implementation.
static void Log(ReadOnlyMemory<char> message)
{
string defensiveCopy = message.ToString();
// Run in the background so that we don't block the main thread while performing IO.
Task.Run(() => {
StreamWriter sw = File.AppendText(@".\input-numbers.dat");
sw.WriteLine(defensiveCopy);
sw.Flush();
});
}

#4 de règle : Si votre méthode accepte une mémoire <T> et retourne une tâche, vous ne devez pas
utiliser l’instance de mémoire <T> après la transition de la tâche vers un état terminal.
Il s’agit simplement la variante asynchrone de la règle 3. La méthode Log de l’exemple précédent peut être écrite
comme suit pour se conformer à cette règle :

// An acceptable implementation.
static void Log(ReadOnlyMemory<char> message)
{
// Run in the background so that we don't block the main thread while performing IO.
Task.Run(() => {
string defensiveCopy = message.ToString();
StreamWriter sw = File.AppendText(@".\input-numbers.dat");
sw.WriteLine(defensiveCopy);
sw.Flush();
});
}

Ici, « état terminal » signifie que la tâche passe à un état terminé, ayant généré une erreur ou annulé. En d’autres
termes, « état terminal » signifie « tout ce qui provoquerait une attente lors du lancement ou de la poursuite de
l’exécution. »
Ces conseils s’appliquent aux méthodes qui retournent Task, Task<TResult>, ValueTask<TResult>, ou n’importe
quel type similaire.
#5 de règle : Si votre constructeur accepte la mémoire <T> en tant que paramètre, les méthodes
d’instance sur l’objet construit sont supposées être des consommateurs de l’instance de mémoire
<T> .
Prenons l’exemple suivant :
class OddValueExtractor
{
public OddValueExtractor(ReadOnlyMemory<int> input);
public bool TryReadNextOddValue(out int value);
}

void PrintAllOddValues(ReadOnlyMemory<int> input)


{
var extractor = new OddValueExtractor(input);
while (extractor.TryReadNextOddValue(out int value))
{
Console.WriteLine(value);
}
}

Ici, le constructeur OddValueExtractor accepte ReadOnlyMemory<int> comme paramètre de constructeur, si bien que
le constructeur lui-même est un consommateur de l’instance ReadOnlyMemory<int> et que toutes les méthodes
d’instance sur la valeur retournée sont également des consommateurs de l’instance ReadOnlyMemory<int> d’origine.
Cela signifie que TryReadNextOddValue consomme l’instance ReadOnlyMemory<int> , même si l’instance n’est pas
passée directement à la méthode TryReadNextOddValue .
#6 de règle : Si vous disposez d’une <T> propriété de type mémoire définissable (ou d’une méthode
d’instance équivalente) sur votre type, les méthodes d’instance sur cet objet sont supposées être des
consommateurs de l’instance de mémoire <T> .
Il s’agit simplement d’une variante de la règle 5. Cette règle existe, car les setters de propriété ou les méthodes
équivalentes sont supposés capturer et conserver leurs entrées, de manière que les méthodes d’instances sur le
même objet puissent utiliser l’état de capture.
L'exemple suivant déclenche cette règle :

class Person
{
// Settable property.
public Memory<char> FirstName { get; set; }

// alternatively, equivalent "setter" method


public SetFirstName(Memory<char> value);

// alternatively, a public settable field


public Memory<char> FirstName;
}

#7 de règle : Si vous disposez d’une <T> référence IMemor yOwner, vous devez à un cer tain stade la
supprimer ou transférer sa propriété (mais pas les deux).
Dans la mesure où une instance Memory<T> peut être sauvegardée par de la mémoire, managée ou non, le
propriétaire doit appeler MemoryPool<T>.Dispose lorsque le travail effectué sur l’instance Memory<T> est
terminé. Le propriétaire peut également transférer la propriété de l’instance IMemoryOwner<T> à un autre
composant. Le composant d’acquisition devient alors responsable de l’appel de MemoryPool<T>.Dispose au
moment opportun (nous y reviendrons plus tard).
Ne pas appeler la méthode Dispose peut entraîner des fuites de mémoire non managée ou une autre dégradation
des performances.
Cette règle s’applique également au code qui appelle les méthodes de fabrique, telles que MemoryPool<T>.Rent.
L’appelant devient le propriétaire du IMemoryOwner<T> retourné et est responsable de la suppression de
l’instance terminée.
#8 de règle : Si vous avez un <T> paramètre IMemor yOwner dans la surface de l’API, vous acceptez
la propriété de cette instance.
Accepter une instance de ce type signale que votre composant a l’intention de prendre possession de cette
instance. Votre composant devient responsable de la suppression correcte conformément à la règle 7.
Tout composant qui transfère la propriété de l’instance IMemoryOwner<T> à un autre composant ne doit plus
utiliser cette instance lorsque l’appel de méthode se termine.

IMPORTANT
Si votre constructeur accepte IMemoryOwner<T> comme paramètre, son type doit implémenter IDisposable et votre
méthode Dispose doit appeler MemoryPool<T>.Dispose.

#9 de règle : Si vous encapsulez une méthode p/Invoke synchrone, votre API doit accepter span <T>
comme paramètre.
Conformément à la règle 1, Span<T> est généralement le type correct à utiliser pour les API synchrones. Vous
pouvez épingler des instances Span<T> via le mot clé fixed mot clé, comme dans l’exemple suivant.

using System.Runtime.InteropServices;

[DllImport(...)]
private static extern unsafe int ExportedMethod(byte* pbData, int cbData);

public unsafe int ManagedWrapper(Span<byte> data)


{
fixed (byte* pbData = &MemoryMarshal.GetReference(data))
{
int retVal = ExportedMethod(pbData, data.Length);

/* error checking retVal goes here */

return retVal;
}
}

Dans l’exemple précédent, pbData peut être Null si, par exemple, l’étendue d’entrée est vide. Si la méthode
exportée requiert absolument que pbData soit non Null, même si cbData est égal à 0, la méthode peut être
implémentée comme suit :

public unsafe int ManagedWrapper(Span<byte> data)


{
fixed (byte* pbData = &MemoryMarshal.GetReference(data))
{
byte dummy = 0;
int retVal = ExportedMethod((pbData != null) ? pbData : &dummy, data.Length);

/* error checking retVal goes here */

return retVal;
}
}

#10 de règle : Si vous encapsulez une méthode p/Invoke asynchrone, votre API doit accepter la
mémoire <T> en tant que paramètre.
Étant donné que vous ne pouvez pas utiliser le fixed mot clé dans les opérations asynchrones, vous utilisez la
Memory<T>.Pin méthode pour épingler des Memory<T> instances, quel que soit le type de mémoire contiguë
représenté par l’instance. L’exemple suivant montre comment utiliser cette API pour effectuer un appel p/invoke
asynchrone.

using System.Runtime.InteropServices;

[UnmanagedFunctionPointer(...)]
private delegate void OnCompletedCallback(IntPtr state, int result);

[DllImport(...)]
private static extern unsafe int ExportedAsyncMethod(byte* pbData, int cbData, IntPtr pState, IntPtr
lpfnOnCompletedCallback);

private static readonly IntPtr _callbackPtr = GetCompletionCallbackPointer();

public unsafe Task<int> ManagedWrapperAsync(Memory<byte> data)


{
// setup
var tcs = new TaskCompletionSource<int>();
var state = new MyCompletedCallbackState
{
Tcs = tcs
};
var pState = (IntPtr)GCHandle.Alloc(state);

var memoryHandle = data.Pin();


state.MemoryHandle = memoryHandle;

// make the call


int result;
try
{
result = ExportedAsyncMethod((byte*)memoryHandle.Pointer, data.Length, pState, _callbackPtr);
}
catch
{
((GCHandle)pState).Free(); // cleanup since callback won't be invoked
memoryHandle.Dispose();
throw;
}

if (result != PENDING)
{
// Operation completed synchronously; invoke callback manually
// for result processing and cleanup.
MyCompletedCallbackImplementation(pState, result);
}

return tcs.Task;
}

private static void MyCompletedCallbackImplementation(IntPtr state, int result)


{
GCHandle handle = (GCHandle)state;
var actualState = (MyCompletedCallbackState)(handle.Target);
handle.Free();
actualState.MemoryHandle.Dispose();

/* error checking result goes here */

if (error)
{
actualState.Tcs.SetException(...);
}
else
{
actualState.Tcs.SetResult(result);
}
}
}

private static IntPtr GetCompletionCallbackPointer()


{
OnCompletedCallback callback = MyCompletedCallbackImplementation;
GCHandle.Alloc(callback); // keep alive for lifetime of application
return Marshal.GetFunctionPointerForDelegate(callback);
}

private class MyCompletedCallbackState


{
public TaskCompletionSource<int> Tcs;
public MemoryHandle MemoryHandle;
}

Voir aussi
System.Memory<T>
System.Buffers.IMemoryOwner<T>
System.Span<T>
Interopérabilité native
24/04/2020 • 2 minutes to read • Edit Online

Les articles suivants montrent les différentes façons d’utiliser l’« interopérabilité native » dans .NET.
Voici quelques raisons qui peuvent vous inciter à appeler du code natif :
Les systèmes d’exploitation sont fournis avec un volume important d’API qui ne sont pas présentes dans les
bibliothèques de classes managées. Un bon exemple pour ce scénario est l’accès à des fonctions de gestion de
matériel ou de gestion de système d’exploitation.
La communication avec d’autres composants qui ont ou peuvent produire des ABI de style C (ABI natifs),
comme du code Java qui est exposé via l’interface JNI (Java Native Interface) ou tout autre langage managé qui
peut produire un composant natif.
Sur Windows, la plupart des logiciels qui sont installés, comme la suite Microsoft Office, inscrivent des
composants COM qui représentent leurs programmes et permettent aux développeurs de les automatiser ou
de les utiliser. Cela nécessite également l’interopérabilité native.
La liste précédente n’englobe pas toutes les situations et scénarios potentiels dans lesquels le développeur
voudrait/aimerait/devrait interagir avec des composants natifs. La bibliothèque de classes .NET, par exemple,
utilise la prise en charge de l’interopérabilité native pour implémenter un certain nombre de ses API, comme la
prise en charge et la manipulation de la console, l’accès au système de fichiers, etc. Toutefois, il est important de
noter qu’il existe une option si nécessaire.

NOTE
La plupart des exemples de cette section sont présentés pour les trois plateformes prises en charge de .NET Core (Windows,
Linux et Mac OS). Toutefois, pour certains exemples courts et illustratifs, seuls des noms de fichiers et des extensions
Windows sont cités (par exemple, « dll » pour les bibliothèques). Cela ne signifie pas que ces fonctionnalités ne sont pas
disponibles sur Linux ou Mac OS, il s’agit d’un choix purement pratique.

Voir aussi
Appel de code non managé (P/Invoke)
Marshaling de types
Meilleures pratiques pour l’interopérabilité native
Appel de code non managé (P/Invoke)
24/04/2020 • 10 minutes to read • Edit Online

P/Invoke est une technologie qui vous permet d’accéder aux structures, aux rappels et aux fonctions des
bibliothèques non managées à partir de votre code managé. Une grande partie de l’API P/Invoke est contenue dans
deux espaces de noms : System et System.Runtime.InteropServices . Utiliser ces deux espaces de noms vous donne
les outils pour décrire la façon dont vous voulez communiquer avec le composant natif.
Commençons par l’exemple le plus courant, c’est-à-dire appeler des fonctions non managées dans votre code
managé. Nous allons afficher une zone de message à partir d’une application de ligne de commande :

using System;
using System.Runtime.InteropServices;

public class Program


{
// Import user32.dll (containing the function we need) and define
// the method corresponding to the native function.
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);

public static void Main(string[] args)


{
// Invoke the function as a regular managed method.
MessageBox(IntPtr.Zero, "Command-line message box", "Attention!", 0);
}
}

L’exemple précédent est simple, mais il ne montre pas ce dont vous avez besoin pour appeler des fonctions non
managées à partir de code managé. Examinons l’exemple :
La ligne 1 montre l’instruction d’utilisation de l’espace de noms System.Runtime.InteropServices , qui contient
tous les éléments dont nous avons besoin.
La ligne 7 introduit l’attribut DllImport . Cet attribut est crucial, car il indique au runtime qu’il doit charger la
DLL non managée. La chaîne passée est la DLL où se trouve notre fonction cible. En outre, il définit le jeu de
caractères à utiliser pour marshaler les chaînes. Il spécifie enfin que cette fonction appelle SetLastError et que le
runtime doit capturer ce code d’erreur pour que l’utilisateur puisse le récupérer avec
Marshal.GetLastWin32Error().
La ligne 8 est l’essentiel du travail de P/Invoke. Elle définit une méthode managée qui a exactement la même
signature que la méthode non managée. Notez que la déclaration a un nouveau mot clé, extern , qui indique
au runtime qu’il s’agit d’une méthode externe et que, quand vous l’appelez, le runtime doit la trouver dans la
DLL spécifiée dans l’attribut DllImport .
Le reste de l’exemple appelle simplement la méthode comme toute autre méthode managée.
L’exemple est similaire pour Mac OS. Le nom de la bibliothèque dans l’attribut DllImport doit être modifié, car
Mac OS a un schéma de nommage des bibliothèques dynamiques différent. L’exemple suivant utilise la fonction
getpid(2) pour obtenir l’ID de processus de l’application et l’afficher dans la console :
using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
public static class Program
{
// Import the libSystem shared library and define the method
// corresponding to the native function.
[DllImport("libSystem.dylib")]
private static extern int getpid();

public static void Main(string[] args)


{
// Invoke the function and get the process ID.
int pid = getpid();
Console.WriteLine(pid);
}
}
}

Le processus est semblable sur Linux. Le nom de fonction est le même, car getpid(2) est un appel système POSIX
standard.

using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
public static class Program
{
// Import the libc shared library and define the method
// corresponding to the native function.
[DllImport("libc.so.6")]
private static extern int getpid();

public static void Main(string[] args)


{
// Invoke the function and get the process ID.
int pid = getpid();
Console.WriteLine(pid);
}
}
}

Appel de code managé à partir de code non managé


Le runtime permet une communication fluide dans les deux directions, ce qui vous donne la possibilité d’effectuer
des rappels dans du code managés à partir de fonctions natives, à l’aide de pointeurs de fonction. Ce qui ressemble
le plus à un pointeur de fonction dans le code managé est un délégué , c’est donc ce qui est utilisé pour autoriser
les rappels du code natif dans le code managé.
La façon d’utiliser cette fonctionnalité est similaire au processus code managé vers du code natif préalablement
décrit. Pour un rappel donné, vous définissez un délégué qui correspond à la signature et vous le passez dans la
méthode externe. Le runtime s’occupe du reste.
using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication1
{
public static class Program
{
// Define a delegate that corresponds to the unmanaged function.
private delegate bool EnumWC(IntPtr hwnd, IntPtr lParam);

// Import user32.dll (containing the function we need) and define


// the method corresponding to the native function.
[DllImport("user32.dll")]
private static extern int EnumWindows(EnumWC lpEnumFunc, IntPtr lParam);

// Define the implementation of the delegate; here, we simply output the window handle.
private static bool OutputWindow(IntPtr hwnd, IntPtr lParam)
{
Console.WriteLine(hwnd.ToInt64());
return true;
}

public static void Main(string[] args)


{
// Invoke the method; note the delegate as a first parameter.
EnumWindows(OutputWindow, IntPtr.Zero);
}
}
}

Avant de parcourir l’exemple, passons en revue les signatures des fonctions non managées dont vous avez besoin.
La fonction à appeler pour énumérer toutes les fenêtres a la signature suivante :
BOOL EnumWindows (WNDENUMPROC lpEnumFunc, LPARAM lParam);

Le premier paramètre est un rappel. Ce rappel a la signature suivante :


BOOL CALLBACK EnumWindowsProc (HWND hwnd, LPARAM lParam);

Examinons maintenant l’exemple en détail :


la ligne 9 dans l’exemple définit un délégué qui correspond à la signature du rappel dans le code non managé.
Notez la façon dont les types LPARAM et HWND sont représentés à l’aide de IntPtr dans le code managé.
Les lignes 13 et 14 introduisent la fonction EnumWindows de la bibliothèque user32.dll.
Les lignes 17 à 20 implémentent le délégué. Dans cet exemple simple, nous voulons simplement générer le
handle dans la console.
Enfin, dans la ligne 24, la méthode externe est appelée et transmise au délégué.
Les exemples de Linux et Mac OS sont présentés ci-dessous. Dans ces exemples, nous utilisons la fonction ftw qui
se trouve dans libc , la bibliothèque C. Cette fonction est utilisée pour parcourir des hiérarchies de répertoires et
accepte un pointeur vers une fonction comme l’un de ses paramètres. Cette fonction a la signature suivante :
int (*fn) (const char *fpath, const struct stat *sb, int typeflag) .
using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
public static class Program
{
// Define a delegate that has the same signature as the native function.
private delegate int DirClbk(string fName, StatClass stat, int typeFlag);

// Import the libc and define the method to represent the native function.
[DllImport("libc.so.6")]
private static extern int ftw(string dirpath, DirClbk cl, int descriptors);

// Implement the above DirClbk delegate;


// this one just prints out the filename that is passed to it.
private static int DisplayEntry(string fName, StatClass stat, int typeFlag)
{
Console.WriteLine(fName);
return 0;
}

public static void Main(string[] args)


{
// Call the native function.
// Note the second parameter which represents the delegate (callback).
ftw(".", DisplayEntry, 10);
}
}

// The native callback takes a pointer to a struct. The below class


// represents that struct in managed code. You can find more information
// about this in the section on marshalling below.
[StructLayout(LayoutKind.Sequential)]
public class StatClass
{
public uint DeviceID;
public uint InodeNumber;
public uint Mode;
public uint HardLinks;
public uint UserID;
public uint GroupID;
public uint SpecialDeviceID;
public ulong Size;
public ulong BlockSize;
public uint Blocks;
public long TimeLastAccess;
public long TimeLastModification;
public long TimeLastStatusChange;
}
}

Un exemple Mac OS utilise la même fonction et la seule différence réside dans l’argument de l’attribut DllImport ,
car Mac OS conserve libc dans un emplacement différent.
using System;
using System.Runtime.InteropServices;

namespace PInvokeSamples
{
public static class Program
{
// Define a delegate that has the same signature as the native function.
private delegate int DirClbk(string fName, StatClass stat, int typeFlag);

// Import the libc and define the method to represent the native function.
[DllImport("libSystem.dylib")]
private static extern int ftw(string dirpath, DirClbk cl, int descriptors);

// Implement the above DirClbk delegate;


// this one just prints out the filename that is passed to it.
private static int DisplayEntry(string fName, StatClass stat, int typeFlag)
{
Console.WriteLine(fName);
return 0;
}

public static void Main(string[] args)


{
// Call the native function.
// Note the second parameter which represents the delegate (callback).
ftw(".", DisplayEntry, 10);
}
}

// The native callback takes a pointer to a struct. The below class


// represents that struct in managed code.
[StructLayout(LayoutKind.Sequential)]
public class StatClass
{
public uint DeviceID;
public uint InodeNumber;
public uint Mode;
public uint HardLinks;
public uint UserID;
public uint GroupID;
public uint SpecialDeviceID;
public ulong Size;
public ulong BlockSize;
public uint Blocks;
public long TimeLastAccess;
public long TimeLastModification;
public long TimeLastStatusChange;
}
}

Les deux exemples précédents dépendent de paramètres et dans les deux cas, les paramètres sont fournis comme
des types managés. Le runtime prend « la bonne décision » et les traite dans leurs équivalents de l’autre côté.
Découvrez comment les types sont marshalés en code natif sur notre page consacrée au Marshaling de types.

Plus de ressources
Wiki PInvoke.net, un excellent Wiki avec des informations sur les API Windows courantes et comment les
appeler.
P/Invoke dans C++/CLI
Documentation Mono sur P/Invoke
Marshaling de types
24/04/2020 • 8 minutes to read • Edit Online

Le marshaling est le processus qui consiste à transformer les types quand ils doivent naviguer entre du code
managé et du code natif.
La raison pour laquelle le marshaling est nécessaire est que les types diffèrent entre le code managé et le code non
managé. Dans le code managé, par exemple, vous avez String un, tandis que dans les chaînes universelles non
managées peuvent être Unicode (« larges »), non-Unicode, terminé par null, ASCII, etc. Par défaut, le sous-système
P/Invoke tente d’effectuer la bonne chose en fonction du comportement par défaut, décrit dans cet article.
Toutefois, dans les cas où vous avez besoin de plus de contrôle, vous pouvez employer l’attribut MarshalAs pour
spécifier le type attendu du côté du code non managé. Par exemple, pour que la chaîne soit envoyée sous forme de
chaîne ANSI terminée par Null, vous pouvez procéder ainsi :

[DllImport("somenativelibrary.dll")]
static extern int MethodA([MarshalAs(UnmanagedType.LPStr)] string parameter);

Règles par défaut de marshaling des types courants


En règle générale, le runtime tente de prendre la bonne décision en matière de marshaling, c’est-à-dire celle qui
demande le moins de travail de votre part. Les tableaux suivants indiquent comment chaque type est marshalé par
défaut lorsqu’il est utilisé dans un paramètre ou un champ. Les types de caractères et d’entiers de longueur fixe
C99/C ++11 garantissent que le tableau suivant est correct pour toutes les plateformes. Vous pouvez utiliser
n’importe quel type natif ayant les mêmes exigence d’alignement et de taille que ces types.
Le premier tableau décrit les correspondances de différents types pour lesquels le marshaling P/Invoke et le
marshaling des champs sont identiques.

T Y P E . N ET T Y P E N AT IF

byte uint8_t

sbyte int8_t

short int16_t

ushort uint16_t

int int32_t

uint uint32_t

long int64_t

ulong uint64_t

char char ou char16_t selon le CharSet de P/Invoke ou de la


structure. Voir la documentation charset.
T Y P E . N ET T Y P E N AT IF

string char* ou char16_t* selon le CharSet de P/Invoke ou de


la structure. Voir la documentation charset.

System.IntPtr intptr_t

System.UIntPtr uintptr_t

Types pointeur .NET (p. ex., void* ) void*

Type dérivé de void*


System.Runtime.InteropServices.SafeHandle

Type dérivé de void*


System.Runtime.InteropServices.CriticalHandle

bool Type BOOL Win32

decimal Struct DECIMAL COM

Délégué .NET Pointeur de fonction natif

System.DateTime Type DATE Win32

System.Guid Type GUID Win32

Certaines catégories ont des valeurs par défaut différentes pour le marshaling comme paramètre ou comme
structure.

T Y P E . N ET T Y P E N AT IF ( PA RA M ÈT RE) T Y P E N AT IF ( C H A M P )

Tableau .NET Pointeur vers le début d’un tableau de Non autorisé sans attribut
représentations natives des éléments [MarshalAs]
du tableau

Classe avec LayoutKind de Pointeur vers la représentation native Représentation native de la classe
Sequential ou de Explicit de la classe

Le tableau suivant présente les règles de marshaling par défaut propres à Windows. Sur les autres plateformes, il
n’est pas possible de marshaler ces types.

T Y P E . N ET T Y P E N AT IF ( PA RA M ÈT RE) T Y P E N AT IF ( C H A M P )

object VARIANT IUnknown*

System.Array Interface COM Non autorisé sans attribut


[MarshalAs]

System.ArgIterator va_list Non autorisé

System.Collections.IEnumeratorI EnumVARIANT* Non autorisé


T Y P E . N ET T Y P E N AT IF ( PA RA M ÈT RE) T Y P E N AT IF ( C H A M P )

System.Collections.IEnumerableI Dispatch* Non autorisé

System.DateTimeOffset représentant le nombre de représentant le nombre de


cycles depuis le 1er janvier cycles depuis le 1er janvier
1601 à minuit 1601 à minuit

Certains types ne peuvent être marshalés que comme paramètres, et non comme champs. :

T Y P E . N ET T Y P E N AT IF ( PA RA M ÈT RE UN IQ UEM EN T )

System.Text.StringBuilder char* ou char16_t* selon le CharSet de P/Invoke. Voir la


documentation charset.

System.ArgIterator va_list (sur Windows x86/x64/arm64 uniquement)

System.Runtime.InteropServices.ArrayWithOffset void*

System.Runtime.InteropServices.HandleRef void*

Si ces valeurs par défaut ne vous conviennent pas tout à fait, vous pouvez personnaliser la façon dont les
paramètres sont marshalés. L’article Marshaling des paramètres explique comment faire, pour différents types de
paramètres.

Marshaling par défaut dans les scénarios COM


Quand vous appelez des méthodes sur des objets COM dans .NET, le runtime .NET modifie les règles de
marshaling par défaut pour qu’elles correspondent à la sémantique COM courante. Le tableau suivant liste les
règles utilisées par les runtimes .NET dans les scénarios COM :

T Y P E . N ET T Y P E N AT IF ( A P P EL S DE M ÉT H O DES C O M )

bool VARIANT_BOOL

StringBuilder LPWSTR

string BSTR

Types délégués _Delegate* dans le .NET Framework. Interdits dans .NET


Core.

System.Drawing.Color OLECOLOR

Tableau .NET SAFEARRAY

string[] SAFEARRAY de BSTR s

Marshaling de classes et de structures


Un autre aspect du marshaling de types consiste à passer une structure à une méthode non managée. Par
exemple, certaines des méthodes non managées nécessitent une structure comme paramètre. Il faut dans ce cas
créer une classe ou un struct correspondant dans la partie managée de l’environnement pour l’utiliser comme
paramètre. Toutefois, il ne suffit pas de définir la classe : il est également nécessaire d’indiquer au marshaleur
comment mapper des champs de la classe au struct non managé. C’est ici qu’intervient l’attribut StructLayout .

[DllImport("kernel32.dll")]
static extern void GetSystemTime(SystemTime systemTime);

[StructLayout(LayoutKind.Sequential)]
class SystemTime {
public ushort Year;
public ushort Month;
public ushort DayOfWeek;
public ushort Day;
public ushort Hour;
public ushort Minute;
public ushort Second;
public ushort Milsecond;
}

public static void Main(string[] args) {


SystemTime st = new SystemTime();
GetSystemTime(st);
Console.WriteLine(st.Year);
}

Le code précédent illustre de manière simple les appels dans la fonction GetSystemTime() . L’élément digne
d’intérêt se trouve sur la ligne 4. L’attribut spécifie que les champs de la classe doivent être mappés
séquentiellement à la structure de l’autre côté (non managé). Cela signifie que le nom des champs n’a pas
d’importance ; seul leur ordre compte, car il doit correspondre au struct non managé, comme dans l’exemple
suivant :

typedef struct _SYSTEMTIME {


WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;

Il est possible que le marshaling par défaut de votre structure ne vous convienne pas. L’article Personnaliser le
marshaling des structures explique comment personnaliser la façon dont les structures sont marshalées.
Personnaliser le marshaling de structures
18/07/2020 • 11 minutes to read • Edit Online

Parfois, les règles de marshaling par défaut pour les structures ne sont pas exactement ce dont vous avez besoin.
Les runtimes .NET fournissent quelques points d’extension qui vous permettent de personnaliser la disposition de
votre structure et la manière dont les champs sont marshalés.

Personnalisation de la disposition de structures


.NET fournit l’attribut System.Runtime.InteropServices.StructLayoutAttribute et l’énumération
System.Runtime.InteropServices.LayoutKind pour vous permettre de personnaliser la façon dont les champs sont
placés en mémoire. Les conseils suivants vous aideront à éviter les problèmes courants.
ENVISAGEZ de ✔
️ d’utiliser LayoutKind.Sequential dans la mesure du possible.
✔ Utilisez uniquement LayoutKind.Explicit dans le marshaling lorsque votre structure native a également une

disposition explicite, telle qu’une Union.
❌Évitez LayoutKind.Explicit d’utiliser lors du marshaling de structures sur des plateformes non-Windows si
vous devez cibler des runtimes avant .net Core 3,0. Le Runtime .NET Core avant 3,0 ne prend pas en charge le
passage de structures explicites par valeur aux fonctions natives sur les systèmes Intel ou AMD 64 bits non-
Windows. Toutefois, le runtime prend en charge le passage de structures par référence sur toutes les plateformes.

Personnalisation du marshaling des champs booléens


Le code natif a de nombreuses représentations booléennes différentes. Sur Windows uniquement, il y a trois
façons de représenter des valeurs booléennes. Le runtime ne connaissant pas la définition native de votre
structure, le mieux est faire une estimation sur la manière de marshaler vos valeurs booléennes. Le runtime .NET
fournit un moyen d’indiquer comment marshaler votre champ booléen. Les exemples suivants montrent comment
marshaler .NET bool pour différents types booléens natifs.
Les valeurs booléennes sont marshalées par défaut en tant que valeur Win32 sur 4 octets native BOOL , comme
indiqué dans l’exemple suivant :

public struct WinBool


{
public bool b;
}

struct WinBool
{
public BOOL b;
};

Si vous souhaitez être explicite, vous pouvez utiliser la valeur UnmanagedType.Bool pour obtenir le même
comportement que ci-dessus :
public struct WinBool
{
[MarshalAs(UnmanagedType.Bool)]
public bool b;
}

struct WinBool
{
public BOOL b;
};

À l’aide des valeurs UnmanagedType.U1 ou UnmanagedType.I1 ci-dessous, vous pouvez indiquer au runtime de
marshaler le champ b comme type bool natif sur 1 octet.

public struct CBool


{
[MarshalAs(UnmanagedType.U1)]
public bool b;
}

struct CBool
{
public bool b;
};

Sur Windows, vous pouvez utiliser la valeur UnmanagedType.VariantBool pour indiquer au runtime de marshaler
votre valeur booléenne dans une valeur VARIANT_BOOL sur 2 octets :

public struct VariantBool


{
[MarshalAs(UnmanagedType.VariantBool)]
public bool b;
}

struct VariantBool
{
public VARIANT_BOOL b;
};

NOTE
VARIANT_BOOL est différent de la plupart des types de bools dans VARIANT_TRUE = -1 et VARIANT_FALSE = 0 . De plus,
toutes les valeurs qui ne sont pas égales à VARIANT_TRUE sont considérées comme false.

Personnalisation du marshaling des champs de tableaux


.NET inclut également quelques façons de personnaliser le marshaling de tableaux.
Par défaut, .NET marshale des tableaux en tant que pointeur vers une liste contiguë d’éléments :
public struct DefaultArray
{
public int[] values;
}

struct DefaultArray
{
int* values;
};

Si vous interagissez avec des API COM, vous devez marshaler des tableaux en tant qu’objets SAFEARRAY* . Vous
pouvez utiliser les valeurs System.Runtime.InteropServices.MarshalAsAttribute et UnmanagedType.SafeArray pour
indiquer au runtime de marshaler un tableau en tant que SAFEARRAY* :

public struct SafeArrayExample


{
[MarshalAs(UnmanagedType.SafeArray)]
public int[] values;
}

struct SafeArrayExample
{
SAFEARRAY* values;
};

Si vous devez personnaliser le type d’élément dans le SAFEARRAY , vous pouvez utiliser les champs
MarshalAsAttribute.SafeArraySubType et MarshalAsAttribute.SafeArrayUserDefinedSubType pour personnaliser le
type d’élément exacte du SAFEARRAY .
Si vous devez marshaler le tableau sur place, vous pouvez utiliser la valeur UnmanagedType.ByValArray pour
indiquer au marshaleur de le faire. Lorsque vous utilisez ce marshaling, vous devez également fournir une valeur
au champ MarshalAsAttribute.SizeConst pour le nombre d’éléments du tableau afin que le runtime puisse allouer
correctement de l’espace pour la structure.

public struct InPlaceArray


{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] values;
}

struct InPlaceArray
{
int values[4];
};

NOTE
.NET ne prend pas en charge le marshaling d’un champ de tableau de longueur variable en tant que membre de tableau
flexible C99.

Personnalisation du marshaling des champs de chaînes


.NET fournit également un large éventail de personnalisations pour marshaler les champs de chaînes.
Par défaut, .NET marshale une chaîne en tant que pointeur vers une chaîne se terminant par Null. L’encodage
dépend de la valeur du champ StructLayoutAttribute.CharSet dans le
System.Runtime.InteropServices.StructLayoutAttribute. Si aucun attribut n’est spécifié, l’encodage par défaut est un
encodage ANSI.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]


public struct DefaultString
{
public string str;
}

struct DefaultString
{
char* str;
};

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]


public struct DefaultString
{
public string str;
}

struct DefaultString
{
char16_t* str; // Could also be wchar_t* on Windows.
};

Si vous devez utiliser différents codages pour les différents champs ou si vous préférez simplement être plus
explicite dans votre définition de struct, vous pouvez utiliser les valeurs UnmanagedType.LPStr ou
UnmanagedType.LPWStr sur un attribut System.Runtime.InteropServices.MarshalAsAttribute.

public struct AnsiString


{
[MarshalAs(UnmanagedType.LPStr)]
public string str;
}

struct AnsiString
{
char* str;
};

public struct UnicodeString


{
[MarshalAs(UnmanagedType.LPWStr)]
public string str;
}
struct UnicodeString
{
char16_t* str; // Could also be wchar_t* on Windows.
};

Si vous voulez marshaler vos chaînes à l’aide de l’encodage UTF-8, vous pouvez utiliser la valeur
UnmanagedType.LPUTF8Str dans votre MarshalAsAttribute.

public struct UTF8String


{
[MarshalAs(UnmanagedType.LPUTF8Str)]
public string str;
}

struct UTF8String
{
char* str;
};

NOTE
L’utilisation de UnmanagedType.LPUTF8Str requiert .NET Framework 4.7 (ou versions ultérieures) ou .NET Core 1.1 (ou
versions ultérieures). Elle n’est pas disponible dans .NET Standard 2.0.

Si vous travaillez avec des API COM, vous devrez peut-être marshaler une chaîne comme BSTR . À l’aide de la
valeur UnmanagedType.BStr, vous pouvez marshaler une chaîne comme BSTR .

public struct BString


{
[MarshalAs(UnmanagedType.BStr)]
public string str;
}

struct BString
{
BSTR str;
};

Lorsque vous utilisez une API WinRT, vous devez marshaler une chaîne comme HSTRING . À l’aide de la valeur
UnmanagedType.HString, vous pouvez marshaler une chaîne comme HSTRING .

public struct HString


{
[MarshalAs(UnmanagedType.HString)]
public string str;
}

struct BString
{
HSTRING str;
};
Si votre API vous oblige à passer la chaîne sur place dans la structure, vous pouvez utiliser la valeur
UnmanagedType.ByValTStr. Notez que l’encodage d’une chaîne marshalée par ByValTStr est déterminé à partir de
l’attribut CharSet . Ceci requiert en outre le passage d’une longueur de chaîne par le champ
MarshalAsAttribute.SizeConst.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]


public struct DefaultString
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string str;
}

struct DefaultString
{
char str[4];
};

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]


public struct DefaultString
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
public string str;
}

struct DefaultString
{
char16_t str[4]; // Could also be wchar_t[4] on Windows.
};

Personnalisation du marshaling de champs décimaux


Si vous travaillez sur Windows, vous pouvez rencontrer des API qui utilisent la structure CY ou CURRENCY native.
Par défaut, le type .NET est decimal marshalé à la DECIMAL structure native. Toutefois, vous pouvez utiliser un
MarshalAsAttribute avec la valeur UnmanagedType.Currency pour indiquer au marshaleur de convertir une valeur
decimal en valeur CY native.

public struct Currency


{
[MarshalAs(UnmanagedType.Currency)]
public decimal dec;
}

struct Currency
{
CY dec;
};

Triez System.Object
Sur Windows, vous pouvez marshaler des champs de type object dans du code natif. Vous pouvez marshaler ces
champs dans l’un des trois types suivants :
VARIANT
IUnknown*
IDispatch*

Par défaut, un champ de type object sera marshalé dans un IUnknown* qui inclut l’objet dans un wrapper.

public struct ObjectDefault


{
public object obj;
}

struct ObjectDefault
{
IUnknown* obj;
};

Si vous voulez marshaler un champ d’objet pour un IDispatch* , ajoutez un MarshalAsAttribute avec la valeur
UnmanagedType.IDispatch.

public struct ObjectDispatch


{
[MarshalAs(UnmanagedType.IDispatch)]
public object obj;
}

struct ObjectDispatch
{
IDispatch* obj;
};

Si vous voulez le marshaler comme VARIANT , ajoutez un MarshalAsAttribute avec la valeur


UnmanagedType.Struct.

public struct ObjectVariant


{
[MarshalAs(UnmanagedType.Struct)]
public object obj;
}

struct ObjectVariant
{
VARIANT obj;
};

Le tableau suivant décrit comment les différents types de runtime du champ obj correspondent aux différents
types stockés dans un VARIANT :

T Y P E . N ET T Y P E VA RIA N T T Y P E . N ET T Y P E VA RIA N T

byte VT_UI1 System.Runtime.InteropServices.BStrWrapper


VT_BSTR

sbyte VT_I1 object VT_DISPATCH


T Y P E . N ET T Y P E VA RIA N T T Y P E . N ET T Y P E VA RIA N T

short VT_I2 System.Runtime.InteropServices.UnknownWrapper


VT_UNKNOWN

ushort VT_UI2 System.Runtime.InteropServices.DispatchWrapper


VT_DISPATCH

int VT_I4 System.Reflection.Missing


VT_ERROR

uint VT_UI4 (object)null VT_EMPTY

long VT_I8 bool VT_BOOL

ulong VT_UI8 System.DateTime VT_DATE

float VT_R4 decimal VT_DECIMAL

double VT_R8 System.Runtime.InteropServices.CurrencyWrapper


VT_CURRENCY

char VT_UI2 System.DBNull VT_NULL

string VT_BSTR
Personnalisation du marshaling de paramètres
18/07/2020 • 5 minutes to read • Edit Online

Si le comportement de marshaling des paramètres par défaut du runtime .NET ne vous convient pas, vous pouvez
utiliser l’attribut System.Runtime.InteropServices.MarshalAsAttribute pour le personnaliser.

Personnaliser des paramètres de chaîne


.NET propose tout un éventail de formats pour le marshaling des chaînes. Ces méthodes sont divisées en deux
sections distinctes : les chaînes de style C et les chaînes orientées Windows.
Chaînes de style C
Chacun de ces formats transmet une chaîne se terminant par Null au code natif. Ils diffèrent par l’encodage de la
chaîne native.

VA L EUR SYSTEM.RUNTIME.INTEROPSERVICES.UNMANAGEDTYPE EN C O DA GE

LPStr ANSI

LPUTF8Str UTF-8

LPWStr UTF-16

LPTStr UTF-16

Le format UnmanagedType.VBByRefStr est légèrement différent. Comme LPWStr , il marshale la chaîne en une
chaîne de style C native encodée en UTF-16. Toutefois, la signature managée vous fait passer la chaîne en référence
; la signature native correspondante prend la chaîne en valeur. Cette distinction permet d’utiliser une API native qui
prend une chaîne en valeur et la modifie sur place sans qu’il soit nécessaire d’utiliser de StringBuilder . Nous vous
déconseillons d’utiliser manuellement ce format, dans la mesure où il risque de provoquer une confusion entre les
signatures native et managée.
Formats de chaînes orientées Windows
En interagissant avec des interfaces COM ou OLE, vous remarquerez sûrement que les fonctions natives prennent
des chaînes comme arguments BSTR . Vous pouvez utiliser le type non managé UnmanagedType.BStr pour
marshaler une chaîne comme BSTR .
Si vous communiquez avec des API WinRT, vous pouvez utiliser le format UnmanagedType.HString pour marshaler
une chaîne comme HSTRING .

Personnaliser des paramètres de tableau


.NET propose également plusieurs moyens de marshaler des paramètres de tableau. Si vous appelez une API qui
accepte un tableau de style C, utilisez le type non managé UnmanagedType.LPArray. Si les valeurs du tableau
exigent un marshaling personnalisé, vous pouvez pour cela utiliser le champ ArraySubType sur l’attribut
[MarshalAs] .

Si vous utilisez les API COM, vous devrez probablement marshaler vos paramètres de tableau comme SAFEARRAY* .
Pour cela, vous pouvez utiliser le type non managé UnmanagedType.SafeArray. Le type par défaut des éléments de
SAFEARRAY est présenté dans la table Personnaliser des champs object . Vous pouvez utiliser les champs
MarshalAsAttribute.SafeArraySubType et MarshalAsAttribute.SafeArrayUserDefinedSubType pour personnaliser
précisément le type d’élément de SAFEARRAY .

Personnaliser des paramètres booléens ou décimaux


Pour plus d’informations sur le marshaling des paramètres booléens ou décimaux, consultez Personnaliser le
marshaling des structures.

Personnaliser des paramètres d’objet (Windows uniquement)


Sous Windows, le runtime .NET propose plusieurs moyens de marshaler des paramètres d’objet en code natif.
Marshaling comme interfaces COM spécifiques
Si votre API accepte un pointeur vers un objet COM, vous pouvez utiliser l’un des formats UnmanagedType suivants
sur un paramètre de type object pour indiquer à .NET de marshaler comme ces interfaces spécifiques :
IUnknown
IDispatch
IInspectable

De plus, si votre type est marqué [ComVisible(true)] ou que vous marshalez le type object , vous pouvez utiliser
le format UnmanagedType.Interface pour marshaler votre objet comme wrapper appelable COM pour la vue COM
de votre type.
Marshaling vers un VARIANT

Si votre API native accepte une VARIANT Win32, vous pouvez utiliser le format UnmanagedType.Struct sur votre
object paramètre pour marshaler vos objets comme VARIANT . Pour connaître les correspondances entre les types
.NET et les types VARIANT , voir la documentation Personnaliser les champs object .
Marshaleurs personnalisés
Si vous voulez projeter une interface COM native dans un autre type managé, vous pouvez utiliser le format
UnmanagedType.CustomMarshaler et une implémentation ICustomMarshaler pour fournir votre propre code de
marshaling personnalisé.
Meilleures pratiques pour l’interopérabilité native
24/04/2020 • 20 minutes to read • Edit Online

.NET propose différents moyens de personnaliser du code d’interopérabilité native. Cet article détaille les instructions
suivies par les équipes .NET de Microsoft pour l’interopérabilité native.

Règle générale
Les instructions de cette section s’appliquent à tous les scénarios d’interopérabilité.
✔ À FAIRE : utilisez les mêmes noms et la même mise en majuscules pour vos méthodes que pour la méthode

native que vous souhaitez appeler.
️ À ENVISAGER : utiliser les mêmes noms et la même mise en majuscules pour les valeurs constantes.

️ À FAIRE : utilisez les types .NET les plus proches d’une correspondance avec le type natif. Par exemple, en C#,

utilisez uint lorsque le type natif est unsigned int .
️ À FAIRE : n’utilisez les attributs [In] et [Out] que si le comportement souhaité diffère du comportement par

défaut.
️ À ENVISAGER : utiliser System.Buffers.ArrayPool<T> pour regrouper les mémoires tampons du tableau natif.

️ À ENVISAGER : encapsuler les déclarations P/Invoke dans une classe portant le même nom et utilisant la même

mise en majuscules que votre bibliothèque native.
Vos attributs [DllImport] pourront ainsi utiliser la fonctionnalité nameof du langage C# pour transmettre le
nom de la bibliothèque native et vérifier qu’il ne comporte pas d’erreur.

Paramètres des attributs DllImport


PA RA M ÈT RE PA R DÉFA UT REC O M M A N DAT IO N DÉTA IL S

PreserveSig true Conserver la valeur par S’il est défini explicitement sur
défaut false, les valeurs de retour
HRESULT en échec sont
converties en exceptions (et la
valeur de retour de la
définition devient ainsi Null).

SetLastError false Dépend de l'API Définissez-le sur true si l’API


utilise GetLastError et
Marshal.GetLastWin32Error
pour obtenir la valeur. Si l’API
définit une condition
indiquant une erreur,
récupérez l’erreur avant
d’effectuer d’autres appels, de
façon à éviter de la remplacer
par inadvertance.
PA RA M ÈT RE PA R DÉFA UT REC O M M A N DAT IO N DÉTA IL S

CharSet CharSet.None , qui bascule Utilisez explicitement Cela permet de spécifier le


en secours vers le CharSet.Unicode ou comportement de marshaling
comportement CharSet.Ansi lorsque des des chaînes et le rôle de
CharSet.Ansi chaînes ou des caractères ExactSpelling quand la
sont présents dans la valeur est false . Notez que
définition CharSet.Ansi est en réalité
UTF-8 sur Unix. La plupart du
temps, Windows utilise
Unicode et Unix UTF-8. Pour
plus d'informations, voir la
documentation sur les
charsets.

ExactSpelling false true Définissez-le sur true pour


augmenter légèrement les
performances. En effet, le
runtime ne recherche pas de
noms de fonctions de
remplacement avec un suffixe
« A » ou « W » selon la valeur
du paramètre CharSet
(« A » pour CharSet.Ansi et
« W » pour
CharSet.Unicode ).

Paramètre de chaîne
Lorsque le charset est Unicode ou que l’argument est explicitement marqué comme
[MarshalAs(UnmanagedType.LPWSTR)] et que la chaîne est passée en valeur (pas ref ou out ), la chaîne est épinglée et
utilisée directement par le code natif (et non copiée).
N’oubliez pas de marquer [DllImport] comme Charset.Unicode sauf si vous souhaitez explicitement un traitement
ANSI de vos chaînes.
❌N’utilisez [Out] string pas de paramètres. Les paramètres de chaînes passés en valeur avec l’attribut [Out]
risquent de déstabiliser le runtime s’il s’agit de chaînes centralisées. Pour plus d’informations sur la centralisation des
chaînes, voir la documentation de String.Intern.
❌Évitez StringBuilder les paramètres. Le marshaling StringBuilder crée toujours une copie de la mémoire tampon
native. Il peut donc se révéler extrêmement inefficace. Prenons le scénario classique d’appel d’une API Windows qui
prend une chaîne :
1. Créer un SB de la capacité souhaitée (alloue la capacité managée){1}
2. Appeler
a. Alloue une mémoire tampon Native**{2}**
b. Copie le contenu si [In] (valeur par défaut d’un paramètre StringBuilder )
c. Copie la mémoire tampon native dans un tableau managé nouvellement alloué si [Out] {3} (valeur par
défaut de StringBuilder également)
3. ToString() alloue un autre groupe géré**{4}**

Il s' {4} agit des allocations pour obtenir une chaîne à partir du code natif. Le mieux que l’on puisse faire pour réduire ce
nombre est de réutiliser StringBuilder dans un autre appel, mais cela ne fait gagner que 1 allocation. Il est largement
préférable d’utiliser et de mettre en cache une mémoire tampon de caractères à partir de ArrayPool : vous pourrez
limiter l’allocation à ToString() lors des appels suivants.
L’autre problème de StringBuilder est qu’il copie toujours la sauvegarde de la mémoire tampon de retour sur la
première valeur Null. Si la chaîne de retour transmise n’est pas terminée ou se termine par un double Null, P/Invoke
est au mieux incorrect.
Si vous utilisez StringBuilder , le dernier piège est que la capacité n’inclut pas de valeur Null masquée, qui est toujours
prise en compte dans l’interopérabilité. Il est courant de se tromper de ce point de vue, car la plupart des API
demandent la taille de la mémoire tampon valeur Null incluse. Il en résulte parfois des allocations gaspillées/inutiles.
En outre, cet écueil empêche le runtime d’optimiser le marshaling StringBuilder afin de réduire les copies.
️ À ENVISAGER : utiliser des
✔ char[] à partir d’un ArrayPool .
Pour plus d’informations sur le marshaling des chaînes, consultez Marshaling par défaut pour les chaînes et
Personnaliser le marshaling des chaînes.

Spécifique à Windows Pour [Out] les chaînes que le CLR CoTaskMemFree utilisera par défaut pour libérer
SysStringFree des chaînes ou pour les chaînes UnmanagedType.BSTR qui sont marquées comme. Pour la plupar t
des API avec une mémoire tampon de chaîne de sor tie : Le nombre de caractères transmis doit inclure la
valeur null. Si la valeur de retour est inférieure au nombre de caractères transmis, c’est le signe que l’appel a réussi
et que la valeur correspond au nombre de caractères sans la valeur Null de fin. Sinon, le nombre représente la taille
requise de la mémoire tampon caractère Null inclus.
Pass in 5, obtenir 4 : la chaîne comporte 4 caractères avec une valeur null de fin.
Pass in 5, obtenir 6 : la chaîne comporte 5 caractères, nécessite une mémoire tampon de 6 caractères pour
contenir la valeur null. Types de données Windows pour les chaînes

Paramètres et champs booléens


Il est facile de se perdre avec les booléens. Par défaut, un bool .NET est marshalé en un BOOL Windows, où il s’agit
d’une valeur de 4 octets. À l’inverse, les types _Bool et bool en C et C++ correspondent à un seul octet. Il peut alors
être difficile de traquer les bogues, car la valeur de retour est à moitié ignorée, ce qui ne modifie que potentiellement le
résultat. Pour plus d’informations sur le marshaling des valeurs bool .NET en types bool C ou C++, consultez la
documentation Personnaliser le marshaling des champs booléens.

GUID
Les GUID sont directement utilisables dans les signatures. De nombreuses API Windows acceptent des alias de type
GUID& comme REFIID , qui, s’ils sont passés en référence, peuvent être transmis par ref ou avec l’attribut
[MarshalAs(UnmanagedType.LPStruct)] .

GUID GUID EN RÉF ÉREN C E

KNOWNFOLDERID REFKNOWNFOLDERID

❌N’utilisez [MarshalAs(UnmanagedType.LPStruct)] pas pour les paramètres de ref GUID.

Types blittables
Les types blittables sont des types qui ont la même représentation au niveau du bit dans le code managé et dans le
code natif. Il n’est donc pas nécessaire de les convertir dans un autre format pour les marshaler vers et à partir du code
natif. Ils sont à privilégier en raison de l’amélioration des performances qui en résulte.
Types blittables :
byte , sbyte , short , ushort , int , uint , long , ulong , single , double
Tableaux unidimensionnels non imbriqués de types blittables (par exemple, int[] )
Structs et classes à disposition fixe qui n’ont que des types valeurs blittables, par exemple les champs :
La disposition fixe exige [StructLayout(LayoutKind.Sequential)] ou [StructLayout(LayoutKind.Explicit)] .
Les structs sont LayoutKind.Sequential par défaut, les classes LayoutKind.Auto .

Types NON blittables :


bool

Types PARFOIS blittables :


char , string

Lorsque des types blittables sont passés en référence, ils sont simplement épinglés par le marshaleur, et non copiés
vers une mémoire tampon intermédiaire. (Les classes sont, par nature, passées en référence ; les structs sont passés en
référence lorsqu’ils sont utilisés avec ref ou out .)
Un est blittable dans un tableau unidimensionnel ou s’il fait partie d’un type explicitement marqué
char
[StructLayout] avec CharSet = CharSet.Unicode .

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]


public struct UnicodeCharStruct
{
public char c;
}

Une est blittable si elle n’est pas contenue dans un autre type et qu’elle est passé en argument marqué
string
[MarshalAs(UnmanagedType.LPWStr)] ou que CharSet = CharSet.Unicode est défini pour [DllImport] .

Pour savoir si un type est blittable, essayez de créer un GCHandle épinglé. Si le type n’est pas une chaîne ou n’est pas
considéré comme blittable, GCHandle.Alloc lèvera une ArgumentException .

️ À FAIRE : rendez vos structures blittables dans la mesure du possible.



Pour plus d’informations, voir :
types blittable et non blittable
Marshaling de type

Maintenir actifs les objets gérés


GC.KeepAlive() garantit qu'un objet reste accessible jusqu'à ce que la méthode KeepAlive soit atteinte.
HandleRef permet au marshaleur de conserver un objet actif pendant la durée d’un appel P/Invoke. Il peut être utilisé à
la place de IntPtr dans les signatures de méthode. SafeHandle remplace cette classe et doit être utilisé à la place.
GCHandle autorise l’épinglage d’un objet managé et l’obtention du pointeur natif vers celui-ci. En voici le modèle de
base :

GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Pinned);


IntPtr ptr = handle.AddrOfPinnedObject();
handle.Free();

L’épinglage n’est pas le comportement par défaut de GCHandle . L’autre grand modèle sert à faire passer une référence
à un objet géré à travers le code natif, puis à nouveau au code managé, en général avec un rappel. En voici le modèle :
GCHandle handle = GCHandle.Alloc(obj);
SomeNativeEnumerator(callbackDelegate, GCHandle.ToIntPtr(handle));

// In the callback
GCHandle handle = GCHandle.FromIntPtr(param);
object managedObject = handle.Target;

// After the last callback


handle.Free();

N’oubliez pas que GCHandle doit être explicitement libéré pour éviter les fuites de mémoire.

Types de données Windows courants


Voici la liste des types de données courants dans les API Windows et des types C# à utiliser pour les appels dans le
code Windows.
Malgré leur nom, les types suivants ont la même taille sur Windows 32 bits et 64 bits.

L A RGEUR W IN DO W S C ( W IN DO W S) C# A LT ERN AT IVE

32 BOOL int int bool

8 BOOLEAN unsigned char byte [MarshalAs(UnmanagedType.U1)]


bool

8 BYTE unsigned char byte

8 CHAR char sbyte

8 UCHAR unsigned char byte

16 SHORT short short

16 CSHORT short short

16 USHORT unsigned short ushort

16 WORD unsigned short ushort

16 ATOM unsigned short ushort

32 INT int int

32 LONG long int

32 ULONG unsigned long uint

32 DWORD unsigned long uint

64 QWORD long long long

64 LARGE_INTEGER long long long

64 LONGLONG long long long


L A RGEUR W IN DO W S C ( W IN DO W S) C# A LT ERN AT IVE

64 ULONGLONG unsigned long long ulong

64 ULARGE_INTEGER unsigned long long ulong

32 HRESULT long int

32 NTSTATUS long int

Comme il s’agit de pointeurs, les types suivants suivent la largeur de la plateforme. Utilisez pour eux IntPtr / UIntPtr .

T Y P ES DE P O IN T EURS SIGN ÉS ( UT IL ISEZ INTPTR ) T Y P ES DE P O IN T EURS N O N SIGN ÉS ( UT IL ISEZ UINTPTR )

HANDLE WPARAM

HWND UINT_PTR

HINSTANCE ULONG_PTR

LPARAM SIZE_T

LRESULT

LONG_PTR

INT_PTR

Un PVOID Windows, qui correspond à un void* C, peut être marshalé comme IntPtr ou UIntPtr ; préférez void*
dans la mesure du possible.
Types de données Windows
Plages de types de données

Structs
Les structs managés sont créés sur la pile et ne sont pas supprimés tant que la méthode n’a pas retourné de valeur. Ils
sont donc par définition « épinglés » (ils ne sont pas déplacés par le récupérateur de mémoire). Vous pouvez aussi
prendre simplement l’adresse dans des blocs de code unsafe si le code natif n’utilise pas le pointeur après la fin de la
méthode actuelle.
Les structs blittables sont beaucoup plus performants, car ils sont directement utilisables par la couche de marshaling.
Essayez de rendre les structs blittables (par exemple, évitez bool ). Pour plus d'informations, voir la section Types
blittables.
Si le struct est blittable, utilisez sizeof() au lieu de Marshal.SizeOf<MyStruct>() pour obtenir de meilleures
performances. Comme nous l’avons mentionné plus haut, vous pouvez valider que le type est blittable en essayant de
créer un GCHandle épinglé. Si le type n’est pas une chaîne ou n’est pas considéré comme blittable, GCHandle.Alloc
lèvera une ArgumentException .
Les pointeurs vers des structs dans les définitions doivent être passés en ref ou utiliser unsafe et * .
✔ À FAIRE : faites correspondre le struct managé aussi fidèlement que possible à la forme et aux noms utilisés dans

l’en-tête ou dans la documentation officielle de la plateforme.
✔ À FAIRE : utilisez le sizeof() C# au lieu de
️ Marshal.SizeOf<MyStruct>() pour les structures blittables afin
d’améliorer les performances.
❌Évitez d' System.Delegate utiliser System.MulticastDelegate des champs ou pour représenter des champs de
pointeur de fonction dans des structures.
Étant System.Delegate donné System.MulticastDelegate que et n’ont pas de signature obligatoire, elles ne garantissent
pas que le délégué passé correspond à la signature attendue par le code natif. En outre, dans .NET Framework et .NET
Core, le marshaling d’un struct System.Delegate contenant System.MulticastDelegate un ou à partir de sa
représentation native vers un objet managé peut déstabiliser le runtime si la valeur du champ dans la représentation
native n’est pas un pointeur de fonction qui encapsule un délégué managé. Dans .NET 5 et versions ultérieures, le
marshaling d’un System.Delegate champ ou System.MulticastDelegate d’une représentation native vers un objet
managé n’est pas pris en charge. Utilisez un type délégué spécifique au lieu System.Delegate de
System.MulticastDelegate ou.

Mémoires tampons fixes


Un tableau comme INT_PTR Reserved1[2] doit être marshalé en deux champs IntPtr , Reserved1a et Reserved1b .
Lorsque le tableau natif est un type primitif, il est possible d’utiliser le mot clé fixed pour que le code soit un peu plus
propre. Par exemple, SYSTEM_PROCESS_INFORMATION se présente ainsi dans l’en-tête natif :

typedef struct _SYSTEM_PROCESS_INFORMATION {


ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
UNICODE_STRING ImageName;
...
} SYSTEM_PROCESS_INFORMATION

On peut l’écrire ainsi en C# :

internal unsafe struct SYSTEM_PROCESS_INFORMATION


{
internal uint NextEntryOffset;
internal uint NumberOfThreads;
private fixed byte Reserved1[48];
internal Interop.UNICODE_STRING ImageName;
...
}

Toutefois, les mémoires tampons fixes présentent quelques pièges. Les mémoires tampons fixes de types non blittables
ne sont pas marshalées correctement ; par conséquent, le tableau sur place doit être étendu à plusieurs champs
différents. En outre, dans .NET Framework et .NET Core avant la version 3.0, si un struct contenant un champ de
mémoire tampon fixe est imbriqué dans un struct non blittable, le champ de mémoire tampon fixe n’est pas marshalé
correctement dans le code natif.
Charsets et marshaling
18/07/2020 • 2 minutes to read • Edit Online

La façon dont les valeurs char et les objets string et System.Text.StringBuilder sont marshalés dépend de la
valeur du champ CharSet sur P/Invoke ou la structure. Pour définir le CharSet d’un P/Invoke, définissez le
champ DllImportAttribute.CharSet lorsque vous déclarez votre P/Invoke. Pour définir le CharSet pour un type,
définissez le StructLayoutAttribute.CharSet champ sur votre déclaration de classe ou de struct. Lorsque ces
champs d’attribut ne sont pas définis, c’est au compilateur du langage de déterminer quel CharSet utiliser. C# et
Visual Basic utilisent le Ansi jeu de caractères par défaut.
Le tableau suivant présente la correspondance entre chaque charset et la représentation d’un caractère ou d’une
chaîne après marshaling avec ce charset :

. N ET C O RE 3. 0 ET
. N ET C O RE 2. 2 ET ULT ÉRIEUR ET M O N O SUR
VA L EUR CHARSET W IN DO W S A N T ÉRIEUR SUR UN IX UN IX

Ansi char (page de codes char (UTF-8) char (UTF-8)


Windows (ANSI) du système
par défaut)

Unicode wchar_t (UTF-16) char16_t (UTF-16) char16_t (UTF-16)

Auto wchar_t (UTF-16) char16_t (UTF-16) char (UTF-8)

Lorsque vous choisissez votre charset, renseignez-vous sur la représentation native attendue.
COM Interop dans .NET
24/04/2020 • 2 minutes to read • Edit Online

Le modèle COM (Component Object Model) permet à un objet d’exposer ses fonctionnalités à d’autres composants
et d’héberger des applications sur les plateformes Windows. Pour permettre aux utilisateurs d’interagir avec leurs
bases de code existantes, le .NET Framework a toujours fourni une prise en charge renforcée de l’interopérabilité
avec les bibliothèques COM. Dans .NET Core 3.0, une grande partie de cette prise en charge a été ajoutée à .NET
Core sur Windows. Cette documentation explique comment les technologies de COM Interop courantes
fonctionnent et comment vous pouvez les utiliser pour interagir avec vos bibliothèques COM existantes.
Wrappers COM
Wrappers CCW (COM Callable Wrapper)
Wrappers RCW (Runtime Callable Wrapper)
Qualification des types .NET en vue de l’interopérabilité COM
Wrappers COM
20/05/2020 • 3 minutes to read • Edit Online

COM diffère du modèle objet de runtime .NET sur plusieurs points importants :
Les clients des objets COM doivent gérer la durée de vie de ces objets ; le common language runtime gère
la durée de vie des objets dans son environnement.
Les clients des objets COM déterminent si un service est disponible en demandant une interface qui fournit
ce service et en récupérant le cas échéant un pointeur d’interface en retour. Les clients des objets .NET
peuvent obtenir une description de la fonctionnalité d’un objet par l’intermédiaire de la réflexion.
Les objets NET résident dans la mémoire managée par l’environnement d’exécution du runtime .NET. Celui-ci
peut déplacer des objets dans la mémoire en vue d’améliorer les performances et mettre à jour toutes les
références aux objets qu’il déplace. Les clients non managés, ayant obtenu un pointeur désignant un objet,
s’attendent à ce que cet objet reste au même emplacement. Ces clients ne disposent d’aucun mécanisme
leur permettant d’utiliser un objet dont l’emplacement n’est pas fixe.
Pour surmonter ces différences, le runtime fournit des classes wrapper pour faire croire aux clients à la fois
managés et non managés qu’ils appellent des objets dans leur environnement respectif. Chaque fois que votre
client managé appelle une méthode sur un objet COM, le runtime crée un wrapper RCW (Runtime Callable
Wrapper). Les wrappers RCW permettent, entre autres, de gommer les différences entre les mécanismes de
référence managé et non managé. Le runtime crée également un wrapper CCW (COM Callable Wrapper) pour
inverser le processus, permettant ainsi à un client COM d’appeler de façon transparente une méthode sur un objet
.NET. Ainsi que le montre l’illustration suivante, la perspective du code appelant détermine la classe wrapper créée
par le runtime.

Dans la plupart des cas, les wrappers RCW ou CCW standard générés par le runtime assurent un marshaling
adéquat pour les appels qui franchissent la limite séparant COM du runtime .NET. En utilisant des attributs
personnalisés, vous pouvez facultativement définir la façon dont le runtime représente le code managé et non
managé.

Voir aussi
Interopérabilité COM avancée dans .NET Framework
Wrapper pouvant être appelé par le runtime
Wrapper CCW (COM Callable Wrapper)
Personnalisation de wrappers standard dans .NET Framework
Comment : personnaliser des wrappers pouvant être appelés par le runtime dans .NET Framework
Wrapper pouvant être appelé par le runtime
20/05/2020 • 7 minutes to read • Edit Online

Le common language runtime expose les objets COM via un proxy appelé wrapper RCW. Même si le
wrapper RCW est un objet ordinaire pour les clients .NET, sa fonction principale est de marshaler les appels entre
un client .NET et un objet COM.
Le runtime crée un wrapper RCW pour chaque objet COM, quel que soit le nombre de références qui existent sur
cet objet. Le runtime gère un seul wrapper RCW par processus pour chaque objet. Si vous créez un wrapper RCW
dans un domaine d'application ou cloisonnement, puis passez une référence à un autre domaine d'application ou
cloisonnement, un proxy du premier objet sera utilisé. Comme le montre l'illustration suivante, il n'existe pas de
limite au nombre de clients managés pouvant contenir une référence aux objets COM qui exposent les interfaces
INew et INewer.
L’image suivante montre le processus d’accès aux objets COM par le biais du wrapper RCW :

À l'aide de métadonnées dérivées d'une bibliothèque de types, le runtime crée l'objet COM appelé, ainsi qu'un
wrapper pour celui-ci. Chaque wrapper RCW gère un cache de pointeurs d'interface sur l'objet COM qu'il
encapsule et libère sa référence à l'objet COM quand le wrapper RCW n'est plus utile. Le runtime exécute le
garbage collection du wrapper RCW.
Entre autres activités, le wrapper RCW marshale les données entre code managé et non managé, pour le compte
de l’objet encapsulé. Plus précisément, le wrapper RCW fournit le marshaling pour les arguments de méthode et
les valeurs de retour de méthode chaque fois que le client et le serveur ont des représentations différentes des
données circulant entre eux.
Le wrapper standard applique les règles de marshaling intégrées. Par exemple, quand un client .NET passe un type
String dans le cadre d’un argument à un objet non managé, le wrapper convertit la chaîne en un type BSTR. Si
l'objet COM retourne un BSTR à son appelant managé, l'appelant reçoit une chaîne (String). Le client et le serveur
envoient et reçoivent des données qui leur sont familières. Les autres types ne nécessitent pas de conversion. Par
exemple, un wrapper standard passera toujours un entier de 4 octets d'un code managé à un code non managé
sans convertir le type.

Marshaling d’interfaces sélectionnées


L’objectif principal du wrapper RCW est de masquer les différences entre les modèles de programmation managé
et non managé. Pour créer une transition transparente, le wrapper RCW consomme les interfaces COM
sélectionnées sans les exposer au client .NET, comme indiqué dans l'illustration suivante.
L’image suivante montre les interfaces COM et le wrapper RCW :
Quand il est créé comme un objet à liaison anticipée, le wrapper RCW est un type spécifique. Il implémente les
interfaces que l'objet COM implémente et expose les méthodes, les propriétés et les événements des interfaces de
l'objet. Dans l’illustration, le wrapper RCW expose l’interface INew, mais consomme les interfaces IUnknown et
IDispatch . De plus, le wrapper RCW expose tous les membres de l'interface INew au client .NET.
Le wrapper RCW consomme les interfaces répertoriées dans le tableau suivant, qui sont exposées par l'objet qu'il
encapsule.

IN T ERFA C E DESC RIP T IO N

IDispatch Pour la liaison tardive aux objets COM via la réflexion.

IErrorInfo Fournit une description textuelle de l’erreur, sa source, un


fichier d’aide, un contexte d’aide et le GUID de l’interface
ayant défini l’erreur (toujours GUID_NULL pour les classes
.NET).

IProvideClassInfo Si l’objet COM qui est encapsulé implémente


IProvideClassInfo , le wrapper RCW extrait les informations
de type à partir de cette interface pour fournir une meilleure
identité de type.

IUnknown Pour l'identité de l'objet, le forçage de type et la gestion de la


durée de vie :

- Identité d’un objet


Le runtime fait la distinction entre les objets COM en
comparant la valeur d’interface IUnknown pour chaque
objet.
- Forçage de type
Le wrapper RCW reconnaît la découverte de type dynamique
exécutée par la méthode Quer yInterface .
- Gestion de la durée de vie
À l’aide de la méthode Quer yInterface , le wrapper RCW
obtient et conserve une référence à un objet non managé
jusqu’à ce que le runtime exécute le garbage collection sur le
wrapper, qui libère l’objet non managé.

Le wrapper RCW peut éventuellement consommer les interfaces répertoriées dans le tableau suivant, qui sont
exposées par l'objet qu'il encapsule.

IN T ERFA C E DESC RIP T IO N

IConnectionPoint et IConnectionPointContainer Le wrapper RCW convertit les objets qui exposent le style
d'événement point de connexion en événements basés sur le
délégué.
IN T ERFA C E DESC RIP T IO N

IDispatchEx (.NET Framework uniquement) Si la classe implémente IDispatchEx, le wrapper RCW


implémente IExpando . L’interface IDispatchEx est une
extension de l’interface IDispatch qui, contrairement à
l’interface IDispatch , permet l’énumération, l’ajout, la
suppression et l’appel de la casse des membres.

IEnumVARIANT Permet aux types COM qui prennent en charge les


énumérations d'être traités comme des collections.

Voir aussi
Wrappers COM
Wrapper CCW (COM Callable Wrapper)
Récapitulatif de la conversion d’une bibliothèque de types en assembly
Importation d'une bibliothèque de types sous la forme d'un assembly
Wrapper pouvant être appelé par COM
20/05/2020 • 17 minutes to read • Edit Online

Quand un client COM appelle un objet .NET, le common language runtime crée l'objet managé et un
wrapper CCW pour cet objet. Parce qu'ils ne peuvent pas référencer directement un objet .NET, les clients COM
utilisent le wrapper CCW en tant que proxy pour l'objet managé.
Le runtime crée un unique wrapper CCW pour un objet managé, indépendamment du nombre de clients COM
demandant ses services. Comme le montre l'illustration suivante, plusieurs clients COM peuvent contenir une
référence au wrapper CCW qui expose l'interface INew. Le wrapper CCW contient quant à lui une référence à
l'objet managé qui implémente l'interface et fait l'objet d'un garbage collection. Les clients COM et .NET peuvent
envoyer simultanément des requêtes au même objet managé.

Les wrappers CCW ne sont pas visibles par les autres classes qui s’exécutent dans le runtime .NET. Leur objectif
principal est de marshaler les appels entre code managé et code non managé. Cependant, les wrappers CCW
gèrent également l'identité et la durée de vie des objets managés qu'ils encapsulent.

Identité d'un objet


Le runtime alloue de la mémoire pour l'objet .NET depuis son tas collecté, ce qui permet au runtime de déplacer
l'objet au sein de la mémoire selon les besoins. En revanche, le runtime alloue de la mémoire pour le
wrapper CCW depuis un tas non collecté, permettant ainsi aux clients COM de faire directement référence au
wrapper.

Durée de vie des objets


Contrairement au client .NET qu'il encapsule, le wrapper CCW fait l'objet d'un comptage de références comme
dans COM. Quand le nombre de références du wrapper CCW atteint zéro, le wrapper libère sa référence à l'objet
managé. Un objet managé sans aucune référence restante sera collecté lors du prochain cycle de garbage
collection.

Simulation d'interfaces COM


CCW expose aux clients COM l’ensemble des interfaces, types de données et valeurs de retour publics et visibles
par COM d’une manière qui est cohérente avec l’application de COM de l’interaction reposant sur l’interface. Pour
un client COM, l’appel de méthodes sur un objet .NET est identique à l’appel de méthodes sur un objet COM.
Pour adopter cette approche transparente, le wrapper CCW fabrique des interfaces COM classiques, comme
IUnknown et IDispatch . Comme le montre l'illustration suivante, le wrapper CCW contient une référence unique
sur l'objet .NET qu'il encapsule. Le client COM et l’objet .NET interagissent via le proxy et la construction stub du
wrapper CCW.
Outre l’exposition des interfaces qui sont implémentées explicitement par une classe dans l’environnement
managé, le runtime .NET fournit également des implémentations des interfaces COM répertoriées dans le tableau
suivant, pour le compte de l’objet. Une classe .NET peut substituer le comportement par défaut par sa propre
implémentation de ces interfaces. Toutefois, le runtime fournit toujours l’implémentation pour les interfaces
IUnknown et IDispatch .

IN T ERFA C E DESC RIP T IO N

IDispatch Fournit un mécanisme de liaison tardive au type.

IErrorInfo Fournit une description textuelle de l’erreur, sa source, un


fichier d’aide, un contexte d’aide et le GUID de l’interface
ayant défini l’erreur (toujours GUID_NULL pour les classes
.NET).

IProvideClassInfo Permet aux clients COM d’accéder à l’interface ITypeInfo


implémentée par une classe managée. Retourne
COR_E_NOTSUPPORTED sur .NET Core pour les types non
importés à partir de COM.

ISuppor tErrorInfo Permet à un client COM de déterminer si l’objet managé


prend en charge l’interface IErrorInfo . Dans ce cas, il permet
au client d'obtenir un pointeur vers le dernier objet exception.
Tous les types managés prennent en charge l’interface
IErrorInfo .

ITypeInfo (.NET Framework uniquement) Fournit des informations de type pour une classe qui sont les
mêmes que celles fournies par Tlbexp.exe.

IUnknown Fournit l’implémentation standard de l’interface IUnknown


avec laquelle le client COM gère la durée de vie du
wrapper CCW et fournit le forçage de type.

Une classe managée peut également fournir les interfaces COM décrites dans le tableau suivant.

IN T ERFA C E DESC RIP T IO N

L’interface de classe ( _ className) Interface, exposée par le runtime et non définie explicitement,
qui expose l'ensemble des interfaces, méthodes, propriétés et
champs publics qui sont exposés explicitement sur un objet
managé.

IConnectionPoint et IConnectionPointContainer Interface pour les objets qui émettent des événements basés
sur les délégués (interface pour l'inscription des abonnés
d'événements).
IN T ERFA C E DESC RIP T IO N

IDispatchEx (.NET Framework uniquement) Interface fournie par le runtime si la classe implémente
IExpando . L’interface IDispatchEx est une extension de
l’interface IDispatch qui, contrairement à l’interface
IDispatch , permet l’énumération, l’ajout, la suppression et
l’appel de la casse des membres.

IEnumVARIANT Interface pour les classes de type collection, qui énumère les
objets d’une collection si la classe implémente IEnumerable .

Présentation de l'interface de classe


L'interface de classe, qui n'est pas explicitement définie dans le code managé, est une interface qui expose
l'ensemble des méthodes, propriétés, champs et événements publics qui sont exposés explicitement sur
l'objet .NET. Cette interface peut être double ou dispatch uniquement. L'interface de classe reçoit le nom de la
classe .NET, précédé d'un trait de soulignement. Par exemple, pour la classe Mammal, l’interface de classe est
_Mammal.
Pour les classes dérivées, l'interface de classe expose l'ensemble des méthodes, propriétés et champs publics de la
classe de base. La classe dérivée expose également une interface de classe pour chaque classe de base. Par
exemple, si la classe Mammal étend la classe MammalSuperclass, qui elle-même étend System.Object, l’objet .NET
expose aux clients COM trois interfaces de classe nommées _Mammal, _MammalSuperclass et _Object.
Regardons, par exemple, la classe .NET suivante :

' Applies the ClassInterfaceAttribute to set the interface to dual.


<ClassInterface(ClassInterfaceType.AutoDual)> _
' Implicitly extends System.Object.
Public Class Mammal
Sub Eat()
Sub Breathe()
Sub Sleep()
End Class

// Applies the ClassInterfaceAttribute to set the interface to dual.


[ClassInterface(ClassInterfaceType.AutoDual)]
// Implicitly extends System.Object.
public class Mammal
{
public void Eat() {}
public void Breathe() {}
public void Sleep() {}
}

Le client COM peut obtenir un pointeur vers une interface de classe nommée _Mammal . Sur .NET Framework, vous
pouvez utiliser l’outil Exportateur de bibliothèques de types (Tlbexp. exe) pour générer une bibliothèque de types
contenant la définition de l’interface _Mammal . L’exportateur de bibliothèques de types n’est pas pris en charge sur
.NET Core. Si la classe Mammal implémentait une ou plusieurs interfaces, les interfaces apparaîtraient sous la
coclasse.
[odl, uuid(…), hidden, dual, nonextensible, oleautomation]
interface _Mammal : IDispatch
{
[id(0x00000000), propget] HRESULT ToString([out, retval] BSTR*
pRetVal);
[id(0x60020001)] HRESULT Equals([in] VARIANT obj, [out, retval]
VARIANT_BOOL* pRetVal);
[id(0x60020002)] HRESULT GetHashCode([out, retval] short* pRetVal);
[id(0x60020003)] HRESULT GetType([out, retval] _Type** pRetVal);
[id(0x6002000d)] HRESULT Eat();
[id(0x6002000e)] HRESULT Breathe();
[id(0x6002000f)] HRESULT Sleep();
}
[uuid(…)]
coclass Mammal
{
[default] interface _Mammal;
}

La génération de l'interface de classe est facultative. Par défaut, COM Interop génère une interface de dispatch
pour chaque classe que vous exportez vers une bibliothèque de types. Vous pouvez empêcher ou modifier la
création automatique de cette interface en appliquant ClassInterfaceAttribute à votre classe. Même si l'interface de
classe simplifie l'exposition des classes managées à COM, ses utilisations sont limitées.
Cau t i on

L'utilisation de l'interface de classe, au lieu de définir explicitement la vôtre, peut compliquer le contrôle de version
de votre classe managée. Lisez les instructions suivantes avant d'utiliser l'interface de classe.
Définissez une interface explicite pour les clients COM plutôt que de générer l'interface de classe.
Étant donné que COM Interop génère automatiquement une interface de classe, les modifications après version
apportées à votre classe peuvent modifier la disposition de l'interface de classe exposée par le common language
runtime. Étant donné que les clients COM ne sont généralement pas préparés à gérer les modifications apportées
à la disposition d'une interface, ils s'arrêtent si vous modifiez la disposition des membres de la classe.
Cette règle renforce l'idée que les interfaces exposées aux clients COM doivent rester intactes. Pour réduire le
risque d'arrêt des clients COM en réorganisant par inadvertance la disposition de l'interface, isolez toutes les
modifications apportées à la classe à partir de la disposition de l'interface en définissant explicitement les
interfaces.
Utilisez ClassInterfaceAttribute pour désactiver la génération automatique de l’interface de classe et
implémenter une interface explicite de la classe, comme dans le fragment de code suivant :

<ClassInterface(ClassInterfaceType.None)>Public Class LoanApp


Implements IExplicit
Sub M() Implements IExplicit.M

End Class

[ClassInterface(ClassInterfaceType.None)]
public class LoanApp : IExplicit
{
int IExplicit.M() { return 0; }
}

La valeur ClassInterfaceType.None empêche l’interface de classe d’être générée quand les métadonnées de
classe sont exportées vers une bibliothèque de types. Dans l'exemple précédent, les clients COM peuvent accéder
à la classe LoanApp uniquement via l'interface IExplicit .
Éviter de mettre en cache des identificateurs de dispatch (DISPID)
L'utilisation de l'interface de classe est une option acceptable pour les clients par script, les clients Microsoft Visual
Basic 6.0 ou tout client à liaison tardive qui ne met pas en cache les DISPID des membres d'interface. Les DISPID
identifient les membres d’interface pour permettre la liaison tardive.
Pour l'interface de classe, la génération de DISPID repose sur la position du membre de l'interface. Si vous
modifiez l'ordre du membre et exportez la classe vers une bibliothèque de types, vous modifierez les DISPID
générés dans l'interface de classe.
Pour éviter l’arrêt des clients COM à liaison tardive lors de l’utilisation de l’interface de classe, appliquez
ClassInterfaceAttribute avec la valeur ClassInterfaceType.AutoDispatch . Cette valeur implémente une
interface de classe de dispatch, mais omet la description de l'interface de la bibliothèque de types. Sans
description d'interface, les clients sont incapables de mettre en cache les DISPID au moment de la compilation.
Même s'il s'agit du type d'interface par défaut pour l'interface de classe, vous pouvez appliquer explicitement la
valeur d'attribut.

<ClassInterface(ClassInterfaceType.AutoDispatch)> Public Class LoanApp


Implements IAnother
Sub M() Implements IAnother.M

End Class

[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class LoanApp
{
public int M() { return 0; }
}

Pour obtenir le DISPID d’un membre d’interface au moment de l’exécution, les clients COM peuvent appeler
IDispatch.GetIdsOfNames . Pour appeler une méthode sur l’interface, passez le DISPID retourné comme
argument à IDispatch.Invoke .
Limitez l'utilisation de l'option d'interface double pour l'interface de classe.
Les interfaces doubles permettent une liaison anticipée et tardive aux membres d’interface par les clients COM. Au
moment du design et au cours des tests, il peut s'avérer utile de faire de l'interface de classe une interface double.
Pour une classe managée (et ses classes de base) qui ne sera jamais modifiée, cette option est également
acceptable. Dans tous les autres cas, évitez d'utiliser l'interface double.
Une interface double générée automatiquement peut être appropriée dans de rares cas. Toutefois, cela rend
souvent le contrôle de version plus complexe. Par exemple, les clients COM qui utilisent l'interface de classe d'une
classe dérivée peuvent facilement s'arrêter en raison des modifications apportées à la classe de base. Quand une
tierce partie fournit la classe de base, la disposition de l'interface de classe est hors de votre contrôle. De plus,
contrairement à une interface Dispatch uniquement, une interface Dual (ClassInterfaceType.AutoDual ) offre
une description de l’interface de classe dans la bibliothèque de types exportée. Une telle description encourage les
clients à liaison tardive à mettre en cache les DISPID au moment de la compilation.
Vérifiez que toutes les notifications d’événements COM sont à liaison tardive.
Par défaut, les informations du type COM sont intégrées directement aux assemblys managés, ce qui éviter d’avoir
à utiliser des assemblys PIA (Primary Interop Assembly). Les informations de type incorporées présentent
toutefois une limitation : elles ne prennent pas en charge la distribution des notifications d’événements COM par
des appels vtable à liaison anticipée, mais seulement par des appels IDispatch::Invoke à liaison tardive.
Si votre application exige des appels à liaison anticipée à des méthodes d’interface d’événements COM, vous
pouvez définir la propriété Incorporer les types Interop sur true dans Visual Studio, ou inclure l’élément
suivant dans votre fichier projet :
<EmbedInteropTypes>True</EmbedInteropTypes>

Voir aussi
ClassInterfaceAttribute
Wrappers COM
Exposition de composants .NET Framework à COM
Exposition de composants .NET Core à COM
Qualification des types .NET pour l’interopérabilité
Wrapper pouvant être appelé par le runtime
Qualification des types .NET en vue de
l’interopérabilité COM
18/07/2020 • 4 minutes to read • Edit Online

Si vous envisagez d’exposer les types d’un assembly à des applications COM, prenez en compte les exigences
COM Interop au moment de la conception. Les types managés (classe, interface, structure et énumération)
s’intègrent parfaitement aux types COM lorsque vous respectez les consignes suivantes :
Les classes doivent implémenter les interfaces de manière explicite.
Même si COM Interop fournit un mécanisme permettant de générer automatiquement une interface
contenant tous les membres de la classe et de sa classe de base, il est fortement recommandé de fournir
des interfaces explicites. L’interface générée automatiquement est appelée « interface de classe ». Pour
obtenir des instructions, consultez Présentation de l’interface de classe.
Vous pouvez utiliser Visual Basic, C# et C++ pour incorporer des définitions d’interface dans votre code, au
lieu du langage IDL (ou équivalent). Pour plus d’informations sur la syntaxe, consultez la documentation
relative à votre langage.
Les types managés doivent être publics.
Seuls les types publics d’un assembly sont inscrits et exportés vers la bibliothèque de types. Par conséquent,
seuls les types publics sont visibles par COM.
Les types managés exposent des fonctionnalités à un autre code managé qui peut ne pas être exposé à
COM. Par exemple, les constructeurs paramétrables, les méthodes statiques et les champs constants ne sont
pas exposés aux clients COM. De plus, comme le runtime marshale les données d’un type à un autre, les
données peuvent être copiées ou transformées.
Les méthodes, les propriétés, les champs et les événements doivent être publics.
Les membres des types publics doivent également être publics pour être visibles par COM. Vous pouvez
restreindre la visibilité d’un assembly, d’un type public ou de membres publics d’un type public en
appliquant ComVisibleAttribute. Par défaut, tous les membres et types publics sont visibles.
Les types doivent avoir un constructeur sans paramètre public pour être activés dans COM.
Les types publics managés sont visibles par COM. Toutefois, sans constructeur public sans paramètre
(constructeur sans arguments), les clients COM ne peuvent pas créer le type. Les clients COM peuvent
toujours utiliser le type s’il est activé par d’autres moyens.
Les types ne peuvent pas être abstraits.
Ni les clients COM ni les clients .NET ne peuvent créer de types abstraits.
Lors de l’exportation vers COM, la hiérarchie d’héritage d’un type managé est aplatie. Le contrôle de version est
également différent dans les environnements managés et non managés. Les types exposés à COM n’ont pas les
mêmes caractéristiques de contrôle de version que les autres types managés.

Voir aussi
ComVisibleAttribute
Exposition de composants .NET Framework à COM
Présentation de l'interface de classe
Application d’attributs d’interopérabilité
Empaquetage d’un assembly .NET Framework pour COM
Application d'attributs d'interopérabilité
18/07/2020 • 9 minutes to read • Edit Online

L’espace de noms System.Runtime.InteropServices propose trois catégories d’attributs spécifiques à


l’interopérabilité : ceux que vous appliquez au moment du design, ceux que les interfaces API et les outils de COM
Interop appliquent au cours du processus de conversion et ceux que vous appliquez ou que COM Interop applique.
Si vous n’êtes pas habitué aux tâches relatives à l’application d’attributs à du code managé, consultez Extension des
métadonnées à l’aide des attributs. Comme pour les autres attributs personnalisés, vous pouvez appliquer des
attributs spécifiques à l’interopérabilité à des types, à des méthodes, à des paramètres, à des propriétés, à des
champs et à d’autres membres.

Attributs au moment du design


Vous pouvez ajuster le résultat du processus de conversion effectué par les interfaces API et les outils de COM
Interop à l’aide d’attributs au moment du design. Le tableau suivant décrit les attributs que vous pouvez appliquer à
votre code source managé. Les outils de COM Interop peuvent également parfois appliquer les attributs décrits
dans ce tableau.

AT T RIB UT DESC RIP T IO N

AutomationProxyAttribute Indique si le type doit être marshalé en utilisant Automation


Marshaler ou un proxy et un stub personnalisés.

ClassInterfaceAttribute Contrôle le type d’interface généré pour une classe.

CoClassAttribute Identifie le CLSID de la coclasse d’origine importée à partir


d’une bibliothèque de types.

Les outils de COM Interop appliquent généralement cet


attribut.

ComImportAttribute Indique qu’une définition d’interface ou de coclasse a été


importée à partir d’une bibliothèque de types COM. Le
runtime utilise cet indicateur pour savoir comment activer et
marshaler le type. Cet attribut interdit au type d’être de
nouveau exporté vers une bibliothèque de types.

Les outils de COM Interop appliquent généralement cet


attribut.

ComRegisterFunctionAttribute Indique qu’une méthode doit être appelée quand l’assembly


est inscrit en vue de son utilisation à partir de COM, de sorte
que le code écrit par l’utilisateur puisse être exécuté au cours
du processus d’inscription.

ComSourceInterfacesAttribute Identifie les interfaces qui sont des sources d’événements pour
la classe.

Les outils de COM Interop peuvent appliquer cet attribut.


AT T RIB UT DESC RIP T IO N

ComUnregisterFunctionAttribute Indique qu’une méthode doit être appelée quand l’inscription


de l’assembly dans COM est annulée, de sorte que le code
écrit par l’utilisateur puisse s’exécuter au cours du processus.

ComVisibleAttribute Rend les types invisibles à COM quand l’attribut a la valeur


false . Cet attribut peut être appliqué à un type individuel ou à
l’intégralité de l’assembly pour contrôler la visibilité COM. Par
défaut, tous les types publics managés sont visibles ; l’attribut
n’est pas nécessaire pour les rendre visibles.

DispIdAttribute Spécifie l’identificateur de répartition COM (DISPID) d’une


méthode ou d’un champ. Cet attribut contient le DISPID de la
méthode, du champ ou de la propriété qu’il décrit.

Les outils de COM Interop peuvent appliquer cet attribut.

ComDefaultInterfaceAttribute Indique l’interface par défaut pour une classe COM


implémentée dans .NET.

Les outils de COM Interop peuvent appliquer cet attribut.

FieldOffsetAttribute Indique la position physique de chaque champ dans une


classe en cas d’utilisation avec StructLayoutAttribute et
d’affectation d’Explicit à LayoutKind .

GuidAttribute Spécifie l’identificateur global unique (GUID) d’une classe,


d’une interface ou d’une bibliothèque de types toute entière.
La chaîne doit être passée à l’attribut sous la forme d’un
argument de constructeur acceptable pour le type
System.Guid .

Les outils de COM Interop peuvent appliquer cet attribut.

IDispatchImplAttribute Indique l’implémentation d’interface IDispatch utilisée par le


common language runtime lors de l’exposition d’interfaces
Dual et de dispinterfaces à COM.

InAttribute Indique que des données doivent être marshalées dans


l’appelant. Peut s’utiliser pour attribuer des paramètres.

InterfaceTypeAttribute Contrôle le mode d’exposition d’une interface managée à des


clients COM (Dual, dérivée d’IUnknown ou IDispatch
uniquement).

Les outils de COM Interop peuvent appliquer cet attribut.

LCIDConversionAttribute Indique que la signature d’une méthode non managée attend


un paramètre LCID.

Les outils de COM Interop peuvent appliquer cet attribut.

MarshalAsAttribute Indique le mode de marshaling des données figurant dans des


champs ou des paramètres entre du code managé et non
managé. L’attribut est toujours facultatif, car chaque type de
données possède un comportement de marshaling par défaut.

Les outils de COM Interop peuvent appliquer cet attribut.


AT T RIB UT DESC RIP T IO N

OptionalAttribute Indique qu'un paramètre est facultatif.

Les outils de COM Interop peuvent appliquer cet attribut.

OutAttribute Indique que les données d’un champ ou d’un paramètre


doivent être remarshalées depuis un objet appelé vers son
appelant.

PreserveSigAttribute Supprime la transformation de la signature HRESULT ou retval


qui s’effectue normalement au cours des appels
d’interopérabilité. Cet attribut affecte le marshaling ainsi que
l’exportation des bibliothèques de types.

Les outils de COM Interop peuvent appliquer cet attribut.

ProgIdAttribute Spécifie le ProgID d’une classe .NET Framework. Peut s’utiliser


pour attribuer des classes.

StructLayoutAttribute Contrôle la disposition physique des champs d’une classe.

Les outils de COM Interop peuvent appliquer cet attribut.

Attributs d’outils de conversion


Le tableau suivant décrit les attributs que les outils de COM Interop appliquent au cours du processus de
conversion. Vous n’appliquez pas ces attributs au moment du design.

AT T RIB UT DESC RIP T IO N

ComAliasNameAttribute Indique l’alias COM pour un type de paramètre ou de champ.


Peut s’utiliser pour attribuer des paramètres, champs ou
valeurs de retour.

ComConversionLossAttribute Indique que des informations sur une classe ou une interface
ont été perdues lors de leur importation d’une bibliothèque
de types vers un assembly.

ComEventInterfaceAttribute Identifie l’interface source et la classe qui implémente les


méthodes de l’interface d’événement.

ImportedFromTypeLibAttribute Indique que l’assembly a été importé à l’origine à partir d’une


bibliothèque de types COM. Cet attribut contient la définition
de la bibliothèque de types d’origine.

TypeLibFuncAttribute Contient les FUNCFL AGS qui ont été importés à l’origine
pour cette fonction à partir de la bibliothèque de types COM.

TypeLibTypeAttribute Contient les TYPEFL AGS qui ont été importés à l’origine pour
ce type à partir de la bibliothèque de types COM.

TypeLibVarAttribute Contient les VARFL AGS qui ont été importés à l’origine pour
cette variable à partir de la bibliothèque de types COM.
Voir aussi
System.Runtime.InteropServices
Exposition de composants .NET Framework à COM
Attributs
Qualification des types .NET pour l’interopérabilité
Empaquetage d’un assembly .NET Framework pour COM
Utilisation d’exceptions d’interopérabilité dans du
code non managé
24/04/2020 • 2 minutes to read • Edit Online

L’interopérabilité des exceptions de code non managé est uniquement prise en charge sur les plateformes
Windows. Des problèmes de portabilité se posent sur les plateformes non-Windows. Étant donné que l’ABI UNIX
n’a aucune définition pour la gestion des exceptions, le code managé ne peut pas savoir comment les mécanismes
d’exception fonctionnent en coulisses. Par conséquent, les exceptions peuvent aboutir à des comportements
imprévisibles et des blocages.

Comportements setjmp/longjmp
L' setjmp interopérabilité longjmp avec les fonctions et C n’est pas prise en charge. Vous ne pouvez longjmp pas
utiliser pour ignorer des frames managés.
Pour plus d’informations, consultez la documentation longjmp.

Voir aussi
Exceptions
Interopérabilité avec des bibliothèques natives
Collections et structures de données
18/07/2020 • 13 minutes to read • Edit Online

Des données similaires peuvent souvent être gérées plus efficacement quand elles sont stockées et manipulées en
tant que collection. Vous pouvez utiliser la System.Array classe ou les classes des System.Collections espaces de
System.Collections.Generic noms,, System.Collections.Concurrent et System.Collections.Immutable pour ajouter,
supprimer et modifier des éléments individuels ou une plage d’éléments dans une collection.
Il existe deux principaux types de collections : les collections génériques et non génériques. Les collections
génériques ont été ajoutées au .NET Framework 2.0 et fournissent des collections de type sécurisé au moment de la
compilation. Pour cette raison, les collections génériques offrent généralement de meilleures performances. Les
collections génériques acceptent un paramètre de type lorsqu'elles sont construites, et ne nécessitent pas de
transtypage du type Object quand vous ajoutez ou supprimez des éléments de la collection. En outre, la plupart des
collections génériques sont prises en charge dans les applications du Windows Store. Les collections non
génériques stockent des éléments en tant que Object , nécessitent un cast et la plupart ne sont pas prises en charge
pour le développement d’applications du Windows Store. Cependant, vous pouvez rencontrer ces collections non
génériques dans du code plus ancien.
À compter de .NET Framework 4, les collections de l’espace de noms System.Collections.Concurrent fournissent des
opérations thread-safe efficaces pour accéder aux éléments de collection de plusieurs threads. Les classes de
collection immuables de l' System.Collections.Immutable espace de noms (package NuGet) sont thread-safe, car les
opérations sont effectuées sur une copie de la collection d’origine et la collection d’origine ne peut pas être
modifiée.

Fonctionnalités communes à toutes les collections


Toutes les collections fournissent des méthodes pour l’ajout, la suppression ou la recherche d’éléments dans la
collection. De plus, toutes les collections qui implémentent directement ou indirectement l'interface ICollection ou
ICollection<T> partagent les fonctionnalités suivantes :
Possibilité d’énumérer la collection
Les collections du .NET Framework implémentent soit System.Collections.IEnumerable, soit
System.Collections.Generic.IEnumerable<T> pour permettre à la collection d'être parcourue. Un
énumérateur peut être vu comme un pointeur mobile pointant vers n'importe quel élément d'une collection.
L’instruction foreach, in et For Each...Next Instruction utilisent l’énumérateur exposé par la méthode
GetEnumerator et masquent la complexité de la manipulation de l’énumérateur. En outre, toute collection
qui implémente System.Collections.Generic.IEnumerable<T> est considérée comme étant un type
requêtable et peut être interrogée avec LINQ. Les requêtes LINQ fournissent un modèle commun d'accès
aux données. Elles sont généralement plus concises et lisibles que les boucles foreach standard, et
fournissent des fonctionnalités de filtrage, de tri et de regroupement. Les requêtes LINQ peuvent également
améliorer les performances. Pour plus d’informations, consultez LINQ to Objects (C#), LINQ to Objects
(Visual Basic), Parallel LINQ (PLINQ), Introduction aux requêtes LINQ (C#) et Opérations de requête de base
(Visual Basic).
Possibilité de copier le contenu d’une collection dans un tableau
Toutes les collections peuvent être copiées dans un tableau à l'aide de la méthode CopyTo . Toutefois, l'ordre
des éléments du nouveau tableau sera basé sur l'ordre où ils sont retournés par l'énumérateur. Le tableau
résultant est toujours unidimensionnel avec une limite inférieure de zéro.
De plus, de nombreuses classes de collection comprennent les fonctionnalités suivantes :
Propriétés Capacity et Count
La propriété Capacity d'une collection indique le nombre d'éléments qu'elle peut contenir. La propriété
Count d'une collection indique le nombre d'éléments qu'elle contient réellement. Certaines collections
masquent l'une de ces propriétés, voire les deux.
La capacité de la plupart des collections s'étend automatiquement quand la valeur de la propriété Capacity
est atteinte. La mémoire est réallouée et les éléments sont copiés depuis l'ancienne collection vers la
nouvelle. Cela permet de réduire le code nécessaire à l'utilisation de la collection. Toutefois, les performances
de la collection peuvent être affectées. Par exemple, pour List<T> , si Count est inférieur à Capacity , l’ajout
d’un élément est une opération O (1). Si la capacité doit être augmentée pour s’adapter au nouvel élément,
l’ajout d’un élément devient une opération O ( n ), où n est Count . Le meilleur moyen d'éviter la
dégradation des performances en raison des réallocations est de définir la propriété Capacity sur la taille
estimée de la collection.
BitArray est un cas particulier. Sa capacité est égale à sa longueur, qui est elle-même égale à son nombre.
Limite inférieure cohérente
La limite inférieure d'une collection est l'index de son premier élément. Toutes les collections indexées dans
l'espace de noms System.Collections ont une limite inférieure de zéro, ce qui signifie qu'elles sont indexées
à 0. Arraya une limite inférieure de zéro par défaut, mais une limite inférieure différente peut être définie
lors de la création d’une instance de la classe Array à l’aide de Array.CreateInstance .
Synchronisation pour l’accès à par tir de plusieurs threads ( System.Collections classes uniquement).
Les types de collections non génériques de l'espace de noms System.Collections fournissent une certaine
cohérence de thread pour la synchronisation, généralement exposée par des membres SyncRoot et
IsSynchronized. Ces collections ne sont pas thread-safe par défaut. Si vous avez besoin d'un accès
multithread évolutif et efficace pour une collection, utilisez l'une des classes de l'espace de noms
System.Collections.Concurrent ou envisagez d'utiliser une collection immuable. Pour plus d’informations,
consultez Collections thread-safe.

Choisir un regroupement
En règle générale, vous devez utiliser des collections génériques. Le tableau suivant décrit certains scénarios
courants concernant les collections, ainsi que les classes de collection que vous pouvez utiliser pour ces scénarios.
Si vous ne connaissez pas encore les collections génériques, ce tableau vous aidera à choisir la collection générique
qui répond le mieux à vos besoins.

O P T IO N S DE C O L L EC T IO N
O P T IO N S DE C O L L EC T IO N O P T IO N S DE C O L L EC T IO N T H REA D- SA F E O U
JE SO UH A IT E : GÉN ÉRIQ UE N O N GÉN ÉRIQ UE IM M UA B L E

Stocker les éléments sous Dictionary<TKey,TValue> Hashtable ConcurrentDictionary<TKey,


forme de paires clé/valeur TValue>
pour une recherche rapide (Collection de paires
par clé clé/valeur organisées en ReadOnlyDictionary<TKey,T
fonction du code de hachage Value>
de la clé).
ImmutableDictionary<TKey,T
Value>

Accéder aux éléments par List<T> Array ImmutableList<T>


index
ArrayList ImmutableArray
O P T IO N S DE C O L L EC T IO N
O P T IO N S DE C O L L EC T IO N O P T IO N S DE C O L L EC T IO N T H REA D- SA F E O U
JE SO UH A IT E : GÉN ÉRIQ UE N O N GÉN ÉRIQ UE IM M UA B L E

Utiliser des éléments premier Queue<T> Queue ConcurrentQueue<T>


entré, premier sorti (FIFO)
ImmutableQueue<T>

Utiliser des données dernier Stack<T> Stack ConcurrentStack<T>


entré, premier sorti (LIFO)
ImmutableStack<T>

Accéder aux éléments de LinkedList<T> Aucune recommandation Aucune recommandation


manière séquentielle

Recevoir des notifications ObservableCollection<T> Aucune recommandation Aucune recommandation


quand des éléments sont
supprimés ou ajoutés à la
collection. (implémente
INotifyPropertyChanged et
INotifyCollectionChanged)

Collection triée SortedList<TKey,TValue> SortedList ImmutableSortedDictionary


<TKey,TValue>

ImmutableSortedSet<T>

Ensemble de fonctions HashSet<T> Aucune recommandation ImmutableHashSet<T>


mathématiques
SortedSet<T> ImmutableSortedSet<T>

Complexité algorithmique des collections


Lors du choix d’une classe de collection, il convient d’envisager des compromis potentiels en matière de
performances. Utilisez le tableau suivant pour référencer la manière dont les différents types de collections
mutables sont comparés dans la complexité algorithmique à leurs équivalents immuables correspondants.
Souvent, les types de collections immuables sont moins performants, mais fournissent une immuabilité, ce qui est
souvent un avantage comparatif valide.

M UTA B L E A M O RT I P IRE DES C A S N O N M O DIF IA B L E C O M P L EXIT É

Stack<T>.Push O (1) O( n ) ImmutableStack<T>.Push O (1)

Queue<T>.Enqueue O (1) O( n ) O (1)


ImmutableQueue<T>.Enqueue

List<T>.Add O (1) O( n ) ImmutableList<T>.Add O (journal n )

List<T>.Item[Int32] O (1) O (1) O (journal n


ImmutableList<T>.Item[Int32] )

List<T>.Enumerator O( n ) O( n ) O( n
ImmutableList<T>.Enumerator )

HashSet<T>.Add , O (1) O( n ) ImmutableHashSet<T>.AddO (journal n )


recherche

SortedSet<T>.Add O (journal n ) O( n ) O (journal n


ImmutableSortedSet<T>.Add )
M UTA B L E A M O RT I P IRE DES C A S N O N M O DIF IA B L E C O M P L EXIT É

Dictionary<T>.Add O (1) O( n ) O (journal n


ImmutableDictionary<T>.Add )

Dictionary<T> O (1) O (1) – ou strictement ImmutableDictionary<T> O (journal n )


directes O( n ) directes

SortedDictionary<T>.AddO (journal n ) O( n journal n ) O (journal n


ImmutableSortedDictionary<T>.Add )

Un List<T>peut être énuméré efficacement à l’aide d’une boucle for ou d’une foreach boucle.
ImmutableList<T> Toutefois, un travail mal fait à l’intérieur d' for une boucle, en raison de l’heure O (log n ) de
son indexeur. L’énumération d’une ImmutableList<T> boucle à l’aide d’une foreach boucle est efficace, car
ImmutableList<T> utilise une arborescence binaire pour stocker ses données au lieu d’un tableau simple comme
les List<T> utilisations. Un tableau peut être indexé très rapidement dans, tandis qu’une arborescence binaire doit
être parcourue jusqu’à ce que le nœud avec l’index souhaité soit trouvé.
En outre, SortedSet<T> a la même complexité que ImmutableSortedSet<T> . En effet, ils utilisent tous deux des
arborescences binaires. La différence significative est évidemment que ImmutableSortedSet<T> utilise une
arborescence binaire immuable. Étant donné que ImmutableSortedSet<T> offre également une
System.Collections.Immutable.ImmutableSortedSet<T>.Builder classe qui permet la mutation, vous pouvez avoir à
la fois une immuabilité et des performances.

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Sélection d’une classe de collection Décrit les différentes collections et permet d'en sélectionner
une pour votre scénario.

Types de collections couramment utilisés Décrit les types de collection génériques et non génériques
fréquemment utilisés, tels que System.Array,
System.Collections.Generic.List<T> et
System.Collections.Generic.Dictionary<TKey,TValue>.

Quand utiliser des collections génériques Traite de l'utilisation des types de collections génériques.

Comparaisons et tris dans les collections Aborde l'utilisation des comparaisons d'égalité et de tri dans
les collections.

Types de collections triées Aborde les caractéristiques et les performances des collections
triées.

Types de collections Hashtable et Dictionary Décrit les fonctionnalités des types de dictionnaires basés sur
le hachage génériques et non génériques.

Collections thread-safe Décrit les types de collections tels que


System.Collections.Concurrent.BlockingCollection<T> et
System.Collections.Concurrent.ConcurrentBag<T> qui
prennent en charge l'accès simultané sécurisé et efficace de
plusieurs threads.

System.Collections.Immutable Présente les collections immuables et fournit des liens vers les
types de collection.
Informations de référence
System.Array System.Collections System.Collections.Concurrent System.Collections.Generic
System.Collections.Specialized System.Linq System.Collections.Immutable
Sélection d’une classe de collection
18/07/2020 • 8 minutes to read • Edit Online

Veillez à choisir votre classe de collection avec soin. L’utilisation d’un type incorrect peut limiter votre utilisation de
la collection.

IMPORTANT
Éviter d’utiliser les types dans l’espace de noms System.Collections. Les versions génériques et simultanées des collections
sont recommandées en raison de la sécurité supérieure des types et d'autres améliorations.

Considérez les questions suivantes :


Avez-vous besoin d'une liste séquentielle où l'élément est en général abandonné une fois sa valeur
récupérée ?
Si oui, envisagez d'utiliser la classe Queue ou bien la classe générique Queue<T> si vous avez besoin
d'un comportement premier entré, premier sorti (FIFO, First-In, First-Out). Envisagez d'utiliser la
classe Stack ou bien la classe générique Stack<T> si vous avez besoin d'un comportement dernier
entré, premier sorti (LIFO, Last-In, First-Out). Pour un accès sécurisé à partir de plusieurs threads,
utilisez les versions simultanées ConcurrentQueue<T> et ConcurrentStack<T>. Pour l’immuabilité,
prenez en compte les versions immuables, ImmutableQueue<T> et ImmutableStack<T> .
Si ce n'est pas le cas, envisagez d'utiliser les autres collections.
Avez-vous besoin d'accéder aux éléments dans un certain ordre, comme FIFO ou LIFO, ou de façon
aléatoire ?
La Queue classe, ainsi que les Queue<T> ConcurrentQueue<T> classes génériques, et
ImmutableQueue<T> offrent tous l’accès FIFO. Pour plus d’informations, consultez Quand utiliser
une collection thread-safe.
La Stack classe, ainsi que les Stack<T> ConcurrentStack<T> classes génériques, et
ImmutableStack<T> offrent tous l’accès LIFO. Pour plus d’informations, consultez Quand utiliser une
collection thread-safe.
La classe générique LinkedList<T> autorise un accès séquentiel de haut en bas ou de bas en haut.
Avez-vous besoin d'accéder à chaque élément selon son index ?
Les classes ArrayList et StringCollection, ainsi que la classe générique List<T>, offrent un accès à
leurs éléments via l'index de base zéro de l'élément. Pour l’immuabilité, prenez en compte les
versions génériques immuables, ImmutableArray<T> et ImmutableList<T> .
Les classes Hashtable, SortedList, ListDictionary et StringDictionary, ainsi que les classes génériques
Dictionary<TKey,TValue> et SortedDictionary<TKey,TValue>, offrent un accès à leurs éléments via la
clé de l'élément. En outre, il existe des versions immuables de plusieurs types correspondants :
ImmutableHashSet<T> , ImmutableDictionary<TKey,TValue> , ImmutableSortedSet<T> et
ImmutableSortedDictionary<TKey,TValue> .
Les classes NameObjectCollectionBase et NameValueCollection, ainsi que les classes génériques
KeyedCollection<TKey,TItem> et SortedList<TKey,TValue>, offrent un accès à leurs éléments via
l'index de base zéro ou via la clé de l'élément.
Chaque élément contiendra-t-il une valeur, une combinaison d'une clé et d'une valeur, ou une combinaison
d'une clé et de plusieurs valeurs ?
Une valeur : utilisez une des collections basées sur l'interface IList ou l'interface générique IList<T>.
Pour une option immuable, considérez l' IImmutableList<T> interface générique.
Une clé et une valeur : utilisez une des collections basées sur l'interface IDictionary ou l'interface
générique IDictionary<TKey,TValue>. Pour une option immuable, prenez en compte
IImmutableSet<T> les IImmutableDictionary<TKey,TValue> interfaces génériques ou.
Une valeur avec une clé incorporée : utilisez la classe générique KeyedCollection<TKey,TItem>.
Une clé et plusieurs valeurs : utilisez la classe NameValueCollection.
Avez-vous besoin de trier les éléments différemment de la façon dont ils ont été entrés ?
La classe Hashtable trie ses éléments selon leur code de hachage.
La classe SortedList et les classes génériques SortedList<TKey,TValue> et
SortedDictionary<TKey,TValue> trient leurs éléments par clé. L’ordre de tri est basé sur
l’implémentation de l’interface IComparer pour la classe SortedList et sur l’implémentation de
l’interface générique IComparer<T> pour les classes génériques SortedList<TKey,TValue> et
SortedDictionary<TKey,TValue>. Des deux types génériques, c’est SortedDictionary<TKey,TValue>
qui offre de meilleures performances que SortedList<TKey,TValue>, tandis que
SortedList<TKey,TValue> consomme moins de mémoire.
ArrayList fournit une méthode Sort qui prend une implémentation de IComparer comme paramètre.
Sa contrepartie générique, la classe générique List<T>, fournit une méthode Sort qui prend une
implémentation de l'interface générique IComparer<T> comme paramètre.
Avez-vous besoin de recherches et d'une récupération rapides des informations ?
ListDictionary est plus rapide que Hashtable pour les petites collections (10 éléments ou moins). La
classe générique Dictionary<TKey,TValue> permet une recherche plus rapide que celle de la classe
générique SortedDictionary<TKey,TValue>. L'implémentation multithread est
ConcurrentDictionary<TKey,TValue>. ConcurrentBag<T> fournit une insertion multithread rapide pour
les données non ordonnées. Pour plus d’informations sur les deux types multithread, consultez Quand
utiliser une collection thread-safe.
Avez-vous besoin de collections qui acceptent seulement des chaînes ?
StringCollection (basée sur IList) et StringDictionary (basée sur IDictionary) se trouvent dans l'espace
de noms System.Collections.Specialized.
En outre, vous pouvez utiliser toutes les classes de collection génériques dans l'espace de noms
System.Collections.Generic comme des collections de chaînes fortement typées en spécifiant la
classe String pour leurs arguments de types génériques. Par exemple, vous pouvez déclarer une
variable comme étant de type List <String> ou dictionary<String, String>.

LINQ to Objects et PLINQ


La fonctionnalité LINQ to Objects permet aux développeurs d'utiliser des requêtes LINQ pour accéder aux objets
en mémoire pour autant que le type d'objet implémente IEnumerable ou IEnumerable<T>. Les requêtes LINQ
fournissent un modèle commun pour accéder aux données. Elles sont généralement plus concises et plus lisibles
que les boucles foreach standard et offrent des fonctions de filtrage, de classement et de regroupement. Pour
plus d’informations, consultez LINQ to Objects (C#) et LINQ to Objects (Visual Basic).
PLINQ fournit une implémentation parallèle de LINQ to Objects, qui peut offrir une exécution plus rapide des
requêtes dans de nombreux scénarios, via une utilisation plus efficace des ordinateurs multicœurs. Pour plus
d’informations, consultez PLINQ (Parallel LINQ).

Voir aussi
System.Collections
System.Collections.Specialized
System.Collections.Generic
Collections thread-safe
Types de collections couramment utilisés
18/07/2020 • 5 minutes to read • Edit Online

Les types de collections sont des variations courantes des collections de données, telles que les tables de hachage,
les files d’attente, les piles, les conteneurs, les dictionnaires et les listes.
Les collections sont basées sur l'interface ICollection, IList ou IDictionary, ou sur leurs équivalents génériques. Les
interfaces IList et IDictionary sont toutes deux dérivées de l'interface ICollection. Par conséquent, toutes les
collections sont basées directement ou indirectement sur l'interface ICollection. Dans les collections basées sur l'
IList interface (telles que Array , ArrayList ou List<T> ) ou directement sur l' ICollection interface (telles que,,,
Queue ConcurrentQueue<T> Stack ConcurrentStack<T> ou LinkedList<T> ), chaque élément contient
uniquement une valeur. Dans les collections basées sur l'interface IDictionary (telles que les classes Hashtable et
SortedList, les classes génériques Dictionary<TKey,TValue> et SortedList<TKey,TValue>) ou sur les classes
ConcurrentDictionary<TKey,TValue>, chaque élément contient à la fois une clé et une valeur. La classe
KeyedCollection<TKey,TItem> est unique, car il s'agit d'une liste de valeurs auxquelles sont incorporées des clés.
Son comportement est donc celui d'une liste ou d'un dictionnaire.
Les collections génériques conviennent le mieux au typage fort. Toutefois, si votre langage ne prend pas en charge
les génériques, l'espace de noms System.Collections contient des collections de base, telles que CollectionBase,
ReadOnlyCollectionBase et DictionaryBase, qui sont des classes de base abstraites pouvant être étendues pour
créer des classes de collection fortement typées. Quand un accès efficace à une collection multithread est requis,
utilisez les collections génériques de l'espace de noms System.Collections.Concurrent.
Les collections peuvent varier, en fonction de la façon dont les éléments sont stockés et triés, ainsi que de la façon
dont les recherches et les comparaisons sont effectuées. La classe Queue et la classe générique Queue<T>
fournissent des listes premier entré, premier sorti (FIFO), alors que la classe Stack et la classe générique Stack<T>
fournissent des listes dernier entré, premier sorti (LIFO). La classe SortedList et la classe générique
SortedList<TKey,TValue> fournissent des versions triées de la classe Hashtable et de la classe générique
Dictionary<TKey,TValue>. Les éléments d'un Hashtable ou d'un Dictionary<TKey,TValue> sont accessibles
uniquement via la clé de l'élément, alors que les éléments d'un SortedList ou d'un KeyedCollection<TKey,TItem>
sont accessibles aussi bien via la clé que l'index de l'élément. Les index de toutes les collections sont de base zéro,
à l'exception de Array, qui acceptent les tableaux qui ne sont pas de base zéro.
La fonctionnalité LINQ to Objects permet d'utiliser des requêtes LINQ pour accéder aux objets en mémoire tant
que le type d'objet implémente l'interface IEnumerable ou IEnumerable<T>. Les requêtes LINQ fournissent un
modèle commun pour accéder aux données ; sont généralement plus concises et lisibles que les boucles standard,
foreach et fournissent des fonctionnalités de filtrage, de classement et de regroupement. Les requêtes LINQ
peuvent également améliorer les performances. Pour plus d’informations, consultez LINQ to Objects (C#), LINQ to
Objects (Visual Basic) et Parallel LINQ (PLINQ).

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Collections et structures de données Présente les différents types de collection disponibles dans le
.NET Framework, notamment les piles, les files d’attente, les
listes, les tableaux et les dictionnaires.

Types de collections Hashtable et Dictionary Décrit les fonctionnalités des types de dictionnaires basés sur
le hachage génériques et non génériques.
IN T IT UL É DESC RIP T IO N

Types de collections triées Décrit les classes qui fournissent une fonctionnalité de tri
pour les listes et les ensembles.

Génériques Décrit la fonctionnalité génériques, notamment les collections,


délégués et interfaces génériques fournis par le .NET
Framework. Fournit des liens vers de la documentation sur les
fonctionnalités pour C#, Visual Basic et Visual C++, ainsi que
vers des technologies de prise en charge comme la réflexion.

Informations de référence
System.Collections
System.Collections.Generic
System.Collections.ICollection
System.Collections.Generic.ICollection<T>
System.Collections.IList
System.Collections.Generic.IList<T>
System.Collections.IDictionary
System.Collections.Generic.IDictionary<TKey,TValue>
Quand utiliser les collections génériques
18/07/2020 • 6 minutes to read • Edit Online

L’utilisation de collections génériques vous offre l’avantage automatique de la cohérence des types sans avoir à
dériver d’un type de collection de base et à implémenter des membres spécifiques au type. Les types de collections
génériques sont généralement plus performants que les types de collections non génériques correspondants (et
mieux que les types dérivés de types de collections de base non génériques) quand les éléments de la collection
sont des types valeur, car avec les génériques, il n’est pas nécessaire de définir un cadre pour les éléments.
Pour les programmes qui ciblent .NET Standard 1,0 ou une version ultérieure, utilisez les classes de collection
génériques dans l' System.Collections.Concurrent espace de noms lorsque plusieurs threads peuvent ajouter ou
supprimer simultanément des éléments de la collection. En outre, lorsque l’immuabilité est souhaitée, tenez
compte des classes de collection génériques dans l' System.Collections.Immutable espace de noms.
Les types génériques suivants correspondent à des types de collections existants :
List<T> est la classe générique qui correspond à ArrayList.
Dictionary<TKey,TValue> et ConcurrentDictionary<TKey,TValue> sont les classes génériques qui
correspondent à Hashtable.
Collection<T> est la classe générique qui correspond à CollectionBase. Collection<T>peut être utilisé
comme classe de base, mais contrairement CollectionBase à, il n’est pas abstrait, ce qui le rend beaucoup
plus facile à utiliser.
ReadOnlyCollection<T> est la classe générique qui correspond à ReadOnlyCollectionBase.
ReadOnlyCollection<T>n’est pas abstraite et possède un constructeur qui facilite l’exposition d’un existant
List<T> en tant que collection en lecture seule.
Les classes génériques,,,, Queue<T> ConcurrentQueue<T> ImmutableQueue<T> ImmutableArray<T>
SortedList<TKey,TValue> et ImmutableSortedSet<T> correspondent aux classes non génériques
respectives portant le même nom.

Autres types
Plusieurs types de collections génériques n'ont pas d'équivalents non génériques. Il s'agit des dossiers suivants :
LinkedList<T> est une liste liée à usage général qui fournit des opérations d'insertion et de suppression
O(1).
SortedDictionary<TKey,TValue> est un dictionnaire trié avec des opérations d'insertion et d'extraction O(log
n ), ce qui en fait une alternative pratique à SortedList<TKey,TValue>.

KeyedCollection<TKey,TItem> est un hybride entre une liste et un dictionnaire qui permet de stocker des
objets qui contiennent leurs propres clés.
BlockingCollection<T> implémente une classe de collection avec une fonctionnalité d'englobement et de
blocage.
ConcurrentBag<T> permet une insertion et une suppression rapides des éléments non triés.
Générateurs immuables
Quand vous souhaitez une fonctionnalité d’immuabilité dans votre application, l' System.Collections.Immutable
espace de noms offre des types de collections génériques que vous pouvez utiliser. Tous les types de collections
immuables offrent des Builder classes qui peuvent optimiser les performances lorsque vous effectuez plusieurs
mutations. La Builder classe traite les opérations dans un État mutable. Une fois toutes les mutations terminées,
appelez la ToImmutable méthode pour « figer » tous les nœuds et créer une collection générique immuable, par
exemple, ImmutableList<T> .
L' Builder objet peut être créé en appelant la méthode non générique CreateBuilder() . À partir d’une Builder
instance, vous pouvez appeler ToImmutable() . De même, à partir de la Immutable* collection, vous pouvez appeler
ToBuilder() pour créer une instance de générateur à partir de la collection immuable générique. Les différents
types sont les suivants Builder .
ImmutableArray<T>.Builder
ImmutableDictionary<TKey,TValue>.Builder
ImmutableHashSet<T>.Builder
ImmutableList<T>.Builder
ImmutableSortedDictionary<TKey,TValue>.Builder
ImmutableSortedSet<T>.Builder

LINQ to Objects
La fonctionnalité LINQ to Objects permet d'utiliser des requêtes LINQ pour accéder aux objets en mémoire tant
que le type d'objet implémente l'interface System.Collections.IEnumerable ou
System.Collections.Generic.IEnumerable<T> . Les requêtes LINQ fournissent un modèle commun pour accéder aux
données ; sont généralement plus concises et lisibles que les boucles standard, foreach et fournissent des
fonctionnalités de filtrage, de classement et de regroupement. Les requêtes LINQ peuvent également améliorer les
performances. Pour plus d’informations, consultez LINQ to Objects (C#), LINQ to Objects (Visual Basic) et Parallel
LINQ (PLINQ).

Autres fonctionnalités
Certains types génériques ont des fonctionnalités que n'ont pas les types de collections non génériques. Par
exemple, la classe List<T> , qui correspond à la classe non générique ArrayList , possède plusieurs méthodes
acceptant des délégués génériques, telles que le délégué Predicate<T> qui permet de spécifier des méthodes pour
effectuer des recherches dans la liste, le délégué Action<T> , qui représente des méthodes qui agissent sur chaque
élément de la liste, et le délégué Converter<TInput,TOutput> qui permet de définir des conversions de types.
La classe List<T> permet de spécifier vos propres implémentations d'interface générique IComparer<T> pour trier
et parcourir des listes. Les classes SortedDictionary<TKey,TValue> et SortedList<TKey,TValue> possèdent
également cette fonctionnalité. De plus, ces classes permettent de spécifier des comparateurs au moment de la
création de la collection. De la même façon, les classes Dictionary<TKey,TValue> et KeyedCollection<TKey,TItem>
permettent de spécifier vos propres comparateurs d'égalité.

Voir aussi
Collections et structures de données
Types de collections couramment utilisés
Génériques
Comparaisons et tris dans les collections
18/07/2020 • 9 minutes to read • Edit Online

Les classes System.Collections effectuent des comparaisons dans quasiment tous les processus impliqués dans la
gestion des collections, que ce soit pendant la recherche d'un élément à supprimer ou le renvoi d'une valeur d'une
paire clé-valeur.
Les collections utilisent généralement un comparateur d’égalité et/ou un comparateur de classement. Deux
constructions sont utilisées pour les comparaisons.

Vérifier l’égalité
Les méthodes telles que Contains , IndexOf, LastIndexOfet Remove utilisent un comparateur d'égalité pour les
éléments de collection. Si la collection est générique, les éléments sont comparés pour déterminer leur égalité
conformément aux indications suivantes :
Si le type T implémente l'interface générique IEquatable<T> , le comparateur d'égalité est la méthode Equals
de cette interface.
Si le type T n'implémente pas IEquatable<T>, Object.Equals est utilisé.
De plus, certaines surcharges de constructeur pour les collections de dictionnaires acceptent une implémentation
IEqualityComparer<T>, qui est utilisée pour comparer l'égalité de clés. Pour obtenir un exemple, consultez le
constructeur Dictionary<TKey,TValue> .

Déterminer l’ordre de tri


Les méthodes telles que BinarySearch et Sort utilisent un comparateur de classement pour les éléments de
collection. Les comparaisons peuvent être effectuées entre les éléments d'une collection, ou entre un élément et
une valeur spécifiée. Pour comparer des objets, il existe le default comparer et le explicit comparer .
Le comparateur par défaut repose sur au moins l'un des objets comparés pour implémenter l'interface
IComparable . Il est recommandé d'implémenter IComparable sur toutes les classes utilisées en tant que valeurs
dans une collection de listes ou en tant que clés dans une collection de dictionnaires. Pour une collection générique,
la comparaison d'égalité est déterminée selon ce qui suit :
Si le type T implémente l'interface générique System.IComparable<T> , le comparateur par défaut est la
méthode IComparable<T>.CompareTo(T) de cette interface.
Si le type T implémente l'interface non générique System.IComparable , le comparateur par défaut est la
méthode IComparable.CompareTo(Object) de cette interface.
Si le type T n’implémente aucune interface, il n’existe aucun comparateur par défaut, et un comparateur ou
un délégué de comparaison doit être fourni explicitement.
Pour fournir des comparaisons explicites, certaines méthodes acceptent une implémentation IComparer en tant
que paramètre. Par exemple, la méthode List<T>.Sort accepte une implémentation
System.Collections.Generic.IComparer<T> .
Le paramètre de culture actuel du système peut affecter les comparaisons et les tris d’une collection. Par défaut, les
comparaisons et les tris des classes Collections sont dépendants de la culture. Pour ignorer le paramètre de
culture et donc obtenir des résultats cohérents de comparaison et de tri, utilisez InvariantCulture avec des
surcharges de membre qui acceptent CultureInfo. Pour plus d’informations, consultez Performing Culture-
Insensitive String Operations in Collections et Performing Culture-Insensitive String Operations in Arrays.

Exemple d'égalité et de tri


Le code suivant illustre une implémentation de IEquatable<T> et de IComparable<T> dans un objet métier simple.
De plus, quand l'objet est stocké dans une liste, puis trié, vous verrez que l'appel de la méthode Sort() se traduit par
l'utilisation du comparateur par défaut pour le type Part , et par l'implémentation de la méthode
Sort(Comparison<T>) à l'aide d'une méthode anonyme.

using System;
using System.Collections.Generic;

// Simple business object. A PartId is used to identify the


// type of part but the part name can change.
public class Part : IEquatable<Part>, IComparable<Part>
{
public string PartName { get; set; }

public int PartId { get; set; }

public override string ToString() =>


$"ID: {PartId} Name: {PartName}";

public override bool Equals(object obj) =>


(obj is Part part)
? Equals(part)
: false;

public int SortByNameAscending(string name1, string name2) =>


name1?.CompareTo(name2) ?? 1;

// Default comparer for Part type.


// A null value means that this object is greater.
public int CompareTo(Part comparePart) =>
comparePart == null ? 1 : PartId.CompareTo(comparePart.PartId);

public override int GetHashCode() => PartId;

public bool Equals(Part other) =>


other is null ? false : PartId.Equals(other.PartId);

// Should also override == and != operators.


}

public class Example


{
public static void Main()
{
// Create a list of parts.
var parts = new List<Part>
{
// Add parts to the list.
new Part { PartName = "regular seat", PartId = 1434 },
new Part { PartName = "crank arm", PartId = 1234 },
new Part { PartName = "shift lever", PartId = 1634 },
// Name intentionally left null.
new Part { PartId = 1334 },
new Part { PartName = "banana seat", PartId = 1444 },
new Part { PartName = "cassette", PartId = 1534 }
};

// Write out the parts in the list. This will call the overridden
// ToString method in the Part class.
Console.WriteLine("\nBefore sort:");
parts.ForEach(Console.WriteLine);
// Call Sort on the list. This will use the
// default comparer, which is the Compare method
// implemented on Part.
parts.Sort();

Console.WriteLine("\nAfter sort by part number:");


parts.ForEach(Console.WriteLine);

// This shows calling the Sort(Comparison<T> comparison) overload using


// a lambda expression as the Comparison<T> delegate.
// This method treats null as the lesser of two values.
parts.Sort((Part x, Part y) =>
x.PartName == null && y.PartName == null
? 0
: x.PartName == null
? -1
: y.PartName == null
? 1
: x.PartName.CompareTo(y.PartName));

Console.WriteLine("\nAfter sort by name:");


parts.ForEach(Console.WriteLine);

/*

Before sort:
ID: 1434 Name: regular seat
ID: 1234 Name: crank arm
ID: 1634 Name: shift lever
ID: 1334 Name:
ID: 1444 Name: banana seat
ID: 1534 Name: cassette

After sort by part number:


ID: 1234 Name: crank arm
ID: 1334 Name:
ID: 1434 Name: regular seat
ID: 1444 Name: banana seat
ID: 1534 Name: cassette
ID: 1634 Name: shift lever

After sort by name:


ID: 1334 Name:
ID: 1444 Name: banana seat
ID: 1534 Name: cassette
ID: 1234 Name: crank arm
ID: 1434 Name: regular seat
ID: 1634 Name: shift lever

*/
}
}

Imports System.Collections.Generic

' Simple business object. A PartId is used to identify the type of part
' but the part name can change.
Public Class Part
Implements IEquatable(Of Part)
Implements IComparable(Of Part)
Public Property PartName() As String
Get
Return m_PartName
End Get
Set(value As String)
m_PartName = Value
End Set
End Property
Private m_PartName As String

Public Property PartId() As Integer


Get
Return m_PartId
End Get
Set(value As Integer)
m_PartId = Value
End Set
End Property
Private m_PartId As Integer

Public Overrides Function ToString() As String


Return "ID: " & PartId & " Name: " & PartName
End Function

Public Overrides Function Equals(obj As Object) As Boolean


If obj Is Nothing Then
Return False
End If
Dim objAsPart As Part = TryCast(obj, Part)
If objAsPart Is Nothing Then
Return False
Else
Return Equals(objAsPart)
End If
End Function

Public Function SortByNameAscending(name1 As String, name2 As String) As Integer

Return name1.CompareTo(name2)
End Function

' Default comparer for Part.


Public Function CompareTo(comparePart As Part) As Integer _
Implements IComparable(Of ListSortVB.Part).CompareTo
' A null value means that this object is greater.
If comparePart Is Nothing Then
Return 1
Else

Return Me.PartId.CompareTo(comparePart.PartId)
End If
End Function
Public Overrides Function GetHashCode() As Integer
Return PartId
End Function
Public Overloads Function Equals(other As Part) As Boolean Implements IEquatable(Of
ListSortVB.Part).Equals
If other Is Nothing Then
Return False
End If
Return (Me.PartId.Equals(other.PartId))
End Function
' Should also override == and != operators.

End Class
Public Class Example
Public Shared Sub Main()
' Create a list of parts.
Dim parts As New List(Of Part)()

' Add parts to the list.


parts.Add(New Part() With { _
.PartName = "regular seat", _
.PartId = 1434 _
})
parts.Add(New Part() With { _
parts.Add(New Part() With { _
.PartName = "crank arm", _
.PartId = 1234 _
})
parts.Add(New Part() With { _
.PartName = "shift lever", _
.PartId = 1634 _
})

' Name intentionally left null.


parts.Add(New Part() With { _
.PartId = 1334 _
})
parts.Add(New Part() With { _
.PartName = "banana seat", _
.PartId = 1444 _
})
parts.Add(New Part() With { _
.PartName = "cassette", _
.PartId = 1534 _
})

' Write out the parts in the list. This will call the overridden
' ToString method in the Part class.
Console.WriteLine(vbLf & "Before sort:")
For Each aPart As Part In parts
Console.WriteLine(aPart)
Next

' Call Sort on the list. This will use the


' default comparer, which is the Compare method
' implemented on Part.
parts.Sort()

Console.WriteLine(vbLf & "After sort by part number:")


For Each aPart As Part In parts
Console.WriteLine(aPart)
Next

' This shows calling the Sort(Comparison(T) overload using


' an anonymous delegate method.
' This method treats null as the lesser of two values.
parts.Sort(Function(x As Part, y As Part)
If x.PartName Is Nothing AndAlso y.PartName Is Nothing Then
Return 0
ElseIf x.PartName Is Nothing Then
Return -1
ElseIf y.PartName Is Nothing Then
Return 1
Else
Return x.PartName.CompareTo(y.PartName)
End If
End Function)

Console.WriteLine(vbLf & "After sort by name:")


For Each aPart As Part In parts
Console.WriteLine(aPart)
Next

'
'
' Before sort:
' ID: 1434 Name: regular seat
' ID: 1234 Name: crank arm
' ID: 1634 Name: shift lever
' ID: 1634 Name: shift lever
' ID: 1334 Name:
' ID: 1444 Name: banana seat
' ID: 1534 Name: cassette
'
' After sort by part number:
' ID: 1234 Name: crank arm
' ID: 1334 Name:
' ID: 1434 Name: regular seat
' ID: 1444 Name: banana seat
' ID: 1534 Name: cassette
' ID: 1634 Name: shift lever
'
' After sort by name:
' ID: 1334 Name:
' ID: 1444 Name: banana seat
' ID: 1534 Name: cassette
' ID: 1234 Name: crank arm
' ID: 1434 Name: regular seat
' ID: 1634 Name: shift lever

End Sub
End Class

Voir aussi
IComparer
IEquatable<T>
IComparer<T>
IComparable
IComparable<T>
Types de collections triées
18/07/2020 • 4 minutes to read • Edit Online

La classe System.Collections.SortedList, la classe générique System.Collections.Generic.SortedList<TKey,TValue> et


la classe générique System.Collections.Generic.SortedDictionary<TKey,TValue> sont similaires à la classe
Hashtable et à la classe générique Dictionary<TKey,TValue>, car elles implémentent l’interface IDictionary, mais
conservent leurs éléments triés par clé et ne possèdent pas les caractéristiques d’insertion ni de récupération O(1)
des tables de hachage. Les trois classes ont plusieurs fonctionnalités en commun :
Les trois classes implémentent toutes l’interface System.Collections.IDictionary. Les deux classes génériques
implémentent également l’interface générique System.Collections.Generic.IDictionary<TKey,TValue>.
Chaque élément est une paire clé/valeur utilisée à des fins d’énumération.

NOTE
La classe SortedList non générique retourne des objets DictionaryEntry quand elle est énumérée, même si les deux
types génériques retournent des objets KeyValuePair<TKey,TValue>.

Les éléments sont triés selon une implémentation d’System.Collections.IComparer (pour la classe SortedList
non générique) ou une implémentation d’System.Collections.Generic.IComparer<T> (pour les deux classes
génériques).
Chaque classe fournit des propriétés qui retournent des collections contenant uniquement les clés ou
uniquement les valeurs.
Le tableau suivant répertorie certaines des différences entre les deux classes de liste triée et la classe
SortedDictionary<TKey,TValue>.

SO RT EDL IST ( C L A SSE N O N GÉN ÉRIQ UE) ET


SO RT EDL IST <T K EY, T VA L UE> ( C L A SSE GÉN ÉRIQ UE) SO RT EDDIC T IO N A RY <T K EY, T VA L UE> ( C L A SSE GÉN ÉRIQ UE)

Les propriétés qui retournent des clés et des valeurs sont Aucune récupération indexée.
indexées, ce qui permet une récupération indexée efficace.

La récupération est O(log n ). La récupération est O(log n ).

L’insertion et la suppression correspondent généralement à O( L’insertion et la suppression sont O(log n ).


n ). Toutefois, l’insertion correspond à O(log n ) pour les
données qui se trouvent déjà en ordre de tri, afin que chaque
élément soit ajouté à la fin de la liste. (Cela suppose qu’un
redimensionnement n’est pas requis.)

Utilise moins de mémoire qu’un Utilise plus de mémoire que la classe non générique
SortedDictionary<TKey,TValue>. SortedList et la classe générique SortedList<TKey,TValue>.

Pour les listes ou les dictionnaires triés qui doivent être accessibles simultanément à partir de plusieurs threads,
vous pouvez ajouter une logique de tri à une classe qui dérive de ConcurrentDictionary<TKey,TValue>. Lorsque
vous envisagez l’immuabilité, les types immuables correspondants suivants suivent la même sémantique de tri :
ImmutableSortedSet<T> et ImmutableSortedDictionary<TKey,TValue> .
NOTE
Pour les valeurs qui contiennent leurs propres clés (par exemple, des enregistrements d’employés qui contiennent un numéro
d’ID de l’employé), vous pouvez créer une collection à clé qui possède certaines caractéristiques d’une liste et certaines
caractéristiques d’un dictionnaire en dérivant de la classe générique KeyedCollection<TKey,TItem>.

À partir de .NET Framework 4, la classe SortedSet<T> fournit une arborescence autonome qui maintient les
données triées dans un certain ordre après les insertions, les suppressions et les recherches. Cette classe et la
classe HashSet<T> implémentent l’interface ISet<T>.

Voir aussi
System.Collections.IDictionary
System.Collections.Generic.IDictionary<TKey,TValue>
ConcurrentDictionary<TKey,TValue>
Types de collections couramment utilisés
Types collection Hashtable et Dictionary
18/07/2020 • 4 minutes to read • Edit Online

La classe System.Collections.Hashtable, ainsi que les classes génériques


System.Collections.Generic.Dictionary<TKey,TValue> et
System.Collections.Concurrent.ConcurrentDictionary<TKey,TValue>, implémentent l'interface
System.Collections.IDictionary. La classe générique Dictionary<TKey,TValue> implémente également l'interface
générique IDictionary<TKey,TValue>. Par conséquent, chaque élément de ces collections est une paire clé-valeur.
Un objet Hashtable est constitué de compartiments contenant les éléments de la collection. Un compartiment est
un sous-groupe virtuel d'éléments dans la Hashtable, ce qui rend la recherche et la récupération plus facile et plus
rapide que dans la plupart des collections. Chaque compartiment est associé à un code de hachage qui est généré
à l'aide d'une fonction de hachage et qui est basé sur la clé de l'élément.
La classe générique HashSet<T> est une collection non ordonnée destinée à contenir des éléments uniques.
Une fonction de hachage est un algorithme qui retourne un code de hachage numérique basé sur une clé. La clé
est la valeur d'une propriété de l'objet stocké. Une fonction de hachage doit toujours retourner le même code de
hachage pour la même clé. Il est possible pour une fonction de hachage pour générer le même code de hachage
pour deux clés différentes, mais une fonction de hachage qui génère un code de hachage unique pour chaque clé
unique produit de meilleures performances lors de la récupération des éléments dans la table de hachage.
Chaque objet qui est utilisé comme un élément dans une table Hashtable doit être en mesure de générer un code
de hachage pour lui-même en utilisant une implémentation de la méthode GetHashCode. Cependant, vous pouvez
également spécifier une fonction de hachage pour tous les éléments d'une table Hashtable en utilisant un
constructeur Hashtable qui accepte une implémentation de IHashCodeProvider comme un de ses paramètres.
Quand un objet est ajouté à une table Hashtable, il est stocké dans le compartiment associé au code de hachage
qui correspond au code de hachage de l'objet. Quand une valeur est recherchée dans la Hashtable, le code de
hachage est généré pour cette valeur, et le compartiment associé à ce code de hachage est recherché.
Par exemple, une fonction de hachage pour une chaîne peut prendre les codes ASCII de chaque caractère de la
chaîne et les additionner pour générer un code de hachage. La chaîne "pique-nique" aurait ainsi un code de
hachage différent du code de hachage de la chaîne "basket". Les chaînes "pique-nique" et "basket" seraient donc
dans des compartiments différents. Par contre, "ail" et "lia" auraient le même code de hachage et seraient dans le
même compartiment.
Les classes Dictionary<TKey,TValue> et ConcurrentDictionary<TKey,TValue> ont les mêmes fonctionnalités que la
classe Hashtable. Un dictionnaire Dictionary<TKey,TValue> d'un type spécifique (autre que Object) offre de
meilleures performances qu'une table Hashtable pour les types valeur. La raison en est que les éléments de
Hashtable sont de type Object. Par conséquent, le boxing et la conversion unboxing se produisent généralement
quand vous stockez ou que vous récupérez un type valeur. La classe ConcurrentDictionary<TKey,TValue> doit être
utilisée quand plusieurs threads sont susceptibles d’accéder simultanément à la collection.

Voir aussi
Hashtable
IDictionary
IHashCodeProvider
Dictionary<TKey,TValue>
System.Collections.Generic.IDictionary<TKey,TValue>
System.Collections.Concurrent.ConcurrentDictionary<TKey,TValue>
Types de collections couramment utilisés
Collections thread-safe
18/07/2020 • 7 minutes to read • Edit Online

.NET Framework 4 introduit l’espace de noms System.Collections.Concurrent, qui contient plusieurs classes de
collection qui sont à la fois thread-safe et scalables. Plusieurs threads peuvent, sans risque et de façon efficace,
ajouter ou supprimer des éléments dans ces collections, sans nécessiter une synchronisation supplémentaire dans
le code utilisateur. Quand vous écrivez du code, utilisez des classes de collections simultanées si plusieurs threads
écrivent en même temps dans la collection. Si vous lisez seulement dans une collection partagée, vous pouvez
utiliser les classes de l’espace de noms System.Collections.Generic. Nous vous recommandons de ne pas utiliser
les classes de collections 1.0, à moins que vous ne deviez cibler le runtime .NET Framework 1.1 ou une version
antérieure.

Synchronisation de threads dans les collections .NET Framework 1.0


et 2.0
Les collections introduites dans le .NET Framework 1.0 se trouvent dans l’espace de noms System.Collections. Ces
collections, qui incluent les ArrayList et Hashtable fréquemment utilisés, garantissent une certaine cohérence des
threads par le biais de la propriété Synchronized , qui retourne un wrapper thread-safe autour de la collection. Le
wrapper fonctionne en verrouillant l’ensemble de la collection à chaque opération d’ajout ou de suppression. Par
conséquent, chaque thread qui tente d’accéder à la collection doit attendre son tour pour prendre le verrou. Ce
fonctionnement n’est pas évolutif et peut provoquer une importante dégradation des performances pour les
grandes collections. De même, la conception n’est pas complètement protégée contre la concurrence critique.
Pour plus d’informations, consultez Synchronisation dans les collections génériques.
Les classes de collection introduites dans le .NET Framework 2.0 se trouvent dans l’espace de noms
System.Collections.Generic. Elles comprennent notamment List<T>, Dictionary<TKey,TValue>, etc. Ces classes
garantissent une cohérence des types et des performances améliorées par rapport aux classes du .NET
Framework 1.0. Toutefois, les classes de collections .NET Framework 2.0 ne fournissent pas de synchronisation des
threads. Le code utilisateur doit fournir toute la synchronisation quand des éléments sont ajoutés ou supprimés
simultanément sur plusieurs threads.
Nous vous recommandons les classes de collections simultanées de .NET Framework 4, car elles offrent non
seulement la cohérence des types des classes de collections .NET Framework 2.0, mais également une cohérence
de thread plus efficace et plus complète que les collections .NET Framework 1.0.

Verrouillage de granularité fine et mécanismes sans verrou


Certains types de collections simultanées utilisent des mécanismes de synchronisation légers, comme SpinLock,
SpinWait, SemaphoreSlim et CountdownEvent, qui sont nouveaux dans .NET Framework 4. Ces types de
synchronisation utilisent généralement la rotation intensive pendant de courtes périodes avant de mettre le
thread dans un véritable état d’attente. Lorsque les temps d’attente sont supposés être très courts, la rotation est
beaucoup moins gourmande en ressources informatiques que l’attente, qui implique une transition de noyau
coûteuse. Pour les classes de collections qui utilisent la rotation, cette efficacité signifie que plusieurs threads
peuvent ajouter et supprimer des éléments à un taux très élevé. Pour plus d'informations sur la rotation et le
blocage, consultez SpinLock et SpinWait.
Les classes ConcurrentQueue<T> et ConcurrentStack<T> n’utilisent pas de verrous du tout. Au lieu de cela, elles
s’appuient sur des opérations Interlocked pour assurer la cohérence des threads.
NOTE
Étant donné que les classes de collections simultanées prennent en charge ICollection, elles fournissent des
implémentations pour les propriétés IsSynchronized et SyncRoot, bien que ces propriétés ne soient pas pertinentes.
IsSynchronized retourne toujours false et SyncRoot a toujours la valeur null ( Nothing dans Visual Basic).

Le tableau suivant répertorie les types de collections dans l’espace de noms System.Collections.Concurrent.

TYPE DESC RIP T IO N

BlockingCollection<T> Fournit des fonctionnalités de délimitation et de blocage pour


tous les types qui implémentent
IProducerConsumerCollection<T>. Pour plus d’informations,
consultez Vue d’ensemble de BlockingCollection.

ConcurrentDictionary<TKey,TValue> Implémentation thread-safe d’un dictionnaire de paires clé-


valeur.

ConcurrentQueue<T> Implémentation thread-safe d’une file d’attente FIFO (premier


entré, premier sorti).

ConcurrentStack<T> Implémentation thread-safe d’une pile LIFO (dernier entré,


premier sorti).

ConcurrentBag<T> Implémentation thread-safe d’une collection non ordonnée


d’éléments.

IProducerConsumerCollection<T> Interface qu’un type doit implémenter pour être utilisé dans
un BlockingCollection .

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Vue d'ensemble de BlockingCollection Décrit la fonctionnalité fournie par le type


BlockingCollection<T>.

Guide pratique : ajouter et supprimer des éléments d'un Décrit comment ajouter et supprimer des éléments dans un
ConcurrentDictionary ConcurrentDictionary<TKey,TValue>.

Guide pratique pour ajouter et prendre des éléments Décrit comment ajouter et récupérer des éléments dans une
individuellement dans un BlockingCollection collection de blocage sans utiliser l’énumérateur en lecture
seule.

Comment : ajouter des fonctionnalités de liaison et de Décrit comment utiliser une classe de collection comme
blocage à une collection mécanisme de stockage sous-jacent pour une collection
IProducerConsumerCollection<T>.

Comment : utiliser la boucle ForEach pour supprimer les Décrit comment utiliser foreach , ( For Each dans Visual
éléments d'un BlockingCollection Basic) pour supprimer tous les éléments d’une collection de
blocage.

Comment : utiliser des tableaux de collections de blocage Décrit comment utiliser simultanément plusieurs collections
dans un pipeline de blocage pour implémenter un pipeline.
IN T IT UL É DESC RIP T IO N

Guide pratique pour créer un pool d'objets à l'aide d'un Montre comment utiliser un conteneur simultané pour
ConcurrentBag améliorer les performances dans les scénarios où vous pouvez
réutiliser des objets au lieu d’en créer continuellement de
nouveaux.

Informations de référence
System.Collections.Concurrent
Valeurs numériques dans .NET
30/04/2020 • 8 minutes to read • Edit Online

.NET fournit une plage de primitives numériques pour les intégraux et les nombres à virgule flottante, ainsi que
System.Numerics.BigInteger, un type intégral sans limite théorique supérieure ou inférieure,
System.Numerics.Complex, qui représente des nombres complexes et un ensemble de types compatibles SIMD
dans l’espace de noms System.Numerics.

Types d'entier
.NET prend en charge à la fois les types d’entier signés et non signés 8, 16, 32 et 64 bits, répertoriés dans le tableau
suivant :

TYPE SIGN É/ N O N SIGN É TA IL L E ( EN O C T ET S) VA L EUR M IN IM A L E VA L EUR M A XIM A L E

System.Byte Non signé 1 0 255

System.Int16 Signé 2 -32,768 32 767

System.Int32 Signé 4 -2,147,483,648 2 147 483 647

System.Int64 Signé 8 - 9,223,372,036,854,77


9,223,372,036,854,77 5,807
5,808

System.SByte Signé 1 -128 127

System.UInt16 Non signé 2 0 65 535

System.UInt32 Non signé 4 0 4,294,967,295

System.UInt64 Non signé 8 0 18 446 744 073 709


551 615

Chaque type d’entier prend en charge un ensemble d’opérateurs arithmétiques standard. La classe System.Math
fournit des méthodes pour un ensemble plus large de fonctions mathématiques.
Vous pouvez également travailler avec les bits individuels d'une valeur d'entier en utilisant la classe
System.BitConverter.

NOTE
Les types d’entier non signés ne sont pas conformes à CLS. Pour plus d'informations, consultez Language Independence and
Language-Independent Components.

BigInteger
La structure System.Numerics.BigInteger est un type immuable qui représente un entier arbitrairement grand dont
la valeur en théorie n'a pas de limite supérieure ou inférieure. Les méthodes du type BigInteger sont très proches
de celles des autres types intégraux.
Types virgule flottante
.NET comprend trois types à virgule flottante primitifs, qui sont répertoriés dans le tableau suivant :

TYPE TA IL L E ( EN O C T ET S) P L A GE A P P RO XIM AT IVE P REC ISIO N

System.Single 4 ±1,5 x 10− 45 à ±3,4 x 1038 ~6-9 chiffres

System.Double 8 De ±5,0 × 10− 324 à ±1,7 × ~15-17 chiffres


10308

System.Decimal 16 ±1,0 x 10-28 to ±7,9228 x 28 à 29 chiffres


1028

Les deux types Single et Double prennent en charge des valeurs spéciales qui représentent une valeur NaN (N’est
pas un nombre) et l’infini. Par exemple, le type Double fournit les valeurs suivantes : Double.NaN,
Double.NegativeInfinity et Double.PositiveInfinity. Vous utilisez les méthodes Double.IsNaN, Double.IsInfinity,
Double.IsPositiveInfinity et Double.IsNegativeInfinity pour tester ces valeurs spéciales.
Chaque type à virgule flottante prend en charge un ensemble d’opérateurs arithmétiques standard. La classe
System.Math fournit des méthodes pour un ensemble plus large de fonctions mathématiques. .NET Core 2,0 et
versions ultérieures System.MathF incluent la classe, qui fournit des méthodes qui acceptent Single des arguments
du type.
Vous pouvez également travailler avec les bits individuels de valeurs Double et Single en utilisant la classe
System.BitConverter. La structure System.Decimal a ses propres méthodes, Decimal.GetBits et Decimal(Int32[])
pour travailler avec les bits individuel d'une valeur décimale, ainsi que son propre ensemble de méthodes pour
effectuer d'autres opérations mathématiques.
Les Double types Single et sont destinés à être utilisés pour les valeurs qui, par nature, sont imprécises (par
exemple, la distance entre deux étoiles) et pour les applications dans lesquelles un degré élevé de précision et une
erreur d’arrondi réduite ne sont pas requis. Utilisez le System.Decimal type pour les cas où une plus grande
précision est nécessaire et où les erreurs d’arrondi doivent être réduites.

NOTE
Le type Decimal n’élimine pas la nécessité d’arrondi. Au lieu de cela, il réduit les erreurs dues à l’arrondi.

Complex
La structure System.Numerics.Complex représente un nombre complexe, c'est-à-dire un nombre avec une partie
réelle et une partie imaginaire. Elle prend en charge un ensemble standard d'opérateurs arithmétiques, de
comparaison, d'égalité, de conversion explicite et implicite, ainsi que des méthodes mathématiques, algébriques et
trigonométriques.

Types SIMD
L’espace de noms System.Numerics comprend un ensemble de types compatibles SIMD pour .NET. Les opérations
SIMD (Single Instruction Multiple Data) peuvent être parallélisées au niveau du matériel. Cela augmente le débit
des calculs vectorisés, couramment utilisés dans les applications mathématiques, scientifiques et graphiques.
Les types .NET compatibles SIMD sont les suivants :
Les types Vector2, Vector3 et Vector4, qui représentent des vecteurs à 2, 3 et 4 valeurs Single.
Deux types de matrices, Matrix3x2, qui représente une matrice 3 x 2, et Matrix4x4, qui représente une
matrice 4 x 4.
Le type Plane, qui représente un plan dans un espace à trois dimensions.
Le type Quaternion, qui représente un vecteur utilisé pour encoder des rotations physiques en trois
dimensions.
Le type Vector<T>, qui représente un vecteur d’un type numérique spécifié et fournit un large éventail
d’opérateurs bénéficiant d’un support SIMD. Le nombre d’une instance Vector<T> est fixe, mais sa valeur
Vector<T>.Count dépend de l’UC de l’ordinateur sur lequel le code est exécuté.

NOTE
Le type Vector<T> n’est pas inclus dans .NET Framework. Vous devez installer le package NuGet
System.Numerics.Vectors pour accéder à ce type.

Les types compatibles SIMD sont implémentés de telle sorte qu’ils peuvent être utilisés avec du matériel non
compatible SIMD ou des compilateurs JIT. Pour tirer parti des instructions SIMD, vos applications 64 bits doivent
être exécutées par le runtime qui utilise le compilateur RyuJIT, inclus dans .NET Core et dans .NET Framework 4.6 et
versions ultérieures. Il ajoute la prise en charge SIMD lors du ciblage de processeurs 64 bits.
Pour plus d’informations, consultez utiliser des types numériques SIMD accélérés.

Voir aussi
Chaînes de format numériques standard
Utiliser des types numériques SIMD accélérés
30/04/2020 • 5 minutes to read • Edit Online

SIMD (instruction unique, plusieurs données) fournit une prise en charge matérielle pour effectuer une opération
sur plusieurs éléments de données, en parallèle, à l’aide d’une seule instruction. Dans .NET, il existe un ensemble de
types accélérés SIMD sous l' System.Numerics espace de noms. Les opérations SIMD peuvent être parallèles au
niveau du matériel. Cela augmente le débit des calculs vectorisés, couramment utilisés dans les applications
mathématiques, scientifiques et graphiques.

Types accélérés SIMD .NET


Les types accélérés par le .NET SIMD incluent les types suivants :
Les types Vector2, Vector3 et Vector4, qui représentent des vecteurs à 2, 3 et 4 valeurs Single.
Deux types de matrices, Matrix3x2, qui représente une matrice matrice Matrix4x4, et, qui représente une
matrice Single 4x4 de valeurs.
Plane Type, qui représente un plan dans l’espace tridimensionnel à l’aide Single de valeurs.
Quaternion Type, qui représente un vecteur utilisé pour encoder des rotations physiques en trois
dimensions à Single l’aide de valeurs.
Le type Vector<T>, qui représente un vecteur d’un type numérique spécifié et fournit un large éventail
d’opérateurs bénéficiant d’un support SIMD. Le nombre d’une Vector<T> instance est fixe pour la durée de
vie d’une application, mais Vector<T>.Count sa valeur dépend de l’UC de l’ordinateur qui exécute le code.

NOTE
Le Vector<T> type n’est pas inclus dans le .NET Framework. Vous devez installer le package NuGet
System.Numerics.Vectors pour accéder à ce type.

Les types accélérés SIMD sont implémentés de telle sorte qu’ils peuvent être utilisés avec le matériel ou les
compilateurs JIT non-SIMD. Pour tirer parti des instructions SIMD, vos applications 64 bits doivent être exécutées
par le runtime qui utilise le compilateur RyuJIT . Un compilateur RyuJIT est inclus dans .net Core et dans .NET
Framework 4,6 et versions ultérieures. La prise en charge SIMD est fournie uniquement lorsque vous ciblez des
processeurs 64 bits.

Comment utiliser SIMD ?


Avant d’exécuter des algorithmes SIMD personnalisés, il est possible de vérifier si l’ordinateur hôte prend en charge
Vector.IsHardwareAcceleratedSIMD à l’aide Booleande, qui retourne un. Cela ne garantit pas que l’accélération
SIMD est activée pour un type spécifique, mais qu’elle est prise en charge par certains types.

Vecteurs simples
Les types accélérés SIMD les plus primitifs dans .NET Vector2sont Vector3les types Vector4 , et, qui représentent
des vecteurs avec 2, 3 et Single 4 valeurs. L’exemple ci- Vector2 dessous utilise pour ajouter deux vecteurs.
var v1 = new Vector2(0.1f, 0.2f);
var v2 = new Vector2(1.1f, 2.2f);
var vResutl = v1 + v2;

Il est également possible d’utiliser des vecteurs .net pour calculer d’autres propriétés mathématiques de vecteurs
Dot product tels Transform que Clamp ,, etc.

var v1 = new Vector2(0.1f, 0.2f);


var v2 = new Vector2(1.1f, 2.2f);
var vResutl1 = Vector2.Dot(v1, v2);
var vResutl2 = Vector2.Distance(v1, v2);
var vResutl3 = Vector2.Clamp(v1, Vector2.Zero, Vector2.One);

Matrix
Matrix3x2, qui représente une matrice matrice, et Matrix4x4, qui représente une matrice 4x4. Peut être utilisé pour
les calculs liés à la matrice. L’exemple ci-dessous illustre la multiplication d’une matrice en sa matrice transposée
correspondante à l’aide de SIMD.

var m1 = new Matrix4x4(


1.1f, 1.2f, 1.3f, 1.4f,
2.1f, 2.2f, 3.3f, 4.4f,
3.1f, 3.2f, 3.3f, 3.4f,
4.1f, 4.2f, 4.3f, 4.4f);

var m2 = Matrix4x4.Transpose(m1);
var mResult = Matrix4x4.Multiply(m1, m2);

Vecteur<T>
Le Vector<T> donne la possibilité d’utiliser des vecteurs plus longs. Le nombre d’une Vector<T> instance est fixe,
mais sa valeur Vector<T>.Count dépend de l’UC de l’ordinateur qui exécute le code.
L’exemple ci-dessous montre comment ajouter des éléments Vector<T>de tableaux longs à l’aide de.

double[] SimdVectorProd(double[] left, double[] right)


{
var offset = Vector<double>.Count;
double[] result = new double[left.Length];
int i = 0;
for (i = 0; i < left.Length; i += offset)
{
var v1 = new Vector<double>(left, i);
var v2 = new Vector<double>(right, i);
(v1 * v2).CopyTo(result, i);
}

//remaining items
for (; i < left.Length; ++i)
{
result[i] = left[i] * right[i];
}

return result;
}
Notes
SIMD est plus susceptible de supprimer un goulot d’étranglement et d’exposer le prochain, par exemple le débit de
mémoire. En général, les avantages en matière de performances de l’utilisation de SIMD varient en fonction du
scénario spécifique et, dans certains cas, il peut même s’exécuter pire qu’un code équivalent non-SIMD plus simple.
Dates, heures et fuseaux horaires
18/07/2020 • 6 minutes to read • Edit Online

En plus de la structure DateTime élémentaire, .NET fournit les classes suivantes qui prennent en charge l’utilisation
des fuseaux horaires :
TimeZone
Utilisez cette classe pour travailler avec le fuseau horaire local du système et le fuseau horaire UTC. Les
fonctionnalités de la TimeZone classe sont en grande partie remplacées par la TimeZoneInfo classe.
TimeZoneInfo
Utilisez cette classe pour travailler avec tout fuseau horaire prédéfini sur un système, créer des fuseaux
horaires et convertir facilement des dates et des heures d'un fuseau horaire à un autre. Pour tout nouveau
développement, utilisez la classe TimeZoneInfo plutôt que la classe TimeZone.
DateTimeOffset
Utilisez cette structure pour travailler avec des dates et heures dont le décalage (ou différence) par rapport à
l'heure UTC est connu. La structure DateTimeOffset associe une valeur de date et d’heure au décalage de
cette heure par rapport à l’heure UTC. En raison de sa relation avec l’heure UTC, une valeur individuelle de
date et d’heure identifie de façon univoque un point unique dans le temps. Cela rend une valeur
DateTimeOffset plus portable d’un ordinateur à un autre par rapport à une valeur DateTime.
Cette section de la documentation fournit les informations dont vous avez besoin pour travailler avec les fuseaux
horaires et pour créer des applications prenant en charge les fuseaux horaires, qui peuvent convertir des dates et
des heures d’un fuseau horaire à un autre.

Dans cette section


Vue d’ensemble des fuseaux horaires Décrit la terminologie, les concepts et les problèmes impliqués dans la
création d’applications prenant en charge les fuseaux horaires.
Choix entre DateTime, DateTimeOffset, TimeSpan et TimeZoneInfo Explique quand utiliser les DateTime
DateTimeOffset types, et TimeZoneInfo lors de l’utilisation de données de date et d’heure.
Recherche des fuseaux horaires définis sur un système local Décrit comment énumérer les fuseaux horaires trouvés
sur un système local.
Guide pratique pour énumérer les fuseaux horaires d’un ordinateur Fournit des exemples qui énumèrent les
fuseaux horaires définis dans le Registre d’un ordinateur et qui permettent aux utilisateurs de sélectionner un
fuseau horaire prédéfini dans une liste.
Guide pratique pour accéder aux objets UTC et aux objets de fuseau horaire local prédéfinis Décrit comment
accéder au temps universel coordonné et au fuseau horaire local.
Guide pratique : instancier un objet TimeZoneInfo Décrit comment instancier un objet TimeZoneInfo à partir du
Registre système local.
Instanciation d’un objet DateTimeOffset Décrit comment un objet DateTimeOffset peut être instancié et comment
une valeur DateTime peut être convertie en valeur DateTimeOffset.
Guide pratique pour créer des fuseaux horaires sans règles d’ajustement Explique comment créer un fuseau horaire
personnalisé qui ne prend pas en charge la transition vers et depuis l’heure d’été.
Guide pratique pour créer des fuseaux horaires avec des règles d’ajustement Explique comment créer un fuseau
horaire personnalisé qui prend en charge une ou plusieurs transitions vers et depuis l’heure d’été.
Enregistrement et restauration de fuseaux horaires Décrit la prise en charge de TimeZoneInfo pour la sérialisation
et la désérialisation des données de fuseau horaire et présente certains des scénarios dans lesquels ces
fonctionnalités peuvent être utilisées.
Guide pratique pour enregistrer des fuseaux horaires dans une ressource incorporée Explique comment créer un
fuseau horaire personnalisé et enregistrer ses informations dans un fichier de ressources.
Guide pratique pour restaurer des fuseaux horaires dans une ressource incorporée Explique comment instancier
des fuseaux horaires personnalisés qui ont été enregistrés dans un fichier de ressources incorporées.
Exécution d’opérations arithmétiques avec des dates et heures Traite les problèmes liés à l’ajout, la soustraction et la
comparaison de valeurs DateTime et DateTimeOffset.
Guide pratique pour utiliser des fuseaux horaires dans des opérations arithmétiques de date et d’heure Explique
comment effectuer des opérations arithmétiques de date et d’heure qui reflètent les règles d’ajustement d’un
fuseau horaire.
Conversion entre DateTime et DateTimeOffset Décrit comment effectuer une conversion entre des valeurs
DateTime et DateTimeOffset.
Conversion d’heures entre fuseaux horaires Décrit comment convertir les heures d’un fuseau horaire dans un autre
fuseau horaire.
Guide pratique pour résoudre des heures ambiguës Décrit comment résoudre une heure ambiguë en la mappant à
l’heure d’hiver du fuseau horaire.
Guide pratique pour permettre aux utilisateurs de résoudre des heures ambiguës Décrit comment permettre à un
utilisateur de déterminer le mappage entre une heure locale ambiguë et le temps universel coordonné.

Référence
System.TimeZoneInfo
Gestion et déclenchement d'événements
18/07/2020 • 15 minutes to read • Edit Online

Les événements dans .NET Framework sont basés sur le modèle délégué. Le modèle délégué suit le modèle de
conception observateur, qui permet à un abonné de s'inscrire pour recevoir des notifications d'un fournisseur. Un
émetteur d'événements émet une notification d'événement, et un récepteur d'événements reçoit cette notification
et définit une réponse à celle-ci. Cet article décrit les principaux composants du modèle délégué, comment
consommer les événements des applications, et comment implémenter des événements dans votre code.
Pour plus d’informations sur la gestion des événements dans les applications Windows 8.x Store, consultez Vue
d’ensemble des événements et des événements routés.

Événements
Un événement est un message envoyé par un objet pour signaler la présence d’une action. L'action peut être
provoquée par l'intervention de l'utilisateur, telle qu'un clic de bouton, ou être déclenchée par une autre logique
de programme, comme la modification d’une valeur de propriété. L’objet qui déclenche l’événement est appelé
l’émetteur d’événements. L'émetteur d'événements ne connaît pas l'objet, ni la méthode qui recevront (géreront)
les événements qu'il déclenche. L'événement est généralement un membre de l'émetteur d'événements ; par
exemple, l'événement Click est membre de la classe Button, et l'événement PropertyChanged est membre de la
classe qui implémente l'interface INotifyPropertyChanged.
Pour définir un événement, vous utilisez le event mot clé C# ou Visual Basic Event dans la signature de votre
classe d’événements, puis vous spécifiez le type de délégué pour l’événement. Les délégués sont décrits dans la
section suivante.
En général, pour déclencher un événement, ajoutez une méthode qui est marquée comme protected et virtual
(C#) ou Protected et Overridable (en Visual Basic). Nommez cette méthode On EventName ; par exemple,
OnDataReceived . La méthode doit prendre un paramètre qui spécifie un objet de données d'événement, qui est un
objet de type EventArgs ou un type dérivé. Vous fournissez cette méthode pour permettre aux classes dérivées de
substituer la logique de déclenchement d'événement. Une classe dérivée doit toujours appeler la méthode On
EventName de la classe de base pour garantir que les délégués inscrits reçoivent l’événement.
L'exemple suivant montre comment déclarer un évènement appelé ThresholdReached . L'événement est associé au
délégué EventHandler et déclenché dans une méthode nommée OnThresholdReached .

class Counter
{
public event EventHandler ThresholdReached;

protected virtual void OnThresholdReached(EventArgs e)


{
EventHandler handler = ThresholdReached;
handler?.Invoke(this, e);
}

// provide remaining implementation for the class


}
Public Class Counter
Public Event ThresholdReached As EventHandler

Protected Overridable Sub OnThresholdReached(e As EventArgs)


RaiseEvent ThresholdReached(Me, e)
End Sub

' provide remaining implementation for the class


End Class

Délégués
Un délégué est un type qui détient une référence à une méthode. Un délégué est déclaré avec une signature qui
indique le type de retour et les paramètres des méthodes qu'il référence, et il peut contenir des références à des
méthodes qui correspondent à sa signature. Un délégué est donc équivalent à un rappel ou un pointeur de
fonction de type sécurisé. Une déclaration Delegate suffit à définir une classe déléguée.
Les délégués ont de nombreux usages dans .NET. Dans le contexte des événements, un délégué est un
intermédiaire (ou un mécanisme similaire aux pointeurs) entre la source d'événements et le code qui gère
l'événement. Vous associez un délégué à un événement en incluant le type de délégué dans la déclaration de
l'événement, comme indiqué dans l'exemple de la section précédente. Pour plus d'informations sur les délégués,
consultez la classe Delegate.
.NET fournit les délégués EventHandler et EventHandler<TEventArgs> pour prendre en charge la plupart des
scénarios d'événement. Utilisez le délégué EventHandler pour tous les événements qui n'incluent pas de données
d'événement. Utilisez le délégué EventHandler<TEventArgs> pour les événements qui incluent des données sur
l'événement. Ces délégués n’ont aucune valeur de type de retour et prennent deux paramètres (un objet pour la
source de l’événement et un objet pour les données d’événement).
Les délégués sont multidiffusion, ce qui signifie qu'ils peuvent contenir des références à plusieurs méthodes de
gestion des événements. Pour plus d'informations, consultez la page de référence Delegate. Les délégués assurent
une souplesse et un contrôle précis lors de la gestion des événements. Un délégué agit comme un répartiteur
d’événements pour la classe qui déclenche l’événement en gérant une liste de gestionnaires d’événements inscrits
pour l’événement.
Pour les scénarios dans lesquels les délégués EventHandler et EventHandler<TEventArgs> ne fonctionnent pas,
vous pouvez définir un délégué. Les scénarios qui nécessitent de définir un délégué sont très rares, par exemple
lorsque vous devez utiliser du code qui ne reconnaît pas les génériques. Vous marquez un délégué avec le
delegate mot clé C# et Visual Basic Delegate dans la déclaration. L'exemple suivant montre comment déclarer
un délégué nommé ThresholdReachedEventHandler .

public delegate void ThresholdReachedEventHandler(object sender, ThresholdReachedEventArgs e);

Public Delegate Sub ThresholdReachedEventHandler(sender As Object, e As ThresholdReachedEventArgs)

Données d’événement
Les données associées à un événement peuvent être obtenues via une classe de données d'événement. .NET
fournit plusieurs classes de données d'événement que vous pouvez utiliser dans vos applications. Par exemple, la
classe SerialDataReceivedEventArgs est la classe de données d'événement pour l'événement
SerialPort.DataReceived. .NET suit un modèle d'affectation de nom qui veut que toutes les classes de données
d'évènement se terminent par EventArgs . Pour savoir quelle classe de données d'événement est associée à un
événement, il suffit d'examiner le délégué de l'événement. Par exemple, le délégué
SerialDataReceivedEventHandler inclut la classe SerialDataReceivedEventArgs comme paramètre.
La classe EventArgs est le type de base pour toutes les classes de données d'événement. EventArgs est également
la classe que vous utilisez quand un événement n'a pas de données associée. Lorsque vous créez un événement
qui vise uniquement à notifier à d'autres classes que quelque chose est survenu et n'a pas besoin de passer des
données, incluez la classe EventArgs comme deuxième paramètre du délégué. Vous pouvez passer la valeur
EventArgs.Empty lorsqu'aucune donnée n'est fournie. Le délégué EventHandler inclut la classe EventArgs comme
paramètre.
Lorsque vous souhaitez créer une classe personnalisée de données d'événement, créez une classe qui dérive de
EventArgs, puis fournissez tous les membres requis pour passer les données liées à l'événement. En général, vous
devriez utiliser le même modèle d'affectation de noms que .NET et terminer le nom de classe de données
d'événement par EventArgs .
L'exemple suivant illustre une classe de données d'événement nommée ThresholdReachedEventArgs . Elle contient
les propriétés spécifiques à l'événement déclenché.

public class ThresholdReachedEventArgs : EventArgs


{
public int Threshold { get; set; }
public DateTime TimeReached { get; set; }
}

Public Class ThresholdReachedEventArgs


Inherits EventArgs

Public Property Threshold As Integer


Public Property TimeReached As DateTime
End Class

Gestionnaires d’événements
Pour répondre à un événement, vous définissez une méthode de gestion d'événements dans le récepteur
d'événements. Cette méthode doit correspondre à la signature du délégué pour l'événement géré. Dans le
gestionnaire des évènements, vous exécutez les actions nécessaires lorsque l'événement est déclenché, comme la
collecte de l'entrée d'utilisateur après que l'utilisateur clique sur un bouton. Pour recevoir des notifications lorsque
l'événement se produit, la méthode de votre gestionnaire d'événements doit s'abonner à l'événement.
L'exemple suivant présente une méthode de gestionnaire d'événements nommée c_ThresholdReached qui
correspond à la signature du délégué EventHandler. La méthode s'abonne à l'événement ThresholdReached .

class Program
{
static void Main()
{
var c = new Counter();
c.ThresholdReached += c_ThresholdReached;

// provide remaining implementation for the class


}

static void c_ThresholdReached(object sender, EventArgs e)


{
Console.WriteLine("The threshold was reached.");
}
}
Module Module1

Sub Main()
Dim c As New Counter()
AddHandler c.ThresholdReached, AddressOf c_ThresholdReached

' provide remaining implementation for the class


End Sub

Sub c_ThresholdReached(sender As Object, e As EventArgs)


Console.WriteLine("The threshold was reached.")
End Sub
End Module

Gestionnaires d’événements statiques et dynamiques


.NET permet aux abonnés de s’inscrire pour les notifications d’événements statiques ou dynamiques. Les
gestionnaires d’événements statiques sont en vigueur pendant toute la durée de vie de la classe dont ils gèrent les
événements. Les gestionnaires d’événements dynamiques sont explicitement activés et désactivés pendant
l’exécution du programme, généralement en réponse à une logique de programme conditionnelle. Par exemple, ils
peuvent être utilisés si les notifications d’événements sont nécessaires uniquement dans certaines conditions ou si
une application fournit plusieurs gestionnaires d’événements et les conditions d’exécution définissent le
gestionnaire approprié à utiliser. L'exemple de la section précédente indique comment ajouter dynamiquement un
gestionnaire d'événements. Pour plus d’informations, consultez Événements (en Visual Basic) et Événements (en
C#).

Déclenchement de plusieurs événements


Si votre classe déclenche plusieurs événements, le compilateur génère un champ par instance de délégué
d'événement. Si le nombre d’événements est important, le coût de stockage d’un champ par délégué peut ne pas
convenir. Dans ce cas, .NET fournit les propriétés de l’événement que vous pouvez utiliser avec une autre structure
de données de votre choix pour stocker les délégués d’événements.
Les propriétés de l’événement se composent de déclarations d’événement accompagnées d’accesseurs
d’événement. Les accesseurs d'événement sont des méthodes que vous définissez pour que des instances de
délégué d'événement puissent être ajoutées ou supprimées de la structure des données de stockage. Notez que
les propriétés d'événement sont plus lentes que les champs d'événement, car chaque délégué d'événement doit
être récupéré avant de pouvoir être appelé. Le compromis réside entre la mémoire et la vitesse. Si votre classe
définit de nombreux événements qui sont déclenchés peu fréquemment, vous souhaiterez implémenter les
propriétés de l’événement. Pour plus d’informations, consultez Comment : gérer plusieurs événements à l’aide des
propriétés d’événements.

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Comment : déclencher et utiliser des événements Contient des exemples de déclenchement et de


consommation d'événements.

Comment : gérer plusieurs événements à l'aide des propriétés Montre comment utiliser des propriétés d'événement pour
d'événements gérer plusieurs événements.

Modèle de conception observateur Décrit le modèle de conception qui permet à un abonné de


s’inscrire pour recevoir des notifications d’un fournisseur.
IN T IT UL É DESC RIP T IO N

Comment : consommer des événements dans une application Montre comment gérer un événement déclenché par un
Web Forms contrôle Web Forms.

Voir aussi
EventHandler
EventHandler<TEventArgs>
EventArgs
Delegate
Événements (Visual Basic)
Événements (Guide de programmation C#)
Vue d’ensemble des événements et des événements routés (applications UWP)
Comment : déclencher et utiliser des événements
18/07/2020 • 6 minutes to read • Edit Online

Les exemples de cette rubrique montrent comment utiliser des événements. Ils incluent des exemples du délégué
EventHandler, le délégué EventHandler<TEventArgs> et un délégué personnalisé, pour illustrer les événements
avec et sans données.
Les exemples utilisent les concepts décrits dans l’article Événements.

Exemple
Le premier exemple montre comment déclencher et consommer un événement n’ayant pas de données. Il contient
une classe nommée Counter qui possède un événement nommé ThresholdReached . Cet événement est déclenché
lorsqu’une valeur de compteur atteint ou dépasse un seuil. Le délégué EventHandler est associé à l’événement, car
aucune donnée d’événement n’est fournie.
using System;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Counter c = new Counter(new Random().Next(10));
c.ThresholdReached += c_ThresholdReached;

Console.WriteLine("press 'a' key to increase total");


while (Console.ReadKey(true).KeyChar == 'a')
{
Console.WriteLine("adding one");
c.Add(1);
}
}

static void c_ThresholdReached(object sender, EventArgs e)


{
Console.WriteLine("The threshold was reached.");
Environment.Exit(0);
}
}

class Counter
{
private int threshold;
private int total;

public Counter(int passedThreshold)


{
threshold = passedThreshold;
}

public void Add(int x)


{
total += x;
if (total >= threshold)
{
OnThresholdReached(EventArgs.Empty);
}
}

protected virtual void OnThresholdReached(EventArgs e)


{
EventHandler handler = ThresholdReached;
if (handler != null)
{
handler(this, e);
}
}

public event EventHandler ThresholdReached;


}
}
Module Module1

Sub Main()
Dim c As Counter = New Counter(New Random().Next(10))
AddHandler c.ThresholdReached, AddressOf c_ThresholdReached

Console.WriteLine("press 'a' key to increase total")


While Console.ReadKey(True).KeyChar = "a"
Console.WriteLine("adding one")
c.Add(1)
End While
End Sub

Sub c_ThresholdReached(sender As Object, e As EventArgs)


Console.WriteLine("The threshold was reached.")
Environment.Exit(0)
End Sub
End Module

Class Counter
Private threshold As Integer
Private total As Integer

Public Sub New(passedThreshold As Integer)


threshold = passedThreshold
End Sub

Public Sub Add(x As Integer)


total = total + x
If (total >= threshold) Then
OnThresholdReached(EventArgs.Empty)
End If
End Sub

Protected Overridable Sub OnThresholdReached(e As EventArgs)


RaiseEvent ThresholdReached(Me, e)
End Sub

Public Event ThresholdReached As EventHandler


End Class

Exemple
L’exemple suivant montre comment déclencher et consommer un événement qui fournit des données. Le délégué
EventHandler<TEventArgs> est associé à l’événement, et une instance d’un objet de données d’événement
personnalisé est fournie.
using namespace System;

public ref class ThresholdReachedEventArgs : public EventArgs


{
public:
property int Threshold;
property DateTime TimeReached;
};

public ref class Counter


{
private:
int threshold;
int total;

public:
Counter() {};

Counter(int passedThreshold)
{
threshold = passedThreshold;
}

void Add(int x)
{
total += x;
if (total >= threshold) {
ThresholdReachedEventArgs^ args = gcnew ThresholdReachedEventArgs();
args->Threshold = threshold;
args->TimeReached = DateTime::Now;
OnThresholdReached(args);
}
}

event EventHandler<ThresholdReachedEventArgs^>^ ThresholdReached;

protected:
virtual void OnThresholdReached(ThresholdReachedEventArgs^ e)
{
ThresholdReached(this, e);
}
};

public ref class SampleHandler


{
public:
static void c_ThresholdReached(Object^ sender, ThresholdReachedEventArgs^ e)
{
Console::WriteLine("The threshold of {0} was reached at {1}.",
e->Threshold, e->TimeReached);
Environment::Exit(0);
}
};

void main()
{
Counter^ c = gcnew Counter((gcnew Random())->Next(10));
c->ThresholdReached += gcnew EventHandler<ThresholdReachedEventArgs^>(SampleHandler::c_ThresholdReached);

Console::WriteLine("press 'a' key to increase total");


while (Console::ReadKey(true).KeyChar == 'a') {
Console::WriteLine("adding one");
c->Add(1);
}
}
using System;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Counter c = new Counter(new Random().Next(10));
c.ThresholdReached += c_ThresholdReached;

Console.WriteLine("press 'a' key to increase total");


while (Console.ReadKey(true).KeyChar == 'a')
{
Console.WriteLine("adding one");
c.Add(1);
}
}

static void c_ThresholdReached(object sender, ThresholdReachedEventArgs e)


{
Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold, e.TimeReached);
Environment.Exit(0);
}
}

class Counter
{
private int threshold;
private int total;

public Counter(int passedThreshold)


{
threshold = passedThreshold;
}

public void Add(int x)


{
total += x;
if (total >= threshold)
{
ThresholdReachedEventArgs args = new ThresholdReachedEventArgs();
args.Threshold = threshold;
args.TimeReached = DateTime.Now;
OnThresholdReached(args);
}
}

protected virtual void OnThresholdReached(ThresholdReachedEventArgs e)


{
EventHandler<ThresholdReachedEventArgs> handler = ThresholdReached;
if (handler != null)
{
handler(this, e);
}
}

public event EventHandler<ThresholdReachedEventArgs> ThresholdReached;


}

public class ThresholdReachedEventArgs : EventArgs


{
public int Threshold { get; set; }
public DateTime TimeReached { get; set; }
}
}
Module Module1

Sub Main()
Dim c As Counter = New Counter(New Random().Next(10))
AddHandler c.ThresholdReached, AddressOf c_ThresholdReached

Console.WriteLine("press 'a' key to increase total")


While Console.ReadKey(True).KeyChar = "a"
Console.WriteLine("adding one")
c.Add(1)
End While
End Sub

Sub c_ThresholdReached(sender As Object, e As ThresholdReachedEventArgs)


Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold, e.TimeReached)
Environment.Exit(0)
End Sub
End Module

Class Counter
Private threshold As Integer
Private total As Integer

Public Sub New(passedThreshold As Integer)


threshold = passedThreshold
End Sub

Public Sub Add(x As Integer)


total = total + x
If (total >= threshold) Then
Dim args As ThresholdReachedEventArgs = New ThresholdReachedEventArgs()
args.Threshold = threshold
args.TimeReached = DateTime.Now
OnThresholdReached(args)
End If
End Sub

Protected Overridable Sub OnThresholdReached(e As ThresholdReachedEventArgs)


RaiseEvent ThresholdReached(Me, e)
End Sub

Public Event ThresholdReached As EventHandler(Of ThresholdReachedEventArgs)


End Class

Class ThresholdReachedEventArgs
Inherits EventArgs

Public Property Threshold As Integer


Public Property TimeReached As DateTime
End Class

Exemple
L'exemple suivant montre comment déclarer un délégué pour un événement. Le délégué est nommé
ThresholdReachedEventHandler . Ce n’est qu’une illustration. En règle générale, il est inutile de déclarer un délégué
pour un événement, car vous pouvez utiliser le délégué EventHandler ou le délégué EventHandler<TEventArgs>.
Vous devez déclarer un délégué uniquement dans de rares cas, par exemple, pour rendre votre classe disponible
pour le code hérité qui ne peut pas utiliser des classes génériques.

using System;

namespace ConsoleApplication1
{
class Program
class Program
{
static void Main(string[] args)
{
Counter c = new Counter(new Random().Next(10));
c.ThresholdReached += c_ThresholdReached;

Console.WriteLine("press 'a' key to increase total");


while (Console.ReadKey(true).KeyChar == 'a')
{
Console.WriteLine("adding one");
c.Add(1);
}
}

static void c_ThresholdReached(Object sender, ThresholdReachedEventArgs e)


{
Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold, e.TimeReached);
Environment.Exit(0);
}
}

class Counter
{
private int threshold;
private int total;

public Counter(int passedThreshold)


{
threshold = passedThreshold;
}

public void Add(int x)


{
total += x;
if (total >= threshold)
{
ThresholdReachedEventArgs args = new ThresholdReachedEventArgs();
args.Threshold = threshold;
args.TimeReached = DateTime.Now;
OnThresholdReached(args);
}
}

protected virtual void OnThresholdReached(ThresholdReachedEventArgs e)


{
ThresholdReachedEventHandler handler = ThresholdReached;
if (handler != null)
{
handler(this, e);
}
}

public event ThresholdReachedEventHandler ThresholdReached;


}

public class ThresholdReachedEventArgs : EventArgs


{
public int Threshold { get; set; }
public DateTime TimeReached { get; set; }
}

public delegate void ThresholdReachedEventHandler(Object sender, ThresholdReachedEventArgs e);


}
Module Module1

Sub Main()
Dim c As Counter = New Counter(New Random().Next(10))
AddHandler c.ThresholdReached, AddressOf c_ThresholdReached

Console.WriteLine("press 'a' key to increase total")


While Console.ReadKey(True).KeyChar = "a"
Console.WriteLine("adding one")
c.Add(1)
End While
End Sub

Sub c_ThresholdReached(sender As Object, e As ThresholdReachedEventArgs)


Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold, e.TimeReached)
Environment.Exit(0)
End Sub
End Module

Class Counter
Private threshold As Integer
Private total As Integer

Public Sub New(passedThreshold As Integer)


threshold = passedThreshold
End Sub

Public Sub Add(x As Integer)


total = total + x
If (total >= threshold) Then
Dim args As ThresholdReachedEventArgs = New ThresholdReachedEventArgs()
args.Threshold = threshold
args.TimeReached = DateTime.Now
OnThresholdReached(args)
End If
End Sub

Protected Overridable Sub OnThresholdReached(e As ThresholdReachedEventArgs)


RaiseEvent ThresholdReached(Me, e)
End Sub

Public Event ThresholdReached As ThresholdReachedEventHandler


End Class

Public Class ThresholdReachedEventArgs


Inherits EventArgs

Public Property Threshold As Integer


Public Property TimeReached As DateTime
End Class

Public Delegate Sub ThresholdReachedEventHandler(sender As Object, e As ThresholdReachedEventArgs)

Voir aussi
Événements
Comment : gérer plusieurs événements à l'aide des
propriétés d'événements
18/07/2020 • 6 minutes to read • Edit Online

Pour utiliser des propriétés d'événements, définissez les propriétés d'événements dans la classe qui déclenche les
événements, puis affectez les délégués pour les propriétés d'événements dans les classes qui gèrent les
événements. Pour implémenter plusieurs propriétés d'événements dans une classe, la classe doit stocker et
maintenir le délégué défini pour chaque événement en interne. Une approche courante consiste à implémenter
une collection de délégués indexée par une clé d’événement.
Pour stocker les délégués pour chaque événement, vous pouvez utiliser la classe EventHandlerList ou implémenter
votre propre collection. La classe de collection doit fournir des méthodes pour le paramétrage, l’accès et la
récupération du délégué de gestionnaire d’événements selon la clé d’événement. Par exemple, vous pouvez utiliser
une classe Hashtable ou dériver une classe personnalisée de la classe DictionaryBase. Les détails de
l’implémentation de la collection de délégués ne doivent pas être exposés à l’extérieur de votre classe.
Chaque propriété d'événement dans la classe définit une méthode d'accesseur add et une méthode d'accesseur
remove. L'accesseur add pour une propriété d'événement ajoute l'instance de délégué d'entrée à la collection de
délégués. L'accesseur remove pour une propriété d'événement supprime l'instance de délégué d'entrée de la
collection de délégués. Les accesseurs de propriété d'événement utilisent la clé prédéfinie pour la propriété
d'événement pour ajouter et supprimer des instances de la collection de délégués.
Pour gérer plusieurs événements à l'aide de propriétés d'événements
1. Définissez une collection de délégués dans la classe qui déclenche les événements.
2. Définissez une clé pour chaque événement.
3. Définissez les propriétés d'événements dans la classe qui déclenche les événements.
4. Utilisez la collection de délégués pour implémenter les méthodes d'accesseur add et remove pour les
propriétés d'événements.
5. Utilisez les propriétés d'événements publiques pour ajouter et supprimer des délégués de gestionnaire
d'événements dans les classes qui gèrent les événements.

Exemple
L'exemple C# suivant implémente les propriétés d'événements MouseDown et MouseUp , à l'aide d'un
EventHandlerList pour stocker le délégué de chaque événement. Les mots clés des constructions des propriétés
d'événements sont en gras.
// The class SampleControl defines two event properties, MouseUp and MouseDown.
ref class SampleControl : Component
{
// :
// Define other control methods and properties.
// :

// Define the delegate collection.


protected:
EventHandlerList^ listEventDelegates;

private:
// Define a unique key for each event.
static Object^ mouseDownEventKey = gcnew Object();
static Object^ mouseUpEventKey = gcnew Object();

// Define the MouseDown event property.


public:
SampleControl()
{
listEventDelegates = gcnew EventHandlerList();
}

event MouseEventHandler^ MouseDown


{
// Add the input delegate to the collection.
void add(MouseEventHandler^ value)
{
listEventDelegates->AddHandler(mouseDownEventKey, value);
}
// Remove the input delegate from the collection.
void remove(MouseEventHandler^ value)
{
listEventDelegates->RemoveHandler(mouseDownEventKey, value);
}
// Raise the event with the delegate specified by mouseDownEventKey
void raise(Object^ sender, MouseEventArgs^ e)
{
MouseEventHandler^ mouseEventDelegate =
(MouseEventHandler^)listEventDelegates[mouseDownEventKey];
mouseEventDelegate(sender, e);
}
}

// Define the MouseUp event property.


event MouseEventHandler^ MouseUp
{
// Add the input delegate to the collection.
void add(MouseEventHandler^ value)
{
listEventDelegates->AddHandler(mouseUpEventKey, value);
}
// Remove the input delegate from the collection.
void remove(MouseEventHandler^ value)
{
listEventDelegates->RemoveHandler(mouseUpEventKey, value);
}
// Raise the event with the delegate specified by mouseUpEventKey
void raise(Object^ sender, MouseEventArgs^ e)
{
MouseEventHandler^ mouseEventDelegate =
(MouseEventHandler^)listEventDelegates[mouseUpEventKey];
mouseEventDelegate(sender, e);
}
}
};
// The class SampleControl defines two event properties, MouseUp and MouseDown.
class SampleControl : Component
{
// :
// Define other control methods and properties.
// :

// Define the delegate collection.


protected EventHandlerList listEventDelegates = new EventHandlerList();

// Define a unique key for each event.


static readonly object mouseDownEventKey = new object();
static readonly object mouseUpEventKey = new object();

// Define the MouseDown event property.


public event MouseEventHandler MouseDown
{
// Add the input delegate to the collection.
add
{
listEventDelegates.AddHandler(mouseDownEventKey, value);
}
// Remove the input delegate from the collection.
remove
{
listEventDelegates.RemoveHandler(mouseDownEventKey, value);
}
}

// Raise the event with the delegate specified by mouseDownEventKey


private void OnMouseDown(MouseEventArgs e)
{
MouseEventHandler mouseEventDelegate =
(MouseEventHandler)listEventDelegates[mouseDownEventKey];
mouseEventDelegate(this, e);
}

// Define the MouseUp event property.


public event MouseEventHandler MouseUp
{
// Add the input delegate to the collection.
add
{
listEventDelegates.AddHandler(mouseUpEventKey, value);
}
// Remove the input delegate from the collection.
remove
{
listEventDelegates.RemoveHandler(mouseUpEventKey, value);
}
}

// Raise the event with the delegate specified by mouseUpEventKey


private void OnMouseUp(MouseEventArgs e)
{
MouseEventHandler mouseEventDelegate =
(MouseEventHandler)listEventDelegates[mouseUpEventKey];
mouseEventDelegate(this, e);
}
}
' The class SampleControl defines two event properties, MouseUp and MouseDown.
Class SampleControl
Inherits Component
' :
' Define other control methods and properties.
' :

' Define the delegate collection.


Protected listEventDelegates As New EventHandlerList()

' Define a unique key for each event.


Shared ReadOnly mouseDownEventKey As New Object()
Shared ReadOnly mouseUpEventKey As New Object()

' Define the MouseDown event property.


Public Custom Event MouseDown As MouseEventHandler
' Add the input delegate to the collection.
AddHandler(Value As MouseEventHandler)
listEventDelegates.AddHandler(mouseDownEventKey, Value)
End AddHandler
' Remove the input delegate from the collection.
RemoveHandler(Value As MouseEventHandler)
listEventDelegates.RemoveHandler(mouseDownEventKey, Value)
End RemoveHandler
' Raise the event with the delegate specified by mouseDownEventKey
RaiseEvent(sender As Object, e As MouseEventArgs)
Dim mouseEventDelegate As MouseEventHandler = _
listEventDelegates(mouseDownEventKey)
mouseEventDelegate(sender, e)
End RaiseEvent
End Event

' Define the MouseUp event property.


Public Custom Event MouseUp As MouseEventHandler
' Add the input delegate to the collection.
AddHandler(Value As MouseEventHandler)
listEventDelegates.AddHandler(mouseUpEventKey, Value)
End AddHandler
' Remove the input delegate from the collection.
RemoveHandler(Value As MouseEventHandler)
listEventDelegates.RemoveHandler(mouseUpEventKey, Value)
End RemoveHandler
' Raise the event with the delegate specified by mouseDownUpKey
RaiseEvent(sender As Object, e As MouseEventArgs)
Dim mouseEventDelegate As MouseEventHandler = _
listEventDelegates(mouseUpEventKey)
mouseEventDelegate(sender, e)
End RaiseEvent
End Event
End Class

Voir aussi
System.ComponentModel.EventHandlerList
Événements
Control.Events
Comment : déclarer des événements personnalisés pour économiser la mémoire
Comment : consommer des événements dans une
application Web Forms
18/07/2020 • 2 minutes to read • Edit Online

Un scénario courant dans les applications ASP.NET Web Forms consiste à remplir une page web avec des contrôles,
puis d’effectuer une action spécifique selon le contrôle sur lequel l’utilisateur clique. Par exemple, un contrôle
System.Web.UI.WebControls.Button déclenche un événement lorsque l’utilisateur clique dessus dans la page web.
En gérant l’événement, votre application peut exécuter la logique d’application appropriée pour ce clic de bouton.
Pour gérer un événement Click du bouton dans une page Web
1. Créez une page web ASP.NET Web Forms qui a un contrôle Button avec la valeur OnClick définie sur le nom
de la méthode que vous allez définir dans l’étape suivante.

<asp:Button ID="Button1" runat="server" Text="Click Me" OnClick="Button1_Click" />

2. Définissez un gestionnaire d’événements qui correspond à la signature du délégué d’événement Click et qui
porte le nom que vous avez défini pour la valeur OnClick .

protected void Button1_Click(object sender, EventArgs e)


{
// perform action
}

Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click


' perform action
End Sub

L’événement Click utilise la classe EventHandler pour le type délégué et la classe EventArgs pour les
données d’événement. L’infrastructure de page ASP.NET génère automatiquement du code qui crée une
instance de EventHandler et ajoute cette instance de délégué à l’événement Click de l’instance Button.
3. Dans la méthode de gestionnaire d’événements que vous avez définie à l’étape 2, ajoutez du code pour
effectuer les actions qui sont requises lorsque l’événement se produit.

Voir aussi
Événements
Modèle de design observateur
18/07/2020 • 20 minutes to read • Edit Online

Le modèle de design Observateur permet à un abonné de s’inscrire auprès d’un fournisseur et d’en recevoir des
notifications. Il convient pour les scénarios nécessitant des notifications selon le modèle push. Le modèle définit
un fournisseur (également appelé un sujet ou un observable) et zéro, un ou plusieurs observateurs. Les
observateurs s'inscrivent auprès du fournisseur et, chaque fois qu'une condition prédéfinie, un événement ou un
changement d'état se produit, le fournisseur notifie automatiquement tous les observateurs en appelant l'une de
leurs méthodes. Dans cet appel de méthode, le fournisseur peut également fournir des informations sur l'état
actuel aux observateurs. Dans le .NET Framework, le modèle de design Observateur est appliqué en implémentant
les interfaces génériques System.IObservable<T> et System.IObserver<T>. Le paramètre de type générique
représente le type qui fournit les informations de notification.

Application du modèle
Le modèle de design Observateur convient pour les notifications push distribuées, car il prend en charge une
séparation nette entre deux composants différents ou deux couches applicatives différentes, comme une couche
de source de données (logique métier) et une couche d'interface utilisateur (affichage). Le modèle peut être
implémenté chaque fois qu’un fournisseur utilise des rappels pour fournir les informations actuelles à ses clients.
Vous devez fournir les éléments suivants pour l’implémentation du modèle :
Un fournisseur ou un sujet, qui est l'objet qui envoie les notifications aux observateurs. Un fournisseur est
une classe ou une structure qui implémente l'interface IObservable<T>. Le fournisseur doit implémenter
une seule méthode, IObservable<T>.Subscribe, qui est appelée par les observateurs qui veulent recevoir
des notifications du fournisseur.
Un observateur, qui est un objet qui reçoit des notifications d'un fournisseur. Un observateur est une classe
ou une structure qui implémente l'interface IObserver<T>. L'observateur doit implémenter trois méthodes,
toutes étant appelées par le fournisseur :
IObserver<T>.OnNext, qui fournit des informations nouvelles ou actuelles à l'observateur.
IObserver<T>.OnError, qui informe l'observateur qu'une erreur s'est produite.
IObserver<T>.OnCompleted, qui indique que le fournisseur a terminé l'envoi des notifications.
Un mécanisme qui permet au fournisseur d'effectuer le suivi des observateurs. En règle générale, le
fournisseur utilise un objet conteneur, comme un objet System.Collections.Generic.List<T>, pour y placer
les références aux implémentations de IObserver<T> qui se sont abonnées aux notifications. L'utilisation
d'un conteneur de stockage à cet effet permet au fournisseur de gérer de zéro à un nombre illimité
d'observateurs. L'ordre dans lequel les observateurs reçoivent les notifications n'est pas défini ; le
fournisseur est libre d'utiliser n'importe quelle méthode pour déterminer l'ordre.
Une implémentation de IDisposable qui permet au fournisseur de supprimer les observateurs quand la
notification est effectuée. Les observateurs reçoivent une référence à l'implémentation de IDisposable de la
part de la méthode Subscribe, et ils peuvent donc également appeler la méthode IDisposable.Dispose pour
se désabonner avant que le fournisseur ait terminé l'envoi des notifications.
Un objet qui contient les données que le fournisseur envoie à ses observateurs. Le type de cet objet
correspond au paramètre de type générique des interfaces IObservable<T> et IObserver<T>. Bien que cet
objet puisse être le même que l'implémentation de IObservable<T>, il s'agit généralement d'un type
distinct.
NOTE
En plus d’implémenter le modèle de design Observateur, vous pouvez être intéressé par l’exploration des bibliothèques
générées à l’aide des interfaces IObservable<T> et IObserver<T>. Par exemple, les Extensions réactives pour .NET (Rx) se
composent d’un ensemble de méthodes d’extension et d’opérateurs de séquence standard LINQ pour prendre en charge la
programmation asynchrone.

Implémentation du modèle
L'exemple suivant utilise le modèle de design Observateur pour implémenter un système de restitution des
bagages d'un aéroport. Une classe BaggageInfo fournit des informations sur les vols arrivés et sur les tapis
roulants où les bagages de chaque vol peuvent être récupérés. Elle est montrée dans l'exemple suivant.

using System;
using System.Collections.Generic;

public class BaggageInfo


{
private int flightNo;
private string origin;
private int location;

internal BaggageInfo(int flight, string from, int carousel)


{
this.flightNo = flight;
this.origin = from;
this.location = carousel;
}

public int FlightNumber {


get { return this.flightNo; }
}

public string From {


get { return this.origin; }
}

public int Carousel {


get { return this.location; }
}
}
Public Class BaggageInfo
Private flightNo As Integer
Private origin As String
Private location As Integer

Friend Sub New(ByVal flight As Integer, ByVal from As String, ByVal carousel As Integer)
Me.flightNo = flight
Me.origin = from
Me.location = carousel
End Sub

Public ReadOnly Property FlightNumber As Integer


Get
Return Me.flightNo
End Get
End Property

Public ReadOnly Property From As String


Get
Return Me.origin
End Get
End Property

Public ReadOnly Property Carousel As Integer


Get
Return Me.location
End Get
End Property
End Class

Une classe BaggageHandler est responsable de la réception des informations sur les vols arrivés et sur les tapis
roulants de récupération des bagages. En interne, elle gère deux collections :
observers : une collection des clients qui recevront les informations mises à jour.
flights : une collection des vols et des tapis roulants qui leur sont affectés.

Les deux collections sont représentées par des objets List<T> génériques qui sont instanciés dans le constructeur
de classe BaggageHandler . Le code source de la classe BaggageHandler est montré dans l'exemple suivant.
public class BaggageHandler : IObservable<BaggageInfo>
{
private List<IObserver<BaggageInfo>> observers;
private List<BaggageInfo> flights;

public BaggageHandler()
{
observers = new List<IObserver<BaggageInfo>>();
flights = new List<BaggageInfo>();
}

public IDisposable Subscribe(IObserver<BaggageInfo> observer)


{
// Check whether observer is already registered. If not, add it
if (! observers.Contains(observer)) {
observers.Add(observer);
// Provide observer with existing data.
foreach (var item in flights)
observer.OnNext(item);
}
return new Unsubscriber<BaggageInfo>(observers, observer);
}

// Called to indicate all baggage is now unloaded.


public void BaggageStatus(int flightNo)
{
BaggageStatus(flightNo, String.Empty, 0);
}

public void BaggageStatus(int flightNo, string from, int carousel)


{
var info = new BaggageInfo(flightNo, from, carousel);

// Carousel is assigned, so add new info object to list.


if (carousel > 0 && ! flights.Contains(info)) {
flights.Add(info);
foreach (var observer in observers)
observer.OnNext(info);
}
else if (carousel == 0) {
// Baggage claim for flight is done
var flightsToRemove = new List<BaggageInfo>();
foreach (var flight in flights) {
if (info.FlightNumber == flight.FlightNumber) {
flightsToRemove.Add(flight);
foreach (var observer in observers)
observer.OnNext(info);
}
}
foreach (var flightToRemove in flightsToRemove)
flights.Remove(flightToRemove);

flightsToRemove.Clear();
}
}

public void LastBaggageClaimed()


{
foreach (var observer in observers)
observer.OnCompleted();

observers.Clear();
}
}
Public Class BaggageHandler : Implements IObservable(Of BaggageInfo)

Private observers As List(Of IObserver(Of BaggageInfo))


Private flights As List(Of BaggageInfo)

Public Sub New()


observers = New List(Of IObserver(Of BaggageInfo))
flights = New List(Of BaggageInfo)
End Sub

Public Function Subscribe(ByVal observer As IObserver(Of BaggageInfo)) As IDisposable _


Implements IObservable(Of BaggageInfo).Subscribe
' Check whether observer is already registered. If not, add it
If Not observers.Contains(observer) Then
observers.Add(observer)
' Provide observer with existing data.
For Each item In flights
observer.OnNext(item)
Next
End If
Return New Unsubscriber(Of BaggageInfo)(observers, observer)
End Function

' Called to indicate all baggage is now unloaded.


Public Sub BaggageStatus(ByVal flightNo As Integer)
BaggageStatus(flightNo, String.Empty, 0)
End Sub

Public Sub BaggageStatus(ByVal flightNo As Integer, ByVal from As String, ByVal carousel As Integer)
Dim info As New BaggageInfo(flightNo, from, carousel)

' Carousel is assigned, so add new info object to list.


If carousel > 0 And Not flights.Contains(info) Then
flights.Add(info)
For Each observer In observers
observer.OnNext(info)
Next
ElseIf carousel = 0 Then
' Baggage claim for flight is done
Dim flightsToRemove As New List(Of BaggageInfo)
For Each flight In flights
If info.FlightNumber = flight.FlightNumber Then
flightsToRemove.Add(flight)
For Each observer In observers
observer.OnNext(info)
Next
End If
Next
For Each flightToRemove In flightsToRemove
flights.Remove(flightToRemove)
Next
flightsToRemove.Clear()
End If
End Sub

Public Sub LastBaggageClaimed()


For Each observer In observers
observer.OnCompleted()
Next
observers.Clear()
End Sub
End Class

Les clients qui veulent recevoir des informations mises à jour appellent la méthode BaggageHandler.Subscribe . Si
le client ne s'est pas auparavant abonné aux notifications, une référence à l'implémentation de IObserver<T> du
client est ajoutée à la collection observers .
La méthode BaggageHandler.BaggageStatus surchargée peut être appelée pour indiquer que les bagages d'un vol
sont en cours de déchargement ou que leur déchargement est terminé. Dans le premier cas, les informations
suivantes sont passées à la méthode : un numéro de vol, l'aéroport de provenance du vol et le tapis roulant où les
bagages sont déchargés. Dans le deuxième cas, seul un numéro de vol est passé à la méthode. Pour les bagages
en cours de déchargement, la méthode vérifie si les informations BaggageInfo passées à la méthode existent dans
la collection flights . Si elles n'y existent pas, la méthode ajoute les informations et appelle la méthode OnNext
de chaque observateur. Pour les vols dont le déchargement des bagages est terminé, la méthode vérifie si les
informations de ce vol sont stockées dans la collection flights . Si c’est le cas, la méthode appelle la méthode
OnNext de chaque observateur et supprime l’objet BaggageInfo de la collection flights .

Quand le dernier vol de la journée a atterri et que ses bagages ont été traités, la méthode
BaggageHandler.LastBaggageClaimed est appelée. Cette méthode appelle la méthode OnCompleted de chaque
observateur pour indiquer que toutes les notifications ont été effectuées, puis supprime la collection observers .
La méthode Subscribe du fournisseur retourne une implémentation de IDisposable qui permet aux observateurs
d'arrêter de recevoir des notifications avant que la méthode OnCompleted soit appelée. Le code source de cette
classe Unsubscriber(Of BaggageInfo) est montré dans l'exemple suivant. Quand la classe est instanciée dans la
méthode BaggageHandler.Subscribe , deux références lui sont passées : une référence à la collection observers et
une référence à l’observateur qui est ajouté à la collection. Ces références sont affectées à des variables locales.
Quand la méthode Dispose de l’objet est appelée, elle vérifie si l’observateur existe toujours dans la collection
observers et, le cas échéant, elle supprime l’observateur.

internal class Unsubscriber<BaggageInfo> : IDisposable


{
private List<IObserver<BaggageInfo>> _observers;
private IObserver<BaggageInfo> _observer;

internal Unsubscriber(List<IObserver<BaggageInfo>> observers, IObserver<BaggageInfo> observer)


{
this._observers = observers;
this._observer = observer;
}

public void Dispose()


{
if (_observers.Contains(_observer))
_observers.Remove(_observer);
}
}

Friend Class Unsubscriber(Of BaggageInfo) : Implements IDisposable


Private _observers As List(Of IObserver(Of BaggageInfo))
Private _observer As IObserver(Of BaggageInfo)

Friend Sub New(ByVal observers As List(Of IObserver(Of BaggageInfo)), ByVal observer As IObserver(Of
BaggageInfo))
Me._observers = observers
Me._observer = observer
End Sub

Public Sub Dispose() Implements IDisposable.Dispose


If _observers.Contains(_observer) Then
_observers.Remove(_observer)
End If
End Sub
End Class

L'exemple suivant fournit une implémentation de IObserver<T> nommée ArrivalsMonitor , qui est une classe de
base qui affiche les informations sur la récupération des bagages. Les informations sont affichées par ordre
alphabétique, selon le nom de la ville d'origine. Les méthodes de ArrivalsMonitor sont marquées overridable
(en Visual Basic) ou virtual (en C#), et elles peuvent donc toutes être remplacées par une classe dérivée.

using System;
using System.Collections.Generic;

public class ArrivalsMonitor : IObserver<BaggageInfo>


{
private string name;
private List<string> flightInfos = new List<string>();
private IDisposable cancellation;
private string fmt = "{0,-20} {1,5} {2, 3}";

public ArrivalsMonitor(string name)


{
if (String.IsNullOrEmpty(name))
throw new ArgumentNullException("The observer must be assigned a name.");

this.name = name;
}

public virtual void Subscribe(BaggageHandler provider)


{
cancellation = provider.Subscribe(this);
}

public virtual void Unsubscribe()


{
cancellation.Dispose();
flightInfos.Clear();
}

public virtual void OnCompleted()


{
flightInfos.Clear();
}

// No implementation needed: Method is not called by the BaggageHandler class.


public virtual void OnError(Exception e)
{
// No implementation.
}

// Update information.
public virtual void OnNext(BaggageInfo info)
{
bool updated = false;

// Flight has unloaded its baggage; remove from the monitor.


if (info.Carousel == 0) {
var flightsToRemove = new List<string>();
string flightNo = String.Format("{0,5}", info.FlightNumber);

foreach (var flightInfo in flightInfos) {


if (flightInfo.Substring(21, 5).Equals(flightNo)) {
flightsToRemove.Add(flightInfo);
updated = true;
}
}
foreach (var flightToRemove in flightsToRemove)
flightInfos.Remove(flightToRemove);

flightsToRemove.Clear();
}
else {
// Add flight if it does not exist in the collection.
string flightInfo = String.Format(fmt, info.From, info.FlightNumber, info.Carousel);
if (! flightInfos.Contains(flightInfo)) {
if (! flightInfos.Contains(flightInfo)) {
flightInfos.Add(flightInfo);
updated = true;
}
}
if (updated) {
flightInfos.Sort();
Console.WriteLine("Arrivals information from {0}", this.name);
foreach (var flightInfo in flightInfos)
Console.WriteLine(flightInfo);

Console.WriteLine();
}
}
}

Public Class ArrivalsMonitor : Implements IObserver(Of BaggageInfo)


Private name As String
Private flightInfos As New List(Of String)
Private cancellation As IDisposable
Private fmt As String = "{0,-20} {1,5} {2, 3}"

Public Sub New(ByVal name As String)


If String.IsNullOrEmpty(name) Then Throw New ArgumentNullException("The observer must be assigned a
name.")

Me.name = name
End Sub

Public Overridable Sub Subscribe(ByVal provider As BaggageHandler)


cancellation = provider.Subscribe(Me)
End Sub

Public Overridable Sub Unsubscribe()


cancellation.Dispose()
flightInfos.Clear()
End Sub

Public Overridable Sub OnCompleted() Implements System.IObserver(Of BaggageInfo).OnCompleted


flightInfos.Clear()
End Sub

' No implementation needed: Method is not called by the BaggageHandler class.


Public Overridable Sub OnError(ByVal e As System.Exception) Implements System.IObserver(Of
BaggageInfo).OnError
' No implementation.
End Sub

' Update information.


Public Overridable Sub OnNext(ByVal info As BaggageInfo) Implements System.IObserver(Of
BaggageInfo).OnNext
Dim updated As Boolean = False

' Flight has unloaded its baggage; remove from the monitor.
If info.Carousel = 0 Then
Dim flightsToRemove As New List(Of String)
Dim flightNo As String = String.Format("{0,5}", info.FlightNumber)
For Each flightInfo In flightInfos
If flightInfo.Substring(21, 5).Equals(flightNo) Then
flightsToRemove.Add(flightInfo)
updated = True
End If
Next
For Each flightToRemove In flightsToRemove
flightInfos.Remove(flightToRemove)
Next
flightsToRemove.Clear()
Else
Else
' Add flight if it does not exist in the collection.
Dim flightInfo As String = String.Format(fmt, info.From, info.FlightNumber, info.Carousel)
If Not flightInfos.Contains(flightInfo) Then
flightInfos.Add(flightInfo)
updated = True
End If
End If
If updated Then
flightInfos.Sort()
Console.WriteLine("Arrivals information from {0}", Me.name)
For Each flightInfo In flightInfos
Console.WriteLine(flightInfo)
Next
Console.WriteLine()
End If
End Sub
End Class

La classe ArrivalsMonitor comprend les méthodes Subscribe et Unsubscribe . La méthode Subscribe permet à
la classe d'enregistrer l'implémentation de IDisposable retournée par l'appel à Subscribe dans une variable privée.
La méthode Unsubscribe permet à la classe de se désabonner des notifications en appelant l'implémentation de
Dispose du fournisseur. ArrivalsMonitor fournit également des implémentations des méthodes OnNext, OnError
et OnCompleted. Seule l'implémentation de OnNext contient une quantité importante de code. La méthode
fonctionne avec un objet List<T> privé, trié et générique, qui gère les informations sur les aéroports d'origine des
vols arrivés et sur les tapis roulants où leurs bagages sont disponibles. Si la classe BaggageHandler signale
l'arrivée d'un nouveau vol, l'implémentation de la méthode OnNext ajoute des informations sur ce vol à la liste. Si
la classe BaggageHandler signale que les bagages du vol ont été déchargées, la méthode OnNext supprime ce vol
de la liste. Chaque fois qu'une modification est apportée, la liste est triée et affichée sur la console.
L'exemple suivant contient le point d'entrée de l'application qui instancie la classe BaggageHandler ainsi que deux
instances de la classe ArrivalsMonitor , et qui utilise la méthode BaggageHandler.BaggageStatus pour ajouter et
supprimer des informations sur les vols arrivés. Dans chaque cas, les observateurs reçoivent des mises à jour et
affichent correctement les informations de récupération des bagages.

using System;
using System.Collections.Generic;

public class Example


{
public static void Main()
{
BaggageHandler provider = new BaggageHandler();
ArrivalsMonitor observer1 = new ArrivalsMonitor("BaggageClaimMonitor1");
ArrivalsMonitor observer2 = new ArrivalsMonitor("SecurityExit");

provider.BaggageStatus(712, "Detroit", 3);


observer1.Subscribe(provider);
provider.BaggageStatus(712, "Kalamazoo", 3);
provider.BaggageStatus(400, "New York-Kennedy", 1);
provider.BaggageStatus(712, "Detroit", 3);
observer2.Subscribe(provider);
provider.BaggageStatus(511, "San Francisco", 2);
provider.BaggageStatus(712);
observer2.Unsubscribe();
provider.BaggageStatus(400);
provider.LastBaggageClaimed();
}
}
// The example displays the following output:
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
//
// Arrivals information from BaggageClaimMonitor1
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
// Kalamazoo 712 3
//
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
//
// Arrivals information from SecurityExit
// Detroit 712 3
//
// Arrivals information from SecurityExit
// Detroit 712 3
// Kalamazoo 712 3
//
// Arrivals information from SecurityExit
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
//
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from SecurityExit
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from BaggageClaimMonitor1
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from SecurityExit
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from BaggageClaimMonitor1
// San Francisco 511 2
Module Example
Public Sub Main()
Dim provider As New BaggageHandler()
Dim observer1 As New ArrivalsMonitor("BaggageClaimMonitor1")
Dim observer2 As New ArrivalsMonitor("SecurityExit")

provider.BaggageStatus(712, "Detroit", 3)
observer1.Subscribe(provider)
provider.BaggageStatus(712, "Kalamazoo", 3)
provider.BaggageStatus(400, "New York-Kennedy", 1)
provider.BaggageStatus(712, "Detroit", 3)
observer2.Subscribe(provider)
provider.BaggageStatus(511, "San Francisco", 2)
provider.BaggageStatus(712)
observer2.Unsubscribe()
provider.BaggageStatus(400)
provider.LastBaggageClaimed()
End Sub
End Module
' The example displays the following output:
' Arrivals information from BaggageClaimMonitor1
' Detroit 712 3
'
' Arrivals information from BaggageClaimMonitor1
' Detroit 712 3
' Kalamazoo 712 3
'
' Arrivals information from BaggageClaimMonitor1
' Detroit 712 3
' Kalamazoo 712 3
' New York-Kennedy 400 1
'
' Arrivals information from SecurityExit
' Detroit 712 3
'
' Arrivals information from SecurityExit
' Detroit 712 3
' Kalamazoo 712 3
'
' Arrivals information from SecurityExit
' Detroit 712 3
' Kalamazoo 712 3
' New York-Kennedy 400 1
'
' Arrivals information from BaggageClaimMonitor1
' Detroit 712 3
' Kalamazoo 712 3
' New York-Kennedy 400 1
' San Francisco 511 2
'
' Arrivals information from SecurityExit
' Detroit 712 3
' Kalamazoo 712 3
' New York-Kennedy 400 1
' San Francisco 511 2
'
' Arrivals information from BaggageClaimMonitor1
' New York-Kennedy 400 1
' San Francisco 511 2
'
' Arrivals information from SecurityExit
' New York-Kennedy 400 1
' San Francisco 511 2
'
' Arrivals information from BaggageClaimMonitor1
' San Francisco 511 2
Rubriques connexes
IN T IT UL É DESC RIP T IO N

Meilleures pratiques du modèle de design observateur Décrit les meilleures pratiques à adopter lors du
développement d'applications qui implémentent le modèle de
design Observateur.

Comment : implémenter un fournisseur Fournit une implémentation pas à pas d'un fournisseur pour
une application de surveillance de la température.

Comment : implémenter un observateur Fournit une implémentation pas à pas d'un observateur pour
une application de surveillance de la température.
Meilleures pratiques du modèle de design
observateur
18/07/2020 • 7 minutes to read • Edit Online

Dans le .NET Framework, le modèle de conception observateur est implémenté comme un ensemble d’interfaces.
L'interface System.IObservable<T> représente le fournisseur de données, qui est également chargé de fournir une
implémentation IDisposable permettant aux observateurs d'annuler leur abonnement aux notifications. L'interface
System.IObserver<T> représente l'observateur. Cette rubrique décrit les meilleures pratiques que les
développeurs doivent suivre quand ils implémentent le modèle de conception observateur à l'aide de ces
interfaces.

Thread
En général, un fournisseur implémente la méthode IObservable<T>.Subscribe en ajoutant un observateur à une
liste d’abonnés représentée par un objet de collection, puis il implémente la méthode IDisposable.Dispose en
supprimant un observateur de la liste des abonnés. Un observateur peut appeler ces méthodes à tout moment. De
plus, étant donné que le contrat fournisseur/observateur ne spécifie pas qui est responsable de l'annulation
d'abonnement après la méthode de rappel IObserver<T>.OnCompleted, il est possible que le fournisseur et
l'observateur tentent de supprimer le même membre de la liste. En raison de cette éventualité, les méthodes
Subscribe et Dispose doivent être thread-safe. En général, cela implique l’utilisation d’une collection simultanée ou
d’un verrou. Les implémentations qui ne sont pas thread-safe doivent indiquer explicitement qu'elles ne le sont
pas.
Les garanties supplémentaires doivent être spécifiées dans une couche supérieure au contrat
fournisseur/observateur. Les implémenteurs doivent indiquer clairement lorsqu'ils imposent des conditions
supplémentaires afin d'éviter la confusion des utilisateurs concernant le contrat observateur.

Gestion des exceptions


En raison du couplage faible entre un fournisseur de données et un observateur, les exceptions du modèle de
conception observateur ont un but informatif. Cela affecte la façon dont les observateurs et les fournisseurs gèrent
les exceptions dans le modèle de conception observateur.
Le fournisseur – Appel de la méthode OnError
La méthode OnError a pour but d'informer les observateurs, tout comme la méthode IObserver<T>.OnNext.
Toutefois, la méthode OnNext sert à fournir aux observateurs des données actuelles ou mises à jour, alors que la
méthode OnError sert à indiquer que le fournisseur ne peut pas fournir de données valides.
Le fournisseur doit suivre ces meilleures pratiques lors de la gestion des exceptions et de l'appel de la méthode
OnError :
Le fournisseur doit gérer ses propres exceptions s’il a des exigences spécifiques.
Le fournisseur ne doit pas exiger ni s'attendre à ce que les observateurs gèrent les exceptions.
Le fournisseur doit appeler la méthode OnError quand il gère une exception qui compromet sa capacité à
fournir des mises à jour. Les informations sur ces exceptions peuvent être passées à l'observateur. Dans les
autres cas, il est inutile d'informer les observateurs des exceptions.
Une fois que le fournisseur a appelé la méthode OnError ou IObserver<T>.OnCompleted, il ne doit y avoir aucune
autre notification et le fournisseur peut annuler l'abonnement de ses observateurs. Toutefois, les observateurs
peuvent également annuler leur abonnement à tout moment, y compris avant et après la réception d'une
notification OnError ou IObserver<T>.OnCompleted. Le modèle de conception observateur ne détermine pas qui
du fournisseur ou de l’observateur est responsable de l’annulation d’abonnement. Par conséquent, il est possible
que les deux tentent de se désabonner. En règle générale, quand les observateurs annulent un abonnement, ils
sont supprimés de la collection d’abonnés. Dans une application à thread unique, l’implémentation
IDisposable.Dispose doit s’assurer qu’une référence d’objet est valide et que l’objet est membre de la collection
d’abonnés avant d’essayer de le supprimer. Dans une application multithread, un objet de collection thread-safe, tel
qu’un objet System.Collections.Concurrent.BlockingCollection<T>, doit être utilisé.
L'observateur – Implémentation de la méthode OnError
Quand un observateur reçoit une notification d'erreur provenant du fournisseur, celle-ci lui est fournie à titre
indicatif et ne l'oblige pas à effectuer d'action particulière.
L'observateur doit utiliser ces meilleures pratiques quand il répond à un appel de méthode OnError provenant du
fournisseur :
L'observateur ne doit pas lever d'exceptions depuis ses implémentations d'interface, telles que OnNext ou
OnError. Si l'observateur lève des exceptions, il doit s'attendre à ce qu'elles ne soient pas gérées.
Pour conserver la pile des appels, un observateur qui souhaite lever un objet Exception passé à sa méthode
OnError doit encapsuler l'exception avant de la lever. Un objet d'exception standard doit être utilisé à cet
effet.

Autres meilleures pratiques


Dans la méthode IObservable<T>.Subscribe, une tentative d'annulation d'abonnement peut entraîner une
référence null. Par conséquent, nous vous recommandons d'éviter cette pratique.
Même s’il est possible d’attacher un observateur à plusieurs fournisseurs, le modèle recommandé consiste à
attacher une instance IObserver<T> à une seule et même instance IObservable<T>.

Voir aussi
Modèle de conception observateur
Comment : implémenter un observateur
Comment : implémenter un fournisseur
Comment : implémenter un fournisseur
18/07/2020 • 9 minutes to read • Edit Online

Le modèle de design observateur requiert une division entre un fournisseur, qui surveille les données et envoie
des notifications, et un ou plusieurs observateurs, qui reçoivent des notifications (rappels) du fournisseur. Cette
rubrique décrit comment créer un fournisseur. Une rubrique connexe, Guide pratique pour implémenter une
méthode Observer, explique comment créer un observateur.
Pour créer un fournisseur
1. Définissez les données que le fournisseur est chargé d’envoyer aux observateurs. Bien que le fournisseur et
les données qu’il envoie aux observateurs puissent être de type unique, ils sont généralement représentés
par différents types. Par exemple, dans une application de surveillance de la température, la structure
Temperature définit les données que le fournisseur (qui est représenté par la classe TemperatureMonitor
définie à l’étape suivante) surveille et auxquelles les observateurs s’abonnent.

using System;

public struct Temperature


{
private decimal temp;
private DateTime tempDate;

public Temperature(decimal temperature, DateTime dateAndTime)


{
this.temp = temperature;
this.tempDate = dateAndTime;
}

public decimal Degrees


{ get { return this.temp; } }

public DateTime Date


{ get { return this.tempDate; } }
}

Public Structure Temperature


Private temp As Decimal
Private tempDate As DateTime

Public Sub New(ByVal temperature As Decimal, ByVal dateAndTime As DateTime)


Me.temp = temperature
Me.tempDate = dateAndTime
End Sub

Public ReadOnly Property Degrees As Decimal


Get
Return Me.temp
End Get
End Property

Public ReadOnly Property [Date] As DateTime


Get
Return tempDate
End Get
End Property
End Structure
2. Définissez le fournisseur de données, qui est un type qui implémente l’interface System.IObservable<T>.
L’argument de type générique du fournisseur est le type que le fournisseur envoie aux observateurs.
L’exemple suivant définit une classe TemperatureMonitor , qui est une implémentation
System.IObservable<T> construite avec un argument de type générique Temperature .

using System;
using System.Collections.Generic;

public class TemperatureMonitor : IObservable<Temperature>


{

Imports System.Collections.Generic

Public Class TemperatureMonitor : Implements IObservable(Of Temperature)

3. Déterminez comment le fournisseur stockera les références aux observateurs afin que chaque observateur
puisse être averti lorsque nécessaire. Plus couramment, un objet de collection comme un objet List<T>
générique est utilisé à cet effet. L’exemple suivant définit un objet List<T> privé instancié dans le
constructeur de classe TemperatureMonitor .

using System;
using System.Collections.Generic;

public class TemperatureMonitor : IObservable<Temperature>


{
List<IObserver<Temperature>> observers;

public TemperatureMonitor()
{
observers = new List<IObserver<Temperature>>();
}

Imports System.Collections.Generic

Public Class TemperatureMonitor : Implements IObservable(Of Temperature)


Dim observers As List(Of IObserver(Of Temperature))

Public Sub New()


observers = New List(Of IObserver(Of Temperature))
End Sub

4. Définissez une implémentation IDisposable que le fournisseur peut retourner aux abonnés afin qu’ils
cessent de recevoir des notifications à tout moment. L’exemple suivant définit une classe Unsubscriber
imbriquée à laquelle une référence à la collection d’abonnés et à l’abonné est passée lorsque la classe est
instanciée. Ce code permet à l’abonné d’appeler l’implémentation IDisposable.Dispose de l’objet pour se
supprimer lui-même de la collection d’abonnés.
private class Unsubscriber : IDisposable
{
private List<IObserver<Temperature>> _observers;
private IObserver<Temperature> _observer;

public Unsubscriber(List<IObserver<Temperature>> observers, IObserver<Temperature> observer)


{
this._observers = observers;
this._observer = observer;
}

public void Dispose()


{
if (! (_observer == null)) _observers.Remove(_observer);
}
}

Private Class Unsubscriber : Implements IDisposable


Private _observers As List(Of IObserver(Of Temperature))
Private _observer As IObserver(Of Temperature)

Public Sub New(ByVal observers As List(Of IObserver(Of Temperature)), ByVal observer As


IObserver(Of Temperature))
Me._observers = observers
Me._observer = observer
End Sub

Public Sub Dispose() Implements IDisposable.Dispose


If _observer IsNot Nothing Then _observers.Remove(_observer)
End Sub
End Class

5. Implémentez la méthode IObservable<T>.Subscribe. Une référence à l’interface System.IObserver<T> est


passée à la méthode et doit être stockée dans l’objet conçu à cet effet à l’étape 3. La méthode doit ensuite
retourner l’implémentation IDisposable développée à l’étape 4. L’exemple suivant illustre l’implémentation
de la méthode Subscribe dans la classe TemperatureMonitor .

public IDisposable Subscribe(IObserver<Temperature> observer)


{
if (! observers.Contains(observer))
observers.Add(observer);

return new Unsubscriber(observers, observer);


}

Public Function Subscribe(ByVal observer As System.IObserver(Of Temperature)) As System.IDisposable


Implements System.IObservable(Of Temperature).Subscribe
If Not observers.Contains(observer) Then
observers.Add(observer)
End If
Return New Unsubscriber(observers, observer)
End Function

6. Informez les observateurs si nécessaire en appelant leurs implémentations IObserver<T>.OnNext,


IObserver<T>.OnError et IObserver<T>.OnCompleted. Dans certains cas, un fournisseur peut ne pas
appeler la méthode OnError lorsqu’une erreur se produit. Par exemple, la méthode GetTemperature
suivante simule un moniteur qui lit les données de température toutes les cinq secondes et avertit les
observateurs si la température a changé d’au moins 1 degré depuis la lecture précédente. Si l’appareil ne
signale pas de température (autrement dit, si sa valeur est Null), le fournisseur avertit les observateurs que
la transmission est terminée. Notez que, en plus d’appeler la méthode OnCompleted de chaque
observateur, la méthode GetTemperature efface la collection List<T>. Dans ce cas, le fournisseur ne fait
aucun appel à la méthode OnError de ses observateurs.

public void GetTemperature()


{
// Create an array of sample data to mimic a temperature device.
Nullable<Decimal>[] temps = {14.6m, 14.65m, 14.7m, 14.9m, 14.9m, 15.2m, 15.25m, 15.2m,
15.4m, 15.45m, null };
// Store the previous temperature, so notification is only sent after at least .1 change.
Nullable<Decimal> previous = null;
bool start = true;

foreach (var temp in temps) {


System.Threading.Thread.Sleep(2500);
if (temp.HasValue) {
if (start || (Math.Abs(temp.Value - previous.Value) >= 0.1m )) {
Temperature tempData = new Temperature(temp.Value, DateTime.Now);
foreach (var observer in observers)
observer.OnNext(tempData);
previous = temp;
if (start) start = false;
}
}
else {
foreach (var observer in observers.ToArray())
if (observer != null) observer.OnCompleted();

observers.Clear();
break;
}
}
}

Public Sub GetTemperature()


' Create an array of sample data to mimic a temperature device.
Dim temps() As Nullable(Of Decimal) = {14.6D, 14.65D, 14.7D, 14.9D, 14.9D, 15.2D, 15.25D, 15.2D,
15.4D, 15.45D, Nothing}
' Store the previous temperature, so notification is only sent after at least .1 change.
Dim previous As Nullable(Of Decimal)
Dim start As Boolean = True

For Each temp In temps


System.Threading.Thread.Sleep(2500)

If temp.HasValue Then
If start OrElse Math.Abs(temp.Value - previous.Value) >= 0.1 Then
Dim tempData As New Temperature(temp.Value, Date.Now)
For Each observer In observers
observer.OnNext(tempData)
Next
previous = temp
If start Then start = False
End If
Else
For Each observer In observers.ToArray()
If observer IsNot Nothing Then observer.OnCompleted()
Next
observers.Clear()
Exit For
End If
Next
End Sub
Exemple
L’exemple suivant contient le code source complet permettant de définir une implémentation IObservable<T>
pour une application de surveillance de la température. Il inclut la structure Temperature , qui correspond aux
données envoyées aux observateurs, et la classe TemperatureMonitor , qui est l’implémentation IObservable<T>.

using System.Threading;
using System;
using System.Collections.Generic;

public class TemperatureMonitor : IObservable<Temperature>


{
List<IObserver<Temperature>> observers;

public TemperatureMonitor()
{
observers = new List<IObserver<Temperature>>();
}

private class Unsubscriber : IDisposable


{
private List<IObserver<Temperature>> _observers;
private IObserver<Temperature> _observer;

public Unsubscriber(List<IObserver<Temperature>> observers, IObserver<Temperature> observer)


{
this._observers = observers;
this._observer = observer;
}

public void Dispose()


{
if (! (_observer == null)) _observers.Remove(_observer);
}
}

public IDisposable Subscribe(IObserver<Temperature> observer)


{
if (! observers.Contains(observer))
observers.Add(observer);

return new Unsubscriber(observers, observer);


}

public void GetTemperature()


{
// Create an array of sample data to mimic a temperature device.
Nullable<Decimal>[] temps = {14.6m, 14.65m, 14.7m, 14.9m, 14.9m, 15.2m, 15.25m, 15.2m,
15.4m, 15.45m, null };
// Store the previous temperature, so notification is only sent after at least .1 change.
Nullable<Decimal> previous = null;
bool start = true;

foreach (var temp in temps) {


System.Threading.Thread.Sleep(2500);
if (temp.HasValue) {
if (start || (Math.Abs(temp.Value - previous.Value) >= 0.1m )) {
Temperature tempData = new Temperature(temp.Value, DateTime.Now);
foreach (var observer in observers)
observer.OnNext(tempData);
previous = temp;
if (start) start = false;
}
}
else {
foreach (var observer in observers.ToArray())
if (observer != null) observer.OnCompleted();
if (observer != null) observer.OnCompleted();

observers.Clear();
break;
}
}
}
}
Imports System.Threading
Imports System.Collections.Generic

Public Class TemperatureMonitor : Implements IObservable(Of Temperature)


Dim observers As List(Of IObserver(Of Temperature))

Public Sub New()


observers = New List(Of IObserver(Of Temperature))
End Sub

Private Class Unsubscriber : Implements IDisposable


Private _observers As List(Of IObserver(Of Temperature))
Private _observer As IObserver(Of Temperature)

Public Sub New(ByVal observers As List(Of IObserver(Of Temperature)), ByVal observer As IObserver(Of
Temperature))
Me._observers = observers
Me._observer = observer
End Sub

Public Sub Dispose() Implements IDisposable.Dispose


If _observer IsNot Nothing Then _observers.Remove(_observer)
End Sub
End Class

Public Function Subscribe(ByVal observer As System.IObserver(Of Temperature)) As System.IDisposable


Implements System.IObservable(Of Temperature).Subscribe
If Not observers.Contains(observer) Then
observers.Add(observer)
End If
Return New Unsubscriber(observers, observer)
End Function

Public Sub GetTemperature()


' Create an array of sample data to mimic a temperature device.
Dim temps() As Nullable(Of Decimal) = {14.6D, 14.65D, 14.7D, 14.9D, 14.9D, 15.2D, 15.25D, 15.2D,
15.4D, 15.45D, Nothing}
' Store the previous temperature, so notification is only sent after at least .1 change.
Dim previous As Nullable(Of Decimal)
Dim start As Boolean = True

For Each temp In temps


System.Threading.Thread.Sleep(2500)

If temp.HasValue Then
If start OrElse Math.Abs(temp.Value - previous.Value) >= 0.1 Then
Dim tempData As New Temperature(temp.Value, Date.Now)
For Each observer In observers
observer.OnNext(tempData)
Next
previous = temp
If start Then start = False
End If
Else
For Each observer In observers.ToArray()
If observer IsNot Nothing Then observer.OnCompleted()
Next
observers.Clear()
Exit For
End If
Next
End Sub
End Class
Voir aussi
IObservable<T>
Modèle de conception observateur
Comment : implémenter un observateur
Meilleures pratiques du modèle de design observateur
Comment : implémenter une méthode Observer
18/07/2020 • 5 minutes to read • Edit Online

Le modèle de design observateur requiert une division entre un observateur, qui s’inscrit pour recevoir des
notifications, et un fournisseur, qui surveille les données et envie des notifications à un ou plusieurs observateurs.
Cette rubrique décrit comment créer un observateur. Une rubrique connexe, Guide pratique pour implémenter un
fournisseur, explique comment créer un fournisseur.
Pour créer un observateur
1. Définissez l’observateur, qui est un type qui implémente l’interface System.IObserver<T>. Par exemple, le
code suivant définit un type intitulé TemperatureReporter , qui est une implémentation
System.IObserver<T> construite avec un argument de type générique Temperature .

public class TemperatureReporter : IObserver<Temperature>

Public Class TemperatureReporter : Implements IObserver(Of Temperature)

2. Si l’observateur peut cesser de recevoir des notifications avant que le fournisseur appelle son
implémentation IObserver<T>.OnCompleted, définissez une variable privée qui contiendra
l’implémentation IDisposable retournée par la méthode IObservable<T>.Subscribe du fournisseur. Vous
devez également définir une méthode d’abonnement qui appelle la méthode Subscribe du fournisseur et
stocke l’objet IDisposable retourné. Par exemple, le code suivant définit une variable privée nommée
unsubscriber et une méthode Subscribe qui appelle la méthode Subscribe du fournisseur, puis affecte
l’objet retourné à la variable unsubscriber .

public class TemperatureReporter : IObserver<Temperature>


{
private IDisposable unsubscriber;
private bool first = true;
private Temperature last;

public virtual void Subscribe(IObservable<Temperature> provider)


{
unsubscriber = provider.Subscribe(this);
}

Public Class TemperatureReporter : Implements IObserver(Of Temperature)

Private unsubscriber As IDisposable


Private first As Boolean = True
Private last As Temperature

Public Overridable Sub Subscribe(ByVal provider As IObservable(Of Temperature))


unsubscriber = provider.Subscribe(Me)
End Sub

3. Définissez une méthode qui permet à l’observateur de cesser de recevoir des notifications avant que le
fournisseur appelle son implémentation IObserver<T>.OnCompleted, si cette fonctionnalité est nécessaire.
L'exemple suivant définit une méthode Unsubscribe .
public virtual void Unsubscribe()
{
unsubscriber.Dispose();
}

Public Overridable Sub Unsubscribe()


unsubscriber.Dispose()
End Sub

4. Fournissez des implémentations des trois méthodes définies par l’interface IObserver<T> :
IObserver<T>.OnNext, IObserver<T>.OnError et IObserver<T>.OnCompleted. Selon le fournisseur et les
besoins de l’application, les méthodes OnError et OnCompleted peuvent être des implémentations de type
stub. Notez que la méthode OnError ne doit pas gérer l’objet Exception transmis en tant qu’exception, et la
méthode OnCompleted est libre d’appeler l’implémentation IDisposable.Dispose du fournisseur. L’exemple
suivant illustre l’implémentation IObserver<T> de la classe TemperatureReporter .

public virtual void OnCompleted()


{
Console.WriteLine("Additional temperature data will not be transmitted.");
}

public virtual void OnError(Exception error)


{
// Do nothing.
}

public virtual void OnNext(Temperature value)


{
Console.WriteLine("The temperature is {0}°C at {1:g}", value.Degrees, value.Date);
if (first)
{
last = value;
first = false;
}
else
{
Console.WriteLine(" Change: {0}° in {1:g}", value.Degrees - last.Degrees,
value.Date.ToUniversalTime() -
last.Date.ToUniversalTime());
}
}
Public Overridable Sub OnCompleted() Implements System.IObserver(Of Temperature).OnCompleted
Console.WriteLine("Additional temperature data will not be transmitted.")
End Sub

Public Overridable Sub OnError(ByVal [error] As System.Exception) Implements System.IObserver(Of


Temperature).OnError
' Do nothing.
End Sub

Public Overridable Sub OnNext(ByVal value As Temperature) Implements System.IObserver(Of


Temperature).OnNext
Console.WriteLine("The temperature is {0}°C at {1:g}", value.Degrees, value.Date)
If first Then
last = value
first = False
Else
Console.WriteLine(" Change: {0}° in {1:g}", value.Degrees - last.Degrees,
value.Date.ToUniversalTime -
last.Date.ToUniversalTime)
End If
End Sub

Exemple
L’exemple suivant contient le code source complet de la classe TemperatureReporter , qui fournit l’implémentation
IObserver<T> pour une application de surveillance de la température.
public class TemperatureReporter : IObserver<Temperature>
{
private IDisposable unsubscriber;
private bool first = true;
private Temperature last;

public virtual void Subscribe(IObservable<Temperature> provider)


{
unsubscriber = provider.Subscribe(this);
}

public virtual void Unsubscribe()


{
unsubscriber.Dispose();
}

public virtual void OnCompleted()


{
Console.WriteLine("Additional temperature data will not be transmitted.");
}

public virtual void OnError(Exception error)


{
// Do nothing.
}

public virtual void OnNext(Temperature value)


{
Console.WriteLine("The temperature is {0}°C at {1:g}", value.Degrees, value.Date);
if (first)
{
last = value;
first = false;
}
else
{
Console.WriteLine(" Change: {0}° in {1:g}", value.Degrees - last.Degrees,
value.Date.ToUniversalTime() -
last.Date.ToUniversalTime());
}
}
}
Public Class TemperatureReporter : Implements IObserver(Of Temperature)

Private unsubscriber As IDisposable


Private first As Boolean = True
Private last As Temperature

Public Overridable Sub Subscribe(ByVal provider As IObservable(Of Temperature))


unsubscriber = provider.Subscribe(Me)
End Sub

Public Overridable Sub Unsubscribe()


unsubscriber.Dispose()
End Sub

Public Overridable Sub OnCompleted() Implements System.IObserver(Of Temperature).OnCompleted


Console.WriteLine("Additional temperature data will not be transmitted.")
End Sub

Public Overridable Sub OnError(ByVal [error] As System.Exception) Implements System.IObserver(Of


Temperature).OnError
' Do nothing.
End Sub

Public Overridable Sub OnNext(ByVal value As Temperature) Implements System.IObserver(Of


Temperature).OnNext
Console.WriteLine("The temperature is {0}°C at {1:g}", value.Degrees, value.Date)
If first Then
last = value
first = False
Else
Console.WriteLine(" Change: {0}° in {1:g}", value.Degrees - last.Degrees,
value.Date.ToUniversalTime -
last.Date.ToUniversalTime)
End If
End Sub
End Class

Voir aussi
IObserver<T>
Modèle de conception observateur
Comment : implémenter un fournisseur
Meilleures pratiques du modèle de design observateur
processus d'exécution managée
18/07/2020 • 16 minutes to read • Edit Online

Le processus d'exécution managé inclut les étapes suivantes,qui sont décrites en détail plus loin dans cette
rubrique :
1. Choix d'un compilateur
Pour bénéficier des avantages qu'apporte le Common Language Runtime, vous devez utiliser un ou
plusieurs compilateurs de langage ciblant le runtime.
2. Compilation de votre code en MSIL
La compilation traduit votre code source en langage MSIL (Microsoft Intermediate Language) et génère les
métadonnées requises.
3. Compilation du MSIL en code natif
Au moment de l'exécution, un compilateur juste-à-temps (JIT) transforme le MSIL en code natif. Au
moment de la compilation, le code est soumis à un processus de vérification qui examine le MSIL et les
métadonnées afin de déterminer si le code peut être considéré comme étant de type sécurisé.
4. Exécution de code
Le Common Language Runtime fournit l'infrastructure qui permet à l'exécution d'avoir lieu et les services
pouvant être utilisés pendant l'exécution.

Choix d'un compilateur


Pour bénéficier des avantages qu'offre le Common Language Runtime (CLR), vous devez utiliser un ou plusieurs
compilateurs de langage ciblant le runtime, tels que les compilateurs Visual Basic, C#, Visual C++, F# ou l'un des
nombreux compilateurs tiers tels que les compilateurs Eiffel, Perl ou COBOL.
Dans la mesure où il représente un environnement d'exécution multilangage, le runtime prend en charge une
grande variété de types de données et de fonctionnalités de langage. Le compilateur de langage que vous utilisez
détermine les fonctionnalités du runtime qui sont disponibles et que vous utilisez pour concevoir votre code. C'est
votre compilateur et non le runtime qui établit la syntaxe à laquelle votre code doit se conformer. Si votre
composant doit être entièrement utilisable par des composants écrits dans d’autres langages, les types exportés
de votre composant doivent exposer uniquement les fonctionnalités de langage qui font partie de la spécification
Language Independence and Language-Independent Components . Vous pouvez utiliser l'attribut
CLSCompliantAttribute pour vous assurer que votre code est conforme CLS. Pour plus d'informations, consultez
Language Independence and Language-Independent Components.
Retour au début

Compilation en MSIL
Lors d'une compilation destinée à produire du code managé, le compilateur convertit le code source en langage
MSIL (Microsoft Intermediate Language), un jeu d'instructions indépendant du processeur qui peut être converti
efficacement en code natif. MSIL inclut des instructions pour le chargement, le stockage, l'initialisation et l'appel
de méthodes sur des objets, ainsi que des instructions pour la réalisation d'opérations arithmétiques et logiques,
le flux de contrôle, l'accès direct à la mémoire, la gestion des exceptions et d'autres opérations. Avant d'exécuter
du code, vous devez d'abord convertir le MSIL en code spécifique au processeur, généralement à l'aide d'un
compilateur juste-à-temps (JIT). Dans la mesure où le Common Language Runtime fournit un ou plusieurs
compilateurs JIT pour chaque architecture d'ordinateur qu'il prend en charge, le même jeu d'instructions MSIL
peut être traité par un compilateur JIT et exécuté sur toute architecture prise en charge.
Quand un compilateur produit du code MSIL, il génère aussi des métadonnées. Les métadonnées décrivent les
types contenus dans votre code, y compris la définition de chaque type, les signatures des membres de chaque
type, les membres référencés par votre code, et d'autres données que le runtime utilise au moment de l'exécution.
Le MSIL et les métadonnées sont stockés dans un fichier exécutable portable (PE) qui est basé sur le fichier
Microsoft PE publié qu'il prolonge et sur le format COFF (Common Object File Format) utilisé traditionnellement
pour le contenu exécutable. Ce format de fichier, qui accepte le code MSIL ou le code natif ainsi que les
métadonnées, permet au système d'exploitation de reconnaître les images du Common Language Runtime. La
présence de métadonnées dans le fichier en même temps que le jeu d'instructions MSIL permet à votre code de
se décrire lui-même, ce qui signifie que les bibliothèques de types et IDL (Interface Definition Language) ne sont
pas nécessaires. Le runtime recherche les métadonnées dans le fichier et les extrait selon les besoins, au moment
de l'exécution.
Retour au début

Compilation du MSIL en code natif


Avant de pouvoir exécuter le langage MSIL (MicroSoft Intermediate Language), vous devez le compiler en code
natif avec le Common Language Runtime pour l'architecture de l'ordinateur cible. Le .NET Framework propose
deux méthodes pour effectuer cette conversion :
À l'aide d'un compilateur juste-à-temps (JIT) .NET Framework
À l’aide de l’outil Ngen.exe (générateur d’images natives) de .NET Framework
Compilation par le compilateur JIT
La compilation JIT convertit à la demande le langage MSIL en code natif au moment de l'exécution de
l'application, quand le contenu d'un assembly est chargé et exécuté. Dans la mesure où le Common Language
Runtime fournit un compilateur JIT pour chaque architecture de processeur qu'il prend en charge, les
développeurs peuvent générer un jeu d'assemblys MSIL pouvant être traité par un compilateur JIT et exécuté sur
différents ordinateurs ayant des architectures d'ordinateur différentes. Cependant, si votre code managé appelle
des API natives spécifiques à une plateforme ou une bibliothèque de classes spécifique à une plateforme, il
s'exécutera sur un système d'exploitation spécifique uniquement.
La compilation JIT tient compte de la possibilité qu'une partie du code ne soit peut-être jamais appelée au
moment de l'exécution. Au lieu de consacrer du temps et des ressources mémoire à la conversion de toutes les
instructions MSIL d'un fichier PE en code natif, elle les convertit au fur et à mesure des besoins au moment de
l'exécution et stocke le code natif obtenu en mémoire afin qu'il soit accessible pour les appels ultérieurs dans le
contexte de ce processus. Le chargeur crée et attache un stub à chaque méthode dans un type quand le type est
chargé et initialisé. Quand une méthode est appelée pour la première fois, le stub passe le contrôle au
compilateur JIT, qui convertit le MSIL de cette méthode en code natif et modifie le stub afin de pointer
directement vers le code natif généré. Par conséquent, les appels suivants à la méthode traitée par le compilateur
JIT passent directement au code natif.
Génération du code d'installation à l'aide de NGen.exe
Comme le compilateur JIT convertit le MSIL d'un assembly en code natif quand les méthodes individuelles
définies dans cet assembly sont appelées, les performances sont nécessairement altérées au moment de
l'exécution. Dans la plupart des cas, cette baisse de performances est acceptable. Et surtout, le code généré par le
compilateur JIT est lié au processus qui a déclenché la compilation. Il ne peut pas être partagé entre plusieurs
processus. Pour que le code généré puisse être partagé entre plusieurs appels d'une application ou entre
plusieurs processus partageant un jeu d'assemblys, le Common Language Runtime prend en charge un mode de
compilation à l'avance. Ce mode de compilation à l’avance utilise Ngen.exe (générateur d’images natives) pour
convertir les assemblys MSIL en code natif de façon similaire au compilateur JIT. Toutefois, le fonctionnement de
Ngen.exe diffère de celui du compilateur JIT sur trois points :
Il exécute la conversion de MSIL en code natif avant d'exécuter l'application et non pendant l'exécution de
celle-ci.
Il compile un assembly entier à la fois, au lieu d'une méthode à la fois.
Il conserve le code généré dans le cache des images natives comme un fichier sur le disque.
Vérification du code
Dans le cadre de sa compilation en code natif, le code MSIL est soumis à un processus de vérification, sauf si un
administrateur a établi une stratégie de sécurité qui autorise le code à ignorer ce processus. La vérification
examine le MSIL et les métadonnées afin de déterminer si le code est de type sécurisé, ce qui signifie qu'il ne doit
accéder qu'aux emplacements de mémoire autorisés. La sécurité de type permet d'isoler les objets les uns des
autres et de les protéger de toute altération accidentelle ou malveillante. Elle garantit également que les
restrictions liées à la sécurité peuvent être appliquées au code de manière fiable.
Le runtime s'appuie sur le fait que les instructions suivantes sont vraies pour le code de type sécurisé vérifié :
une référence à un type qui est strictement compatible avec le type référencé ;
seules les opérations définies de façon appropriée sont appelées pour un objet ;
les identités sont conformes à ce qu'elles prétendent être.
Pendant le processus de vérification, le code MSIL est examiné en vue d'essayer de confirmer qu'il peut accéder
aux emplacements de mémoire et appeler des méthodes uniquement par le biais de types correctement définis.
Par exemple, le code n'autorise pas l'accès aux champs d'un objet d'une manière qui accepte le débordement de
capacité des emplacements de mémoire. Par ailleurs, le processus de vérification inspecte le code MSIL afin de
déterminer s'il a été généré correctement, car un code MSIL incorrect peut donner lieu à une violation des règles
de sécurité des types. Le processus de vérification passe un jeu de code de type sécurisé et correctement défini, et
ne passe que du code de ce type. Cependant, une partie du code de type sécurisé peut ne pas passer le test de
vérification avec succès en raison de certaines limitations du processus de vérification, et certains langages, de
par leur design, ne produisent pas un code de type sécurisé vérifié. Si le code de type sécurisé est requis par la
stratégie de sécurité, mais qu'il ne passe pas le test de vérification avec succès, une exception est levée quand le
code est exécuté.
Retour au début

Exécution de code
Le Common Language Runtime fournit l'infrastructure qui permet à l'exécution managée d'avoir lieu et les
services pouvant être utilisés pendant l'exécution. Pour qu'une méthode puisse être exécutée, elle doit d'abord
être compilée en un code spécifique au processeur. Chaque méthode pour laquelle le MSIL a été généré est
compilée juste-à-temps quand elle est appelée pour la première fois, puis s'exécute. Quand la méthode est
exécutée la fois suivante, le code natif existant traité par le compilateur JIT est exécuté. Le processus de
compilation JIT puis d'exécution du code est répété jusqu'à ce que l'exécution soit complètement terminée.
Pendant l'exécution, le code managé bénéficie de services tels que le garbage collection, la sécurité,
l'interopérabilité avec le code non managé, la prise en charge du débogage interlangage ainsi que la prise en
charge améliorée du déploiement et du versioning.
Dans Microsoft Windows Vista, le chargeur du système d’exploitation recherche des modules managés en
examinant un bit dans l’en-tête COFF. Le bit défini indique un module managé. Si le chargeur détecte des modules
managés, il charge mscoree.dll. _CorValidateImage et _CorImageUnloading informent le chargeur quand les
images de modules managés sont chargées et déchargées. _CorValidateImage effectue les actions suivantes :
1. garantit que le code est du code managé valide ;
2. change le point d'entrée dans l'image en point d'entrée dans le runtime.
Sous Windows 64 bits, _CorValidateImage modifie l'image en mémoire en la transformant du format PE32 au
format PE32+.
Retour au début

Voir aussi
Vue d’ensemble
Indépendance du langage et composants indépendants du langage
Métadonnées et composants autodescriptifs
Ilasm. exe (assembleur IL)
Sécurité
Interopération avec du code non managé
Déploiement
Assemblys dans .NET
Domaines d'application
Métadonnées et composants autodescriptifs
18/07/2020 • 16 minutes to read • Edit Online

Par le passé, un composant logiciel (.exe or .dll) écrit dans un langage ne pouvait pas utiliser aisément un
composant logiciel écrit dans un autre langage. COM a fourni une étape vers la résolution de ce problème. Le .NET
Framework facilite encore plus l'interopérabilité des composants en autorisant les compilateurs à émettre des
informations déclaratives supplémentaires dans tous les modules et assemblys. Ces informations, appelées
métadonnées, aident les composants à interagir de façon transparente.
Les métadonnées sont des informations binaires décrivant votre programme ; elles sont stockées dans un fichier
exécutable portable (fichier PE) du Common Language Runtime ou en mémoire. Lorsque vous compilez votre code
dans un fichier PE, les métadonnées sont insérées dans une partie du fichier, tandis que votre code est converti en
langage MSIL (Microsoft Intermediate Language) et inséré dans une autre partie du fichier. Les types et membre
définis et référencés dans un module ou un assembly sont décrits au sein des métadonnées. Quand le code est
exécuté, le runtime charge les métadonnées en mémoire et y fait référence pour découvrir les informations
concernant les classes de votre code, les membres, l'héritage, etc.
Les métadonnées décrivent tous les types et membres définis dans votre code sous une forme indépendante du
langage. Les métadonnées stockent les informations suivantes :
Description de l'assembly.
Identité (nom, version, culture, clé publique).
Les types exportés.
Les autres assemblys dont dépend cet assembly.
Les autorisations de sécurité nécessaires à l'exécution.
Description des types.
Nom, visibilité, classe de base et interfaces implémentées.
Membres (méthodes, champs, propriétés, événements, types imbriqués).
Attributs.
Éléments descriptifs supplémentaires qui modifient les types et les membres.

Avantages des métadonnées


Les métadonnées sont la clé d'un modèle de programmation plus simple et suppriment la nécessité de fichiers IDL
(Interface Definition Language), de fichiers d'en-tête ou de toute méthode externe de référence aux composants.
Les métadonnées permettent aux langages du .NET Framework de se décrire eux-mêmes automatiquement d'une
manière indépendante du langage, transparente aussi bien au développeur qu'à l'utilisateur. En outre, les
métadonnées sont extensibles via l'utilisation d'attributs. Les métadonnées offrent les principaux avantages
suivants :
Fichiers autodescriptifs.
Les modules et assemblys du Common Language Runtime sont autodescriptifs. Les métadonnées d'un
module contiennent tous les éléments nécessaires pour interagir avec un autre module. Les métadonnées
offrent automatiquement la fonctionnalité IDL du modèle COM, ce qui vous permet d'utiliser un seul fichier
pour la définition et l'implémentation. Les modules et assemblys du runtime ne nécessitent même pas
l'inscription dans le système d'exploitation. En conséquence, les descriptions utilisées par le runtime
reflètent toujours le code réel de votre fichier compilé, ce qui accroît la fiabilité de l'application.
Interopérabilité des langages et design à base de composants plus simple.
Les métadonnées fournissent toutes les informations requises concernant le code compilé pour que vous
puissiez hériter une classe à partir d'un fichier PE écrit dans un autre langage. Vous pouvez créer une
instance de toute classe écrite dans un langage managé (tout langage ciblant le Common Language
Runtime) sans vous préoccuper de marshaling explicite ou d'utiliser un code d'interopérabilité personnalisé.
Attributs.
Le .NET Framework vous permet de déclarer des types particuliers de métadonnées, appelés attributs, dans
votre fichier compilé. Les attributs peuvent être recherchés via le .NET Framework et sont utilisés pour
contrôler de façon plus détaillée comment votre programme se comporte au moment de l'exécution. En
outre, vous pouvez émettre vos propres métadonnées personnalisées dans les fichiers .NET Framework via
les attributs personnalisés définis par l'utilisateur. Pour plus d’informations, consultez Attributs.

Métadonnées et structure des fichiers PE


Les métadonnées sont stockées dans une section du fichier exécutable portable (fichier PE) .NET Framework, tandis
que le code MSIL (Microsoft Intermediate Language) est stocké dans une autre section. La partie métadonnées du
fichier contient un ensemble de structures de données de table et de tas. La partie MSIL contient le code MSIL et les
jetons de métadonnées qui font référence à la partie métadonnées du fichier PE. Vous pouvez rencontrer des
jetons de métadonnées quand vous utilisez des outils tels que le Désassembleur MSIL (Ildasm.exe) pour consulter
le MSIL de votre code, par exemple.
Tas et tables de métadonnées
Chaque table de métadonnées contient des informations sur les éléments de votre programme. Par exemple, une
table de métadonnées décrit les classes de votre code, une autre décrit les champs, etc. Si vous disposez de dix
classes dans votre code, la table des classes aura dix lignes, une par classe. Les tables de métadonnées font
référence à d'autres tables et d'autres tas. Par exemple, la table des métadonnées pour les classes fait référence à la
table pour les méthodes.
Les métadonnées stockent aussi des informations dans quatre structures de tas : chaîne, blob, chaîne utilisateur et
un Identificateur Global Unique (GUID, Globally Unique IDentifier). Toutes les chaînes utilisées pour nommer les
types et les membres sont stockées dans le tas de chaîne. Par exemple, une table de méthodes ne stocke pas
directement le nom d'une méthode particulière, mais pointe vers le nom de la méthode stocké dans le tas de
chaîne.
Jetons de métadonnées
Chaque ligne des tables de métadonnées est identifiée de façon unique dans la partie MSIL du fichier PE au moyen
d'un jeton de métadonnées. Conceptuellement, les jetons de métadonnées sont similaires à des pointeurs,
persistants dans le langage MSIL, qui font référence à une table de métadonnées spécifique.
Un jeton de métadonnées est un nombre stocké sur quatre octets. L'octet de poids le plus fort identifie la table de
métadonnées à laquelle un jeton donné fait référence (méthode, type, etc.). Les trois octets restants spécifient la
ligne de la table de métadonnées qui correspond à l'élément de programmation en cours de description. Si vous
définissez une méthode en C# et la compilez dans un fichier PE, le jeton de métadonnées suivant figurera
probablement dans la partie MSIL du fichier PE :
0x06000004

L’octet de poids le plus fort ( 0x06 ) indique qu’il s’agit d’un jeton MethodDef . Les trois autres octets ( 000004 )
indiquent au Common Language Runtime de rechercher dans la quatrième ligne de la table MethodDef les
informations décrivant la définition de cette méthode.
Métadonnées au sein d'un fichier PE
Quand un programme est compilé pour le Common Language Runtime, il est converti en fichier PE, composé de
trois parties. Le tableau ci-après décrit le contenu de chaque partie.

SEC T IO N P E C O N T EN U DE L A SEC T IO N P E

En-tête PE L'index des sections principales du fichier PE et l'adresse du


point d'entrée.

Le runtime utilise ces informations pour identifier le fichier


comme fichier PE et pour déterminer où commence l'exécution
lors du chargement du programme en mémoire.

Instructions MSIL Les instructions MSIL (Microsoft Intermediate Language)


composant votre code. Nombre d'instructions MSIL sont
accompagnées de jetons de métadonnées.

Métadonnées Tas et tables de métadonnées. Le runtime utilise cette section


pour enregistrer les informations sur chaque type et membre
de votre code. Cette section contient aussi des attributs
personnalisés et des informations de sécurité.

Utilisation de métadonnées au moment de l'exécution


Pour mieux comprendre les métadonnées et leur rôle dans le Common Language Runtime, il peut être utile de
construire un programme simple qui illustre comment les métadonnées influencent son comportement à
l'exécution. L'exemple de code suivant montre deux méthodes à l'intérieur d'une classe intitulée MyApp . La
méthode Main constitue le point d’entrée du programme, tandis que la méthode Add retourne simplement le
total de deux arguments de type entier.

Public Class MyApp


Public Shared Sub Main()
Dim ValueOne As Integer = 10
Dim ValueTwo As Integer = 20
Console.WriteLine("The Value is: {0}", Add(ValueOne, ValueTwo))
End Sub

Public Shared Function Add(One As Integer, Two As Integer) As Integer


Return (One + Two)
End Function
End Class

using System;
public class MyApp
{
public static int Main()
{
int ValueOne = 10;
int ValueTwo = 20;
Console.WriteLine("The Value is: {0}", Add(ValueOne, ValueTwo));
return 0;
}
public static int Add(int One, int Two)
{
return (One + Two);
}
}
Quand le code s'exécute, le runtime charge le module en mémoire et consulte les métadonnées correspondant à la
classe. Une fois chargé, le runtime effectue une analyse complète du flux MSIL de la méthode pour le convertir en
instructions machine natives rapides. Le runtime utilise un compilateur juste-à-temps (JIT, Just-In-Time) pour
convertir les instructions MSIL en code machine natif, une méthode à la fois, selon les besoins.
L'exemple suivant montre une partie du MSIL générée à partir de la fonction Main du code précédent. Vous
pouvez afficher le code MSIL et les métadonnées de toute application .NET Framework à l’aide du Désassembleur
MSIL (Ildasm.exe).

.entrypoint
.maxstack 3
.locals ([0] int32 ValueOne,
[1] int32 ValueTwo,
[2] int32 V_2,
[3] int32 V_3)
IL_0000: ldc.i4.s 10
IL_0002: stloc.0
IL_0003: ldc.i4.s 20
IL_0005: stloc.1
IL_0006: ldstr "The Value is: {0}"
IL_000b: ldloc.0
IL_000c: ldloc.1
IL_000d: call int32 ConsoleApplication.MyApp::Add(int32,int32) /* 06000003 */

Le compilateur JIT lit le code MSIL de la totalité de la méthode, l'analyse intégralement et génère des instructions
natives performantes pour la méthode. L’adresse IL_000d contient un jeton de métadonnées pour la méthode
Add ( /* 06000003 */ ) et le runtime utilise ce jeton pour consulter la troisième ligne de la table MethodDef .

Le tableau suivant montre une partie de la table MethodDef référencée par le jeton de métadonnées qui décrit la
méthode Add . Bien qu'il existe d'autres tables de métadonnées dans cet assembly et qu'elles aient leurs propres
valeurs uniques, seule cette table est prise en compte dans le tableau.

A DRESSE RVA NOM


( REL AT IVE SIGN AT URE
VIRT UA L ( P O IN T E VERS L E ( P O IN T E VERS L E
L IGN E A DDRESS) IM P L F L A GS IN DIC AT EURS TA S DE C H A ÎN E) TA S DE B LO B )

1 0x00002050 IL Public .ctor


(constructeur)
Géré ReuseSlot

SpecialName

RTSpecialName

.ctor

2 0x00002058 IL Public Principal String

Géré statique

ReuseSlot

3 0x0000208c IL Public Ajouter int, int, int

Géré statique

ReuseSlot

Chaque colonne de la table contient des informations importantes concernant votre code. La colonne RVA permet
au runtime de calculer l’adresse mémoire de départ du code MSIL qui définit la méthode. Les colonnes ImplFlags
et Flags contiennent des masques de bits qui décrivent la méthode (par exemple, ils indiquent si la méthode est
publique ou privée). La colonne Name indexe le nom de la méthode à partir du tas de chaîne. La colonne
Signature indexe la définition de la signature de la méthode dans le tas de blob.
Le runtime calcule l’adresse offset souhaitée à partir de la troisième ligne de la colonne RVA et retourne cette
adresse au compilateur JIT, qui poursuit alors jusqu’à la nouvelle adresse. Le compilateur JIT continue le traitement
du code MSIL à la nouvelle adresse jusqu'à ce qu'il rencontre un autre jeton de métadonnées, auquel cas le
processus est répété.
Grâce aux métadonnées, le runtime a accès à toutes les informations dont il a besoin pour charger votre code et le
traiter en instructions machine natives. Les métadonnées permettent ainsi les fichiers autodescriptifs et, en même
temps que le système de type commun (CTS, Common Type System), l'héritage interlangage.

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Attributs Décrit comment appliquer les attributs, écrire des attributs


personnalisés et récupérer les informations stockées dans les
attributs.
Génération d'applications de console dans le .NET
Framework
18/07/2020 • 3 minutes to read • Edit Online

Les applications dans le .NET Framework peuvent utiliser la classe System.Console pour lire et écrire des caractères
en provenance ou à destination de la console. Les données provenant de la console sont lues dans le flux d'entrée
standard, les données à destination de la console sont écrites dans le flux de sortie standard et les données d'erreur
à destination de la console sont écrites dans le flux de sortie standard des erreurs. Ces flux de données, associés
automatiquement à la console au démarrage de l'application, sont présentés respectivement en tant que propriétés
In, Out et Error.
La valeur de la propriété Console.In est un objet System.IO.TextReader, alors que les valeurs des propriétés
Console.Out et Console.Error sont des objets System.IO.TextWriter. Vous pouvez associer ces propriétés à des flux
qui ne représentent pas la console, ce qui vous permet de désigner un autre emplacement pour les entrées ou les
sorties. Par exemple, vous pouvez rediriger la sortie vers un fichier en définissant la propriété Console.Out sur un
objet System.IO.StreamWriter, qui encapsule un System.IO.FileStream au moyen de la méthode Console.SetOut. Il
n'est pas nécessaire que les propriétés Console.In et Console.Out fassent référence au même flux.

NOTE
Pour plus d'informations sur la génération d'applications de console, notamment des exemples dans C#, Visual Basic et C++,
consultez la documentation relative à la classe Console.

Si la console n'existe pas, comme c'est le cas dans une application Windows, la sortie écrite dans le flux de sortie
standard ne sera pas visible, puisqu'il n'existe pas de console sur laquelle écrire les informations. L'écriture
d'informations sur une console inaccessible ne déclenche pas d'exception.
Par ailleurs, pour activer la console pour la lecture et l’écriture dans une application Windows développée à l’aide
de Visual Studio, ouvrez la boîte de dialogue Propriétés du projet, cliquez sur l’onglet Application et définissez le
Type d’application sur Application console .
Les applications console ne disposent pas de pompe de messages démarrant par défaut. Par conséquent, les appels
de console aux minuteries Microsoft Win32 peuvent échouer.
La classe System.Console possède des méthodes qui peuvent lire des caractères ou des lignes complètes à partir
de la console. D'autres méthodes convertissent des données et mettent en forme des chaînes, puis écrivent les
chaînes mises en forme sur la console. Pour plus d’informations sur la mise en forme des chaînes, consultez Mise
en forme des types.

Voir aussi
System.Console
Mise en forme des types
Fichier et flux de données E/S
18/07/2020 • 17 minutes to read • Edit Online

E/S de fichier et de flux (entrées/sorties) fait référence au transfert de données vers ou depuis un support de
stockage. Dans le .NET Framework, les espaces de noms System.IO contiennent des types qui permettent la
lecture et l'écriture, de façon synchrone ou asynchrone, sur les flux de données et les fichiers. Ces espaces de
noms contiennent également les types qui exécutent la compression et la décompression sur les fichiers, et ceux
qui permettent la communication via des canaux et des ports série.
Un fichier est une collection ordonnée et nommée d'octets ayant un stockage persistant. Lorsque vous travaillez
sur des fichiers, vous manipulez des chemins d'accès aux répertoires, du stockage disque et des noms de fichiers
et de répertoires. En revanche, un flux est une séquence d'octets que vous pouvez lire et écrire dans un magasin
de stockage, qui peut être l'un des nombreux supports de stockage (par exemple, des disques ou de la mémoire).
De même qu'il existe d'autres magasins de stockage que les disques, il existe d'autres flux que les flux de fichiers,
tels que les flux de réseau, de mémoire et de canaux.

Fichiers et répertoires
Utilisez les types de l'espace de noms System.IO pour interagir avec les fichiers et les répertoires. Par exemple,
obtenez et définissez les propriétés des fichiers et des répertoires, et extrayez les collections de fichiers et de
répertoires en fonction de critères de recherche.
Pour les conventions de nommage des chemins et les nouvelles méthodes pour exprimer un chemin de fichier
pour les systèmes Windows, notamment avec la syntaxe des appareils DOS prise en charge dans .NET Core 1.1 et
ultérieur et le .NET Framework 4.6.2 et ultérieur, consultez Formats de chemin de fichier sur les systèmes
Windows.
Voici quelques classes de fichiers et de répertoires couramment utilisées :
File fournit des méthodes statiques pour la création, la copie, la suppression, le déplacement et l'ouverture
de fichiers et permet de créer un objet FileStream.
FileInfo fournit des méthodes d'instance pour la création, la copie, la suppression, le déplacement et
l'ouverture de fichiers et permet de créer un objet FileStream.
Directory fournit des méthodes statiques pour la création, le déplacement et l'énumération dans les
répertoires et les sous-répertoires.
DirectoryInfo fournit des méthodes d'instance pour la création, le déplacement et l'énumération dans les
répertoires et les sous-répertoires.
Path fournit des méthodes et des propriétés pour le traitement des chaînes de répertoire entre
plateformes.
Vous devez toujours fournir une gestion des exceptions robuste lors de l’appel des méthodes de système de
fichiers. Pour plus d’informations, consultez Gestion des erreurs E/S.
En plus d'utiliser ces classes, les utilisateurs de Visual Basic peuvent utiliser les méthodes et les propriétés
fournies par la classe Microsoft.VisualBasic.FileIO.FileSystem pour l'E/S de fichier.
Voir Comment : copier des répertoires, Comment : créer une liste de répertoires et Comment : énumérer des
répertoires et des fichiers.
Flux
La classe abstraite de base Stream prend en charge la lecture et l'écriture d'octets. Toutes les classes qui
représentent des flux héritent de la classe Stream. La classe Stream et ses classes dérivées donnent une vue
globale des sources de données et des référentiels, isolant ainsi le programmeur des détails propres au système
d'exploitation et aux périphériques sous-jacents.
Les flux impliquent trois opérations fondamentales :
Lecture : transfert de données d'un flux vers une structure de données tel qu'un tableau d'octets.
Écriture : transfert de données d'une source de données dans un flux.
Recherche : envoi d'une requête concernant la position actuelle dans un flux et modification de cette
dernière.
Selon la source de données sous-jacente ou le référentiel, un flux peut prendre en charge certaines de ces
capacités. Par exemple, la classe PipeStream ne prend pas en charge la recherche. Les propriétés CanRead,
CanWrite et CanSeek d'un flux spécifient les opérations que le flux prend en charge.
Voici quelques classes de flux couramment utilisées :
FileStream pour la lecture et l'écriture dans un fichier.
IsolatedStorageFileStream pour la lecture et l'écriture dans un fichier d'un stockage isolé.
MemoryStream pour la lecture et l'écriture dans la mémoire en tant que magasin de stockage.
BufferedStream pour l'amélioration des performances de vos opérations de lecture et d'écriture.
NetworkStream pour la lecture et l'écriture sur vos sockets réseau.
PipeStream pour la lecture et l'écriture sur des canaux nommés ou anonymes.
CryptoStream pour la liaison des flux de données aux transformations de chiffrement.
Pour obtenir un exemple d'utilisation de flux de façon asynchrone, consultez E/S sur fichier asynchrones.

Lecteurs et writers
L'espace de noms System.IO fournit également des types pour la lecture des caractères encodés à partir des flux
et l'écriture des caractères encodés dans les flux. En général, les flux sont conçus pour l'entrée et la sortie
d'octets. Les types lecteur et writer gèrent la conversion des caractères encodés en octets et inversement pour
permettre au flux de terminer l'opération. Chaque classe de lecteur et writer est associée à un flux, qui peut être
récupéré via la propriété BaseStream de la classe.
Voici quelques classes de lecteur et writer couramment utilisées :
BinaryReader et BinaryWriter pour la lecture et l'écriture des types de données primitifs comme valeurs
binaires.
StreamReader et StreamWriter pour la lecture et l'écriture à l'aide d'une valeur d'encodage pour convertir
des caractères en octets et inversement.
StringReader et StringWriter pour la lecture et l'écriture des caractères à partir de chaînes et inversement.
TextReader et TextWriter servent de classes de base abstraites à d'autres lecteurs et writers qui lisent et
écrivent des caractères et des chaînes, mais pas des données binaires.
Voir Comment : lire du texte dans un fichier, Comment : écrire du texte dans un fichier, Comment : lire les
caractères d'une chaîne et Comment : écrire des caractères dans une chaîne.
Opérations d’E/S asynchrones
La lecture ou l'écriture de grandes quantités de données peut consommer beaucoup de ressources. Vous devez
effectuer ces tâches de façon asynchrone si votre application doit rester réactive aux actions de l'utilisateur. Avec
les opérations d'E/S synchrones, le thread d'interface utilisateur est bloqué jusqu'à ce que l'opération
consommatrice de ressources soit terminée. Utilisez des opérations d’e/s asynchrones lors du développement
d’applications Windows 8. x Store pour éviter de créer l’impression que votre application a cessé de fonctionner.
Les membres asynchrones contiennent Async dans leurs noms, comme les méthodes CopyToAsync, FlushAsync,
ReadAsync et WriteAsync. Utilisez ces méthodes avec async et les mots clés await .
Pour plus d'informations, consultez E/S de fichier asynchrone.

Compression
La compression désigne le processus de réduction de la taille d'un fichier pour le stockage. La décompression
consiste à extraire le contenu d'un fichier compressé afin qu'il soit dans un format utilisable. L'espace de noms
System.IO.Compression contient des types pour la compression et la décompression de fichiers et de flux.
Les classes suivantes sont fréquemment utilisées lors de la compression et de la décompression de fichiers et de
flux :
ZipArchive pour la création et l'extraction des entrées de l'archive zip.
ZipArchiveEntry pour la représentation d'un fichier compressé.
ZipFile pour la création, l'extraction et l'ouverture d'un package compressé.
ZipFileExtensions pour la création et l'extraction des entrées dans un package compressé.
DeflateStream pour la compression et la décompression des flux en utilisant l'algorithme Deflate.
GZipStream pour la compression et la décompression des flux au format de données gzip.
Voir Comment : compresser et extraire des fichiers.

Stockage isolé
Le stockage isolé est un mécanisme de stockage de données qui offre une isolation et une sécurité en définissant
des méthodes standardisées pour associer du code à des données enregistrées. Le stockage fournit un système
de fichiers virtuel qui est isolé par utilisateur, assembly et (éventuellement) domaine. Le stockage isolé est
particulièrement utile lorsque votre application n'a pas l'autorisation d'accès aux fichiers utilisateur. Enregistrez
les paramètres ou les fichiers de votre application d'une façon contrôlée par la stratégie de sécurité de
l'ordinateur.
Le stockage isolé n’est pas disponible pour les applications du Windows 8. x Store ; au lieu de cela, utilisez les
classes de données d’application dans l' Windows.Storage espace de noms. Pour plus d’informations, consultez
Données de l’application.
Les classes suivantes sont fréquemment utilisées lors d'une implémentation de stockage isolé :
IsolatedStorage fournit la classe de base pour les implémentations de stockage isolé.
IsolatedStorageFile fournit une zone de stockage isolé qui contient les fichiers et les répertoires.
IsolatedStorageFileStream expose un fichier au sein d'un stockage isolé.
Voir Stockage isolé.
Opérations d’E/S dans les applications Windows Store
Le .NET pour les applications du Windows 8. x Store contient un grand nombre des types pour la lecture et
l’écriture dans les flux. Toutefois, cet ensemble n’inclut pas tous les types d’e/s .NET Framework.
Voici quelques différences importantes à noter lors de l’utilisation d’opérations d’e/s dans les applications du
Windows 8. x Store :
Les types spécifiquement associés à des opérations de fichier, tels que File , FileInfo Directory et
DirectoryInfo , ne sont pas inclus dans .net pour les applications du Windows 8. x Store. À la place, utilisez
les types dans l’espace de noms Windows.Storage de Windows Runtime, par exemple StorageFile et
StorageFolder.
Le stockage isolé n'est pas disponible ; à la place, utilisez les données d'application.
Utilisez les méthodes asynchrones, telles que ReadAsync et WriteAsync pour empêcher le blocage du
thread d'interface utilisateur.
Les types de compression ZipFile et ZipFileExtensions basés sur le chemin d’accès ne sont pas disponibles.
À la place, utilisez les types dans l’espace de noms Windows.Storage.Compression.
Vous pouvez convertir entre les flux .NET Framework et les flux Windows Runtime, si nécessaire. Pour plus
d’informations, consultez Comment : effectuer une conversion entre des flux de .NET Framework et des flux de
Windows Runtime ou WindowsRuntimeStreamExtensions .
Pour plus d’informations sur les opérations d’e/s dans une application du Windows 8. x Store, consultez
démarrage rapide : lecture et écriture de fichiers.

E/S et sécurité
Lorsque vous utilisez les classes de l’espace de noms System.IO, vous devez suivre les exigences de sécurité du
système d’exploitation telles que les listes de contrôle d’accès (ACL) pour contrôler l’accès aux fichiers et aux
répertoires. Ces spécifications s'ajoutent aux spécifications FileIOPermission existantes. Les listes de contrôle
d'accès peuvent être gérées par programmation. Pour plus d'informations, consultez Comment : ajouter ou
supprimer des entrées dans la liste de contrôle d'accès.
Les stratégies de sécurité par défaut empêchent les applications provenant d'Internet ou de l'intranet d'accéder
aux fichiers sur l'ordinateur de l'utilisateur. Par conséquent, n’utilisez pas les classes d’E/S qui requièrent un
chemin d’accès à un fichier physique lors de l’écriture du code qui sera téléchargé sur Internet ou sur l’intranet.
Au lieu de cela, utilisez le stockage isolé pour les applications de .NET Framework traditionnelles ou utilisez les
données d’application pour les applications du Windows 8. x Store.
La vérification de sécurité n'est exécutée qu'à la création du flux. Par conséquent, n'ouvrez pas de flux pour le
passer ensuite à du code ou à des domaines d'application d'un niveau de sécurité inférieur.

Rubriques connexes
Tâches d’e/s courantes
Présente les tâches d’E/S associées aux fichiers, aux répertoires et aux flux, et des liens vers du contenu et
des exemples appropriés pour chaque tâche.
E/s de fichier asynchrones
Décrit les opérations élémentaires des E/S asynchrones et leurs avantages en termes de performances.
Stockage isolé
Décrit un dispositif de stockage des données qui assure l'isolation et la sécurité en définissant des
solutions standardisées visant à associer le code aux données enregistrées.
Canaux
Décrit des opérations de canal nommé et anonyme dans le .NET Framework.
Fichiers mappés en mémoire
Décrit les fichiers mappés en mémoire, qui contiennent le contenu de fichiers stockés sur le disque dans la
mémoire virtuelle. Vous pouvez utiliser des fichiers mappés en mémoire afin de modifier des fichiers très
volumineux et de créer la mémoire partagée pour la communication entre processus.
Formats de chemin de fichier sur les systèmes
Windows
18/07/2020 • 27 minutes to read • Edit Online

Dans l’espace de noms System.IO, les membres de nombreux types incluent un paramètre path qui vous permet
de spécifier un chemin absolu ou relatif à une ressource de système de fichiers. Ce chemin est ensuite passé aux
API de système de fichiers Windows. Cette rubrique décrit les formats de chemins de fichier que vous pouvez
utiliser dans les systèmes Windows.

Chemins DOS traditionnels


Un chemin DOS standard peut être constitué de trois composants :
Une lettre de volume ou de lecteur suivie du séparateur de volumes ( : ).
Un nom de répertoire. Le caractère de séparation de répertoires sépare les sous-répertoires au sein de la
hiérarchie de répertoires imbriqués.
Un nom de fichier facultatif. Le caractère de séparation de répertoires sépare le chemin et le nom de fichier.
Si les trois composants sont présents, le chemin est absolu. Si aucune lettre de lecteur ou de volume n’est spécifiée
et que le caractère de séparation de répertoires précède les noms de répertoires, le chemin est relatif à la racine du
lecteur actif. Sinon, le chemin est relatif au répertoire actif. Le tableau suivant présente certains chemins de
répertoire et de fichier.

PAT H DESC RIP T IO N

C:\Documents\Newsletters\Summer2018.pdf Chemin de fichier absolu à partir de la racine du lecteur C:.

\Program Files\Custom Utilities\StringFinder.exe Chemin absolu à partir de la racine du lecteur actif.

2018\January.xlsx Chemin relatif à un fichier dans un sous-répertoire du


répertoire actif.

..\Publications\TravelBrochure.pdf Chemin relatif à un fichier dans répertoire qui est un pair du


répertoire actif.

C:\Projects\apilibrary\apilibrary.sln Chemin absolu à un fichier à partir de la racine du lecteur C:.

C:Projects\apilibrary\apilibrary.sln Chemin relatif à partir du répertoire actif du lecteur C:.

IMPORTANT
Notez la différence entre les deux derniers chemins. Les deux chemins spécifient le spécificateur de volume facultatif (C: dans
les deux cas), mais le premier commence par la racine du volume spécifié, contrairement au second. Le premier chemin est
donc un chemin absolu à partir du répertoire racine du lecteur C:, tandis que le second est un chemin relatif à partir du
répertoire actif du lecteur C:. L’utilisation involontaire de la deuxième forme à la place de la première est une source courante
de bogues impliquant des chemins de fichier Windows.

Pour déterminer si un chemin de fichier est complet (autrement dit, si le chemin est indépendant du répertoire actif
et qu’il reste inchangé quand le répertoire actif change), appelez la méthode IsPathFullyQualified. Notez qu’un tel
chemin peut inclure des segments de répertoire relatifs ( . et .. ) et toujours être complet si le chemin résolu
pointe toujours vers le même emplacement.
L’exemple suivant illustre la différence entre les chemins absolus et relatifs. Il part du principe que le répertoire
D:\FY2018\ existe et que vous n’avez défini aucun répertoire actif pour D:\ à partir de l’invite de commandes avant
d’exécuter l’exemple.

using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;

public class Example


{
public static void Main(string[] args)
{
Console.WriteLine($"Current directory is '{Environment.CurrentDirectory}'");
Console.WriteLine("Setting current directory to 'C:\\'");

Directory.SetCurrentDirectory(@"C:\");
string path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");
path = Path.GetFullPath(@"D:FY2018");
Console.WriteLine($"'D:FY2018' resolves to {path}");

Console.WriteLine("Setting current directory to 'D:\\Docs'");


Directory.SetCurrentDirectory(@"D:\Docs");

path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");
path = Path.GetFullPath(@"D:FY2018");

// This will be "D:\Docs\FY2018" as it happens to match the drive of the current directory
Console.WriteLine($"'D:FY2018' resolves to {path}");

Console.WriteLine("Setting current directory to 'C:\\'");


Directory.SetCurrentDirectory(@"C:\");

path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");

// This will be either "D:\FY2018" or "D:\FY2018\FY2018" in the subprocess. In the sub process,
// the command prompt set the current directory before launch of our application, which
// sets a hidden environment variable that is considered.
path = Path.GetFullPath(@"D:FY2018");
Console.WriteLine($"'D:FY2018' resolves to {path}");

if (args.Length < 1)
{
Console.WriteLine(@"Launching again, after setting current directory to D:\FY2018");
Uri currentExe = new Uri(Assembly.GetExecutingAssembly().GetName().CodeBase, UriKind.Absolute);
string commandLine = $"/C cd D:\\FY2018 & \"{currentExe.LocalPath}\" stop";
ProcessStartInfo psi = new ProcessStartInfo("cmd", commandLine); ;
Process.Start(psi).WaitForExit();

Console.WriteLine("Sub process returned:");


path = Path.GetFullPath(@"D:\FY2018");
Console.WriteLine($"'D:\\FY2018' resolves to {path}");
path = Path.GetFullPath(@"D:FY2018");
Console.WriteLine($"'D:FY2018' resolves to {path}");
}
Console.WriteLine("Press any key to continue... ");
Console.ReadKey();
}
}
// The example displays the following output:
// The example displays the following output:
// Current directory is 'C:\Programs\file-paths'
// Setting current directory to 'C:\'
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to d:\FY2018
// Setting current directory to 'D:\Docs'
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to D:\Docs\FY2018
// Setting current directory to 'C:\'
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to d:\FY2018
// Launching again, after setting current directory to D:\FY2018
// Sub process returned:
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to d:\FY2018
// The subprocess displays the following output:
// Current directory is 'C:\'
// Setting current directory to 'C:\'
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to D:\FY2018\FY2018
// Setting current directory to 'D:\Docs'
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to D:\Docs\FY2018
// Setting current directory to 'C:\'
// 'D:\FY2018' resolves to D:\FY2018
// 'D:FY2018' resolves to D:\FY2018\FY2018

Imports System.Diagnostics
Imports System.IO
Imports System.Reflection

Public Module Example

Public Sub Main(args() As String)


Console.WriteLine($"Current directory is '{Environment.CurrentDirectory}'")
Console.WriteLine("Setting current directory to 'C:\'")
Directory.SetCurrentDirectory("C:\")

Dim filePath As String = Path.GetFullPath("D:\FY2018")


Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")
filePath = Path.GetFullPath("D:FY2018")
Console.WriteLine($"'D:FY2018' resolves to {filePath}")

Console.WriteLine("Setting current directory to 'D:\\Docs'")


Directory.SetCurrentDirectory("D:\Docs")

filePath = Path.GetFullPath("D:\FY2018")
Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")
filePath = Path.GetFullPath("D:FY2018")

' This will be "D:\Docs\FY2018" as it happens to match the drive of the current directory
Console.WriteLine($"'D:FY2018' resolves to {filePath}")

Console.WriteLine("Setting current directory to 'C:\\'")


Directory.SetCurrentDirectory("C:\")

filePath = Path.GetFullPath("D:\FY2018")
Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")

' This will be either "D:\FY2018" or "D:\FY2018\FY2018" in the subprocess. In the sub process,
' the command prompt set the current directory before launch of our application, which
' sets a hidden environment variable that is considered.
filePath = Path.GetFullPath("D:FY2018")
Console.WriteLine($"'D:FY2018' resolves to {filePath}")

If args.Length < 1 Then


Console.WriteLine("Launching again, after setting current directory to D:\FY2018")
Dim currentExe As New Uri(Assembly.GetExecutingAssembly().GetName().CodeBase, UriKind.Absolute)
Dim currentExe As New Uri(Assembly.GetExecutingAssembly().GetName().CodeBase, UriKind.Absolute)
Dim commandLine As String = $"/C cd D:\FY2018 & ""{currentExe.LocalPath}"" stop"
Dim psi As New ProcessStartInfo("cmd", commandLine)
Process.Start(psi).WaitForExit()

Console.WriteLine("Sub process returned:")


filePath = Path.GetFullPath("D:\FY2018")
Console.WriteLine($"'D:\\FY2018' resolves to {filePath}")
filePath = Path.GetFullPath("D:FY2018")
Console.WriteLine($"'D:FY2018' resolves to {filePath}")
End If
Console.WriteLine("Press any key to continue... ")
Console.ReadKey()
End Sub
End Module
' The example displays the following output:
' Current directory is 'C:\Programs\file-paths'
' Setting current directory to 'C:\'
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to d:\FY2018
' Setting current directory to 'D:\Docs'
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to D:\Docs\FY2018
' Setting current directory to 'C:\'
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to d:\FY2018
' Launching again, after setting current directory to D:\FY2018
' Sub process returned:
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to d:\FY2018
' The subprocess displays the following output:
' Current directory is 'C:\'
' Setting current directory to 'C:\'
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to D:\FY2018\FY2018
' Setting current directory to 'D:\Docs'
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to D:\Docs\FY2018
' Setting current directory to 'C:\'
' 'D:\FY2018' resolves to D:\FY2018
' 'D:FY2018' resolves to D:\FY2018\FY2018

Si vous souhaitez voir les commentaires de code traduits dans des langues autres que l’anglais, faites-le nous
savoir dans ce problème de discussion GitHub.

Chemins UNC
Les chemins respectant la convention d’affectation de noms (UNC), qui sont utilisés pour accéder aux ressources
réseau, ont le format suivant :
Un nom de serveur ou d’hôte, précédé de \\. Le nom du serveur peut être un nom d’ordinateur NetBIOS ou une
adresse IP/FQDN (IPv4 et IPv6 sont pris en charge).
Un nom de partage, séparé du nom d’hôte par \. Ensemble, le serveur et le partage forment le volume.
Un nom de répertoire. Le caractère de séparation de répertoires sépare les sous-répertoires au sein de la
hiérarchie de répertoires imbriqués.
Un nom de fichier facultatif. Le caractère de séparation de répertoires sépare le chemin et le nom de fichier.
Voici quelques exemples de chemins UNC :

PAT H DESC RIP T IO N

\\system07\C$\ Répertoire racine du lecteur C: sur system07 .


PAT H DESC RIP T IO N

\\Server2\Share\Test\Foo.txt Fichier Foo.txt dans le répertoire Test du volume


\\Server2\Share.

Les chemins UNC doivent toujours être complets. Ils peuvent inclure des segments de répertoire relatifs ( . et ..
), mais il doivent faire partie d’un chemin complet. Pour utiliser des chemins relatifs, vous devez impérativement
mapper un chemin UNC à une lettre de lecteur.

Chemins de périphérique DOS


Le système d’exploitation Windows a un modèle objet unifié qui pointe vers toutes les ressources, notamment les
fichiers. Ces chemins d’objet sont accessibles à partir de la fenêtre de console et sont exposés à la couche Win32
par le biais d’un dossier spécial de liens symboliques mappés aux chemins DOS et UNC hérités. Ce dossier spécial
est accessible par le biais d’un chemin de périphérique DOS, dont la syntaxe est l’une des suivantes :
\\.\C:\Test\Foo.txt \\?\C:\Test\Foo.txt

En plus d’identifier un lecteur par sa lettre de lecteur, vous pouvez identifier un volume à l’aide de son GUID de
volume. Cela prend la forme :
\\.\Volume{b75e2c83-0000-0000-0000-602f00000000}\Test\Foo.txt
\\?\Volume{b75e2c83-0000-0000-0000-602f00000000}\Test\Foo.txt

NOTE
La syntaxe des chemins de périphérique DOS est prise en charge sur les implémentations .NET s’exécutant sur Windows à
compter de .NET Core 1.1 et .NET Framework 4.6.2.

Le chemin de périphérique DOS comprend les composants suivants :


Le spécificateur de chemin de périphérique ( \\.\ ou \\?\ ), qui identifie le chemin comme chemin de
périphérique DOS.

NOTE
Le spécificateur \\?\ est pris en charge dans toutes les versions de .NET Core et dans le .NET Framework à compter
de la version 4.6.2.

Un lien symbolique vers le « vrai » objet d’appareil (C: dans le cas d’un nom de lecteur, ou
Volume{b75e2c83-0000-0000-0000-602f00000000} dans le cas d’un GUID de volume).
Le premier segment du chemin de périphérique DOS après le spécificateur de chemin de périphérique
identifie le volume ou le lecteur. (Par exemple, \\?\C:\ et \\.\BootPartition\ .)
Il existe un lien spécifique pour les chemins UNC. Celui-ci s’appelle, sans surprise, UNC . Par exemple :
\\.\UNC\Server\Share\Test\Foo.txt \\?\UNC\Server\Share\Test\Foo.txt

Pour les chemins UNC de périphérique, la partie serveur/partage forme le volume. Par exemple, dans
\\?\server1\e:\utilities\\filecomparer\ , la partie serveur/partage est server1\utilities. Ceci est important
quand vous appelez une méthode comme Path.GetFullPath(String, String) avec des segments de répertoire
relatifs ; il est impossible de naviguer au-delà du volume.
Par définition, les chemins de périphérique DOS sont complets. Les segments de répertoire relatifs ( . et .. ) ne
sont pas autorisés. Les répertoires actifs ne font jamais partie de ce type de chemin.

Exemple : Comment faire référence au même fichier


L’exemple suivant illustre quelques-unes des méthodes vous permettant de faire référence à un fichier à l’aide des
API dans l’espace de noms System.IO. L’exemple instancie un objet FileInfo et utilise ses propriétés Name et Length
pour afficher le nom et la longueur du fichier.

using System;
using System.IO;

class Program
{
static void Main()
{
string[] filenames = {
@"c:\temp\test-file.txt",
@"\\127.0.0.1\c$\temp\test-file.txt",
@"\\LOCALHOST\c$\temp\test-file.txt",
@"\\.\c:\temp\test-file.txt",
@"\\?\c:\temp\test-file.txt",
@"\\.\UNC\LOCALHOST\c$\temp\test-file.txt",
@"\\127.0.0.1\c$\temp\test-file.txt" };

foreach (var filename in filenames)


{
FileInfo fi = new FileInfo(filename);
Console.WriteLine($"file {fi.Name}: {fi.Length:N0} bytes");
}
}
}
// The example displays output like the following:
// file test-file.txt: 22 bytes
// file test-file.txt: 22 bytes
// file test-file.txt: 22 bytes
// file test-file.txt: 22 bytes
// file test-file.txt: 22 bytes
// file test-file.txt: 22 bytes
// file test-file.txt: 22 bytes

Imports System.IO

Module Program
Sub Main()
Dim filenames() As String = {
"c:\temp\test-file.txt",
"\\127.0.0.1\c$\temp\test-file.txt",
"\\LOCALHOST\c$\temp\test-file.txt",
"\\.\c:\temp\test-file.txt",
"\\?\c:\temp\test-file.txt",
"\\.\UNC\LOCALHOST\c$\temp\test-file.txt",
"\\127.0.0.1\c$\temp\test-file.txt"}

For Each filename In filenames


Dim fi As New FileInfo(filename)
Console.WriteLine($"file {fi.Name}: {fi.Length:N0} bytes")
Next
End Sub
End Module

Normalisation des chemins d’accès


Presque tous les chemins passés aux API Windows sont normalisés. Durant la normalisation, Windows effectue les
étapes suivantes :
Identifie le chemin.
Applique le répertoire actif aux chemins partiels (relatifs).
Applique une mise en forme canonique aux séparateurs de composants et de répertoires.
Évalue les composants des répertoires relatifs ( . pour le répertoire actif et .. pour le répertoire parent).
Supprime certains caractères.
Cette normalisation se produit implicitement, mais vous pouvez l’effectuer explicitement en appelant la méthode
Path.GetFullPath, qui inclut dans un wrapper un appel à la fonction GetFullPathName(). Vous pouvez également
appeler directement la fonction GetFullPathName() Windows à l’aide de P/Invoke.
Identification du chemin
La première étape de normalisation d’un chemin consiste à identifier le type du chemin. Les chemins
appartiennent à l’une des catégories suivantes :
Chemins de périphérique : commencent par deux séparateurs et un point d’interrogation ou un point ( \\? ou
\\. ).
Chemins UNC : commencent par deux séparateurs sans point d’interrogation ou point.
Chemins DOS complets : commencent par une lettre de lecteur, un séparateur de volumes et un séparateur de
composants ( C:\ ).
Chemins désignant un périphérique hérité ( CON , LPT1 ).
Chemins relatifs à la racine du lecteur actif : commencent par un séparateur de composants unique ( \ ).
Chemins relatifs au répertoire actif d’un lecteur spécifié : commencent par une lettre de lecteur, un séparateur
de volumes et aucun séparateur de composants ( C: ).
Chemins relatifs au répertoire actif : ils commencent par autre chose ( temp\testfile.txt ).

Le type du chemin détermine si un répertoire actif est appliqué ou non d’une certaine façon. Il détermine
également la « racine » du chemin.
Gestion des périphériques hérités
Si le chemin est un périphérique DOS hérité comme CON , COM1 ou LPT1 , il est converti en chemin de
périphérique (préfixe \\.\ ajouté) et retourné.
Un chemin qui commence par un nom de périphérique hérité est toujours interprété comme périphérique hérité
par la méthode Path.GetFullPath(String). Par exemple, le chemin de périphérique DOS pour CON.TXT est \\.\CON ,
et celui pour COM1.TXT\file1.txt est \\.\COM1 .
Application du répertoire actif
Si un chemin n’est pas complet, Windows applique à celui-ci le répertoire actif. Le répertoire actif n’est pas appliqué
aux chemins UNC et de périphérique. Il n’est pas non plus appliqué à un lecteur complet avec le séparateur C:\.
Si le chemin commence par un séparateur de composant unique, le lecteur du répertoire actif est appliqué. Par
exemple, si le chemin de fichier est \utilities et le répertoire actif C:\temp\ , la normalisation produit
C:\utilities .

Si le chemin commence par une lettre de lecteur, comprend un séparateur de volumes, mais ne contient aucun
séparateur de composants, le dernier répertoire actif défini à partir de l’interface de commande pour le lecteur
spécifié est appliqué. Si le dernier répertoire actif n’a pas été défini, seul le lecteur est appliqué. Par exemple, si le
chemin de fichier est D:sources , le répertoire actif C:\Documents\ et le dernier répertoire actif sur le lecteur D:
D:\sources\ , le résultat est D:\sources\sources . Ces chemins « relatifs au lecteur » sont une source commune
d’erreurs de logique qui affectent les programmes et les scripts. Il est évidemment incorrect d’assumer qu’un
chemin commençant par une lettre et un signe deux-points n’est pas relatif.
Si le chemin commence par un élément autre qu’un séparateur, le lecteur et le répertoire actifs sont appliqués. Par
exemple, si le chemin est filecompare et le répertoire actif C:\utilities\ , le résultat est
C:\utilities\filecompare\ .

IMPORTANT
Les chemins relatifs sont dangereux dans les applications multithreads (c’est-à-dire dans la plupart des applications), car le
répertoire actif est un paramètre par processus. N’importe quel thread peut changer le répertoire actif à tout moment. À
compter de .NET Core 2.1, vous pouvez appeler la méthode Path.GetFullPath(String, String) pour obtenir un chemin absolu à
partir d’un chemin relatif et le chemin de base (répertoire actif) par rapport auquel vous souhaitez le résoudre.

Mise en forme canonique des séparateurs


Toute barre oblique ( / ) est convertie en séparateur Windows standard, à savoir la barre oblique inverse ( \ ).
Toute série de barres obliques après les deux premières barres obliques est réduite en barre oblique unique.
Évaluation des composants relatifs
À mesure que le chemin est traité, tout composant ou segment constitué d’un point unique ou double ( . ou .. )
est évalué :
Pour un point unique, le segment actif est supprimé, car il fait référence au répertoire actif.
Pour un point double, le segment actif et le segment parent sont supprimés, car il fait référence au
répertoire parent.
Les répertoires parents sont uniquement supprimés s’ils ne sont pas situés au-delà de la racine du chemin.
La racine du chemin varie selon le type de chemin. Il s’agit du lecteur ( C:\ ) pour les chemins DOS, du
serveur/partage pour les chemins UNC ( \\Server\Share ) et du préfixe du chemin de périphérique pour les
chemins de périphérique ( \\?\ ou \\.\ ).
Suppression de caractères
Outre les séries de séparateurs et de segments relatifs supprimés précédemment, d’autres caractères sont
supprimés durant la normalisation :
Si un segment se termine par un point unique, celui-ci est supprimé. (Un segment constitué d’un point
unique ou double est normalisé à l’étape précédente. Un segment constitué de trois ou quatre points n’est
pas normalisé, car il s’agit d’un nom de fichier/répertoire valide.)
Si le chemin ne se termine pas par un séparateur, tous les points et espaces (U+0020) de fin sont supprimés.
Si le dernier segment est simplement un point simple ou double, il relève de la règle des composants
relatifs ci-dessus.
Cette règle signifie que vous pouvez créer un nom de répertoire avec un espace de fin en ajoutant un
séparateur de fin après l’espace.

IMPORTANT
Ne créez jamais un répertoire ou un nom de fichier avec un espace de fin. Les espaces de fin peuvent rendre l’accès
à un répertoire difficile voire impossible, et il arrive fréquemment que des applications échouent quand vous tentez
de gérer des répertoires ou des fichiers dont les noms comprennent des espaces de fin.

Ignorer la normalisation
En règle générale, tout chemin passé à une API Windows est (effectivement) passé à la fonction GetFullPathName
et normalisé. Il existe toutefois une exception importante : un chemin de périphérique qui commence par un point
d’interrogation et non un point. À moins qu’il ne commence exactement par \\?\ (notez l’utilisation de la barre
oblique inverse canonique), le chemin est normalisé.
Pourquoi ignorer la normalisation ? Voici les trois raisons principales :
1. Accéder aux chemins normalement indisponibles, mais autorisés. Par exemple, il est impossible d’accéder à
un fichier ou à un répertoire appelé hidden. d’une autre manière.
2. Améliorer le niveau de performance en ignorant la normalisation précédemment effectuée.
3. Sur le .NET Framework uniquement, ignorer la vérification de la longueur du chemin ( MAX_PATH ) et
autoriser les chemins de plus de 259 caractères. La plupart des API autorisent ceci, à quelques exceptions
près.

NOTE
.NET Core gère implicitement les chemins longs et n’effectue pas la vérification MAX_PATH . La vérification MAX_PATH
s’applique uniquement au .NET Framework.

La seule différence entre les deux syntaxes de chemin de périphérique tient au fait que vous pouvez ignorer la
normalisation et les vérifications de la longueur maximale des chemins ; sinon, elles sont identiques. Soyez prudent
si vous choisissez d’ignorer la normalisation, car vous pouvez facilement créer des chemins difficiles à gérer pour
les applications « normales ».
Les chemins qui commencent par \\?\ sont toujours normalisés si vous les passez explicitement à la fonction
GetFullPathName.
Vous pouvez passer des chemins de plus de MAX_PATH caractères à GetFullPathName sans \\?\ . Elle prend en
charge les chemins de longueur arbitraire jusqu’à la taille de chaîne maximale gérée par Windows.

Casse et système de fichiers Windows


Le fait que les noms de chemin et de répertoire ne respectent pas la casse est une particularité du système de
fichiers Windows que les développeurs et utilisateurs d’autres systèmes d’exploitation trouvent déroutante.
Autrement dit, les noms de répertoire et de fichier reflètent la casse des chaînes utilisée au moment de leur
création. Par exemple, l’appel de méthode

Directory.Create("TeStDiReCtOrY");

Directory.Create("TeStDiReCtOrY")

crée un répertoire nommé TeStDiReCtOrY. Si vous renommez un répertoire ou un fichier pour changer sa casse, le
nom du répertoire ou du fichier reflète la casse de la chaîne utilisée au moment du renommage. Par exemple, le
code suivant renomme un fichier nommé test.txt en Test.txt :
using System.IO;

class Example
{
static void Main()
{
var fi = new FileInfo(@".\test.txt");
fi.MoveTo(@".\Test.txt");
}
}

Imports System.IO

Module Example
Public Sub Main()
Dim fi As New FileInfo(".\test.txt")
fi.MoveTo(".\Test.txt")
End Sub
End Module

Toutefois, les comparaisons des noms de répertoire et de fichier ne respectent pas la casse. Si vous recherchez un
fichier nommé « test.txt », les API du système de fichiers .NET ignorent la casse dans la comparaison. Test.txt,
TEST.TXT, test.TXT et toute autre combinaison de lettres majuscules et minuscules équivalent à « test.txt ».
Tâches d’E/S courantes
18/07/2020 • 3 minutes to read • Edit Online

L'espace de noms System.IO fournit plusieurs classes qui permettent d'exécuter différentes actions, telles que la
lecture et l'écriture, sur des fichiers, des répertoires et des flux de données. Pour plus d’informations, consultez e/s
de fichier et de flux.

Tâches de fichier courantes


A C T IO N À RÉA L ISER. . . C O N SULT EZ L 'EXEM P L E DÉC RIT DA N S C ET T E RUB RIQ UE. . .

Créer un fichier texte Méthode File.CreateText

Méthode FileInfo.CreateText

Méthode File.Create

Méthode FileInfo.Create

Écrire dans un fichier texte Procédure : écrire du texte dans un fichier

Comment : écrire un fichier texte (C++/CLI)

Lire à partir d'un fichier texte Procédure : lire le texte d’un fichier

Ajouter du texte dans un fichier Procédure : ouvrir un fichier journal et y ajouter des éléments

Méthode File.AppendText

Méthode FileInfo.AppendText

Renommer ou déplacer un fichier Méthode File.Move

Méthode FileInfo.MoveTo

Supprimer un fichier Méthode File.Delete

Méthode FileInfo.Delete

Copier un fichier Méthode File.Copy

Méthode FileInfo.CopyTo

Obtenir la taille d'un fichier Propriété FileInfo.Length

Obtenir les attributs d'un fichier Méthode File.GetAttributes

Définir les attributs d'un fichier Méthode File.SetAttributes

Déterminer si un fichier existe Méthode File.Exists


A C T IO N À RÉA L ISER. . . C O N SULT EZ L 'EXEM P L E DÉC RIT DA N S C ET T E RUB RIQ UE. . .

Lire à partir d'un fichier binaire Procédure : lire et écrire dans un fichier de données créé
récemment

Écrire dans un fichier binaire Procédure : lire et écrire dans un fichier de données créé
récemment

Récupérer une extension de nom de fichier Méthode Path.GetExtension

Récupérer le chemin d’accès qualifié complet d’un fichier Méthode Path.GetFullPath

Récupérer le nom de fichier et son extension à partir d’un Méthode Path.GetFileName


chemin d’accès

Modifier l’extension d’un fichier Méthode Path.ChangeExtension

Tâches de répertoire courantes


A C T IO N À RÉA L ISER. . . C O N SULT EZ L 'EXEM P L E DÉC RIT DA N S C ET T E RUB RIQ UE. . .

Accéder à un fichier dans un dossier spécial comme Mes Procédure : écrire du texte dans un fichier
documents

Créer un répertoire Méthode Directory.CreateDirectory

Propriété FileInfo.Directory

Créer un sous-répertoire Méthode DirectoryInfo.CreateSubdirectory

Renommer ou déplacer un répertoire Méthode Directory.Move

Méthode DirectoryInfo.MoveTo

Copier un répertoire Procédure : Copier des répertoires

Supprimer un répertoire Méthode Directory.Delete

Méthode DirectoryInfo.Delete

Afficher les fichiers et les sous-répertoires d'un répertoire Procédure : énumérer des répertoires et des fichiers

Rechercher la taille d'un répertoire Classe System.IO.Directory

Déterminer si un répertoire existe Méthode Directory.Exists

Voir aussi
E/s de fichier et de flux
Composition de flux
E/s de fichier asynchrones
Comment : copier des répertoires
18/07/2020 • 2 minutes to read • Edit Online

Cette rubrique montre comment utiliser les classes d’E/S pour copier de manière synchrone le contenu d’un
répertoire vers un autre emplacement.
Pour obtenir un exemple de copie de fichier asynchrone, consultez E/S sur fichier asynchrones.
Cet exemple copie des sous-répertoires en définissant la propriété copySubDirs de la méthode DirectoryCopy sur
true . La méthode DirectoryCopy copie les sous-répertoires de manière récursive en s’appelant elle-même sur
chaque sous-répertoire jusqu’à ce qu’il n’y ait plus rien à copier.

Exemple
using System;
using System.IO;

class DirectoryCopyExample
{
static void Main()
{
// Copy from the current directory, include subdirectories.
DirectoryCopy(".", @".\temp", true);
}

private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)


{
// Get the subdirectories for the specified directory.
DirectoryInfo dir = new DirectoryInfo(sourceDirName);

if (!dir.Exists)
{
throw new DirectoryNotFoundException(
"Source directory does not exist or could not be found: "
+ sourceDirName);
}

DirectoryInfo[] dirs = dir.GetDirectories();


// If the destination directory doesn't exist, create it.
if (!Directory.Exists(destDirName))
{
Directory.CreateDirectory(destDirName);
}

// Get the files in the directory and copy them to the new location.
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string temppath = Path.Combine(destDirName, file.Name);
file.CopyTo(temppath, false);
}

// If copying subdirectories, copy them and their contents to new location.


if (copySubDirs)
{
foreach (DirectoryInfo subdir in dirs)
{
string temppath = Path.Combine(destDirName, subdir.Name);
DirectoryCopy(subdir.FullName, temppath, copySubDirs);
}
}
}
}
Imports System.IO

Class DirectoryCopyExample

Shared Sub Main()


' Copy from the current directory, include subdirectories.
DirectoryCopy(".", ".\\temp", True)
End Sub

Private Shared Sub DirectoryCopy( _


ByVal sourceDirName As String, _
ByVal destDirName As String, _
ByVal copySubDirs As Boolean)

' Get the subdirectories for the specified directory.


Dim dir As DirectoryInfo = New DirectoryInfo(sourceDirName)

If Not dir.Exists Then


Throw New DirectoryNotFoundException( _
"Source directory does not exist or could not be found: " _
+ sourceDirName)
End If

Dim dirs As DirectoryInfo() = dir.GetDirectories()


' If the destination directory doesn't exist, create it.
If Not Directory.Exists(destDirName) Then
Directory.CreateDirectory(destDirName)
End If
' Get the files in the directory and copy them to the new location.
Dim files As FileInfo() = dir.GetFiles()
For Each file In files
Dim temppath As String = Path.Combine(destDirName, file.Name)
file.CopyTo(temppath, False)
Next file

' If copying subdirectories, copy them and their contents to new location.
If copySubDirs Then
For Each subdir In dirs
Dim temppath As String = Path.Combine(destDirName, subdir.Name)
DirectoryCopy(subdir.FullName, temppath, copySubDirs)
Next subdir
End If
End Sub
End Class

Si vous souhaitez voir les commentaires de code traduits dans des langues autres que l’anglais, faites-le nous
savoir dans ce problème de discussion GitHub.

Voir aussi
FileInfo
DirectoryInfo
FileStream
E/S de fichier et de flux
Tâches d’E/S courantes
E/S sur fichier asynchrones
Comment : énumérer des répertoires et des fichiers
18/07/2020 • 6 minutes to read • Edit Online

Les collections énumérables offrent de meilleures performances que les tableaux lorsque vous travaillez avec
collections volumineuses de fichiers et de répertoires. Pour énumérer des répertoires et des fichiers, utilisez des
méthodes qui retournent une collection énumérable de noms de répertoire ou de fichier ou leurs objets
DirectoryInfo, FileInfo ou FileSystemInfo.
Si vous souhaitez parcourir et retourner uniquement les noms des répertoires ou des fichiers, utilisez les
méthodes d’énumération de la classe Directory. Si vous souhaitez parcourir et retourner d’autres propriétés des
répertoires ou des fichiers, utilisez les classes DirectoryInfo et FileSystemInfo.
Vous pouvez utiliser les collections énumérables issues de ces méthodes en tant que paramètre IEnumerable<T>
pour les constructeurs de classes de collection, comme List<T>.
Le tableau suivant récapitule les méthodes qui retournent des collections énumérables de fichiers et de
répertoires .

P O UR PA RC O URIR ET RETO URN ER UT IL ISER L A M ÉT H O DE

Noms de répertoires Directory.EnumerateDirectories

Informations sur le répertoire (DirectoryInfo) DirectoryInfo.EnumerateDirectories

Noms de fichiers Directory.EnumerateFiles

Informations sur le fichier (FileInfo) DirectoryInfo.EnumerateFiles

Noms d’entrée de système de fichiers Directory.EnumerateFileSystemEntries

Informations sur les entrées de système de fichiers DirectoryInfo.EnumerateFileSystemInfos


(FileSystemInfo)

Noms de répertoires et de fichiers Directory.EnumerateFileSystemEntries

NOTE
Bien que vous puissiez énumérer immédiatement tous les fichiers dans les sous-répertoires d’un répertoire parent à l’aide
de l’option AllDirectories de l’énumération SearchOption facultative, des erreurs UnauthorizedAccessException peuvent
rendre l’énumération incomplète. Vous pouvez intercepter ces exceptions en énumérant d’abord les répertoires, puis les
fichiers.

Exemples : utiliser la classe Directory


L’exemple suivant utilise la méthode Directory.EnumerateDirectories(String) pour obtenir une liste des noms de
répertoires de niveau supérieur dans un chemin spécifié.
using System;
using System.Collections.Generic;
using System.IO;

class Program
{
private static void Main(string[] args)
{
try
{
// Set a variable to the My Documents path.
string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

List<string> dirs = new List<string>(Directory.EnumerateDirectories(docPath));

foreach (var dir in dirs)


{
Console.WriteLine($"{dir.Substring(dir.LastIndexOf(Path.DirectorySeparatorChar) + 1)}");
}
Console.WriteLine($"{dirs.Count} directories found.");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine(ex.Message);
}
catch (PathTooLongException ex)
{
Console.WriteLine(ex.Message);
}
}
}

Imports System.Collections.Generic
Imports System.IO

Module Module1

Sub Main()
Try
Dim dirPath As String = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)

Dim dirs As List(Of String) = New List(Of String)(Directory.EnumerateDirectories(dirPath))

For Each folder In dirs


Console.WriteLine($"{dir.Substring(dir.LastIndexOf(Path.DirectorySeparatorChar) + 1)}")
Next
Console.WriteLine($"{dirs.Count} directories found.")
Catch ex As UnauthorizedAccessException
Console.WriteLine(ex.Message)
Catch ex As PathTooLongException
Console.WriteLine(ex.Message)
End Try

End Sub
End Module

L’exemple suivant utilise la méthode Directory.EnumerateFiles(String, String, SearchOption) pour énumérer de


manière récursive tous les noms de fichiers dans un répertoire et dans les sous-répertoires qui correspondent à
un certain modèle. Il lit chaque ligne de chaque fichier et affiche les lignes qui contiennent la chaîne spécifiée, avec
les noms de fichiers et chemins correspondants.
using System;
using System.IO;
using System.Linq;

class Program
{
static void Main(string[] args)
{
try
{
// Set a variable to the My Documents path.
string docPath =
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

var files = from file in Directory.EnumerateFiles(docPath, "*.txt", SearchOption.AllDirectories)


from line in File.ReadLines(file)
where line.Contains("Microsoft")
select new
{
File = file,
Line = line
};

foreach (var f in files)


{
Console.WriteLine($"{f.File}\t{f.Line}");
}
Console.WriteLine($"{files.Count().ToString()} files found.");
}
catch (UnauthorizedAccessException uAEx)
{
Console.WriteLine(uAEx.Message);
}
catch (PathTooLongException pathEx)
{
Console.WriteLine(pathEx.Message);
}
}
}

Imports System.IO
Imports System.Xml.Linq

Module Module1

Sub Main()
Try
Dim docPath As String = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
Dim files = From chkFile In Directory.EnumerateFiles(docPath, "*.txt",
SearchOption.AllDirectories)
From line In File.ReadLines(chkFile)
Where line.Contains("Microsoft")
Select New With {.curFile = chkFile, .curLine = line}

For Each f In files


Console.WriteLine($"{f.File}\t{f.Line}")
Next
Console.WriteLine($"{files.Count} files found.")
Catch uAEx As UnauthorizedAccessException
Console.WriteLine(uAEx.Message)
Catch pathEx As PathTooLongException
Console.WriteLine(pathEx.Message)
End Try
End Sub
End Module
Exemples : utiliser la classe DirectoryInfo
L’exemple suivant utilise la méthode DirectoryInfo.EnumerateDirectories pour lister une collection de répertoires
de niveau supérieur dont CreationTimeUtc est antérieur à une certaine valeur DateTime.

using System;
using System.IO;

namespace EnumDir
{
class Program
{
static void Main(string[] args)
{
// Set a variable to the Documents path.
string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

DirectoryInfo dirPrograms = new DirectoryInfo(docPath);


DateTime StartOf2009 = new DateTime(2009, 01, 01);

var dirs = from dir in dirPrograms.EnumerateDirectories()


where dir.CreationTimeUtc > StartOf2009
select new
{
ProgDir = dir,
};

foreach (var di in dirs)


{
Console.WriteLine($"{di.ProgDir.Name}");
}
}
}
}
// </Snippet1>

Imports System.IO

Module Module1

Sub Main()

Dim dirPath As String = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)


Dim dirPrograms As New DirectoryInfo(dirPath)
Dim StartOf2009 As New DateTime(2009, 1, 1)

Dim dirs = From dir In dirPrograms.EnumerateDirectories()


Where dir.CreationTimeUtc > StartOf2009

For Each di As DirectoryInfo In dirs


Console.WriteLine("{0}", di.Name)
Next

End Sub

End Module

L’exemple suivant utilise la méthode DirectoryInfo.EnumerateFiles pour lister tous les fichiers dont Length dépasse
10 Mo. Cet exemple énumère d’abord les répertoires de niveau supérieur, pour intercepter les éventuelles
exceptions d’accès non autorisé, puis énumère les fichiers.

using System;
using System.IO;
using System.IO;

class Program
{
static void Main(string[] args)
{
// Set a variable to the My Documents path.
string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

DirectoryInfo diTop = new DirectoryInfo(docPath);

try
{
foreach (var fi in diTop.EnumerateFiles())
{
try
{
// Display each file over 10 MB;
if (fi.Length > 10000000)
{
Console.WriteLine($"{fi.FullName}\t\t{fi.Length.ToString("NO")}");
}
}
catch (UnauthorizedAccessException unAuthTop)
{
Console.WriteLine($"{unAuthTop.Message}");
}
}

foreach (var di in diTop.EnumerateDirectories("*"))


{
try
{
foreach (var fi in di.EnumerateFiles("*", SearchOption.AllDirectories))
{
try
{
// Display each file over 10 MB;
if (fi.Length > 10000000)
{
Console.WriteLine($"{fi.FullName}\t\t{fi.Length.ToString("NO")}");
}
}
catch (UnauthorizedAccessException unAuthFile)
{
Console.WriteLine($"unAuthFile: {unAuthFile.Message}");
}
}
}
catch (UnauthorizedAccessException unAuthSubDir)
{
Console.WriteLine($"unAuthSubDir: {unAuthSubDir.Message}");
}
}
}
catch (DirectoryNotFoundException dirNotFound)
{
Console.WriteLine($"{dirNotFound.Message}");
}
catch (UnauthorizedAccessException unAuthDir)
{
Console.WriteLine($"unAuthDir: {unAuthDir.Message}");
}
catch (PathTooLongException longPath)
{
Console.WriteLine($"{longPath.Message}");
}
}
}
Imports System.IO

Class Program
Public Shared Sub Main(ByVal args As String())
Dim dirPath As String = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
Dim diTop As New DirectoryInfo(dirPath)
Try
For Each fi In diTop.EnumerateFiles()
Try
' Display each file over 10 MB;
If fi.Length > 10000000 Then
Console.WriteLine("{0}" & vbTab & vbTab & "{1}", fi.FullName,
fi.Length.ToString("N0"))
End If
Catch unAuthTop As UnauthorizedAccessException
Console.WriteLine($"{unAuthTop.Message}")
End Try
Next

For Each di In diTop.EnumerateDirectories("*")


Try
For Each fi In di.EnumerateFiles("*", SearchOption.AllDirectories)
Try
' // Display each file over 10 MB;
If fi.Length > 10000000 Then
Console.WriteLine("{0}" & vbTab &
vbTab & "{1}", fi.FullName, fi.Length.ToString("N0"))
End If
Catch unAuthFile As UnauthorizedAccessException
Console.WriteLine($"unAuthFile: {unAuthFile.Message}")
End Try
Next
Catch unAuthSubDir As UnauthorizedAccessException
Console.WriteLine($"unAuthSubDir: {unAuthSubDir.Message}")
End Try
Next
Catch dirNotFound As DirectoryNotFoundException
Console.WriteLine($"{dirNotFound.Message}")
Catch unAuthDir As UnauthorizedAccessException
Console.WriteLine($"unAuthDir: {unAuthDir.Message}")
Catch longPath As PathTooLongException
Console.WriteLine($"{longPath.Message}")
End Try
End Sub
End Class

Voir aussi
E/S de fichier et de flux
Comment : lire et écrire dans un fichier de données
nouvellement créé
18/07/2020 • 3 minutes to read • Edit Online

Les classes System.IO.BinaryWriter et System.IO.BinaryReader sont utilisées pour écrire et lire des données autres
que des chaînes de caractères. L’exemple suivant montre comment créer un flux de fichier vide, y écrire des
données, puis les lire.
L’exemple créé un fichier de données appelé Test.data dans le répertoire actif, crée les objets BinaryWriter et
BinaryReader associés, puis utilise l’objet BinaryWriter pour écrire les entiers de 0 à 10 dans Test.data, ce qui laisse
le pointeur de fichier à la fin du fichier. Ensuite, l’objet BinaryReader replace le pointeur de fichier au début, puis lit
le contenu spécifié.

NOTE
Si Test.data existe déjà dans le répertoire actif, une exception IOException est levée. Utilisez l’option de mode de fichier
FileMode.Create plutôt que FileMode.CreateNew pour toujours créer un fichier sans lever d’exception.

Exemple
using System;
using System.IO;

class MyStream
{
private const string FILE_NAME = "Test.data";

public static void Main()


{
if (File.Exists(FILE_NAME))
{
Console.WriteLine($"{FILE_NAME} already exists!");
return;
}

using (FileStream fs = new FileStream(FILE_NAME, FileMode.CreateNew))


{
using (BinaryWriter w = new BinaryWriter(fs))
{
for (int i = 0; i < 11; i++)
{
w.Write(i);
}
}
}

using (FileStream fs = new FileStream(FILE_NAME, FileMode.Open, FileAccess.Read))


{
using (BinaryReader r = new BinaryReader(fs))
{
for (int i = 0; i < 11; i++)
{
Console.WriteLine(r.ReadInt32());
}
}
}
}
}

// The example creates a file named "Test.data" and writes the integers 0 through 10 to it in binary format.
// It then writes the contents of Test.data to the console with each integer on a separate line.
Imports System.IO

Class MyStream
Private Const FILE_NAME As String = "Test.data"

Public Shared Sub Main()


If File.Exists(FILE_NAME) Then
Console.WriteLine($"{FILE_NAME} already exists!")
Return
End If

Using fs As New FileStream(FILE_NAME, FileMode.CreateNew)


Using w As New BinaryWriter(fs)
For i As Integer = 0 To 10
w.Write(i)
Next
End Using
End Using

Using fs As New FileStream(FILE_NAME, FileMode.Open, FileAccess.Read)


Using r As New BinaryReader(fs)
For i As Integer = 0 To 10
Console.WriteLine(r.ReadInt32())
Next
End Using
End Using
End Sub
End Class

' The example creates a file named "Test.data" and writes the integers 0 through 10 to it in binary format.
' It then writes the contents of Test.data to the console with each integer on a separate line.

Voir aussi
BinaryReader
BinaryWriter
FileStream
FileStream.Seek
SeekOrigin
Comment : énumérer des répertoires et des fichiers
Procédure : ouvrir un fichier journal et y ajouter des éléments
Comment : lire du texte à partir d’un fichier
Comment : écrire du texte dans un fichier
Comment : lire les caractères d’une chaîne
Comment : écrire des caractères dans une chaîne
E/S de fichier et de flux
Procédure : ouvrir un fichier journal et y ajouter des
éléments
18/07/2020 • 3 minutes to read • Edit Online

StreamWriter et StreamReader écrivent des caractères dans et lisent des caractères à partir des flux. L’exemple de
code suivant ouvre le fichier log.txt pour l’entrée, ou crée le fichier s’il n’existe pas déjà, puis ajoute les informations
à la fin du fichier. Ensuite, l’exemple écrit le contenu du fichier dans la sortie standard en vue de son affichage.
Comme alternative à cet exemple, vous pourriez stocker les informations dans une chaîne ou un tableau de
chaînes, puis utiliser la méthode File.WriteAllText ou File.WriteAllLines pour obtenir la même fonctionnalité.

NOTE
Les utilisateurs Visual Basic peuvent choisir d’utiliser les méthodes et les propriétés fournies par la classe Log ou la classe
FileSystem pour la création ou l’écriture dans des fichiers journaux.

Exemple
using System;
using System.IO;

class DirAppend
{
public static void Main()
{
using (StreamWriter w = File.AppendText("log.txt"))
{
Log("Test1", w);
Log("Test2", w);
}

using (StreamReader r = File.OpenText("log.txt"))


{
DumpLog(r);
}
}

public static void Log(string logMessage, TextWriter w)


{
w.Write("\r\nLog Entry : ");
w.WriteLine($"{DateTime.Now.ToLongTimeString()} {DateTime.Now.ToLongDateString()}");
w.WriteLine(" :");
w.WriteLine($" :{logMessage}");
w.WriteLine ("-------------------------------");
}

public static void DumpLog(StreamReader r)


{
string line;
while ((line = r.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
// The example creates a file named "log.txt" and writes the following lines to it,
// or appends them to the existing "log.txt" file:

// Log Entry : <current long time string> <current long date string>
// :
// :Test1
// -------------------------------

// Log Entry : <current long time string> <current long date string>
// :
// :Test2
// -------------------------------

// It then writes the contents of "log.txt" to the console.


Imports System.IO

Class DirAppend
Public Shared Sub Main()
Using w As StreamWriter = File.AppendText("log.txt")
Log("Test1", w)
Log("Test2", w)
End Using

Using r As StreamReader = File.OpenText("log.txt")


DumpLog(r)
End Using
End Sub

Public Shared Sub Log(logMessage As String, w As TextWriter)


w.Write(vbCrLf + "Log Entry : ")
w.WriteLine($"{DateTime.Now.ToLongTimeString()} {DateTime.Now.ToLongDateString()}")
w.WriteLine(" :")
w.WriteLine($" :{logMessage}")
w.WriteLine("-------------------------------")
End Sub

Public Shared Sub DumpLog(r As StreamReader)


Dim line As String
line = r.ReadLine()
While Not (line Is Nothing)
Console.WriteLine(line)
line = r.ReadLine()
End While
End Sub
End Class

' The example creates a file named "log.txt" and writes the following lines to it,
' or appends them to the existing "log.txt" file:

' Log Entry : <current long time string> <current long date string>
' :
' :Test1
' -------------------------------

' Log Entry : <current long time string> <current long date string>
' :
' :Test2
' -------------------------------

' It then writes the contents of "log.txt" to the console.

Voir aussi
StreamWriter
StreamReader
File.AppendText
File.OpenText
StreamReader.ReadLine
Comment : énumérer des répertoires et des fichiers
Comment : lire et écrire dans un fichier de données nouvellement créé
Comment : lire du texte à partir d’un fichier
Comment : écrire du texte dans un fichier
Comment : lire les caractères d’une chaîne
Comment : écrire des caractères dans une chaîne
E/S de fichier et de flux
Comment : écrire du texte dans un fichier
18/07/2020 • 7 minutes to read • Edit Online

Cette rubrique présente différentes façons d’écrire du texte dans un fichier pour une application .NET.
Les classes et méthodes suivantes sont généralement utilisées pour écrire du texte dans un fichier :
StreamWriter contient des méthodes permettant d’écrire dans un fichier de façon synchrone (Write et
WriteLine) ou asynchrone (WriteAsync et WriteLineAsync).
File fournit des méthodes statiques pour écrire du texte dans un fichier, comme WriteAllLines et
WriteAllText, ou pour ajouter du texte à un fichier, comme AppendAllLines, AppendAllText et AppendText.
Path concerne les chaînes qui contiennent des informations relatives au chemin d’un fichier ou d’un
répertoire. Elle contient la méthode Combine et, dans .NET Core versions 2.1 et ultérieures, les méthodes
Join et TryJoin, qui permettent la concaténation de chaînes pour générer un chemin de fichier ou de
répertoire.

NOTE
Les exemples suivants ne montrent que la quantité minimale de code nécessaire. Une application réelle fournit
généralement une vérification des erreurs et une gestion des exceptions plus robuste.

Exemple : écrire du texte de façon synchrone avec StreamWriter


L’exemple suivant montre comment utiliser la classe StreamWriter pour écrire, ligne par ligne, du texte de façon
synchrone dans un nouveau fichier. Étant donné que l’objet StreamWriter est déclaré et instancié dans une
instruction using , la méthode Dispose est appelée, ce qui vide et ferme automatiquement le flux.
using System;
using System.IO;

class Program
{
static void Main(string[] args)
{

// Create a string array with the lines of text


string[] lines = { "First line", "Second line", "Third line" };

// Set a variable to the Documents path.


string docPath =
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

// Write the string array to a new file named "WriteLines.txt".


using (StreamWriter outputFile = new StreamWriter(Path.Combine(docPath, "WriteLines.txt")))
{
foreach (string line in lines)
outputFile.WriteLine(line);
}
}
}
// The example creates a file named "WriteLines.txt" with the following contents:
// First line
// Second line
// Third line

Imports System.IO

Class WriteText

Public Shared Sub Main()

' Create a string array with the lines of text


Dim lines() As String = {"First line", "Second line", "Third line"}

' Set a variable to the Documents path.


Dim docPath As String = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)

' Write the string array to a new file named "WriteLines.txt".


Using outputFile As New StreamWriter(Path.Combine(docPath, Convert.ToString("WriteLines.txt")))
For Each line As String In lines
outputFile.WriteLine(line)
Next
End Using

End Sub

End Class

' The example creates a file named "WriteLines.txt" with the following contents:
' First line
' Second line
' Third line

Si vous souhaitez voir les commentaires de code traduits dans des langues autres que l’anglais, faites-le nous
savoir dans ce problème de discussion GitHub.

Exemple : ajouter de façon synchrone du texte avec StreamWriter


L’exemple suivant montre comment utiliser la classe StreamWriter pour ajouter du texte de façon synchrone au
fichier texte créé dans le premier exemple.
using System;
using System.IO;

class Program
{
static void Main(string[] args)
{

// Set a variable to the Documents path.


string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

// Append text to an existing file named "WriteLines.txt".


using (StreamWriter outputFile = new StreamWriter(Path.Combine(docPath, "WriteLines.txt"), true))
{
outputFile.WriteLine("Fourth Line");
}
}
}
// The example adds the following line to the contents of "WriteLines.txt":
// Fourth Line

Imports System.IO

Class AppendText

Public Shared Sub Main()

' Set a variable to the Documents path.


Dim docPath As String =
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)

' Append text to an existing file named "WriteLines.txt".


Using outputFile As New StreamWriter(Path.Combine(docPath, Convert.ToString("WriteLines.txt")), True)
outputFile.WriteLine("Fourth Line")
End Using

End Sub

End Class

' The example adds the following line to the contents of "WriteLines.txt":
' Fourth Line

Exemple : écrire du texte de façon asynchrone avec StreamWriter


L’exemple suivant montre comment écrire du texte de façon asynchrone dans un nouveau fichier à l’aide de la
classe StreamWriter . Pour appeler la méthode WriteAsync, l’appel de méthode doit se trouver dans une méthode
async . L’exemple C# nécessite C# 7.1 ou version ultérieure, qui ajoute la prise en charge du modificateur async
sur le point d’entrée du programme.
using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
// Set a variable to the Documents path.
string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

// Write the specified text asynchronously to a new file named "WriteTextAsync.txt".


using (StreamWriter outputFile = new StreamWriter(Path.Combine(docPath, "WriteTextAsync.txt")))
{
await outputFile.WriteAsync("This is a sentence.");
}
}
}
// The example creates a file named "WriteTextAsync.txt" with the following contents:
// This is a sentence.

Imports System.IO

Public Module Example


Public Sub Main()
WriteTextAsync()
End Sub

Async Sub WriteTextAsync()


' Set a variable to the Documents path.
Dim docPath As String = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)

' Write the text asynchronously to a new file named "WriteTextAsync.txt".


Using outputFile As New StreamWriter(Path.Combine(docPath, Convert.ToString("WriteTextAsync.txt")))
Await outputFile.WriteAsync("This is a sentence.")
End Using
End Sub
End Module

' The example creates a file named "WriteTextAsync.txt" with the following contents:
' This is a sentence.

Exemple : écrire et ajouter du texte avec la classe file


L’exemple suivant montre comment écrire du texte dans un nouveau fichier et ajouter de nouvelles lignes de texte
à ce même fichier à l’aide de la classe File . Les méthodes WriteAllText et AppendAllLines ouvrent et ferment
automatiquement le fichier. Si le chemin que vous fournissez à la méthode WriteAllText existe déjà, le fichier est
remplacé.
using System;
using System.IO;

class Program
{
static void Main(string[] args)
{
// Create a string with a line of text.
string text = "First line" + Environment.NewLine;

// Set a variable to the Documents path.


string docPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

// Write the text to a new file named "WriteFile.txt".


File.WriteAllText(Path.Combine(docPath, "WriteFile.txt"), text);

// Create a string array with the additional lines of text


string[] lines = { "New line 1", "New line 2" };

// Append new lines of text to the file


File.AppendAllLines(Path.Combine(docPath, "WriteFile.txt"), lines);
}
}
// The example creates a file named "WriteFile.txt" with the contents:
// First line
// And then appends the following contents:
// New line 1
// New line 2

Imports System.IO

Class WriteFile

Public Shared Sub Main()

' Create a string array with the lines of text


Dim text As String = "First line" & Environment.NewLine

' Set a variable to the Documents path.


Dim docPath As String = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)

' Write the text to a new file named "WriteFile.txt".


File.WriteAllText(Path.Combine(docPath, Convert.ToString("WriteFile.txt")), text)

' Create a string array with the additional lines of text


Dim lines() As String = {"New line 1", "New line 2"}

' Append new lines of text to the file


File.AppendAllLines(Path.Combine(docPath, Convert.ToString("WriteFile.txt")), lines)

End Sub

End Class

' The example creates a file named "WriteFile.txt" with the following contents:
' First line
' And then appends the following contents:
' New line 1
' New line 2

Voir aussi
StreamWriter
Path
File.CreateText
Comment : énumérer des répertoires et des fichiers
Comment : lire et écrire dans un fichier de données nouvellement créé
Procédure : ouvrir un fichier journal et y ajouter des éléments
Comment : lire du texte à partir d’un fichier
E/S de fichier et de flux
Comment : lire du texte à partir d’un fichier
18/07/2020 • 4 minutes to read • Edit Online

Les exemples suivants montrent comment lire le texte de façon synchrone et asynchrone à partir d'un fichier
texte à l'aide du .NET pour les applications de bureau. Dans les deux exemples, lorsque vous créez l’instance de la
classe StreamReader, vous fournissez le chemin d’accès relatif ou absolu au fichier.

NOTE
Ces exemples de code ne s’appliquent pas au développement d’applications UWP, car le Windows Runtime fournit des types
de flux différents pour la lecture et l’écriture des fichiers. Pour obtenir un exemple qui montre comment lire le texte d’un
fichier dans une application UWP, consultez démarrage rapide : lecture et écriture de fichiers. Pour obtenir des exemples qui
montrent comment effectuer une conversion entre des flux de .NET Framework et des flux de Windows Runtime, consultez
Comment : effectuer une conversion entre des flux de .NET Framework et desflux de Windows Runtime.

Exemple : lecture synchrone dans une application console


L’exemple suivant montre une opération de lecture synchrone dans une application console. Cet exemple ouvre le
fichier texte à l’aide d’un lecteur de flux, copie le contenu dans une chaîne, puis retourne une chaîne dans la
console.

IMPORTANT
L’exemple suppose qu’un fichier nommé TestFile.txt existe déjà dans le dossier où se trouve l’application.

using System;
using System.IO;

class Test
{
public static void Main()
{
try
{ // Open the text file using a stream reader.
using (StreamReader sr = new StreamReader("TestFile.txt"))
{
// Read the stream to a string, and write the string to the console.
String line = sr.ReadToEnd();
Console.WriteLine(line);
}
}
catch (IOException e)
{
Console.WriteLine("The file could not be read:");
Console.WriteLine(e.Message);
}
}
}
Imports System.IO

Class Test
Public Shared Sub Main()
Try
' Open the file using a stream reader.
Using sr As New StreamReader("TestFile.txt")
' Read the stream to a string and write the string to the console.
Dim line = sr.ReadToEnd()
Console.WriteLine(line)
End Using
Catch e As IOException
Console.WriteLine("The file could not be read:")
Console.WriteLine(e.Message)
End Try
End Sub
End Class

Exemple : lecture asynchrone dans une application WPF


L’exemple suivant montre une opération de lecture asynchrone dans une application WPF (Windows Presentation
Foundation).

IMPORTANT
L’exemple suppose qu’un fichier nommé TestFile.txt existe déjà dans le dossier où se trouve l’application.

using System;
using System.IO;
using System.Windows;

namespace TextFiles
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private async void MainWindow_Loaded(object sender, RoutedEventArgs e)


{
try
{
using (StreamReader sr = new StreamReader("TestFile.txt"))
{
string line = await sr.ReadToEndAsync();
ResultBlock.Text = line;
}
}
catch (FileNotFoundException ex)
{
ResultBlock.Text = ex.Message;
}
}
}
}
Imports System.IO
Imports System.Windows

''' <summary>
''' Interaction logic for MainWindow.xaml
''' </summary>

Partial Public Class MainWindow


Inherits Window
Public Sub New()
InitializeComponent()
End Sub

Private Async Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs)


Try
Using sr As StreamReader = New StreamReader("TestFile.txt")
Dim line = Await sr.ReadToEndAsync()
ResultBlock.Text = line
End Using
Catch ex As FileNotFoundException
ResultBlock.Text = ex.Message
End Try
End Sub
End Class

Voir aussi
StreamReader
File.OpenText
StreamReader.ReadLine
E/S sur fichier asynchrones
Procédure : créer une liste de répertoires
Démarrage rapide : lire et écrire dans des fichiers
Comment : effectuer une conversion entre des flux de .NET Framework et des flux de Windows Runtime
Comment : lire et écrire dans un fichier de données nouvellement créé
Procédure : ouvrir un fichier journal et y ajouter des éléments
Comment : écrire du texte dans un fichier
Comment : lire les caractères d’une chaîne
Comment : écrire des caractères dans une chaîne
E/S de fichier et de flux
Comment : lire les caractères d’une chaîne
18/07/2020 • 3 minutes to read • Edit Online

Les exemples de code suivants montrent comment lire des caractères de façon synchrone et asynchrone à partir
d’une chaîne.

Exemple : lire des caractères de façon synchrone


Cet exemple lit 13 caractères de façon synchrone à partir d’une chaîne, les stocke dans un tableau, puis les affiche.
Ensuite, l’exemple lit les caractères restants de la chaîne, les stocke dans le tableau à partir du sixième élément,
puis affiche le contenu du tableau.

using System;
using System.IO;

public class CharsFromStr


{
public static void Main()
{
string str = "Some number of characters";
char[] b = new char[str.Length];

using (StringReader sr = new StringReader(str))


{
// Read 13 characters from the string into the array.
sr.Read(b, 0, 13);
Console.WriteLine(b);

// Read the rest of the string starting at the current string position.
// Put in the array starting at the 6th array member.
sr.Read(b, 5, str.Length - 13);
Console.WriteLine(b);
}
}
}
// The example has the following output:
//
// Some number o
// Some f characters
Imports System.IO

Public Class CharsFromStr


Public Shared Sub Main()
Dim str As String = "Some number of characters"
Dim b(str.Length - 1) As Char

Using sr As StringReader = New StringReader(str)


' Read 13 characters from the string into the array.
sr.Read(b, 0, 13)
Console.WriteLine(b)

' Read the rest of the string starting at the current string position.
' Put in the array starting at the 6th array member.
sr.Read(b, 5, str.Length - 13)
Console.WriteLine(b)
End Using
End Sub
End Class
' The example has the following output:
'
' Some number o
' Some f characters

Exemple : lecture de caractères de façon asynchrone


L’exemple suivant montre le code-behind d’une application WPF. Au chargement de la fenêtre, l’exemple lit tous
les caractères de façon asynchrone à partir d’un contrôle TextBox et les stocke dans un tableau. Ensuite, il écrit de
façon asynchrone chaque lettre ou espace blanc sur une ligne distincte d’un contrôle TextBlock.
using System;
using System.Text;
using System.Windows;
using System.IO;

namespace StringReaderWriter
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private async void Window_Loaded(object sender, RoutedEventArgs e)


{
char[] charsRead = new char[UserInput.Text.Length];
using (StringReader reader = new StringReader(UserInput.Text))
{
await reader.ReadAsync(charsRead, 0, UserInput.Text.Length);
}

StringBuilder reformattedText = new StringBuilder();


using (StringWriter writer = new StringWriter(reformattedText))
{
foreach (char c in charsRead)
{
if (char.IsLetter(c) || char.IsWhiteSpace(c))
{
await writer.WriteLineAsync(char.ToLower(c));
}
}
}
Result.Text = reformattedText.ToString();
}
}
}
Imports System.IO
Imports System.Text

''' <summary>
''' Interaction logic for MainWindow.xaml
''' </summary>

Partial Public Class MainWindow


Inherits Window
Public Sub New()
InitializeComponent()
End Sub
Private Async Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
Dim charsRead As Char() = New Char(UserInput.Text.Length) {}
Using reader As StringReader = New StringReader(UserInput.Text)
Await reader.ReadAsync(charsRead, 0, UserInput.Text.Length)
End Using

Dim reformattedText As StringBuilder = New StringBuilder()


Using writer As StringWriter = New StringWriter(reformattedText)
For Each c As Char In charsRead
If Char.IsLetter(c) Or Char.IsWhiteSpace(c) Then
Await writer.WriteLineAsync(Char.ToLower(c))
End If
Next
End Using
Result.Text = reformattedText.ToString()
End Sub
End Class

Voir aussi
StringReader
StringReader.Read
E/S sur fichier asynchrones
Procédure : créer une liste de répertoires
Comment : lire et écrire dans un fichier de données nouvellement créé
Procédure : ouvrir un fichier journal et y ajouter des éléments
Comment : lire du texte à partir d’un fichier
Comment : écrire du texte dans un fichier
Comment : écrire des caractères dans une chaîne
E/S de fichier et de flux
Comment : écrire des caractères dans une chaîne
18/07/2020 • 3 minutes to read • Edit Online

Les exemples de code suivants écrivent les caractères d’un tableau dans une chaîne de façon synchrone et
asynchrone.

Exemple : écrire des caractères de façon synchrone dans une


application console
L’exemple suivant utilise un StringWriter pour écrire cinq caractères dans un objet StringBuilder de façon
synchrone.

using System;
using System.IO;
using System.Text;

public class CharsToStr


{
public static void Main()
{
StringBuilder sb = new StringBuilder("Start with a string and add from ");
char[] b = { 'c', 'h', 'a', 'r', '.', ' ', 'B', 'u', 't', ' ', 'n', 'o', 't', ' ', 'a', 'l', 'l' };

using (StringWriter sw = new StringWriter(sb))


{
// Write five characters from the array into the StringBuilder.
sw.Write(b, 0, 5);
Console.WriteLine(sb);
}
}
}
// The example has the following output:
//
// Start with a string and add from char.

Imports System.IO
Imports System.Text

Public Class CharsToStr


Public Shared Sub Main()
Dim sb As New StringBuilder("Start with a string and add from ")
Dim b() As Char = {"c", "h", "a", "r", ".", " ", "B", "u", "t", " ", "n", "o", "t", " ", "a", "l",
"l"}

Using sw As StringWriter = New StringWriter(sb)


' Write five characters from the array into the StringBuilder.
sw.Write(b, 0, 5)
Console.WriteLine(sb)
End Using
End Sub
End Class
' The example has the following output:
'
' Start with a string and add from char.
Exemple : écrire des caractères de façon asynchrone dans une
application WPF
L’exemple suivant montre le code-behind d’une application WPF. Au chargement de la fenêtre, l’exemple lit tous
les caractères de façon asynchrone à partir d’un contrôle TextBox et les stocke dans un tableau. Ensuite, il écrit de
façon asynchrone chaque lettre ou espace blanc sur une ligne distincte d’un contrôle TextBlock.

using System;
using System.Text;
using System.Windows;
using System.IO;

namespace StringReaderWriter
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private async void Window_Loaded(object sender, RoutedEventArgs e)


{
char[] charsRead = new char[UserInput.Text.Length];
using (StringReader reader = new StringReader(UserInput.Text))
{
await reader.ReadAsync(charsRead, 0, UserInput.Text.Length);
}

StringBuilder reformattedText = new StringBuilder();


using (StringWriter writer = new StringWriter(reformattedText))
{
foreach (char c in charsRead)
{
if (char.IsLetter(c) || char.IsWhiteSpace(c))
{
await writer.WriteLineAsync(char.ToLower(c));
}
}
}
Result.Text = reformattedText.ToString();
}
}
}
Imports System.IO
Imports System.Text

''' <summary>
''' Interaction logic for MainWindow.xaml
''' </summary>

Partial Public Class MainWindow


Inherits Window
Public Sub New()
InitializeComponent()
End Sub
Private Async Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
Dim charsRead As Char() = New Char(UserInput.Text.Length) {}
Using reader As StringReader = New StringReader(UserInput.Text)
Await reader.ReadAsync(charsRead, 0, UserInput.Text.Length)
End Using

Dim reformattedText As StringBuilder = New StringBuilder()


Using writer As StringWriter = New StringWriter(reformattedText)
For Each c As Char In charsRead
If Char.IsLetter(c) Or Char.IsWhiteSpace(c) Then
Await writer.WriteLineAsync(Char.ToLower(c))
End If
Next
End Using
Result.Text = reformattedText.ToString()
End Sub
End Class

Voir aussi
StringWriter
StringWriter.Write
StringBuilder
E/S de fichier et de flux
E/S sur fichier asynchrones
Comment : énumérer des répertoires et des fichiers
Comment : lire et écrire dans un fichier de données nouvellement créé
Procédure : ouvrir un fichier journal et y ajouter des éléments
Comment : lire du texte à partir d’un fichier
Comment : écrire du texte dans un fichier
Comment : lire les caractères d’une chaîne
Comment : Ajouter ou supprimer les entrées de la
liste de contrôle d’accès (.NET Framework
uniquement)
18/03/2020 • 3 minutes to read • Edit Online

Pour ajouter ou supprimer des entrées de la liste de contrôle d’accès (ACL) dans un fichier ou un répertoire,
récupérez l’objet FileSecurity ou DirectorySecurity dans le fichier ou le répertoire. Modifiez l’objet, puis appliquez-le
de nouveau au fichier ou au répertoire.

Ajouter ou supprimer une entrée ACL dans un fichier


1. Appelez la méthode File.GetAccessControl pour obtenir un objet FileSecurity contenant les entrées ACL
actuelles d'un fichier.
2. Ajoutez ou supprimez des entrées ACL à partir de l'objet FileSecurity retourné à l'étape 1.
3. Pour appliquer les modifications, passez l’objet FileSecurity à la méthode File.SetAccessControl.

Ajouter ou supprimer une entrée ACL dans un répertoire


1. Appelez la méthode Directory.GetAccessControl pour obtenir un objet DirectorySecurity contenant les
entrées ACL actuelles d'un répertoire.
2. Ajoutez ou supprimez des entrées ACL à partir de l'objet DirectorySecurity retourné à l'étape 1.
3. Pour appliquer les modifications, passez l’objet DirectorySecurity à la méthode Directory.SetAccessControl.

Exemple
Vous devez utiliser un compte d’utilisateur ou de groupe valide pour exécuter cet exemple. Cet exemple utilise un
objet File. Utilisez la même procédure pour les classes FileInfo, Directory et DirectoryInfo.

using System;
using System.IO;
using System.Security.AccessControl;

namespace FileSystemExample
{
class FileExample
{
public static void Main()
{
try
{
string fileName = "test.xml";

Console.WriteLine("Adding access control entry for "


+ fileName);

// Add the access control entry to the file.


AddFileSecurity(fileName, @"DomainName\AccountName",
FileSystemRights.ReadData, AccessControlType.Allow);

Console.WriteLine("Removing access control entry from "


+ fileName);
+ fileName);

// Remove the access control entry from the file.


RemoveFileSecurity(fileName, @"DomainName\AccountName",
FileSystemRights.ReadData, AccessControlType.Allow);

Console.WriteLine("Done.");
}
catch (Exception e)
{
Console.WriteLine(e);
}
}

// Adds an ACL entry on the specified file for the specified account.
public static void AddFileSecurity(string fileName, string account,
FileSystemRights rights, AccessControlType controlType)
{

// Get a FileSecurity object that represents the


// current security settings.
FileSecurity fSecurity = File.GetAccessControl(fileName);

// Add the FileSystemAccessRule to the security settings.


fSecurity.AddAccessRule(new FileSystemAccessRule(account,
rights, controlType));

// Set the new access settings.


File.SetAccessControl(fileName, fSecurity);
}

// Removes an ACL entry on the specified file for the specified account.
public static void RemoveFileSecurity(string fileName, string account,
FileSystemRights rights, AccessControlType controlType)
{

// Get a FileSecurity object that represents the


// current security settings.
FileSecurity fSecurity = File.GetAccessControl(fileName);

// Remove the FileSystemAccessRule from the security settings.


fSecurity.RemoveAccessRule(new FileSystemAccessRule(account,
rights, controlType));

// Set the new access settings.


File.SetAccessControl(fileName, fSecurity);
}
}
}

Imports System.IO
Imports System.Security.AccessControl

Module FileExample

Sub Main()
Try
Dim fileName As String = "test.xml"

Console.WriteLine("Adding access control entry for " & fileName)

' Add the access control entry to the file.


AddFileSecurity(fileName, "DomainName\AccountName", _
FileSystemRights.ReadData, AccessControlType.Allow)

Console.WriteLine("Removing access control entry from " & fileName)


Console.WriteLine("Removing access control entry from " & fileName)

' Remove the access control entry from the file.


RemoveFileSecurity(fileName, "DomainName\AccountName", _
FileSystemRights.ReadData, AccessControlType.Allow)

Console.WriteLine("Done.")
Catch e As Exception
Console.WriteLine(e)
End Try

End Sub

' Adds an ACL entry on the specified file for the specified account.
Sub AddFileSecurity(ByVal fileName As String, ByVal account As String, _
ByVal rights As FileSystemRights, ByVal controlType As AccessControlType)

' Get a FileSecurity object that represents the


' current security settings.
Dim fSecurity As FileSecurity = File.GetAccessControl(fileName)

' Add the FileSystemAccessRule to the security settings.


Dim accessRule As FileSystemAccessRule = _
New FileSystemAccessRule(account, rights, controlType)

fSecurity.AddAccessRule(accessRule)

' Set the new access settings.


File.SetAccessControl(fileName, fSecurity)

End Sub

' Removes an ACL entry on the specified file for the specified account.
Sub RemoveFileSecurity(ByVal fileName As String, ByVal account As String, _
ByVal rights As FileSystemRights, ByVal controlType As AccessControlType)

' Get a FileSecurity object that represents the


' current security settings.
Dim fSecurity As FileSecurity = File.GetAccessControl(fileName)

' Remove the FileSystemAccessRule from the security settings.


fSecurity.RemoveAccessRule(New FileSystemAccessRule(account, _
rights, controlType))

' Set the new access settings.


File.SetAccessControl(fileName, fSecurity)

End Sub
End Module
Guide pratique pour compresser et extraire des
fichiers
18/07/2020 • 7 minutes to read • Edit Online

L'espace de noms System.IO.Compression contient les types suivants pour la compression et la décompression de
fichiers et de flux. Vous pouvez également utiliser ces types pour lire et modifier le contenu d’un fichier compressé.
ZipFile
ZipArchive
ZipArchiveEntry
DeflateStream
GZipStream
Les exemples suivants présentent certaines des opérations que vous pouvez effectuer avec des fichiers
compressés. Ces exemples nécessitent l’ajout des packages NuGet suivants à votre projet :
System.IO.Compression
System.IO.Compression.ZipFile
Si vous utilisez .NET Framework, ajoutez des références à ces deux bibliothèques à votre projet :
System.IO.Compression
System.IO.Compression.FileSystem

Exemple 1 : créer et extraire un fichier. zip


L’exemple suivant montre comment créer et extraire un fichier .zip à l’aide de la classe ZipFile. Cet exemple
compresse le contenu d’un dossier dans un nouveau fichier .zip, puis extrait ce fichier zip dans un nouveau dossier.
Pour exécuter l’exemple, créez un dossier start dans le dossier de votre programme, puis placez-y les fichiers à
compresser.

using System;
using System.IO.Compression;

class Program
{
static void Main(string[] args)
{
string startPath = @".\start";
string zipPath = @".\result.zip";
string extractPath = @".\extract";

ZipFile.CreateFromDirectory(startPath, zipPath);

ZipFile.ExtractToDirectory(zipPath, extractPath);
}
}
Imports System.IO.Compression

Module Module1

Sub Main()
Dim startPath As String = ".\start"
Dim zipPath As String = ".\result.zip"
Dim extractPath As String = ".\extract"

ZipFile.CreateFromDirectory(startPath, zipPath)

ZipFile.ExtractToDirectory(zipPath, extractPath)
End Sub

End Module

Exemple 2 : extraire des extensions de fichier spécifiques


L’exemple suivant montre comment parcourir le contenu d’un fichier .zip existant et en extraire les fichiers ayant
une extension .txt. Il utilise la classe ZipArchive pour accéder au fichier zip, et la classe ZipArchiveEntry pour
inspecter chaque entrée. La méthode d’extension ExtractToFile de l’objet ZipArchiveEntry est disponible dans la
classe System.IO.Compression.ZipFileExtensions.
Pour exécuter l’exemple, placez un fichier .zip appelé result.zip dans le dossier de votre programme. Lorsque vous y
êtes invité, fournissez le nom du dossier vers lequel extraire les fichiers.

IMPORTANT
Lors de la décompression des fichiers, vous devez rechercher les chemins malveillants qui risquent d’extraire les fichiers en
dehors du répertoire. Il s’agit d’une attaque par chemin transversal. L’exemple suivant montre comment rechercher les
chemins malveillants et garantir une décompression sûre.
using System;
using System.IO;
using System.IO.Compression;

class Program
{
static void Main(string[] args)
{
string zipPath = @".\result.zip";

Console.WriteLine("Provide path where to extract the zip file:");


string extractPath = Console.ReadLine();

// Normalizes the path.


extractPath = Path.GetFullPath(extractPath);

// Ensures that the last character on the extraction path


// is the directory separator char.
// Without this, a malicious zip file could try to traverse outside of the expected
// extraction path.
if (!extractPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
extractPath += Path.DirectorySeparatorChar;

using (ZipArchive archive = ZipFile.OpenRead(zipPath))


{
foreach (ZipArchiveEntry entry in archive.Entries)
{
if (entry.FullName.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
{
// Gets the full path to ensure that relative segments are removed.
string destinationPath = Path.GetFullPath(Path.Combine(extractPath, entry.FullName));

// Ordinal match is safest, case-sensitive volumes can be mounted within volumes that
// are case-insensitive.
if (destinationPath.StartsWith(extractPath, StringComparison.Ordinal))
entry.ExtractToFile(destinationPath);
}
}
}
}
}
Imports System.IO
Imports System.IO.Compression

Module Module1

Sub Main()
Dim zipPath As String = ".\result.zip"

Console.WriteLine("Provide path where to extract the zip file:")


Dim extractPath As String = Console.ReadLine()

' Normalizes the path.


extractPath = Path.GetFullPath(extractPath)

' Ensures that the last character on the extraction path


' is the directory separator char.
' Without this, a malicious zip file could try to traverse outside of the expected
' extraction path.
If Not extractPath.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) Then
extractPath += Path.DirectorySeparatorChar
End If

Using archive As ZipArchive = ZipFile.OpenRead(zipPath)


For Each entry As ZipArchiveEntry In archive.Entries
If entry.FullName.EndsWith(".txt", StringComparison.OrdinalIgnoreCase) Then

' Gets the full path to ensure that relative segments are removed.
Dim destinationPath As String = Path.GetFullPath(Path.Combine(extractPath,
entry.FullName))

' Ordinal match is safest, case-sensitive volumes can be mounted within volumes that
' are case-insensitive.
If destinationPath.StartsWith(extractPath, StringComparison.Ordinal) Then
entry.ExtractToFile(destinationPath)
End If

End If
Next
End Using
End Sub

End Module

Exemple 3 : ajouter un fichier à un fichier zip existant


L’exemple suivant utilise la classe ZipArchive pour accéder à un fichier .zip existant, et y ajouter un fichier. Le
nouveau fichier est compressé quand vous l’ajoutez au fichier zip existant.
using System;
using System.IO;
using System.IO.Compression;

namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
using (FileStream zipToOpen = new FileStream(@"c:\users\exampleuser\release.zip", FileMode.Open))
{
using (ZipArchive archive = new ZipArchive(zipToOpen, ZipArchiveMode.Update))
{
ZipArchiveEntry readmeEntry = archive.CreateEntry("Readme.txt");
using (StreamWriter writer = new StreamWriter(readmeEntry.Open()))
{
writer.WriteLine("Information about this package.");
writer.WriteLine("========================");
}
}
}
}
}
}

Imports System.IO
Imports System.IO.Compression

Module Module1

Sub Main()
Using zipToOpen As FileStream = New FileStream("c:\users\exampleuser\release.zip", FileMode.Open)
Using archive As ZipArchive = New ZipArchive(zipToOpen, ZipArchiveMode.Update)
Dim readmeEntry As ZipArchiveEntry = archive.CreateEntry("Readme.txt")
Using writer As StreamWriter = New StreamWriter(readmeEntry.Open())
writer.WriteLine("Information about this package.")
writer.WriteLine("========================")
End Using
End Using
End Using
End Sub

End Module

Exemple 4 : compresser et décompresser les fichiers. gz


Vous pouvez également utiliser les classes GZipStream et DeflateStream pour compresser et décompresser des
données. Elles utilisent le même algorithme de compression. Vous pouvez décompresser les objets GZipStream qui
sont écrits dans un fichier .gz à l’aide de nombreux outils courants. L’exemple suivant montre comment compresser
et décompresser un répertoire de fichiers à l’aide de la classe GZipStream :
using System;
using System.IO;
using System.IO.Compression;

public class Program


{
private static string directoryPath = @".\temp";
public static void Main()
{
DirectoryInfo directorySelected = new DirectoryInfo(directoryPath);
Compress(directorySelected);

foreach (FileInfo fileToDecompress in directorySelected.GetFiles("*.gz"))


{
Decompress(fileToDecompress);
}
}

public static void Compress(DirectoryInfo directorySelected)


{
foreach (FileInfo fileToCompress in directorySelected.GetFiles())
{
using (FileStream originalFileStream = fileToCompress.OpenRead())
{
if ((File.GetAttributes(fileToCompress.FullName) &
FileAttributes.Hidden) != FileAttributes.Hidden & fileToCompress.Extension != ".gz")
{
using (FileStream compressedFileStream = File.Create(fileToCompress.FullName + ".gz"))
{
using (GZipStream compressionStream = new GZipStream(compressedFileStream,
CompressionMode.Compress))
{
originalFileStream.CopyTo(compressionStream);
}
}
FileInfo info = new FileInfo(directoryPath + Path.DirectorySeparatorChar +
fileToCompress.Name + ".gz");
Console.WriteLine($"Compressed {fileToCompress.Name} from
{fileToCompress.Length.ToString()} to {info.Length.ToString()} bytes.");
}
}
}
}

public static void Decompress(FileInfo fileToDecompress)


{
using (FileStream originalFileStream = fileToDecompress.OpenRead())
{
string currentFileName = fileToDecompress.FullName;
string newFileName = currentFileName.Remove(currentFileName.Length -
fileToDecompress.Extension.Length);

using (FileStream decompressedFileStream = File.Create(newFileName))


{
using (GZipStream decompressionStream = new GZipStream(originalFileStream,
CompressionMode.Decompress))
{
decompressionStream.CopyTo(decompressedFileStream);
Console.WriteLine($"Decompressed: {fileToDecompress.Name}");
}
}
}
}
}
Imports System.IO
Imports System.IO.Compression

Module Module1

Private directoryPath As String = ".\temp"


Public Sub Main()
Dim directorySelected As New DirectoryInfo(directoryPath)
Compress(directorySelected)

For Each fileToDecompress As FileInfo In directorySelected.GetFiles("*.gz")


Decompress(fileToDecompress)
Next
End Sub

Public Sub Compress(directorySelected As DirectoryInfo)


For Each fileToCompress As FileInfo In directorySelected.GetFiles()
Using originalFileStream As FileStream = fileToCompress.OpenRead()
If (File.GetAttributes(fileToCompress.FullName) And FileAttributes.Hidden) <>
FileAttributes.Hidden And fileToCompress.Extension <> ".gz" Then
Using compressedFileStream As FileStream = File.Create(fileToCompress.FullName & ".gz")
Using compressionStream As New GZipStream(compressedFileStream,
CompressionMode.Compress)

originalFileStream.CopyTo(compressionStream)
End Using
End Using
Dim info As New FileInfo(directoryPath & Path.DirectorySeparatorChar & fileToCompress.Name
& ".gz")
Console.WriteLine($"Compressed {fileToCompress.Name} from
{fileToCompress.Length.ToString()} to {info.Length.ToString()} bytes.")

End If
End Using
Next
End Sub

Private Sub Decompress(ByVal fileToDecompress As FileInfo)


Using originalFileStream As FileStream = fileToDecompress.OpenRead()
Dim currentFileName As String = fileToDecompress.FullName
Dim newFileName = currentFileName.Remove(currentFileName.Length -
fileToDecompress.Extension.Length)

Using decompressedFileStream As FileStream = File.Create(newFileName)


Using decompressionStream As GZipStream = New GZipStream(originalFileStream,
CompressionMode.Decompress)
decompressionStream.CopyTo(decompressedFileStream)
Console.WriteLine($"Decompressed: {fileToDecompress.Name}")
End Using
End Using
End Using
End Sub
End Module

Voir aussi
ZipArchive
ZipFile
ZipArchiveEntry
DeflateStream
GZipStream
E/S de fichier et de flux
Composer des flux
18/03/2020 • 4 minutes to read • Edit Online

Un magasin de support est un support de stockage, comme un disque ou une mémoire. Chaque magasin de
stockage implémente son propre flux en tant qu’implémentation de la classe Stream.
Chaque type de flux lit et écrit des octets depuis et vers le magasin de stockage donné. Les flux qui se connectent
aux magasins de soutien sont appelés flux de base. Les flux de base comprennent des constructeurs qui ont les
paramètres nécessaires pour connecter le flux au magasin de stockage. Par exemple, FileStream comprend des
constructeurs qui spécifient un paramètre de chemin, qui indique la façon dont le fichier est partagé par les
processus.
La conception des classes System.IO fournit une composition simplifiée des flux. Vous pouvez attacher des flux de
base à un ou plusieurs flux directs qui fournissent les fonctionnalités souhaitées. Vous pouvez attacher un lecteur
ou un enregistreur à la fin de la chaîne pour que les types préférés puissent être lus ou écrits facilement.
L’exemple de code suivant crée un FileStream autour du fichier MyFile.txt existant afin de placer MyFile.txt dans la
mémoire tampon. Notez que les FileStreams sont mis en mémoire tampon par défaut.

IMPORTANT
L’exemple suppose qu’un fichier nommé MyFile.txt existe déjà dans le dossier où se trouve l’application.

Exemple : Utilisez StreamReader


L’exemple suivant crée un StreamReader pour lire les caractères à partir du FileStream , qui est passé à
StreamReader en tant qu’argument de son constructeur. Ensuite, StreamReader.ReadLine lit jusqu’à ce que
StreamReader.Peek ne détecte plus aucun caractère.
using System;
using System.IO;

public class CompBuf


{
private const string FILE_NAME = "MyFile.txt";

public static void Main()


{
if (!File.Exists(FILE_NAME))
{
Console.WriteLine($"{FILE_NAME} does not exist!");
return;
}
FileStream fsIn = new FileStream(FILE_NAME, FileMode.Open,
FileAccess.Read, FileShare.Read);
// Create an instance of StreamReader that can read
// characters from the FileStream.
using (StreamReader sr = new StreamReader(fsIn))
{
string input;
// While not at the end of the file, read lines from the file.
while (sr.Peek() > -1)
{
input = sr.ReadLine();
Console.WriteLine(input);
}
}
}
}

Imports System.IO

Public Class CompBuf


Private Const FILE_NAME As String = "MyFile.txt"

Public Shared Sub Main()


If Not File.Exists(FILE_NAME) Then
Console.WriteLine($"{FILE_NAME} does not exist!")
Return
End If
Dim fsIn As new FileStream(FILE_NAME, FileMode.Open, _
FileAccess.Read, FileShare.Read)
' Create an instance of StreamReader that can read
' characters from the FileStream.
Using sr As New StreamReader(fsIn)
Dim input As String
' While not at the end of the file, read lines from the file.
While sr.Peek() > -1
input = sr.ReadLine()
Console.WriteLine(input)
End While
End Using
End Sub
End Class

Exemple : Utilisez BinaryReader


L’exemple suivant crée un BinaryReader pour lire les octets à partir du FileStream , qui est passé à Binar yReader
en tant qu’argument de son constructeur. Ensuite, ReadByte lit jusqu’à ce que PeekChar ne détecte plus aucun octet.
using System;
using System.IO;

public class ReadBuf


{
private const string FILE_NAME = "MyFile.txt";

public static void Main()


{
if (!File.Exists(FILE_NAME))
{
Console.WriteLine($"{FILE_NAME} does not exist.");
return;
}
FileStream f = new FileStream(FILE_NAME, FileMode.Open,
FileAccess.Read, FileShare.Read);
// Create an instance of BinaryReader that can
// read bytes from the FileStream.
using (BinaryReader br = new BinaryReader(f))
{
byte input;
// While not at the end of the file, read lines from the file.
while (br.PeekChar() > -1 )
{
input = br.ReadByte();
Console.WriteLine(input);
}
}
}
}

Imports System.IO

Public Class ReadBuf


Private Const FILE_NAME As String = "MyFile.txt"

Public Shared Sub Main()


If Not File.Exists(FILE_NAME) Then
Console.WriteLine($"{FILE_NAME} does not exist.")
Return
End If
Dim f As New FileStream(FILE_NAME, FileMode.Open, _
FileAccess.Read, FileShare.Read)
' Create an instance of BinaryReader that can
' read bytes from the FileStream.
Using br As new BinaryReader(f)
Dim input As Byte
' While not at the end of the file, read lines from the file.
While br.PeekChar() > -1
input = br.ReadByte()
Console.WriteLine(input)
End While
End Using
End Sub
End Class

Voir aussi
StreamReader
StreamReader.ReadLine
StreamReader.Peek
FileStream
BinaryReader
BinaryReader.ReadByte
BinaryReader.PeekChar
Comment: Convertir entre .NET Framework et
Windows Runtime streams (Windows uniquement)
18/03/2020 • 10 minutes to read • Edit Online

Le .NET Framework pour les applications UWP est un sous-ensemble du .NET Framework complet. En raison de la
sécurité et d’autres spécifications des applications UWP, vous ne pouvez pas utiliser l’ensemble d’API .NET
Framework pour ouvrir et lire des fichiers. Pour plus d’informations, consultez Vue d’ensemble de .NET pour les
applications UWP. Toutefois, vous pouvez utiliser des API .NET Framework pour les autres opérations de
manipulation des flux. Pour manipuler ces flux, vous pouvez convertir un flux .NET Framework, tel que
MemoryStream ou FileStream, en flux Windows Runtime tel que IInputStream, IOutputStream ou
IRandomAccessStream.
La classe System.IO.WindowsRuntimeStreamExtensions contient des méthodes qui rendent ces conversions plus
faciles. Toutefois, il existe des différences sous-jacentes entre les flux .NET Framework et Windows Runtime qui
affecteront les résultats obtenus lors de l’utilisation de ces méthodes, comme l’expliquent les sections suivantes :

Convertir un flux Windows Runtime en flux .NET Framework


Pour convertir un flux Windows Runtime en un flux .NET Framework, utilisez l’une des méthodes
System.IO.WindowsRuntimeStreamExtensions suivantes :
WindowsRuntimeStreamExtensions.AsStream convertit un flux d’accès aléatoire Windows Runtime en un
flux managé dans .NET pour les applications de plateforme Windows universelle (UWP).
WindowsRuntimeStreamExtensions.AsStreamForWrite convertit un flux de sortie dans Windows Runtime
en un flux managé dans .NET pour les applications de plateforme Windows universelle (UWP).
WindowsRuntimeStreamExtensions.AsStreamForRead convertit un flux d’entrée dans Windows Runtime en
un flux managé dans .NET pour les applications de plateforme Windows universelle (UWP).
Windows Runtime propose des types de flux qui prennent en charge la lecture seule, l’écriture seule et la lecture-
écriture. Ces fonctionnalités sont conservées lorsque vous convertissez un flux Windows Runtime en flux .NET
Framework. En outre, si vous convertissez un flux Windows Runtime en un flux .NET Framework et inversement,
vous obtenez l'instance du Windows Runtime en retour.
Il est recommandé d’utiliser la méthode de conversion qui correspond aux fonctionnalités du flux Windows
Runtime que vous souhaitez convertir. Toutefois, comme IRandomAccessStream est accessible en lecture et en
écriture (il implémente IOutputStream et IInputStream), les méthodes de conversion conservent les fonctionnalités
du flux d’origine. Par exemple, l’utilisation de WindowsRuntimeStreamExtensions.AsStreamForRead pour convertir
IRandomAccessStream n’empêche pas le flux .NET Framework converti d’être accessible en lecture. Il est
également accessible en écriture.

Exemple : Convertir Windows Runtime accès aléatoire au flux cadre


.NET
Pour convertir un flux d’accès aléatoire Windows Runtime en un flux .NET Framework, utilisez la méthode
WindowsRuntimeStreamExtensions.AsStream.
L’exemple de code suivant vous invite à sélectionner un fichier, l’ouvre à l’aide des API Windows Runtime, puis le
convertit en flux .NET Framework. Il lit le flux et l’exporte vers un bloc de texte. En général, vous devez manipuler
le flux à l’aide des API .NET Framework avant de sortir les résultats.
Pour exécuter cet exemple, créez une application XAML UWP, contenant un bloc de texte nommé TextBlock1 et un
bouton nommé Button1 . Associez l’événement de clic de bouton à la méthode button1_Click qui est illustrée
dans l’exemple.

using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Storage;
using System.Net.Http;
using Windows.Storage.Pickers;

private async void button1_Click(object sender, RoutedEventArgs e)


{
// Create a file picker.
FileOpenPicker picker = new FileOpenPicker();

// Set properties on the file picker such as start location and the type
// of files to display.
picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
picker.ViewMode = PickerViewMode.List;
picker.FileTypeFilter.Add(".txt");

// Show picker enabling user to pick one file.


StorageFile result = await picker.PickSingleFileAsync();

if (result != null)
{
try
{
// Retrieve the stream. This method returns a IRandomAccessStreamWithContentType.
var stream = await result.OpenReadAsync();

// Convert the stream to a .NET stream using AsStream, pass to a


// StreamReader and read the stream.
using (StreamReader sr = new StreamReader(stream.AsStream()))
{
TextBlock1.Text = sr.ReadToEnd();
}
}
catch (Exception ex)
{
TextBlock1.Text = "Error occurred reading the file. " + ex.Message;
}
}
else
{
TextBlock1.Text = "User did not pick a file";
}
}
Imports System.IO
Imports System.Runtime.InteropServices.WindowsRuntime
Imports Windows.UI.Xaml
Imports Windows.UI.Xaml.Controls
Imports Windows.UI.Xaml.Media.Imaging
Imports Windows.Storage
Imports System.Net.Http
Imports Windows.Storage.Pickers

Private Async Sub button1_Click(sender As Object, e As RoutedEventArgs)


' Create a file picker.
Dim picker As New FileOpenPicker()

' Set properties on the file picker such as start location and the type of files to display.
picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary
picker.ViewMode = PickerViewMode.List
picker.FileTypeFilter.Add(".txt")

' Show picker that enable user to pick one file.


Dim result As StorageFile = Await picker.PickSingleFileAsync()

If result IsNot Nothing Then


Try
' Retrieve the stream. This method returns a IRandomAccessStreamWithContentType.
Dim stream = Await result.OpenReadAsync()

' Convert the stream to a .NET stream using AsStreamForRead, pass to a


' StreamReader and read the stream.
Using sr As New StreamReader(stream.AsStream())
TextBlock1.Text = sr.ReadToEnd()
End Using
Catch ex As Exception
TextBlock1.Text = "Error occurred reading the file. " + ex.Message
End Try
Else
TextBlock1.Text = "User did not pick a file"
End If
End Sub

Convertir un flux .NET Framework en flux Windows Runtime


Pour convertir un flux .NET Framework en un flux Windows Runtime, utilisez l’une des méthodes
System.IO.WindowsRuntimeStreamExtensions suivantes :
WindowsRuntimeStreamExtensions.AsInputStream convertit un flux managé dans .NET pour les
applications de plateforme Windows universelle (UWP) en un flux d’entrée dans Windows Runtime.
WindowsRuntimeStreamExtensions.AsOutputStream convertit un flux managé dans .NET pour les
applications de plateforme Windows universelle (UWP) en un flux de sortie dans Windows Runtime.
WindowsRuntimeStreamExtensions.AsRandomAccessStream convertit un flux managé dans .NET pour les
applications UWP en un flux d’accès aléatoire que Windows Runtime peut utiliser pour la lecture ou
l’écriture.
Lorsque vous convertissez un flux .NET Framework en flux Windows Runtime, les fonctionnalités du flux converti
dépendent de celles du flux d’origine. Par exemple, si le flux d’origine prend en charge la lecture et l’écriture, et que
vous appelez WindowsRuntimeStreamExtensions.AsInputStream pour convertir le flux, le type retourné est un
IRandomAccessStream . IRandomAccessStream implémente IInputStream et IOutputStream , et prend en charge la
lecture et l’écriture.
Les flux .NET Framework ne prennent pas en charge le clonage, même après la conversion. Si vous convertissez
un flux .NET Framework en flux Windows Runtime, et appelez GetInputStreamAt ou GetOutputStreamAt, qui
appelle CloneStream, ou si vous appelez CloneStream directement, une exception est levée.

Exemple : Convertir le cadre .NET en flux d’accès aléatoire Windows


Runtime
Pour convertir un flux .NET Framework en flux d’accès aléatoire Windows Runtime, utilisez une méthode
AsRandomAccessStream, comme indiqué dans l’exemple suivant :

IMPORTANT
Vérifiez que le flux du .NET Framework que vous utilisez prend en charge la recherche, ou copiez-le dans un flux qui la prend
en charge. Vous pouvez utiliser la propriété Stream.CanSeek pour le déterminer.

Pour exécuter cet exemple, créez une application XAML UWP qui cible .NET Framework 4.5.1 et contient un bloc de
texte nommé TextBlock2 et un bouton nommé Button2 . Associez l’événement de clic de bouton à la méthode
button2_Click qui est illustrée dans l’exemple.

using System;
using System.IO;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Storage;
using System.Net.Http;
using Windows.Storage.Pickers;

private async void button2_Click(object sender, RoutedEventArgs e)


{
// Create an HttpClient and access an image as a stream.
var client = new HttpClient();
Stream stream = await client.GetStreamAsync("https://docs.microsoft.com/en-us/dotnet/images/hub/featured-
1.png");
// Create a .NET memory stream.
var memStream = new MemoryStream();
// Convert the stream to the memory stream, because a memory stream supports seeking.
await stream.CopyToAsync(memStream);
// Set the start position.
memStream.Position = 0;
// Create a new bitmap image.
var bitmap = new BitmapImage();
// Set the bitmap source to the stream, which is converted to a IRandomAccessStream.
bitmap.SetSource(memStream.AsRandomAccessStream());
// Set the image control source to the bitmap.
image1.Source = bitmap;
}
Imports System.IO
Imports System.Runtime.InteropServices.WindowsRuntime
Imports Windows.UI.Xaml
Imports Windows.UI.Xaml.Controls
Imports Windows.UI.Xaml.Media.Imaging
Imports Windows.Storage
Imports System.Net.Http
Imports Windows.Storage.Pickers

Private Async Sub button2_Click(sender As Object, e As RoutedEventArgs)

' Create an HttpClient and access an image as a stream.


Dim client = New HttpClient()
Dim stream As Stream = Await client.GetStreamAsync("https://docs.microsoft.com/en-
us/dotnet/images/hub/featured-1.png")
' Create a .NET memory stream.
Dim memStream = New MemoryStream()

' Convert the stream to the memory stream, because a memory stream supports seeking.
Await stream.CopyToAsync(memStream)

' Set the start position.


memStream.Position = 0

' Create a new bitmap image.


Dim bitmap = New BitmapImage()

' Set the bitmap source to the stream, which is converted to a IRandomAccessStream.
bitmap.SetSource(memStream.AsRandomAccessStream())

' Set the image control source to the bitmap.


image1.Source = bitmap
End Sub

Voir aussi
Quickstart: Lire et écrire un fichier (Windows)
Vue d’ensemble de .NET pour les applications du Windows Store
.NET pour les API d’applications du Windows Store
E/S sur fichier asynchrones
18/07/2020 • 6 minutes to read • Edit Online

Les opérations asynchrones vous permettent d'exécuter des opérations d'E/S consommant beaucoup de
ressources sans bloquer le thread principal. Cette considération de performance est particulièrement
importante dans une application de Windows 8. x Store ou une application de bureau où une opération de flux
de temps peut bloquer le thread d’interface utilisateur et faire apparaître votre application comme si elle ne
fonctionnait pas.
Depuis .NET Framework 4.5, les types d’E/S incluent des méthodes asynchrones pour simplifier les opérations
asynchrones. Une méthode asynchrone contient Async dans son nom, comme ReadAsync, WriteAsync,
CopyToAsync, FlushAsync, ReadLineAsync, et ReadToEndAsync. Ces méthodes asynchrones sont implémentées
sur les classes de flux, telles que Stream, FileStream, MemoryStream, et les classes qui sont utilisées pour lire ou
écrire dans des flux, comme TextReader et l' TextWriter.
Dans .NET Framework 4 et les versions antérieures, on doit utiliser des méthodes telles que BeginRead et
EndRead pour implémenter les opérations d'E/S asynchrones. Ces méthodes sont toujours disponibles dans
.NET Framework 4.5 pour prendre en charge le code hérité. Toutefois, les méthodes asynchrones vous aident à
implémenter les opérations d’E/S asynchrones plus facilement.
C# et Visual Basic ont chacun deux mots clés pour la programmation asynchrone :
Async(Visual Basic) ou async modificateur (C#), qui est utilisé pour marquer une méthode contenant
une opération asynchrone.
Await (Visual Basic) ou await opérateur (C#), qui est appliqué au résultat d'une méthode asynchrone.
Pour implémenter des opérations d'E/S asynchrones, utilisez ces mots clés conjointement aux méthodes async,
comme indiqué dans les exemples suivants. Pour plus d'informations, consultez Programmation asynchrone
avec async et await (C#) et Programmation asynchrone avec Async et Await (Visual Basic).
L'exemple suivant montre comment utiliser deux objets FileStream pour copier des fichiers de façon
asynchrone d'un répertoire à un autre. Notez que le gestionnaire d'événements Click pour le contrôle Button
est marqué avec le modificateur async car il appelle une méthode asynchrone.
using System;
using System.Threading.Tasks;
using System.Windows;
using System.IO;

namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private async void Button_Click(object sender, RoutedEventArgs e)


{
string StartDirectory = @"c:\Users\exampleuser\start";
string EndDirectory = @"c:\Users\exampleuser\end";

foreach (string filename in Directory.EnumerateFiles(StartDirectory))


{
using (FileStream SourceStream = File.Open(filename, FileMode.Open))
{
using (FileStream DestinationStream = File.Create(EndDirectory +
filename.Substring(filename.LastIndexOf('\\'))))
{
await SourceStream.CopyToAsync(DestinationStream);
}
}
}
}
}
}

Imports System.IO

Class MainWindow

Private Async Sub Button_Click(sender As Object, e As RoutedEventArgs)


Dim StartDirectory As String = "c:\Users\exampleuser\start"
Dim EndDirectory As String = "c:\Users\exampleuser\end"

For Each filename As String In Directory.EnumerateFiles(StartDirectory)


Using SourceStream As FileStream = File.Open(filename, FileMode.Open)
Using DestinationStream As FileStream = File.Create(EndDirectory +
filename.Substring(filename.LastIndexOf("\"c)))
Await SourceStream.CopyToAsync(DestinationStream)
End Using

End Using
Next
End Sub

End Class

L'exemple suivant est semblable au précédent, mais utilise des objets StreamReader et StreamWriter pour lire et
écrire le contenu d'un fichier texte de façon asynchrone.
private async void Button_Click(object sender, RoutedEventArgs e)
{
string UserDirectory = @"c:\Users\exampleuser\";

using (StreamReader SourceReader = File.OpenText(UserDirectory + "BigFile.txt"))


{
using (StreamWriter DestinationWriter = File.CreateText(UserDirectory + "CopiedFile.txt"))
{
await CopyFilesAsync(SourceReader, DestinationWriter);
}
}
}

public async Task CopyFilesAsync(StreamReader Source, StreamWriter Destination)


{
char[] buffer = new char[0x1000];
int numRead;
while ((numRead = await Source.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
await Destination.WriteAsync(buffer, 0, numRead);
}
}

Private Async Sub Button_Click(sender As Object, e As RoutedEventArgs)


Dim UserDirectory As String = "c:\Users\exampleuser\"

Using SourceReader As StreamReader = File.OpenText(UserDirectory + "BigFile.txt")


Using DestinationWriter As StreamWriter = File.CreateText(UserDirectory + "CopiedFile.txt")
Await CopyFilesAsync(SourceReader, DestinationWriter)
End Using
End Using
End Sub

Public Async Function CopyFilesAsync(Source As StreamReader, Destination As StreamWriter) As Task


Dim buffer(4095) As Char
Dim numRead As Integer

numRead = Await Source.ReadAsync(buffer, 0, buffer.Length)


Do While numRead <> 0
Await Destination.WriteAsync(buffer, 0, numRead)
numRead = Await Source.ReadAsync(buffer, 0, buffer.Length)
Loop

End Function

L’exemple suivant montre le fichier code-behind et le fichier XAML utilisés pour ouvrir un fichier en tant que
Stream dans une application du Windows 8. x Store et lire son contenu à l’aide d’une instance de la
StreamReader classe. Il utilise des méthodes asynchrones pour ouvrir le fichier en tant que flux et lire son
contenu.
using System;
using System.IO;
using System.Text;
using Windows.Storage.Pickers;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace ExampleApplication
{
public sealed partial class BlankPage : Page
{
public BlankPage()
{
this.InitializeComponent();
}

private async void Button_Click_1(object sender, RoutedEventArgs e)


{
StringBuilder contents = new StringBuilder();
string nextLine;
int lineCounter = 1;

var openPicker = new FileOpenPicker();


openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
openPicker.FileTypeFilter.Add(".txt");
StorageFile selectedFile = await openPicker.PickSingleFileAsync();

using (StreamReader reader = new StreamReader(await selectedFile.OpenStreamForReadAsync()))


{
while ((nextLine = await reader.ReadLineAsync()) != null)
{
contents.AppendFormat("{0}. ", lineCounter);
contents.Append(nextLine);
contents.AppendLine();
lineCounter++;
if (lineCounter > 3)
{
contents.AppendLine("Only first 3 lines shown.");
break;
}
}
}
DisplayContentsBlock.Text = contents.ToString();
}
}
}
Imports System.Text
Imports System.IO
Imports Windows.Storage.Pickers
Imports Windows.Storage

NotInheritable Public Class BlankPage


Inherits Page

Private Async Sub Button_Click_1(sender As Object, e As RoutedEventArgs)


Dim contents As StringBuilder = New StringBuilder()
Dim nextLine As String
Dim lineCounter As Integer = 1

Dim openPicker = New FileOpenPicker()


openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary

openPicker.FileTypeFilter.Add(".txt")
Dim selectedFile As StorageFile = Await openPicker.PickSingleFileAsync()

Using reader As StreamReader = New StreamReader(Await selectedFile.OpenStreamForReadAsync())


nextLine = Await reader.ReadLineAsync()
While (nextLine <> Nothing)
contents.AppendFormat("{0}. ", lineCounter)
contents.Append(nextLine)
contents.AppendLine()
lineCounter = lineCounter + 1
If (lineCounter > 3) Then
contents.AppendLine("Only first 3 lines shown.")
Exit While
End If
nextLine = Await reader.ReadLineAsync()
End While
End Using
DisplayContentsBlock.Text = contents.ToString()
End Sub
End Class

<Page
x:Class="ExampleApplication.BlankPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ExampleApplication"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<StackPanel Background="{StaticResource ApplicationPageBackgroundBrush}" VerticalAlignment="Center"


HorizontalAlignment="Center">
<TextBlock Text="Display lines from a file."></TextBlock>
<Button Content="Load File" Click="Button_Click_1"></Button>
<TextBlock Name="DisplayContentsBlock"></TextBlock>
</StackPanel>
</Page>

Voir aussi
Stream
E/s de fichier et de flux
Programmation asynchrone avec Async et await (C#)
Programmation asynchrone avec Async et Await (Visual Basic)
Gestion des erreurs E/S dans .NET
18/07/2020 • 9 minutes to read • Edit Online

Outre les exceptions qui peuvent être levées dans n’importe quel appel de méthode (comme
OutOfMemoryException lorsqu’un système est sous contrainte ou NullReferenceException en raison d’erreurs de
programmation), les méthodes du système de fichiers .NET peuvent lever les exceptions suivantes :
System.IO.IOException, la classe de base de tous les types d’exceptions System.IO. Elle est levée pour les erreurs
dont les codes d’erreur à partir du système d’exploitation ne sont pas directement mappés vers un autre type
d’exception.
System.IO.FileNotFoundException.
System.IO.DirectoryNotFoundException.
DriveNotFoundException.
System.IO.PathTooLongException.
System.OperationCanceledException.
System.UnauthorizedAccessException.
System.ArgumentException, qui est levée pour les caractères de chemin non valides sur le .NET Framework et
.NET Core 2.0 et les versions précédentes.
System.NotSupportedException, qui est levée pour les deux-points non valides dans le .NET Framework.
System.Security.SecurityException, qui est levée pour les applications exécutées en mode de confiance limitée et
qui ne disposent pas des autorisations nécessaires sur le .NET Framework uniquement. (La confiance totale est
la valeur par défaut sur le .NET Framework.)

Mappage des codes d'erreur aux exceptions


Étant donné que le système de fichiers est une ressource de système d’exploitation, les méthodes E/S dans .NET
Core et .NET Framework encapsulent les appels avec le système d’exploitation sous-jacent. Lorsqu’une erreur E/S
se produit dans le code exécuté par le système d’exploitation, celui-ci retourne des informations sur l’erreur à la
méthode E/S .NET. La méthode convertit ensuite les informations sur l’erreur, généralement sous la forme d’un
code d’erreur, dans un type d’exception .NET. Dans la plupart des cas, cela signifie traduire directement le code
d’erreur dans son type d’exception correspondant. La méthode n’effectue pas un mappage spécial de l’erreur en
fonction du contexte de l’appel de la méthode.
Par exemple, sur le système d’exploitation Windows, un appel de méthode qui retourne un code d’erreur
ERROR_FILE_NOT_FOUND (ou 0x02) est mappé à FileNotFoundException et le code d’erreur ERROR_PATH_NOT_FOUND (ou
0x03) correspond à DirectoryNotFoundException.
Toutefois, les conditions précises sous lesquelles le système d’exploitation retourne des codes d’erreur particuliers
sont souvent non documentées ou mal documentées. Par conséquent, des exceptions inattendues peuvent se
produire. Par exemple, étant donné que vous travaillez avec un répertoire au lieu d’un fichier, vous pourriez penser
que le fait de fournir un chemin d’accès de répertoire non valide au constructeur DirectoryInfo lèverait une
exception DirectoryNotFoundException. Toutefois, une exception FileNotFoundException peut également être
levée.

Gestion des exceptions dans les opérations E/S


En raison de cette dépendance envers le système d’exploitation, des conditions d’exception identiques (par
exemple, l’erreur répertoire introuvable dans ce cas) peuvent faire qu’une méthode E/S lève l’une des exceptions
de la classe entière d’exceptions E/S. Cela signifie que, lors de l’appel d’API E/S, votre code doit être prêt à gérer la
plupart ou toutes ces exceptions, comme indiqué dans le tableau suivant :

T Y P E D'EXC EP T IO N . N ET C O RE . N ET F RA M EW O RK

IOException Oui Oui

FileNotFoundException Oui Oui

DirectoryNotFoundException Oui Oui

DriveNotFoundException Oui Oui

PathTooLongException Oui Oui

OperationCanceledException Oui Oui

UnauthorizedAccessException Oui Oui

ArgumentException .NET Core 2.0 et versions antérieures Oui

NotSupportedException Non Oui

SecurityException Non Confiance limitée uniquement

Gestion d’IOException
Comme la classe de base pour les exceptions dans l’espace de noms System.IO, IOException est également levée
pour n’importe quel code d’erreur qui n’est pas mappé à un type d’exception prédéfini. Cela signifie qu’elle peut
être levée par n’importe quelle opération E/S.

IMPORTANT
Étant donné que IOException est la classe de base des autres types d’exception dans l’espace de noms System.IO, vous
devez la gérer dans un bloc catch une fois que vous avez géré les autres exceptions E/S associées.

En outre, à compter de .NET Core 2.1, les contrôles de validation de l’exactitude du chemin d’accès (par exemple,
pour s’assurer que des caractères non valides ne sont pas présents dans un chemin d’accès) ont été supprimés, et
le runtime lève une exception mappée à partir d’un code d’erreur de système d’exploitation plutôt qu’à partir de
son propre code de validation. L’exception la plus susceptible d’être levée dans ce cas est IOException, bien que
n’importe quel autre type d’exception puisse également être levé.
Notez que, dans votre code gestion des exceptions, vous devez toujours gérer IOException en dernier. Sinon, étant
donné qu’il s’agit de la classe de base de toutes les autres exceptions d’E/S, les blocs catch des classes dérivées ne
seront pas évalués.
Dans le cas de IOException, vous pouvez obtenir des informations supplémentaires sur l’erreur à partir de la
propriété IOException.HResult. Pour convertir la valeur HResult en un code d’erreur Win32, éliminez les 16 bits
supérieurs de la valeur de 32 bits. Le tableau suivant répertorie les codes d’erreur qui peuvent être encapsulés
dans IOException.

H RESULT C O N STA N T DESC RIP T IO N


H RESULT C O N STA N T DESC RIP T IO N

ERROR_SHARING_VIOLATION 32 Le nom de fichier est manquant ou le


fichier ou le répertoire est en cours
d’utilisation.

ERROR_FILE_EXISTS 80 Le fichier existe déjà.

ERROR_INVALID_PARAMETER 87 Un argument fourni à la méthode n’est


pas valide.

ERROR_ALREADY_EXISTS 183 Le fichier ou le répertoire existe déjà.

Vous pouvez les gérer à l’aide d’une clause When dans une instruction catch, comme le montre l’exemple suivant.
using System;
using System.IO;
using System.Text;

class Program
{
static void Main()
{
var sw = OpenStream(@".\textfile.txt");
if (sw is null)
return;
sw.WriteLine("This is the first line.");
sw.WriteLine("This is the second line.");
sw.Close();
}

static StreamWriter OpenStream(string path)


{
if (path is null) {
Console.WriteLine("You did not supply a file path.");
return null;
}

try {
var fs = new FileStream(path, FileMode.CreateNew);
return new StreamWriter(fs);
}
catch (FileNotFoundException) {
Console.WriteLine("The file or directory cannot be found.");
}
catch (DirectoryNotFoundException) {
Console.WriteLine("The file or directory cannot be found.");
}
catch (DriveNotFoundException) {
Console.WriteLine("The drive specified in 'path' is invalid.");
}
catch (PathTooLongException) {
Console.WriteLine("'path' exceeds the maxium supported path length.");
}
catch (UnauthorizedAccessException) {
Console.WriteLine("You do not have permission to create this file.");
}
catch (IOException e) when ((e.HResult & 0x0000FFFF) == 32 ) {
Console.WriteLine("There is a sharing violation.");
}
catch (IOException e) when ((e.HResult & 0x0000FFFF) == 80) {
Console.WriteLine("The file already exists.");
}
catch (IOException e) {
Console.WriteLine($"An exception occurred:\nError code: " +
$"{e.HResult & 0x0000FFFF}\nMessage: {e.Message}");
}
return null;
}
}
Imports System.IO

Module Program
Sub Main(args As String())
Dim sw = OpenStream(".\textfile.txt")
If sw Is Nothing Then Return

sw.WriteLine("This is the first line.")


sw.WriteLine("This is the second line.")
sw.Close()
End Sub

Function OpenStream(path As String) As StreamWriter


If path Is Nothing Then
Console.WriteLine("You did not supply a file path.")
Return Nothing
End If

Try
Dim fs As New FileStream(path, FileMode.CreateNew)
Return New StreamWriter(fs)
Catch e As FileNotFoundException
Console.WriteLine("The file or directory cannot be found.")
Catch e As DirectoryNotFoundException
Console.WriteLine("The file or directory cannot be found.")
Catch e As DriveNotFoundException
Console.WriteLine("The drive specified in 'path' is invalid.")
Catch e As PathTooLongException
Console.WriteLine("'path' exceeds the maxium supported path length.")
Catch e As UnauthorizedAccessException
Console.WriteLine("You do not have permission to create this file.")
Catch e As IOException When (e.HResult And &h0000FFFF) = 32
Console.WriteLine("There is a sharing violation.")
Catch e As IOException When (e.HResult And &h0000FFFF) = 80
Console.WriteLine("The file already exists.")
Catch e As IOException
Console.WriteLine($"An exception occurred:{vbCrLf}Error code: " +
$"{e.HResult And &h0000FFFF}{vbCrLf}Message: {e.Message}")
End Try
Return Nothing
End Function
End Module

Voir aussi
Gestion et levée d’exceptions dans .NET
Gestion des exceptions (bibliothèque parallèle de tâches)
Bonnes pratiques pour les exceptions
Utiliser des exceptions spécifiques dans un bloc Catch
Stockage isolé
18/07/2020 • 37 minutes to read • Edit Online

Pour les applications de bureau, le stockage isolé est un mécanisme de stockage de données qui offre une
isolation et une sécurité en définissant des méthodes standardisées pour associer du code à des données
enregistrées. La standardisation offre également d'autres avantages. Les administrateurs peuvent utiliser des
outils conçus pour manipuler un stockage isolé afin de configurer l'espace de stockage du fichier, de définir des
stratégies de sécurité et de supprimer des données inutilisées. Grâce au stockage isolé, votre code ne nécessite
plus de chemins d'accès uniques pour spécifier des emplacements sécurisés dans le système de fichiers. En
outre, les données sont protégées des autres applications qui possèdent uniquement un accès au stockage isolé.
Les informations codées en dur concernant l'emplacement de la zone de stockage d'une application ne sont pas
nécessaires.

IMPORTANT
Le stockage isolé n’est pas disponible pour les applications du Windows 8. x Store. À la place, utilisez les classes de données
d’application des espaces de noms Windows.Storage inclus dans l’API Windows Runtime pour stocker des données
locales et des fichiers. Pour plus d’informations, consultez Données d’applications dans le Centre de développement
Windows.

Magasins et compartiments de données


Lorsqu'une application stocke des données dans un fichier, le nom de fichier et l'emplacement de stockage
doivent être choisis avec soin afin de réduire la possibilité que l'emplacement de stockage soit connu d'une autre
application et, par conséquent, vulnérable en termes d'endommagement. Sans un système standard pour gérer
ces problèmes, le développement de techniques adéquates qui minimisent les conflits de stockage peut être
complexe et les résultats peuvent être incertains.
Grâce au stockage isolé, les données sont toujours isolées par utilisateur et par assembly. Les informations
d'identification telles que l'origine ou le nom fort de l'assembly déterminent l'identité de l'assembly. Les
données peuvent également être isolées par domaine d'application, en utilisant des informations d'identification
similaires.
Lors de l'utilisation du stockage isolé, votre application enregistre des données dans un seul compartiment de
données associé à un aspect de l'identité du code, tel que son éditeur ou sa signature. Le compartiment de
données est une abstraction et non un emplacement de stockage spécifique ; il est composé d'au moins un
fichier de stockage isolé, appelé un magasin, contenant des emplacements de répertoire réels où sont stockées
les données. Par exemple, une application peut comprendre un compartiment de données qui lui est associé, et
un répertoire du système de fichiers implémente le magasin qui préserve réellement les données de cette
application. Les données enregistrées dans le magasin peuvent être de tout type, à partir des informations sur
les préférences de l'utilisateur à des informations sur l'état de l'application. Pour le développeur, l'emplacement
du compartiment de données est transparent. Les magasins résident généralement sur le client, mais une
application serveur peut utiliser des magasins isolés pour stocker des informations en empruntant l'identité de
l'utilisateur légitime. Le stockage isolé peut également stocker des informations sur un serveur avec le profil
itinérant d'un utilisateur, de sorte que les informations se déplacent avec l'utilisateur itinérant.

Quotas pour le stockage isolé


Un quota représente une limite de la quantité de stockage isolé pouvant être utilisée. Le quota inclut des octets
d'espace de fichier ainsi que la surcharge associée au répertoire et d'autres informations du magasin. Le
stockage isolé utilise des quotas d'autorisation qui représentent des limites de stockage définies via des objets
IsolatedStoragePermission . Lors d'une tentative d'écriture de données qui dépassent le quota, l'exception
IsolatedStorageException est levée. La stratégie de sécurité, modifiable à l'aide de l'outil .NET Framework
Configuration Tool (Mscorcfg.msc) détermine les autorisations accordées au code. Le code possédant une
autorisation IsolatedStoragePermission ne peut pas utiliser davantage de stockage que celui autorisé par la
propriété UserQuota . Toutefois, dans la mesure où le code peut ignorer des quotas d'autorisation en présentant
diverses identités d'utilisateur, les quotas d'autorisation servent d'indications sur la façon dont le code doit se
comporter et ne représentent pas une limite définitive du comportement du code.
Les quotas ne sont pas appliqués sur les magasins itinérants. C'est pourquoi, pour que le code les utilise, le
niveau d'autorisation requis est un peu plus haut. Les valeurs d'énumération AssemblyIsolationByRoamingUser
et DomainIsolationByRoamingUser spécifient une autorisation appropriée pour utiliser le stockage isolé lorsqu'il
s'agit d'un utilisateur itinérant.

Accès sécurisé
Le stockage isolé permet aux applications qui ne disposent pas d'un niveau de confiance suffisant de stocker des
données d'une façon contrôlée par la stratégie de sécurité de l'ordinateur. Ceci est particulièrement utile pour les
composants téléchargés qu'un utilisateur doit exécuter avec précaution. La stratégie de sécurité accorde
rarement ce genre d'autorisation de code lorsque vous accédez au système de fichiers en utilisant des
mécanismes d'E/S standard. Toutefois, l'autorisation d'utiliser le stockage isolé est accordée par défaut au code
s'exécutant à partir de l'ordinateur local, d'un réseau local ou d'Internet.
Les administrateurs peuvent limiter la quantité de stockage isolé disponible pour un utilisateur ou une
application, en fonction d'un niveau de confiance approprié. En outre, les administrateurs peuvent supprimer
toutes les données rendues persistantes d'un utilisateur. Pour créer le stockage isolé ou y accéder, l'autorisation
IsolatedStorageFilePermission appropriée doit être accordée au code.
Pour accéder au stockage isolé, le code doit posséder tous les droits de système d'exploitation de la plateforme
native. Les listes de contrôle d'accès (ACL) qui contrôlent les utilisateurs autorisés à utiliser le système de fichiers
doivent être respectées. Les applications .NET Framework possèdent déjà des droits de système d'exploitation
pour accéder au stockage isolé, sauf si elles effectuent un emprunt d'identité (spécifique à la plateforme). Dans
ce cas, l'application doit garantir que l'identité de l'utilisateur empruntée possède les droits de système
d'exploitation appropriés pour accéder au stockage isolé. Cet accès permet au code exécuté ou téléchargé à
partir du Web de lire et d'écrire facilement dans une zone de stockage liée à un utilisateur particulier.
Pour contrôler l'accès au stockage isolé, le Common Language Runtime utilise des objets
IsolatedStorageFilePermission . Chaque objet possède des propriétés qui spécifient les valeurs suivantes :
Utilisation autorisée, qui indique le type d'accès autorisé. Les valeurs sont des membres de l'énumération
IsolatedStorageContainment . Pour plus d'informations sur ces valeurs, consultez le tableau de la section
suivante.
Quota de stockage, comme décrit dans la section précédente.
Le runtime exige l'autorisation IsolatedStorageFilePermission lors de la première tentative d'ouverture d'un
magasin par le code. Il détermine s’il doit ou non accorder cette autorisation, en fonction du niveau de fiabilité
du code. Si l'autorisation est accordée, l'utilisation autorisée et les valeurs de quota de stockage sont
déterminées par la stratégie de sécurité et par la demande du code de l'autorisation
IsolatedStorageFilePermission. La configuration de la stratégie de sécurité est définie à l'aide de l'outil .NET
Framework Configuration Tool (Mscorcfg.msc). Tous les appelants de la pile des appels sont vérifiés pour
garantir que chaque appelant possède au moins l'utilisation autorisée appropriée. Le runtime vérifie également
le quota imposé au code qui a ouvert ou créé le magasin dans lequel le fichier doit être enregistré. Si ces
conditions sont remplies, l'autorisation est accordée. Le quota est revérifié à chaque fois qu'un fichier est écrit
dans le magasin.
Le code d'application n'est pas nécessaire pour demander l'autorisation, car le Common Language Runtime
accorde l'autorisation IsolatedStorageFilePermission appropriée selon la stratégie de sécurité. Toutefois, il existe
de bonnes raisons de demander des autorisations spécifiques nécessaires à votre application, notamment
l'autorisation IsolatedStorageFilePermission.

Utilisation autorisée et risques pour la sécurité


L'utilisation autorisée spécifiée par l'autorisation IsolatedStorageFilePermission détermine le degré
d'autorisation du code pour créer et utiliser le stockage isolé. Le tableau suivant illustre la correspondance entre
l'utilisation autorisée spécifiée dans l'autorisation et les types d'isolation. En outre, il résume les problèmes de
sécurité associés à chaque utilisation autorisée.

UT IL ISAT IO N A UTO RISÉE T Y P ES D'ISO L AT IO N IM PA C T SUR L A SÉC URIT É

None Aucune utilisation de stockage isolé Aucun impact sur la sécurité.


n'est autorisée.

DomainIsolationByUser Isolation par utilisateur, par domaine et Ce niveau d'autorisation laisse les
par assembly. Chaque assembly ressources ouvertes à une
possède un sous-magasin distinct dans surutilisation non autorisée, même si
le domaine. Les magasins utilisant les quotas appliqués rendent cette
cette autorisation sont également surutilisation plus difficile. C'est une
implicitement isolés par l'ordinateur. attaque de refus de service.

DomainIsolationByRoamingUser Identique à DomainIsolationByUser , Dans la mesure où les quotas doivent


mais le magasin est enregistré à un être désactivés, les ressources de
emplacement itinérant si les profils stockage sont plus vulnérables à une
utilisateurs itinérants sont activés et si attaque de refus de service.
les quotas ne sont pas appliqués.

AssemblyIsolationByUser Isolation par utilisateur et par Les quotas sont appliqués à ce niveau
assembly. Les magasins utilisant cette pour empêcher une attaque de refus
autorisation sont également de service. Un assembly identique d'un
implicitement isolés par l'ordinateur. autre domaine peut accéder à ce
magasin, ce qui permet la divulgation
d'informations entre des applications.

AssemblyIsolationByRoamingUser Identique à Identique à


AssemblyIsolationByUser , mais le AssemblyIsolationByUser , mais le
magasin est enregistré à un risque d'attaque par déni de service
emplacement itinérant si les profils augmente sans les quotas.
utilisateurs itinérants sont activés et si
les quotas ne sont pas appliqués.

AdministerIsolatedStorageByUser Isolation par utilisateur. Généralement, L'accès avec cette autorisation permet
seuls les outils d'administration ou de au code d'afficher ou de supprimer un
débogage utilisent ce niveau fichier de stockage isolé d'un utilisateur
d'autorisation. ou des répertoires (sans tenir compte
de l'isolation d'assembly). Les risques
comprennent notamment une
divulgation d'informations et une perte
de données.
UT IL ISAT IO N A UTO RISÉE T Y P ES D'ISO L AT IO N IM PA C T SUR L A SÉC URIT É

UnrestrictedIsolatedStorage Isolation par tous les utilisateurs, Cette autorisation permet un


domaines et assemblys. Généralement, compromis total de tous les magasins
seuls les outils d'administration ou de isolés pour tous les utilisateurs.
débogage utilisent ce niveau
d'autorisation.

Sécurité des composants de stockage isolé en ce qui concerne les


données non approuvées
Cette section s’applique aux frameworks suivants :
.NET Framework (toutes les versions)
.NET Core 2.1 +
.NET 5.0 +
.NET Framework et .NET Core offrent un stockage isolé comme un mécanisme permettant de conserver les
données d’un utilisateur, d’une application ou d’un composant. Il s’agit d’un composant hérité principalement
conçu pour les scénarios de sécurité d’accès du code dépréciés.
Diverses API et outils de stockage isolé peuvent être utilisés pour lire les données au-delà des limites
d’approbation. Par exemple, la lecture de données à partir d’une étendue à l’échelle de l’ordinateur peut agréger
des données à partir d’autres comptes d’utilisateurs, éventuellement moins fiables, sur l’ordinateur. Les
composants ou les applications qui lisent à partir des étendues de stockage isolé à l’échelle de l’ordinateur
doivent être conscients des conséquences de la lecture de ces données.
API sensibles à la sécurité qui peuvent lire à partir de l’étendue à l’échelle de l’ordinateur
Les composants ou les applications qui appellent les API suivantes lisent à partir de l’étendue à l’échelle de
l’ordinateur :
IsolatedStorageFile. GetEnumerator, en passant une étendue qui comprend l’indicateur IsolatedStorageScope.
machine
IsolatedStorageFile. GetMachineStoreForApplication
IsolatedStorageFile. GetMachineStoreForAssembly
IsolatedStorageFile. GetMachineStoreForDomain
IsolatedStorageFile. GetStore, passant une portée qui comprend l’indicateur IsolatedStorageScope. machine
IsolatedStorageFile. Remove, passage d’une étendue incluant l' IsolatedStorageScope.Machine indicateur

L' outil de stockage isolé storeadm.exe est affecté s’il est appelé avec le /machine commutateur, comme illustré
dans le code suivant :

storeadm.exe /machine [any-other-switches]

L’outil Isolated Storage est fourni avec Visual Studio et le kit de développement logiciel (SDK) .NET Framework.
Si l’application n’implique pas d’appels aux API précédentes, ou si le flux de travail n’implique pas storeadm.exe
d’appel de cette manière, ce document n’est pas applicable.
Impact dans les environnements multi-utilisateurs
Comme mentionné précédemment, l’impact sur la sécurité de ces API résulte de la lecture des données écrites à
partir d’un environnement de confiance dans un environnement de confiance différent. Le stockage isolé utilise
généralement l’un des trois emplacements suivants pour lire et écrire des données :
1. %LOCALAPPDATA%\IsolatedStorage\ : Par exemple, C:\Users\<username>\AppData\Local\IsolatedStorage\ pour l'
User étendue.
2. %APPDATA%\IsolatedStorage\ : Par exemple, C:\Users\<username>\AppData\Roaming\IsolatedStorage\ pour l'
User|Roaming étendue.
3. %PROGRAMDATA%\IsolatedStorage\ : Par exemple, C:\ProgramData\IsolatedStorage\ pour l' Machine étendue.

Les deux premiers emplacements sont isolés par utilisateur. Windows garantit que différents comptes
d’utilisateur sur le même ordinateur ne peuvent pas accéder aux dossiers de profil utilisateur de l’autre. Deux
comptes d’utilisateur différents qui utilisent User les User|Roaming magasins ou ne voient pas les données de
l’autre et ne peuvent pas interférer avec les données de l’autre.
Le troisième emplacement est partagé entre tous les comptes d’utilisateur sur l’ordinateur. Différents comptes
peuvent lire et écrire à cet emplacement, et ils peuvent voir les données de chacun.
Les chemins d’accès précédents peuvent différer selon la version de Windows utilisée.
Considérons maintenant un système multi-utilisateur avec deux utilisateurs inscrits Mallory et Bob. Mallory a la
possibilité d’accéder à son répertoire de profil utilisateur C:\Users\Mallory\ et peut accéder à l’emplacement de
stockage partagé à l’ensemble de l’ordinateur C:\ProgramData\IsolatedStorage\ . Elle ne peut pas accéder au
répertoire du profil utilisateur de Bob C:\Users\Bob\ .
Si Mallory souhaite attaquer Bob, il peut écrire des données dans l’emplacement de stockage à l’intérieur de
l’ordinateur, puis tenter d’influencer Bob dans la lecture à partir du magasin à l’intérieur de l’ordinateur. Quand
Bob exécute une application qui lit à partir de ce magasin, cette application fonctionnera sur les données que
Mallory y a placées, mais à partir du contexte du compte d’utilisateur de Bob. Le reste de ce document présente
différents vecteurs d’attaque et les étapes que les applications peuvent effectuer pour réduire les risques liés à
ces attaques.
Remarque : Pour qu’une telle attaque ait lieu, Mallory requiert :
Un compte d’utilisateur sur l’ordinateur.
Capacité de placer un fichier dans un emplacement connu sur le système de fichiers.
Savoir que Bob va à un moment donné exécuter une application qui tente de lire ces données.
Il ne s’agit pas de vecteurs de menace qui s’appliquent aux environnements de bureau à utilisateur unique
standard, tels que les ordinateurs personnels ou les stations de travail d’entreprise à un seul employé.
Élévation de privilège
Une attaque par élévation de privilèges se produit lorsque l’application de Bob lit le fichier de Mallory et tente
automatiquement d’entreprendre une action en fonction du contenu de cette charge utile. Prenons l’exemple
d’une application qui lit le contenu d’un script de démarrage à partir du magasin de l’ordinateur et transmet ce
contenu à Process.Start . Si Mallory peut placer un script malveillant à l’intérieur du magasin de l’ordinateur,
quand Bob lance son application :
Son application analyse et lance le script malveillant de Mallory dans le contexte du profil utilisateur de Bob.
Mallory accède au compte de Bob sur l’ordinateur local.
Déni de service
Une attaque par déni de ser vice se produit lorsque l’application de Bob lit le fichier et les blocages de Mallory,
ou s’arrête de fonctionner correctement. Reprenons l’application mentionnée précédemment, qui tente
d’analyser un script de démarrage à partir du magasin de l’ordinateur. Si Mallory peut placer un fichier dont le
contenu est incorrect dans le magasin de l’ordinateur, il peut :
Oblige l’application de Bob à lever une exception tôt dans le chemin de démarrage.
Empêcher le lancement de l’application en raison de l’exception.
Elle a ensuite refusé à Bob la possibilité de lancer l’application sous son propre compte d’utilisateur.
Divulgation d’informations
Une attaque de Divulgation d’informations se produit lorsque Mallory peut inciter Bob à divulguer le
contenu d’un fichier auquel Mallory n’a normalement pas accès. Supposons que Bob a un fichier de secret
C:\Users\Bob\secret.txt que Mallory souhaite lire. Elle connaît le chemin d’accès à ce fichier, mais elle ne peut pas
la lire, car Windows lui interdit d’accéder au répertoire du profil utilisateur de Bob.
Au lieu de cela, Mallory place un lien physique dans le magasin à l’intérieur de l’ordinateur. Il s’agit d’un type
spécial de fichier qui ne contient pas de contenu, mais il pointe vers un autre fichier sur le disque. La tentative de
lecture du fichier de liaison physique aura pour effet de lire le contenu du fichier ciblé par le lien. Une fois le lien
physique créé, Mallory ne peut toujours pas lire le contenu du fichier, car il n’a pas accès à la cible (
C:\Users\Bob\secret.txt ) du lien. Toutefois , Bob a accès à ce fichier.

Lorsque l’application de Bob lit à partir du magasin de l’ordinateur, elle lit par inadvertance le contenu de son
fichier, de la même secret.txt façon que si le fichier lui-même était présent dans le magasin de l’ordinateur.
Lorsque l’application de Bob s’arrête, si elle tente de réenregistrer le fichier dans le magasin de l’ordinateur, elle
finit par placer une copie réelle du fichier dans le répertoire * C:\ProgramData\IsolatedStorage * . Étant donné
que ce répertoire est lisible par n’importe quel utilisateur sur l’ordinateur, Mallory peut maintenant lire le
contenu du fichier.
Meilleures pratiques pour la protection contre ces attaques
Impor tant : Si votre environnement a plusieurs utilisateurs mutuellement non approuvés, n’appelez pas l’API
IsolatedStorageFile.GetEnumerator(IsolatedStorageScope.Machine) ou appelez l’outil storeadm.exe /machine /list
. Les deux partent du principe qu’ils fonctionnent avec des données approuvées. Si une personne malveillante
peut amorcer une charge malveillante dans le magasin de l’ordinateur, cette charge utile peut mener à une
attaque par élévation de privilège dans le contexte de l’utilisateur qui exécute ces commandes.
En cas de fonctionnement dans un environnement multi-utilisateur, reconsidérez l’utilisation des fonctionnalités
de stockage isolé qui ciblent la portée de l' ordinateur . Si une application doit lire les données à partir d’un
emplacement au niveau de l’ordinateur, préférez lire les données à partir d’un emplacement accessible en
écriture uniquement par les comptes d’administrateur. Le %PROGRAMFILES% répertoire et la HKLM ruche de
Registre sont des exemples d’emplacements accessibles en écriture par les administrateurs et accessibles en
lecture par tout le monde. Les données lues à partir de ces emplacements sont donc considérées comme dignes
de confiance.
Si une application doit utiliser la portée de l' ordinateur dans un environnement multi-utilisateur, validez le
contenu des fichiers que vous lisez à partir du magasin de l’ordinateur. Si l’application désérialise des graphiques
d’objets à partir de ces fichiers, envisagez d’utiliser des sérialiseurs plus sûrs comme XmlSerializer au lieu de
sérialiseurs dangereux comme BinaryFormatter ou NetDataContractSerializer . Soyez vigilant avec les
graphiques d’objets profondément imbriqués ou les graphiques d’objets qui effectuent l’allocation des
ressources en fonction du contenu du fichier.

Emplacements de stockage isolé


Il est parfois utile de vérifier une modification apportée au stockage isolé en utilisant le système de fichiers du
système d'exploitation. Vous devrez peut-être également avoir besoin de connaître l'emplacement des fichiers de
stockage isolé. Cet emplacement dépend du système d'exploitation. Le tableau suivant illustre les emplacements
racine du stockage isolé sur quelques systèmes d'exploitation courants. Examinez les répertoires
Microsoft\IsolatedStorage sous cet emplacement racine. Vous devez modifier les paramètres de dossier pour
afficher les fichiers et les dossiers cachés afin de visualiser le stockage isolé dans le système de fichiers.
SY ST ÈM E D’EXP LO ITAT IO N EM P L A C EM EN T DA N S L E SY ST ÈM E DE F IC H IERS

Windows 2000, Windows XP, Windows Server 2003 (mise à Magasins itinérants =
niveau de Windows NT 4.0)
<SYSTEMROOT>\Profiles \<utilisateur > \Application Data

Magasins non itinérants =

<SYSTEMROOT>\Profiles \<utilisateur > \Local


Settings\Application Data

Windows 2000 - Nouvelle installation (et mises à niveau de Magasins itinérants =


Windows 98 et Windows NT 3.51)
<SYSTEMDRIVE>\Documents and Settings \<utilisateur >
\Application Data

Magasins non itinérants =

<SYSTEMDRIVE>\Documents and Settings \<utilisateur >


\Local Settings\Application Data

Windows XP, Windows Server 2003 - Nouvelle installation (et Magasins itinérants =
mises à niveau de Windows 2000 et Windows 98)
<SYSTEMDRIVE>\Documents and Settings \<utilisateur >
\Application Data

Magasins non itinérants =

<SYSTEMDRIVE>\Documents and Settings \<utilisateur >


\Local Settings\Application Data

Windows 8, Windows 7, Windows Server 2008, Windows Magasins itinérants =


Vista
<SYSTEMDRIVE>\Utilisateurs \<utilisateur >
\AppData\Roaming

Magasins non itinérants =

<SYSTEMDRIVE>\Utilisateurs \<utilisateur > \AppData\Local

Création, énumération et suppression du stockage isolé


Le .NET Framework fournit trois classes dans l'espace de noms System.IO.IsolatedStorage pour vous aider à
exécuter des tâches qui impliquent le stockage isolé :
IsolatedStorageFile, qui dérive de System.IO.IsolatedStorage.IsolatedStorage , assure la gestion de base
des fichiers d'application et d'assembly stockés. Une instance de la classe IsolatedStorageFile représente
un magasin unique situé dans le système de fichiers.
IsolatedStorageFileStream , qui dérive de System.IO.FileStream , donne accès aux fichiers d'un magasin.
IsolatedStorageScope est une énumération qui vous permet de créer et de sélectionner un magasin avec
un type d'isolation approprié.
Les classes de stockage isolé vous permettent de créer, d'énumérer et de supprimer le stockage isolé. Les
méthodes permettant d'effectuer ces tâches sont disponibles par l'intermédiaire de l'objet IsolatedStorageFile .
Avec certaines opérations, vous devez disposer de l'autorisation IsolatedStorageFilePermission qui représente le
droit d'administrer le stockage isolé ; vous pouvez également être amené à disposer de droits de système
d'exploitation pour accéder au fichier ou au répertoire.
Pour obtenir une série d’exemples illustrant les tâches de stockage isolé courantes, consultez les rubriques
« comment » sous Rubriques connexes.

Scénarios de stockage isolé


Le stockage isolé est utile dans de nombreuses situations, notamment dans ces quatre scénarios :
Contrôles téléchargés. Les contrôles de code managé téléchargés à partir d'Internet ne sont pas autorisés
à écrire sur le disque dur via les classes E/S ordinaires, mais ils peuvent utiliser le stockage isolé pour
rendre persistants les paramètres des utilisateurs et les états des applications.
Stockage de composant partagé. Les composants partagés entre plusieurs applications peuvent utiliser le
stockage isolé pour fournir un accès contrôlé aux magasins de données.
Stockage serveur. Les applications serveur peuvent utiliser le stockage isolé pour fournir des magasins
individuels à de nombreux utilisateurs qui effectuent des demandes à l'application. Dans la mesure où le
stockage isolé est toujours isolé par l'utilisateur, le serveur doit emprunter l'identité de l'utilisateur qui
effectue la demande. Dans ce cas, les données sont isolées en fonction de l'identité de l'entité de sécurité,
qui est identique à l'identité utilisée par l'application pour établir une distinction entre ses utilisateurs.
Profil itinérant. Les applications peuvent également utiliser le stockage isolé avec des profils d'utilisateur
itinérant. Ainsi, les magasins isolés d'un utilisateur peuvent être itinérants avec le profil.
Vous ne devez pas utiliser le stockage isolé dans les situations suivantes :
Pour stocker des secrets de grande valeur, tels que des clés ou des mots de passe non chiffrés, car le
stockage isolé n'est pas protégé contre le code à niveau de confiance extrêmement élevé, contre le code
non managé ou contre les utilisateurs de confiance de l'ordinateur.
Pour stocker du code.
Pour enregistrer les paramètres de configuration et de déploiement, qui sont contrôlés par les
administrateurs. (les préférences de l'utilisateur ne sont pas considérées comme des paramètres de
configuration car les administrateurs ne les contrôlent pas).
De nombreuses applications utilisent une base de données pour stocker et isoler des données. Dans ce cas, une
ou plusieurs lignes d'une base de données peuvent représenter le stockage d'un utilisateur spécifique. Vous
pouvez utiliser le stockage isolé plutôt qu'une base de données lorsque le nombre d'utilisateurs est petit, lorsque
la surcharge de l'utilisation d'une base de données est significative ou lorsque aucune base de données n'existe.
Le stockage isolé peut également être utilisé lorsque l'application nécessite un stockage plus flexible et plus
complexe que celui proposé par une ligne d'une base de données.

Articles connexes
T IT RE DESC RIP T IO N

Types d'isolation Décrit les divers types d'isolation.

Procédure : obtenir des magasins pour le stockage isolé Donne un exemple d'utilisation de la classe
IsolatedStorageFile pour obtenir un magasin isolé par
utilisateur et par assembly.

Procédure : énumérer des magasins pour le stockage isolé Indique comment utiliser la méthode
IsolatedStorageFile.GetEnumerator pour calculer la taille de
tout le stockage isolé de l'utilisateur.
T IT RE DESC RIP T IO N

Procédure : supprimer des magasins dans le stockage isolé Indique comment utiliser la méthode
IsolatedStorageFile.Remove de deux façons différentes pour
supprimer des magasins isolés.

Procédure : anticiper des conditions d’espace insuffisant avec Illustre comment mesurer l'espace restant dans un magasin
le stockage isolé isolé.

Procédure : créer des fichiers et des répertoires dans un Propose plusieurs exemples pour créer des fichiers et des
stockage isolé répertoires dans un magasin isolé.

Procédure : rechercher des fichiers et des répertoires Illustre comment lire la structure de répertoire et les fichiers
existants dans un stockage isolé dans le stockage isolé.

Procédure : lire et écrire des fichiers dans un stockage isolé Fournit un exemple d'écriture d'une chaîne dans un fichier de
stockage isolé et de sa lecture ultérieure.

Procédure : supprimer des fichiers et des répertoires dans un Indique comment supprimer des fichiers et des répertoires
stockage isolé d'un stockage isolé

E/s de fichier et de flux Explique comment accéder aux flux de données et de fichiers
de façon synchrone et asynchrone.

Référence
System.IO.IsolatedStorage.IsolatedStorage
System.IO.IsolatedStorage.IsolatedStorageFile
System.IO.IsolatedStorage.IsolatedStorageFileStream
System.IO.IsolatedStorage.IsolatedStorageScope
Types d'isolation
18/07/2020 • 10 minutes to read • Edit Online

L’accès au stockage isolé est toujours limité à l’utilisateur qui l’a créé. Pour implémenter ce type d’isolation, le
Common Language Runtime utilise la même notion d’identité de l’utilisateur reconnue par le système
d’exploitation, c’est-à-dire l’identité associée au processus par lequel le code s’exécute lorsque le magasin est
ouvert. Cette identité est l’identité d’un utilisateur, mais l’emprunt d’identité peut provoquer un changement
dynamique de l’identité actuelle de l’utilisateur.
L’accès au stockage isolé est également limité en fonction de l’identité associée au domaine et à l’assembly de
l’application, ou à l’assembly uniquement. Le runtime obtient ces identités des façons suivantes :
L’identité de domaine constitue la preuve de l’application, et dans le cas d’une application Web, il peut s’agir
de l’URL complète. Pour le code hébergé par l’interpréteur de commandes, l’identité de domaine peut être
basée sur le chemin du répertoire de l’application. Par exemple, si l’exécutable s’exécute à partir du chemin
d’accès C:\Office\MyApp.exe, l’identité de domaine serait C:\Office\MyApp.exe.
L’identité d’assembly est la preuve de l’assembly. Elle peut provenir d’une signature numérique chiffrée, qui
peut être le nom fort de l’assembly, de l’éditeur du logiciel de l’assembly, ou de son identité d’URL. Si un
assembly possède un nom fort et une identité d’éditeur de logiciel, l’identité de l’éditeur de logiciel est
utilisée. Si l’assembly provient d’Internet et n’est pas signé, l’identité d’URL est utilisée. Pour plus
d’informations sur les assemblys et les noms forts, consultez Programmation à l'aide d'assemblys.
Les magasins itinérants se déplacent avec un utilisateur qui dispose d’un profil utilisateur itinérant. Les
fichiers sont enregistrés dans un répertoire réseau et téléchargés sur n’importe quel ordinateur auquel
l’utilisateur se connecte. Pour plus d'informations sur les profils utilisateur itinérants, consultez
IsolatedStorageScope.Roaming.
En combinant les concepts d’utilisateur, de domaine et d’identité d’assembly, le stockage isolé permet d’isoler des
données de plusieurs manières, chacune avec ses propres scénarios d’utilisation :
Isolation par utilisateur et par assembly
Isolation par utilisateur, par domaine et par assembly
Chacune de ces isolations peut être combinée avec un profil utilisateur itinérant. Pour plus d’informations,
consultez la section Stockage et profil itinérant isolé.
L’illustration suivante montre comment les magasins sont isolés dans différentes étendues :

À l’exception des magasins itinérants, le stockage isolé est toujours implicitement isolé par l’ordinateur, car il utilise
les fonctionnalités de stockage qui sont locales à un ordinateur donné.
IMPORTANT
Le stockage isolé n’est pas disponible pour les applications du Windows 8. x Store. À la place, utilisez les classes de données
d’application des espaces de noms Windows.Storage inclus dans l’API Windows Runtime pour stocker des données locales
et des fichiers. Pour plus d’informations, consultez Données d’applications dans le Centre de développement Windows.

Isolation par utilisateur et par assembly


Lorsque l’assembly qui utilise les données du magasin doit être accessible depuis n’importe quel domaine
d’application, l’isolation par utilisateur et par assembly est recommandée. Dans ce cas, le stockage isolé sert
généralement à stocker les données qui s’appliquent à plusieurs applications et qui ne sont pas liées à une
application spécifique, par exemple le nom d’utilisateur ou des informations de licence. Pour accéder au stockage
isolé par utilisateur et par assembly, le code doit être approuvé pour transférer des informations entre les
applications. En règle générale, l’isolation par utilisateur et par assembly est autorisée sur les intranets mais pas
sur Internet. L’appel de la méthode statique IsolatedStorageFile.GetStore et le passage d’un utilisateur et d’un
assembly IsolatedStorageScope génèrent un stockage avec ce type d’isolation.
L’exemple de code suivant extrait un magasin isolé par utilisateur et par assembly. Le magasin est accessible via
l’objet isoFile .

IsolatedStorageFile^ isoFile =
IsolatedStorageFile::GetStore(IsolatedStorageScope::User |
IsolatedStorageScope::Assembly, (Type^)nullptr, (Type^)nullptr);

IsolatedStorageFile isoFile =
IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Assembly, null, null);

Dim isoFile As IsolatedStorageFile = _


IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or _
IsolatedStorageScope.Assembly, Nothing, Nothing)

Pour obtenir un exemple qui utilise les paramètres de preuve, consultez GetStore(IsolatedStorageScope, Evidence,
Type, Evidence, Type).
La méthode GetUserStoreForAssembly est disponible comme raccourci, comme illustré dans l’exemple de code
suivant. Ce raccourci ne peut pas être utilisé pour ouvrir des magasins capables d’itinérance ; utilisez GetStore
dans ce cas.

IsolatedStorageFile^ isoFile = IsolatedStorageFile::GetUserStoreForAssembly();

IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForAssembly();

Dim isoFile As IsolatedStorageFile = _


IsolatedStorageFile.GetUserStoreForAssembly()

Isolation par utilisateur, par domaine et par assembly


Si votre application utilise un assembly tiers qui nécessite une banque de données privée, vous pouvez utiliser le
stockage isolé pour stocker ces données privées. L’isolation par utilisateur, domaine et assembly garantit que seul
le code d’un assembly donné peut accéder aux données et uniquement lorsque cet assembly est utilisé par
l’application en cours d’exécution lors de la création du magasin, et lorsque l’utilisateur pour lequel le magasin a
été créé exécute l’application. L’isolation par utilisateur, domaine et assembly empêche l’assembly tiers de
transmettre des données à d’autres applications. Ce type d’isolation devrait être votre choix par défaut si vous
savez que vous utiliserez le stockage isolé, mais que vous hésitez sur le type d’isolation adéquat. L’appel de la
méthode statique GetStore de IsolatedStorageFile et le passage d’un utilisateur, d’un domaine et d’un assembly
IsolatedStorageScope génèrent un stockage avec ce type d’isolation.
L’exemple de code suivant extrait un magasin isolé par utilisateur, domaine et assembly. Le magasin est accessible
via l’objet isoFile .

IsolatedStorageFile^ isoFile =
IsolatedStorageFile::GetStore(IsolatedStorageScope::User |
IsolatedStorageScope::Domain |
IsolatedStorageScope::Assembly, (Type^)nullptr, (Type^)nullptr);

IsolatedStorageFile isoFile =
IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Domain |
IsolatedStorageScope.Assembly, null, null);

Dim isoFile As IsolatedStorageFile = _


IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or _
IsolatedStorageScope.Domain Or _
IsolatedStorageScope.Assembly, Nothing, Nothing)

Une autre méthode est disponible comme raccourci, comme illustré dans l’exemple de code suivant. Ce raccourci
ne peut pas être utilisé pour ouvrir des magasins capables d’itinérance ; utilisez GetStore dans ce cas.

IsolatedStorageFile^ isoFile = IsolatedStorageFile::GetUserStoreForDomain();

IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForDomain();

Dim isoFile As IsolatedStorageFile = _


IsolatedStorageFile.GetUserStoreForDomain()

Stockage et profil itinérant isolé


Les profils utilisateur itinérants représentent une fonctionnalité Windows qui permet à un utilisateur de configurer
une identité sur un réseau et d’utiliser cette identité pour se connecter à n’importe quel ordinateur du réseau, en
conservant tous les réglages personnalisés. Un assembly qui utilise le stockage isolé peut spécifier que le stockage
isolé de l’utilisateur devra accompagner le profil utilisateur itinérant. L’itinérance peut être utilisée conjointement
avec l’isolation par utilisateur et par assembly, ou avec l’isolation par utilisateur, domaine et assembly. Si aucune
portée d’itinérance n’est définie, les magasins ne bénéficieront d’aucune itinérance, même si un profil utilisateur
itinérant est utilisé.
L’exemple de code suivant récupère un magasin itinérant isolé par utilisateur et assembly. Le magasin est
accessible via l’objet isoFile .
IsolatedStorageFile^ isoFile =
IsolatedStorageFile::GetStore(IsolatedStorageScope::User |
IsolatedStorageScope::Assembly |
IsolatedStorageScope::Roaming, (Type^)nullptr, (Type^)nullptr);

IsolatedStorageFile isoFile =
IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Assembly |
IsolatedStorageScope.Roaming, null, null);

Dim isoFile As IsolatedStorageFile = _


IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or _
IsolatedStorageScope.Assembly Or _
IsolatedStorageScope.Roaming, Nothing, Nothing)

Une portée de domaine peut être ajoutée afin de créer un magasin itinérant isolé par utilisateur, domaine et
application. L'exemple de code suivant illustre cette tâche.

IsolatedStorageFile^ isoFile =
IsolatedStorageFile::GetStore(IsolatedStorageScope::User |
IsolatedStorageScope::Assembly | IsolatedStorageScope::Domain |
IsolatedStorageScope::Roaming, (Type^)nullptr, (Type^)nullptr);

IsolatedStorageFile isoFile =
IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Assembly | IsolatedStorageScope.Domain |
IsolatedStorageScope.Roaming, null, null);

Dim isoFile As IsolatedStorageFile = _


IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or _
IsolatedStorageScope.Assembly Or IsolatedStorageScope.Domain Or _
IsolatedStorageScope.Roaming, Nothing, Nothing)

Voir aussi
IsolatedStorageScope
Stockage isolé
Procédure : obtenir des magasins pour le stockage
isolé
18/07/2020 • 4 minutes to read • Edit Online

Un magasin isolé expose un système de fichiers virtuel dans un compartiment de données. La classe
IsolatedStorageFile fournit plusieurs méthodes pour interagir avec un magasin isolé. Pour créer et récupérer des
magasins, IsolatedStorageFile propose trois méthodes statiques :
GetUserStoreForAssembly retourne un stockage isolé par utilisateur et par assembly.
GetUserStoreForDomain retourne un stockage isolé par domaine et par assembly.
Les deux méthodes récupèrent un magasin qui appartient au code à partir duquel elles sont appelées.
La méthode statique GetStore retourne un magasin isolé spécifié en passant une combinaison de
paramètres d’étendue.
Le code suivant retourne un magasin isolé par utilisateur, assembly et domaine.

IsolatedStorageFile^ isoStore = IsolatedStorageFile::GetStore(IsolatedStorageScope::User |


IsolatedStorageScope::Assembly | IsolatedStorageScope::Domain, (Type ^)nullptr, (Type ^)nullptr);

IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User |


IsolatedStorageScope.Assembly | IsolatedStorageScope.Domain, null, null);

Dim isoStore As IsolatedStorageFile = IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or


IsolatedStorageScope.Assembly Or IsolatedStorageScope.Domain, Nothing, Nothing)

Vous pouvez utiliser la méthode GetStore pour spécifier qu’un magasin doit se déplacer avec un profil utilisateur
itinérant. Pour plus d’informations sur la façon de configurer ce paramètre, consultez Types d’isolation.
Les magasins isolés obtenus à partir de différents assemblys sont, par défaut, des magasins différents. Vous
pouvez accéder au magasin d’un domaine ou d’un assembly différent en passant la preuve d’assembly ou de
domaine dans les paramètres de la méthode GetStore. Pour ce faire, l’autorisation d’accéder au stockage isolé par
l’identité de domaine d’application est requise. Pour plus d'informations, consultez les surcharges de la méthode
GetStore.
Les méthodes GetUserStoreForAssembly, GetUserStoreForDomain et GetStore retournent un objet
IsolatedStorageFile. Pour vous aider à déterminer le type d’isolation le plus approprié à votre situation, consultez
Types d’isolation. Lorsque vous disposez d’un objet de fichier de stockage isolé, vous pouvez utiliser les méthodes
de stockage isolé pour lire, écrire, créer et supprimer des fichiers et répertoires.
Il n’existe aucun mécanisme qui empêche le code de passer un objet IsolatedStorageFile vers du code qui n’a pas de
droits d’accès suffisants pour obtenir le magasin lui-même. Les identités de domaine et d’assembly et les
autorisations pour le stockage isolé sont vérifiées uniquement lorsqu’une référence à un objet IsolatedStorage est
obtenue, en général dans la méthode GetUserStoreForAssembly, GetUserStoreForDomain ou GetStore. La
protection des références aux objets IsolatedStorageFile est, par conséquent, la responsabilité du code qui utilise
ces références.
Exemple
Le code suivant fournit un exemple simple d’une classe obtenant un magasin isolé par utilisateur et par assembly.
Le code peut ensuite être modifié afin de récupérer un magasin isolé par utilisateur, domaine et assembly en
ajoutant IsolatedStorageScope.Domain aux arguments passés par la méthode GetStore.
Après avoir exécuté le code, vous pouvez confirmer qu’un magasin a été créé en tapant StoreAdm /LIST sur la
ligne de commande. Cette commande exécute l’outil Stockage isolé (Storeadm.exe) et répertorie tous les magasins
actuellement isolés pour l’utilisateur.

using namespace System;


using namespace System::IO::IsolatedStorage;

public ref class ObtainingAStore


{
public:
static void Main()
{
// Get a new isolated store for this assembly and put it into an
// isolated store object.

IsolatedStorageFile^ isoStore = IsolatedStorageFile::GetStore(IsolatedStorageScope::User |


IsolatedStorageScope::Assembly, (Type ^)nullptr, (Type ^)nullptr);
}
};

using System;
using System.IO.IsolatedStorage;

public class ObtainingAStore


{
public static void Main()
{
// Get a new isolated store for this assembly and put it into an
// isolated store object.

IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User |


IsolatedStorageScope.Assembly, null, null);
}
}

Imports System.IO.IsolatedStorage

Public Class ObtainingAStore


Public Shared Sub Main()
' Get a new isolated store for this assembly and put it into an
' isolated store object.

Dim isoStore As IsolatedStorageFile = IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or


IsolatedStorageScope.Assembly, Nothing, Nothing)
End Sub
End Class

Voir aussi
IsolatedStorageFile
IsolatedStorageScope
Stockage isolé
Types d'isolation
Assemblys dans .NET
Procédure : énumérer des magasins pour le stockage
isolé
18/07/2020 • 2 minutes to read • Edit Online

Vous pouvez énumérer tous les magasins isolés pour l’utilisateur actif à l’aide de la méthode statique
IsolatedStorageFile.GetEnumerator. Cette méthode prend une valeur IsolatedStorageScope et retourne un
énumérateur IsolatedStorageFile. Pour énumérer les magasins, vous devez avoir l’autorisation
IsolatedStorageFilePermission qui spécifie la valeur AdministerIsolatedStorageByUser. Si vous appelez la méthode
GetEnumerator avec la valeur User, elle retourne un tableau d’objets IsolatedStorageFile qui sont définis pour
l’utilisateur actif.

Exemple
L’exemple de code suivant obtient un magasin qui est isolé par utilisateur et par assembly, crée quelques fichiers, et
récupère ces fichiers à l’aide de la méthode GetEnumerator.

using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Collections;

public class EnumeratingStores


{
public static void Main()
{
using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Assembly, null, null))
{
isoStore.CreateFile("TestFileA.Txt");
isoStore.CreateFile("TestFileB.Txt");
isoStore.CreateFile("TestFileC.Txt");
isoStore.CreateFile("TestFileD.Txt");
}

IEnumerator allFiles = IsolatedStorageFile.GetEnumerator(IsolatedStorageScope.User);


long totalsize = 0;

while (allFiles.MoveNext())
{
IsolatedStorageFile storeFile = (IsolatedStorageFile)allFiles.Current;
totalsize += (long)storeFile.UsedSize;
}

Console.WriteLine("The total size = " + totalsize);


}
}
Imports System.IO
Imports System.IO.IsolatedStorage

Module Module1
Sub Main()
Using isoStore As IsolatedStorageFile = IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or
IsolatedStorageScope.Assembly, Nothing, Nothing)
isoStore.CreateFile("TestFileA.Txt")
isoStore.CreateFile("TestFileB.Txt")
isoStore.CreateFile("TestFileC.Txt")
isoStore.CreateFile("TestFileD.Txt")
End Using

Dim allFiles As IEnumerator = IsolatedStorageFile.GetEnumerator(IsolatedStorageScope.User)


Dim totalsize As Long = 0

While (allFiles.MoveNext())
Dim storeFile As IsolatedStorageFile = CType(allFiles.Current, IsolatedStorageFile)
totalsize += CType(storeFile.UsedSize, Long)
End While

Console.WriteLine("The total size = " + totalsize.ToString())

End Sub
End Module

Voir aussi
IsolatedStorageFile
Stockage isolé
Procédure : supprimer des magasins dans le stockage
isolé
18/07/2020 • 3 minutes to read • Edit Online

La classe IsolatedStorageFile fournit deux méthodes pour supprimer les fichiers de stockage isolés :
La méthode d’instance Remove() n’accepte pas d’argument et supprime le magasin qui l’appelle. Aucune
autorisation n’est nécessaire pour cette opération. Tout code pouvant accéder au magasin peut supprimer
tout ou partie des données qu’il contient.
La méthode statique Remove(IsolatedStorageScope) prend la valeur d’énumération User et supprime tous
les magasins pour l’utilisateur qui exécute le code. Cette opération nécessite une autorisation
IsolatedStorageFilePermission pour la valeur AdministerIsolatedStorageByUser .

Exemple
L’exemple de code suivant illustre l’utilisation des méthodes d’instance et statique Remove . La classe obtient deux
magasins. L’un est isolé pour l’utilisateur et l’assembly, et l’autre est isolé pour l’utilisateur, le domaine et l’assembly.
Le magasin de l’utilisateur, du domaine et de l’assembly est ensuite supprimé en appelant la méthode Remove() du
fichier de stockage isolé isoStore1 . Ensuite, tous les magasins restants pour l’utilisateur sont supprimés en
appelant la méthode statique Remove(IsolatedStorageScope).
using namespace System;
using namespace System::IO::IsolatedStorage;

public ref class DeletingStores


{
public:
static void Main()
{
// Get a new isolated store for this user, domain, and assembly.
// Put the store into an IsolatedStorageFile object.

IsolatedStorageFile^ isoStore1 = IsolatedStorageFile::GetStore(IsolatedStorageScope::User |


IsolatedStorageScope::Domain | IsolatedStorageScope::Assembly, (Type ^)nullptr, (Type ^)nullptr);
Console::WriteLine("A store isolated by user, assembly, and domain has been obtained.");

// Get a new isolated store for user and assembly.


// Put that store into a different IsolatedStorageFile object.

IsolatedStorageFile^ isoStore2 = IsolatedStorageFile::GetStore(IsolatedStorageScope::User |


IsolatedStorageScope::Assembly, (Type ^)nullptr, (Type ^)nullptr);
Console::WriteLine("A store isolated by user and assembly has been obtained.");

// The Remove method deletes a specific store, in this case the


// isoStore1 file.

isoStore1->Remove();
Console::WriteLine("The user, domain, and assembly isolated store has been deleted.");

// This static method deletes all the isolated stores for this user.

IsolatedStorageFile::Remove(IsolatedStorageScope::User);
Console::WriteLine("All isolated stores for this user have been deleted.");
} // End of Main.
};

int main()
{
DeletingStores::Main();
}
using System;
using System.IO.IsolatedStorage;

public class DeletingStores


{
public static void Main()
{
// Get a new isolated store for this user, domain, and assembly.
// Put the store into an IsolatedStorageFile object.

IsolatedStorageFile isoStore1 = IsolatedStorageFile.GetStore(IsolatedStorageScope.User |


IsolatedStorageScope.Domain | IsolatedStorageScope.Assembly, null, null);
Console.WriteLine("A store isolated by user, assembly, and domain has been obtained.");

// Get a new isolated store for user and assembly.


// Put that store into a different IsolatedStorageFile object.

IsolatedStorageFile isoStore2 = IsolatedStorageFile.GetStore(IsolatedStorageScope.User |


IsolatedStorageScope.Assembly, null, null);
Console.WriteLine("A store isolated by user and assembly has been obtained.");

// The Remove method deletes a specific store, in this case the


// isoStore1 file.

isoStore1.Remove();
Console.WriteLine("The user, domain, and assembly isolated store has been deleted.");

// This static method deletes all the isolated stores for this user.

IsolatedStorageFile.Remove(IsolatedStorageScope.User);
Console.WriteLine("All isolated stores for this user have been deleted.");
} // End of Main.
}

Imports System.IO.IsolatedStorage

Public Class DeletingStores


Public Shared Sub Main()
' Get a new isolated store for this user, domain, and assembly.
' Put the store into an IsolatedStorageFile object.

Dim isoStore1 As IsolatedStorageFile = IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or


IsolatedStorageScope.Domain Or IsolatedStorageScope.Assembly, Nothing, Nothing)
Console.WriteLine("A store isolated by user, assembly, and domain has been obtained.")

' Get a new isolated store for user and assembly.


' Put that store into a different IsolatedStorageFile object.

Dim isoStore2 As IsolatedStorageFile = IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or


IsolatedStorageScope.Assembly, Nothing, Nothing)
Console.WriteLine("A store isolated by user and assembly has been obtained.")

' The Remove method deletes a specific store, in this case the
' isoStore1 file.

isoStore1.Remove()
Console.WriteLine("The user, domain, and assembly isolated store has been deleted.")

' This static method deletes all the isolated stores for this user.

IsolatedStorageFile.Remove(IsolatedStorageScope.User)
Console.WriteLine("All isolated stores for this user have been deleted.")

End Sub
End Class
Voir aussi
IsolatedStorageFile
Stockage isolé
Procédure : anticiper des conditions d’espace
insuffisant avec le stockage isolé
18/07/2020 • 4 minutes to read • Edit Online

Le code qui utilise le stockage isolé est limité par un quota qui spécifie la taille maximale du compartiment de
données dans lequel des fichiers et répertoires de stockage isolé existent. Le quota est défini par la stratégie de
sécurité et peut être configuré par les administrateurs. Si la taille maximale autorisée est dépassée lorsque vous
tenez d’écrire des données, une exception IsolatedStorageException est levée et l’opération échoue. Cela permet
d’éviter des attaques malveillantes par déni de service, qui pourraient amener l’application à refuser des requêtes
parce que le stockage des données est rempli.
Pour vous aider à déterminer si une tentative d’écriture donnée est susceptible d’échouer pour cette raison, la
classe IsolatedStorage fournit trois propriétés en lecture seule : AvailableFreeSpace, UsedSize et Quota. Vous
pouvez utiliser ces propriétés pour déterminer si l’écriture dans le magasin entraînera le dépassement de la taille
maximale autorisée de ce dernier. N’oubliez pas que le stockage isolé est accessible simultanément. Par conséquent,
lorsque vous calculez la quantité de stockage restant, l’espace de stockage peut être consommé pendant que vous
tentez d’écrire dans le magasin. Toutefois, vous pouvez utiliser la taille maximale du magasin pour aider à
déterminer si la limite maximale de stockage disponible est sur le point d’être atteinte.
La propriété Quota dépend de la preuve du bon fonctionnement de l’assembly. Pour cette raison, vous devez
récupérer cette propriété uniquement sur des objets IsolatedStorageFile créés à l’aide de la méthode
GetUserStoreForAssembly, GetUserStoreForDomain ou GetStore. Les objets IsolatedStorageFile qui ont été créés
d’une autre façon (par exemple, les objets retournés à partir de la méthode GetEnumerator ne retourneront pas
une taille maximale précise.

Exemple
L’exemple de code suivant obtient un magasin isolé, crée quelques fichiers et récupère la propriété
AvailableFreeSpace. L’espace restant est indiqué en octets.
using namespace System;
using namespace System::IO;
using namespace System::IO::IsolatedStorage;

public ref class CheckingSpace


{
public:
static void Main()
{
// Get an isolated store for this assembly and put it into an
// IsolatedStoreFile object.
IsolatedStorageFile^ isoStore = IsolatedStorageFile::GetStore(IsolatedStorageScope::User |
IsolatedStorageScope::Assembly, (Type ^)nullptr, (Type ^)nullptr);

// Create a few placeholder files in the isolated store.


gcnew IsolatedStorageFileStream("InTheRoot.txt", FileMode::Create, isoStore);
gcnew IsolatedStorageFileStream("Another.txt", FileMode::Create, isoStore);
gcnew IsolatedStorageFileStream("AThird.txt", FileMode::Create, isoStore);
gcnew IsolatedStorageFileStream("AFourth.txt", FileMode::Create, isoStore);
gcnew IsolatedStorageFileStream("AFifth.txt", FileMode::Create, isoStore);

Console::WriteLine(isoStore->AvailableFreeSpace + " bytes of space remain in this isolated store.");


} // End of Main.
};

int main()
{
CheckingSpace::Main();
}

using System;
using System.IO;
using System.IO.IsolatedStorage;

public class CheckingSpace


{
public static void Main()
{
// Get an isolated store for this assembly and put it into an
// IsolatedStoreFile object.
IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Assembly, null, null);

// Create a few placeholder files in the isolated store.


new IsolatedStorageFileStream("InTheRoot.txt", FileMode.Create, isoStore);
new IsolatedStorageFileStream("Another.txt", FileMode.Create, isoStore);
new IsolatedStorageFileStream("AThird.txt", FileMode.Create, isoStore);
new IsolatedStorageFileStream("AFourth.txt", FileMode.Create, isoStore);
new IsolatedStorageFileStream("AFifth.txt", FileMode.Create, isoStore);

Console.WriteLine(isoStore.AvailableFreeSpace + " bytes of space remain in this isolated store.");


} // End of Main.
}
Imports System.IO
Imports System.IO.IsolatedStorage

Public Class CheckingSpace


Public Shared Sub Main()
' Get an isolated store for this assembly and put it into an
' IsolatedStoreFile object.
Dim isoStore As IsolatedStorageFile = _
IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or _
IsolatedStorageScope.Assembly, Nothing, Nothing)

' Create a few placeholder files in the isolated store.


Dim aStream As New IsolatedStorageFileStream("InTheRoot.txt", FileMode.Create, isoStore)
Dim bStream As New IsolatedStorageFileStream("Another.txt", FileMode.Create, isoStore)
Dim cStream As New IsolatedStorageFileStream("AThird.txt", FileMode.Create, isoStore)
Dim dStream As New IsolatedStorageFileStream("AFourth.txt", FileMode.Create, isoStore)
Dim eStream As New IsolatedStorageFileStream("AFifth.txt", FileMode.Create, isoStore)

Console.WriteLine(isoStore.AvailableFreeSpace + " bytes of space remain in this isolated store.")


End Sub
End Class

Voir aussi
IsolatedStorageFile
Stockage isolé
Procédure : obtenir des magasins pour le stockage isolé
Procédure : créer des fichiers et des répertoires dans
un stockage isolé
18/07/2020 • 2 minutes to read • Edit Online

Une fois que vous avez obtenu un magasin isolé, vous pouvez créer des répertoires et des fichiers pour le stockage
des données. Dans un magasin, les noms de répertoires et de fichiers sont spécifiées par rapport à la racine du
système de fichiers virtuel.
Pour créer un répertoire, utilisez la méthode d’instance IsolatedStorageFile.CreateDirectory. Si vous spécifiez un
sous-répertoire d’un répertoire qui n’existe pas, les deux répertoires sont créés. Si vous spécifiez un répertoire qui
existe déjà, la méthode retourne sans créer de répertoire et aucune exception n’est levée. Toutefois, si vous spécifiez
un nom de répertoire qui contient des caractères non valides, une exception IsolatedStorageException est levée.
Utilisez la méthode IsolatedStorageFile.CreateFile pour créer un fichier.
Dans le système d’exploitation Windows, les noms de fichiers et de répertoires du stockage isolé ne sont pas
sensibles à la casse. Autrement dit, si vous créez un fichier nommé ThisFile.txt , puis créez un autre fichier
nommé THISFILE.TXT , un seul fichier est créé. Le nom de fichier conserve sa casse d’origine pour l’affichage.
La création d’un fichier de stockage isolé lève une IsolatedStorageException si le chemin d’accès contient un
répertoire qui n’existe pas.

Exemple
L’exemple de code suivant illustre comment créer des fichiers et des répertoires dans un magasin isolé.

using System;
using System.IO;
using System.IO.IsolatedStorage;

public class CreatingFilesDirectories


{
public static void Main()
{
using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Domain | IsolatedStorageScope.Assembly, null, null))
{
isoStore.CreateDirectory("TopLevelDirectory");
isoStore.CreateDirectory("TopLevelDirectory/SecondLevel");
isoStore.CreateDirectory("AnotherTopLevelDirectory/InsideDirectory");
Console.WriteLine("Created directories.");

isoStore.CreateFile("InTheRoot.txt");
Console.WriteLine("Created a new file in the root.");

isoStore.CreateFile("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt");
Console.WriteLine("Created a new file in the InsideDirectory.");
}
}
}
Imports System.IO
Imports System.IO.IsolatedStorage

Module Module1
Sub Main()
Using isoStore As IsolatedStorageFile = IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or
IsolatedStorageScope.Assembly Or IsolatedStorageScope.Domain, Nothing, Nothing)

isoStore.CreateDirectory("TopLevelDirectory")
isoStore.CreateDirectory("TopLevelDirectory/SecondLevel")
isoStore.CreateDirectory("AnotherTopLevelDirectory/InsideDirectory")
Console.WriteLine("Created directories.")

isoStore.CreateFile("InTheRoot.txt")
Console.WriteLine("Created a new file in the root.")

isoStore.CreateFile("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt")
Console.WriteLine("Created a new file in the InsideDirectory.")
End Using
End Sub
End Module

Voir aussi
IsolatedStorageFile
IsolatedStorageFileStream
Stockage isolé
Procédure : rechercher des fichiers et des répertoires
existants dans un stockage isolé
18/07/2020 • 7 minutes to read • Edit Online

Pour rechercher un répertoire dans un stockage isolé, utilisez la méthode IsolatedStorageFile.GetDirectoryNames.


Cette méthode prend une chaîne qui représente un modèle de recherche. Vous pouvez utiliser des caractères
génériques à caractère unique (?) et à caractères multiples (*) dans le modèle de recherche, mais les caractères
génériques doivent apparaître dans la partie finale du nom. Par exemple, directory1/*ect* est une chaîne de
recherche valide, mais *ect*/directory2 ne l’est pas.
Utilisez la méthode IsolatedStorageFile.GetFileNames pour rechercher un fichier. La restriction des caractères
génériques dans des chaînes de recherche qui s’applique à GetDirectoryNames s’applique également à
GetFileNames.
Aucune de ces méthodes n’est récursive ; la classe IsolatedStorageFile ne fournit pas de méthode pour répertorier
tous les répertoires ou fichiers de votre magasin. Toutefois, les méthodes récursives sont affichées dans l’exemple
de code suivant.

Exemple
L’exemple de code suivant illustre comment créer des fichiers et des répertoires dans un magasin isolé. Tout
d’abord, un magasin isolé pour l’utilisateur, le domaine et l’assembly est récupéré et placé dans la variable
isoStore . La méthode CreateDirectory est utilisée pour configurer quelques répertoires différents dans lesquels le
constructeur IsolatedStorageFileStream(String, FileMode, IsolatedStorageFile) crée des fichiers. Le code parcourt
ensuite en boucle les résultats de la méthode GetAllDirectories . Cette méthode utilise GetDirectoryNames pour
rechercher tous les noms de répertoires dans le répertoire actif. Ces noms sont stockés dans un tableau, puis
GetAllDirectories appelle lui-même en passant dans chaque répertoire détecté. Par conséquent, tous les noms de
répertoires sont retournés dans un tableau. Ensuite, le code appelle la méthode GetAllFiles . Cette méthode
appelle GetAllDirectories pour connaître les noms de tous les répertoires, puis recherche des fichiers dans
chaque répertoire à l’aide de la méthode GetFileNames. Le résultat est retourné dans un tableau pour y être
affiché.

using namespace System;


using namespace System::IO;
using namespace System::IO::IsolatedStorage;
using namespace System::Collections;
using namespace System::Collections::Generic;

public class FindingExistingFilesAndDirectories


{
public:
// Retrieves an array of all directories in the store, and
// displays the results.
static void Main()
{
// This part of the code sets up a few directories and files in the
// store.
IsolatedStorageFile^ isoStore = IsolatedStorageFile::GetStore(IsolatedStorageScope::User |
IsolatedStorageScope::Assembly, (Type ^)nullptr, (Type ^)nullptr);
isoStore->CreateDirectory("TopLevelDirectory");
isoStore->CreateDirectory("TopLevelDirectory/SecondLevel");
isoStore->CreateDirectory("AnotherTopLevelDirectory/InsideDirectory");
gcnew IsolatedStorageFileStream("InTheRoot.txt", FileMode::Create, isoStore);
gcnew IsolatedStorageFileStream("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt",
gcnew IsolatedStorageFileStream("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt",
FileMode::Create, isoStore);
// End of setup.

Console::WriteLine('\r');
Console::WriteLine("Here is a list of all directories in this isolated store:");

for each (String^ directory in GetAllDirectories("*", isoStore))


{
Console::WriteLine(directory);
}
Console::WriteLine('\r');

// Retrieve all the files in the directory by calling the GetFiles


// method.

Console::WriteLine("Here is a list of all the files in this isolated store:");


for each (String^ file in GetAllFiles("*", isoStore))
{
Console::WriteLine(file);
}

} // End of Main.

// Method to retrieve all directories, recursively, within a store.


static List<String^>^ GetAllDirectories(String^ pattern, IsolatedStorageFile^ storeFile)
{
// Get the root of the search string.
String^ root = Path::GetDirectoryName(pattern);

if (root != "")
{
root += "/";
}

// Retrieve directories.
array<String^>^ directories = storeFile->GetDirectoryNames(pattern);

List<String^>^ directoryList = gcnew List<String^>(directories);

// Retrieve subdirectories of matches.


for (int i = 0, max = directories->Length; i < max; i++)
{
String^ directory = directoryList[i] + "/";
List<String^>^ more = GetAllDirectories (root + directory + "*", storeFile);

// For each subdirectory found, add in the base path.


for (int j = 0; j < more->Count; j++)
{
more[j] = directory + more[j];
}

// Insert the subdirectories into the list and


// update the counter and upper bound.
directoryList->InsertRange(i + 1, more);
i += more->Count;
max += more->Count;
}

return directoryList;
}

static List<String^>^ GetAllFiles(String^ pattern, IsolatedStorageFile^ storeFile)


{
// Get the root and file portions of the search string.
String^ fileString = Path::GetFileName(pattern);
array<String^>^ files = storeFile->GetFileNames(pattern);

List<String^>^ fileList = gcnew List<String^>(files);


// Loop through the subdirectories, collect matches,
// and make separators consistent.
for each (String^ directory in GetAllDirectories( "*", storeFile))
{
for each (String^ file in storeFile->GetFileNames(directory + "/" + fileString))
{
fileList->Add((directory + "/" + file));
}
}

return fileList;
} // End of GetFiles.
};

int main()
{
FindingExistingFilesAndDirectories::Main();
}

using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Collections;
using System.Collections.Generic;

public class FindingExistingFilesAndDirectories


{
// Retrieves an array of all directories in the store, and
// displays the results.
public static void Main()
{
// This part of the code sets up a few directories and files in the
// store.
IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Assembly, null, null);
isoStore.CreateDirectory("TopLevelDirectory");
isoStore.CreateDirectory("TopLevelDirectory/SecondLevel");
isoStore.CreateDirectory("AnotherTopLevelDirectory/InsideDirectory");
isoStore.CreateFile("InTheRoot.txt");
isoStore.CreateFile("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt");
// End of setup.

Console.WriteLine('\r');
Console.WriteLine("Here is a list of all directories in this isolated store:");

foreach (string directory in GetAllDirectories("*", isoStore))


{
Console.WriteLine(directory);
}
Console.WriteLine('\r');

// Retrieve all the files in the directory by calling the GetFiles


// method.

Console.WriteLine("Here is a list of all the files in this isolated store:");


foreach (string file in GetAllFiles("*", isoStore)){
Console.WriteLine(file);
}
} // End of Main.

// Method to retrieve all directories, recursively, within a store.


public static List<String> GetAllDirectories(string pattern, IsolatedStorageFile storeFile)
{
// Get the root of the search string.
string root = Path.GetDirectoryName(pattern);

if (root != "")
if (root != "")
{
root += "/";
}

// Retrieve directories.
List<String> directoryList = new List<String>(storeFile.GetDirectoryNames(pattern));

// Retrieve subdirectories of matches.


for (int i = 0, max = directoryList.Count; i < max; i++)
{
string directory = directoryList[i] + "/";
List<String> more = GetAllDirectories(root + directory + "*", storeFile);

// For each subdirectory found, add in the base path.


for (int j = 0; j < more.Count; j++)
{
more[j] = directory + more[j];
}

// Insert the subdirectories into the list and


// update the counter and upper bound.
directoryList.InsertRange(i + 1, more);
i += more.Count;
max += more.Count;
}

return directoryList;
}

public static List<String> GetAllFiles(string pattern, IsolatedStorageFile storeFile)


{
// Get the root and file portions of the search string.
string fileString = Path.GetFileName(pattern);

List<String> fileList = new List<String>(storeFile.GetFileNames(pattern));

// Loop through the subdirectories, collect matches,


// and make separators consistent.
foreach (string directory in GetAllDirectories("*", storeFile))
{
foreach (string file in storeFile.GetFileNames(directory + "/" + fileString))
{
fileList.Add((directory + "/" + file));
}
}

return fileList;
} // End of GetFiles.
}
Imports System.IO
Imports System.IO.IsolatedStorage
Imports System.Collections
Imports System.Collections.Generic

Public class FindingExistingFilesAndDirectories


' These arrayLists hold the directory and file names as they are found.

Private Shared directoryList As New List(Of String)


Private Shared fileList As New List(Of String)

' Retrieves an array of all directories in the store, and


' displays the results.

Public Shared Sub Main()


' This part of the code sets up a few directories and files in the store.
Dim isoStore As IsolatedStorageFile = IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or _
IsolatedStorageScope.Assembly Or IsolatedStorageScope.Domain, Nothing, Nothing)
isoStore.CreateDirectory("TopLevelDirectory")
isoStore.CreateDirectory("TopLevelDirectory/SecondLevel")
isoStore.CreateDirectory("AnotherTopLevelDirectory/InsideDirectory")
isoStore.CreateFile("InTheRoot.txt")
isoStore.CreateFile("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt")
' End of setup.

Console.WriteLine()
Console.WriteLine("Here is a list of all directories in this isolated store:")

GetAllDirectories("*", isoStore)
For Each directory As String In directoryList
Console.WriteLine(directory)
Next

Console.WriteLine()
Console.WriteLine("Retrieve all the files in the directory by calling the GetFiles method.")

GetAllFiles(isoStore)
For Each file As String In fileList
Console.WriteLine(file)
Next
End Sub

Public Shared Sub GetAllDirectories(ByVal pattern As String, ByVal storeFile As IsolatedStorageFile)


' Retrieve directories.
Dim directories As String() = storeFile.GetDirectoryNames(pattern)

For Each directory As String In directories


' Add the directory to the final list.
directoryList.Add((pattern.TrimEnd(CChar("*"))) + directory + "/")
' Call the method again using directory.
GetAllDirectories((pattern.TrimEnd(CChar("*")) + directory + "/*"), storeFile)
Next
End Sub

Public Shared Sub GetAllFiles(ByVal storefile As IsolatedStorageFile)


' This adds the root to the directory list.
directoryList.Add("*")
For Each directory As String In directoryList
Dim files As String() = storefile.GetFileNames(directory + "*")
For Each dirfile As String In files
fileList.Add(dirfile)
Next
Next
End Sub
End Class
Voir aussi
IsolatedStorageFile
Stockage isolé
Procédure : lire et écrire des fichiers dans un stockage
isolé
18/07/2020 • 2 minutes to read • Edit Online

Pour lire ou écrire dans un fichier dans un magasin isolé, utilisez un objet IsolatedStorageFileStream avec un lecteur
de flux (objet StreamReader) ou un writer de flux (objet StreamWriter).

Exemple
L’exemple de code suivant obtient un magasin isolé et vérifie l’existence d’un fichier intitulé TestStore.txt dans le
magasin. S’il n’existe pas, il crée le fichier et écrit « Hello Isolated Storage » dans le fichier. Si TestStore.txt existe déjà,
l’exemple de code lit le fichier.

using System;
using System.IO;
using System.IO.IsolatedStorage;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User |
IsolatedStorageScope.Assembly, null, null);

if (isoStore.FileExists("TestStore.txt"))
{
Console.WriteLine("The file already exists!");
using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream("TestStore.txt",
FileMode.Open, isoStore))
{
using (StreamReader reader = new StreamReader(isoStream))
{
Console.WriteLine("Reading contents:");
Console.WriteLine(reader.ReadToEnd());
}
}
}
else
{
using (IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream("TestStore.txt",
FileMode.CreateNew, isoStore))
{
using (StreamWriter writer = new StreamWriter(isoStream))
{
writer.WriteLine("Hello Isolated Storage");
Console.WriteLine("You have written to the file.");
}
}
}
}
}
}
Imports System.IO
Imports System.IO.IsolatedStorage

Module Module1

Sub Main()
Dim isoStore As IsolatedStorageFile = IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or
IsolatedStorageScope.Assembly, Nothing, Nothing)

If (isoStore.FileExists("TestStore.txt")) Then
Console.WriteLine("The file already exists!")
Using isoStream As IsolatedStorageFileStream = New IsolatedStorageFileStream("TestStore.txt",
FileMode.Open, isoStore)
Using reader As StreamReader = New StreamReader(isoStream)
Console.WriteLine("Reading contents:")
Console.WriteLine(reader.ReadToEnd())
End Using
End Using
Else
Using isoStream As IsolatedStorageFileStream = New IsolatedStorageFileStream("TestStore.txt",
FileMode.CreateNew, isoStore)
Using writer As StreamWriter = New StreamWriter(isoStream)
writer.WriteLine("Hello Isolated Storage")
Console.WriteLine("You have written to the file.")
End Using
End Using
End If
End Sub

End Module

Voir aussi
IsolatedStorageFile
IsolatedStorageFileStream
System.IO.FileMode
System.IO.FileAccess
System.IO.StreamReader
System.IO.StreamWriter
E/s de fichier et de flux
Stockage isolé
Procédure : supprimer des fichiers et des répertoires
dans un stockage isolé
18/07/2020 • 4 minutes to read • Edit Online

Vous pouvez supprimer des répertoires et des fichiers dans un fichier de stockage isolé. Dans un magasin, les
noms de répertoires et de fichiers dépendent du système d’exploitation et sont spécifiées par rapport à la racine du
système de fichiers virtuel. Ils ne sont pas sensibles à la casse sur les systèmes d’exploitation Windows.
La classe System.IO.IsolatedStorage.IsolatedStorageFile fournit deux méthodes pour supprimer des répertoires et
des fichiers : DeleteDirectory et DeleteFile. Une exception IsolatedStorageException est levée si vous essayez de
supprimer un fichier ou un répertoire qui n’existe pas. Si vous incluez un caractère générique dans le nom,
DeleteDirectory lève une exception IsolatedStorageException et DeleteFile lève une exception ArgumentException.
La méthode DeleteDirectory échoue si le répertoire contient des fichiers ou des sous-répertoires. Vous pouvez
utiliser les méthodes GetFileNames et GetDirectoryNames pour récupérer les fichiers et les répertoires existants.
Pour plus d’informations sur la recherche dans le système de fichiers virtuel d’un magasin, consultez Comment :
rechercher des fichiers et des répertoires existants dans un stockage isolé.

Exemple
L’exemple de code suivant crée et supprime ensuite plusieurs fichiers et répertoires.

using namespace System;


using namespace System::IO::IsolatedStorage;
using namespace System::IO;

public ref class DeletingFilesDirectories


{
public:
static void Main()
{
// Get a new isolated store for this user domain and assembly.
// Put the store into an isolatedStorageFile object.

IsolatedStorageFile^ isoStore = IsolatedStorageFile::GetStore(IsolatedStorageScope::User |


IsolatedStorageScope::Domain | IsolatedStorageScope::Assembly, (Type ^)nullptr, (Type ^)nullptr);

Console::WriteLine("Creating Directories:");

// This code creates several different directories.

isoStore->CreateDirectory("TopLevelDirectory");
Console::WriteLine("TopLevelDirectory");
isoStore->CreateDirectory("TopLevelDirectory/SecondLevel");
Console::WriteLine("TopLevelDirectory/SecondLevel");

// This code creates two new directories, one inside the other.

isoStore->CreateDirectory("AnotherTopLevelDirectory/InsideDirectory");
Console::WriteLine("AnotherTopLevelDirectory/InsideDirectory");
Console::WriteLine();

// This code creates a few files and places them in the directories.

Console::WriteLine("Creating Files:");

// This file is placed in the root.


IsolatedStorageFileStream^ isoStream1 = gcnew IsolatedStorageFileStream("InTheRoot.txt",
FileMode::Create, isoStore);
Console::WriteLine("InTheRoot.txt");

isoStream1->Close();

// This file is placed in the InsideDirectory.

IsolatedStorageFileStream^ isoStream2 = gcnew IsolatedStorageFileStream(


"AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt", FileMode::Create, isoStore);
Console::WriteLine("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt");
Console::WriteLine();

isoStream2->Close();

Console::WriteLine("Deleting File:");

// This code deletes the HereIAm.txt file.


isoStore->DeleteFile("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt");
Console::WriteLine("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt");
Console::WriteLine();

Console::WriteLine("Deleting Directory:");

// This code deletes the InsideDirectory.

isoStore->DeleteDirectory("AnotherTopLevelDirectory/InsideDirectory/");
Console::WriteLine("AnotherTopLevelDirectory/InsideDirectory/");
Console::WriteLine();

} // End of main.
};

int main()
{
DeletingFilesDirectories::Main();
}
using System;
using System.IO.IsolatedStorage;
using System.IO;

public class DeletingFilesDirectories


{
public static void Main()
{
// Get a new isolated store for this user domain and assembly.
// Put the store into an isolatedStorageFile object.

IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User |


IsolatedStorageScope.Domain | IsolatedStorageScope.Assembly, null, null);

Console.WriteLine("Creating Directories:");

// This code creates several different directories.

isoStore.CreateDirectory("TopLevelDirectory");
Console.WriteLine("TopLevelDirectory");
isoStore.CreateDirectory("TopLevelDirectory/SecondLevel");
Console.WriteLine("TopLevelDirectory/SecondLevel");

// This code creates two new directories, one inside the other.

isoStore.CreateDirectory("AnotherTopLevelDirectory/InsideDirectory");
Console.WriteLine("AnotherTopLevelDirectory/InsideDirectory");
Console.WriteLine();

// This code creates a few files and places them in the directories.

Console.WriteLine("Creating Files:");

// This file is placed in the root.

IsolatedStorageFileStream isoStream1 = new IsolatedStorageFileStream("InTheRoot.txt",


FileMode.Create, isoStore);
Console.WriteLine("InTheRoot.txt");

isoStream1.Close();

// This file is placed in the InsideDirectory.

IsolatedStorageFileStream isoStream2 = new IsolatedStorageFileStream(


"AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt", FileMode.Create, isoStore);
Console.WriteLine("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt");
Console.WriteLine();

isoStream2.Close();

Console.WriteLine("Deleting File:");

// This code deletes the HereIAm.txt file.


isoStore.DeleteFile("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt");
Console.WriteLine("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt");
Console.WriteLine();

Console.WriteLine("Deleting Directory:");

// This code deletes the InsideDirectory.

isoStore.DeleteDirectory("AnotherTopLevelDirectory/InsideDirectory/");
Console.WriteLine("AnotherTopLevelDirectory/InsideDirectory/");
Console.WriteLine();
} // End of main.
}
Imports System.IO.IsolatedStorage
Imports System.IO

Public Class DeletingFilesDirectories


Public Shared Sub Main()
' Get a new isolated store for this user domain and assembly.
' Put the store into an isolatedStorageFile object.

Dim isoStore As IsolatedStorageFile = IsolatedStorageFile.GetStore(IsolatedStorageScope.User Or


IsolatedStorageScope.Domain Or IsolatedStorageScope.Assembly, Nothing, Nothing)

Console.WriteLine("Creating Directories:")

' This code creates several different directories.

isoStore.CreateDirectory("TopLevelDirectory")
Console.WriteLine("TopLevelDirectory")
isoStore.CreateDirectory("TopLevelDirectory/SecondLevel")
Console.WriteLine("TopLevelDirectory/SecondLevel")

' This code creates two new directories, one inside the other.

isoStore.CreateDirectory("AnotherTopLevelDirectory/InsideDirectory")
Console.WriteLine("AnotherTopLevelDirectory/InsideDirectory")
Console.WriteLine()

' This code creates a few files and places them in the directories.

Console.WriteLine("Creating Files:")

' This file is placed in the root.

Dim isoStream1 As New IsolatedStorageFileStream("InTheRoot.txt", FileMode.Create, isoStore)


Console.WriteLine("InTheRoot.txt")

isoStream1.Close()

' This file is placed in the InsideDirectory.

Dim isoStream2 As New IsolatedStorageFileStream(


"AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt", FileMode.Create, isoStore)
Console.WriteLine("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt")
Console.WriteLine()

isoStream2.Close()

Console.WriteLine("Deleting File:")

' This code deletes the HereIAm.txt file.


isoStore.DeleteFile("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt")
Console.WriteLine("AnotherTopLevelDirectory/InsideDirectory/HereIAm.txt")
Console.WriteLine()

Console.WriteLine("Deleting Directory:")

' This code deletes the InsideDirectory.

isoStore.DeleteDirectory("AnotherTopLevelDirectory/InsideDirectory/")
Console.WriteLine("AnotherTopLevelDirectory/InsideDirectory/")
Console.WriteLine()

End Sub
End Class

Voir aussi
System.IO.IsolatedStorage.IsolatedStorageFile
Stockage isolé
Opérations de canal dans .NET
18/07/2020 • 2 minutes to read • Edit Online

Les canaux sont un moyen de communication entre processus. Il existe deux types de canaux :
Canaux anonymes.
Les canaux anonymes fournissent une communication entre processus sur un ordinateur local. Les canaux
anonymes nécessitent moins de traitement que les canaux nommés, mais ils offrent des services limités. Ils
sont unidirectionnels et ne peuvent pas être utilisés sur un réseau. Ils prennent seulement en charge une
instance de serveur. Les canaux anonymes sont utiles pour la communication entre threads, ou entre
processus parents et enfants où les handles de canaux peuvent être facilement passés au processus enfant
durant sa création.
Dans .NET, l’implémentation de canaux anonymes s’effectue avec les classes AnonymousPipeServerStream
et AnonymousPipeClientStream.
Consultez Guide pratique pour utiliser des canaux anonymes pour la communication entre processus en
local.
Canaux nommés.
Les canaux nommés fournissent la communication entre un serveur de canaux et un ou plusieurs clients de
canaux. Ils peuvent être unidirectionnels ou en duplex. Ils prennent en charge la communication basée sur
les messages et permettent à plusieurs clients de se connecter simultanément au processus serveur à l’aide
du même nom de canal. Les canaux nommés prennent également en charge l’emprunt d’identité, ce qui
permet aux processus de connexion d’utiliser leurs propres autorisations sur des serveurs distants.
Dans .NET, l’implémentation de canaux nommés s’effectue avec les classes NamedPipeServerStream et
NamedPipeClientStream.
Consultez Comment : utiliser des canaux nommés pour la communication entre processus en réseau.

Voir aussi
E/s de fichier et de flux
Procédure : utiliser des canaux anonymes pour la communication entre processus en local
Procédure : utiliser des canaux nommés pour la communication entre processus en réseau
Procédure : utiliser des canaux anonymes pour la
communication entre processus en local
18/07/2020 • 6 minutes to read • Edit Online

Les canaux anonymes fournissent une communication entre processus sur un ordinateur local. Ils offrent moins de
fonctionnalités que les canaux nommés, mais nécessitent également moins de surcharge. Vous pouvez utiliser les
canaux anonymes pour établir plus facilement une communication entre processus sur un ordinateur local. Vous
ne pouvez pas utiliser les canaux anonymes pour la communication sur un réseau.
Pour implémenter des canaux anonymes, utilisez les classes AnonymousPipeServerStream et
AnonymousPipeClientStream.

Exemple
L’exemple suivant montre une façon d’envoyer une chaîne à partir d’un processus parent à un processus enfant à
l’aide de canaux anonymes. Cet exemple crée un objet AnonymousPipeServerStream dans un processus parent
avec une valeur PipeDirection de Out. Le processus parent crée ensuite un processus enfant à l’aide d’un handle
client pour créer un objet AnonymousPipeClientStream. Le processus enfant a une valeur PipeDirection de In.
Le processus parent envoie ensuite une chaîne fournie par l’utilisateur au processus enfant. La chaîne est affichée
sur la console dans le processus enfant.
L’exemple suivant présente le processus serveur.
#using <System.dll>
#using <System.Core.dll>

using namespace System;


using namespace System::IO;
using namespace System::IO::Pipes;
using namespace System::Diagnostics;

ref class PipeServer


{
public:
static void Main()
{
Process^ pipeClient = gcnew Process();

pipeClient->StartInfo->FileName = "pipeClient.exe";

AnonymousPipeServerStream^ pipeServer =
gcnew AnonymousPipeServerStream(PipeDirection::Out,
HandleInheritability::Inheritable);

Console::WriteLine("[SERVER] Current TransmissionMode: {0}.",


pipeServer->TransmissionMode);

// Pass the client process a handle to the server.


pipeClient->StartInfo->Arguments =
pipeServer->GetClientHandleAsString();
pipeClient->StartInfo->UseShellExecute = false;
pipeClient->Start();

pipeServer->DisposeLocalCopyOfClientHandle();

try
{
// Read user input and send that to the client process.
StreamWriter^ sw = gcnew StreamWriter(pipeServer);

sw->AutoFlush = true;
// Send a 'sync message' and wait for client to receive it.
sw->WriteLine("SYNC");
pipeServer->WaitForPipeDrain();
// Send the console input to the client process.
Console::Write("[SERVER] Enter text: ");
sw->WriteLine(Console::ReadLine());
sw->Close();
}
// Catch the IOException that is raised if the pipe is broken
// or disconnected.
catch (IOException^ e)
{
Console::WriteLine("[SERVER] Error: {0}", e->Message);
}
pipeServer->Close();
pipeClient->WaitForExit();
pipeClient->Close();
Console::WriteLine("[SERVER] Client quit. Server terminating.");
}
};

int main()
{
PipeServer::Main();
}
using System;
using System.IO;
using System.IO.Pipes;
using System.Diagnostics;

class PipeServer
{
static void Main()
{
Process pipeClient = new Process();

pipeClient.StartInfo.FileName = "pipeClient.exe";

using (AnonymousPipeServerStream pipeServer =


new AnonymousPipeServerStream(PipeDirection.Out,
HandleInheritability.Inheritable))
{
Console.WriteLine("[SERVER] Current TransmissionMode: {0}.",
pipeServer.TransmissionMode);

// Pass the client process a handle to the server.


pipeClient.StartInfo.Arguments =
pipeServer.GetClientHandleAsString();
pipeClient.StartInfo.UseShellExecute = false;
pipeClient.Start();

pipeServer.DisposeLocalCopyOfClientHandle();

try
{
// Read user input and send that to the client process.
using (StreamWriter sw = new StreamWriter(pipeServer))
{
sw.AutoFlush = true;
// Send a 'sync message' and wait for client to receive it.
sw.WriteLine("SYNC");
pipeServer.WaitForPipeDrain();
// Send the console input to the client process.
Console.Write("[SERVER] Enter text: ");
sw.WriteLine(Console.ReadLine());
}
}
// Catch the IOException that is raised if the pipe is broken
// or disconnected.
catch (IOException e)
{
Console.WriteLine("[SERVER] Error: {0}", e.Message);
}
}

pipeClient.WaitForExit();
pipeClient.Close();
Console.WriteLine("[SERVER] Client quit. Server terminating.");
}
}
Imports System.IO
Imports System.IO.Pipes
Imports System.Diagnostics

Class PipeServer
Shared Sub Main()
Dim pipeClient As New Process()

pipeClient.StartInfo.FileName = "pipeClient.exe"

Using pipeServer As New AnonymousPipeServerStream(PipeDirection.Out, _


HandleInheritability.Inheritable)

Console.WriteLine("[SERVER] Current TransmissionMode: {0}.",


pipeServer.TransmissionMode)

' Pass the client process a handle to the server.


pipeClient.StartInfo.Arguments = pipeServer.GetClientHandleAsString()
pipeClient.StartInfo.UseShellExecute = false
pipeClient.Start()

pipeServer.DisposeLocalCopyOfClientHandle()

Try
' Read user input and send that to the client process.
Using sw As New StreamWriter(pipeServer)
sw.AutoFlush = true
' Send a 'sync message' and wait for client to receive it.
sw.WriteLine("SYNC")
pipeServer.WaitForPipeDrain()
' Send the console input to the client process.
Console.Write("[SERVER] Enter text: ")
sw.WriteLine(Console.ReadLine())
End Using
Catch e As IOException
' Catch the IOException that is raised if the pipe is broken
' or disconnected.
Console.WriteLine("[SERVER] Error: {0}", e.Message)
End Try
End Using

pipeClient.WaitForExit()
pipeClient.Close()
Console.WriteLine("[SERVER] Client quit. Server terminating.")
End Sub
End Class

Si vous souhaitez voir les commentaires de code traduits dans des langues autres que l’anglais, faites-le nous
savoir dans ce problème de discussion GitHub.

Exemple
L’exemple suivant présente le processus client. Le processus serveur démarre le processus client et donne à ce
processus un handle client. Le fichier exécutable obtenu à partir du code client doit être nommé pipeClient.exe et
être copié dans le même répertoire que l’exécutable serveur avant d’exécuter le processus serveur.
#using <System.Core.dll>

using namespace System;


using namespace System::IO;
using namespace System::IO::Pipes;

ref class PipeClient


{
public:
static void Main(array<String^>^ args)
{
if (args->Length > 1)
{
PipeStream^ pipeClient = gcnew AnonymousPipeClientStream(PipeDirection::In, args[1]);

Console::WriteLine("[CLIENT] Current TransmissionMode: {0}.",


pipeClient->TransmissionMode);

StreamReader^ sr = gcnew StreamReader(pipeClient);

// Display the read text to the console


String^ temp;

// Wait for 'sync message' from the server.


do
{
Console::WriteLine("[CLIENT] Wait for sync...");
temp = sr->ReadLine();
}
while (!temp->StartsWith("SYNC"));

// Read the server data and echo to the console.


while ((temp = sr->ReadLine()) != nullptr)
{
Console::WriteLine("[CLIENT] Echo: " + temp);
}
sr->Close();
pipeClient->Close();
}
Console::Write("[CLIENT] Press Enter to continue...");
Console::ReadLine();
}
};

int main()
{
array<String^>^ args = Environment::GetCommandLineArgs();
PipeClient::Main(args);
}
using System;
using System.IO;
using System.IO.Pipes;

class PipeClient
{
static void Main(string[] args)
{
if (args.Length > 0)
{
using (PipeStream pipeClient =
new AnonymousPipeClientStream(PipeDirection.In, args[0]))
{
Console.WriteLine("[CLIENT] Current TransmissionMode: {0}.",
pipeClient.TransmissionMode);

using (StreamReader sr = new StreamReader(pipeClient))


{
// Display the read text to the console
string temp;

// Wait for 'sync message' from the server.


do
{
Console.WriteLine("[CLIENT] Wait for sync...");
temp = sr.ReadLine();
}
while (!temp.StartsWith("SYNC"));

// Read the server data and echo to the console.


while ((temp = sr.ReadLine()) != null)
{
Console.WriteLine("[CLIENT] Echo: " + temp);
}
}
}
}
Console.Write("[CLIENT] Press Enter to continue...");
Console.ReadLine();
}
}
Imports System.IO
Imports System.IO.Pipes

Class PipeClient
Shared Sub Main(args() as String)
If args.Length > 0 Then
Using pipeClient As New AnonymousPipeClientStream(PipeDirection.In, args(0))
Console.WriteLine("[CLIENT] Current TransmissionMode: {0}.", _
pipeClient.TransmissionMode)

Using sr As New StreamReader(pipeClient)


' Display the read text to the console
Dim temp As String

' Wait for 'sync message' from the server.


Do
Console.WriteLine("[CLIENT] Wait for sync...")
temp = sr.ReadLine()
Loop While temp.StartsWith("SYNC") = False

' Read the server data and echo to the console.


temp = sr.ReadLine()
While Not temp = Nothing
Console.WriteLine("[CLIENT] Echo: " + temp)
temp = sr.ReadLine()
End While
End Using
End Using
End If
Console.Write("[CLIENT] Press Enter to continue...")
Console.ReadLine()
End Sub
End Class

Voir aussi
Canaux
Procédure : utiliser des canaux nommés pour la communication entre processus en réseau
Procédure : utiliser des canaux nommés pour la
communication entre processus en réseau
18/07/2020 • 13 minutes to read • Edit Online

Les canaux nommés fournissent la communication entre un serveur de canaux et un ou plusieurs clients de
canaux. Ils offrent plus de fonctionnalités que les canaux anonymes, qui fournissent la communication entre
processus sur un ordinateur local. Les canaux nommés prennent en charge la communication en duplex intégrale
sur un réseau et plusieurs instances de serveur, la communication basée sur message et l’emprunt d’identité du
client, ce qui permet aux processus de connexion d’utiliser leur propre jeu d’autorisations sur des serveurs
distants.
Pour implémenter des canaux nommés, utilisez les classes NamedPipeServerStream et NamedPipeClientStream.

Exemple
L’exemple suivant illustre la création d’un canal nommé à l’aide de la classe NamedPipeServerStream. Dans cet
exemple, le processus serveur crée quatre threads. Chaque thread peut accepter une connexion cliente. Le
processus client connecté fournit ensuite au serveur un nom de fichier. Si le client dispose des autorisations
suffisantes, le processus serveur ouvre le fichier et renvoie son contenu au client.

#using <System.Core.dll>

using namespace System;


using namespace System::IO;
using namespace System::IO::Pipes;
using namespace System::Text;
using namespace System::Threading;

// Defines the data protocol for reading and writing strings on our stream
public ref class StreamString
{
private:
Stream^ ioStream;
UnicodeEncoding^ streamEncoding;

public:
StreamString(Stream^ ioStream)
{
this->ioStream = ioStream;
streamEncoding = gcnew UnicodeEncoding();
}

String^ ReadString()
{
int len;

len = ioStream->ReadByte() * 256;


len += ioStream->ReadByte();
array<Byte>^ inBuffer = gcnew array<Byte>(len);
ioStream->Read(inBuffer, 0, len);

return streamEncoding->GetString(inBuffer);
}

int WriteString(String^ outString)


{
array<Byte>^ outBuffer = streamEncoding->GetBytes(outString);
int len = outBuffer->Length;
int len = outBuffer->Length;
if (len > UInt16::MaxValue)
{
len = (int)UInt16::MaxValue;
}
ioStream->WriteByte((Byte)(len / 256));
ioStream->WriteByte((Byte)(len & 255));
ioStream->Write(outBuffer, 0, len);
ioStream->Flush();

return outBuffer->Length + 2;
}
};

// Contains the method executed in the context of the impersonated user


public ref class ReadFileToStream
{
private:
String^ fn;
StreamString ^ss;

public:
ReadFileToStream(StreamString^ str, String^ filename)
{
fn = filename;
ss = str;
}

void Start()
{
String^ contents = File::ReadAllText(fn);
ss->WriteString(contents);
}
};

public ref class PipeServer


{
private:
static int numThreads = 4;

public:
static void Main()
{
int i;
array<Thread^>^ servers = gcnew array<Thread^>(numThreads);

Console::WriteLine("\n*** Named pipe server stream with impersonation example ***\n");


Console::WriteLine("Waiting for client connect...\n");
for (i = 0; i < numThreads; i++)
{
servers[i] = gcnew Thread(gcnew ThreadStart(&ServerThread));
servers[i]->Start();
}
Thread::Sleep(250);
while (i > 0)
{
for (int j = 0; j < numThreads; j++)
{
if (servers[j] != nullptr)
{
if (servers[j]->Join(250))
{
Console::WriteLine("Server thread[{0}] finished.", servers[j]->ManagedThreadId);
servers[j] = nullptr;
i--; // decrement the thread watch count
}
}
}
}
Console::WriteLine("\nServer threads exhausted, exiting.");
}

private:
static void ServerThread()
{
NamedPipeServerStream^ pipeServer =
gcnew NamedPipeServerStream("testpipe", PipeDirection::InOut, numThreads);

int threadId = Thread::CurrentThread->ManagedThreadId;

// Wait for a client to connect


pipeServer->WaitForConnection();

Console::WriteLine("Client connected on thread[{0}].", threadId);


try
{
// Read the request from the client. Once the client has
// written to the pipe its security token will be available.

StreamString^ ss = gcnew StreamString(pipeServer);

// Verify our identity to the connected client using a


// string that the client anticipates.

ss->WriteString("I am the one true server!");


String^ filename = ss->ReadString();

// Read in the contents of the file while impersonating the client.


ReadFileToStream^ fileReader = gcnew ReadFileToStream(ss, filename);

// Display the name of the user we are impersonating.


Console::WriteLine("Reading file: {0} on thread[{1}] as user: {2}.",
filename, threadId, pipeServer->GetImpersonationUserName());
pipeServer->RunAsClient(gcnew PipeStreamImpersonationWorker(fileReader,
&ReadFileToStream::Start));
}
// Catch the IOException that is raised if the pipe is broken
// or disconnected.
catch (IOException^ e)
{
Console::WriteLine("ERROR: {0}", e->Message);
}
pipeServer->Close();
}
};

int main()
{
PipeServer::Main();
}

using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;

public class PipeServer


{
private static int numThreads = 4;

public static void Main()


{
int i;
Thread[] servers = new Thread[numThreads];

Console.WriteLine("\n*** Named pipe server stream with impersonation example ***\n");


Console.WriteLine("\n*** Named pipe server stream with impersonation example ***\n");
Console.WriteLine("Waiting for client connect...\n");
for (i = 0; i < numThreads; i++)
{
servers[i] = new Thread(ServerThread);
servers[i].Start();
}
Thread.Sleep(250);
while (i > 0)
{
for (int j = 0; j < numThreads; j++)
{
if (servers[j] != null)
{
if (servers[j].Join(250))
{
Console.WriteLine("Server thread[{0}] finished.", servers[j].ManagedThreadId);
servers[j] = null;
i--; // decrement the thread watch count
}
}
}
}
Console.WriteLine("\nServer threads exhausted, exiting.");
}

private static void ServerThread(object data)


{
NamedPipeServerStream pipeServer =
new NamedPipeServerStream("testpipe", PipeDirection.InOut, numThreads);

int threadId = Thread.CurrentThread.ManagedThreadId;

// Wait for a client to connect


pipeServer.WaitForConnection();

Console.WriteLine("Client connected on thread[{0}].", threadId);


try
{
// Read the request from the client. Once the client has
// written to the pipe its security token will be available.

StreamString ss = new StreamString(pipeServer);

// Verify our identity to the connected client using a


// string that the client anticipates.

ss.WriteString("I am the one true server!");


string filename = ss.ReadString();

// Read in the contents of the file while impersonating the client.


ReadFileToStream fileReader = new ReadFileToStream(ss, filename);

// Display the name of the user we are impersonating.


Console.WriteLine("Reading file: {0} on thread[{1}] as user: {2}.",
filename, threadId, pipeServer.GetImpersonationUserName());
pipeServer.RunAsClient(fileReader.Start);
}
// Catch the IOException that is raised if the pipe is broken
// or disconnected.
catch (IOException e)
{
Console.WriteLine("ERROR: {0}", e.Message);
}
pipeServer.Close();
}
}

// Defines the data protocol for reading and writing strings on our stream
public class StreamString
{
private Stream ioStream;
private UnicodeEncoding streamEncoding;

public StreamString(Stream ioStream)


{
this.ioStream = ioStream;
streamEncoding = new UnicodeEncoding();
}

public string ReadString()


{
int len = 0;

len = ioStream.ReadByte() * 256;


len += ioStream.ReadByte();
byte[] inBuffer = new byte[len];
ioStream.Read(inBuffer, 0, len);

return streamEncoding.GetString(inBuffer);
}

public int WriteString(string outString)


{
byte[] outBuffer = streamEncoding.GetBytes(outString);
int len = outBuffer.Length;
if (len > UInt16.MaxValue)
{
len = (int)UInt16.MaxValue;
}
ioStream.WriteByte((byte)(len / 256));
ioStream.WriteByte((byte)(len & 255));
ioStream.Write(outBuffer, 0, len);
ioStream.Flush();

return outBuffer.Length + 2;
}
}

// Contains the method executed in the context of the impersonated user


public class ReadFileToStream
{
private string fn;
private StreamString ss;

public ReadFileToStream(StreamString str, string filename)


{
fn = filename;
ss = str;
}

public void Start()


{
string contents = File.ReadAllText(fn);
ss.WriteString(contents);
}
}

Imports System.IO
Imports System.IO.Pipes
Imports System.Text
Imports System.Threading

Public Class PipeServer


Private Shared numThreads As Integer = 4
Public Shared Sub Main()
Dim i As Integer
Dim servers(numThreads) As Thread

Console.WriteLine(vbNewLine + "*** Named pipe server stream with impersonation example ***" +
vbNewLine)
Console.WriteLine("Waiting for client connect..." + vbNewLine)
For i = 0 To numThreads - 1
servers(i) = New Thread(AddressOf ServerThread)
servers(i).Start()
Next i
Thread.Sleep(250)
While i > 0
For j As Integer = 0 To numThreads - 1
If Not (servers(j) Is Nothing) Then
if servers(j).Join(250)
Console.WriteLine("Server thread[{0}] finished.", servers(j).ManagedThreadId)
servers(j) = Nothing
i -= 1 ' decrement the thread watch count
End If
End If
Next j
End While
Console.WriteLine(vbNewLine + "Server threads exhausted, exiting.")
End Sub

Private Shared Sub ServerThread(data As Object)


Dim pipeServer As New _
NamedPipeServerStream("testpipe", PipeDirection.InOut, numThreads)

Dim threadId As Integer = Thread.CurrentThread.ManagedThreadId

' Wait for a client to connect


pipeServer.WaitForConnection()

Console.WriteLine("Client connected on thread[{0}].", threadId)


Try
' Read the request from the client. Once the client has
' written to the pipe its security token will be available.

Dim ss As new StreamString(pipeServer)

' Verify our identity to the connected client using a


' string that the client anticipates.

ss.WriteString("I am the one true server!")


Dim filename As String = ss.ReadString()

' Read in the contents of the file while impersonating the client.
Dim fileReader As New ReadFileToStream(ss, filename)

' Display the name of the user we are impersonating.


Console.WriteLine("Reading file: {0} on thread[{1}] as user: {2}.",
filename, threadId, pipeServer.GetImpersonationUserName())
pipeServer.RunAsClient(AddressOf fileReader.Start)
' Catch the IOException that is raised if the pipe is broken
' or disconnected.
Catch e As IOException
Console.WriteLine("ERROR: {0}", e.Message)
End Try
pipeServer.Close()
End Sub
End Class

' Defines the data protocol for reading and writing strings on our stream
Public Class StreamString
Private ioStream As Stream
Private streamEncoding As UnicodeEncoding
Public Sub New(ioStream As Stream)
Me.ioStream = ioStream
streamEncoding = New UnicodeEncoding(False, False)
End Sub

Public Function ReadString() As String


Dim len As Integer = 0
len = CType(ioStream.ReadByte(), Integer) * 256
len += CType(ioStream.ReadByte(), Integer)
Dim inBuffer As Array = Array.CreateInstance(GetType(Byte), len)
ioStream.Read(inBuffer, 0, len)

Return streamEncoding.GetString(inBuffer)
End Function

Public Function WriteString(outString As String) As Integer


Dim outBuffer() As Byte = streamEncoding.GetBytes(outString)
Dim len As Integer = outBuffer.Length
If len > UInt16.MaxValue Then
len = CType(UInt16.MaxValue, Integer)
End If
ioStream.WriteByte(CType(len \ 256, Byte))
ioStream.WriteByte(CType(len And 255, Byte))
ioStream.Write(outBuffer, 0, outBuffer.Length)
ioStream.Flush()

Return outBuffer.Length + 2
End Function
End Class

' Contains the method executed in the context of the impersonated user
Public Class ReadFileToStream
Private fn As String
Private ss As StreamString

Public Sub New(str As StreamString, filename As String)


fn = filename
ss = str
End Sub

Public Sub Start()


Dim contents As String = File.ReadAllText(fn)
ss.WriteString(contents)
End Sub
End Class

Exemple
L’exemple suivant présente le processus client, qui utilise la classe NamedPipeClientStream. Le client se connecte
au processus serveur et envoie un nom de fichier au serveur. L’exemple utilise l’emprunt d’identité, donc l’identité
qui exécute l’application cliente doit avoir l’autorisation d’accéder au fichier. Le serveur renvoie ensuite le contenu
du fichier au client. Le contenu du fichier est ensuite affiché sur la console.

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Security.Principal;
using System.Text;
using System.Threading;

public class PipeClient


{
private static int numClients = 4;
public static void Main(string[] args)
{
if (args.Length > 0)
{
if (args[0] == "spawnclient")
{
var pipeClient =
new NamedPipeClientStream(".", "testpipe",
PipeDirection.InOut, PipeOptions.None,
TokenImpersonationLevel.Impersonation);

Console.WriteLine("Connecting to server...\n");
pipeClient.Connect();

var ss = new StreamString(pipeClient);


// Validate the server's signature string.
if (ss.ReadString() == "I am the one true server!")
{
// The client security token is sent with the first write.
// Send the name of the file whose contents are returned
// by the server.
ss.WriteString("c:\\textfile.txt");

// Print the file to the screen.


Console.Write(ss.ReadString());
}
else
{
Console.WriteLine("Server could not be verified.");
}
pipeClient.Close();
// Give the client process some time to display results before exiting.
Thread.Sleep(4000);
}
}
else
{
Console.WriteLine("\n*** Named pipe client stream with impersonation example ***\n");
StartClients();
}
}

// Helper function to create pipe client processes


private static void StartClients()
{
string currentProcessName = Environment.CommandLine;
currentProcessName = Path.ChangeExtension(currentProcessName, ".exe");
Process[] plist = new Process[numClients];

Console.WriteLine("Spawning client processes...\n");

if (currentProcessName.Contains(Environment.CurrentDirectory))
{
currentProcessName = currentProcessName.Replace(Environment.CurrentDirectory, String.Empty);
}

// Remove extra characters when launched from Visual Studio


currentProcessName = currentProcessName.Replace("\\", String.Empty);
currentProcessName = currentProcessName.Replace("\"", String.Empty);

int i;
for (i = 0; i < numClients; i++)
{
// Start 'this' program but spawn a named pipe client.
plist[i] = Process.Start(currentProcessName, "spawnclient");
}
while (i > 0)
{
for (int j = 0; j < numClients; j++)
for (int j = 0; j < numClients; j++)
{
if (plist[j] != null)
{
if (plist[j].HasExited)
{
Console.WriteLine($"Client process[{plist[j].Id}] has exited.");
plist[j] = null;
i--; // decrement the process watch count
}
else
{
Thread.Sleep(250);
}
}
}
}
Console.WriteLine("\nClient processes finished, exiting.");
}
}

// Defines the data protocol for reading and writing strings on our stream.
public class StreamString
{
private Stream ioStream;
private UnicodeEncoding streamEncoding;

public StreamString(Stream ioStream)


{
this.ioStream = ioStream;
streamEncoding = new UnicodeEncoding();
}

public string ReadString()


{
int len;
len = ioStream.ReadByte() * 256;
len += ioStream.ReadByte();
var inBuffer = new byte[len];
ioStream.Read(inBuffer, 0, len);

return streamEncoding.GetString(inBuffer);
}

public int WriteString(string outString)


{
byte[] outBuffer = streamEncoding.GetBytes(outString);
int len = outBuffer.Length;
if (len > UInt16.MaxValue)
{
len = (int)UInt16.MaxValue;
}
ioStream.WriteByte((byte)(len / 256));
ioStream.WriteByte((byte)(len & 255));
ioStream.Write(outBuffer, 0, len);
ioStream.Flush();

return outBuffer.Length + 2;
}
}

Imports System.Diagnostics
Imports System.IO
Imports System.IO.Pipes
Imports System.Security.Principal
Imports System.Text
Imports System.Threading
Public Class PipeClient
Private Shared numClients As Integer = 4

Public Shared Sub Main(args() As String)


If args.Length > 0 Then
If args(0) = "spawnclient" Then
Dim pipeClient As New NamedPipeClientStream( _
".", "testpipe", _
PipeDirection.InOut, PipeOptions.None, _
TokenImpersonationLevel.Impersonation)

Console.WriteLine("Connecting to server..." + vbNewLine)


pipeClient.Connect()

Dim ss As New StreamString(pipeClient)


' Validate the server's signature string.
If ss.ReadString() = "I am the one true server!" Then
' The client security token is sent with the first write.
' Send the name of the file whose contents are returned
' by the server.
ss.WriteString("c:\textfile.txt")

' Print the file to the screen.


Console.Write(ss.ReadString())
Else
Console.WriteLine("Server could not be verified.")
End If
pipeClient.Close()
' Give the client process some time to display results before exiting.
Thread.Sleep(4000)
End If
Else
Console.WriteLine(vbNewLine + "*** Named pipe client stream with impersonation example ***" +
vbNewLine)
StartClients()
End If
End Sub

' Helper function to create pipe client processes


Private Shared Sub StartClients()
Dim currentProcessName As String = Environment.CommandLine
Dim plist(numClients - 1) As Process

Console.WriteLine("Spawning client processes..." + vbNewLine)

If currentProcessName.Contains(Environment.CurrentDirectory) Then
currentProcessName = currentProcessName.Replace(Environment.CurrentDirectory, String.Empty)
End If

' Remove extra characters when launched from Visual Studio.


currentProcessName = currentProcessName.Replace("\", String.Empty)
currentProcessName = currentProcessName.Replace("""", String.Empty)

' Change extension for .NET Core "dotnet run" returns the DLL, not the host exe.
currentProcessName = Path.ChangeExtension(currentProcessName, ".exe")

Dim i As Integer
For i = 0 To numClients - 1
' Start 'this' program but spawn a named pipe client.
plist(i) = Process.Start(currentProcessName, "spawnclient")
Next
While i > 0
For j As Integer = 0 To numClients - 1
If plist(j) IsNot Nothing Then
If plist(j).HasExited Then
Console.WriteLine($"Client process[{plist(j).Id}] has exited.")
plist(j) = Nothing
i -= 1 ' decrement the process watch count
Else
Else
Thread.Sleep(250)
End If
End If
Next
End While
Console.WriteLine(vbNewLine + "Client processes finished, exiting.")
End Sub
End Class

' Defines the data protocol for reading and writing strings on our stream
Public Class StreamString
Private ioStream As Stream
Private streamEncoding As UnicodeEncoding

Public Sub New(ioStream As Stream)


Me.ioStream = ioStream
streamEncoding = New UnicodeEncoding(False, False)
End Sub

Public Function ReadString() As String


Dim len As Integer = 0
len = CType(ioStream.ReadByte(), Integer) * 256
len += CType(ioStream.ReadByte(), Integer)
Dim inBuffer As Array = Array.CreateInstance(GetType(Byte), len)
ioStream.Read(inBuffer, 0, len)

Return streamEncoding.GetString(inBuffer)
End Function

Public Function WriteString(outString As String) As Integer


Dim outBuffer As Byte() = streamEncoding.GetBytes(outString)
Dim len As Integer = outBuffer.Length
If len > UInt16.MaxValue Then
len = CType(UInt16.MaxValue, Integer)
End If
ioStream.WriteByte(CType(len \ 256, Byte))
ioStream.WriteByte(CType(len And 255, Byte))
ioStream.Write(outBuffer, 0, outBuffer.Length)
ioStream.Flush()

Return outBuffer.Length + 2
End Function
End Class

Programmation fiable
Les processus client et serveur dans cet exemple sont censés être exécutés sur le même ordinateur, donc le nom
du serveur fourni à l’objet NamedPipeClientStream est "." . Si les processus client et serveur se trouvent sur des
ordinateurs distincts, "." est remplacé par le nom réseau de l’ordinateur qui exécute le processus serveur.

Voir aussi
TokenImpersonationLevel
GetImpersonationUserName
Canaux
Procédure : utiliser des canaux anonymes pour la communication entre processus en local
System.IO.Pipelines en .NET
16/04/2020 • 33 minutes to read • Edit Online

System.IO.Pipelinesest une nouvelle bibliothèque qui est conçu pour le rendre plus facile à faire haute performance
I / O en .NET. C’est une bibliothèque ciblant .NET Standard qui fonctionne sur toutes les implémentations .NET.

Quel problème le problème ne System.IO.Pipelines résoudre


Les applications qui analysent les données de streaming sont composées de code boilerplate ayant de nombreux
flux de code spécialisés et inhabituels. La plaque chauffante et le code de cas spécial est complexe et difficile à
entretenir.
System.IO.Pipelines a été conçu pour :
Avoir des données de diffusion en continu de haute performance.
Réduisez la complexité du code.
Le code suivant est typique pour un serveur TCP qui reçoit des '\n' messages délimités en ligne (délimités par)
d’un client :

async Task ProcessLinesAsync(NetworkStream stream)


{
var buffer = new byte[1024];
await stream.ReadAsync(buffer, 0, buffer.Length);

// Process a single line from the buffer


ProcessLine(buffer);
}

Le code précédent a plusieurs problèmes :


L’ensemble du message (fin de ligne) pourrait ReadAsync ne pas être reçu en un seul appel à .
C’est ignorer le stream.ReadAsync résultat de . stream.ReadAsync retourne la quantité de données lues.
Il ne gère pas le cas où ReadAsync plusieurs lignes sont lues en un seul appel.
Il alloue un byte tableau à chaque lecture.
Pour résoudre les problèmes précédents, les changements suivants sont nécessaires :
Tamponner les données entrantes jusqu’à ce qu’une nouvelle ligne soit trouvée.
Parse toutes les lignes retournées dans le tampon.
Il est possible que la ligne soit supérieure à 1 KB (1024 octets). Le code doit resize le tampon d’entrée jusqu’à
ce que le délimitateur soit trouvé afin d’adapter la ligne complète à l’intérieur du tampon.
Si le tampon est resized, plus de copies tampons sont faites à mesure que des lignes plus longues
apparaissent dans l’entrée.
Pour réduire l’espace gaspillé, compactez le tampon utilisé pour les lignes de lecture.
Envisagez d’utiliser la mise en commun des tampons pour éviter d’allouer la mémoire à plusieurs reprises.
Le code suivant traite de certains de ces problèmes :
async Task ProcessLinesAsync(NetworkStream stream)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(1024);
var bytesBuffered = 0;
var bytesConsumed = 0;

while (true)
{
// Calculate the amount of bytes remaining in the buffer.
var bytesRemaining = buffer.Length - bytesBuffered;

if (bytesRemaining == 0)
{
// Double the buffer size and copy the previously buffered data into the new buffer.
var newBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length * 2);
Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
// Return the old buffer to the pool.
ArrayPool<byte>.Shared.Return(buffer);
buffer = newBuffer;
bytesRemaining = buffer.Length - bytesBuffered;
}

var bytesRead = await stream.ReadAsync(buffer, bytesBuffered, bytesRemaining);


if (bytesRead == 0)
{
// EOF
break;
}

// Keep track of the amount of buffered bytes.


bytesBuffered += bytesRead;
var linePosition = -1;

do
{
// Look for a EOL in the buffered data.
linePosition = Array.IndexOf(buffer, (byte)'\n', bytesConsumed,
bytesBuffered - bytesConsumed);

if (linePosition >= 0)
{
// Calculate the length of the line based on the offset.
var lineLength = linePosition - bytesConsumed;

// Process the line.


ProcessLine(buffer, bytesConsumed, lineLength);

// Move the bytesConsumed to skip past the line consumed (including \n).
bytesConsumed += lineLength + 1;
}
}
while (linePosition >= 0);
}
}

Le code précédent est complexe et ne traite pas de tous les problèmes identifiés. Le réseautage haute performance
signifie généralement écrire du code très complexe pour maximiser les performances. System.IO.Pipelines a été
conçu pour faciliter l’écriture de ce type de code.
Si vous souhaitez voir les commentaires de code traduits dans des langues autres que l’anglais, faites-le nous
savoir dans ce problème de discussion GitHub.

Pipe
La Pipe classe peut être PipeWriter/PipeReader utilisée pour créer une paire. Toutes les données PipeWriter
inscrites PipeReader dans le est disponible dans le :

var pipe = new Pipe();


PipeReader reader = pipe.Reader;
PipeWriter writer = pipe.Writer;

Utilisation de base de pipe

async Task ProcessLinesAsync(Socket socket)


{
var pipe = new Pipe();
Task writing = FillPipeAsync(socket, pipe.Writer);
Task reading = ReadPipeAsync(pipe.Reader);

await Task.WhenAll(reading, writing);


}

async Task FillPipeAsync(Socket socket, PipeWriter writer)


{
const int minimumBufferSize = 512;

while (true)
{
// Allocate at least 512 bytes from the PipeWriter.
Memory<byte> memory = writer.GetMemory(minimumBufferSize);
try
{
int bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None);
if (bytesRead == 0)
{
break;
}
// Tell the PipeWriter how much was read from the Socket.
writer.Advance(bytesRead);
}
catch (Exception ex)
{
LogError(ex);
break;
}

// Make the data available to the PipeReader.


FlushResult result = await writer.FlushAsync();

if (result.IsCompleted)
{
break;
}
}

// By completing PipeWriter, tell the PipeReader that there's no more data coming.
await writer.CompleteAsync();
}

async Task ReadPipeAsync(PipeReader reader)


{
while (true)
{
ReadResult result = await reader.ReadAsync();
ReadOnlySequence<byte> buffer = result.Buffer;

while (TryReadLine(ref buffer, out ReadOnlySequence<byte> line))


{
// Process the line.
ProcessLine(line);
ProcessLine(line);
}

// Tell the PipeReader how much of the buffer has been consumed.
reader.AdvanceTo(buffer.Start, buffer.End);

// Stop reading if there's no more data coming.


if (result.IsCompleted)
{
break;
}
}

// Mark the PipeReader as complete.


await reader.CompleteAsync();
}

bool TryReadLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> line)


{
// Look for a EOL in the buffer.
SequencePosition? position = buffer.PositionOf((byte)'\n');

if (position == null)
{
line = default;
return false;
}

// Skip the line + the \n.


line = buffer.Slice(0, position.Value);
buffer = buffer.Slice(buffer.GetPosition(1, position.Value));
return true;
}

Il y a deux boucles :
FillPipeAsync lit de Socket la et PipeWriter écrit à la .
ReadPipeAsync lit à PipeReader partir des lignes entrantes et analyse.

Il n’y a pas de tampons explicites alloués. Toute gestion des tampons PipeReader PipeWriter est déléguée aux
mises en œuvre et aux mises en œuvre. La délégation de la gestion des tampons permet de se concentrer plus
facilement sur la logique métier.
Dans la première boucle:
PipeWriter.GetMemory(Int32)est appelé à obtenir la mémoire de l’écrivain sous-jacent.
PipeWriter.Advance(Int32)est appelé à PipeWriter dire combien de données ont été écrites au tampon.
PipeWriter.FlushAsyncest appelé à mettre les PipeReader données à la disposition du .

Dans la deuxième PipeReader boucle, le consomme PipeWriter les tampons écrits par . Les tampons proviennent
de la prise. L’appel PipeReader.ReadAsync à :
Renvoie ReadResult un qui contient deux éléments d’information importants :
Les données qui ont été ReadOnlySequence<byte> lues sous la forme de .
Un boolean IsCompleted qui indique si la fin des données (EOF) a été atteinte.

Après avoir trouvé la fin de la ligne (EOL) délimiter et d’analyser la ligne:


La logique traite le tampon pour sauter ce qui est déjà traité.
PipeReader.AdvanceTo est appelé à PipeReader dire combien de données ont été consommées et examinées.

Le lecteur et les boucles Complete d’écrivain se terminent par l’appel . Complete permet au Tuyau sous-jacent de
libérer la mémoire qu’il a allouée.
Rétropression et contrôle du débit
Idéalement, la lecture et l’analyse travaillent ensemble :
Le fil d’écriture consomme les données du réseau et les met dans des tampons.
Le fil d’analyse est responsable de la construction des structures de données appropriées.
En règle générale, l’analyse prend plus de temps que de simplement copier des blocs de données du réseau :
Le fil de lecture prend de l’avance sur le fil d’analyse.
Le fil de lecture doit soit ralentir ou allouer plus de mémoire pour stocker les données pour le fil d’analyse.
Pour des performances optimales, il y a un équilibre entre les pauses fréquentes et l’attribution de plus de mémoire.
Pour résoudre le problème Pipe précédent, le a deux paramètres pour contrôler le flux de données:
PauseWriterThreshold: Détermine la quantité de données qui FlushAsync doivent être tamponnées avant les
appels à faire une pause.
ResumeWriterThreshold: Détermine la quantité de données que PipeWriter.FlushAsync le lecteur doit observer
avant les appels à reprendre.

PipeWriter.FlushAsync:
Retourne une ValueTask<FlushResult> incomplet lorsque Pipe la PauseWriterThreshold quantité de données
dans les croix .
Se ValueTask<FlushResult> termine quand il ResumeWriterThreshold devient inférieur à .

Deux valeurs sont utilisées pour prévenir le cycle rapide, qui peut se produire si une valeur est utilisée.
Exemples

// The Pipe will start returning incomplete tasks from FlushAsync until
// the reader examines at least 5 bytes.
var options = new PipeOptions(pauseWriterThreshold: 10, resumeWriterThreshold: 5);
var pipe = new Pipe(options);

PipeScheduler PipeScheduler
Typiquement lors async await de l’utilisation et , code asynchrone reprend sur un TaskScheduler ou sur le courant
SynchronizationContext.
Lorsque vous faites I / O, il est important d’avoir un contrôle à grain fin sur l’endroit où l’I / O est effectué. Ce
contrôle permet de tirer parti efficacement des caches CPU. La mise en cache efficace est essentielle pour les
applications haute performance comme les serveurs Web. PipeSchedulerdonne un contrôle sur l’endroit où les
rappels asynchrones s’exécutent. Par défaut :
Le SynchronizationContext courant est utilisé.
S’il n’y a pas, SynchronizationContext il utilise le pool de thread pour exécuter des rappels.
public static void Main(string[] args)
{
var writeScheduler = new SingleThreadPipeScheduler();
var readScheduler = new SingleThreadPipeScheduler();

// Tell the Pipe what schedulers to use and disable the SynchronizationContext.
var options = new PipeOptions(readerScheduler: readScheduler,
writerScheduler: writeScheduler,
useSynchronizationContext: false);
var pipe = new Pipe(options);
}

// This is a sample scheduler that async callbacks on a single dedicated thread.


public class SingleThreadPipeScheduler : PipeScheduler
{
private readonly BlockingCollection<(Action<object> Action, object State)> _queue =
new BlockingCollection<(Action<object> Action, object State)>();
private readonly Thread _thread;

public SingleThreadPipeScheduler()
{
_thread = new Thread(DoWork);
_thread.Start();
}

private void DoWork()


{
foreach (var item in _queue.GetConsumingEnumerable())
{
item.Action(item.State);
}
}

public override void Schedule(Action<object> action, object state)


{
_queue.Add((action, state));
}
}

PipeScheduler.ThreadPool est PipeScheduler la mise en œuvre que les files d’attente rappelle à la piscine de fil.
PipeScheduler.ThreadPool est la valeur par défaut et généralement le meilleur choix. PipeScheduler.Inline peut
causer des conséquences imprévues telles que des impasses.
Réinitialisation de tuyau
Il est souvent efficace de Pipe réutiliser l’objet. Pour réinitialiser le PipeReader PipeWriter tuyau, appelez
PipeReader Reset lorsque le et sont terminés.

PipeReader PipeReader
PipeReadergère la mémoire au nom de l’appelant. Toujours PipeReader.AdvanceTo appeler
PipeReader.ReadAsyncaprès avoir appelé . Cela permet PipeReader de savoir quand l’appelant est fait avec la
mémoire afin qu’il puisse être suivi. Le ReadOnlySequence<byte> retour PipeReader.ReadAsync n’est valable
PipeReader.AdvanceTo que jusqu’à l’appel le . Il est illégal ReadOnlySequence<byte> d’utiliser après avoir appelé
PipeReader.AdvanceTo .

PipeReader.AdvanceTo prend SequencePosition deux arguments:


Le premier argument détermine la quantité de mémoire consommée.
Le deuxième argument détermine la quantité de tampon observée.
Le marquage des données consommées signifie que le tuyau peut retourner la mémoire dans le pool tampon sous-
jacent. Le marquage des données observées PipeReader.ReadAsync contrôle ce que le prochain appel à faire.
Marquer tout ce qui est PipeReader.ReadAsync observé signifie que le prochain appel à ne pas revenir jusqu’à ce
qu’il y ait plus de données écrites sur le tuyau. Toute autre valeur fera le PipeReader.ReadAsync prochain appel pour
revenir immédiatement avec les données observées et non observées, mais pas les données qui ont déjà été
consommées.
Lire les scénarios de données en streaming
Il ya un couple de modèles typiques qui émergent lorsque vous essayez de lire les données en streaming:
Compte tenu d’un flux de données, analysez un seul message.
Compte tenu d’un flux de données, analysez tous les messages disponibles.
Les exemples TryParseMessage suivants utilisent la méthode ReadOnlySequence<byte> pour analyser les messages à
partir d’un . TryParseMessage analyse un seul message et met à jour le tampon d’entrée pour couper le message
analysé à partir du tampon. TryParseMessage ne fait pas partie de .NET, c’est une méthode écrite par l’utilisateur
utilisée dans les sections suivantes.

bool TryParseMessage(ref ReadOnlySequence<byte> buffer, out Message message);

Lire un seul message


Le code suivant lit un PipeReader seul message à partir d’un et le renvoie à l’appelant.
async ValueTask<Message> ReadSingleMessageAsync(PipeReader reader,
CancellationToken cancellationToken = default)
{
while (true)
{
ReadResult result = await reader.ReadAsync(cancellationToken);
ReadOnlySequence<byte> buffer = result.Buffer;

// In the event that no message is parsed successfully, mark consumed


// as nothing and examined as the entire buffer.
SequencePosition consumed = buffer.Start;
SequencePosition examined = buffer.End;

try
{
if (TryParseMessage(ref buffer, out Message message))
{
// A single message was successfully parsed so mark the start as the
// parsed buffer as consumed. TryParseMessage trims the buffer to
// point to the data after the message was parsed.
consumed = buffer.Start;

// Examined is marked the same as consumed here, so the next call


// to ReadSingleMessageAsync will process the next message if there's
// one.
examined = consumed;

return message;
}

// There's no more data to be processed.


if (result.IsCompleted)
{
if (buffer.Length > 0)
{
// The message is incomplete and there's no more data to process.
throw new InvalidDataException("Incomplete message.");
}

break;
}
}
finally
{
reader.AdvanceTo(consumed, examined);
}
}

return null;
}

Le code précédent :
Parses un seul message.
Mises à SequencePosition jour SequencePosition de la consommation et examinée pour indiquer le début du
tampon d’entrée coupé.
Les SequencePosition deux arguments TryParseMessage sont mis à jour parce que supprime le message analysé du
tampon d’entrée. En général, lors de l’analyse d’un seul message à partir du tampon, la position examinée doit être
l’une des suivantes :
La fin du message.
La fin du tampon reçu si aucun message n’a été trouvé.
Le cas de message unique a le plus de potentiel d’erreurs. Passer les mauvaises valeurs à examiner peut entraîner
une exception hors mémoire ou une boucle infinie. Pour plus d’informations, consultez la section des problèmes
courants de PipeReader dans cet article.
Lecture de plusieurs messages
Le code suivant lit PipeReader tous ProcessMessageAsync les messages d’un et appelle sur chacun.

async Task ProcessMessagesAsync(PipeReader reader, CancellationToken cancellationToken = default)


{
try
{
while (true)
{
ReadResult result = await reader.ReadAsync(cancellationToken);
ReadOnlySequence<byte> buffer = result.Buffer;

try
{
// Process all messages from the buffer, modifying the input buffer on each
// iteration.
while (TryParseMessage(ref buffer, out Message message))
{
await ProcessMessageAsync(message);
}

// There's no more data to be processed.


if (result.IsCompleted)
{
if (buffer.Length > 0)
{
// The message is incomplete and there's no more data to process.
throw new InvalidDataException("Incomplete message.");
}
break;
}
}
finally
{
// Since all messages in the buffer are being processed, you can use the
// remaining buffer's Start and End position to determine consumed and examined.
reader.AdvanceTo(buffer.Start, buffer.End);
}
}
}
finally
{
await reader.CompleteAsync();
}
}

Annulation
PipeReader.ReadAsync :
Soutient l’adoption d’un CancellationToken.
Jette un OperationCanceledException si CancellationToken le est annulé alors qu’il ya une lecture en attente.
Prend en charge un moyen PipeReader.CancelPendingReadd’annuler l’opération de lecture en cours via , ce qui
évite de soulever une exception. L’appel PipeReader.CancelPendingRead provoque l’appel actuel ReadResult
IsCanceled ou true suivant pour PipeReader.ReadAsync retourner un avec réglé à . Cela peut être utile pour
arrêter la boucle de lecture existante d’une manière non destructive et non exceptionnelle.
private PipeReader reader;

public MyConnection(PipeReader reader)


{
this.reader = reader;
}

public void Abort()


{
// Cancel the pending read so the process loop ends without an exception.
reader.CancelPendingRead();
}

public async Task ProcessMessagesAsync()


{
try
{
while (true)
{
ReadResult result = await reader.ReadAsync();
ReadOnlySequence<byte> buffer = result.Buffer;

try
{
if (result.IsCanceled)
{
// The read was canceled. You can quit without reading the existing data.
break;
}

// Process all messages from the buffer, modifying the input buffer on each
// iteration.
while (TryParseMessage(ref buffer, out Message message))
{
await ProcessMessageAsync(message);
}

// There's no more data to be processed.


if (result.IsCompleted)
{
break;
}
}
finally
{
// Since all messages in the buffer are being processed, you can use the
// remaining buffer's Start and End position to determine consumed and examined.
reader.AdvanceTo(buffer.Start, buffer.End);
}
}
}
finally
{
await reader.CompleteAsync();
}
}

Problèmes courants PipeReader


Passer les mauvaises consumed examined valeurs à ou peut entraîner la lecture déjà des données lues.
La buffer.End réussite examinée peut entraîner :
Données bloquées
Peut-être une éventuelle exception hors mémoire (OOM) si les données ne sont pas consommées. Par
exemple, PipeReader.AdvanceTo(position, buffer.End) lors du traitement d’un seul message à la fois à
partir du tampon.
Passer les mauvaises consumed valeurs à ou peut entraîner une boucle infinie. Par
examined
PipeReader.AdvanceTo(buffer.Start) exemple, buffer.Start si n’a pas changé PipeReader.ReadAsync fera le
prochain appel de revenir immédiatement avant l’arrivée de nouvelles données.
Passer les mauvaises consumed examined valeurs à l’OOM ou peut entraîner une mise en mémoire tampon
infinie (éventuellement OOM).
L’utilisation PipeReader.AdvanceTo de l’appel ReadOnlySequence<byte> après peut entraîner une corruption de
mémoire (utilisation après gratuit).
Ne pas PipeReader.Complete/CompleteAsync appeler peut entraîner une fuite de mémoire.
La ReadResult.IsCompleted vérification et la sortie de la logique de lecture avant le traitement du tampon
entraînent une perte de données. L’état de sortie ReadResult.Buffer.IsEmpty de ReadResult.IsCompleted
boucle doit être basé sur et . Faire cela incorrectement pourrait entraîner une boucle infinie.
Code problématique
❌Per te de données
Le ReadResult peut retourner le segment IsCompleted final true de données quand est réglé à . Ne pas lire ces
données avant de sortir de la boucle de lecture entraînera une perte de données.

WARNING
N’utilisez PAS le code suivant. L’utilisation de cet échantillon entraînera une perte de données, des pends, des problèmes de
sécurité et ne devrait PAS être copiée. L’échantillon suivant est fourni pour expliquer les problèmes de PipeReader Common.

Environment.FailFast("This code is terrible, don't use it!");


while (true)
{
ReadResult result = await reader.ReadAsync(cancellationToken);
ReadOnlySequence<byte> dataLossBuffer = result.Buffer;

if (result.IsCompleted)
{
break;
}

Process(ref dataLossBuffer, out Message message);

reader.AdvanceTo(dataLossBuffer.Start, dataLossBuffer.End);
}

WARNING
N’utilisez PAS le code précédent. L’utilisation de cet échantillon entraînera une perte de données, des pends, des problèmes
de sécurité et ne devrait PAS être copiée. L’échantillon précédent est fourni pour expliquer les problèmes de PipeReader
Common.

❌Boucle infinie
La logique suivante peut entraîner une Result.IsCompleted true boucle infinie si l’est, mais il n’y a jamais un
message complet dans le tampon.
WARNING
N’utilisez PAS le code suivant. L’utilisation de cet échantillon entraînera une perte de données, des pends, des problèmes de
sécurité et ne devrait PAS être copiée. L’échantillon suivant est fourni pour expliquer les problèmes de PipeReader Common.

Environment.FailFast("This code is terrible, don't use it!");


while (true)
{
ReadResult result = await reader.ReadAsync(cancellationToken);
ReadOnlySequence<byte> infiniteLoopBuffer = result.Buffer;
if (result.IsCompleted && infiniteLoopBuffer.IsEmpty)
{
break;
}

Process(ref infiniteLoopBuffer, out Message message);

reader.AdvanceTo(infiniteLoopBuffer.Start, infiniteLoopBuffer.End);
}

WARNING
N’utilisez PAS le code précédent. L’utilisation de cet échantillon entraînera une perte de données, des pends, des problèmes
de sécurité et ne devrait PAS être copiée. L’échantillon précédent est fourni pour expliquer les problèmes de PipeReader
Common.

Voici un autre morceau de code avec le même problème. Il vérifie un tampon non vide ReadResult.IsCompleted
avant de vérifier . Parce qu’il else if est dans un , il va boucler pour toujours s’il n’y a jamais un message complet
dans le tampon.

WARNING
N’utilisez PAS le code suivant. L’utilisation de cet échantillon entraînera une perte de données, des pends, des problèmes de
sécurité et ne devrait PAS être copiée. L’échantillon suivant est fourni pour expliquer les problèmes de PipeReader Common.

Environment.FailFast("This code is terrible, don't use it!");


while (true)
{
ReadResult result = await reader.ReadAsync(cancellationToken);
ReadOnlySequence<byte> infiniteLoopBuffer = result.Buffer;

if (!infiniteLoopBuffer.IsEmpty)
{
Process(ref infiniteLoopBuffer, out Message message);
}
else if (result.IsCompleted)
{
break;
}

reader.AdvanceTo(infiniteLoopBuffer.Start, infiniteLoopBuffer.End);
}
WARNING
N’utilisez PAS le code précédent. L’utilisation de cet échantillon entraînera une perte de données, des pends, des problèmes
de sécurité et ne devrait PAS être copiée. L’échantillon précédent est fourni pour expliquer les problèmes de PipeReader
Common.

❌Hang inattendu
Appeler PipeReader.AdvanceTo sans buffer.End condition examined dans la position peut entraîner des
pendaisons lors de l’analyse d’un seul message. Le prochain PipeReader.AdvanceTo appel à ne pas revenir jusqu’à
ce que:
Il y a plus de données écrites sur le tuyau.
Et les nouvelles données n’ont pas été examinées auparavant.

WARNING
N’utilisez PAS le code suivant. L’utilisation de cet échantillon entraînera une perte de données, des pends, des problèmes de
sécurité et ne devrait PAS être copiée. L’échantillon suivant est fourni pour expliquer les problèmes de PipeReader Common.

Environment.FailFast("This code is terrible, don't use it!");


while (true)
{
ReadResult result = await reader.ReadAsync(cancellationToken);
ReadOnlySequence<byte> hangBuffer = result.Buffer;

Process(ref hangBuffer, out Message message);

if (result.IsCompleted)
{
break;
}

reader.AdvanceTo(hangBuffer.Start, hangBuffer.End);

if (message != null)
{
return message;
}
}

WARNING
N’utilisez PAS le code précédent. L’utilisation de cet échantillon entraînera une perte de données, des pends, des problèmes
de sécurité et ne devrait PAS être copiée. L’échantillon précédent est fourni pour expliquer les problèmes de PipeReader
Common.

❌Hors mémoire (OOM)


Dans les conditions suivantes, le code OutOfMemoryException suivant conserve la mise en mémoire tampon
jusqu’à ce qu’un se produise :
Il n’y a pas de taille maximale de message.
Les données retournées de la PipeReader ne fait pas un message complet. Par exemple, il ne fait pas un
message complet parce que l’autre côté écrit un grand message (par exemple, un message de 4 Go).
WARNING
N’utilisez PAS le code suivant. L’utilisation de cet échantillon entraînera une perte de données, des pends, des problèmes de
sécurité et ne devrait PAS être copiée. L’échantillon suivant est fourni pour expliquer les problèmes de PipeReader Common.

Environment.FailFast("This code is terrible, don't use it!");


while (true)
{
ReadResult result = await reader.ReadAsync(cancellationToken);
ReadOnlySequence<byte> thisCouldOutOfMemory = result.Buffer;

Process(ref thisCouldOutOfMemory, out Message message);

if (result.IsCompleted)
{
break;
}

reader.AdvanceTo(thisCouldOutOfMemory.Start, thisCouldOutOfMemory.End);

if (message != null)
{
return message;
}
}

WARNING
N’utilisez PAS le code précédent. L’utilisation de cet échantillon entraînera une perte de données, des pends, des problèmes
de sécurité et ne devrait PAS être copiée. L’échantillon précédent est fourni pour expliquer les problèmes de PipeReader
Common.

❌Corruption de mémoire
Lors de l’écriture d’aides qui lisent le Advance tampon, toute charge utile retournée doit être copiée avant d’appeler
. L’exemple suivant rendra Pipe la mémoire que le a jeté et peut la réutiliser pour la prochaine opération
(lire/écrire).

WARNING
N’utilisez PAS le code suivant. L’utilisation de cet échantillon entraînera une perte de données, des pends, des problèmes de
sécurité et ne devrait PAS être copiée. L’échantillon suivant est fourni pour expliquer les problèmes de PipeReader Common.

public class Message


{
public ReadOnlySequence<byte> CorruptedPayload { get; set; }
}
Environment.FailFast("This code is terrible, don't use it!");
Message message = null;

while (true)
{
ReadResult result = await reader.ReadAsync(cancellationToken);
ReadOnlySequence<byte> buffer = result.Buffer;

ReadHeader(ref buffer, out int length);

if (length <= buffer.Length)


{
message = new Message
{
// Slice the payload from the existing buffer
CorruptedPayload = buffer.Slice(0, length)
};

buffer = buffer.Slice(length);
}

if (result.IsCompleted)
{
break;
}

reader.AdvanceTo(buffer.Start, buffer.End);

if (message != null)
{
// This code is broken since reader.AdvanceTo() was called with a position *after* the buffer
// was captured.
break;
}
}

return message;
}

WARNING
N’utilisez PAS le code précédent. L’utilisation de cet échantillon entraînera une perte de données, des pends, des problèmes
de sécurité et ne devrait PAS être copiée. L’échantillon précédent est fourni pour expliquer les problèmes de PipeReader
Common.

PipeWriter (pipeWriter)
Le PipeWriter gère les tampons pour écrire au nom de l’appelant. PipeWriter met IBufferWriter<byte> en œuvre .
IBufferWriter<byte> permet d’avoir accès à des tampons pour effectuer des écritures sans copies tampons
supplémentaires.
async Task WriteHelloAsync(PipeWriter writer, CancellationToken cancellationToken = default)
{
// Request at least 5 bytes from the PipeWriter.
Memory<byte> memory = writer.GetMemory(5);

// Write directly into the buffer.


int written = Encoding.ASCII.GetBytes("Hello".AsSpan(), memory.Span);

// Tell the writer how many bytes were written.


writer.Advance(written);

await writer.FlushAsync(cancellationToken);
}

Le code précédent:
Demande un tampon d’au moins PipeWriter 5 octets de l’utilisation GetMemory.
Écrit des octets pour "Hello" la chaîne Memory<byte> ASCII au retour .
Appels Advance pour indiquer combien d’octets ont été écrits au tampon.
Flushes PipeWriter le , qui envoie les octets à l’appareil sous-jacent.
La méthode précédente d’écriture utilise PipeWriter les tampons fournis par le . Alternativement,
PipeWriter.WriteAsync:
Copie le tampon PipeWriter existant à la .
Appels GetSpan Advance , le FlushAsynccas échéant et les appels .

async Task WriteHelloAsync(PipeWriter writer, CancellationToken cancellationToken = default)


{
byte[] helloBytes = Encoding.ASCII.GetBytes("Hello");

// Write helloBytes to the writer, there's no need to call Advance here


// (Write does that).
await writer.WriteAsync(helloBytes, cancellationToken);
}

Annulation
FlushAsyncsoutient l’adoption d’un CancellationToken. Passer CancellationToken un résultat
OperationCanceledException dans un si le jeton est annulé alors qu’il ya une chasse d’eau en attente.
PipeWriter.FlushAsync prend en charge un moyen PipeWriter.CancelPendingFlush d’annuler l’opération de chasse
d’eau en cours via sans soulever une exception. PipeWriter.CancelPendingFlush L’appel provoque l’appel
PipeWriter.WriteAsync actuel FlushResult ou IsCanceled suivant true à PipeWriter.FlushAsync ou de retourner
un avec réglé à . Cela peut être utile pour arrêter la chasse d’eau de rendement d’une manière non destructive et
non exceptionnelle.
Problèmes courants PipeWriter
GetSpanet GetMemory retourner un tampon avec au moins la quantité demandée de mémoire. N’assumez
pas les tailles exactes de tampon.
Il n’y a aucune garantie que les appels successifs retourneront le même tampon ou le même tampon de taille.
Un nouveau tampon doit Advance être demandé après avoir appelé pour continuer à écrire plus de données. Le
tampon précédemment acquis ne peut pas être écrit à.
Appeler GetMemory GetSpan ou pendant qu’il FlushAsync y a un appel incomplet n’est pas sûr.
Appeler Complete CompleteAsync ou pendant qu’il y a des données non gonflées peut entraîner une corruption
de la mémoire.
IDuplexPipe
Il IDuplexPipe s’agit d’un contrat pour les types qui prennent en charge à la fois la lecture et l’écriture. Par exemple,
une connexion réseau serait IDuplexPipe représentée par un .
Contrairement Pipe à PipeReader ce PipeWriter qui IDuplexPipe contient a et a , représente un seul côté d’une
connexion en duplex complet. Cela signifie que ce PipeWriter qui est écrit PipeReader à la volonté ne sera pas lu
de la .

Flux
Lorsque vous lisez ou écrivez des données de flux, vous lisez généralement des données à l’aide d’un dé-serializer
et écrivez des données à l’aide d’un sérialisateur. La plupart de ces API Stream de lecture et d’écriture ont un
paramètre. Pour faciliter l’intégration à ces PipeReader API existantes, et PipeWriter exposer un AsStream.
AsStreamretourne Stream une mise PipeReader PipeWriter en œuvre autour de la ou .
Utiliser des mémoires tampons dans .NET
18/07/2020 • 16 minutes to read • Edit Online

Cet article fournit une vue d’ensemble des types qui aident à lire les données qui s’exécutent sur plusieurs
mémoires tampons. Ils sont principalement utilisés pour prendre en charge les PipeReader objets.

IBufferWriter < T>


System.Buffers.IBufferWriter<T>est un contrat pour l’écriture en mémoire tampon synchrone. Au niveau le plus
bas, l’interface :
Est de base et n’est pas difficile à utiliser.
Autorise l’accès à un Memory<T> ou un Span<T> . Memory<T> Ou Span<T> peut être écrit dans et vous pouvez
déterminer le nombre d' T éléments qui ont été écrits.

void WriteHello(IBufferWriter<byte> writer)


{
// Request at least 5 bytes.
Span<byte> span = writer.GetSpan(5);
ReadOnlySpan<char> helloSpan = "Hello".AsSpan();
int written = Encoding.ASCII.GetBytes(helloSpan, span);

// Tell the writer how many bytes were written.


writer.Advance(written);
}

La méthode précédente :
Demande une mémoire tampon d’au moins 5 octets à l' IBufferWriter<byte> aide de GetSpan(5) .
Écrit des octets pour la chaîne ASCII « Hello » sur le retourné Span<byte> .
Appelle IBufferWriter<T> pour indiquer le nombre d’octets qui ont été écrits dans la mémoire tampon.
Cette méthode d’écriture utilise la Memory<T> / Span<T> mémoire tampon fournie par IBufferWriter<T> . La Write
méthode d’extension peut également être utilisée pour copier une mémoire tampon existante dans le
IBufferWriter<T> . Write le travail d’appel est- GetSpan / Advance il approprié, donc il n’est pas nécessaire
d’appeler Advance après l’écriture :

void WriteHello(IBufferWriter<byte> writer)


{
byte[] helloBytes = Encoding.ASCII.GetBytes("Hello");

// Write helloBytes to the writer. There's no need to call Advance here


// since Write calls Advance.
writer.Write(helloBytes);
}

ArrayBufferWriter<T>est une implémentation de IBufferWriter<T> dont le magasin de stockage est un tableau


contigu unique.
Problèmes courants liés à IBufferWriter
GetSpan et GetMemory retournent une mémoire tampon avec au moins la quantité de mémoire demandée. Ne
supposez pas des tailles de mémoire tampon exactes.
Il n’y a aucune garantie que les appels successifs retourneront la même mémoire tampon ou la même mémoire
tampon de taille.
Une nouvelle mémoire tampon doit être demandée après l’appel Advance de pour continuer à écrire davantage
de données. Une mémoire tampon acquise précédemment ne peut pas être écrite dans après l' Advance appel
de.

ReadOnlySequence < T>

ReadOnlySequence<T>est un struct qui peut représenter une séquence contiguë ou non contiguë de T . Il peut
être construit à partir de :
1. Une variable de type T[] .
2. Une variable de type ReadOnlyMemory<T> .
3. Paire de nœuds de liste liée ReadOnlySequenceSegment<T> et d’index pour représenter la position de début et
de fin de la séquence.
La troisième représentation est la plus intéressante, car elle a une incidence sur les performances de diverses
opérations sur le ReadOnlySequence<T> :

REP RÉSEN TAT IO N O P ÉRAT IO N C O M P L EXIT É

T[] / ReadOnlyMemory<T> Length O(1)

T[] / ReadOnlyMemory<T> GetPosition(long) O(1)

T[] / ReadOnlyMemory<T> Slice(int, int) O(1)

T[] / ReadOnlyMemory<T> Slice(SequencePostion, O(1)


SequencePostion)

ReadOnlySequenceSegment<T> Length O(1)

ReadOnlySequenceSegment<T> GetPosition(long) O(number of segments)

ReadOnlySequenceSegment<T> Slice(int, int) O(number of segments)

ReadOnlySequenceSegment<T> Slice(SequencePostion, O(1)


SequencePostion)

En raison de cette représentation mixte, le ReadOnlySequence<T> expose les index au SequencePosition lieu d’un
entier. R SequencePosition :
Valeur opaque qui représente un index dans le d' ReadOnlySequence<T> où il provient.
Se compose de deux parties : un entier et un objet. Ce que représentent ces deux valeurs sont liées à
l’implémentation de ReadOnlySequence<T> .
Accéder aux données
ReadOnlySequence<T> Expose des données en tant qu’énumérable de ReadOnlyMemory<T> . L’énumération de chacun
des segments peut être effectuée à l’aide d’une instruction foreach de base :

long FindIndexOf(in ReadOnlySequence<byte> buffer, byte data)


{
long position = 0;

foreach (ReadOnlyMemory<byte> segment in buffer)


{
ReadOnlySpan<byte> span = segment.Span;
var index = span.IndexOf(data);
if (index != -1)
{
return position + index;
}

position += span.Length;
}

return -1;
}

La méthode précédente recherche un octet spécifique dans chaque segment. Si vous devez effectuer le suivi de
chaque segment SequencePosition , ReadOnlySequence<T>.TryGet est plus approprié. L’exemple suivant modifie le
code précédent pour retourner un SequencePosition au lieu d’un entier. Le retour d’un SequencePosition a
l’avantage de permettre à l’appelant d’éviter une deuxième analyse pour obtenir les données à un index spécifique.

SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data)


{
SequencePosition position = buffer.Start;

while (buffer.TryGet(ref position, out ReadOnlyMemory<byte> segment))


{
ReadOnlySpan<byte> span = segment.Span;
var index = span.IndexOf(data);
if (index != -1)
{
return buffer.GetPosition(position, index);
}
}
return null;
}

La combinaison de SequencePosition et TryGet agit comme un énumérateur. Le champ position est modifié au
début de chaque itération pour démarrer chaque segment dans le ReadOnlySequence<T> .
La méthode précédente existe en tant que méthode d’extension sur ReadOnlySequence<T> . PositionOfpeut être
utilisé pour simplifier le code précédent :

SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data) => buffer.PositionOf(data);

Traiter un ReadOnlySequence < T>


Le traitement d’un ReadOnlySequence<T> peut être difficile, car les données peuvent être réparties entre plusieurs
segments au sein de la séquence. Pour des performances optimales, fractionnez le code en deux chemins d’accès :
Un chemin d’accès rapide qui gère le cas de segment unique.
Chemin d’accès lent qui traite les données fractionnées entre les segments.
Il existe plusieurs approches qui peuvent être utilisées pour traiter les données dans des séquences multisegment :
Utilisez le SequenceReader<T> .
Analyser le segment de données par segment, en effectuant le suivi de l' SequencePosition index et dans le
segment analysé. Cela évite les allocations inutiles, mais peut s’avérer inefficace, en particulier pour les
mémoires tampons de petite taille.
Copiez le ReadOnlySequence<T> dans un tableau contigu et traitez-le comme une mémoire tampon unique :
Si la taille du ReadOnlySequence<T> est faible, il peut être raisonnable de copier les données dans une
mémoire tampon allouée par la pile à l’aide de l’opérateur stackalloc .
Copiez ReadOnlySequence<T> dans un tableau mis en pool à l’aide de ArrayPool<T>.Shared .
Utilisez ReadOnlySequence<T>.ToArray() . Cela n’est pas recommandé dans les chemins d’accès à chaud, car
il alloue un nouveau T[] sur le tas.

Les exemples suivants illustrent des cas courants de traitement ReadOnlySequence<byte> :


T r ai t er des do n n ées bi n ai r es

L’exemple suivant analyse une longueur d’entier Big-endian de 4 octets à partir du début de
ReadOnlySequence<byte> .

bool TryParseHeaderLength(ref ReadOnlySequence<byte> buffer, out int length)


{
// If there's not enough space, the length can't be obtained.
if (buffer.Length < 4)
{
length = 0;
return false;
}

// Grab the first 4 bytes of the buffer.


var lengthSlice = buffer.Slice(buffer.Start, 4);
if (lengthSlice.IsSingleSegment)
{
// Fast path since it's a single segment.
length = BinaryPrimitives.ReadInt32BigEndian(lengthSlice.First.Span);
}
else
{
// There are 4 bytes split across multiple segments. Since it's so small, it
// can be copied to a stack allocated buffer. This avoids a heap allocation.
Span<byte> stackBuffer = stackalloc byte[4];
lengthSlice.CopyTo(stackBuffer);
length = BinaryPrimitives.ReadInt32BigEndian(stackBuffer);
}

// Move the buffer 4 bytes ahead.


buffer = buffer.Slice(lengthSlice.End);

return true;
}

Si vous souhaitez voir les commentaires de code traduits dans des langues autres que l’anglais, faites-le nous
savoir dans ce problème de discussion GitHub.
T r ai t er des do n n ées t ext e

L’exemple suivant :
Recherche le premier saut de ligne ( \r\n ) dans ReadOnlySequence<byte> et le retourne via le paramètre
out’line'.
Supprime cette ligne, à l’exclusion \r\n de de la mémoire tampon d’entrée.

static bool TryParseLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> line)


{
SequencePosition position = buffer.Start;
SequencePosition previous = position;
var index = -1;
line = default;

while (buffer.TryGet(ref position, out ReadOnlyMemory<byte> segment))


{
ReadOnlySpan<byte> span = segment.Span;

// Look for \r in the current segment.


index = span.IndexOf((byte)'\r');

if (index != -1)
{
// Check next segment for \n.
if (index + 1 >= span.Length)
{
var next = position;
if (!buffer.TryGet(ref next, out ReadOnlyMemory<byte> nextSegment))
{
// You're at the end of the sequence.
return false;
}
else if (nextSegment.Span[0] == (byte)'\n')
{
// A match was found.
break;
}
}
// Check the current segment of \n.
else if (span[index + 1] == (byte)'\n')
{
// It was found.
break;
}
}

previous = position;
}

if (index != -1)
{
// Get the position just before the \r\n.
var delimeter = buffer.GetPosition(index, previous);

// Slice the line (excluding \r\n).


line = buffer.Slice(buffer.Start, delimeter);

// Slice the buffer to get the remaining data after the line.
buffer = buffer.Slice(buffer.GetPosition(2, delimeter));
return true;
}

return false;
}

Se g m e n t s v i d e s

Il est valide de stocker des segments vides à l’intérieur d’un ReadOnlySequence<T> . Des segments vides peuvent se
produire lors de l’énumération des segments de manière explicite :
static void EmptySegments()
{
// This logic creates a ReadOnlySequence<byte> with 4 segments,
// two of which are empty.
var first = new BufferSegment(new byte[0]);
var last = first.Append(new byte[] { 97 })
.Append(new byte[0]).Append(new byte[] { 98 });

// Construct the ReadOnlySequence<byte> from the linked list segments.


var data = new ReadOnlySequence<byte>(first, 0, last, 1);

// Slice using numbers.


var sequence1 = data.Slice(0, 2);

// Slice using SequencePosition pointing at the empty segment.


var sequence2 = data.Slice(data.Start, 2);

Console.WriteLine($"sequence1.Length={sequence1.Length}"); // sequence1.Length=2
Console.WriteLine($"sequence2.Length={sequence2.Length}"); // sequence2.Length=2

// sequence1.FirstSpan.Length=1
Console.WriteLine($"sequence1.FirstSpan.Length={sequence1.FirstSpan.Length}");

// Slicing using SequencePosition will Slice the ReadOnlySequence<byte> directly


// on the empty segment!
// sequence2.FirstSpan.Length=0
Console.WriteLine($"sequence2.FirstSpan.Length={sequence2.FirstSpan.Length}");

// The following code prints 0, 1, 0, 1.


SequencePosition position = data.Start;
while (data.TryGet(ref position, out ReadOnlyMemory<byte> memory))
{
Console.WriteLine(memory.Length);
}
}

class BufferSegment : ReadOnlySequenceSegment<byte>


{
public BufferSegment(Memory<byte> memory)
{
Memory = memory;
}

public BufferSegment Append(Memory<byte> memory)


{
var segment = new BufferSegment(memory)
{
RunningIndex = RunningIndex + Memory.Length
};
Next = segment;
return segment;
}
}

Le code précédent crée un ReadOnlySequence<byte> avec des segments vides et montre comment ces segments
vides affectent les différentes API :
ReadOnlySequence<T>.Slice en SequencePosition pointant sur un segment vide, vous conservez ce segment.
ReadOnlySequence<T>.Slice avec un int ignore les segments vides.
L’énumération de ReadOnlySequence<T> énumère les segments vides.
Problèmes potentiels avec ReadOnlySequence < T> et SequencePosition
Il existe plusieurs résultats inhabituels lors de la gestion d’un ReadOnlySequence<T> / SequencePosition et d’un
normal ReadOnlySpan<T> / ReadOnlyMemory<T> / T[] / int :
SequencePosition est un marqueur de position pour un spécifique ReadOnlySequence<T> , et non une position
absolue. Étant donné qu’il est relatif à un spécifique ReadOnlySequence<T> , il n’a pas de sens s’il est utilisé en
dehors de l' ReadOnlySequence<T> emplacement d’origine.
L’arithmétique ne peut pas être exécutée sur SequencePosition sans ReadOnlySequence<T> . Cela signifie que les
choses de base position++ sont écrites ReadOnlySequence<T>.GetPosition(position, 1) .
GetPosition(long) ne prend pas en charge les index négatifs. Cela signifie qu’il est impossible d’extraire l’avant-
dernier caractère sans parcourir tous les segments.
Deux SequencePosition ne peuvent pas être comparés, ce qui complique la tâche :
Savoir si une position est supérieure ou inférieure à une autre position.
Écrivez des algorithmes d’analyse.
ReadOnlySequence<T> est plus grand qu’une référence d’objet et doit être passé par in ou ref dans la mesure du
possible. ReadOnlySequence<T> Le passage par in ou ref réduit les copies du struct.
Segments vides :
Sont valides dans un ReadOnlySequence<T> .
Peut apparaître lors de l’itération à l’aide de la ReadOnlySequence<T>.TryGet méthode.
Peut apparaître le découpage de la séquence à l’aide de la ReadOnlySequence<T>.Slice() méthode avec
des SequencePosition objets.

SequenceReader < T>


SequenceReader<T>:
Est un nouveau type qui a été introduit dans .NET Core 3,0 pour simplifier le traitement d’un
ReadOnlySequence<T> .
Unifie les différences entre un segment unique ReadOnlySequence<T> et plusieurs segments ReadOnlySequence<T>
.
Fournit des assistances pour la lecture de données binaires et textuelles ( byte et char ) qui peuvent ou non
être fractionnées entre les segments.
Il existe des méthodes intégrées pour traiter le traitement des données binaires et délimitées. La section suivante
montre à quoi ressemblent ces mêmes méthodes avec SequenceReader<T> :
Accéder aux données
SequenceReader<T> a des méthodes pour énumérer des données dans ReadOnlySequence<T> directement. Le code
suivant est un exemple de traitement d’un ReadOnlySequence<byte> a byte à la fois :

while (reader.TryRead(out byte b))


{
Process(b);
}

Le CurrentSpan expose le du segment actuel Span , qui est similaire à ce qui a été effectué manuellement dans la
méthode.
Utiliser la position
Le code suivant est un exemple d’implémentation de FindIndexOf à l’aide de SequenceReader<T> :
SequencePosition? FindIndexOf(in ReadOnlySequence<byte> buffer, byte data)
{
var reader = new SequenceReader<byte>(buffer);

while (!reader.End)
{
// Search for the byte in the current span.
var index = reader.CurrentSpan.IndexOf(data);
if (index != -1)
{
// It was found, so advance to the position.
reader.Advance(index);

return reader.Position;
}
// Skip the current segment since there's nothing in it.
reader.Advance(reader.CurrentSpan.Length);
}

return null;
}

Traiter des données binaires


L’exemple suivant analyse une longueur d’entier Big-endian de 4 octets à partir du début de
ReadOnlySequence<byte> .

bool TryParseHeaderLength(ref ReadOnlySequence<byte> buffer, out int length)


{
var reader = new SequenceReader<byte>(buffer);
return reader.TryReadBigEndian(out length);
}

Traiter des données texte

static ReadOnlySpan<byte> NewLine => new byte[] { (byte)'\r', (byte)'\n' };

static bool TryParseLine(ref ReadOnlySequence<byte> buffer,


out ReadOnlySequence<byte> line)
{
var reader = new SequenceReader<byte>(buffer);

if (reader.TryReadTo(out line, NewLine))


{
buffer = buffer.Slice(reader.Position);

return true;
}

line = default;
return false;
}

< > Problèmes courants liés à SequenceReader T


Étant donné que SequenceReader<T> est un struct mutable, il doit toujours être passé par référence.
SequenceReader<T> est un struct de référence afin qu’il ne puisse être utilisé que dans des méthodes synchrones
et ne peut pas être stocké dans des champs. Pour plus d’informations, consultez écrire du code C# sécurisé et
efficace.
SequenceReader<T> est optimisé pour une utilisation en tant que lecteur avant uniquement. Rewind est destiné
aux petites sauvegardes qui ne peuvent pas être traitées à l’aide d’autres Read Peek API, et IsNext .
Fichiers mappés en mémoire
18/07/2020 • 15 minutes to read • Edit Online

Un fichier mappé en mémoire comporte le contenu d'un fichier en mémoire virtuelle. Ce mappage entre un fichier
et un espace mémoire permet à une application incluant plusieurs processus de modifier le fichier en lisant et en
écrivant directement dans la mémoire. À compter de .NET Framework 4, vous pouvez utiliser du code managé
pour accéder aux fichiers mappés en mémoire de la même façon que les fonctions Windows natives accèdent à
des fichiers mappés en mémoire, comme décrit dans Gestion des fichiers mappés en mémoire.
Il existe deux types de fichiers mappés en mémoire :
Fichiers mappés en mémoire persistants
Les fichiers persistants sont des fichiers mappés en mémoire associés à un fichier source sur un disque.
Lorsque le dernier processus a fini de travailler avec le fichier, les données sont enregistrées dans le fichier
source sur le disque. Ces fichiers mappés en mémoire sont adaptés au travail avec des fichiers sources
extrêmement volumineux.
Fichiers mappés en mémoire non persistants
Les non fichiers persistants sont des fichiers mappés en mémoire associés à un fichier sur un disque.
Lorsque le dernier processus a fini de travailler avec le fichier, les données sont perdues et l'espace mémoire
est récupéré par le nettoyage de la mémoire. Ces fichiers sont adaptés à la création d’une mémoire
partagée pour les communications entre processus (IPC).

Processus, vues et gestion de la mémoire


Les fichiers mappés en mémoire ne peuvent pas être partagé entre plusieurs processus. Des processus peuvent
être mappés dans le même fichier mappé en mémoire à l’aide d’un nom commun attribué par le processus qui a
créé le fichier.
Pour utiliser un fichier mappé en mémoire, vous devez créer une vue de l’intégralité du fichier mappé en mémoire
ou une partie de celui-ci. Vous pouvez également créer plusieurs vues dans la même partie du fichier mappé en
mémoire et créer ainsi une mémoire simultanée. Pour que deux vues restent simultanées, elles doivent être créées
à partir du même fichier mappé en mémoire.
Plusieurs vues peuvent également être nécessaires si le fichier est supérieur à la taille de l’espace de mémoire
logique de l’application disponible pour le mappage de mémoire (2 Go sur un ordinateur 32 bits).
Il existe deux types de vues : une vue d’accès à Stream et une vue d’accès aléatoire. Utilisez les vues d’accès à
Stream pour un accès séquentiel à un fichier ; c’est recommandé pour les fichiers non persistants et pour IPC. Les
vues d’accès aléatoire sont privilégiées pour l’utilisation de fichiers persistants.
Les fichiers mappés en mémoire sont accessibles via le gestionnaire de mémoire du système d’exploitation. Le
fichier est alors automatiquement partitionné en un nombre de pages et consulté en fonction des besoins. Vous
n’avez pas à gérer vous-même la mémoire.
L’illustration suivante montre comment plusieurs processus peuvent avoir simultanément plusieurs vues qui se
chevauchent dans le même fichier mappé en mémoire.
L’image suivante montre plusieurs vues qui se chevauchent dans un fichier mappé en mémoire :
Programmation avec des fichiers mappés en mémoire
Le tableau suivant fournit un guide pour l’utilisation d’objets de fichier mappé en mémoire et de leurs membres.

TÂ C H E M ÉT H O DES O U P RO P RIÉT ÉS À UT IL ISER

Pour obtenir un objet MemoryMappedFile qui représente un MéthodeMemoryMappedFile.CreateFromFile .


fichier mappé en mémoire persistant à partir d’un fichier sur
disque.

Pour obtenir un objet MemoryMappedFile qui représente un MéthodeMemoryMappedFile.CreateNew .


fichier mappé en mémoire non persistant (pas associé à un
fichier sur disque). - ou -

MéthodeMemoryMappedFile.CreateOrOpen .

Pour obtenir un objet MemoryMappedFile d’un fichier mappé MéthodeMemoryMappedFile.OpenExisting .


en mémoire existant (persistant ou non persistant).

Pour obtenir un objet UnmanagedMemoryStream pour une MéthodeMemoryMappedFile.CreateViewStream .


vue à accès séquentiel dans le fichier mappé en mémoire.

Pour obtenir un objet UnmanagedMemoryAccessor pour une MéthodeMemoryMappedFile.CreateViewAccessor .


vue à accès aléatoire dans un fichier mappé en mémoire.

Pour obtenir un objet SafeMemoryMappedViewHandle à Propriété


utiliser avec du code non managé. MemoryMappedFile.SafeMemoryMappedFileHandle.

- ou -

Propriété
MemoryMappedViewAccessor.SafeMemoryMappedViewHandl
e.

- ou -

Propriété
MemoryMappedViewStream.SafeMemoryMappedViewHandle
.
TÂ C H E M ÉT H O DES O U P RO P RIÉT ÉS À UT IL ISER

Pour différer l’allocation de mémoire jusqu’à ce qu’une vue soit Méthode CreateNew avec la valeur
créée (fichiers non persistants uniquement). MemoryMappedFileOptions.DelayAllocatePages.

(Pour déterminer la taille de page du système actuel, utilisez la - ou -


propriété Environment.SystemPageSize.)
Méthodes CreateOrOpen qui ont une énumération
MemoryMappedFileOptions en tant que paramètre.

Sécurité
Vous pouvez appliquer des droits d’accès lorsque vous créez un fichier mappé en mémoire, en utilisant les
méthodes suivantes qui prennent une énumération MemoryMappedFileAccess en tant que paramètre :
MemoryMappedFile.CreateFromFile
MemoryMappedFile.CreateNew
MemoryMappedFile.CreateOrOpen
vous pouvez spécifier des droits d’accès pour l’ouverture d’un fichier mappé en mémoire existant à l’aide des
méthodes OpenExisting qui prennent un MemoryMappedFileRights en tant que paramètre.
Vous pouvez également inclure un objet MemoryMappedFileSecurity qui contient des règles d’accès prédéfinies.
Pour appliquer des règles d’accès nouvelles ou modifiées à un fichier mappé en mémoire, utilisez la méthode
SetAccessControl. Pour récupérer un accès ou des règles d’audit à partir d’un fichier existant, utilisez la méthode
GetAccessControl.

Exemples
Fichiers mappés en mémoire persistants
Les méthodes CreateFromFile créent un fichier mappé en mémoire à partir d’un fichier existant sur le disque.
L’exemple suivant crée une vue mappée en mémoire d’une partie d’un fichier très volumineux et manipule une
partie de ce dernier.
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;

class Program
{
static void Main(string[] args)
{
long offset = 0x10000000; // 256 megabytes
long length = 0x20000000; // 512 megabytes

// Create the memory-mapped file.


using (var mmf = MemoryMappedFile.CreateFromFile(@"c:\ExtremelyLargeImage.data",
FileMode.Open,"ImgA"))
{
// Create a random access view, from the 256th megabyte (the offset)
// to the 768th megabyte (the offset plus length).
using (var accessor = mmf.CreateViewAccessor(offset, length))
{
int colorSize = Marshal.SizeOf(typeof(MyColor));
MyColor color;

// Make changes to the view.


for (long i = 0; i < length; i += colorSize)
{
accessor.Read(i, out color);
color.Brighten(10);
accessor.Write(i, ref color);
}
}
}
}
}

public struct MyColor


{
public short Red;
public short Green;
public short Blue;
public short Alpha;

// Make the view brighter.


public void Brighten(short value)
{
Red = (short)Math.Min(short.MaxValue, (int)Red + value);
Green = (short)Math.Min(short.MaxValue, (int)Green + value);
Blue = (short)Math.Min(short.MaxValue, (int)Blue + value);
Alpha = (short)Math.Min(short.MaxValue, (int)Alpha + value);
}
}
Imports System.IO
Imports System.IO.MemoryMappedFiles
Imports System.Runtime.InteropServices

Class Program

Sub Main()
Dim offset As Long = &H10000000 ' 256 megabytes
Dim length As Long = &H20000000 ' 512 megabytes

' Create the memory-mapped file.


Using mmf = MemoryMappedFile.CreateFromFile("c:\ExtremelyLargeImage.data", FileMode.Open, "ImgA")
' Create a random access view, from the 256th megabyte (the offset)
' to the 768th megabyte (the offset plus length).
Using accessor = mmf.CreateViewAccessor(offset, length)
Dim colorSize As Integer = Marshal.SizeOf(GetType(MyColor))
Dim color As MyColor
Dim i As Long = 0

' Make changes to the view.


Do While (i < length)
accessor.Read(i, color)
color.Brighten(10)
accessor.Write(i, color)
i += colorSize
Loop
End Using
End Using
End Sub
End Class

Public Structure MyColor


Public Red As Short
Public Green As Short
Public Blue As Short
Public Alpha As Short

' Make the view brighter.


Public Sub Brighten(ByVal value As Short)
Red = CType(Math.Min(Short.MaxValue, (CType(Red, Integer) + value)), Short)
Green = CType(Math.Min(Short.MaxValue, (CType(Green, Integer) + value)), Short)
Blue = CType(Math.Min(Short.MaxValue, (CType(Blue, Integer) + value)), Short)
Alpha = CType(Math.Min(Short.MaxValue, (CType(Alpha, Integer) + value)), Short)
End Sub
End Structure

Si vous souhaitez voir les commentaires de code traduits dans des langues autres que l’anglais, faites-le nous
savoir dans ce problème de discussion GitHub.
L’exemple suivant ouvre le même fichier mappé en mémoire pour un autre processus.
using System;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;

class Program
{
static void Main(string[] args)
{
// Assumes another process has created the memory-mapped file.
using (var mmf = MemoryMappedFile.OpenExisting("ImgA"))
{
using (var accessor = mmf.CreateViewAccessor(4000000, 2000000))
{
int colorSize = Marshal.SizeOf(typeof(MyColor));
MyColor color;

// Make changes to the view.


for (long i = 0; i < 1500000; i += colorSize)
{
accessor.Read(i, out color);
color.Brighten(20);
accessor.Write(i, ref color);
}
}
}
}
}

public struct MyColor


{
public short Red;
public short Green;
public short Blue;
public short Alpha;

// Make the view brigher.


public void Brighten(short value)
{
Red = (short)Math.Min(short.MaxValue, (int)Red + value);
Green = (short)Math.Min(short.MaxValue, (int)Green + value);
Blue = (short)Math.Min(short.MaxValue, (int)Blue + value);
Alpha = (short)Math.Min(short.MaxValue, (int)Alpha + value);
}
}
Imports System.IO.MemoryMappedFiles
Imports System.Runtime.InteropServices

Class Program
Public Shared Sub Main(ByVal args As String())
' Assumes another process has created the memory-mapped file.
Using mmf = MemoryMappedFile.OpenExisting("ImgA")
Using accessor = mmf.CreateViewAccessor(4000000, 2000000)
Dim colorSize As Integer = Marshal.SizeOf(GetType(MyColor))
Dim color As MyColor

' Make changes to the view.


Dim i As Long = 0
While i < 1500000
accessor.Read(i, color)
color.Brighten(30)
accessor.Write(i, color)
i += colorSize
End While
End Using
End Using
End Sub
End Class

Public Structure MyColor


Public Red As Short
Public Green As Short
Public Blue As Short
Public Alpha As Short

' Make the view brigher.


Public Sub Brighten(ByVal value As Short)
Red = CShort(Math.Min(Short.MaxValue, CInt(Red) + value))
Green = CShort(Math.Min(Short.MaxValue, CInt(Green) + value))
Blue = CShort(Math.Min(Short.MaxValue, CInt(Blue) + value))
Alpha = CShort(Math.Min(Short.MaxValue, CInt(Alpha) + value))
End Sub
End Structure

Fichiers mappés en mémoire non persistants


Les méthodes CreateNew et CreateOrOpen créent un fichier mappé en mémoire qui n’est pas mappé à un fichier
existant sur le disque.
L’exemple suivant se compose de trois processus distincts (applications console) qui écrivent des valeurs
booléennes dans un fichier mappé en mémoire. La séquence d’actions suivante se produit :
1. Process A crée le fichier mappé en mémoire et y écrit une valeur.
2. Process B ouvre le fichier mappé en mémoire et y écrit une valeur.
3. Process C ouvre le fichier mappé en mémoire et y écrit une valeur.
4. Process A lit et affiche les valeurs à partir du fichier mappé en mémoire.
5. Après que Process A est terminé avec le fichier mappé en mémoire, ce dernier est immédiatement
récupéré par le nettoyage de la mémoire.
Pour exécuter cet exemple, procédez comme suit :
1. Compilez les applications et ouvrez trois fenêtres d’invite de commandes.
2. Dans la première fenêtre d’invite de commande, exécutez Process A .
3. Dans la deuxième fenêtre d’invite de commande, exécutez Process B .
4. Retournez à Process A et appuyez sur ENTRÉE.
5. Dans la troisième fenêtre d’invite de commande, exécutez Process C .
6. Retournez à Process A et appuyez sur ENTRÉE.
La sortie de Process A est la suivante :

Start Process B and press ENTER to continue.


Start Process C and press ENTER to continue.
Process A says: True
Process B says: False
Process C says: True

Processus A

using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;

class Program
{
// Process A:
static void Main(string[] args)
{
using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("testmap", 10000))
{
bool mutexCreated;
Mutex mutex = new Mutex(true, "testmapmutex", out mutexCreated);
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
BinaryWriter writer = new BinaryWriter(stream);
writer.Write(1);
}
mutex.ReleaseMutex();

Console.WriteLine("Start Process B and press ENTER to continue.");


Console.ReadLine();

Console.WriteLine("Start Process C and press ENTER to continue.");


Console.ReadLine();

mutex.WaitOne();
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
BinaryReader reader = new BinaryReader(stream);
Console.WriteLine("Process A says: {0}", reader.ReadBoolean());
Console.WriteLine("Process B says: {0}", reader.ReadBoolean());
Console.WriteLine("Process C says: {0}", reader.ReadBoolean());
}
mutex.ReleaseMutex();
}
}
}
Imports System.IO
Imports System.IO.MemoryMappedFiles
Imports System.Threading

Module Module1

' Process A:
Sub Main()
Using mmf As MemoryMappedFile = MemoryMappedFile.CreateNew("testmap", 10000)
Dim mutexCreated As Boolean
Dim mTex As Mutex = New Mutex(True, "testmapmutex", mutexCreated)
Using Stream As MemoryMappedViewStream = mmf.CreateViewStream()
Dim writer As BinaryWriter = New BinaryWriter(Stream)
writer.Write(1)
End Using
mTex.ReleaseMutex()
Console.WriteLine("Start Process B and press ENTER to continue.")
Console.ReadLine()

Console.WriteLine("Start Process C and press ENTER to continue.")


Console.ReadLine()

mTex.WaitOne()
Using Stream As MemoryMappedViewStream = mmf.CreateViewStream()
Dim reader As BinaryReader = New BinaryReader(Stream)
Console.WriteLine("Process A says: {0}", reader.ReadBoolean())
Console.WriteLine("Process B says: {0}", reader.ReadBoolean())
Console.WriteLine("Process C says: {0}", reader.ReadBoolean())
End Using
mTex.ReleaseMutex()

End Using

End Sub

End Module

Processus B
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;

class Program
{
// Process B:
static void Main(string[] args)
{
try
{
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("testmap"))
{

Mutex mutex = Mutex.OpenExisting("testmapmutex");


mutex.WaitOne();

using (MemoryMappedViewStream stream = mmf.CreateViewStream(1, 0))


{
BinaryWriter writer = new BinaryWriter(stream);
writer.Write(0);
}
mutex.ReleaseMutex();
}
}
catch (FileNotFoundException)
{
Console.WriteLine("Memory-mapped file does not exist. Run Process A first.");
}
}
}

Imports System.IO
Imports System.IO.MemoryMappedFiles
Imports System.Threading

Module Module1
' Process B:
Sub Main()
Try
Using mmf As MemoryMappedFile = MemoryMappedFile.OpenExisting("testmap")
Dim mTex As Mutex = Mutex.OpenExisting("testmapmutex")
mTex.WaitOne()
Using Stream As MemoryMappedViewStream = mmf.CreateViewStream(1, 0)
Dim writer As BinaryWriter = New BinaryWriter(Stream)
writer.Write(0)
End Using
mTex.ReleaseMutex()
End Using
Catch noFile As FileNotFoundException
Console.WriteLine("Memory-mapped file does not exist. Run Process A first." & vbCrLf &
noFile.Message)
End Try

End Sub

End Module

Processus C
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;

class Program
{
// Process C:
static void Main(string[] args)
{
try
{
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("testmap"))
{

Mutex mutex = Mutex.OpenExisting("testmapmutex");


mutex.WaitOne();

using (MemoryMappedViewStream stream = mmf.CreateViewStream(2, 0))


{
BinaryWriter writer = new BinaryWriter(stream);
writer.Write(1);
}
mutex.ReleaseMutex();
}
}
catch (FileNotFoundException)
{
Console.WriteLine("Memory-mapped file does not exist. Run Process A first, then B.");
}
}
}

Imports System.IO
Imports System.IO.MemoryMappedFiles
Imports System.Threading

Module Module1
' Process C:
Sub Main()
Try
Using mmf As MemoryMappedFile = MemoryMappedFile.OpenExisting("testmap")
Dim mTex As Mutex = Mutex.OpenExisting("testmapmutex")
mTex.WaitOne()
Using Stream As MemoryMappedViewStream = mmf.CreateViewStream(2, 0)
Dim writer As BinaryWriter = New BinaryWriter(Stream)
writer.Write(1)
End Using
mTex.ReleaseMutex()
End Using
Catch noFile As FileNotFoundException
Console.WriteLine("Memory-mapped file does not exist. Run Process A first, then B." & vbCrLf &
noFile.Message)
End Try

End Sub

End Module

Voir aussi
E/s de fichier et de flux
Internationalisation et localisation d’applications .NET
18/07/2020 • 5 minutes to read • Edit Online

Le développement d’une application mondialisable, notamment une application qui peut être localisée dans une
ou plusieurs langues, implique trois étapes : la mondialisation, l’étude de la localisabilité et la localisation.
Globalisation
Cette étape implique la conception et le codage d'une application indépendante des cultures et des langues qui
prend en charge les interfaces utilisateur localisées et les paramètres régionaux pour tous les utilisateurs. Elle
implique également de prendre des décisions relatives à la conception et à la programmation qui ne sont pas
basées sur des hypothèses spécifiques à la culture. Même si une application globalisée n'est pas localisée, elle est
néanmoins conçue et écrite pour pouvoir être ensuite localisée assez facilement en une ou plusieurs langues.
Révision de l’adaptabilité
Cette étape implique d’examiner le code et la conception d’une application pour vérifier qu’elle peut être localisée
facilement et identifier les obstacles éventuels à la localisation, et de vérifier que le code exécutable de l’application
est bien séparé de ses ressources. Si la phase de globalisation s'est déroulée correctement, l'examen de la
faisabilité de localisation confirmera les choix de conception et de codage effectués pendant la globalisation. La
phase d'examen de la faisabilité de localisation peut également identifier les problèmes restants afin de ne pas
avoir à modifier le code source d'une application pendant la phase de localisation.
Localisation
Cette étape implique la personnalisation d'une application pour des cultures ou des régions spécifiques. Si les
étapes de globalisation et de faisabilité de localisation ont été effectuées correctement, la localisation consiste
principalement à traduire l'interface utilisateur.
L'exécution de ces trois étapes offre deux avantages :
Vous n'avez plus à adapter a posteriori une application conçue pour prendre en charge une seule culture,
telle que l'anglais (États-Unis), pour prendre en charge d'autres cultures.
Il en résulte des applications localisées plus stables qui présentent moins de bogues.
.NET assure une prise en charge complète du développement d’applications internationalisables et localisées. En
particulier, plusieurs membres de type de la bibliothèque de classes .NET facilitent l’internationalisation en
retournant des valeurs qui reflètent les conventions de la culture de l’utilisateur actuel ou d’une culture spécifiée.
De plus, .NET prend en charge les assemblys satellites, qui simplifient le processus de localisation d’une
application.
Pour plus d'informations, consultez la documentation sur la globalisation.

Dans cette section


Globalisation
Décrit la première phase de création d'une application mondialisable, qui implique la conception et le codage
d'une application indépendante des cultures et des langues.
Globalisation et ICU .NET
Décrit comment la globalisation .NET utilise les composants internationaux pour Unicode (ICU).
Révision de l’adaptabilité
Décrit la seconde phase de création d'une application localisée, qui implique l'identification d'obstacles éventuels à
la localisation.
Localisation
Décrit la phase finale de la création d'une application localisée, qui implique la personnalisation de l'interface
utilisateur d'une application en fonction de régions ou de cultures spécifiques.
Opérations de chaînes indépendantes de la culture
Explique comment utiliser les méthodes et les classes .NET dépendantes de la culture par défaut pour obtenir des
résultats indépendants de la culture.
Bonnes pratiques pour développer des applications internationales
Décrit les meilleures pratiques en matière de globalisation, de localisation et de développement d'applications
ASP.NET mondialisables.

Informations de référence
Espace de noms System.Globalization
Contient des classes qui définissent des informations liées à la culture, notamment la langue, le pays ou la
région, les calendriers utilisés, les formats des dates, des monnaies et des nombres, ainsi que l'ordre de tri à
respecter pour les chaînes.
Espace de noms System.Resources
Fournit des classes pour créer, manipuler et utiliser des ressources.
Espace de noms System.Text
Contient des classes représentant les encodages de caractères ASCII, ANSI, Unicode et autres.
Resgen. exe (générateur de fichier de ressources)
Explique comment utiliser Resgen.exe pour convertir des fichiers .txt et des fichiers .resx (format de
ressource basé sur XML) en fichiers binaires .resources du Common Language Runtime.
Winres. exe (Windows Forms éditeur de ressources)
Explique comment utiliser Winres.exe pour localiser des formulaires Windows Forms.
Extension des métadonnées à l'aide des attributs
18/07/2020 • 2 minutes to read • Edit Online

Le common language runtime vous permet d’ajouter des déclarations descriptives de type mot clé, appelées
attributs, pour annoter les éléments de programmation comme des types, des champs, des méthodes et des
propriétés. Quand vous compilez votre code pour le runtime, il est converti en langage MSIL (Microsoft
Intermediate Language) et placé dans un fichier exécutable portable avec des métadonnées générées par le
compilateur. Les attributs vous permettent de placer des informations descriptives supplémentaires dans les
métadonnées, qui peuvent être extraites à l'aide des services de réflexion du runtime. Le compilateur crée des
attributs quand vous déclarez des instances de classes spéciales qui dérivent de System.Attribute.
Le .NET Framework utilise des attributs pour différentes raisons et pour résoudre un certain nombre de
problèmes. Les attributs décrivent comment sérialiser les données, spécifient des caractéristiques qui sont
utilisées pour appliquer la sécurité et limitent les optimisations du compilateur juste-à-temps (JIT) pour que le
code reste facile à déboguer. Les attributs peuvent également enregistrer le nom d'un fichier ou l'auteur du code,
ou bien contrôler la visibilité des contrôles et des membres pendant le développement des formulaires.

Rubriques connexes
IN T IT UL É DESC RIP T IO N

Application des attributs Décrit comment appliquer un attribut à un élément de votre


code.

Écriture des attributs personnalisés Décrit comment concevoir des classes d'attributs
personnalisés.

Récupération des informations stockées dans les attributs Décrit comment récupérer des attributs personnalisés pour
le code qui est chargé dans le contexte d'exécution.

Métadonnées et composants autodescriptifs Fournit une vue d'ensemble des métadonnées et décrit
comment elles sont implémentées dans un fichier exécutable
portable du .NET Framework.

Procédure : charger des assemblys dans le contexte de Explique comment récupérer les informations des attributs
réflexion uniquement personnalisés dans le contexte de réflexion uniquement.

Informations de référence
System.Attribute
Application des attributs
18/07/2020 • 5 minutes to read • Edit Online

Effectuez la procédure suivante pour appliquer un attribut à un élément de votre code.


1. Définissez un nouvel attribut ou utilisez un attribut existant en important son espace de noms à partir de
.NET Framework.
2. Appliquez l’attribut à l’élément de code en le plaçant immédiatement avant l’élément.
Chaque langage possède sa propre syntaxe d’attribut. Dans C++ et C#, l’attribut est entouré par des crochets
et séparé de l’élément par un espace blanc, qui peut inclure un saut de ligne. Dans Visual Basic, l’attribut est
entouré par des crochets angulaires et doit se trouver sur la même ligne logique. Le caractère de
continuation de ligne peut être utilisé si vous souhaitez un saut de ligne.
3. Spécifiez des paramètres positionnels et des paramètres nommés pour l’attribut.
Les paramètres positionnels sont requis et doivent précéder les paramètres nommés. Ils correspondent aux
paramètres d’un des constructeurs de l’attribut. Les paramètres nommés sont facultatifs et correspondent
aux propriétés de lecture/écriture de l’attribut. En C++ et C#, spécifiez name = value pour chaque paramètre
facultatif, où name est le nom de la propriété. Dans Visual Basic, spécifiez name := value .
L’attribut est émis dans des métadonnées lorsque vous compilez votre code et est disponible pour le common
language runtime et toute application ou tout outil personnalisé via les services de réflexion du runtime.
Par convention, tous les noms d’attribut se terminent par Attribute. Toutefois, plusieurs langages qui ciblent le
runtime, tels que Visual Basic et C#, ne nécessitent pas de spécifier le nom complet d’un attribut. Par exemple, si
vous souhaitez initialiser System.ObsoleteAttribute, vous devez uniquement le référencer en tant qu’attribut
obsolète .

Application d’un attribut à une méthode


L’exemple de code suivant montre comment déclarer System.ObsoleteAttribute , qui marque le code comme
obsolète. La chaîne "Will be removed in next version" est passée à l’attribut. Cet attribut provoque un
avertissement du compilateur qui affiche la chaîne passée lorsque le code que l’attribut décrit est appelé.
public ref class Example
{
// Specify attributes between square brackets in C#.
// This attribute is applied only to the Add method.
public:
[Obsolete("Will be removed in next version.")]
static int Add(int a, int b)
{
return (a + b);
}
};

ref class Test


{
public:
static void Main()
{
// This generates a compile-time warning.
int i = Example::Add(2, 2);
}
};

int main()
{
Test::Main();
}

public class Example


{
// Specify attributes between square brackets in C#.
// This attribute is applied only to the Add method.
[Obsolete("Will be removed in next version.")]
public static int Add(int a, int b)
{
return (a + b);
}
}

class Test
{
public static void Main()
{
// This generates a compile-time warning.
int i = Example.Add(2, 2);
}
}

Public Class Example


' Specify attributes between square brackets in C#.
' This attribute is applied only to the Add method.
<Obsolete("Will be removed in next version.")>
Public Shared Function Add(a As Integer, b As Integer) As Integer
Return a + b
End Function
End Class

Class Test
Public Shared Sub Main()
' This generates a compile-time warning.
Dim i As Integer = Example.Add(2, 2)
End Sub
End Class
Application d’attributs au niveau de l’assembly
Si vous souhaitez appliquer un attribut au niveau de l’assembly, utilisez le mot clé assembly ( Assembly en Visual
Basic). Le code suivant illustre l’attribut AssemblyTitleAttribute appliqué au niveau de l’assembly.

using namespace System::Reflection;


[assembly:AssemblyTitle("My Assembly")];

using System.Reflection;
[assembly:AssemblyTitle("My Assembly")]

Imports System.Reflection
<Assembly: AssemblyTitle("My Assembly")>

Lorsque cet attribut est appliqué, la chaîne "My Assembly" est placée dans le manifeste de l’assembly dans la partie
métadonnées du fichier. Vous pouvez afficher l’attribut à l’aide du Désassembleur MSIL (Ildasm.exe) ou en créant un
programme personnalisé pour récupérer l’attribut.

Voir aussi
Attributs
Récupération des informations stockées dans les attributs
Concepts
Attributs (C#)
Vue d’ensemble des attributs (Visual Basic)
Écriture des attributs personnalisés
18/07/2020 • 14 minutes to read • Edit Online

Pour concevoir vos propres attributs personnalisés, vous n’avez pas besoin de maîtriser les nombreux nouveaux
concepts. Si vous êtes familiarisé avec la programmation orientée objet et savez concevoir des classes, vous
possédez déjà la plupart des connaissances nécessaires. Les attributs personnalisés sont essentiellement des
classes traditionnelles qui dérivent directement ou indirectement de System.Attribute. Tout comme les classes
traditionnelles, les attributs personnalisés contiennent des méthodes qui stockent et récupèrent les données.
Les principales étapes permettant de concevoir correctement des classes d’attributs personnalisés sont les
suivantes :
Application d’AttributeUsageAttribute
Déclaration de la classe d’attributs
Déclarer des constructeurs
Déclaration des propriétés
Cette section décrit chacune de ces étapes et se termine par un exemple d’attribut personnalisé.

Application d’AttributeUsageAttribute
Une déclaration d’attribut personnalisé commence par System.AttributeUsageAttribute, qui définit certaines
caractéristiques clés de votre classe d’attributs. Par exemple, vous pouvez spécifier si votre attribut peut être hérité
par d’autres classes ou spécifier les éléments auxquels l’attribut peut être appliqué. Le fragment de code suivant
montre comment utiliser l’attribut AttributeUsageAttribute.

[AttributeUsage(AttributeTargets::All, Inherited = false, AllowMultiple = true)]

[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)]

<AttributeUsage(AttributeTargets.All, Inherited:=False, AllowMultiple:=True)>


Public Class SomeClass
Inherits Attribute
'...
End Class

AttributeUsageAttribute possède trois membres importants pour la création d’attributs personnalisés :


AttributeTargets, Inheritedet AllowMultiple.
Membre AttributeTargets
Dans l’exemple précédent, AttributeTargets.All est spécifié, ce qui indique que cet attribut peut être appliqué à tous
les éléments de programme. Vous pouvez également spécifier AttributeTargets.Class, qui indique que votre attribut
peut être appliqué uniquement à une classe, ou AttributeTargets.Method, qui indique que votre attribut peut être
appliqué uniquement à une méthode. Tous les éléments de programme peuvent être marqués pour description par
un attribut personnalisé de cette manière.
Vous pouvez également transmettre plusieurs valeurs de AttributeTargets. Le fragment de code suivant spécifie
qu’un attribut personnalisé peut être appliqué à n’importe quelle classe ou méthode.
[AttributeUsage(AttributeTargets::Class | AttributeTargets::Method)]

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]

<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Method)>
Public Class SomeOtherClass
Inherits Attribute
'...
End Class

Propriété Inherited
La propriété AttributeUsageAttribute.Inherited indique si votre attribut peut être hérité par les classes qui sont
dérivées des classes auxquelles votre attribut est appliqué. Cette propriété reçoit un indicateur true (par défaut)
ou false . Dans l’exemple suivant, MyAttribute affiche une valeur par défaut Inherited de true , tandis
YourAttribute affiche une valeur Inherited de false .

// This defaults to Inherited = true.


public ref class MyAttribute : Attribute
{
//...
};

[AttributeUsage(AttributeTargets::Method, Inherited = false)]


public ref class YourAttribute : Attribute
{
//...
};

// This defaults to Inherited = true.


public class MyAttribute : Attribute
{
//...
}

[AttributeUsage(AttributeTargets.Method, Inherited = false)]


public class YourAttribute : Attribute
{
//...
}

' This defaults to Inherited = true.


Public Class MyAttribute
Inherits Attribute
'...
End Class

<AttributeUsage(AttributeTargets.Method, Inherited:=False)>
Public Class YourAttribute
Inherits Attribute
'...
End Class

Les deux attributs sont ensuite appliqués à une méthode dans la classe de base MyClass .
public ref class MyClass
{
public:
[MyAttribute]
[YourAttribute]
virtual void MyMethod()
{
//...
}
};

public class MyClass


{
[MyAttribute]
[YourAttribute]
public virtual void MyMethod()
{
//...
}
}

Public Class MeClass


<MyAttribute>
<YourAttribute>
Public Overridable Sub MyMethod()
'...
End Sub
End Class

Enfin, la classe YourClass est héritée de la classe de base MyClass . La méthode MyMethod affiche MyAttribute ,
mais pas YourAttribute .

public ref class YourClass : MyClass


{
public:
// MyMethod will have MyAttribute but not YourAttribute.
virtual void MyMethod() override
{
//...
}

};

public class YourClass : MyClass


{
// MyMethod will have MyAttribute but not YourAttribute.
public override void MyMethod()
{
//...
}
}
Public Class YourClass
Inherits MeClass
' MyMethod will have MyAttribute but not YourAttribute.
Public Overrides Sub MyMethod()
'...
End Sub

End Class

Propriété AllowMultiple
La propriété AttributeUsageAttribute.AllowMultiple indique si plusieurs instances de votre attribut peuvent exister
sur un élément. Si la valeur est true , plusieurs instances sont autorisées, si la valeur est false (par défaut), une
seule instance est autorisée.
Dans l’exemple suivant, MyAttribute affiche une valeur par défaut AllowMultiple de false , tandis YourAttribute
affiche une valeur de true .

//This defaults to AllowMultiple = false.


public ref class MyAttribute : Attribute
{
};

[AttributeUsage(AttributeTargets::Method, AllowMultiple = true)]


public ref class YourAttribute : Attribute
{
};

//This defaults to AllowMultiple = false.


public class MyAttribute : Attribute
{
}

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]


public class YourAttribute : Attribute
{
}

' This defaults to AllowMultiple = false.


Public Class MyAttribute
Inherits Attribute
End Class

<AttributeUsage(AttributeTargets.Method, AllowMultiple:=true)>
Public Class YourAttribute
Inherits Attribute
End Class

Quand plusieurs instances de ces attributs sont appliquées, MyAttribute génère une erreur du compilateur.
L’exemple de code suivant illustre l’utilisation valide de YourAttribute et l’utilisation non valide de MyAttribute .
public ref class MyClass
{
public:
// This produces an error.
// Duplicates are not allowed.
[MyAttribute]
[MyAttribute]
void MyMethod()
{
//...
}

// This is valid.
[YourAttribute]
[YourAttribute]
void YourMethod()
{
//...
}
};

public class MyClass


{
// This produces an error.
// Duplicates are not allowed.
[MyAttribute]
[MyAttribute]
public void MyMethod()
{
//...
}

// This is valid.
[YourAttribute]
[YourAttribute]
public void YourMethod()
{
//...
}
}

Public Class MyClass


' This produces an error.
' Duplicates are not allowed.
<MyAttribute>
<MyAttribute>
Public Sub MyMethod()
'...
End Sub

' This is valid.


<YourAttribute>
<YourAttribute>
Public Sub YourMethod()
'...
End Sub
End Class

Si la propriété AllowMultiple et la propriété Inherited ont la valeur true , une classe héritée d’une autre classe peut
hériter d’un attribut et avoir une autre instance du même attribut appliquée dans la même classe enfant. Si
AllowMultiple a la valeur false , les valeurs des attributs de la classe parente sont remplacées par les nouvelles
instances du même attribut dans la classe enfant.
Déclaration de la classe d’attributs
Une fois que vous avez appliqué AttributeUsageAttribute, vous pouvez commencer à définir les particularités de
votre attribut. La déclaration d’une classe d’attributs ressemble à la déclaration d’une classe traditionnelle, comme
illustré dans le code suivant.

[AttributeUsage(AttributeTargets::Method)]
public ref class MyAttribute : Attribute
{
// . . .
};

[AttributeUsage(AttributeTargets.Method)]
public class MyAttribute : Attribute
{
// . . .
}

<AttributeUsage(AttributeTargets.Method)>
Public Class MyAttribute
Inherits Attribute
' . . .
End Class

Cette définition de l’attribut illustre les points suivants :


Les classes d’attributs doivent être déclarées comme des classes publiques.
Par convention, le nom de la classe d’attributs se termine par le mot Attribute . Même si elle n’est pas
obligatoire, cette convention est recommandée pour une meilleure lisibilité. Quand l’attribut est appliqué,
l’inclusion du mot Attribute est facultative.
Toutes les classes d’attributs doivent hériter directement ou indirectement de System.Attribute.
Dans Microsoft Visual Basic, toutes les classes d’attributs personnalisés doivent avoir l’attribut
System.AttributeUsageAttribute.

Déclaration des constructeurs


Les attributs sont initialisés avec des constructeurs de la même façon que les classes traditionnelles. Le fragment de
code suivant illustre un constructeur d’attribut classique. Ce constructeur public accepte un paramètre et définit
une variable membre égale à sa valeur.

MyAttribute(bool myvalue)
{
this->myvalue = myvalue;
}

public MyAttribute(bool myvalue)


{
this.myvalue = myvalue;
}
Public Sub New(myvalue As Boolean)
Me.myvalue = myvalue
End Sub

Vous pouvez surcharger le constructeur pour qu’il reçoive différentes combinaisons de valeurs. Si vous définissez
également une propriété pour votre classe d’attributs personnalisés, vous pouvez utiliser une combinaison de
paramètres nommés et positionnels lors de l’initialisation de l’attribut. En général, vous définissez tous les
paramètres obligatoires comme des paramètres positionnels et tous les paramètres facultatifs comme des
paramètres nommés. Dans ce cas, l’attribut ne peut pas être initialisé sans le paramètre obligatoire. Tous les autres
paramètres sont facultatifs. Notez que dans Visual Basic, les constructeurs d’une classe d’attributs ne doivent pas
utiliser d’argument ParamArray.
L’exemple de code suivant montre comment un attribut qui utilise le constructeur précédent peut être appliqué à
l’aide de paramètres obligatoires et facultatifs. Il suppose que l’attribut a une valeur booléenne obligatoire et une
propriété de chaîne facultative.

// One required (positional) and one optional (named) parameter are applied.
[MyAttribute(false, OptionalParameter = "optional data")]
public ref class SomeClass
{
//...
};
// One required (positional) parameter is applied.
[MyAttribute(false)]
public ref class SomeOtherClass
{
//...
};

// One required (positional) and one optional (named) parameter are applied.
[MyAttribute(false, OptionalParameter = "optional data")]
public class SomeClass
{
//...
}
// One required (positional) parameter is applied.
[MyAttribute(false)]
public class SomeOtherClass
{
//...
}

' One required (positional) and one optional (named) parameter are applied.
<MyAttribute(false, OptionalParameter:="optional data")>
Public Class SomeClass
'...
End Class

' One required (positional) parameter is applied.


<MyAttribute(false)>
Public Class SomeOtherClass
'...
End Class

Déclaration des propriétés


Si vous voulez définir un paramètre nommé ou fournir un moyen facile de retourner les valeurs stockées par votre
attribut, déclarez une propriété. Les propriétés d’attribut doivent être déclarées comme des entités publiques avec
une description du type de données à retourner. Définissez la variable qui contient la valeur de votre propriété et
associez-la aux méthodes get et set . L’exemple de code suivant montre comment implémenter une propriété
simple dans votre attribut.

property bool MyProperty


{
bool get() {return this->myvalue;}
void set(bool value) {this->myvalue = value;}
}

public bool MyProperty


{
get {return this.myvalue;}
set {this.myvalue = value;}
}

Public Property MyProperty As Boolean


Get
Return Me.myvalue
End Get
Set
Me.myvalue = Value
End Set
End Property

exemple d’attribut personnalisé


Cette section intègre les informations précédentes et montre comment concevoir un attribut simple qui documente
des informations sur l’auteur d’une section de code. L’attribut de cet exemple stocke le nom et le niveau du
programmeur, et indique si le code a été révisé. Il utilise trois variables privées pour stocker les valeurs réelles à
enregistrer. Chaque variable est représentée par une propriété publique qui obtient et définit les valeurs. Enfin, le
constructeur est défini avec deux paramètres obligatoires.
[AttributeUsage(AttributeTargets::All)]
public ref class DeveloperAttribute : Attribute
{
// Private fields.
private:
String^ name;
String^ level;
bool reviewed;

public:
// This constructor defines two required parameters: name and level.

DeveloperAttribute(String^ name, String^ level)


{
this->name = name;
this->level = level;
this->reviewed = false;
}

// Define Name property.


// This is a read-only attribute.

virtual property String^ Name


{
String^ get() {return name;}
}

// Define Level property.


// This is a read-only attribute.

virtual property String^ Level


{
String^ get() {return level;}
}

// Define Reviewed property.


// This is a read/write attribute.

virtual property bool Reviewed


{
bool get() {return reviewed;}
void set(bool value) {reviewed = value;}
}
};
[AttributeUsage(AttributeTargets.All)]
public class DeveloperAttribute : Attribute
{
// Private fields.
private string name;
private string level;
private bool reviewed;

// This constructor defines two required parameters: name and level.

public DeveloperAttribute(string name, string level)


{
this.name = name;
this.level = level;
this.reviewed = false;
}

// Define Name property.


// This is a read-only attribute.

public virtual string Name


{
get {return name;}
}

// Define Level property.


// This is a read-only attribute.

public virtual string Level


{
get {return level;}
}

// Define Reviewed property.


// This is a read/write attribute.

public virtual bool Reviewed


{
get {return reviewed;}
set {reviewed = value;}
}
}
<AttributeUsage(AttributeTargets.All)>
Public Class DeveloperAttribute
Inherits Attribute
' Private fields.
Private myname As String
Private mylevel As String
Private myreviewed As Boolean

' This constructor defines two required parameters: name and level.

Public Sub New(name As String, level As String)


Me.myname = name
Me.mylevel = level
Me.myreviewed = False
End Sub

' Define Name property.


' This is a read-only attribute.

Public Overridable ReadOnly Property Name() As String


Get
Return myname
End Get
End Property

' Define Level property.


' This is a read-only attribute.

Public Overridable ReadOnly Property Level() As String


Get
Return mylevel
End Get
End Property

' Define Reviewed property.


' This is a read/write attribute.

Public Overridable Property Reviewed() As Boolean


Get
Return myreviewed
End Get
Set
myreviewed = value
End Set
End Property
End Class

Vous pouvez appliquer cet attribut à l’aide du nom complet, DeveloperAttribute , ou à l’aide du nom abrégé,
Developer , de l’une des manières suivantes.

[Developer("Joan Smith", "1")]

-or-

[Developer("Joan Smith", "1", Reviewed = true)]

[Developer("Joan Smith", "1")]

-or-

[Developer("Joan Smith", "1", Reviewed = true)]


<Developer("Joan Smith", "1")>

-or-

<Developer("Joan Smith", "1", Reviewed := true)>

Le premier exemple montre l’attribut appliqué uniquement avec les paramètres nommés obligatoires, tandis que le
deuxième exemple montre l’attribut appliqué avec les paramètres obligatoires et facultatifs.

Voir aussi
System.Attribute
System.AttributeUsageAttribute
Attributs
Récupération des informations stockées dans les
attributs
18/07/2020 • 12 minutes to read • Edit Online

La récupération d’un attribut personnalisé est un processus simple. Tout d’abord, déclarez une instance de l’attribut
que vous souhaitez récupérer. Ensuite, utilisez la méthode Attribute.GetCustomAttribute pour initialiser le nouvel
attribut à la valeur de l’attribut que vous souhaitez récupérer. Une fois le nouvel attribut initialisé, vous utilisez
simplement ses propriétés pour obtenir les valeurs.

IMPORTANT
Cette rubrique explique comment récupérer des attributs pour le code chargé dans le contexte d'exécution. Pour récupérer
les attributs du code chargé dans le contexte de réflexion uniquement, vous devez utiliser la classe CustomAttributeData,
comme indiqué dans Guide pratique pour charger des assemblys dans le contexte de réflexion uniquement.

Cette section décrit les méthodes suivantes pour récupérer des attributs :
Récupération d’une seule instance d’un attribut
Récupération de plusieurs instances d’un attribut appliqué à la même étendue
Récupération de plusieurs instances d’un attribut appliqué à différentes portées

Récupération d’une seule instance d’un attribut


Dans l’exemple suivant, la méthode DeveloperAttribute (décrite dans la section précédente) est appliquée à la
classe MainApp au niveau de la classe. La méthode GetAttribute utilise GetCustomAttribute pour récupérer les
valeurs stockées dans DeveloperAttribute au niveau de la classe avant de les afficher dans la console.
using namespace System;
using namespace System::Reflection;
using namespace CustomCodeAttributes;

[Developer("Joan Smith", "42", Reviewed = true)]


ref class MainApp
{
public:
static void Main()
{
// Call function to get and display the attribute.
GetAttribute(MainApp::typeid);
}

static void GetAttribute(Type^ t)


{
// Get instance of the attribute.
DeveloperAttribute^ MyAttribute =
(DeveloperAttribute^) Attribute::GetCustomAttribute(t, DeveloperAttribute::typeid);

if (MyAttribute == nullptr)
{
Console::WriteLine("The attribute was not found.");
}
else
{
// Get the Name value.
Console::WriteLine("The Name Attribute is: {0}." , MyAttribute->Name);
// Get the Level value.
Console::WriteLine("The Level Attribute is: {0}." , MyAttribute->Level);
// Get the Reviewed value.
Console::WriteLine("The Reviewed Attribute is: {0}." , MyAttribute->Reviewed);
}
}
};
using System;
using System.Reflection;
using CustomCodeAttributes;

[Developer("Joan Smith", "42", Reviewed = true)]


class MainApp
{
public static void Main()
{
// Call function to get and display the attribute.
GetAttribute(typeof(MainApp));
}

public static void GetAttribute(Type t)


{
// Get instance of the attribute.
DeveloperAttribute MyAttribute =
(DeveloperAttribute) Attribute.GetCustomAttribute(t, typeof (DeveloperAttribute));

if (MyAttribute == null)
{
Console.WriteLine("The attribute was not found.");
}
else
{
// Get the Name value.
Console.WriteLine("The Name Attribute is: {0}." , MyAttribute.Name);
// Get the Level value.
Console.WriteLine("The Level Attribute is: {0}." , MyAttribute.Level);
// Get the Reviewed value.
Console.WriteLine("The Reviewed Attribute is: {0}." , MyAttribute.Reviewed);
}
}
}

Imports System.Reflection
Imports CustomCodeAttributes

<Developer("Joan Smith", "42", Reviewed:=True)>


Class MainApp
Public Shared Sub Main()
' Call function to get and display the attribute.
GetAttribute(GetType(MainApp))
End Sub

Public Shared Sub GetAttribute(t As Type)


' Get instance of the attribute.
Dim MyAttribute As DeveloperAttribute =
CType(Attribute.GetCustomAttribute(t, GetType(DeveloperAttribute)), DeveloperAttribute)

If MyAttribute Is Nothing Then


Console.WriteLine("The attribute was not found.")
Else
' Get the Name value.
Console.WriteLine("The Name Attribute is: {0}.", MyAttribute.Name)
' Get the Level value.
Console.WriteLine("The Level Attribute is: {0}.", MyAttribute.Level)
' Get the Reviewed value.
Console.WriteLine("The Reviewed Attribute is: {0}.", MyAttribute.Reviewed)
End If
End Sub
End Class

Lorsqu’il est exécuté, ce programme affiche le texte suivant.


The Name Attribute is: Joan Smith.
The Level Attribute is: 42.
The Reviewed Attribute is: True.

Si l’attribut est introuvable, la méthode GetCustomAttribute initialise MyAttribute à une valeur null. Cet exemple
recherche une telle instance dans MyAttribute et avertit l’utilisateur si aucun attribut n’est trouvé. Si aucune valeur
DeveloperAttribute n’est trouvée dans l’étendue de la classe, le message suivant s’affiche dans la console.

The attribute was not found.

Cet exemple suppose que l’attribut est défini dans l’espace de noms actuel. N’oubliez pas d’importer l’espace de
noms dans lequel se trouve la définition de l’attribut si celle-ci ne figure pas dans l’espace de noms actuel.

Récupération de plusieurs instances d’un attribut appliqué à la même


étendue
Dans l’exemple précédent, la classe à inspecter et l’attribut spécifique à rechercher sont transmis à
GetCustomAttribute. Ce code fonctionne correctement uniquement si une instance d’un attribut est appliquée au
niveau de la classe. Toutefois, si plusieurs instances d’un attribut sont appliquées au niveau de la même classe, la
méthode GetCustomAttribute ne récupère pas toutes les informations. Dans les cas où plusieurs instances du
même attribut sont appliquées à la même étendue, vous pouvez utiliser Attribute.GetCustomAttributes pour placer
toutes les instances d’un attribut dans un tableau. Par exemple, si deux instances de DeveloperAttribute sont
appliquées au niveau de la même classe, la méthode GetAttribute peut être modifiée pour afficher les
informations trouvées dans les deux attributs. N’oubliez pas que pour appliquer plusieurs attributs au même
niveau, l’attribut doit être défini avec la propriété AllowMultiple définie sur true dans AttributeUsageAttribute.
L’exemple de code suivant montre comment utiliser la méthode GetCustomAttributes pour créer un tableau qui
référence toutes les instances de DeveloperAttribute dans une classe donnée. Les valeurs de tous les attributs sont
ensuite affichées dans la console.

public:
static void GetAttribute(Type^ t)
{
array<DeveloperAttribute^>^ MyAttributes =
(array<DeveloperAttribute^>^) Attribute::GetCustomAttributes(t, DeveloperAttribute::typeid);

if (MyAttributes->Length == 0)
{
Console::WriteLine("The attribute was not found.");
}
else
{
for (int i = 0 ; i < MyAttributes->Length; i++)
{
// Get the Name value.
Console::WriteLine("The Name Attribute is: {0}." , MyAttributes[i]->Name);
// Get the Level value.
Console::WriteLine("The Level Attribute is: {0}." , MyAttributes[i]->Level);
// Get the Reviewed value.
Console::WriteLine("The Reviewed Attribute is: {0}.", MyAttributes[i]->Reviewed);
}
}
}
public static void GetAttribute(Type t)
{
DeveloperAttribute[] MyAttributes =
(DeveloperAttribute[]) Attribute.GetCustomAttributes(t, typeof (DeveloperAttribute));

if (MyAttributes.Length == 0)
{
Console.WriteLine("The attribute was not found.");
}
else
{
for (int i = 0 ; i < MyAttributes.Length ; i++)
{
// Get the Name value.
Console.WriteLine("The Name Attribute is: {0}." , MyAttributes[i].Name);
// Get the Level value.
Console.WriteLine("The Level Attribute is: {0}." , MyAttributes[i].Level);
// Get the Reviewed value.
Console.WriteLine("The Reviewed Attribute is: {0}.", MyAttributes[i].Reviewed);
}
}
}

Public Shared Sub GetAttribute(t As Type)


Dim MyAttributes() As DeveloperAttribute =
CType(Attribute.GetCustomAttributes(t, GetType(DeveloperAttribute)), DeveloperAttribute())

If MyAttributes.Length = 0 Then
Console.WriteLine("The attribute was not found.")
Else
For i As Integer = 0 To MyAttributes.Length - 1
' Get the Name value.
Console.WriteLine("The Name Attribute is: {0}.", MyAttributes(i).Name)
' Get the Level value.
Console.WriteLine("The Level Attribute is: {0}.", MyAttributes(i).Level)
' Get the Reviewed value.
Console.WriteLine("The Reviewed Attribute is: {0}.", MyAttributes(i).Reviewed)
Next i
End If
End Sub

Si aucun attribut n’est trouvé, ce code avertit l’utilisateur. Sinon, les informations contenues dans les deux instances
de DeveloperAttribute s’affichent.

Récupération de plusieurs instances d’un attribut appliqué à différentes


étendues
Les méthodes GetCustomAttributes et GetCustomAttribute ne recherchent pas une classe entière et retournent
toutes les instances d’un attribut dans cette classe. Elles recherchent plutôt une seule méthode spécifiée ou un
membre à la fois. Si vous utilisez une classe avec le même attribut appliqué à chaque membre et que vous
souhaitez récupérer les valeurs de tous les attributs appliqués à ces membres, vous devez fournir chaque méthode
ou membre individuellement à GetCustomAttributes et à GetCustomAttribute .
L’exemple de code suivant prend une classe en tant que paramètre et recherche l’attribut DeveloperAttribute
(défini précédemment) au niveau de la classe et dans chaque méthode individuelle de cette classe.
public:
static void GetAttribute(Type^ t)
{
DeveloperAttribute^ att;

// Get the class-level attributes.

// Put the instance of the attribute on the class level in the att object.
att = (DeveloperAttribute^) Attribute::GetCustomAttribute (t, DeveloperAttribute::typeid);

if (att == nullptr)
{
Console::WriteLine("No attribute in class {0}.\n", t->ToString());
}
else
{
Console::WriteLine("The Name Attribute on the class level is: {0}.", att->Name);
Console::WriteLine("The Level Attribute on the class level is: {0}.", att->Level);
Console::WriteLine("The Reviewed Attribute on the class level is: {0}.\n", att->Reviewed);
}

// Get the method-level attributes.

// Get all methods in this class, and put them


// in an array of System.Reflection.MemberInfo objects.
array<MemberInfo^>^ MyMemberInfo = t->GetMethods();

// Loop through all methods in this class that are in the


// MyMemberInfo array.
for (int i = 0; i < MyMemberInfo->Length; i++)
{
att = (DeveloperAttribute^) Attribute::GetCustomAttribute(MyMemberInfo[i],
DeveloperAttribute::typeid);
if (att == nullptr)
{
Console::WriteLine("No attribute in member function {0}.\n" , MyMemberInfo[i]->ToString());
}
else
{
Console::WriteLine("The Name Attribute for the {0} member is: {1}.",
MyMemberInfo[i]->ToString(), att->Name);
Console::WriteLine("The Level Attribute for the {0} member is: {1}.",
MyMemberInfo[i]->ToString(), att->Level);
Console::WriteLine("The Reviewed Attribute for the {0} member is: {1}.\n",
MyMemberInfo[i]->ToString(), att->Reviewed);
}
}
}
public static void GetAttribute(Type t)
{
DeveloperAttribute att;

// Get the class-level attributes.

// Put the instance of the attribute on the class level in the att object.
att = (DeveloperAttribute) Attribute.GetCustomAttribute (t, typeof (DeveloperAttribute));

if (att == null)
{
Console.WriteLine("No attribute in class {0}.\n", t.ToString());
}
else
{
Console.WriteLine("The Name Attribute on the class level is: {0}.", att.Name);
Console.WriteLine("The Level Attribute on the class level is: {0}.", att.Level);
Console.WriteLine("The Reviewed Attribute on the class level is: {0}.\n", att.Reviewed);
}

// Get the method-level attributes.

// Get all methods in this class, and put them


// in an array of System.Reflection.MemberInfo objects.
MemberInfo[] MyMemberInfo = t.GetMethods();

// Loop through all methods in this class that are in the


// MyMemberInfo array.
for (int i = 0; i < MyMemberInfo.Length; i++)
{
att = (DeveloperAttribute) Attribute.GetCustomAttribute(MyMemberInfo[i], typeof (DeveloperAttribute));
if (att == null)
{
Console.WriteLine("No attribute in member function {0}.\n" , MyMemberInfo[i].ToString());
}
else
{
Console.WriteLine("The Name Attribute for the {0} member is: {1}.",
MyMemberInfo[i].ToString(), att.Name);
Console.WriteLine("The Level Attribute for the {0} member is: {1}.",
MyMemberInfo[i].ToString(), att.Level);
Console.WriteLine("The Reviewed Attribute for the {0} member is: {1}.\n",
MyMemberInfo[i].ToString(), att.Reviewed);
}
}
}
Public Shared Sub GetAttribute(t As Type)
Dim att As DeveloperAttribute

' Get the class-level attributes.

' Put the instance of the attribute on the class level in the att object.
att = CType(Attribute.GetCustomAttribute(t, GetType(DeveloperAttribute)), DeveloperAttribute)

If att Is Nothing
Console.WriteLine("No attribute in class {0}.\n", t.ToString())
Else
Console.WriteLine("The Name Attribute on the class level is: {0}.", att.Name)
Console.WriteLine("The Level Attribute on the class level is: {0}.", att.Level)
Console.WriteLine("The Reviewed Attribute on the class level is: {0}.\n", att.Reviewed)
End If

' Get the method-level attributes.

' Get all methods in this class, and put them


' in an array of System.Reflection.MemberInfo objects.
Dim MyMemberInfo() As MemberInfo = t.GetMethods()

' Loop through all methods in this class that are in the
' MyMemberInfo array.
For i As Integer = 0 To MyMemberInfo.Length - 1
att = CType(Attribute.GetCustomAttribute(MyMemberInfo(i), _
GetType(DeveloperAttribute)), DeveloperAttribute)
If att Is Nothing Then
Console.WriteLine("No attribute in member function {0}.\n", MyMemberInfo(i).ToString())
Else
Console.WriteLine("The Name Attribute for the {0} member is: {1}.",
MyMemberInfo(i).ToString(), att.Name)
Console.WriteLine("The Level Attribute for the {0} member is: {1}.",
MyMemberInfo(i).ToString(), att.Level)
Console.WriteLine("The Reviewed Attribute for the {0} member is: {1}.\n",
MyMemberInfo(i).ToString(), att.Reviewed)
End If
Next
End Sub

Si aucune instance de l’attribut DeveloperAttribute n’est trouvée au niveau de la méthode ou de la classe, la


méthode GetAttribute avertit l’utilisateur qu’aucun attribut n’a été trouvé puis affiche le nom de la méthode ou
classe qui ne contient pas cet attribut. Si un attribut est trouvé, les champs Name , Level et Reviewed apparaissent
dans la console.
Vous pouvez utiliser les membres de la classe Type pour obtenir les méthodes individuelles et les membres de la
classe transmise. Cet exemple interroge d’abord l’objet Type pour obtenir des informations sur les attributs au
niveau de la classe. Puis il utilise Type.GetMethods pour placer des instances de toutes les méthodes dans un
tableau d’objets System.Reflection.MemberInfo pour récupérer des informations sur les attributs au niveau de la
méthode. Vous pouvez également utiliser la méthode Type.GetProperties pour vérifier les attributs au niveau de la
propriété, ou Type.GetConstructors pour vérifier les attributs au niveau du constructeur.

Voir aussi
System.Type
Attribute.GetCustomAttribute
Attribute.GetCustomAttributes
Attributs
Règles de conception de .NET Framework
18/07/2020 • 3 minutes to read

Cette section fournit des instructions pour concevoir des bibliothèques qui étendent et interagissent avec l'
.NET Framework. L’objectif est d’aider les concepteurs de bibliothèques à garantir la cohérence et la facilité
d’utilisation des API en fournissant un modèle de programmation unifié qui est indépendant du langage de
programmation utilisé pour le développement. Nous vous recommandons de suivre ces instructions de
conception lors du développement de classes et de composants qui étendent les .NET Framework. Une
conception incohérente de la bibliothèque affecte la productivité des développeurs et déconseille l’adoption.
Les instructions sont organisées en tant que recommandations simples précédées des termes Do ,,
Consider Avoid et Do not . Ces instructions sont destinées à aider les concepteurs de bibliothèques de
classes à comprendre les compromis entre les différentes solutions. Il peut y avoir des situations où une
bonne conception de bibliothèque exige que vous violiez ces règles de conception. Ce cas de figure doit être
rare et il est important que vous ayez une raison claire et attrayante pour votre décision.
Ces instructions sont extraites des règles de conception de la structure Book : conventions, idiomes et
modèles pour les bibliothèques .net réutilisables, 2e édition, par Krzysztof Cwalina et Brad Abrams.

Dans cette section


Instructions d’affectation de noms
Fournit des instructions pour nommer les assemblys, les espaces de noms, les types et les membres dans les
bibliothèques de classes.
Règles de conception de type
Fournit des instructions pour l’utilisation des classes, des interfaces, des énumérations, des structures et des
types statiques et abstraits.
Recommandations en matière de conception de membres
Fournit des indications sur la conception et l’utilisation des propriétés, des méthodes, des constructeurs, des
champs, des événements, des opérateurs et des paramètres.
Conception en vue de l’extensibilité
Traite des mécanismes d’extensibilité tels que le sous-classing, l’utilisation d’événements, de membres
virtuels et de rappels, et explique comment choisir les mécanismes qui répondent le mieux aux besoins de
votre infrastructure.
Instructions de conception pour les exceptions
Décrit les instructions de conception pour la conception, la levée et l’interception des exceptions.
Instructions d’utilisation
Fournit des instructions pour l’utilisation de types communs tels que des tableaux, des attributs et des
collections, la prise en charge de la sérialisation et la surcharge des opérateurs d’égalité.
Modèles de conception courants
Fournit des instructions pour le choix et l’implémentation des propriétés de dépendance.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines:
Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad
Abrams, publié le 22 octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le
développement Microsoft Windows.

Voir aussi
Vue d’ensemble
Guide de développement
Indications concernant l'attribution d'un nom
18/07/2020 • 2 minutes to read

À la suite d’un ensemble cohérent de conventions d’affectation de noms dans le développement d’une
infrastructure, il peut s’agir d’une contribution majeure à la facilité d’utilisation du Framework. Il permet à un
grand nombre de développeurs d’utiliser le Framework sur des projets largement séparés. Au-delà de la
cohérence de la forme, les noms des éléments de l’infrastructure doivent être facilement compris et doivent
communiquer avec la fonction de chaque élément.
L’objectif de ce chapitre est de fournir un ensemble cohérent de conventions d’affectation de noms qui se traduit
par des noms qui prennent un sens immédiat pour les développeurs.
Bien que l’adoption de ces conventions d’affectation de noms en tant que directives de développement de code
générales produirait des noms plus cohérents dans votre code, vous ne devez les appliquer qu’aux API qui sont
exposées publiquement (types et membres publics ou protégés, et interfaces implémentées explicitement).

Dans cette section


Conventions de mise en majuscules
Conventions d’affectation de noms générales
Noms d’assemblys et de dll
Noms des espaces de noms
Noms de classes, de structures et d’interfaces
Noms des membres de type
Paramètres d’attribution de noms
Attribution de noms aux ressources
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft
Windows.

Voir aussi
Directives de conception d’infrastructure
Conventions de mise en majuscules
18/07/2020 • 5 minutes to read

Les instructions de ce chapitre présentent une méthode simple pour l’utilisation de cas qui, lorsqu’elle est appliquée
de manière cohérente, facilite la lecture des identificateurs des types, des membres et des paramètres.

Règles de mise en majuscules pour les identificateurs


Pour différencier les mots dans un identificateur, mettez en majuscule la première lettre de chaque mot dans
l’identificateur. N’utilisez pas de traits de soulignement pour différencier des mots, ou à cette question, n’importe
où dans les identificateurs. Il existe deux façons de mettre des identificateurs en majuscules, en fonction de
l’utilisation de l’identificateur :
PascalCasing
camelCasing
La Convention PascalCasing, utilisée pour tous les identificateurs à l’exception des noms de paramètres, met en
majuscule le premier caractère de chaque mot (y compris les acronymes de longueur de deux lettres), comme
indiqué dans les exemples suivants :
PropertyDescriptor HtmlTag

Un cas spécial est fait pour les acronymes à deux lettres dans lesquels les deux lettres sont en majuscules, comme
indiqué dans l’identificateur suivant :
IOStream

La Convention camelCasing, utilisée uniquement pour les noms de paramètres, met en majuscule le premier
caractère de chaque mot, à l’exception du premier mot, comme indiqué dans les exemples suivants. Comme
l’illustre l’exemple, les acronymes à deux lettres qui commencent un identificateur en casse mixte sont à la fois en
minuscules.
propertyDescriptor ioStream htmlTag

✔ Utilisez PascalCasing pour tous les noms de membre, de type et d’espace de noms publics composés de

plusieurs mots.
️ Utilisez camelCasing pour les noms de paramètres.

Le tableau suivant décrit les règles de mise en majuscules pour les différents types d’identificateurs.

IDEN T IF IC AT EUR C A SSE EXEM P L E

Espace de noms Casse namespace System.Security { ... }

Type Casse public class StreamReader { ... }

Interface Casse public interface IEnumerable {


... }
IDEN T IF IC AT EUR C A SSE EXEM P L E

Méthode Casse public class Object {


public virtual string ToString();
}

Propriété Casse public class String {


public int Length { get; }
}

Événement Casse public class Process {


public event EventHandler Exited;
}

Champ Casse public class MessageQueue {


public static readonly TimeSpan
InfiniteTimeout;
}
public struct UInt32 {
public const Min = 0;
}

Valeur enum Casse public enum FileMode {


Append,
...
}

Paramètre mixte public class Convert {


public static int ToInt32(string
value);
}

Mettre en majuscules les mots composés et les termes courants


La plupart des termes composés sont traités comme des mots simples à des fins de mise en majuscules.
❌NE pas mettre en majuscules chaque mot dans les mots composés « fermés ».
Il s’agit de mots composés écrits sous la forme d’un mot unique, tel qu’un point de terminaison. Dans le cadre des
instructions de la casse, traitez un mot composé fermé comme un mot unique. Utilisez un dictionnaire actuel pour
déterminer si un mot composé est écrit sous forme fermée.

C A SSE M IXT E N OT

BitFlag bitFlag Bitflag

Callback callback CallBack

Canceled canceled Cancelled

DoNot doNot Don't

Email email EMail


C A SSE M IXT E N OT

Endpoint endpoint EndPoint

FileName fileName Filename

Gridline gridline GridLine

Hashtable hashtable HashTable

Id id ID

Indexes indexes Indices

LogOff logOff LogOut

LogOn logOn LogIn

Metadata metadata MetaData, metaData

Multipanel multipanel MultiPanel

Multiview multiview MultiView

Namespace namespace NameSpace

Ok ok OK

Pi pi PI

Placeholder placeholder PlaceHolder

SignIn signIn SignOn

SignOut signOut SignOff

UserName userName Username

WhiteSpace whiteSpace Whitespace

Writable writable Writeable

Respect de la casse
Les langages qui peuvent s’exécuter sur le CLR ne sont pas requis pour prendre en charge le respect de la casse,
bien que d’autres. Même si votre langage le prend en charge, les autres langages qui peuvent accéder à votre
infrastructure ne le sont pas. Par conséquent, toutes les API qui sont accessibles de l’extérieur ne peuvent pas
s’appuyer uniquement sur la casse pour faire la distinction entre deux noms dans le même contexte.
❌NE partez pas du principe que tous les langages de programmation respectent la casse. mais ils ne le sont pas.
Les noms ne peuvent pas différer uniquement par la casse.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions d’affectation de noms
Conventions générales d'affectation de noms
18/07/2020 • 7 minutes to read

Cette section décrit les conventions d’affectation de noms générales relatives au choix de mots, des instructions sur
l’utilisation des abréviations et des acronymes, ainsi que des recommandations sur la façon d’éviter d’utiliser des
noms spécifiques à une langue.

Choix de mots
️ Choisissez des noms d’identificateur facilement lisibles.

Par exemple, une propriété nommée HorizontalAlignment est plus lisible en anglais que AlignmentHorizontal .
️ préférez la lisibilité par rapport à la concision.

Le nom de la propriété CanScrollHorizontally est mieux que ScrollableX (une référence cachée à l’axe X).
❌N’utilisez pas de traits de soulignement, de traits d’Union ou tout autre caractère non alphanumérique.
❌N’utilisez pas la notation hongroise.
❌Évitez d’utiliser des identificateurs qui sont en conflit avec des mots clés de langages de programmation
largement utilisés.
Conformément à la règle 4 du Common Language Specification (CLS), tous les langages conformes doivent fournir
un mécanisme qui permet d’accéder à des éléments nommés qui utilisent un mot clé de ce langage comme
identificateur. C#, par exemple, utilise le signe @ comme mécanisme d’échappement dans ce cas. Toutefois, il est
toujours judicieux d’éviter les mots clés courants, car il est bien plus difficile d’utiliser une méthode avec la
séquence d’échappement que l’autre sans lui.

Utilisation des abréviations et des acronymes


❌N’utilisez pas d’abréviations ou de contractions dans les noms d’identificateurs.
Par exemple, utilisez GetWindow plutôt que GetWin .
❌N’utilisez pas les acronymes qui ne sont pas largement acceptés, et même s’ils le sont, uniquement lorsque cela
est nécessaire.

Éviter les noms spécifiques à une langue


✔ Utilisez des noms sémantiquement intéressants plutôt que des mots clés spécifiques à une langue pour les

noms de types.
Par exemple, GetLength est un meilleur nom que GetInt .
✔ Utilisez un nom de type CLR générique, plutôt qu’un nom propre au langage, dans les rares cas où un

identificateur n’a aucune signification sémantique au-delà de son type.
Par exemple, une méthode qui est convertie en Int64 doit être nommée ToInt64 , et non ToLong (car Int64 est un
nom CLR pour l’alias spécifique à C# long ). Le tableau suivant présente plusieurs types de données de base à
l’aide des noms de types CLR (ainsi que des noms de types correspondants pour C#, Visual Basic et C++).
C# VISUA L B A SIC C ++ CLR

sbyte SByte char SByte

byte Poids unsigned char Poids

shor t Résumé shor t Int16

ushor t UInt16 unsigned shor t UInt16

int Integer int Int32

uint UInt32 nombre entier non signé UInt32

long Long __int64 Int64

correspondante UInt64 unsigned __int64 UInt64

float Unique float Unique

double Double double Double

bool Booléen bool Booléen

char Char wchar_t Char

string Chaîne Chaîne Chaîne

object Object Object Object

✔ Utilisez un nom commun, tel que value ou item , au lieu de répéter le nom de type, dans les rares cas où un

identificateur n’a aucune signification sémantique et que le type du paramètre n’est pas important.

Attribution de noms aux nouvelles versions des API existantes


️ Utilisez un nom similaire à l’ancienne API lors de la création de nouvelles versions d’une API existante.

Cela permet de mettre en évidence la relation entre les API.
️ préférez ajouter un suffixe plutôt qu’un préfixe pour indiquer une nouvelle version d’une API existante.

Cela aidera la découverte lors de la navigation dans la documentation ou à l’aide d’IntelliSense. L’ancienne version
de l’API sera organisée à proximité des nouvelles API, car la plupart des navigateurs et IntelliSense affichent des
identificateurs dans l’ordre alphabétique.
️ envisagez d’utiliser un nouvel identificateur, mais explicite, au lieu d’ajouter un suffixe ou un préfixe.

✔ Utilisez un suffixe numérique pour indiquer une nouvelle version d’une API existante, en particulier si le nom

existant de l’API est le seul nom pertinent (c’est-à-dire, s’il s’agit d’une norme industrielle) et si l’ajout d’un suffixe
significatif (ou la modification du nom) n’est pas une option appropriée.
❌N’utilisez pas le suffixe « ex » (ou un suffixe similaire) pour un identificateur pour le distinguer d’une version
antérieure de la même API.
️ Utilisez le suffixe « 64 » lors de l’introduction de versions d’API qui fonctionnent sur un entier 64 bits (entier

long) au lieu d’un entier de 32 bits. Vous devez adopter cette approche uniquement lorsque l’API 32 bits existante
existe. ne le faites pas pour les nouvelles API avec une version 64 bits uniquement.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Règles de conception du .NET Framework
Instructions de nommage
Conventions de nommage .NET pour EditorConfig
Noms d'assemblys et de DLL
18/07/2020 • 2 minutes to read

Un assembly est l’unité de déploiement et d’identité des programmes de code managé. Bien que les assemblys
puissent couvrir un ou plusieurs fichiers, en général un assembly mappe un à un avec une DLL. Par conséquent,
cette section décrit uniquement les conventions d’affectation de noms de DLL, qui peuvent alors être mappées aux
conventions d’affectation de noms d’assembly.
✔ Choisissez des noms pour vos dll d’assembly qui suggèrent de grands blocs de fonctionnalités, tels que System.

Data.
Les noms d’assemblys et de DLL ne doivent pas nécessairement correspondre aux noms d’espaces de noms, mais il
est raisonnable de suivre le nom de l’espace de noms lors de l’attribution de noms aux assemblys. Une bonne règle
empirique consiste à nommer la DLL en fonction du préfixe commun des espaces de noms contenus dans
l’assembly. Par exemple, un assembly avec deux espaces de noms, MyCompany.MyTechnology.FirstFeature et
MyCompany.MyTechnology.SecondFeature , peut être appelé MyCompany.MyTechnology.dll .

️ ENVISAGER de nommer les dll selon le modèle suivant :



<Company>.<Component>.dll

où <Component> contient une ou plusieurs clauses séparées par des points. Par exemple :
Litware.Controls.dll .
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions d’affectation de noms
Noms d'espaces de noms
18/07/2020 • 6 minutes to read

Comme pour les autres instructions d’affectation de noms, l’objectif de l’attribution de noms aux espaces de noms
est de créer suffisamment de clarté pour le programmeur qui utilise l’infrastructure pour savoir immédiatement ce
que le contenu de l’espace de noms est susceptible d’être. Le modèle suivant spécifie la règle générale pour
nommer les espaces de noms :
<Company>.(<Product>|<Technology>)[.<Feature>][.<Subnamespace>]

Voici quelques exemples :


Fabrikam.Math Litware.Security

✔ FAITES précéder les noms d’espaces de noms d’un nom de société pour empêcher les espaces de noms de

sociétés différentes d’avoir le même nom.
✔ Utilisez un nom de produit stable et indépendant de la version au deuxième niveau d’un nom d’espace de

noms.
❌N’utilisez pas de hiérarchies organisationnelles comme base pour les noms dans les hiérarchies d’espaces de
noms, car les noms de groupe dans les sociétés ont tendance à être éphémères. Organiser la hiérarchie des
espaces de noms autour des groupes de technologies associées.
️ Utilisez PascalCasing et séparez les composants d’espace de noms par des points (par exemple,

Microsoft.Office.PowerPoint ). Si votre personnalisation utilise une casse qui n’est pas traditionnelle, vous devez
suivre la casse définie par votre personnalisation, même si elle diffère de la casse normale des espaces de noms.
️ envisagez d’utiliser des noms d’espaces de noms pluriels lorsque cela est approprié.

Par exemple, utilisez System.Collections au lieu de System.Collection . Toutefois, les noms et les acronymes sont
des exceptions à cette règle. Par exemple, utilisez System.IO au lieu de System.IOs .
❌N’utilisez pas le même nom pour un espace de noms et un type dans cet espace de noms.
Par exemple, n’utilisez pas Debug comme nom d’espace de noms et fournissez également une classe nommée
Debug dans le même espace de noms. Plusieurs compilateurs requièrent que ces types soient qualifiés complets.

Conflits d’espaces de noms et de noms de types


❌N’introduisez pas de noms de types génériques tels que Element ,, Node Log et Message .
Il y a une très grande probabilité que cela entraîne des conflits de noms de types dans les scénarios courants. Vous
devez qualifier les noms de types génériques ( FormElement , XmlNode , EventLog , SoapMessage ).
Il existe des instructions spécifiques pour éviter les conflits de noms de types pour différentes catégories d’espaces
de noms.
Espaces de noms du modèle d’application
Les espaces de noms appartenant à un modèle d’application unique sont souvent utilisés ensemble, mais ils
ne sont jamais utilisés avec des espaces de noms d’autres modèles d’application. Par exemple, l'
System.Windows.Forms espace de noms est très rarement utilisé avec l' System.Web.UI espace de noms.
Voici une liste de groupes d’espaces de noms de modèle d’application connus :
System.Windows* System.Web.UI*
❌N’attribuez pas le même nom aux types dans des espaces de noms dans un modèle d’application unique.
Par exemple, n’ajoutez pas de type nommé Page à l' System.Web.UI.Adapters espace de noms, car l'
System.Web.UI espace de noms contient déjà un type nommé Page .
Espaces de noms d’infrastructure
Ce groupe contient des espaces de noms rarement importés pendant le développement d’applications
courantes. Par exemple, les .Design espaces de noms sont principalement utilisés lors du développement
d’outils de programmation. Éviter les conflits avec les types de ces espaces de noms n’est pas critique.
Espaces de noms principaux
Les espaces de noms principaux incluent tous les System espaces de noms, à l’exception des espaces de
noms des modèles d’application et des espaces de noms d’infrastructure. Les espaces de noms principaux
incluent, entre autres,,, System System.IO System.Xml et System.Net .
❌N’attribuez pas de noms de types qui seraient en conflit avec n’importe quel type dans les espaces de
noms de base.
Par exemple, n’utilisez jamais Stream comme nom de type. Il serait en conflit avec System.IO.Stream , un
type très couramment utilisé.
Groupes d’espaces de noms technologiques
Cette catégorie comprend tous les espaces de noms ayant les mêmes deux premiers nœuds d’espace de
noms (<Company>.<Technology>* , tels que Microsoft.Build.Utilities et Microsoft.Build.Tasks . Il est
important que les types appartenant à une même technologie n’entrent pas en conflit.
❌N’assignez pas de noms de types qui sont en conflit avec d’autres types au sein d’une même technologie.
❌N’introduisez pas de conflits de nom de type entre les types dans les espaces de noms technologiques et
un espace de noms de modèle d’application (sauf si la technologie n’est pas destinée à être utilisée avec le
modèle d’application).
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions d’affectation de noms
Noms de classes, de structures et d'interfaces
18/07/2020 • 7 minutes to read

Les règles d’affectation des noms qui suivent s’appliquent à la dénomination générale des types.
️ Nommez les classes et les structs avec des noms ou des expressions nominales, à l’aide de PascalCasing.

Cela distingue les noms de types des méthodes, qui sont nommées à l’aide d’expressions de verbe.
️ Nommez les interfaces avec des expressions adjectifs, ou parfois avec des noms ou des expressions nominales.

Les noms et les expressions nominales doivent être utilisés rarement et peuvent indiquer que le type doit être une
classe abstraite, et non une interface.
❌N’attribuez pas de noms de classe à un préfixe (par exemple, « C »).
️ envisagez de terminer le nom des classes dérivées par le nom de la classe de base.

Cela est très lisible et explique clairement la relation. Voici quelques exemples de ceci dans le code :
ArgumentOutOfRangeException , qui est un genre de Exception , et SerializableAttribute , qui est un type de
Attribute . Toutefois, il est important d’utiliser un jugement raisonnable dans le cadre de l’application de cette
règle. par exemple, la Button classe est un type d' Control événement, bien que Control n’apparaisse pas dans
son nom.
️ FAITES précéder les noms d’interface par la lettre I, pour indiquer que le type est une interface.

Par exemple, IComponent (nom descriptif), ICustomAttributeProvider (expression nominale) et IPersistable
(adjectif) sont des noms d’interface appropriés. Comme avec d’autres noms de types, évitez les abréviations.
✔ Assurez-vous que les noms diffèrent uniquement par le préfixe « I » sur le nom de l’interface lorsque vous

définissez une paire classe-interface où la classe est une implémentation standard de l’interface.

Noms des paramètres de type générique


Des génériques ont été ajoutés à .NET Framework 2,0. La fonctionnalité a introduit un nouveau type d’identificateur
appelé paramètre de type.
✔ Nommez les paramètres de type générique avec des noms descriptifs, sauf si un nom de lettre unique est

entièrement explicite et qu’un nom descriptif n’ajoute pas de valeur.
✔ envisagez
️ T d’utiliser comme nom de paramètre de type pour les types avec un paramètre de type à une
seule lettre.

public int IComparer<T> { ... }


public delegate bool Predicate<T>(T item);
public struct Nullable<T> where T:struct { ... }

️ FAITES précéder les noms de paramètre de type descriptif de


✔ T .

public interface ISessionChannel<TSession> where TSession : ISession {


TSession Session { get; }
}

️ envisagez d’indiquer des contraintes placées sur un paramètre de type dans le nom du paramètre.

Par exemple, un paramètre avec une limite ISession peut être appelé TSession .

Noms des types courants


✔ Suivez les instructions décrites dans le tableau suivant lors de l’attribution de noms aux types dérivés de ou de

l’implémentation de certains types d' .NET Framework.

B A SE T Y P E IN ST RUC T IO N DE T Y P E DERIVED/ IM P L EM EN TAT IO N

System.Attribute ✔ Ajoutez le suffixe « Attribute » aux noms des classes



d’attributs personnalisés.

System.Delegate ✔ Ajoutez le suffixe « EventHandler » aux noms des délégués



utilisés dans les événements.

✔ Ajoutez le suffixe « callback » aux noms des délégués



autres que ceux utilisés comme gestionnaires d’événements.

❌N’ajoutez pas le suffixe « Delegate » à un délégué.

System.EventArgs ️ Ajoutez le suffixe « EventArgs ».


System.Enum ❌NE dérivez pas de cette classe ; Utilisez le mot clé pris en
charge par votre langage à la place. par exemple, en C#,
utilisez le enum mot clé.

❌N’ajoutez pas le suffixe « enum » ou « Flag ».

System.Exception ️ Ajoutez le suffixe « exception ».


IDictionary ✔ Ajoutez le suffixe « dictionary ». Notez que IDictionary



IDictionary<TKey,TValue> est un type spécifique de collection, mais cette règle est
prioritaire sur la règle de regroupements plus généraux qui
suit.

IEnumerable ️ Ajoutez le suffixe « collection ».



ICollection
IList
IEnumerable<T>
ICollection<T>
IList<T>

System.IO.Stream ️ Ajoutez le suffixe « Stream ».


CodeAccessPermission IPermission ️ Ajoutez le suffixe « permission ».


Énumération des noms


En général, les noms de types énumération (également appelés énumérations) doivent respecter les règles
d’attribution de noms de type standard (PascalCasing, etc.). Toutefois, des instructions supplémentaires s’appliquent
spécifiquement aux enums.
️ Utilisez un nom de type singulier pour une énumération, sauf si ses valeurs sont des champs de bits.

✔ Utilisez un nom de type pluriel pour une énumération avec des champs de bits comme valeurs, également

appelé enum Flags.
❌N’utilisez pas de suffixe « enum » dans les noms de types ENUM.
❌N’utilisez pas les suffixes « Flag » ou « flags » dans les noms de types ENUM.
❌N’utilisez pas de préfixe pour les noms de valeurs d’énumération (par exemple, « AD » pour les enums ADO,
« RTF » pour les énumérations de texte enrichi, etc.).
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions d’affectation de noms
Noms de membres de type
18/07/2020 • 6 minutes to read

Les types se composent de membres, de méthodes, de propriétés, d’événements, de constructeurs et de champs.


Les sections suivantes décrivent les règles de nommage des membres de type.

Noms des méthodes


Comme les méthodes permettent d’entreprendre des actions, les règles de conception exigent que les noms des
méthodes soient des verbes ou des expressions verbales. Cette règle sert également à distinguer les noms de
méthode des noms de propriété et de type, qui sont des expressions nominales ou adjectivales.
️ Donnez des noms de méthodes qui sont des verbes ou des expressions de verbe.

public class String {


public int CompareTo(...);
public string[] Split(...);
public string Trim();
}

Noms des propriétés


Contrairement aux autres membres, les noms des propriétés doivent être des expressions nominales ou
adjectivales. C’est parce que les propriétés font référence à des données, donc leur nom doivent le refléter. La casse
Pascal est toujours utilisée pour les noms de propriété.
️ Nommez les propriétés à l’aide d’un nom, d’une expression nominale ou d’un adjectif.

❌N’avez pas de propriétés qui correspondent au nom des méthodes « obtenir » comme dans l’exemple suivant :
public string TextWriter { get {...} set {...} } public string GetTextWriter(int value) { ... }

Ce modèle indique typiquement que la propriété doit vraiment être une méthode.
✔ Nommez les propriétés de collection avec une expression pluriel décrivant les éléments de la collection au lieu

d’utiliser une expression singulière suivie de « List » ou « collection ».
✔ Nommez les propriétés booléennes avec une expression affirmative ( CanSeek au lieu de CantSeek ). Si vous le

souhaitez, vous pouvez également préfixer les propriétés booléennes avec « is », « CAN » ou « has », mais
uniquement à l’endroit où elle ajoute value.
️ envisagez de donner à une propriété le même nom que son type.

Par exemple, la propriété suivante obtient et définit correctement une valeur enum nommée Color , donc la
propriété est nommée Color :

public enum Color {...}


public class Control {
public Color Color { get {...} set {...} }
}

Noms des événements


Les événements font toujours référence à une action, soit une action en cours, soit une action passée. Par
conséquent, comme avec les méthodes, les événements sont nommés avec des verbes, le temps des verbes
servant à indiquer l’heure où l’événement est déclenché.
️ Nommez les événements avec un verbe ou une phrase verbale.

Par exemple, Clicked , Painting , DroppedDown , etc.
✔ Donnez des noms d’événements avec un concept antérieur et postérieur, en utilisant les dizaines présents et

précédents.
Par exemple, un événement de fermeture déclenché avant la fermeture d’une fenêtre serait nommé Closing ,
tandis qu’un événement déclenché après la fermeture de la fenêtre serait nommé Closed .
❌N’utilisez pas les préfixes ou les suffixes « Before » ou « after » pour indiquer les pré-et post-événements.
Utilisez les temps du présent et du passé, comme nous venons de le décrire.
✔ Nommez les gestionnaires d’événements (délégués utilisés comme types d’événements) avec le suffixe

« EventHandler », comme indiqué dans l’exemple suivant :
public delegate void ClickedEventHandler(object sender, ClickedEventArgs e);

️ Utilisez deux paramètres nommés


✔ sender et e dans des gestionnaires d’événements.
Le paramètre d’expéditeur représente l’objet qui a déclenché l’événement. Le paramètre d’expéditeur est
généralement de type object , même s’il est possible d’employer un type plus spécifique.
️ Nommez les classes d’argument d’événement avec le suffixe « EventArgs ».

Noms des champs


Les règles de nommage des champs s’appliquent à des champs publics et protégés statiques. Les champs internes
et privés ne sont pas couverts par les règles, tandis que les champs d’instance publics ou protégés ne sont pas
autorisés par les règles de conception de membres.
️ Utilisez PascalCasing dans les noms de champs.

️ Nommez les champs à l’aide d’un nom, d’une expression nominale ou d’un adjectif.

❌N’utilisez pas de préfixe pour les noms de champs.
Par exemple, n’utilisez pas « g_ » ou « s_ » pour indiquer des champs statiques.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions d’affectation de noms
Attribution d'un nom à des paramètres
18/07/2020 • 2 minutes to read

Au-delà de la raison évidente de la lisibilité, il est important de suivre les instructions pour les noms de paramètres,
car les paramètres sont affichés dans la documentation et dans le concepteur quand les outils de conception
visuelle proposent IntelliSense et la fonctionnalité de navigation des classes.
️ Utilisez camelCasing dans les noms de paramètres.

️ Utilisez des noms de paramètres descriptifs.

️ envisagez d’utiliser des noms basés sur la signification d’un paramètre plutôt que sur le type du paramètre.

Affectation de noms aux paramètres de surcharge d’opérateur
️ Utilisez left et right pour les noms de paramètre de surcharge d’opérateur binaire s’il n’y a aucune

signification pour les paramètres.
✔ Utilisez value pour les noms de paramètre de surcharge d’opérateur unaire si les paramètres n’ont aucune

signification.
✔ CONSIDÉRez les noms significatifs pour les paramètres de surcharge d’opérateur si cela ajoute une valeur

significative.
❌N’utilisez pas d’abréviations ou d’index numériques pour les noms de paramètre de surcharge d’opérateur.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions d’affectation de noms
Attribution d'un nom à des ressources
18/07/2020 • 2 minutes to read

Étant donné que les ressources localisables peuvent être référencées par le biais de certains objets comme s’il
s’agissait de propriétés, les instructions d’affectation de noms pour les ressources sont similaires aux instructions
de propriété.
️ Utilisez PascalCasing dans les clés de ressources.

️ fournissez des identificateurs descriptifs et non courts.

❌N’utilisez pas de mots clés spécifiques au langage pour les principaux langages CLR.
✔ Utilisez uniquement des caractères alphanumériques et des traits de soulignement dans les ressources de

nommage.
️ Utilisez la Convention d’affectation de noms suivante pour les ressources de message d’exception.

L’identificateur de ressource doit être le nom du type d’exception plus un identificateur abrégé de l’exception :
ArgumentExceptionIllegalCharacters ArgumentExceptionInvalidName ArgumentExceptionFileNameIsMalformed

Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.


Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions d’affectation de noms
Instructions de conception de types
18/07/2020 • 3 minutes to read

Du point de vue du CLR, il n’existe que deux catégories de types : les types référence et les types valeur, mais pour
les besoins d’une discussion sur la conception de l’infrastructure, nous dipartissons les types en groupes plus
logiques, chacun avec ses propres règles de conception spécifiques.
Les classes sont le cas général de types référence. Ils constituent la majeure partie des types dans la majorité des
infrastructures. Les classes ont eu la popularité de l’ensemble complet des fonctionnalités orientées objet qu’elles
prennent en charge et de leur applicabilité générale. Les classes de base et les classes abstraites sont des groupes
logiques spéciaux liés à l’extensibilité.
Les interfaces sont des types qui peuvent être implémentés par les types référence et les types valeur. Ils peuvent
ainsi servir de racines de hiérarchies polymorphes de types référence et de types valeur. En outre, les interfaces
peuvent être utilisées pour simuler plusieurs héritages, ce qui n’est pas pris en charge en mode natif par le CLR.
Les structs sont le cas général des types valeur et doivent être réservés pour les petits types simples, similaires
aux primitives de langage.
Les enums sont un cas spécial de types valeur utilisés pour définir des jeux de valeurs courts, tels que les jours de
la semaine, les couleurs de la console, etc.
Les classes statiques sont des types destinés à être des conteneurs pour des membres statiques. Ils sont
généralement utilisés pour fournir des raccourcis vers d’autres opérations.
Les délégués, les exceptions, les attributs, les tableaux et les collections sont tous des cas spéciaux de types
référence destinés à des utilisations spécifiques, et les recommandations relatives à leur conception et à leur
utilisation sont décrites ailleurs dans ce document.
✔ Assurez-vous que chaque type est un ensemble bien défini de membres associés, et pas seulement une

collection aléatoire de fonctionnalités non liées.

Dans cette section


Choix entre une classe et une structure abstraite conception d’une classe statique conception d' interface
conception d’une structure enum conception des types imbriqués parties © 2005, 2009 Microsoft Corporation.
Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft
Windows.

Voir aussi
Directives de conception d’infrastructure
Choix entre classe et structure
18/07/2020 • 6 minutes to read

L’une des décisions de conception de base de chaque concepteur d’infrastructure est de concevoir un type en tant
que classe (type référence) ou en tant que struct (un type valeur). Il est essentiel de bien comprendre les
différences de comportement des types de référence et des types de valeur pour faire ce choix.
La première différence entre les types référence et les types valeur est que nous envisageons que les types
référence sont alloués sur le tas et récupérés par le garbage collector, tandis que les types valeur sont alloués sur la
pile ou inline dans les types contenants et désalloués lorsque la pile se déroule ou lorsque leur type conteneur est
libéré. Par conséquent, les allocations et les désallocations de types valeur sont en général moins coûteuses que les
allocations et les désallocations de types référence.
Ensuite, les tableaux de types référence sont alloués hors ligne, ce qui signifie que les éléments de tableau sont
simplement des références aux instances du type référence résidant sur le tas. Les tableaux de types valeur sont
alloués inline, ce qui signifie que les éléments de tableau sont les instances réelles du type valeur. Par conséquent,
les allocations et les désallocations des tableaux de types valeur sont très coûteuses que les allocations et les
désallocations de tableaux de types référence. En outre, dans la majorité des cas, les tableaux de types valeur
présentent une plus grande localité de référence.
La différence suivante est liée à l’utilisation de la mémoire. Les types valeur sont boxed lorsqu’ils sont castés en un
type référence ou l’une des interfaces qu’ils implémentent. Ils obtiennent unboxed lorsqu’ils sont reconvertis en
type valeur. Étant donné que les zones sont des objets alloués sur le tas et qui sont récupérés par le garbage
collector, un trop grand nombre de conversions boxing et unboxing peuvent avoir un impact négatif sur le tas, le
garbage collector et enfin sur les performances de l’application. En revanche, aucune conversion boxing de ce type
ne se produit, car les types référence sont convertis. (Pour plus d’informations, consultez conversion boxing et
unboxing).
Ensuite, les assignations de type référence copient la référence, tandis que les assignations de type valeur copient
la valeur entière. Par conséquent, les assignations de types de référence volumineux sont moins coûteuses que les
assignations de types de valeur élevée.
Enfin, les types référence sont passés par référence, tandis que les types valeur sont passés par valeur. Les
modifications apportées à une instance d’un type référence affectent toutes les références pointant vers l’instance.
Les instances de type valeur sont copiées lorsqu’elles sont passées par valeur. Quand une instance d’un type valeur
est modifiée, elle n’affecte pas l’une de ses copies. Étant donné que les copies ne sont pas créées explicitement par
l’utilisateur mais sont implicitement créées lorsque des arguments sont passés ou que des valeurs de retour sont
retournées, les types valeur qui peuvent être modifiés peuvent être déroutants pour de nombreux utilisateurs. Par
conséquent, les types valeur doivent être immuables.
En règle générale, la majorité des types dans un Framework doivent être des classes. Toutefois, il existe certaines
situations dans lesquelles les caractéristiques d’un type de valeur rendent l’utilisation de structs plus appropriée.
✔ ENVISAGER de définir un struct au lieu d’une classe si les instances du type sont petites et généralement

éphémères ou sont généralement incorporées dans d’autres objets.
❌Évitez de définir un struct à moins que le type ait toutes les caractéristiques suivantes :
Il représente logiquement une valeur unique, similaire aux types primitifs ( int , double , etc.).
Elle a une taille d’instance inférieure à 16 octets.
Elle est immuable.
Il n’est pas nécessaire d’effectuer fréquemment des Boxing.
Dans tous les autres cas, vous devez définir vos types en tant que classes.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Règles de conception de type
Directives de conception d’infrastructure
Conception de classes abstraites
18/07/2020 • 2 minutes to read

❌NE définissez pas de constructeurs internes publics ou protégés dans les types abstraits.
Les constructeurs doivent être publics uniquement si les utilisateurs doivent créer des instances du type. Étant
donné que vous ne pouvez pas créer d’instances d’un type abstrait, un type abstrait avec un constructeur public
n’est pas correctement conçu et trompe les utilisateurs.
️ Définissez un constructeur protégé ou interne dans des classes abstraites.

Un constructeur protégé est plus courant et permet simplement à la classe de base d’effectuer sa propre
initialisation lors de la création de sous-types.
Un constructeur interne peut être utilisé pour limiter les implémentations concrètes de la classe abstraite à
l’assembly qui définit la classe.
️ fournissez au moins un type concret qui hérite de chaque classe abstraite que vous livrez.

Cela permet de valider la conception de la classe abstraite. Par exemple, System.IO.FileStream est une
implémentation de la System.IO.Stream classe abstraite.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Règles de conception de type
Directives de conception d’infrastructure
Conception de classes statiques
18/07/2020 • 2 minutes to read

Une classe statique est définie en tant que classe qui contient uniquement des membres statiques (bien entendu,
outre les membres d’instance hérités de System.Object et éventuellement un constructeur privé). Certains
langages fournissent une prise en charge intégrée des classes statiques. En C# 2,0 et versions ultérieures,
lorsqu’une classe est déclarée comme étant statique, elle est sealed, abstract, et aucun membre d’instance ne peut
être substitué ou déclaré.
Les classes statiques sont un compromis entre une conception pure orientée objet et une simplicité. Elles sont
généralement utilisées pour fournir des raccourcis vers d’autres opérations (telles que System.IO.File ), des
détenteurs de méthodes d’extension ou des fonctionnalités pour lesquelles un wrapper orienté objet complet n’est
pas justifié (comme System.Environment ).
️ utiliser des classes statiques avec modération.

Les classes statiques doivent être utilisées uniquement comme classes de prise en charge pour le noyau orienté
objet de l’infrastructure.
❌NE traitez pas les classes statiques comme un compartiment divers.
❌NE déclarez pas ou ne substituez pas de membres d’instance dans des classes statiques.
✔ déclarez des classes statiques comme sealed, abstract et ajoutez un constructeur d’instance privé si votre

langage de programmation n’a pas de prise en charge intégrée des classes statiques.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Règles de conception de type
Directives de conception d’infrastructure
Conception d'interfaces
18/07/2020 • 4 minutes to read

Bien que la plupart des API soient mieux modélisées à l’aide de classes et de structs, il existe des cas dans lesquels
les interfaces sont plus appropriées ou sont la seule option.
Le CLR ne prend pas en charge l’héritage multiple (autrement dit, les classes CLR ne peuvent pas hériter de
plusieurs classes de base), mais autorisent les types à implémenter une ou plusieurs interfaces en plus de
l’héritage d’une classe de base. Par conséquent, les interfaces sont souvent utilisées pour obtenir l’effet de plusieurs
héritages. Par exemple, IDisposable est une interface qui permet aux types de prendre en charge disposability
indépendamment de toute autre hiérarchie d’héritage dans laquelle ils souhaitent participer.
L’autre situation dans laquelle la définition d’une interface est appropriée est la création d’une interface commune
qui peut être prise en charge par plusieurs types, y compris certains types valeur. Les types valeur ne peuvent pas
hériter de types autres que ValueType , mais ils peuvent implémenter des interfaces. par conséquent, l’utilisation
d’une interface est la seule option pour fournir un type de base commun.
✔ définir une interface si vous avez besoin d’une API commune prise en charge par un ensemble de types

incluant des types valeur.
✔ envisagez de définir une interface si vous devez prendre en charge ses fonctionnalités sur les types qui héritent

déjà d’un autre type.
❌Évitez d’utiliser des interfaces de marqueur (interfaces sans membres).
Si vous avez besoin de marquer une classe comme ayant une caractéristique spécifique (marqueur), en général,
utilisez un attribut personnalisé plutôt qu’une interface.
️ fournissez au moins un type qui est une implémentation d’une interface.

Cela permet de valider la conception de l’interface. Par exemple, List<T> est une implémentation de l' IList<T>
interface.
✔ fournissez au moins une API qui utilise chaque interface que vous définissez (une méthode qui prend

l’interface en tant que paramètre ou une propriété de type interface).
Cela permet de valider la conception de l’interface. Par exemple, List<T>.Sort utilise l'
System.Collections.Generic.IComparer<T> interface.
❌N’ajoutez pas de membres à une interface qui a déjà été expédiée.
Cela entraînerait l’arrêt des implémentations de l’interface. Vous devez créer une nouvelle interface afin d’éviter les
problèmes de Versioning.
À l’exception des situations décrites dans ces instructions, vous devez, en général, choisir des classes plutôt que des
interfaces pour concevoir des bibliothèques réutilisables de code managé.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Règles de conception de type
Directives de conception d’infrastructure
Conception de structures
18/07/2020 • 3 minutes to read

Le type de valeur à usage général est le plus souvent appelé struct, son mot clé C#. Cette section fournit des
instructions pour la conception générale des structs.
❌NE fournissez pas de constructeur sans paramètre pour un struct.
Le respect de cette règle permet de créer des tableaux de structs sans avoir à exécuter le constructeur sur chaque
élément du tableau. Notez que C# n’autorise pas les structs à avoir des constructeurs sans paramètre.
❌NE définissez pas de types valeur mutable.
Les types valeur mutable présentent plusieurs problèmes. Par exemple, lorsqu’un accesseur Get de propriété
retourne un type valeur, l’appelant reçoit une copie. Étant donné que la copie est créée implicitement, les
développeurs peuvent ne pas savoir qu’ils font muter la copie, et non la valeur d’origine. En outre, certains
langages (Dynamic Languages, en particulier) rencontrent des problèmes lors de l’utilisation des types valeur
mutables, car même les variables locales, lorsqu’elles sont déréférencées, provoquent une copie.
✔ Assurez-vous qu’un État dans lequel toutes les données d’instance est définie sur zéro, false ou null (selon le

cas) est valide.
Cela empêche la création accidentelle d’instances non valides lorsqu’un tableau de structs est créé.
️ implémenter IEquatable<T> sur des types valeur.

La Object.Equals méthode sur les types valeur provoque un boxing, et son implémentation par défaut n’est pas très
efficace, car elle utilise la réflexion. Equalspeut offrir de meilleures performances et peut être implémentée pour ne
pas provoquer de conversion boxing.
❌N’étendez pas explicitement ValueType . En fait, la plupart des langues l’empêchent.
En général, les structs peuvent être très utiles, mais ne doivent être utilisés que pour des valeurs petites, uniques et
immuables qui ne seront pas converties fréquemment.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Règles de conception de type
Directives de conception d’infrastructure
Choix entre classe et structure
Conception d'énumérations
18/07/2020 • 9 minutes to read

Les enums sont un genre spécial de type valeur. Il existe deux types d’énumérations : les énumérations simples et
les énumérations d’indicateur.
Les énumérations simples représentent de petits jeux de choix fermés. Un ensemble de couleurs est un exemple
courant de l’énumération simple.
Les enums d’indicateur sont conçus pour prendre en charge des opérations au niveau du bit sur les valeurs enum.
Une liste d’options est un exemple courant de l’énumération Flags.
✔ Utilisez une énumération pour fortement taper des paramètres, des propriétés et des valeurs de retour qui

représentent des ensembles de valeurs.
️ préférez l’utilisation d’une énumération plutôt que de constantes statiques.

❌N’utilisez pas d’énumération pour les jeux ouverts (tels que la version du système d’exploitation, les noms de
vos amis, etc.).
❌NE fournissez pas de valeurs enum réservées destinées à un usage ultérieur.
Vous pouvez toujours simplement ajouter des valeurs à l’énumération existante à un moment ultérieur. Pour plus
d’informations sur l’ajout de valeurs aux enums, consultez Ajout de valeurs aux enums . Les valeurs réservées
polluent simplement l’ensemble des valeurs réelles et ont tendance à entraîner des erreurs de l’utilisateur.
❌Évitez d’exposer publiquement des enums avec une seule valeur.
Une pratique courante pour garantir une extensibilité future des API C consiste à ajouter des paramètres réservés
aux signatures de méthode. Ces paramètres réservés peuvent être exprimés comme des enums avec une seule
valeur par défaut. Cela ne doit pas être fait dans les API managées. La surcharge de méthode permet d’ajouter des
paramètres dans les versions ultérieures.
❌N’incluez pas de valeurs Sentinel dans les enums.
Bien qu’elles soient parfois utiles pour les développeurs d’infrastructure, les valeurs sentinelles sont confuses pour
les utilisateurs de l’infrastructure. Ils sont utilisés pour suivre l’état de l’enum au lieu d’être l’une des valeurs de
l’ensemble représenté par l’enum.
️ fournissez une valeur égale à zéro sur les énumérations simples.

Envisagez d’appeler la valeur comme « None ». Si une telle valeur n’est pas appropriée pour cette énumération
particulière, la valeur par défaut la plus courante pour l’énumération doit être assignée à la valeur sous-jacente de
zéro.
✔ envisagez Int32 d’utiliser (valeur par défaut dans la plupart des langages de programmation) comme type

sous-jacent d’une énumération, sauf si l’une des conditions suivantes est vraie :
L’énumération est une énumération d’indicateurs et vous avez plus de 32 indicateurs, ou vous prévoyez
d’en avoir plus à l’avenir.
Le type sous-jacent doit être différent Int32 de pour faciliter l’interopérabilité avec du code non managé qui
attend des énumérations de taille différente.
Un type sous-jacent plus petit entraînerait des économies substantielles dans l’espace. Si vous vous
attendez à ce que l’énumération soit principalement utilisée comme argument pour le workflow de
contrôle, la taille n’a pas de différence. Les économies de taille peuvent être significatives dans les cas
suivants :
Vous vous attendez à ce que l’énumération soit utilisée en tant que champ dans une classe ou une
structure très fréquemment instanciée.
Vous vous attendez à ce que les utilisateurs créent des tableaux ou des collections volumineux des
instances d’énumération.
Vous vous attendez à ce qu’un grand nombre d’instances de l’énumération soient sérialisées.
Pour l’utilisation en mémoire, sachez que les objets managés sont toujours DWORD alignés. par conséquent, vous
avez besoin de plusieurs enums ou d’autres petites structures dans une instance pour empaqueter une
énumération plus petite avec afin de faire la différence, car la taille totale de l’instance est toujours arrondie à un
DWORD .

✔ Nommez les énumérations d’indicateur avec des noms au pluriel ou des expressions nominales et des

énumérations simples avec des noms au singulier ou des expressions nominales.
❌N’étendez pas System.Enum directement.
System.Enumest un type spécial utilisé par le CLR pour créer des énumérations définies par l’utilisateur. La plupart
des langages de programmation fournissent un élément de programmation qui vous permet d’accéder à cette
fonctionnalité. Par exemple, en C#, le enum mot clé est utilisé pour définir une énumération.
Conception d’énumérations d’indicateur
️ Appliquez System.FlagsAttribute pour marquer les enums. N’appliquez pas cet attribut à des énumérations

simples.
✔ Utilisez les puissances de deux pour les valeurs d’énumération d’indicateur afin qu’elles puissent être

combinées librement à l’aide de l’opération or au niveau du bit.
✔ envisagez de fournir des valeurs d’énumération spéciales pour les combinaisons d’indicateurs couramment

utilisées.
Les opérations au niveau du bit sont un concept avancé qui ne doit pas être nécessaire pour les tâches simples.
ReadWriteest un exemple de cette valeur spéciale.
❌Évitez de créer des énumérations d’indicateur lorsque certaines combinaisons de valeurs ne sont pas valides.
❌Évitez d’utiliser des valeurs d’énumération d’indicateur de zéro, sauf si la valeur représente « tous les
indicateurs sont désactivés » et s’il est nommé de manière appropriée, comme indiqué par l’instruction suivante.
✔ Nommez la valeur zéro des énumérations d’indicateur None . Pour une énumération d’indicateur, la valeur

doit toujours signifier que « tous les indicateurs sont effacés ».
Ajouter de la valeur aux enums
Il est très courant de découvrir que vous devez ajouter des valeurs à une énumération une fois que vous l’avez
déjà livrée. Il y a un problème potentiel de compatibilité des applications lorsque la valeur récemment ajoutée est
retournée à partir d’une API existante, car les applications mal écrites peuvent ne pas gérer correctement la
nouvelle valeur.
️ envisagez d’ajouter des valeurs aux enums, en dépit d’un faible risque de compatibilité.

Si vous avez des données réelles sur les incompatibilités d’application provoquées par des ajouts à une
énumération, envisagez d’ajouter une nouvelle API qui retourne les valeurs nouvelles et anciennes, et de déprécier
l’ancienne API, qui doit continuer à retourner uniquement les anciennes valeurs. Cela permet de s’assurer que vos
applications existantes restent compatibles.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Règles de conception de type
Directives de conception d’infrastructure
Types imbriqués
18/07/2020 • 4 minutes to read

Un type imbriqué est un type défini dans la portée d’un autre type, appelé type englobant. Un type imbriqué a
accès à tous les membres de son type englobant. Par exemple, il a accès aux champs privés définis dans le type
englobant et aux champs protégés définis dans tous les ascendants du type englobant.
En général, les types imbriqués doivent être utilisés avec modération. et ce, pour plusieurs raisons. Certains
développeurs ne sont pas entièrement familiarisés avec le concept. Ces développeurs peuvent, par exemple,
rencontrer des problèmes avec la syntaxe de la déclaration de variables de types imbriqués. Les types imbriqués
sont également très étroitement couplés avec leurs types englobants, et en tant que tels ne sont pas adaptés aux
types à usage général.
Les types imbriqués sont mieux adaptés à la modélisation des détails d’implémentation de leurs types englobants.
L’utilisateur final doit rarement déclarer des variables d’un type imbriqué et, presque, ne doit jamais avoir à
instancier explicitement des types imbriqués. Par exemple, l’énumérateur d’une collection peut être un type
imbriqué de cette collection. Les énumérateurs sont généralement instanciés par leur type englobant, et étant
donné que de nombreux langages prennent en charge l’instruction foreach, les variables d’énumérateur doivent
rarement être déclarées par l’utilisateur final.
✔ Utilisez des types imbriqués lorsque la relation entre le type imbriqué et son type externe est telle que la

sémantique d’accessibilité des membres est souhaitable.
❌N’utilisez pas de types imbriqués publics comme construction de regroupement logique. Utilisez des espaces
de noms pour ce.
❌Évitez les types imbriqués exposés publiquement. La seule exception à cela est si les variables du type imbriqué
doivent être déclarées uniquement dans des scénarios rares tels que le sous-classement ou d’autres scénarios de
personnalisation avancée.
❌N’utilisez pas de types imbriqués si le type est susceptible d’être référencé en dehors du type conteneur.
Par exemple, une énumération passée à une méthode définie sur une classe ne doit pas être définie en tant que
type imbriqué dans la classe.
❌N’utilisez pas de types imbriqués s’ils doivent être instanciés par le code client. Si un type a un constructeur
public, il ne doit probablement pas être imbriqué.
Si un type peut être instancié, cela semble indiquer que le type a un emplacement dans l’infrastructure proprement
dit (vous pouvez le créer, l’utiliser et le détruire sans jamais utiliser le type externe), et ne doit donc pas être
imbriqué. Les types internes ne doivent pas être largement réutilisés en dehors du type externe sans aucune
relation au type externe.
❌NE définissez pas un type imbriqué en tant que membre d’une interface. De nombreux langages ne prennent
pas en charge ce type de construction.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Règles de conception de type
Directives de conception d’infrastructure
Instructions de conception des membres
18/07/2020 • 2 minutes to read

Les méthodes, propriétés, événements, constructeurs et champs sont collectivement appelés membres. Les
membres sont finalement les moyens par lesquels les fonctionnalités de l’infrastructure sont exposées aux
utilisateurs finaux d’une infrastructure.
Les membres peuvent être virtuels ou non virtuels, concrets ou abstraits, statiques ou d’instance et peuvent avoir
plusieurs étendues d’accessibilité différentes. Toute cette variété offre une expressivité incroyable, mais dans le
même temps, vous devez faire attention à la partie du concepteur de Framework.
Ce chapitre propose des instructions de base qui doivent être suivies lors de la conception de membres de
n’importe quel type.

Dans cette section


Surcharge de membre
Conception des propriétés
Conception de constructeur
Conception des événements
Conception de champs
Méthodes d’extension
Surcharges d’opérateur
Conception de paramètres
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft
Windows.

Voir aussi
Directives de conception d’infrastructure
Surcharge de membre
18/07/2020 • 3 minutes to read

La surcharge de membre consiste à créer deux membres ou plus sur le même type qui diffèrent uniquement par le
nombre ou le type de paramètres, mais qui portent le même nom. Par exemple, dans le code suivant, la WriteLine
méthode est surchargée :

public static class Console {


public void WriteLine();
public void WriteLine(string value);
public void WriteLine(bool value);
...
}

Étant donné que seules les méthodes, les constructeurs et les propriétés indexées peuvent avoir des paramètres,
seuls ces membres peuvent être surchargés.
La surcharge est l’une des techniques les plus importantes pour améliorer la convivialité, la productivité et la
lisibilité des bibliothèques réutilisables. La surcharge sur le nombre de paramètres permet de fournir des versions
plus simples des constructeurs et des méthodes. La surcharge sur le type de paramètre permet d’utiliser le même
nom de membre pour les membres effectuant des opérations identiques sur un ensemble sélectionné de types
différents.
✔ essayez d’utiliser des noms de paramètres descriptifs pour indiquer la valeur par défaut utilisée par les

surcharges plus courtes.
❌Évitez la variation arbitraire des noms de paramètres dans les surcharges. Si un paramètre d’une surcharge
représente la même entrée qu’un paramètre dans une autre surcharge, les paramètres doivent avoir le même nom.
❌Évitez d’être incohérent dans l’ordre des paramètres dans les membres surchargés. Les paramètres portant le
même nom doivent apparaître à la même position dans toutes les surcharges.
✔ Effectuez uniquement la surcharge virtuelle la plus longue (si l’extensibilité est requise). Les surcharges plus

courtes doivent simplement appeler une surcharge plus longue.
❌N’utilisez pas ref out les modificateurs ou pour surcharger des membres.
Certains langages ne peuvent pas résoudre les appels aux surcharges de ce type. En outre, ces surcharges ont
généralement une sémantique complètement différente et ne doivent probablement pas être des surcharges, mais
deux méthodes distinctes à la place.
❌N’ont pas de surcharges avec des paramètres à la même position et des types similaires encore avec une
sémantique différente.
️ autoriser
✔ null à être passé pour des arguments facultatifs.
️ Utilisez la surcharge des membres plutôt que de définir des membres avec des arguments par défaut.

Les arguments par défaut ne sont pas conformes CLS.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.
Voir aussi
Recommandations en matière de conception de membres
Directives de conception d’infrastructure
Conception des propriétés
18/07/2020 • 9 minutes to read

Bien que les propriétés soient techniquement très similaires aux méthodes, elles sont très différentes en termes de
scénarios d’utilisation. Elles doivent être considérées comme des champs intelligents. Ils ont la syntaxe d’appel des
champs et la flexibilité des méthodes.
✔ créer des propriétés d’extraction uniquement si l’appelant ne doit pas être en mesure de modifier la valeur de la

propriété.
Gardez à l’esprit que si le type de la propriété est un type référence mutable, la valeur de la propriété peut être
modifiée même si la propriété est en extraction seule.
❌NE fournissez pas de propriétés ou de propriétés définies uniquement avec la méthode setter dont l’accessibilité
est plus étendue que l’accesseur Get.
Par exemple, n’utilisez pas de propriétés avec un accesseur Set public et un accesseur Get protégé.
Si l’accesseur Get de la propriété ne peut pas être fourni, implémentez la fonctionnalité comme une méthode à la
place. Envisagez de démarrer le nom de la méthode avec Set et de suivre le nom de la propriété. Par exemple,
AppDomain a une méthode appelée SetCachePath au lieu d’avoir une propriété définie uniquement appelée
CachePath .

✔ fournissez des valeurs par défaut sensibles pour toutes les propriétés, en veillant à ce que les valeurs par défaut

n’entraînent pas de brèche de sécurité ou de code peu efficace.
✔ autorisez les propriétés à être définies dans n’importe quel ordre, même si cela se traduit par un État non valide

temporaire de l’objet.
Il est courant que deux propriétés ou plus soient associées à un point où certaines valeurs d’une propriété peuvent
ne pas être valides étant donné les valeurs d’autres propriétés sur le même objet. Dans ce cas, les exceptions
résultant de l’état non valide doivent être reportées jusqu’à ce que les propriétés interdépendantes soient utilisées
ensemble par l’objet.
️ conserver la valeur précédente si un accesseur Set de propriété lève une exception.

❌Évitez de lever des exceptions à partir des accesseurs get de propriété.
Les accesseurs get de propriété doivent être des opérations simples et ne doivent pas avoir de conditions
préalables. Si un accesseur Get peut lever une exception, il doit probablement être remanié pour être une méthode.
Notez que cette règle ne s’applique pas aux indexeurs, où nous attendons des exceptions en raison de la validation
des arguments.
Conception des propriétés indexées
Une propriété indexée est une propriété spéciale qui peut avoir des paramètres et qui peut être appelée avec une
syntaxe spéciale similaire à l’indexation de tableau.
Les propriétés indexées sont communément appelées indexeurs. Les indexeurs ne doivent être utilisés que dans les
API qui permettent d’accéder aux éléments d’une collection logique. Par exemple, une chaîne est une collection de
caractères, et l’indexeur sur System.String a été ajouté pour accéder à ses caractères.
️ envisagez d’utiliser des indexeurs pour fournir l’accès aux données stockées dans un tableau interne.

️ envisagez de fournir des indexeurs sur des types représentant des collections d’éléments.

❌Évitez d’utiliser des propriétés indexées avec plusieurs paramètres.
Si la conception requiert plusieurs paramètres, reconsidérez si la propriété représente vraiment un accesseur à une
collection logique. Si ce n’est pas le cas, utilisez plutôt des méthodes. Envisagez de démarrer le nom de la méthode
avec Get ou Set .
❌Évitez les indexeurs avec des types de paramètres autres que System.Int32 , System.Int64 ,, System.String
System.Object ou une énumération.
Si la conception requiert d’autres types de paramètres, réévaluez fortement si l’API représente vraiment un
accesseur à une collection logique. Si ce n’est pas le cas, utilisez une méthode. Envisagez de démarrer le nom de la
méthode avec Get ou Set .
✔ Utilisez le nom Item pour les propriétés indexées, sauf si un nom est évidemment mieux adapté (par exemple,

consultez la Chars[] propriété sur System.String ).
En C#, les indexeurs sont par défaut un élément nommé. Le IndexerNameAttribute peut être utilisé pour
personnaliser ce nom.
❌NE fournissez pas à la fois un indexeur et des méthodes sémantiquement équivalentes.
❌NE fournissez pas plusieurs familles d’indexeurs surchargés dans un même type.
Cela est appliqué par le compilateur C#.
❌N’utilisez pas de propriétés indexées qui ne sont pas des valeurs par défaut.
Cela est appliqué par le compilateur C#.
Événements de notification de modification de propriété
Parfois, il est utile de fournir un événement pour notifier l’utilisateur des modifications apportées à une valeur de
propriété. Par exemple, System.Windows.Forms.Control déclenche un TextChanged événement après la modification
de la valeur de sa Text propriété.
✔ envisagez de déclencher des événements de notification de modification lorsque des valeurs de propriété dans

des API de haut niveau (généralement des composants de concepteur) sont modifiées.
S’il existe un bon scénario pour qu’un utilisateur sache quand une propriété d’un objet change, l’objet doit
déclencher un événement de notification de modification pour la propriété.
Toutefois, il est peu probable qu’il soit nécessaire de déclencher de tels événements pour les API de bas niveau,
telles que les types de base ou les collections. Par exemple, List<T> ne déclencherait pas ces événements lorsqu’un
nouvel élément est ajouté à la liste et que la Count propriété est modifiée.
✔ envisagez de déclencher des événements de notification de modification lorsque la valeur d’une propriété

change via des forces externes.
Si une valeur de propriété change via une force externe (d’une manière autre que en appelant des méthodes sur
l’objet), les événements Raise indiquent au développeur que la valeur change et a changé. La Text propriété d’un
contrôle Text Box en est un bon exemple. Lorsque l’utilisateur tape du texte dans un TextBox , la valeur de la
propriété change automatiquement.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Recommandations en matière de conception de membres
Directives de conception d’infrastructure
Conception de constructeurs
18/07/2020 • 6 minutes to read

Il existe deux genres de constructeurs : des constructeurs de type et des constructeurs d’instance.
Les constructeurs de type sont statiques et sont exécutés par le CLR avant l’utilisation du type. Les constructeurs
d’instance s’exécutent lors de la création d’une instance d’un type.
Les constructeurs de type ne peuvent pas accepter de paramètres. Les constructeurs d’instance peuvent. Les
constructeurs d’instance qui ne prennent pas de paramètres sont souvent appelés constructeurs sans paramètre.
Les constructeurs sont le moyen le plus naturel de créer des instances d’un type. La plupart des développeurs
recherchent et essaient d’utiliser un constructeur avant qu’ils n’envisagent d’autres façons de créer des instances
(comme les méthodes de fabrique).
️ envisagez de fournir des constructeurs simples, idéalement par défaut.

Un constructeur simple possède un très petit nombre de paramètres, et tous les paramètres sont des primitives ou
des enums. Ces constructeurs simples augmentent l’utilisation de l’infrastructure.
✔ envisagez d’utiliser une méthode de fabrique statique à la place d’un constructeur si la sémantique de

l’opération souhaitée ne mappe pas directement à la construction d’une nouvelle instance, ou si les règles de
conception de constructeur se présentent comme non naturelles.
️ Utilisez des paramètres de constructeur comme raccourcis pour définir des propriétés principales.

Il ne doit y avoir aucune différence de sémantique entre l’utilisation du constructeur vide suivi par certains jeux de
propriétés et l’utilisation d’un constructeur avec plusieurs arguments.
✔ Utilisez le même nom pour les paramètres du constructeur et une propriété si les paramètres du constructeur

sont utilisés pour définir simplement la propriété.
La seule différence entre ces paramètres et les propriétés doit être la casse.
️ EFFECTUER un travail minimal dans le constructeur.

Les constructeurs ne doivent pas effectuer de nombreuses tâches autres que la capture des paramètres de
constructeur. Le coût d’un autre traitement doit être retardé jusqu’à ce qu’il soit nécessaire.
️ lever des exceptions à partir de constructeurs d’instance, le cas échéant.

️ déclarer explicitement le constructeur sans paramètre public dans les classes, si un tel constructeur est requis.

Si vous ne déclarez pas explicitement de constructeurs sur un type, de nombreux langages (tels que C#) ajouteront
automatiquement un constructeur sans paramètre public. (Les classes abstraites obtiennent un constructeur
protégé.)
L’ajout d’un constructeur paramétrable à une classe empêche le compilateur d’ajouter le constructeur sans
paramètre. Cela provoque souvent des modifications avec rupture accidentelles.
❌Évitez de définir explicitement des constructeurs sans paramètre sur des structs.
La création de tableau est ainsi plus rapide, car si le constructeur sans paramètre n’est pas défini, il n’est pas
nécessaire qu’il soit exécuté sur chaque emplacement du tableau. Notez que de nombreux compilateurs, y compris
C#, n’autorisent pas les structs à avoir des constructeurs sans paramètre pour cette raison.
❌Évitez d’appeler des membres virtuels sur un objet à l’intérieur de son constructeur.
L’appel d’un membre virtuel entraîne l’appel de la substitution la plus dérivée, même si le constructeur du type le
plus dérivé n’a pas encore été complètement exécuté.

Directives de constructeur de type


️ rendent les constructeurs statiques privés.

Un constructeur statique, également appelé constructeur de classe, est utilisé pour initialiser un type. Le CLR
appelle le constructeur statique avant la création de la première instance du type ou l’appel de tous les membres
statiques de ce type. L’utilisateur n’a aucun contrôle sur le moment où le constructeur statique est appelé. Si un
constructeur statique n’est pas privé, il peut être appelé par un code autre que le CLR. Selon les opérations
effectuées dans le constructeur, cela peut provoquer un comportement inattendu. Le compilateur C# force la
confidentialité des constructeurs statiques.
❌NE levez pas d’exceptions à partir de constructeurs statiques.
Si une exception est levée à partir d’un constructeur de type, le type n’est pas utilisable dans le domaine
d’application actuel.
✔ envisagez d’initialiser des champs statiques inline plutôt que d’utiliser explicitement des constructeurs

statiques, car le runtime est en mesure d’optimiser les performances des types qui n’ont pas de constructeur
statique défini explicitement.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Recommandations en matière de conception de membres
Directives de conception d’infrastructure
Conception d'événements
18/07/2020 • 7 minutes to read

Les événements sont la forme de rappels la plus couramment utilisée (les constructions qui permettent à
l’infrastructure d’appeler le code utilisateur). Parmi les autres mécanismes de rappel, citons les membres qui
prennent des délégués, des membres virtuels et des plug-ins basés sur des interfaces. les données des études
d’utilisation indiquent que la majorité des développeurs sont plus à l’aise avec les événements qu’avec les autres
mécanismes de rappel. Les événements sont parfaitement intégrés à Visual Studio et de nombreux langages.
Il est important de noter qu’il existe deux groupes d’événements : les événements déclenchés avant un état des
modifications du système, appelés pré-événements et les événements déclenchés après la modification d’un État,
appelés événements postérieurs à. Un exemple d’un pré-événement serait Form.Closing , qui est déclenché avant
la fermeture d’un formulaire. Un exemple d’événement de publication est Form.Closed , qui est déclenché après la
fermeture d’un formulaire.
️ Utilisez le terme « Raise » pour les événements plutôt que « déclencher » ou « déclencheur ».

✔ Utilisez System.EventHandler<TEventArgs> plutôt que de créer manuellement de nouveaux délégués à utiliser

comme gestionnaires d’événements.
✔ envisagez d’utiliser une sous-classe de EventArgs comme argument d’événement, sauf si vous êtes

absolument sûr que l’événement n’aura jamais besoin de transmettre des données à la méthode de gestion des
événements, auquel cas vous pouvez utiliser EventArgs directement le type.
Si vous livrez une API à l’aide de EventArgs directement, vous ne serez jamais en mesure d’ajouter des données à
exécuter avec l’événement sans interrompre la compatibilité. Si vous utilisez une sous-classe, même si elle est
initialement vide, vous pouvez ajouter des propriétés à la sous-classe si nécessaire.
✔ Utilisez une méthode virtuelle protégée pour déclencher chaque événement. Cela s’applique uniquement aux

événements non statiques sur les classes non scellées, pas aux structs, aux classes scellées ou aux événements
statiques.
L’objectif de la méthode est de fournir un moyen pour une classe dérivée de gérer l’événement à l’aide d’un
remplacement. La substitution est un moyen plus flexible, plus rapide et plus naturel de gérer les événements de
classe de base dans les classes dérivées. Par Convention, le nom de la méthode doit commencer par « on » et être
suivi du nom de l’événement.
La classe dérivée peut choisir de ne pas appeler l’implémentation de base de la méthode dans sa substitution.
Soyez prêt à le faire en n’incluant aucun traitement dans la méthode qui est requis pour que la classe de base
fonctionne correctement.
️ prennent un paramètre à la méthode protégée qui déclenche un événement.

Le paramètre doit être nommé e et doit être typé en tant que classe d’argument d’événement.
❌NE transmettez pas NULL comme expéditeur lors du déclenchement d’un événement non statique.
️ passer NULL comme expéditeur lors du déclenchement d’un événement statique.

❌NE transmettez pas de valeur null en tant que paramètre de données d’événement lors du déclenchement d’un
événement.
Vous devez passer EventArgs.Empty si vous ne souhaitez pas transmettre de données à la méthode de gestion des
événements. Les développeurs s’attendent à ce que ce paramètre ne soit pas null.
✔ envisagez de déclencher des événements que l’utilisateur final peut annuler. Cela s’applique uniquement aux

pré-événements.
Utilisez System.ComponentModel.CancelEventArgs ou sa sous-classe comme argument d’événement pour
permettre à l’utilisateur final d’annuler des événements.
Conception du gestionnaire d’événements personnalisé
Dans certains cas, il EventHandler<T> ne peut pas être utilisé, par exemple quand le Framework doit fonctionner
avec des versions antérieures du CLR, qui ne prenait pas en charge les génériques. Dans ce cas, vous devrez peut-
être concevoir et développer un délégué de gestionnaire d’événements personnalisé.
️ Utilisez un type de retour void pour les gestionnaires d’événements.

Un gestionnaire d’événements peut appeler plusieurs méthodes de gestion des événements, éventuellement sur
plusieurs objets. Si les méthodes de gestion des événements étaient autorisées à retourner une valeur, il y aurait
plusieurs valeurs de retour pour chaque appel d’événement.
️ Utilisez
✔ object comme type du premier paramètre du gestionnaire d’événements et appelez-le sender .
✔ Utilisez System.EventArgs ou sa sous-classe comme type du deuxième paramètre du gestionnaire

d’événements et appelez-le e .
❌Il n’y a pas plus de deux paramètres sur les gestionnaires d’événements.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Recommandations en matière de conception de membres
Directives de conception d’infrastructure
Conception de champs
18/07/2020 • 3 minutes to read

Le principe de l’encapsulation est l’une des notions les plus importantes en matière de conception orientée objet.
Ce principe stipule que les données stockées dans un objet ne doivent être accessibles qu’à cet objet.
Une méthode utile pour interpréter le principe consiste à indiquer qu’un type doit être conçu de manière à ce que
les modifications apportées aux champs de ce type (modification de nom ou de type) puissent être effectuées sans
interrompre le code autre que pour les membres du type. Cette interprétation implique immédiatement que tous
les champs doivent être privés.
Nous excluons les champs constants et statiques en lecture seule de cette restriction stricte, car ces champs,
presque par définition, ne sont jamais requis pour la modification.
❌NE fournissez pas de champs d’instance publics ou protégés.
Vous devez fournir des propriétés pour accéder aux champs au lieu de les rendre publics ou protégés.
️ Utilisez des champs constants pour les constantes qui ne changeront jamais.

Le compilateur grave les valeurs des champs const directement dans le code appelant. Par conséquent, les valeurs
const ne peuvent jamais être modifiées sans risque d’interruption de la compatibilité.
️ Utilisez des champs statiques publics
✔ readonly pour les instances d’objet prédéfinies.
S’il existe des instances prédéfinies du type, déclarez-les en tant que champs statiques publics en lecture seule du
type lui-même.
❌N’assignez pas d’instances de types mutables à des readonly champs.
Un type mutable est un type dont les instances peuvent être modifiées une fois qu’elles ont été instanciées. Par
exemple, les tableaux, la plupart des collections et des flux sont des types mutables, mais System.Int32 , System.Uri
et System.String sont tous immuables. Le modificateur en lecture seule sur un champ de type référence empêche le
remplacement de l’instance stockée dans le champ, mais n’empêche pas la modification des données d’instance du
champ en appelant des membres qui modifient l’instance.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Recommandations en matière de conception de membres
Directives de conception d’infrastructure
Méthodes d’extension
18/07/2020 • 5 minutes to read

Les méthodes d’extension sont une fonctionnalité de langage qui permet d’appeler des méthodes statiques à l’aide
de la syntaxe d’appel de méthode d’instance. Ces méthodes doivent accepter au moins un paramètre, qui
représente l’instance sur laquelle la méthode doit fonctionner.
La classe qui définit ces méthodes d’extension est appelée classe « sponsor » et doit être déclarée comme static.
Pour utiliser des méthodes d’extension, vous devez importer l’espace de noms définissant la classe sponsor.
❌Évitez les frivolously définissant les méthodes d’extension, en particulier sur les types dont vous n’êtes pas
propriétaire.
Si vous disposez d’un code source de type, envisagez plutôt d’utiliser des méthodes d’instance normales. Si vous
ne possédez pas et que vous souhaitez ajouter une méthode, soyez très vigilant. La libéralisation de l’utilisation des
méthodes d’extension risque d’encombrer les API de types qui n’ont pas été conçues pour avoir ces méthodes.
️ envisagez d’utiliser des méthodes d’extension dans l’un des scénarios suivants :

Pour fournir des fonctionnalités d’assistance relatives à chaque implémentation d’une interface, si cette
fonctionnalité peut être écrite en termes d’interface principale. Cela est dû au fait que les implémentations
concrètes ne peuvent pas être assignées à des interfaces. Par exemple, les LINQ to Objects opérateurs sont
implémentés en tant que méthodes d’extension pour tous les IEnumerable<T> types. Ainsi, toutes les
IEnumerable<> implémentations sont automatiquement compatibles LINQ.

Quand une méthode d’instance introduit une dépendance sur un certain type, mais une telle dépendance
rompt les règles de gestion des dépendances. Par exemple, une dépendance de String à System.Uri n’est
probablement pas souhaitable. par conséquent, String.ToUri() la méthode d’instance qui retourne
System.Uri serait la mauvaise conception du point de vue de la gestion des dépendances. Le retour d’une
méthode d’extension statique Uri.ToUri(this string str) System.Uri est une bien meilleure conception.
❌Évitez de définir des méthodes d’extension sur System.Object .
Les utilisateurs VB ne peuvent pas appeler ces méthodes sur des références d’objet à l’aide de la syntaxe de
méthode d’extension. VB ne prend pas en charge l’appel de telles méthodes, car, en VB, la déclaration d’une
référence en tant qu’objet force la liaison tardive de tous les appels de méthode sur celui-ci (le membre réel appelé
est déterminé au moment de l’exécution), tandis que les liaisons aux méthodes d’extension sont déterminées au
moment de la compilation (liaison anticipée).
Notez que l’instruction s’applique à d’autres langages où le même comportement de liaison est présent, ou où les
méthodes d’extension ne sont pas prises en charge.
❌NE placez pas les méthodes d’extension dans le même espace de noms que le type étendu, sauf s’il s’agit
d’ajouter des méthodes aux interfaces ou pour la gestion des dépendances.
❌Évitez de définir deux ou plusieurs méthodes d’extension avec la même signature, même si elles se trouvent
dans des espaces de noms différents.
✔ ENVISAGER de définir des méthodes d’extension dans le même espace de noms que le type étendu si le type

est une interface et si les méthodes d’extension sont destinées à être utilisées dans la plupart ou dans tous les cas.
❌NE définissez pas de méthodes d’extension implémentant une fonctionnalité dans les espaces de noms
généralement associés à d’autres fonctionnalités. Au lieu de cela, définissez-les dans l’espace de noms associé à la
fonctionnalité à laquelle ils appartiennent.
❌Évitez les noms génériques des espaces de noms dédiés aux méthodes d’extension (par exemple,
« extensions »). Utilisez un nom descriptif (par exemple, « routage ») à la place.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Recommandations en matière de conception de membres
Directives de conception d’infrastructure
Surcharges d'opérateurs
18/07/2020 • 7 minutes to read

Les surcharges d’opérateur permettent d’afficher les types de Framework comme s’ils étaient des primitives de
langage intégrées.
Bien qu’elles soient autorisées et utiles dans certains cas, les surcharges d’opérateur doivent être utilisées avec
prudence. Il existe de nombreux cas dans lesquels la surcharge d’opérateur a été abusée, par exemple quand les
concepteurs de Framework ont commencé à utiliser des opérateurs pour les opérations qui doivent être des
méthodes simples. Les instructions suivantes doivent vous aider à déterminer quand et comment utiliser la
surcharge d’opérateur.
❌Évitez de définir des surcharges d’opérateur, sauf dans les types qui doivent ressembler à des types primitifs
(intégrés).
️ envisagez de définir des surcharges d’opérateur dans un type qui doit ressembler à un type primitif.

Par exemple, System.String a operator== et operator!= défini.
️ définir des surcharges d’opérateur dans des structs qui représentent des nombres (tels que System.Decimal ).

❌NE soyez pas au fait de définir des surcharges d’opérateur.
La surcharge d’opérateur est utile dans les cas où il est immédiatement évident de savoir ce que sera le résultat de
l’opération. Par exemple, il est logique de pouvoir soustraire l’un DateTime d’un autre DateTime et d’obtenir un
TimeSpan . Toutefois, il n’est pas approprié d’utiliser l’opérateur d’Union logique pour unir deux requêtes de base
de données, ou pour utiliser l’opérateur Shift pour écrire dans un flux.
❌NE fournissez pas de surcharges d’opérateur sauf si au moins l’un des opérandes est du type définissant la
surcharge.
️ surchargent les opérateurs de manière symétrique.

Par exemple, si vous surchargez le operator== , vous devez également surcharger operator!= . De même, si vous
surchargez le operator< , vous devez également surcharger operator> , et ainsi de suite.
✔ envisagez de fournir des méthodes avec des noms conviviaux qui correspondent à chaque opérateur

surchargé.
De nombreux langages ne prennent pas en charge la surcharge d’opérateur. Pour cette raison, il est recommandé
que les types qui surchargent les opérateurs incluent une méthode secondaire avec un nom spécifique au domaine
approprié qui fournit des fonctionnalités équivalentes.
Le tableau suivant contient une liste d’opérateurs et les noms de méthode conviviaux correspondants.

SY M B O L E D’O P ÉRAT EUR C # N O M DES M ÉTA DO N N ÉES N O M C O N VIVIA L

N/A op_Implicit To<TypeName>/From<TypeName>

N/A op_Explicit To<TypeName>/From<TypeName>

+ (binary) op_Addition Add


SY M B O L E D’O P ÉRAT EUR C # N O M DES M ÉTA DO N N ÉES N O M C O N VIVIA L

- (binary) op_Subtraction Subtract

* (binary) op_Multiply Multiply

/ op_Division Divide

% op_Modulus Mod or Remainder

^ op_ExclusiveOr Xor

& (binary) op_BitwiseAnd BitwiseAnd

| op_BitwiseOr BitwiseOr

&& op_LogicalAnd And

|| op_LogicalOr Or

= op_Assign Assign

<< op_LeftShift LeftShift

>> op_RightShift RightShift

N/A op_SignedRightShift SignedRightShift

N/A op_UnsignedRightShift UnsignedRightShift

== op_Equality Equals

!= op_Inequality Equals

> op_GreaterThan CompareTo

< op_LessThan CompareTo

>= op_GreaterThanOrEqual CompareTo

<= op_LessThanOrEqual CompareTo

*= op_MultiplicationAssignment Multiply

-= op_SubtractionAssignment Subtract

^= op_ExclusiveOrAssignment Xor

<<= op_LeftShiftAssignment LeftShift


SY M B O L E D’O P ÉRAT EUR C # N O M DES M ÉTA DO N N ÉES N O M C O N VIVIA L

%= op_ModulusAssignment Mod

+= op_AdditionAssignment Add

&= op_BitwiseAndAssignment BitwiseAnd

|= op_BitwiseOrAssignment BitwiseOr

, op_Comma Comma

/= op_DivisionAssignment Divide

-- op_Decrement Decrement

++ op_Increment Increment

- (unary) op_UnaryNegation Negate

+ (unary) op_UnaryPlus Plus

~ op_OnesComplement OnesComplement

Surcharge de l’opérateur = =
La surcharge operator == est assez complexe. La sémantique de l’opérateur doit être compatible avec plusieurs
autres membres, tels que Object.Equals .
Opérateurs de conversion
Les opérateurs de conversion sont des opérateurs unaires qui autorisent la conversion d’un type en un autre. Les
opérateurs doivent être définis en tant que membres statiques sur l’opérande ou le type de retour. Il existe deux
types d’opérateurs de conversion : implicites et explicites.
❌NE fournissez pas d’opérateur de conversion si une telle conversion n’est pas clairement attendue par les
utilisateurs finaux.
❌NE définissez pas d’opérateurs de conversion en dehors du domaine d’un type.
Par exemple, Int32 , Double et Decimal sont tous des types numériques, alors que DateTime n’est pas. Par
conséquent, il ne doit y avoir aucun opérateur de conversion pour convertir un Double(long) en DateTime . Un
constructeur est préféré dans ce cas.
❌NE fournissez pas d’opérateur de conversion implicite si la conversion est potentiellement perdue.
Par exemple, il ne doit pas y avoir de conversion implicite de Double en Int32 , car Double a une plage plus large
que Int32 . Un opérateur de conversion explicite peut être fourni même si la conversion est potentiellement
perdue.
❌NE levez pas d’exceptions à partir de casts implicites.
Il est très difficile pour les utilisateurs finaux de comprendre ce qui se passe, car ils n’ont peut-être pas conscience
qu’une conversion a lieu.
✔ Levez une exception System.InvalidCastException si un appel à un opérateur de cast entraîne une conversion

avec perte et que le contrat de l’opérateur n’autorise pas les conversions avec perte de résultats.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Recommandations en matière de conception de membres
Directives de conception d’infrastructure
Conception de paramètres
18/07/2020 • 15 minutes to read

Cette section fournit des instructions générales sur la conception des paramètres, y compris des sections avec des
indications pour la vérification des arguments. En outre, vous devez vous reporter aux instructions décrites dans
Naming Parameters.
️ Utilisez le type de paramètre le moins dérivé qui fournit les fonctionnalités requises par le membre.

Supposons, par exemple, que vous souhaitiez concevoir une méthode qui énumère une collection et imprime
chaque élément sur la console. Une telle méthode doit prendre IEnumerable comme paramètre, et non ArrayList ou
IList , par exemple.
❌N’utilisez pas de paramètres réservés.
Si une entrée supplémentaire à un membre est nécessaire dans une version ultérieure, une nouvelle surcharge
peut être ajoutée.
❌N’ont pas de méthodes exposées publiquement qui prennent des pointeurs, des tableaux de pointeurs ou des
tableaux multidimensionnels en tant que paramètres.
Les pointeurs et les tableaux multidimensionnels sont relativement difficiles à utiliser correctement. Dans presque
tous les cas, les API peuvent être repensées pour éviter de prendre ces types comme paramètres.
✔ Placez tous les out paramètres après la valeur et les ref paramètres (à l’exception des tableaux de

paramètres), même si cela entraîne une incohérence dans l’ordre des paramètres entre les surcharges (consultez
surcharge des membres).
Les out paramètres peuvent être considérés comme des valeurs de retour supplémentaires et les regrouper
ensemble rendent la signature de méthode plus facile à comprendre.
✔ sont cohérentes dans les paramètres d’attribution de noms lors du remplacement des membres ou de

l’implémentation des membres d’interface.
Cela communique mieux la relation entre les méthodes.
Choix entre les paramètres enum et Boolean
️ Utilisez des enums si un membre a sinon deux paramètres booléens ou plus.

❌N’utilisez pas de valeurs booléennes sauf si vous êtes absolument sûr qu’il n’y aura jamais besoin de plus de
deux valeurs.
Les enums vous donnent de la place pour l’ajout ultérieur de valeurs, mais vous devez être conscient de toutes les
implications liées à l’ajout de valeurs aux enums, qui sont décrites dans la section enum Design.
✔ envisagez d’utiliser des valeurs booléennes pour les paramètres de constructeur qui sont véritablement des

valeurs à deux États et qui sont simplement utilisées pour initialiser des propriétés booléennes.
Validation des arguments
️ validez les arguments passés aux membres publics, protégés ou implémentés explicitement. Throw

System.ArgumentException , ou l’une de ses sous-classes, si la validation échoue.
Notez que la validation réelle ne doit pas nécessairement se produire dans le membre public ou protégé lui-même.
Cela peut se produire à un niveau inférieur dans une routine privée ou interne. Le point principal est que la surface
d’exposition entière exposée aux utilisateurs finaux vérifie les arguments.
✔ Levez ArgumentNullException une exception si un argument null est passé et que le membre ne prend pas en

charge les arguments null.
️ validez les paramètres Enum.

Ne partez pas du principe que les arguments enum se trouvent dans la plage définie par l’enum. Le CLR permet de
convertir n’importe quelle valeur entière en valeur enum, même si la valeur n’est pas définie dans l’enum.
❌N’utilisez pas Enum.IsDefined pour les vérifications de plage d’énumération.
️ N’oubliez pas que les arguments mutables peuvent avoir changé après leur validation.

Si le membre est sensible à la sécurité, il est recommandé d’effectuer une copie, puis de valider et de traiter
l’argument.
Passage de paramètres
Du point de vue d’un concepteur d’infrastructure, il existe trois groupes principaux de paramètres : les paramètres
par valeur, ref les paramètres et les out paramètres.
Quand un argument est passé via un paramètre par valeur, le membre reçoit une copie de l’argument réel passé. Si
l’argument est un type valeur, une copie de l’argument est placée sur la pile. Si l’argument est un type référence,
une copie de la référence est placée sur la pile. La plupart des langages CLR populaires, tels que C#, VB.NET et C++,
passent par défaut les paramètres par valeur.
Quand un argument est passé via un ref paramètre, le membre reçoit une référence à l’argument réel passé. Si
l’argument est un type valeur, une référence à l’argument est placée sur la pile. Si l’argument est un type référence,
une référence à la référence est placée sur la pile. Ref les paramètres peuvent être utilisés pour permettre au
membre de modifier les arguments passés par l’appelant.
Out les paramètres sont similaires aux ref paramètres, avec quelques petites différences. Initialement, le
paramètre est considéré comme non assigné et ne peut pas être lu dans le corps du membre avant qu’une valeur
ne lui soit assignée. En outre, une valeur doit être assignée au paramètre avant que le membre retourne.
❌Évitez d’utiliser des out ref paramètres ou.
L' out utilisation ref de paramètres ou nécessite une expérience avec les pointeurs, la compréhension de la
différence entre les types valeur et les types référence, ainsi que la gestion des méthodes avec plusieurs valeurs de
retour. En outre, la différence entre les out ref paramètres et n’est pas largement comprise. Les architectes
d’infrastructure qui sont conçus pour un public général ne doivent pas s’attendre à ce que les utilisateurs maîtrisent
le travail avec les out ref paramètres ou.
❌NE transmettez pas de types référence par référence.
Il existe quelques exceptions limitées à la règle, comme une méthode qui peut être utilisée pour échanger des
références.
Membres avec nombre variable de paramètres
Les membres qui peuvent prendre un nombre variable d’arguments sont exprimés en fournissant un paramètre de
tableau. Par exemple, String fournit la méthode suivante :

public class String {


public static string Format(string format, object[] parameters);
}

Un utilisateur peut ensuite appeler la String.Format méthode, comme suit :


String.Format("File {0} not found in {1}",new object[]{filename,directory});
L’ajout du mot clé params C# à un paramètre de tableau modifie le paramètre en paramètre de tableau « params »
et fournit un raccourci pour créer un tableau temporaire.

public class String {


public static string Format(string format, params object[] parameters);
}

Cela permet à l’utilisateur d’appeler la méthode en passant les éléments du tableau directement dans la liste
d’arguments.
String.Format("File {0} not found in {1}",filename,directory);

Notez que le mot clé params ne peut être ajouté qu’au dernier paramètre de la liste de paramètres.
✔ envisagez d’ajouter le mot clé params aux paramètres de tableau si vous vous attendez à ce que les utilisateurs

finaux passent des tableaux avec un petit nombre d’éléments. S’il est prévu que beaucoup d’éléments soient passés
dans des scénarios courants, les utilisateurs ne passeront probablement pas ces éléments Inline, et le mot clé
params n’est donc pas nécessaire.
❌Évitez d’utiliser des tableaux de paramètres si l’appelant aurait presque toujours l’entrée déjà dans un tableau.
Par exemple, les membres avec des paramètres de tableau d’octets ne seraient presque jamais appelés en passant
des octets individuels. Pour cette raison, les paramètres de tableau d’octets dans le .NET Framework n’utilisent pas
le mot clé params.
❌N’utilisez pas de tableaux params si le tableau est modifié par le membre qui prend le paramètre de tableau
params.
Du fait que de nombreux compilateurs transforment les arguments en un tableau temporaire au niveau du site
d’appel, le tableau peut être un objet temporaire et, par conséquent, toutes les modifications apportées au tableau
seront perdues.
✔ envisagez d’utiliser le mot clé params dans une surcharge simple, même si une surcharge plus complexe ne

peut pas l’utiliser.
Demandez-vous si les utilisateurs ont un tableau de paramètres dans une surcharge, même s’ils n’étaient pas dans
toutes les surcharges.
️ essayez d’ordonner les paramètres afin de pouvoir utiliser le mot clé params.

✔ envisagez de fournir des surcharges et des chemins de code spéciaux pour les appels avec un petit nombre

d’arguments dans les API très sensibles aux performances.
Cela permet d’éviter la création d’objets tableau lorsque l’API est appelée avec un petit nombre d’arguments.
Formez les noms des paramètres en utilisant une forme singulière du paramètre de tableau et en ajoutant un
suffixe numérique.
Vous ne devez effectuer cette opération que si vous envisagez de faire de la casse spéciale le chemin de code entier,
et non pas simplement de créer un tableau et d’appeler la méthode plus générale.
️ N’oubliez pas que la valeur NULL peut être passée comme argument de tableau params.

Vous devez vérifier que le tableau n’a pas la valeur null avant le traitement.
❌N’utilisez pas les varargs méthodes, également appelées points de suspension.
Certains langages CLR, tels que C++, prennent en charge une autre convention pour passer des listes de
paramètres de variables appelées varargs méthodes. La Convention ne doit pas être utilisée dans les frameworks,
car elle n’est pas conforme CLS.
Paramètres du pointeur
En général, les pointeurs ne doivent pas apparaître dans la surface d’exposition publique d’un Framework de code
géré bien conçu. La plupart du temps, les pointeurs doivent être encapsulés. Toutefois, dans certains cas, les
pointeurs sont requis pour des raisons d’interopérabilité et l’utilisation de pointeurs dans de tels cas est
appropriée.
✔ fournissez une alternative pour tout membre qui accepte un argument de pointeur, car les pointeurs ne sont

pas conformes CLS.
❌Évitez d’exécuter une vérification coûteuse des arguments de pointeur.
️ Suivez les conventions courantes liées aux pointeurs lors de la conception de membres avec des pointeurs.

Par exemple, il n’est pas nécessaire de passer l’index de début, car une opération arithmétique de pointeur simple
peut être utilisée pour obtenir le même résultat.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Recommandations en matière de conception de membres
Directives de conception d’infrastructure
Conception en vue de l'extensibilité
18/07/2020 • 2 minutes to read

Un aspect important de la conception d’une infrastructure consiste à s’assurer que l’extensibilité de l’infrastructure
a été soigneusement étudiée. Cela nécessite que vous compreniez les coûts et les avantages associés à différents
mécanismes d’extensibilité. Ce chapitre vous aide à choisir les mécanismes d’extensibilité (sous-classe,
événements, membres virtuels, rappels, etc.) qui répondent le mieux aux besoins de votre infrastructure.
Il existe de nombreuses façons d’autoriser l’extensibilité dans les infrastructures. Ils vont de moins puissants mais
moins coûteux à très puissants, mais onéreux. Pour toute exigence d’extensibilité donnée, vous devez choisir le
mécanisme d’extensibilité le moins coûteux qui répond aux exigences. Gardez à l’esprit qu’il est généralement
possible d’ajouter plus d’extensibilité ultérieurement, mais vous ne pouvez jamais le retirer sans introduire de
modifications avec rupture.

Dans cette section


Classes non scellées
Membres protégés
Événements et rappels
Membres virtuels
Abstractions (types et interfaces abstraits)
Classes de base pour l’implémentation d’abstractions
Sceller
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft
Windows.

Voir aussi
Directives de conception d’infrastructure
Classes unsealed
18/07/2020 • 2 minutes to read

Les classes sealed ne peuvent pas être héritées de, et elles empêchent l’extensibilité. En revanche, les classes qui
peuvent être héritées de sont appelées « classes non scellées ».
✔ envisagez d’utiliser des classes non scellées sans membres virtuels ou protégés comme un excellent moyen de

fournir une extensibilité encore plus coûteuse à un Framework.
Les développeurs souhaitent souvent hériter de classes non scellées afin d’ajouter des membres de commodité
tels que des constructeurs personnalisés, de nouvelles méthodes ou des surcharges de méthode. Par exemple,
System.Messaging.MessageQueue est déscellée et permet ainsi aux utilisateurs de créer des files d’attente
personnalisées qui sont par défaut vers un chemin d’accès de file d’attente particulier ou d’ajouter des méthodes
personnalisées qui simplifient l’API pour des scénarios spécifiques.
Les classes sont déscellées par défaut dans la plupart des langages de programmation, et il s’agit également de la
valeur par défaut recommandée pour la plupart des classes dans les infrastructures. L’extensibilité offerte par les
types non scellés est grandement appréciée par les utilisateurs du Framework et très peu coûteuse à fournir en
raison des coûts de test relativement faibles associés aux types non scellés.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Conception en vue de l’extensibilité
Sceller
Membres protégés
18/07/2020 • 2 minutes to read

Les membres protégés par eux-mêmes ne fournissent pas d’extensibilité, mais ils peuvent rendre l’extensibilité à
l’aide d’une sous-classe plus puissante. Ils peuvent être utilisés pour exposer des options de personnalisation
avancées sans compliquer inutilement l’interface publique principale.
Les concepteurs de Framework doivent être vigilants avec les membres protégés, car le nom « protégé » peut avoir
un sens de sécurité faux. Tout le monde peut sous-faire une classe non scellée et accéder à des membres protégés,
de sorte que toutes les pratiques de codage défensives utilisées pour les membres publics s’appliquent aux
membres protégés.
️ envisagez d’utiliser des membres protégés pour une personnalisation avancée.

✔ Traitez les membres protégés dans des classes non scellées comme étant publics dans le cadre de la sécurité, de

la documentation et de l’analyse de compatibilité.
Tout le monde peut hériter d’une classe et accéder aux membres protégés.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Conception en vue de l’extensibilité
Événements et rappels
18/07/2020 • 3 minutes to read

Les rappels sont des points d’extensibilité qui permettent à un Framework d’effectuer un rappel dans le code
utilisateur via un délégué. Ces délégués sont généralement passés à l’infrastructure par le biais d’un paramètre
d’une méthode.
Les événements sont un cas spécial de rappels qui prend en charge une syntaxe pratique et cohérente pour fournir
le délégué (un gestionnaire d’événements). En outre, la saisie semi-automatique des instructions de Visual Studio
et les concepteurs fournissent de l’aide pour l’utilisation des API basées sur les événements. (Voir conception des
événements.)
✔ envisagez d’utiliser des rappels pour permettre aux utilisateurs de fournir un code personnalisé à exécuter par

le Framework.
✔ envisagez d’utiliser des événements pour permettre aux utilisateurs de personnaliser le comportement d’une

infrastructure sans avoir besoin de comprendre la conception orientée objet.
✔ préférez des événements aux rappels simples, car ils sont plus familiers à un plus grand nombre de

développeurs et sont intégrés à la saisie semi-automatique des instructions Visual Studio.
❌Évitez d’utiliser des rappels dans les API sensibles aux performances.
✔ Utilisez les nouveaux Func<...> types,
️ Action<...> ou Expression<...> à la place des délégués personnalisés,
lors de la définition d’API avec des rappels.
Func<...> et Action<...> représentent des délégués génériques. Expression<...> représente les définitions de
fonction qui peuvent être compilées et appelées par la suite au moment de l’exécution, mais peuvent également
être sérialisées et passées aux processus distants.
✔ Mesurez et comprenez les implications en matière de performances de l’utilisation de
️ Expression<...> , au lieu
d’utiliser des Func<...> Action<...> délégués et.
Expression<...> dans la plupart des cas, les types sont logiquement équivalents aux Func<...> Action<...>
délégués et. La principale différence réside dans le fait que les délégués sont destinés à être utilisés dans les
scénarios de processus local. les expressions sont prévues dans les cas où il est bénéfique et possible d’évaluer
l’expression dans un processus ou un ordinateur distant.
✔ comprenez qu’en appelant un délégué, vous exécutez du code arbitraire et cela peut avoir des répercussions en

matière de sécurité, d’exactitude et de compatibilité.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Conception en vue de l’extensibilité
Directives de conception d’infrastructure
Membres virtuels
18/07/2020 • 3 minutes to read

Les membres virtuels peuvent être remplacés, ce qui modifie le comportement de la sous-classe. Elles sont assez
similaires aux rappels en termes d’extensibilité, mais elles sont préférables en termes de performances d’exécution
et de consommation de mémoire. En outre, les membres virtuels s’apaisent plus naturellement dans les scénarios
qui requièrent la création d’un type spécial de type existant (spécialisation).
Les membres virtuels sont plus performants que les rappels et les événements, mais ne sont pas plus performants
que les méthodes non virtuelles.
Le principal inconvénient des membres virtuels est que le comportement d’un membre virtuel ne peut être
modifié qu’au moment de la compilation. Le comportement d’un rappel peut être modifié au moment de
l’exécution.
Les membres virtuels, comme les rappels (et peut-être plus que les rappels), sont coûteux pour la conception, le
test et la maintenance, car tout appel à un membre virtuel peut être substitué de manière imprévisible et peut
exécuter du code arbitraire. En outre, un plus grand nombre d’efforts est généralement requis pour définir
clairement le contrat des membres virtuels, de sorte que le coût de la conception et de la documentation est plus
élevé.
❌N’effectuez pas de membres virtuels, sauf si vous avez une bonne raison de le faire et que vous connaissez tous
les coûts liés à la conception, au test et à la maintenance des membres virtuels.
Les membres virtuels sont moins indulgent avec en termes de modifications qui peuvent être apportées sans
interrompre la compatibilité. En outre, ils sont plus lents que les membres non virtuels, principalement parce que
les appels aux membres virtuels ne sont pas Inline.
️ ENVISAGER de limiter l’extensibilité à ce qui est absolument nécessaire.

✔ préférez l’accessibilité protégée par rapport à l’accessibilité publique pour les membres virtuels. Les membres

publics doivent fournir une extensibilité (si nécessaire) en appelant dans un membre virtuel protégé.
Les membres publics d’une classe doivent fournir le jeu de fonctionnalités approprié aux consommateurs directs
de cette classe. Les membres virtuels sont conçus pour être remplacés dans les sous-classes et l’accessibilité
protégée est un excellent moyen d’étendre l’ensemble des points d’extensibilité virtuels à l’emplacement où ils
peuvent être utilisés.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Conception en vue de l’extensibilité
Abstractions (Types et interfaces abstraits)
18/07/2020 • 4 minutes to read

Une abstraction est un type qui décrit un contrat, mais qui ne fournit pas une implémentation complète du contrat.
Les abstractions sont généralement implémentées en tant que classes abstraites ou interfaces, et elles sont fournies
avec un ensemble bien défini de documentation de référence décrivant la sémantique requise des types qui
implémentent le contrat. Certaines des abstractions les plus importantes dans le .NET Framework incluent Stream ,
IEnumerable<T> et Object .
Vous pouvez étendre des frameworks en implémentant un type concret qui prend en charge le contrat d’une
abstraction et à l’aide de ce type concret avec les API d’infrastructure consommant (fonctionnant sur) l’abstraction.
Une abstraction significative et utile qui peut résister au test de temps est très difficile à concevoir. La difficulté
principale est l’obtention du jeu de membres approprié, et pas plus ou moins. Si une abstraction a trop de
membres, elle devient difficile, voire impossible à implémenter. S’il a trop peu de membres pour la fonctionnalité
promis, il devient inutile dans de nombreux scénarios intéressants.
Un trop grand nombre d’abstractions dans une infrastructure affecte également la convivialité de l’infrastructure. Il
est souvent assez difficile de comprendre une abstraction sans comprendre comment elle s’intègre à la plus grande
image des implémentations concrètes et aux API opérant sur l’abstraction. En outre, les noms des abstractions et de
leurs membres sont nécessairement abstraits, ce qui les rend souvent énigmatiques et incompréhensibles sans
avoir d’abord à comprendre le contexte plus large de leur utilisation.
Toutefois, les abstractions offrent une extensibilité extrêmement puissante que les autres mécanismes
d’extensibilité ne correspondent pas souvent. Ils sont au cœur de nombreux modèles architecturaux, tels que les
plug-ins, l’inversion de contrôle (IoC), les pipelines, etc. Ils sont également extrêmement importants pour la
testabilité des frameworks. De bonnes abstractions permettent de remplacer les dépendances lourdes dans le
cadre des tests unitaires. En résumé, les abstractions sont responsables de la richesse recherchée des frameworks
orientés objet modernes.
❌NE fournissez pas d’abstractions à moins d’être testées en développant plusieurs implémentations concrètes et
API consommant les abstractions.
️ choisir soigneusement entre une classe abstraite et une interface lors de la conception d’une abstraction.

✔ envisagez de fournir des tests de référence pour les implémentations concrètes des abstractions. Ces tests

doivent permettre aux utilisateurs de tester si leurs implémentations implémentent correctement le contrat.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Conception en vue de l’extensibilité
Classes de base pour l'implémentation d'abstractions
18/07/2020 • 4 minutes to read

Strictement parlant, une classe devient une classe de base lorsqu’une autre classe en est dérivée. Dans le cadre de
cette section, toutefois, une classe de base est une classe conçue principalement pour fournir une abstraction
commune ou pour d’autres classes afin de réutiliser une implémentation par défaut via l’héritage. Les classes de
base se trouvent généralement au milieu des hiérarchies d’héritage, entre une abstraction à la racine d’une
hiérarchie et plusieurs implémentations personnalisées en bas.
Elles servent d’assistance d’implémentation pour l’implémentation d’abstractions. Par exemple, l’une des
abstractions de l’infrastructure pour les collections ordonnées d’éléments est l' IList<T> interface.
IList<T>L’implémentation de n’est pas simple et, par conséquent, l’infrastructure fournit plusieurs classes de base,
telles que Collection<T> et KeyedCollection<TKey,TItem> , qui servent d’assistance pour l’implémentation des
collections personnalisées.
Les classes de base ne sont généralement pas adaptées à des abstractions, car elles ont tendance à contenir un trop
grand nombre d’implémentations. Par exemple, la Collection<T> classe de base contient un grand nombre
d’implémentations relatives au fait qu’elle implémente l’interface non générique IList (pour une intégration
optimale avec les collections non génériques) et au fait qu’il s’agit d’une collection d’éléments stockés en mémoire
dans l’un de ses champs.
Comme indiqué précédemment, les classes de base peuvent fournir une aide précieuse pour les utilisateurs qui
doivent implémenter des abstractions, mais en même temps, elles peuvent être une responsabilité significative. Ils
ajoutent de la surface d’exposition et augmentent la profondeur des hiérarchies d’héritage. par conséquent, le
concept complique le Framework. Par conséquent, les classes de base doivent être utilisées uniquement si elles
fournissent une valeur significative aux utilisateurs de l’infrastructure. Elles doivent être évitées si elles fournissent
la valeur uniquement aux implémenteurs de l’infrastructure, auquel cas la délégation à une implémentation interne
au lieu d’un héritage à partir d’une classe de base doit être fortement prise en compte.
✔ ENVISAGER de rendre les classes de base abstraites même si elles ne contiennent pas de membres abstraits.

Cela communique clairement aux utilisateurs que la classe est conçue pour être héritée uniquement.
✔ envisagez de placer les classes de base dans un espace de noms distinct des types de scénario principaux. Par

définition, les classes de base sont destinées aux scénarios d’extensibilité avancée et, par conséquent, ne sont pas
intéressantes pour la majorité des utilisateurs.
❌Évitez de nommer les classes de base avec un suffixe « base » si la classe est destinée à être utilisée dans les API
publiques.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Conception en vue de l’extensibilité
Sceller
18/07/2020 • 4 minutes to read

L’une des fonctionnalités des frameworks orientés objet est que les développeurs peuvent les étendre et les
personnaliser de manière inattendue par les concepteurs de Framework. Il s’agit de la puissance et du danger de la
conception extensible. Lorsque vous concevez votre infrastructure, il est donc très important de concevoir
soigneusement l’extensibilité quand vous le souhaitez, et de limiter l’extensibilité quand elle est dangereuse.
Mécanisme puissant qui empêche la fermeture de l’extensibilité. Vous pouvez sceller la classe ou des membres
individuels. La fermeture d’une classe empêche les utilisateurs d’hériter de la classe. La fermeture d’un membre
empêche les utilisateurs de se substituer à un membre particulier.
❌NE scellez pas les classes sans avoir une bonne raison de le faire.
Sceller une classe parce que vous ne pouvez pas considérer un scénario d’extensibilité n’est pas une bonne raison.
Les utilisateurs de l’infrastructure aiment hériter des classes pour différentes raisons qui ne sont pas évidentes,
comme l’ajout de membres de commodité. Consultez classes non scellées pour obtenir des exemples de raisons
non évidentes que les utilisateurs souhaitent hériter d’un type.
Les bonnes raisons pour sceller une classe sont les suivantes :
La classe est une classe statique. Consultez conception de classes statiques.
La classe stocke des secrets sensibles à la sécurité dans les membres protégés hérités.
La classe hérite de nombreux membres virtuels et le coût de leur scellement individuellement compenserait
les avantages de laisser la classe non scellée.
La classe est un attribut qui nécessite une recherche très rapide au moment de l’exécution. Les attributs
scellés ont des niveaux de performances légèrement supérieurs à ceux des attributs non scellés. Consultez
attributs.
❌NE déclarez pas de membres protégés ou virtuels sur des types sealed.
Par définition, les types sealed ne peuvent pas être hérités de. Cela signifie que les membres protégés sur les types
sealed ne peuvent pas être appelés, et les méthodes virtuelles sur les types sealed ne peuvent pas être substituées.
️ envisagez de sceller les membres que vous substituez.

Les problèmes qui peuvent résulter de l’introduction de membres virtuels (décrits dans membres virtuels)
s’appliquent également aux remplacements, bien qu’à un degré légèrement moindre. Le scellage d’un
remplacement vous protège de ces problèmes à partir de ce point dans la hiérarchie d’héritage.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Conception en vue de l’extensibilité
Classes non scellées
Instructions de conception pour les exceptions
18/07/2020 • 2 minutes to read

La gestion des exceptions présente de nombreux avantages par rapport aux rapports d’erreurs basés sur les
valeurs de retour. Une bonne conception d’infrastructure aide le développeur d’applications à tirer parti des
avantages des exceptions. Cette section présente les avantages des exceptions et présente les instructions
permettant de les utiliser efficacement.

Dans cette section


Levée d’exception
Utilisation de types d’exceptions standard
Exceptions et performances
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Levée d'exceptions
18/07/2020 • 6 minutes to read

Les instructions de levée des exceptions décrites dans cette section nécessitent une bonne définition de la
signification de l’échec de l’exécution. L’échec de l’exécution se produit chaque fois qu’un membre ne peut pas faire
ce qu’il a prévu (ce que signifie le nom du membre). Par exemple, si la OpenFile méthode ne peut pas retourner un
descripteur de fichier ouvert à l’appelant, elle est considérée comme un échec d’exécution.
La plupart des développeurs se familiarisent avec l’utilisation d’exceptions pour les erreurs d’utilisation telles que la
division par zéro ou les références null. Dans le Framework, les exceptions sont utilisées pour toutes les conditions
d’erreur, y compris les erreurs d’exécution.
❌NE retourne pas de codes d’erreur.
Les exceptions sont le principal moyen de signaler les erreurs dans les infrastructures.
️ signalent des échecs d’exécution en levant des exceptions.

✔ envisagez de mettre fin au processus en appelant System.Environment.FailFast (.NET Framework fonctionnalité

2,0) au lieu de lever une exception si votre code rencontre une situation où il n’est pas sûr pour une exécution
ultérieure.
❌N’utilisez pas d’exceptions pour le déroulement normal du contrôle, si possible.
À l’exception des défaillances système et des opérations avec des conditions de concurrence potentielles, les
concepteurs d’infrastructure doivent concevoir des API pour que les utilisateurs puissent écrire du code qui ne lève
pas d’exceptions. Par exemple, vous pouvez fournir un moyen de vérifier les conditions préalables avant d’appeler
un membre afin que les utilisateurs puissent écrire du code qui ne lève pas d’exceptions.
Le membre utilisé pour vérifier les conditions préalables d’un autre membre est souvent appelé testeur, et le
membre qui effectue réellement le travail est appelé Doer.
Dans certains cas, le modèle testeur-Doer peut avoir une surcharge de performance inacceptable. Dans ce cas, le
modèle « try-parse » doit être pris en compte (pour plus d’informations, consultez exceptions et performances ).
✔ PRENDRE en compte les implications en matière de performances liées à la levée d’exceptions. Les taux de rejet

supérieurs à 100 par seconde sont susceptibles d’avoir un impact notable sur les performances de la plupart des
applications.
✔ Documentez toutes les exceptions levées par les membres pouvant être appelés publiquement en raison d’une

violation du contrat de membre (plutôt qu’une défaillance du système) et traitez-les dans le cadre de votre contrat.
Les exceptions qui font partie du contrat ne doivent pas passer d’une version à l’autre (par exemple, le type
d’exception ne doit pas changer et les nouvelles exceptions ne doivent pas être ajoutées).
❌N’ont pas de membres publics qui peuvent lever ou non en fonction d’une option.
❌N’ont pas de membres publics qui retournent des exceptions comme valeur de retour ou out paramètre.
Le retour d’exceptions à partir d’API publiques au lieu de les lever est à l’encontre de nombreux avantages du
rapport d’erreurs basé sur les exceptions.
️ envisagez d’utiliser des méthodes de générateur d’exceptions.

Il est courant de lever la même exception à partir de différents emplacements. Pour éviter l’augmentation du code,
utilisez des méthodes d’assistance qui créent des exceptions et initialisent leurs propriétés.
En outre, les membres qui lèvent des exceptions ne sont pas Inline. Le déplacement de l’instruction throw dans le
générateur peut permettre au membre d’être Inline.
❌NE levez pas d’exceptions à partir de blocs de filtre d’exception.
Lorsqu’un filtre d’exception lève une exception, l’exception est interceptée par le CLR et le filtre retourne la valeur
false. Ce comportement ne peut pas être distingué du filtre en cours d’exécution et retourne explicitement false et
est donc très difficile à déboguer.
❌Évitez de lever explicitement des exceptions à partir de blocs finally. Les exceptions levées implicitement qui
résultent de l’appel de méthodes qui lèvent sont acceptables.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions de conception pour les exceptions
Utilisation de types d'exceptions standard
18/07/2020 • 4 minutes to read

Cette section décrit les exceptions standard fournies par l’infrastructure et les détails de leur utilisation. La liste n’est
pas exhaustive. Pour plus d’informations sur l’utilisation d’autres types d’exception d’infrastructure, consultez la
documentation de référence .NET Framework.

Exception et SystemException
❌NE levez pas System.Exception ou System.SystemException .
❌N’interceptez pas System.Exception ou System.SystemException dans le code d’infrastructure, sauf si vous
envisagez de lever à nouveau.
❌Évitez d’intercepter System.Exception ou System.SystemException , sauf dans les gestionnaires d’exceptions de
niveau supérieur.

ApplicationException
❌NE levez pas ou ne dérivez pas de ApplicationException .

InvalidOperationException
️ Levez une exception InvalidOperationException si l’objet est dans un État inapproprié.

ArgumentException, ArgumentNullException et
ArgumentOutOfRangeException
✔ ne levez pas ArgumentException d’exception ou un de ses sous-types si des arguments incorrects sont passés à

un membre. Préférer le type d’exception le plus dérivé, le cas échéant.
️ définir la propriété lors de la levée de l'
✔ ParamName une des sous-classes de ArgumentException .
Cette propriété représente le nom du paramètre qui a provoqué la levée de l’exception. Notez que la propriété peut
être définie à l’aide de l’une des surcharges de constructeur.
️ Utilisez
✔ value pour le nom du paramètre de valeur implicite des accesseurs set de propriété.

NullReferenceException, IndexOutOfRangeException et
AccessViolationException
❌N’autorisez pas les API pouvant être appelées publiquement à lever explicitement ou implicitement
NullReferenceException , AccessViolationException ou IndexOutOfRangeException . Ces exceptions sont réservées
et levées par le moteur d’exécution et, dans la plupart des cas, indiquent un bogue.
Procédez à la vérification des arguments pour éviter de lever ces exceptions. La levée de ces exceptions expose les
détails d’implémentation de votre méthode qui peuvent changer au fil du temps.

StackOverflowException
❌NE levez pas explicitement StackOverflowException . L’exception doit être levée explicitement uniquement par le
CLR.
❌N’interceptez pas StackOverflowException .
Il est presque impossible d’écrire du code managé qui reste cohérent en présence de dépassements de pile
arbitraires. Les parties non managées du CLR restent cohérentes en utilisant des sondes pour déplacer les
débordements de pile vers des emplacements bien définis plutôt que en sauvegardant des débordements de pile
arbitraires.

OutOfMemoryException
❌NE levez pas explicitement OutOfMemoryException . Cette exception doit être levée uniquement par
l’infrastructure CLR.

COMException, SEHException et ExecutionEngineException


❌NE levez pas explicitement COMException , ExecutionEngineException et SEHException . Ces exceptions doivent
être levées uniquement par l’infrastructure CLR.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions de conception pour les exceptions
Exceptions et performances
18/07/2020 • 5 minutes to read

L’une des préoccupations courantes liées aux exceptions est que, si des exceptions sont utilisées pour du code qui
échoue régulièrement, les performances de l’implémentation seront inacceptables. Il s’agit d’un problème valide.
Lorsqu’un membre lève une exception, ses performances peuvent être plus lentes. Toutefois, il est possible
d’obtenir de bonnes performances tout en respectant strictement les règles d’exception qui interdisent l’utilisation
de codes d’erreur. Deux modèles décrits dans cette section montrent comment procéder.
❌N’utilisez pas de codes d’erreur en raison de problèmes liés au fait que les exceptions peuvent affecter les
performances de manière négative.
Pour améliorer les performances, il est possible d’utiliser le modèle testeur-Doer ou le modèle try-Parse, décrit
dans les deux sections suivantes.

Modèle testeur-Doer
Parfois, les performances d’un membre levant une exception peuvent être améliorées en divisant le membre en
deux. Examinons la Add méthode de l' ICollection<T> interface.

ICollection<int> numbers = ...


numbers.Add(1);

La méthode Add lève une exception si la collection est en lecture seule. Il peut s’agir d’un problème de
performances dans les scénarios où l’appel de méthode est supposé échouer souvent. L’une des façons d’atténuer
le problème consiste à tester si la collection est accessible en écriture avant d’essayer d’ajouter une valeur.

ICollection<int> numbers = ...


...
if (!numbers.IsReadOnly)
{
numbers.Add(1);
}

Le membre utilisé pour tester une condition, qui, dans notre exemple, est la propriété IsReadOnly , est appelé
testeur. Le membre utilisé pour effectuer une opération de levée potentielle, la Add méthode dans notre exemple,
est appelé Doer.
✔ EXAMINEz le modèle testeur-Doer pour les membres qui peuvent lever des exceptions dans les scénarios

courants afin d’éviter les problèmes de performances liés aux exceptions.

Modèle try-parse
Pour les API très performantes, il est préférable d’utiliser un modèle encore plus rapide que le modèle testeur-Doer
décrit dans la section précédente. Le modèle appelle pour ajuster le nom de membre afin qu’un cas de test bien
défini fasse partie de la sémantique de membre. Par exemple, DateTime définit une Parse méthode qui lève une
exception en cas d’échec de l’analyse d’une chaîne. Il définit également une TryParse méthode correspondante qui
tente d’analyser, mais retourne la valeur false si l’analyse échoue et retourne le résultat d’une analyse réussie à
l’aide d’un out paramètre.
public struct DateTime
{
public static DateTime Parse(string dateTime)
{
...
}
public static bool TryParse(string dateTime, out DateTime result)
{
...
}
}

Lors de l’utilisation de ce modèle, il est important de définir la fonctionnalité try en termes stricts. Si le membre
échoue pour une raison autre que la tentative bien définie, le membre doit toujours lever une exception
correspondante.
✔ EXAMINEz le modèle try-parse pour les membres qui peuvent lever des exceptions dans les scénarios courants

afin d’éviter les problèmes de performances liés aux exceptions.
️ Utilisez le préfixe « try » et le type de retour booléen pour les méthodes qui implémentent ce modèle.

️ fournissez un membre levant des exceptions pour chaque membre à l’aide du modèle try-parse.

Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions de conception pour les exceptions
Indications relatives à l'utilisation
18/07/2020 • 2 minutes to read

Cette section contient des instructions pour l’utilisation des types courants dans les API accessibles publiquement.
Il traite de l’utilisation directe des types d’infrastructure intégrés (par exemple, les attributs de sérialisation) et de la
surcharge des opérateurs courants.
L' System.IDisposable interface n’est pas traitée dans cette section, mais elle est décrite dans la section modèle de
suppression .

NOTE
Pour obtenir des instructions et des informations supplémentaires sur d’autres types de .NET Framework intégrés communs,
consultez les rubriques de référence pour les éléments suivants : System.DateTime , System.DateTimeOffset ,
System.ICloneable , System.IComparable<T> , System.IEquatable<T> , System.Nullable<T> , System.Object , System.Uri .

Dans cette section


Tableaux
Attributs
Regroupements
Sérialisation
Utilisation de System. Xml
Opérateurs d’égalité
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft
Windows.

Voir aussi
Directives de conception d’infrastructure
Tableaux
18/07/2020 • 2 minutes to read

✔ préférez utiliser des collections sur des tableaux dans des API publiques. La section Collections fournit des

détails sur la manière de choisir entre des collections et des tableaux.
❌N’utilisez pas de champs de tableau en lecture seule. Le champ lui-même est en lecture seule et ne peut pas être
modifié, mais les éléments du tableau peuvent être modifiés.
️ envisagez d’utiliser des tableaux en escalier à la place de tableaux multidimensionnels.

Un tableau en escalier est un tableau avec des éléments qui sont également des tableaux. Les tableaux qui
composent les éléments peuvent être de différentes tailles, ce qui permet d’obtenir un espace moins gaspillé pour
certains jeux de données (par exemple, la matrice éparse) par rapport aux tableaux multidimensionnels. En outre, le
CLR optimise les opérations d’index sur les tableaux en escalier, de sorte qu’ils peuvent présenter de meilleures
performances d’exécution dans certains scénarios.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Array
Directives de conception d’infrastructure
Instructions d’utilisation
Attributs
18/07/2020 • 4 minutes to read

System.Attributeest une classe de base utilisée pour définir des attributs personnalisés.
Les attributs sont des annotations qui peuvent être ajoutées aux éléments de programmation tels que les
assemblys, les types, les membres et les paramètres. Elles sont stockées dans les métadonnées de l’assembly et
sont accessibles au moment de l’exécution à l’aide des API de réflexion. Par exemple, le Framework définit
ObsoleteAttribute , qui peut être appliqué à un type ou à un membre pour indiquer que le type ou le membre a été
déconseillé.
Les attributs peuvent avoir une ou plusieurs propriétés qui contiennent des données supplémentaires relatives à
l’attribut. Par exemple, ObsoleteAttribute peut contenir des informations supplémentaires sur la version dans
laquelle un type ou un membre a été déconseillé et la description des nouvelles API qui remplacent l’API obsolète.
Certaines propriétés d’un attribut doivent être spécifiées quand l’attribut est appliqué. Ils sont appelés propriétés
requises ou arguments requis, car ils sont représentés en tant que paramètres de constructeur positionnel. Par
exemple, la ConditionString propriété de ConditionalAttribute est une propriété obligatoire.
Les propriétés qui n’ont pas nécessairement besoin d’être spécifiées lors de l’application de l’attribut sont appelées
propriétés facultatives (ou arguments facultatifs). Elles sont représentées par des propriétés définissables. Les
compilateurs fournissent une syntaxe spéciale pour définir ces propriétés lorsqu’un attribut est appliqué. Par
exemple, la AttributeUsageAttribute.Inherited propriété représente un argument facultatif.
️ Nommez les classes d’attributs personnalisés avec le suffixe « Attribute ».

️ appliquez à des AttributeUsageAttribute attributs personnalisés.

️ fournissez des propriétés définissables pour les arguments facultatifs.

️ fournissez des propriétés d’extraction uniquement pour les arguments requis.

✔ fournissez des paramètres de constructeur pour initialiser les propriétés correspondant aux arguments requis.

Chaque paramètre doit avoir le même nom (mais avec une casse différente) que la propriété correspondante.
❌Évitez de fournir des paramètres de constructeur pour initialiser les propriétés correspondant aux arguments
facultatifs.
En d’autres termes, les propriétés ne peuvent pas être définies avec un constructeur et un accesseur Set. Cette
recommandation rend très explicite les arguments facultatifs et obligatoires, et évite d’avoir deux façons de faire la
même chose.
❌Évitez de surcharger les constructeurs d’attributs personnalisés.
Le fait de n’avoir qu’un seul constructeur communique clairement à l’utilisateur les arguments requis et ceux qui
sont facultatifs.
️ scellent les classes d’attributs personnalisés, si possible. La recherche de l’attribut est ainsi plus rapide.

Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.
Voir aussi
Directives de conception d’infrastructure
Instructions d’utilisation
Instructions relatives aux collections
18/07/2020 • 15 minutes to read

Tout type conçu spécifiquement pour manipuler un groupe d’objets ayant des caractéristiques communes peut
être considéré comme une collection. Il est presque toujours approprié pour que ces types implémentent
IEnumerable ou IEnumerable<T> . par conséquent, dans cette section, nous prenons uniquement en compte les
types qui implémentent l’une de ces interfaces, ou les deux, pour être des collections.
❌N’utilisez pas de collections faiblement typées dans les API publiques.
Le type de toutes les valeurs de retour et paramètres représentant les éléments de la collection doit être le type
d’élément exact, et non l’un de ses types de base (cela s’applique uniquement aux membres publics de la
collection).
❌N’utilisez pas ArrayList ou List<T> dans les API publiques.
Ces types sont des structures de données conçues pour être utilisées dans une implémentation interne, et non
dans des API publiques. List<T> est optimisé pour les performances et la puissance au détriment du nettoyage
des API et de la flexibilité. Par exemple, si vous retournez List<T> , vous ne serez jamais en mesure de recevoir
des notifications lorsque le code client modifie la collection. En outre, List<T> expose de nombreux membres, tels
que BinarySearch , qui ne sont pas utiles ou applicables dans de nombreux scénarios. Les deux sections suivantes
décrivent les types (abstractions) conçus spécifiquement pour une utilisation dans les API publiques.
❌N’utilisez pas Hashtable ou Dictionary<TKey,TValue> dans les API publiques.
Ces types sont des structures de données conçues pour être utilisées dans une implémentation interne. Les API
publiques doivent utiliser IDictionary , IDictionary <TKey, TValue> ou un type personnalisé qui implémente l’une
des interfaces ou les deux.
❌N’utilisez pas IEnumerator<T> , IEnumerator ou tout autre type qui implémente l’une de ces interfaces, sauf en
tant que type de retour d’une GetEnumerator méthode.
Les types qui retournent des énumérateurs à partir de méthodes autres que GetEnumerator ne peuvent pas être
utilisés avec l' foreach instruction.
❌N’implémentez pas IEnumerator<T> et IEnumerable<T> sur le même type. Il en va de même pour les interfaces
non génériques IEnumerator et IEnumerable .

Paramètres de collection
✔ Utilisez le type le moins spécialisé possible comme type de paramètre. La plupart des membres qui prennent

des collections comme paramètres utilisent l' IEnumerable<T> interface.
❌Évitez ICollection<T> d’utiliser ou ICollection comme paramètre simplement pour accéder à la Count
propriété.
Au lieu de cela, envisagez d’utiliser IEnumerable<T> ou IEnumerable et de vérifier dynamiquement si l’objet
implémente ICollection<T> ou ICollection .

Propriétés de collection et valeurs de retour


❌NE fournissez pas de propriétés de collection définissables.
Les utilisateurs peuvent remplacer le contenu de la collection en effaçant d’abord la collection, puis en ajoutant le
nouveau contenu. Si le remplacement de l’ensemble de la collection est un scénario courant, envisagez de fournir
la AddRange méthode sur la collection.
✔ Utilisez Collection<T> ou une sous-classe de
️ Collection<T> pour les propriétés ou les valeurs de retour
représentant des collections en lecture/écriture.
Si Collection<T> ne répond pas à certaines exigences (par exemple, si la collection ne doit pas implémenter IList ),
utilisez une collection personnalisée en implémentant IEnumerable<T> , ICollection<T> ou IList<T> .
️ Utilisez ReadOnlyCollection<T> , une sous-classe de ReadOnlyCollection<T> ou, dans de rares cas,

IEnumerable<T> des propriétés ou des valeurs de retour représentant des collections en lecture seule.

En général, préférez ReadOnlyCollection<T> . S’il ne répond pas à certaines exigences (par exemple, si la collection
ne doit pas implémenter IList ), utilisez une collection personnalisée en implémentant IEnumerable<T> ,
ICollection<T> ou IList<T> . Si vous implémentez une collection en lecture seule personnalisée, implémentez
ICollection<T>.IsReadOnly pour retourner true .

Dans les cas où vous êtes certain que le seul scénario que vous souhaiterez peut-être prendre en charge est
l’itération avant uniquement, vous pouvez simplement utiliser IEnumerable<T> .
✔ envisagez d’utiliser des sous-classes de collections de base génériques au lieu d’utiliser directement les

collections.
Cela permet d’obtenir un meilleur nom et d’ajouter des membres d’assistance qui ne sont pas présents sur les
types de collections de base. Cela s’applique en particulier aux API de haut niveau.
✔ envisagez de retourner une sous-classe de
️ Collection<T> ou ReadOnlyCollection<T> à partir de méthodes et
de propriétés très couramment utilisées.
Cela vous permettra d’ajouter des méthodes d’assistance ou de modifier l’implémentation de la collection à
l’avenir.
✔ envisagez d’utiliser une collection à clé si les éléments stockés dans la collection ont des clés uniques (noms,

ID, etc.). Les collections à clé sont des collections qui peuvent être indexées à la fois par un entier et une clé et qui
sont généralement implémentées à partir de KeyedCollection<TKey,TItem> .
Les collections à clé sont généralement plus volumineuses et ne doivent pas être utilisées si la surcharge de
mémoire compense les avantages de l’utilisation des clés.
❌NE retournez pas de valeurs NULL à partir de propriétés de collection ou de méthodes qui retournent des
collections. Retourne une collection vide ou un tableau vide à la place.
La règle générale est que les collections ou tableaux null et vides (0 élément) doivent être traités de la même façon.
Captures instantanées et collections en direct
Les collections représentant un État à un moment donné sont appelées collections d’instantanés. Par exemple, une
collection contenant des lignes retournées par une requête de base de données serait un instantané. Les
collections qui représentent toujours l’état actuel sont appelées collections dynamiques. Par exemple, une
collection d' ComboBox éléments est une collection en direct.
❌NE retournez pas de collections d’instantanés à partir des propriétés. Les propriétés doivent retourner des
collections dynamiques.
Les accesseurs get de propriété doivent être des opérations très légères. Le retour d’un instantané requiert la
création d’une copie d’une collection interne dans une opération O (n).
✔ Utilisez soit une collection d’instantanés, soit un IEnumerable<T> en direct (ou son sous-type) pour représenter

les collections volatiles (c’est-à-dire, qui peuvent changer sans modification explicite de la collection).
En général, toutes les collections représentant une ressource partagée (par exemple, les fichiers dans un
répertoire) sont volatiles. Ces collections sont très difficiles ou impossibles à implémenter en tant que collections
dynamiques, sauf si l’implémentation est simplement un énumérateur avant uniquement.

Choix entre les tableaux et les collections


️ préférez les collections sur les tableaux.

Les collections permettent de mieux contrôler le contenu, peuvent évoluer au fil du temps et sont plus utilisables.
En outre, il est déconseillé d’utiliser des tableaux pour les scénarios en lecture seule, car le coût du clonage du
tableau est prohibitif. Les études de convivialité ont montré que certains développeurs se sentent plus à l’aise avec
les API basées sur les collections.
Toutefois, si vous développez des API de bas niveau, il peut être préférable d’utiliser des tableaux pour les
scénarios de lecture-écriture. Les tableaux ont un faible encombrement mémoire, ce qui permet de réduire la plage
de travail, et l’accès aux éléments d’un tableau est plus rapide, car il est optimisé par le Runtime.
✔ envisagez d’utiliser des tableaux dans les API de bas niveau pour réduire la consommation de mémoire et

optimiser les performances.
️ Utilisez des tableaux d’octets au lieu de collections d’octets.

❌N’utilisez pas de tableaux pour les propriétés si la propriété doit retourner un nouveau tableau (par exemple,
une copie d’un tableau interne) chaque fois que l’accesseur Get de propriété est appelé.

Implémentation de regroupements personnalisés


✔ ENVISAGER d’hériter de Collection<T> ,
️ ReadOnlyCollection<T> ou lors de la KeyedCollection<TKey,TItem>
conception de nouvelles collections.
️ implémenter IEnumerable<T> lors de la conception de nouvelles collections. Envisagez d’implémenter

ICollection<T> ou même IList<T> là où cela est pertinent.

Lors de l’implémentation de cette collection personnalisée, suivez le modèle d’API établi par et le plus
Collection<T> ReadOnlyCollection<T> fidèlement possible. Autrement dit, implémentez les mêmes membres
explicitement, nommez-les comme ces deux collections, et ainsi de suite.
✔ envisagez d’implémenter des interfaces de collection non génériques (
️ IList et ICollection ) si la collection
est souvent passée à des API acceptant ces interfaces comme entrée.
❌Évitez d’implémenter des interfaces de collection sur des types avec des API complexes sans rapport avec le
concept d’une collection.
❌N’héritez pas de collections de base non génériques telles que CollectionBase . Utilisez Collection<T> ,
ReadOnlyCollection<T> et à la KeyedCollection<TKey,TItem> place.

Nommer des collections personnalisées


Les collections (types qui implémentent IEnumerable ) sont créées principalement pour deux raisons : (1) pour
créer une nouvelle structure de données avec des opérations spécifiques à la structure et souvent différentes
caractéristiques de performances que les structures de données existantes (par exemple,,, List<T> LinkedList<T>
Stack<T> ) et (2) pour créer une collection spécialisée pour conserver un ensemble spécifique d’éléments (par
exemple, StringCollection ). Les structures de données sont le plus souvent utilisées dans l’implémentation interne
des applications et des bibliothèques. Les collections spécialisées sont principalement exposées dans les API (en
tant que types de propriété et de paramètre).
️ Utilisez le suffixe « dictionary » dans les noms d’abstractions implémentant
✔ IDictionary ou
IDictionary<TKey,TValue> .
✔ Utilisez le suffixe « collection » dans les noms des types
️ IEnumerable qui implémentent (ou l’un de ses
descendants) et qui représentent une liste d’éléments.
️ Utilisez le nom de structure de données approprié pour les structures de données personnalisées.

❌Évitez d’utiliser des suffixes qui impliquent une implémentation particulière, telle que « LinkedList » ou
« Hashtable », dans les noms des abstractions de collection.
✔ envisagez de préfixer les noms de collections avec le nom du type d’élément. Par exemple, une collection qui

stocke des éléments de type Address (implémentation IEnumerable<Address> ) doit être nommée
AddressCollection . Si le type d’élément est une interface, le préfixe « I » du type d’élément peut être omis. Par
conséquent, une collection d' IDisposable éléments peut être appelée DisposableCollection .
✔ envisagez d’utiliser le préfixe « ReadOnly » dans les noms de collections en lecture seule si une collection

accessible en écriture correspondante peut être ajoutée ou existe déjà dans le Framework.
Par exemple, une collection de chaînes en lecture seule doit être appelée ReadOnlyStringCollection .
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions d’utilisation
Sérialisation
18/07/2020 • 12 minutes to read

La sérialisation est le processus de conversion d’un objet en un format qui peut être facilement rendu persistant ou
transporté. Par exemple, vous pouvez sérialiser un objet, le transporter sur Internet à l’aide de HTTP et le
désérialiser sur l’ordinateur de destination.
Le .NET Framework offre trois technologies de sérialisation principales, optimisées pour différents scénarios de
sérialisation. Le tableau suivant répertorie ces technologies et les principaux types Framework qui leur sont
associés.

N O M DE T EC H N O LO GIE T Y P ES P RIN C IPA UX SC ÉN A RIO S

Sérialisation du contrat de DataContractAttribute Persistance générale


données DataMemberAttribute Services Web
DataContractSerializer JSON
NetDataContractSerializer
DataContractJsonSerializer
ISerializable

Sérialisation XML XmlSerializer Format XML avec contrôle total sur la


forme du code XML

Sérialisation du Runtime (binaire SerializableAttribute .NET Remoting


et SOAP) ISerializable
BinaryFormatter
SoapFormatter

️ Pensez à la sérialisation quand vous concevez de nouveaux types.


Sélection de la technologie de sérialisation appropriée à prendre en


charge
✔ envisagez de prendre en charge la sérialisation de contrat de données si des instances de votre type doivent

être persistantes ou utilisées dans les services Web.
✔ envisagez de prendre en charge la sérialisation XML au lieu de ou en plus de la sérialisation de contrat de

données si vous avez besoin de davantage de contrôle sur le format XML qui est généré lorsque le type est
sérialisé.
Cela peut être nécessaire dans certains scénarios d’interopérabilité dans lesquels vous devez utiliser une
construction XML qui n’est pas prise en charge par la sérialisation de contrat de données, par exemple pour
produire des attributs XML.
✔ envisagez de prendre en charge la sérialisation du Runtime si des instances de votre type doivent voyager

entre les limites .NET Remoting.
❌Évitez de prendre en charge la sérialisation du runtime ou la sérialisation XML uniquement pour des raisons
générales de persistance. Préférez la sérialisation du contrat de données à la place.

Prise en charge de la sérialisation du contrat de données


Les types peuvent prendre en charge la sérialisation de contrat de données en appliquant au DataContractAttribute
type et DataMemberAttribute aux membres (champs et propriétés) du type.
✔ envisagez de marquer des membres de données de votre type public si le type peut être utilisé en confiance

partielle.
En mode de confiance totale, les sérialiseurs de contrat de données peuvent sérialiser et désérialiser des membres
et des types non publics, mais seuls les membres publics peuvent être sérialisés et désérialisés en confiance
partielle.
✔ Implémentez un accesseur get et une méthode setter sur toutes les propriétés qui possèdent

DataMemberAttribute . Les sérialiseurs de contrat de données requièrent que l’accesseur get et l’accesseur Set pour
le type soient considérés comme sérialisables. (Dans .NET Framework 3,5 SP1, certaines propriétés de collection
peuvent être accessibles en tout lieu.) Si le type n’est pas utilisé en confiance partielle, l’un des accesseurs de
propriété, ou les deux, peuvent être non publics.
️ envisagez d’utiliser les rappels de sérialisation pour l’initialisation des instances désérialisées.

Les constructeurs ne sont pas appelés lorsque les objets sont désérialisés. (Il existe des exceptions à la règle. Les
constructeurs de collections marqués avec CollectionDataContractAttribute sont appelés pendant la
désérialisation.) Par conséquent, toute logique qui s’exécute pendant une construction normale doit être
implémentée comme l’un des rappels de sérialisation.
OnDeserializedAttribute est l’attribut de rappel le plus couramment utilisé. Les autres attributs de la famille sont
OnDeserializingAttribute, OnSerializingAttribute et OnSerializedAttribute. Ils peuvent être utilisés pour marquer les
rappels exécutés avant la désérialisation, avant la sérialisation et enfin, après la sérialisation, respectivement.
✔ envisagez KnownTypeAttribute d’utiliser pour indiquer les types concrets qui doivent être utilisés lors de la

désérialisation d’un graphique d’objets complexe.
✔ prendre en compte la compatibilité descendante et ascendante lors de la création ou de la modification de

types sérialisables.
Gardez à l'esprit que les flux sérialisés de futures versions de votre type peuvent être désérialisés dans la version
actuelle du type, et inversement.
Assurez-vous que vous comprenez que les membres de données, même privés et internes, ne peuvent pas
modifier leurs noms, types ou même leur ordre dans les versions ultérieures du type, sauf si une attention
particulière est prise pour préserver le contrat à l’aide de paramètres explicites pour les attributs de contrat de
données.
Testez la compatibilité de la sérialisation lorsque vous apportez des modifications aux types sérialisables. Essayez
de désérialiser la nouvelle version dans une ancienne version, et inversement.
✔ ENVISAGER d’implémenter IExtensibleDataObject pour permettre l’aller-retour entre les différentes versions du

type.
L'interface permet au sérialiseur de garantir qu'aucune donnée n'est perdue lors de l'aller-retour. La
IExtensibleDataObject.ExtensionData propriété est utilisée pour stocker toutes les données de la version future du
type qui est inconnu de la version actuelle et ne peut donc pas la stocker dans ses membres de données. Lorsque la
version actuelle est par la suite sérialisée et désérialisée dans une version future, les données supplémentaires sont
disponibles dans le flux sérialisé.

Prise en charge de la sérialisation XML


La sérialisation de contrat de données est la technologie de sérialisation principale (par défaut) dans le .NET
Framework, mais il existe des scénarios de sérialisation que la sérialisation de contrat de données ne prend pas en
charge. Par exemple, vous n'avez pas le contrôle total sur la forme du XML généré ou consommé par le sérialiseur.
Si ce contrôle précis est requis, la sérialisation XML doit être utilisée, et vous devez concevoir vos types pour
prendre en charge cette technologie de sérialisation.
❌Évitez de concevoir vos types spécifiquement pour la sérialisation XML, sauf si vous avez une raison très
importante de contrôler la forme du code XML produit. Cette technologie de sérialisation a été remplacée par la
sérialisation du contrat de données présentée dans la section précédente.
✔ envisagez d’implémenter l' IXmlSerializable interface si vous souhaitez encore plus de contrôle sur la forme du

code XML sérialisé que ce qui est proposé en appliquant les attributs de SÉRIALISATION XML. Deux méthodes de
l’interface, ReadXml et WriteXml , vous permettent de contrôler entièrement le flux XML sérialisé. Vous pouvez
également contrôler le schéma XML qui est généré pour le type en appliquant XmlSchemaProviderAttribute .

Prise en charge de la sérialisation du runtime


La sérialisation du runtime est une technologie utilisée par .NET Remoting. Si vous pensez que vos types seront
transportés à l’aide de .NET Remoting, vous devez vous assurer qu’ils prennent en charge la sérialisation du
Runtime.
La prise en charge de base de la sérialisation du runtime peut être fournie en appliquant SerializableAttribute , et
des scénarios plus avancés impliquent l’implémentation d’un modèle sérialisable simple du Runtime (implémenter
ISerializable et fournir un constructeur de sérialisation).
✔ envisagez de prendre en charge la sérialisation du Runtime si vos types sont utilisés avec .NET Remoting. Par

exemple, l' System.AddIn espace de noms utilise .NET Remoting, de sorte que tous les types échangés entre les
System.AddIn compléments doivent prendre en charge la sérialisation du Runtime.

✔ envisagez d’implémenter le modèle sérialisable du Runtime si vous souhaitez un contrôle total sur le processus

de sérialisation. Par exemple, si vous souhaitez transformer des données à mesure qu'elles sont sérialisées ou
désérialisées.
Le modèle est très simple. Il suffit d'implémenter l'interface ISerializable et de fournir un constructeur spécial qui
est utilisé lorsque l'objet est désérialisé.
✔ faites en sorte que le constructeur de sérialisation soit protégé et fournissez deux paramètres typés et nommés

exactement comme indiqué dans l’exemple ici.

[Serializable]
public class Person : ISerializable
{
protected Person(SerializationInfo info, StreamingContext context)
{
// ...
}
}

️ implémentent les ISerializable membres explicitement.



✔ Appliquez une demande de liaison à l' ISerializable.GetObjectData implémentation. Cela garantit que seuls les

cœurs entièrement fiables et le sérialiseur de Runtime ont accès au membre.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions d’utilisation
System.Xml, utilisation
18/07/2020 • 2 minutes to read

Cette section parle de l’utilisation de plusieurs types résidant dans des System.Xml espaces de noms qui peuvent
être utilisés pour représenter des données XML.
❌N’utilisez pas XmlNode ou XmlDocument pour représenter des données XML. Privilégiez l’utilisation d’instances
de IXPathNavigable ,, XmlReader XmlWriter ou de sous-types de à la XNode place. XmlNode et ne XmlDocument sont
pas conçus pour être exposés dans des API publiques.
✔ Utilisez XmlReader IXPathNavigable les sous-types, ou
️ XNode en tant qu’entrée ou sortie des membres qui
acceptent ou retournent du code XML.
Utilisez ces abstractions au lieu de XmlDocument , XmlNode ou XPathDocument , car cela dissocie les méthodes des
implémentations spécifiques d’un document XML en mémoire et leur permet de fonctionner avec des sources de
données XML virtuelles qui exposent XNode , XmlReader ou XPathNavigator .
❌Ne procédez pas XmlDocument à la sous-classe si vous souhaitez créer un type représentant une vue XML d’un
modèle objet ou d’une source de données sous-jacent.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions d’utilisation
Opérateurs d'égalité
18/07/2020 • 2 minutes to read

Cette section décrit la surcharge des opérateurs d’égalité et fait référence à operator== et à des operator!=
opérateurs d’égalité.
❌NE surchargez pas l’un des opérateurs d’égalité et non l’autre.
✔ Assurez-vous que Object.Equals et les opérateurs d’égalité ont exactement la même sémantique et les mêmes

caractéristiques de performances similaires.
Cela signifie souvent que Object.Equals doit être substitué lorsque les opérateurs d’égalité sont surchargés.
❌Évitez de lever des exceptions à partir d’opérateurs d’égalité.
Par exemple, retourne false si l’un des arguments a la valeur null au lieu de lever NullReferenceException .

Opérateurs d’égalité sur les types valeur


️ surchargent les opérateurs d’égalité sur les types valeur, si l’égalité est significative.

Dans la plupart des langages de programmation, il n’y a pas d’implémentation par défaut de operator== pour les
types valeur.

Opérateurs d’égalité sur les types référence


❌Évitez de surcharger les opérateurs d’égalité sur les types référence mutables.
De nombreux langages ont des opérateurs d’égalité intégrés pour les types référence. Les opérateurs intégrés
implémentent généralement l’égalité des références, et de nombreux développeurs sont surpris lorsque le
comportement par défaut est remplacé par l’égalité de la valeur.
Ce problème est atténué pour les types référence immuables, car l’immuabilité rend beaucoup plus difficile la
différence entre l’égalité des références et l’égalité des valeurs.
❌Évitez de surcharger les opérateurs d’égalité sur les types référence si l’implémentation est beaucoup plus lente
que celle de l’égalité de référence.
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Instructions d’utilisation
Modèles de design courants
18/07/2020 • 2 minutes to read

Il existe de nombreux ouvrages sur les modèles de logiciels, les langages de modèle et les anti-modèles qui traitent
le sujet très large des modèles. Ainsi, ce chapitre fournit des instructions et des discussions relatives à un ensemble
très limité de modèles utilisés fréquemment dans la conception des API .NET Framework.

Dans cette section


Propriétés de dépendance
Dispose, modèle
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Propriétés de dépendance
18/07/2020 • 6 minutes to read

Une propriété de dépendance (DP) est une propriété normale qui stocke sa valeur dans un magasin de propriétés
au lieu de la stocker dans une variable de type (champ), par exemple.
Une propriété de dépendance attachée est un genre de propriété de dépendance modélisée en tant que méthodes
d’extraction et de définition statiques représentant des « propriétés » qui décrivent les relations entre les objets et
leurs conteneurs (par exemple, la position d’un Button objet sur un Panel conteneur).
✔ fournissez les propriétés de dépendance, si vous avez besoin des propriétés pour prendre en charge des

fonctionnalités WPF telles que le style, les déclencheurs, la liaison de données, les animations, les ressources
dynamiques et l’héritage.

Conception des propriétés de dépendance


✔ héritent de DependencyObject , ou de l’un de ses sous-types, lors de l’implémentation des propriétés de

dépendance. Le type fournit une implémentation très efficace d’une banque de propriétés et prend
automatiquement en charge la liaison de données WPF.
✔ fournissez une propriété CLR normale et un champ en lecture seule statique public qui stocke une instance de

System.Windows.DependencyProperty pour chaque propriété de dépendance.
✔ Implémentez des propriétés de dépendance en appelant des méthodes d’instance DependencyObject.GetValue

et DependencyObject.SetValue .
✔ Nommez le champ statique de la propriété de dépendance en suffixant le nom de la propriété avec

« Property ».
❌NE définissez pas explicitement les valeurs par défaut des propriétés de dépendance dans le code ; Définissez-
les plutôt dans les métadonnées.
Si vous définissez une propriété par défaut explicitement, vous pouvez empêcher la définition de cette propriété
par certains moyens implicites, tels qu’un style.
❌NE placez pas de code dans les accesseurs de propriété autres que le code standard pour accéder au champ
statique.
Ce code ne s’exécute pas si la propriété est définie par des moyens implicites, tels qu’un style, car le style utilise
directement le champ statique.
❌N’utilisez pas de propriétés de dépendance pour stocker des données sécurisées. Même les propriétés de
dépendance privées sont accessibles publiquement.

Conception de propriété de dépendance attachée


Les propriétés de dépendance décrites dans la section précédente représentent des propriétés intrinsèques du type
déclarant. par exemple, la Text propriété est une propriété de TextButton , qui la déclare. La propriété de
dépendance attachée est un type spécial de propriété de dépendance.
La propriété est un exemple classique d’une propriété jointe Grid.Column . La propriété représente la position de la
colonne du bouton (et non de la grille), mais elle ne s’applique que si le bouton est contenu dans une grille et est
donc « attaché » aux boutons par grilles.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>

<Button Grid.Column="0">Click</Button>
<Button Grid.Column="1">Clack</Button>
</Grid>

La définition d’une propriété jointe ressemble principalement à celle d’une propriété de dépendance normale, sauf
que les accesseurs sont représentés par des méthodes d’extraction et de définition statiques :

public class Grid {

public static int GetColumn(DependencyObject obj) {


return (int)obj.GetValue(ColumnProperty);
}

public static void SetColumn(DependencyObject obj, int value) {


obj.SetValue(ColumnProperty,value);
}

public static readonly DependencyProperty ColumnProperty =


DependencyProperty.RegisterAttached(
"Column",
typeof(int),
typeof(Grid)
);
}

Validation de la propriété de dépendance


Les propriétés implémentent souvent ce que l’on appelle la validation. La logique de validation s’exécute lorsqu’une
tentative est faite pour modifier la valeur d’une propriété.
Malheureusement, les accesseurs de propriété de dépendance ne peuvent pas contenir de code de validation
arbitraire. Au lieu de cela, la logique de validation de la propriété de dépendance doit être spécifiée lors de
l’inscription de la propriété.
❌NE placez pas la logique de validation de la propriété de dépendance dans les accesseurs de la propriété. Au lieu
de cela, passer un rappel de validation à la DependencyProperty.Register méthode.

Notifications de modification de propriété de dépendance


❌N’implémentez pas la logique de notification de modification dans les accesseurs de propriété de dépendance.
Les propriétés de dépendance possèdent une fonctionnalité de notifications de modifications intégrée qui doit être
utilisée en fournissant un rappel de notification de modification à PropertyMetadata .

Contrainte de valeur de propriété de dépendance


La contrainte de propriété a lieu lorsque la valeur donnée à un accesseur Set de propriété est modifiée par
l’accesseur Set avant la modification réelle de la Banque de propriétés.
❌N’implémentez pas la logique de contrainte dans les accesseurs de propriété de dépendance.
Les propriétés de dépendance ont une fonctionnalité de contrainte intégrée, et elles peuvent être utilisées en
fournissant un rappel de contrainte à PropertyMetadata .
Parties © 2005, 2009 Microsoft Corporation. Tous droits réservés.
Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions,
Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22
octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi
Directives de conception d’infrastructure
Modèles de conception courants
Vue d’ensemble de Microsoft. Data. sqlite
24/04/2020 • 2 minutes to read • Edit Online

Microsoft. Data. sqlite est un fournisseur ADO.net léger pour SQLite. Le fournisseur Entity Framework Core pour
SQLite s’appuie sur cette bibliothèque. Toutefois, il peut également être utilisé indépendamment ou avec d’autres
bibliothèques d’accès aux données.

Installation
La dernière version stable est disponible sur NuGet.
CLI .NET Core
Visual Studio

dotnet add package Microsoft.Data.Sqlite

Usage
Cette bibliothèque implémente les abstractions ADO.NET courantes pour les connexions, les commandes, les
lecteurs de données, etc.

using (var connection = new SqliteConnection("Data Source=hello.db"))


{
connection.Open();

var command = connection.CreateCommand();


command.CommandText =
@"
SELECT name
FROM user
WHERE id = $id
";
command.Parameters.AddWithValue("$id", id);

using (var reader = command.ExecuteReader())


{
while (reader.Read())
{
var name = reader.GetString(0);

Console.WriteLine($"Hello, {name}!");
}
}
}

Voir aussi
Chaînes de connexion
Référence des API
Syntaxe SQL
Documents et données XML
18/07/2020 • 6 minutes to read • Edit Online

Le .NET Framework fournit un jeu de classes complet et intégré qui vous permet de créer facilement des
applications capables de traiter du code XML. Les classes dans les espaces de noms suivants prennent en charge
l’analyse et l’écriture de code XML, l’édition de donnés XML en mémoire, la validation de données et la
transformation XSLT.
System.Xml
System.Xml.XPath
System.Xml.Xsl
System.Xml.Schema
System.Xml.Linq
Pour obtenir la liste complète, recherchez « System.Xml » sur le navigateur de l’API .NET.
Les classes dans ces espaces de noms prennent en charge les recommandations World Wide Web Consortium
(W3C). Par exemple :
La classe System.Xml.XmlDocument implémente les recommandations du W3C relatives aux modèles objet
de documents (DOM) de niveau 1 et 2 (standard).
Les classes System.Xml.XmlReader et System.Xml.XmlWriter sont conformes aux recommandations du W3C
relatives à XML 1.0 et aux espaces de noms dans XML.
Les schémas de la classe System.Xml.Schema.XmlSchemaSet sont conformes aux recommandations du
W3C relatives aux structures (Part 1) et aux types de données (Part 2) du schéma XML.
Les classes de l’espace de noms System.Xml.Xsl prennent en charge les transformations XSLT qui sont
conformes à la recommandation W3C XSLT 1.0.
Les classes XML du .NET Framework offrent les avantages suivants :
Améliorer. LINQ to XML (C#) et LINQ to XML (Visual Basic) facilitent la programmation avec XML et offrent
une expérience de requête similaire à SQL.
Amélioré. Les classes XML du .NET Framework sont extensibles grâce à l'utilisation de classes de base
abstraites et de méthodes virtuelles. Par exemple, vous pouvez créer une classe dérivée de la classe
XmlUrlResolver qui stocke le flux mis en cache sur le disque local.
Architecture enfichable. Le .NET Framework propose une architecture dans laquelle les composants
peuvent s'utiliser réciproquement et où les données peuvent être transmises en continu entre les
composants. Par exemple, un magasin de données, tel qu'un objet XPathDocument ou XmlDocument, peut
être transformé à l'aide de la classe XslCompiledTransform, et la sortie peut ensuite être soit transmise sous
la forme de flux à un autre magasin, soit retournée sous la forme d'un flux à partir d'un service web.
Niveau de performance. Pour améliorer les performances des applications, certaines des classes XML du
.NET Framework prennent en charge un modèle basé sur les flux de données ayant les caractéristiques
suivantes :
Mise en cache minimale pour une analyse avant uniquement et de tirage (XmlReader).
Validation avant uniquement (XmlReader).
Navigation par curseur qui minimise la création de nœuds à un seul nœud virtuel, tout en permettant
l'accès aléatoire au document (XPathNavigator).
Pour de meilleures performances chaque fois que le traitement XSLT est nécessaire, vous pouvez utiliser la
classe XPathDocument qui est un magasin optimisé en lecture seule pour les requêtes XPath, conçu pour
fonctionner de manière efficace avec la classe XslCompiledTransform.
Intégration à ADO.NET. les classes XML et ADO.NET sont étroitement intégrés afin de rassembler les
données relationnelles et XML. La classe DataSet est un cache en mémoire de données extraites d'une base
de données. La classe DataSet a la capacité de lire et d'écrire du XML à l'aide des classes XmlReader et
XmlWriter, de rendre persistante sa structure de schéma relationnel interne sous la forme de schémas XML
(XSD) et de déduire la structure de schéma d'un document XML.

Dans cette section


Options de traitement XML Présente les options de traitement des données XML.
Traitement des données XML en mémoire Présente les trois modèles pour le traitement des données XML en
mémoire : LINQ to XML (C#) et LINQ to XML (Visual Basic), la XmlDocument classe (basée sur le document Object
Model W3C) et la XPathDocument classe (basée sur le modèle de données XPath).
Transformations XSLT
Décrit comment utiliser le processeur XSLT.
Modèle d’objet de schéma XML (SOM)
Décrit les classes utilisées pour créer et manipuler des schémas XML (XSD) en fournissant une classe XmlSchema
pour le chargement et la modification d'un schéma.
Intégration de XML aux données relationnelles et ADO.NET
Décrit comment le .NET Framework permet un accès synchrone en temps réel aux représentations relationnelles et
hiérarchiques des données via l’objet DataSet et l’objet XmlDataDocument.
Gestion des espaces de noms dans un document XML
Décrit comment la classe XmlNamespaceManager est utilisée pour stocker et gérer des informations d'espace de
noms.
Prise en charge des types dans les classes System. Xml
Décrit comment les types de données XML sont mappés aux types CLR, comment convertir des types de données
XML, ainsi que d’autres fonctionnalités de prise en charge des types dans les classes System.Xml.

Sections connexes
ADO.NET
Présente des informations sur la manière d'accéder à des données à l'aide d'ADO.NET.
Caution
Offre une vue d'ensemble du système de sécurité .NET Framework.
Sécurité dans .NET
18/07/2020 • 2 minutes to read • Edit Online

Les common language runtime et .NET fournissent de nombreux services et classes utiles qui permettent aux
développeurs d’écrire facilement du code sécurisé et permettent aux administrateurs système de personnaliser les
autorisations accordées au code afin qu’il puisse accéder aux ressources protégées. En outre, le runtime et le .NET
fournissent des classes et des services utiles qui facilitent l’utilisation du chiffrement et de la sécurité basée sur les
rôles.

Dans cette section


Concepts fondamentaux sur la sécurité
Offre une vue d’ensemble des fonctionnalités de sécurité du Common Language Runtime. Cette section
s'adresse aux développeurs et aux administrateurs système.
Sécurité basée sur les rôles
Explique comment interagir avec la sécurité basée sur les rôles dans votre code. Cette section s'adresse aux
développeurs.
Modèle de chiffrement
Fournit une vue d’ensemble des services de chiffrement fournis par .NET. Cette section s'adresse aux
développeurs.
Instructions de codage sécurisé
Décrit quelques-unes des meilleures pratiques pour la création d’applications .NET fiables. Cette section
s'adresse aux développeurs.

Sections connexes
Guide de développement
Fournit un guide sur tous les domaines technologiques clés et les tâches relatives au développement
d’applications, notamment la création, la configuration, le débogage, la sécurisation et le déploiement de votre
application, ainsi que des informations sur la programmation dynamique, l’interopérabilité, l’extensibilité, la
gestion de mémoire et les threads.
Sérialisation dans .NET
18/07/2020 • 2 minutes to read • Edit Online

La sérialisation correspond au processus de conversion de l'état d'un objet en un formulaire persistant ou


transportable. Le complément de la sérialisation est la désérialisation, qui convertit un flux de données en un objet.
Ensemble, ces processus autorisent le stockage et le transfert des données.
.NET propose les technologies de sérialisation suivantes :
La sérialisation binaire préserve la fidélité du type, ce qui est utile pour conserver l’état d’un objet entre
différents appels d’une application. Par exemple, vous pouvez partager un objet entre plusieurs applications
en le sérialisant dans le Presse-papiers. Vous pouvez sérialiser un objet vers un flux, un disque, la mémoire,
le réseau, et ainsi de suite. La communication à distance utilise la sérialisation pour passer des objets « par
valeur » d'un ordinateur ou d'un domaine d'application à un autre.
La sérialisation XML et SOAP sérialise uniquement les propriétés et les champs publics et ne conserve pas la
fidélité du type. Ceci est utile lorsque vous souhaitez fournir ou consommer des données sans restreindre
l'application qui les utilise. XML étant une norme ouverte, elle constitue une option intéressante pour
partager des données via le Web. Le protocole SOAP est également une norme ouverte et représente par
conséquent une option avantageuse.
La sérialisation JSON sérialise uniquement les propriétés publiques et ne conserve pas la fidélité du type.
JSON est une norme ouverte qui est un choix attrayant pour le partage de données sur le Web.

Référence
System.Runtime.Serialization
Contient des classes qui peuvent être utilisées pour sérialiser et désérialiser des objets.
System.Xml.Serialization
Contient des classes qui peuvent être utilisées pour sérialiser des objets en documents ou en flux de données au
format XML.
System.Text.Json
Contient des classes qui peuvent être utilisées pour sérialiser des objets dans des documents ou des flux au format
JSON.
Sérialisation et désérialisation JSON (marshaling et
démarshaling) dans .NET-vue d’ensemble
18/07/2020 • 2 minutes to read • Edit Online

L' System.Text.Json espace de noms fournit des fonctionnalités pour sérialiser vers et désérialiser à partir de
JavaScript Object Notation (JSON).
La conception de la bibliothèque met l’accent sur des performances élevées et une allocation de mémoire faible
sur un ensemble complet de fonctionnalités. La prise en charge UTF-8 intégrée optimise le processus de lecture et
d’écriture du texte JSON encodé au format UTF-8, qui est l’encodage le plus courant pour les données sur le Web
et les fichiers sur le disque.
La bibliothèque fournit également des classes pour l’utilisation d’un modèle DOM (Document Object Model) en
mémoire. Cette fonctionnalité permet un accès en lecture seule aléatoire des éléments dans un fichier JSON ou
une chaîne.

Obtention de la bibliothèque
La bibliothèque est intégrée dans le cadre de l’infrastructure partagée .net Core 3,0 .
Pour les autres frameworks cibles, installez le System.Text.Json package NuGet. Le package prend en charge :
.NET Standard 2,0 et versions ultérieures
.NET Framework 4.7.2 et versions ultérieures
.NET Core 2,0, 2,1 et 2,2

Ressources supplémentaires
Comment utiliser la bibliothèque
Migration à partir deNewtonsoft.Json
Comment écrire des convertisseurs
System.Text.Jsoncode source
System.Text.JsonRéférence d’API
System.Text.Json. Référence de l’API de sérialisation
Comment sérialiser et désérialiser (marshaler et
démarshaler) JSON dans .NET
18/07/2020 • 48 minutes to read • Edit Online

Cet article explique comment utiliser l' System.Text.Json espace de noms pour sérialiser et désérialiser vers et à
partir de JavaScript Object Notation (JSON). Si vous transférez du code existant à partir de Newtonsoft.Json ,
consultez Comment migrer vers System.Text.Json .
Les instructions et l’exemple de code utilisent directement la bibliothèque, et non un Framework comme ASP.net
Core.
La majeure partie de l’exemple de code de sérialisation affecte JsonSerializerOptions.WriteIndented true la valeur
« joli Printing » au format JSON (avec mise en retrait et espace blanc pour la lisibilité humaine). Pour une
utilisation en production, vous acceptez généralement la valeur par défaut de false pour ce paramètre.
Les exemples de code font référence à la classe et aux variantes suivantes :

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Espaces de noms
L' System.Text.Json espace de noms contient tous les points d’entrée et les principaux types. L'
System.Text.Json.Serialization espace de noms contient des attributs et des API pour les scénarios avancés et la
personnalisation propres à la sérialisation et à la désérialisation. Les exemples de code présentés dans cet article
requièrent des using directives pour l’un de ces espaces de noms ou les deux :

using System.Text.Json;
using System.Text.Json.Serialization;

Les attributs de l' System.Runtime.Serialization espace de noms ne sont actuellement pas pris en charge dans
System.Text.Json .

Comment écrire des objets .NET dans JSON (sérialiser)


Pour écrire du code JSON dans une chaîne ou dans un fichier, appelez la JsonSerializer.Serialize méthode.
L’exemple suivant crée JSON sous forme de chaîne :

string jsonString;
jsonString = JsonSerializer.Serialize(weatherForecast);

L’exemple suivant utilise du code synchrone pour créer un fichier JSON :


jsonString = JsonSerializer.Serialize(weatherForecast);
File.WriteAllText(fileName, jsonString);

L’exemple suivant utilise du code asynchrone pour créer un fichier JSON :

using (FileStream fs = File.Create(fileName))


{
await JsonSerializer.SerializeAsync(fs, weatherForecast);
}

Les exemples précédents utilisent l’inférence de type pour le type en cours de sérialisation. Une surcharge de
Serialize() prend un paramètre de type générique :

jsonString = JsonSerializer.Serialize<WeatherForecastWithPOCOs>(weatherForecast);

Exemple de sérialisation
Voici un exemple de classe qui contient des collections et une classe imbriquée :

public class WeatherForecastWithPOCOs


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public string SummaryField;
public IList<DateTimeOffset> DatesAvailable { get; set; }
public Dictionary<string, HighLowTemps> TemperatureRanges { get; set; }
public string[] SummaryWords { get; set; }
}

public class HighLowTemps


{
public int High { get; set; }
public int Low { get; set; }
}

La sortie JSON de la sérialisation d’une instance du type précédent ressemble à l’exemple suivant. La sortie JSON
est minimisés par défaut :

{"Date":"2019-08-01T00:00:00-07:00","TemperatureCelsius":25,"Summary":"Hot","DatesAvailable":["2019-08-
01T00:00:00-07:00","2019-08-02T00:00:00-07:00"],"TemperatureRanges":{"Cold":{"High":20,"Low":-10},"Hot":
{"High":60,"Low":20}},"SummaryWords":["Cool","Windy","Humid"]}

L’exemple suivant montre le même JSON, mis en forme (autrement dit, avec un espace blanc et une mise en
retrait) :
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"DatesAvailable": [
"2019-08-01T00:00:00-07:00",
"2019-08-02T00:00:00-07:00"
],
"TemperatureRanges": {
"Cold": {
"High": 20,
"Low": -10
},
"Hot": {
"High": 60,
"Low": 20
}
},
"SummaryWords": [
"Cool",
"Windy",
"Humid"
]
}

Sérialiser vers UTF -8


Pour sérialiser vers UTF-8, appelez la JsonSerializer.SerializeToUtf8Bytes méthode :

byte[] jsonUtf8Bytes;
var options = new JsonSerializerOptions
{
WriteIndented = true
};
jsonUtf8Bytes = JsonSerializer.SerializeToUtf8Bytes(weatherForecast, options);

Une Serialize surcharge qui prend un Utf8JsonWriter est également disponible.


La sérialisation en UTF-8 est environ 5-10% plus rapide que l’utilisation des méthodes basées sur une chaîne. La
différence est que les octets (au format UTF-8) n’ont pas besoin d’être convertis en chaînes (UTF-16).

Comportements de sérialisation
Par défaut, toutes les propriétés publiques sont sérialisées. Vous pouvez spécifier les propriétés à exclure.
L' encodeur par défaut échappe les caractères non-ASCII, les caractères respectant le format HTML dans la
plage ASCII, et les caractères qui doivent être échappés conformément à la spécification JSON RFC 8259.
Par défaut, JSON est minimisés. Vous pouvez très bien imprimer le JSON.
Par défaut, la casse des noms JSON correspond aux noms .NET. Vous pouvez personnaliser la casse du nom
JSON.
Les références circulaires sont détectées et les exceptions levées.
Actuellement, les champs sont exclus.
Les types pris en charge sont les suivants :
Primitives .NET qui sont mappées à des primitives JavaScript, telles que des types numériques, des chaînes et
des valeurs booléennes.
Objets CLR Plain Olddéfinis par l’utilisateur (POCO).
Tableaux unidimensionnels et en escalier ( ArrayName[][] ).
Dictionary<string,TValue> où TValue est object , JsonElement ou un poco.
Collections des espaces de noms suivants.
System.Collections
System.Collections.Generic
System.Collections.Immutable
Vous pouvez implémenter des convertisseurs personnalisés pour gérer des types supplémentaires ou pour fournir
des fonctionnalités qui ne sont pas prises en charge par les convertisseurs intégrés.

Comment lire JSON dans des objets .NET (désérialisation)


Pour désérialiser à partir d’une chaîne ou d’un fichier, appelez la JsonSerializer.Deserialize méthode.
L’exemple suivant lit JSON à partir d’une chaîne et crée une instance de la WeatherForecast classe indiquée
précédemment pour l' exemple de sérialisation:

weatherForecast = JsonSerializer.Deserialize<WeatherForecastWithPOCOs>(jsonString);

Pour désérialiser à partir d’un fichier à l’aide d’un code synchrone, lisez le fichier dans une chaîne, comme indiqué
dans l’exemple suivant :

jsonString = File.ReadAllText(fileName);
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString);

Pour désérialiser à partir d’un fichier à l’aide de code asynchrone, appelez la DeserializeAsync méthode :

using (FileStream fs = File.OpenRead(fileName))


{
weatherForecast = await JsonSerializer.DeserializeAsync<WeatherForecast>(fs);
}

Désérialiser à partir d’UTF -8


Pour désérialiser à partir d’UTF-8, appelez une JsonSerializer.Deserialize surcharge qui accepte un Utf8JsonReader
ou un ReadOnlySpan<byte> , comme indiqué dans les exemples suivants. Les exemples supposent que le JSON se
trouve dans un tableau d’octets nommé jsonUtf8Bytes.

var readOnlySpan = new ReadOnlySpan<byte>(jsonUtf8Bytes);


weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(readOnlySpan);

var utf8Reader = new Utf8JsonReader(jsonUtf8Bytes);


weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(ref utf8Reader);

Comportement de la désérialisation
Par défaut, la correspondance de nom de propriété respecte la casse. Vous pouvez spécifier le non-respect de la
casse.
Si le JSON contient une valeur pour une propriété en lecture seule, la valeur est ignorée et aucune exception
n’est levée.
La désérialisation en types référence sans constructeur sans paramètre n’est pas prise en charge.
La désérialisation des objets immuables ou des propriétés en lecture seule n’est pas prise en charge.
Par défaut, les enums sont pris en charge en tant que nombres. Vous pouvez sérialiser les noms d’enum en tant
que chaînes.
Les champs ne sont pas pris en charge.
Par défaut, les commentaires ou les virgules de fin dans le JSON lèvent des exceptions. Vous pouvez autoriser
les commentaires et les virgules de fin.
La profondeur maximale par défaut est 64.
Vous pouvez implémenter des convertisseurs personnalisés pour fournir des fonctionnalités qui ne sont pas prises
en charge par les convertisseurs intégrés.

Sérialiser au format JSON


Pour imprimer la sortie JSON, définissez JsonSerializerOptions.WriteIndented sur true :

var options = new JsonSerializerOptions


{
WriteIndented = true,
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Voici un exemple de type à sérialiser et à imprimer une sortie JSON :

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}

Personnaliser les noms et les valeurs JSON


Par défaut, les noms de propriété et les clés de dictionnaire sont inchangés dans la sortie JSON, y compris la casse.
Les valeurs enum sont représentées sous forme de nombres. Cette section explique comment :
Personnaliser les noms de propriété individuels
Convertir tous les noms de propriété en casse mixte
Implémenter une stratégie d’attribution de noms de propriété personnalisée
Convertir les clés du dictionnaire en casse mixte
Convertir des enums en chaînes et casse mixte
Pour les autres scénarios qui nécessitent un traitement spécial des noms et des valeurs de propriété JSON, vous
pouvez implémenter des convertisseurs personnalisés.
Personnaliser les noms de propriété individuels
Pour définir le nom de chaque propriété, utilisez l’attribut [JsonPropertyName] .
Voici un exemple de type pour sérialiser et JSON obtenu :
public class WeatherForecastWithPropertyNameAttribute
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
[JsonPropertyName("Wind")]
public int WindSpeed { get; set; }
}

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"Wind": 35
}

Nom de propriété défini par cet attribut :


S’applique dans les deux directions, pour la sérialisation et la désérialisation.
Est prioritaire sur les stratégies d’attribution de noms de propriété.
Utiliser la casse mixte pour tous les noms de propriété JSON
Pour utiliser la casse mixte pour tous les noms de propriétés JSON, affectez
JsonSerializerOptions.PropertyNamingPolicy la valeur à JsonNamingPolicy.CamelCase , comme indiqué dans
l’exemple suivant :

var serializeOptions = new JsonSerializerOptions


{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);

Voici un exemple de classe pour sérialiser et la sortie JSON :

public class WeatherForecastWithPropertyNameAttribute


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
[JsonPropertyName("Wind")]
public int WindSpeed { get; set; }
}

{
"date": "2019-08-01T00:00:00-07:00",
"temperatureCelsius": 25,
"summary": "Hot",
"Wind": 35
}

La stratégie de nommage de propriété casse mixte :


S’applique à la sérialisation et à la désérialisation.
Est substitué par des [JsonPropertyName] attributs. C’est pourquoi le nom de la propriété JSON Wind dans
l’exemple n’est pas une casse mixte.
Utiliser une stratégie d’attribution de noms de propriété JSON personnalisée
Pour utiliser une stratégie d’attribution de noms de propriété JSON personnalisée, créez une classe qui dérive de
JsonNamingPolicy et substituez la ConvertName méthode, comme illustré dans l’exemple suivant :

using System.Text.Json;

namespace SystemTextJsonSamples
{
public class UpperCaseNamingPolicy : JsonNamingPolicy
{
public override string ConvertName(string name) =>
name.ToUpper();
}
}

Définissez ensuite la JsonSerializerOptions.PropertyNamingPolicy propriété sur une instance de votre classe de


stratégie d’attribution de noms :

var options = new JsonSerializerOptions


{
PropertyNamingPolicy = new UpperCaseNamingPolicy(),
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Voici un exemple de classe pour sérialiser et la sortie JSON :

public class WeatherForecastWithPropertyNameAttribute


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
[JsonPropertyName("Wind")]
public int WindSpeed { get; set; }
}

{
"DATE": "2019-08-01T00:00:00-07:00",
"TEMPERATURECELSIUS": 25,
"SUMMARY": "Hot",
"Wind": 35
}

La stratégie d’attribution de noms de propriété JSON :


S’applique à la sérialisation et à la désérialisation.
Est substitué par des [JsonPropertyName] attributs. C’est pourquoi le nom de la propriété JSON Wind dans
l’exemple n’est pas en majuscules.
Clés de dictionnaire de casse mixte
Si une propriété d’un objet à sérialiser est de type Dictionary<string,TValue> , les string clés peuvent être
converties en casse mixte. Pour ce faire, affectez DictionaryKeyPolicy à JsonNamingPolicy.CamelCase la valeur,
comme illustré dans l’exemple suivant :
var options = new JsonSerializerOptions
{
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

La sérialisation d’un objet avec un dictionnaire nommé TemperatureRanges qui a des paires clé-valeur
"ColdMinTemp", 20 et "HotMinTemp", 40 provoquerait une sortie JSON similaire à l’exemple suivant :

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"TemperatureRanges": {
"coldMinTemp": 20,
"hotMinTemp": 40
}
}

La stratégie d’attribution de noms de casse mixte pour les clés de dictionnaire s’applique uniquement à la
sérialisation. Si vous désérialisez un dictionnaire, les clés correspondent au fichier JSON même si vous spécifiez
JsonNamingPolicy.CamelCase pour le DictionaryKeyPolicy .

Enums en tant que chaînes


Par défaut, les enums sont sérialisés en tant que nombres. Pour sérialiser les noms d’enum sous forme de chaînes,
utilisez JsonStringEnumConverter .
Par exemple, supposons que vous deviez sérialiser la classe suivante qui a une énumération :

public class WeatherForecastWithEnum


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public Summary Summary { get; set; }
}

public enum Summary


{
Cold, Cool, Warm, Hot
}

Si le résumé est Hot , par défaut, le JSON sérialisé a la valeur numérique 3 :

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": 3
}

L’exemple de code suivant sérialise les noms d’enum à la place des valeurs numériques, et convertit les noms en
casse mixte :

options = new JsonSerializerOptions();


options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
options.WriteIndented = true;
jsonString = JsonSerializer.Serialize(weatherForecast, options);
Le JSON obtenu ressemble à l’exemple suivant :

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "hot"
}

Les noms de chaîne enum peuvent également être désérialisés, comme illustré dans l’exemple suivant :

options = new JsonSerializerOptions();


options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
weatherForecast = JsonSerializer.Deserialize<WeatherForecastWithEnum>(jsonString, options);

Exclure des propriétés de la sérialisation


Par défaut, toutes les propriétés publiques sont sérialisées. Si vous ne souhaitez pas que certains d’entre eux
s’affichent dans la sortie JSON, vous avez plusieurs options. Cette section explique comment exclure les éléments
suivants :
Propriétés individuelles
Toutes les propriétés en lecture seule
Toutes les propriétés de valeur null
Exclure des propriétés individuelles
Pour ignorer des propriétés individuelles, utilisez l’attribut [JsonIgnore] .
Voici un exemple de type pour sérialiser et la sortie JSON :

public class WeatherForecastWithIgnoreAttribute


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
[JsonIgnore]
public string Summary { get; set; }
}

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
}

Exclure toutes les propriétés en lecture seule


Une propriété est en lecture seule si elle contient un accesseur get public, mais pas un accesseur Set public. Pour
exclure toutes les propriétés en lecture seule, affectez la valeur JsonSerializerOptions.IgnoreReadOnlyProperties à
true , comme illustré dans l’exemple suivant :

var options = new JsonSerializerOptions


{
IgnoreReadOnlyProperties = true,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Voici un exemple de type pour sérialiser et la sortie JSON :


public class WeatherForecastWithROProperty
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public int WindSpeedReadOnly { get; private set; } = 35;
}

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
}

Cette option s’applique uniquement à la sérialisation. Pendant la désérialisation, les propriétés en lecture seule
sont ignorées par défaut.
Exclure toutes les propriétés de valeur null
Pour exclure toutes les propriétés de valeur null, affectez à la propriété la valeur IgnoreNullValues true , comme
illustré dans l’exemple suivant :

var options = new JsonSerializerOptions


{
IgnoreNullValues = true,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Voici un exemple d’objet pour sérialiser et la sortie JSON :

P RO P RIÉT É VA L EUR

Date DE 8/1/2019 12:00:00 À 07:00

TemperatureCelsius 25

Résumé null

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25
}

Ce paramètre s’applique à la sérialisation et à la désérialisation. Pour plus d’informations sur son effet sur la
désérialisation, consultez ignorer la valeur null lors de la désérialisation.

Personnaliser l’encodage de caractères


Par défaut, le sérialiseur échappe tous les caractères non-ASCII. Autrement dit, il les remplace par \uxxxx où xxxx
est le code Unicode du caractère. Par exemple, si la Summary propriété a la valeur cyrillique Жарко, l'
WeatherForecast objet est sérialisé comme indiqué dans cet exemple :
{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "\u0436\u0430\u0440\u043A\u043E"
}

Sérialiser les jeux de caractères de la langue


Pour sérialiser le ou les jeux de caractères d’une ou plusieurs langues sans échappement, spécifiez la ou les plages
Unicode lors de la création d’une instance de System.Text.Encodings.Web.JavaScriptEncoder , comme illustré dans
l’exemple suivant :

using System;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;

options = new JsonSerializerOptions


{
Encoder = JavaScriptEncoder.Create(UnicodeRanges.BasicLatin, UnicodeRanges.Cyrillic),
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Ce code n’échappe pas aux caractères cyrilliques ou grecs. Si la Summary propriété a la valeur cyrillique Жарко, l'
WeatherForecast objet est sérialisé comme indiqué dans cet exemple :

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "жарко"
}

Pour sérialiser tous les jeux de langues sans échappement, utilisez UnicodeRanges.All .
Sérialiser des caractères spécifiques
Une alternative consiste à spécifier des caractères individuels que vous souhaitez autoriser sans être échappés.
L’exemple suivant sérialise uniquement les deux premiers caractères de Жарко :

using System;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;

var encoderSettings = new TextEncoderSettings();


encoderSettings.AllowCharacters('\u0436', '\u0430');
encoderSettings.AllowRange(UnicodeRanges.BasicLatin);
options = new JsonSerializerOptions
{
Encoder = JavaScriptEncoder.Create(encoderSettings),
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Voici un exemple de JSON généré par le code précédent :


{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "жа\u0440\u043A\u043E"
}

Sérialiser tous les caractères


Pour réduire l’échappement, vous pouvez utiliser JavaScriptEncoder.UnsafeRelaxedJsonEscaping , comme illustré
dans l’exemple suivant :

using System;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;

options = new JsonSerializerOptions


{
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Cau t i on

Par rapport à l’encodeur par défaut, l' UnsafeRelaxedJsonEscaping encodeur est plus permissif en ce qui concerne la
transmission sans séquence d’échappement des caractères :
Elle n’échappe pas les caractères HTML, tels que < ,, > & et ' .
Il n’offre aucune protection supplémentaire en profondeur contre les attaques XSS ou de divulgation
d’informations, telles que celles qui peuvent résulter du client et du serveur qui ne sont pas en accord avec le
jeude caractères.
Utilisez l’encodeur non sécurisé uniquement lorsqu’il est connu que le client interprétera la charge utile obtenue
en tant que code JSON encodé UTF-8. Par exemple, vous pouvez l’utiliser si le serveur envoie l’en-tête de réponse
Content-Type: application/json; charset=utf-8 . N’autorisez jamais l' UnsafeRelaxedJsonEscaping émission de la
sortie brute dans une page HTML ou un <script> élément.

Sérialiser les propriétés des classes dérivées


La sérialisation d’une hiérarchie de type polymorphe n’est pas prise en charge. Par exemple, si une propriété est
définie comme une interface ou une classe abstraite, seules les propriétés définies sur l’interface ou la classe
abstraite sont sérialisées, même si le type de runtime a des propriétés supplémentaires. Les exceptions à ce
comportement sont expliquées dans cette section.
Par exemple, supposons que vous avez une WeatherForecast classe et une classe dérivée WeatherForecastDerived :

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}
public class WeatherForecastDerived : WeatherForecast
{
public int WindSpeed { get; set; }
}

Et supposons que l’argument de type de la Serialize méthode au moment de la compilation est WeatherForecast
:

var options = new JsonSerializerOptions


{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecast>(weatherForecast, options);

Dans ce scénario, la WindSpeed propriété n’est pas sérialisée, même si l' weatherForecast objet est en fait un
WeatherForecastDerived objet. Seules les propriétés de la classe de base sont sérialisées :

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}

Ce comportement est destiné à empêcher l’exposition accidentelle de données dans un type créé par le runtime
dérivé.
Pour sérialiser les propriétés du type dérivé dans l’exemple précédent, utilisez l’une des approches suivantes :
Appelez une surcharge de Serialize qui vous permet de spécifier le type au moment de l’exécution :

options = new JsonSerializerOptions


{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecast, weatherForecast.GetType(), options);

Déclarez l’objet à sérialiser en tant que object .

options = new JsonSerializerOptions


{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize<object>(weatherForecast, options);

Dans l’exemple de scénario précédent, les deux approches entraînent l’inclusion de la WindSpeed propriété dans la
sortie JSON :

{
"WindSpeed": 35,
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
IMPORTANT
Ces approches fournissent la sérialisation polymorphe uniquement pour l’objet racine à sérialiser, et non pour les propriétés
de cet objet racine.

Vous pouvez obtenir la sérialisation polymorphe pour les objets de niveau inférieur si vous les définissez en tant
que type object . Par exemple, supposons que votre WeatherForecast classe ait une propriété nommée
PreviousForecast qui peut être définie en tant que type WeatherForecast ou object :

public class WeatherForecastWithPrevious


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public WeatherForecast PreviousForecast { get; set; }
}

public class WeatherForecastWithPreviousAsObject


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public object PreviousForecast { get; set; }
}

Si la PreviousForecast propriété contient une instance de WeatherForecastDerived :


La sortie JSON de la sérialisation WeatherForecastWithPrevious n’inclut pas WindSpeed .
La sortie JSON de la sérialisation WeatherForecastWithPreviousAsObject comprend WindSpeed .

Pour sérialiser WeatherForecastWithPreviousAsObject , il n’est pas nécessaire d’appeler Serialize<object> ou


GetType parce que l’objet racine n’est pas celui d’un type dérivé. L’exemple de code suivant n’appelle pas
Serialize<object> ou GetType :

options = new JsonSerializerOptions


{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecastWithPreviousAsObject, options);

Le code précédent sérialise correctement WeatherForecastWithPreviousAsObject :

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"PreviousForecast": {
"WindSpeed": 35,
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot"
}
}

La même approche de la définition des propriétés object fonctionne avec les interfaces. Supposons que vous
disposez de l’interface et de l’implémentation suivantes, et que vous souhaitez sérialiser une classe avec des
propriétés qui contiennent des instances d’implémentation :

using System;

namespace SystemTextJsonSamples
{
public interface IForecast
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

public class Forecast : IForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public int WindSpeed { get; set; }
}

public class Forecasts


{
public IForecast Monday { get; set; }
public object Tuesday { get; set; }
}
}

Lorsque vous sérialisez une instance de Forecasts , Tuesday affiche uniquement la WindSpeed propriété, car
Tuesday est défini comme object suit :

var forecasts = new Forecasts


{
Monday = new Forecast
{
Date = DateTime.Parse("2020-01-06"),
TemperatureCelsius = 10,
Summary = "Cool",
WindSpeed = 8
},
Tuesday = new Forecast
{
Date = DateTime.Parse("2020-01-07"),
TemperatureCelsius = 11,
Summary = "Rainy",
WindSpeed = 10
}
};

options = new JsonSerializerOptions


{
WriteIndented = true
};
jsonString = JsonSerializer.Serialize(forecasts, options);

L’exemple suivant montre le code JSON qui résulte du code précédent :


{
"Monday": {
"Date": "2020-01-06T00:00:00-08:00",
"TemperatureCelsius": 10,
"Summary": "Cool"
},
"Tuesday": {
"Date": "2020-01-07T00:00:00-08:00",
"TemperatureCelsius": 11,
"Summary": "Rainy",
"WindSpeed": 10
}
}

Pour plus d’informations sur la sérialisation polymorphe et sur la désérialisation , consultez Comment migrer
de Newtonsoft.Json vers System.Text.Json .

Autoriser les commentaires et les virgules de fin


Par défaut, les commentaires et les virgules de fin ne sont pas autorisés dans JSON. Pour autoriser les
commentaires dans le JSON, affectez à la propriété la valeur JsonSerializerOptions.ReadCommentHandling
JsonCommentHandling.Skip . Et pour autoriser les virgules de fin, affectez à la propriété la valeur
JsonSerializerOptions.AllowTrailingCommas true . L’exemple suivant montre comment autoriser les deux :

var options = new JsonSerializerOptions


{
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true,
};
var weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString, options);

Voici un exemple de code JSON avec des commentaires et une virgule de fin :

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25, // Fahrenheit 77
"Summary": "Hot", /* Zharko */
}

Correspondance de propriété ne respectant pas la casse


Par défaut, la désérialisation recherche les correspondances de noms de propriétés respectant la casse entre JSON
et les propriétés de l’objet cible. Pour modifier ce comportement, affectez à la valeur
JsonSerializerOptions.PropertyNameCaseInsensitive true :

var options = new JsonSerializerOptions


{
PropertyNameCaseInsensitive = true,
};
var weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString, options);

Voici un exemple de JSON avec les noms de propriété de casse mixte. Il peut être désérialisé dans le type suivant
qui a des noms de propriété de casse Pascal.
{
"date": "2019-08-01T00:00:00-07:00",
"temperatureCelsius": 25,
"summary": "Hot",
}

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Handle de dépassement JSON


Lors de la désérialisation, vous pouvez recevoir des données dans le JSON qui ne sont pas représentées par les
propriétés du type cible. Par exemple, supposons que votre type de cible est le suivant :

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Et le JSON à désérialiser est le suivant :

{
"Date": "2019-08-01T00:00:00-07:00",
"temperatureCelsius": 25,
"Summary": "Hot",
"DatesAvailable": [
"2019-08-01T00:00:00-07:00",
"2019-08-02T00:00:00-07:00"
],
"SummaryWords": [
"Cool",
"Windy",
"Humid"
]
}

Si vous désérialisez le JSON affiché dans le type indiqué, les DatesAvailable SummaryWords Propriétés et ne sont
pas visibles et sont perdues. Pour capturer des données supplémentaires telles que ces propriétés, appliquez
l’attribut JsonExtensionData à une propriété de type Dictionary<string,object> ou
Dictionary<string,JsonElement> :

public class WeatherForecastWithExtensionData


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
[JsonExtensionData]
public Dictionary<string, object> ExtensionData { get; set; }
}

Lorsque vous désérialisez le JSON indiqué plus haut dans ce type d’exemple, les données supplémentaires
deviennent des paires clé-valeur de la ExtensionData propriété :

P RO P RIÉT É VA L EUR N OT ES

Date DE 8/1/2019 12:00:00 À 07:00

TemperatureCelsius 0 Incompatibilité sensible à la casse (


temperatureCelsius dans le JSON), la
propriété n’est donc pas définie.

Résumé À chaud

ExtensionData temperatureCelsius : 25 Étant donné que le cas ne


correspondait pas, cette propriété JSON
est un extra et devient une paire clé-
valeur dans le dictionnaire.

DatesAvailable: Une propriété supplémentaire du JSON


DE 8/1/2019 12:00:00 À 07:00 devient une paire clé-valeur, avec un
DE 8/2/2019 12:00:00 À 07:00 tableau comme objet de valeur.

SummaryWords: Une propriété supplémentaire du JSON


À froid devient une paire clé-valeur, avec un
Venteux tableau comme objet de valeur.
Humide

Lorsque l’objet cible est sérialisé, les paires de valeurs de clés de données d’extension deviennent des propriétés
JSON comme elles étaient dans le JSON entrant :

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 0,
"Summary": "Hot",
"temperatureCelsius": 25,
"DatesAvailable": [
"2019-08-01T00:00:00-07:00",
"2019-08-02T00:00:00-07:00"
],
"SummaryWords": [
"Cool",
"Windy",
"Humid"
]
}

Notez que le ExtensionData nom de la propriété n’apparaît pas dans le JSON. Ce comportement permet au JSON
d’effectuer un aller-retour sans perdre aucune donnée supplémentaire qui, sinon, ne serait pas désérialisée.

Ignorer la valeur null lors de la désérialisation


Par défaut, si une propriété dans JSON a la valeur null, la propriété correspondante dans l’objet cible a la valeur
null. Dans certains scénarios, la propriété cible peut avoir une valeur par défaut et vous ne souhaitez pas qu’une
valeur null remplace la valeur par défaut.
Par exemple, supposons que le code suivant représente votre objet cible :
public class WeatherForecastWithDefault
{
public WeatherForecastWithDefault()
{
Date = DateTimeOffset.Parse("2001-01-01");
Summary = "No summary";
}
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Et supposons que le code JSON suivant est désérialisé :

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": null
}

Après la désérialisation, la Summary propriété de l' WeatherForecastWithDefault objet a la valeur null.


Pour modifier ce comportement, affectez JsonSerializerOptions.IgnoreNullValues à true la valeur, comme illustré
dans l’exemple suivant :

var options = new JsonSerializerOptions


{
IgnoreNullValues = true
};
weatherForecast = JsonSerializer.Deserialize<WeatherForecastWithDefault>(jsonString, options);

Avec cette option, la Summary propriété de l' WeatherForecastWithDefault objet est la valeur par défaut « no
Summary » après la désérialisation.
Les valeurs NULL dans le JSON sont ignorées uniquement si elles sont valides. Les valeurs NULL pour les types
valeur non Nullable provoquent des exceptions.

Utf8JsonReader, Utf8JsonWriter et JsonDocument


System.Text.Json.Utf8JsonReaderest un lecteur haute performance, à faible allocation et en avant uniquement pour
le texte JSON encodé en UTF-8, lu à partir d’un ou d’un ReadOnlySpan<byte> ReadOnlySequence<byte> . Le
Utf8JsonReader est un type de bas niveau qui peut être utilisé pour créer des analyseurs et des désérialiseurs
personnalisés. La JsonSerializer.Deserialize méthode utilise des Utf8JsonReader couvertures.
System.Text.Json.Utf8JsonWriterest une méthode très performante pour écrire du texte JSON encodé en UTF-8 à
partir de types .NET courants tels que String , Int32 et DateTime . Le writer est un type de bas niveau qui peut
être utilisé pour créer des sérialiseurs personnalisés. La JsonSerializer.Serialize méthode utilise des
Utf8JsonWriter couvertures.

System.Text.Json.JsonDocumentoffre la possibilité de générer un Document Object Model en lecture seule (DOM)


à l’aide de Utf8JsonReader . Le DOM fournit un accès aléatoire aux données dans une charge utile JSON. Les
éléments JSON qui composent la charge utile sont accessibles via le JsonElement type. Le JsonElement type
fournit des énumérateurs de tableau et d’objet, ainsi que des API pour convertir du texte JSON en types .net
courants. JsonDocument expose une RootElement propriété.
Les sections suivantes montrent comment utiliser ces outils pour lire et écrire du code JSON.
Utiliser JsonDocument pour l’accès aux données
L’exemple suivant montre comment utiliser la JsonDocument classe pour l’accès aléatoire aux données d’une
chaîne JSON :

double sum = 0;
int count = 0;

using (JsonDocument document = JsonDocument.Parse(jsonString))


{
JsonElement root = document.RootElement;
JsonElement studentsElement = root.GetProperty("Students");
foreach (JsonElement student in studentsElement.EnumerateArray())
{
if (student.TryGetProperty("Grade", out JsonElement gradeElement))
{
sum += gradeElement.GetDouble();
}
else
{
sum += 70;
}
count++;
}
}

double average = sum / count;


Console.WriteLine($"Average grade : {average}");

Le code précédent :
Suppose que le JSON à analyser se trouve dans une chaîne nommée jsonString .
Calcule une qualité moyenne pour les objets d’un Students tableau qui ont une Grade propriété.
Affecte une catégorie par défaut de 70 pour les étudiants qui n’ont pas de qualité.
Compte les élèves en incrémentant une count variable à chaque itération. Une alternative consiste à
appeler GetArrayLength , comme illustré dans l’exemple suivant :
double sum = 0;
int count = 0;

using (JsonDocument document = JsonDocument.Parse(jsonString))


{
JsonElement root = document.RootElement;
JsonElement studentsElement = root.GetProperty("Students");

count = studentsElement.GetArrayLength();

foreach (JsonElement student in studentsElement.EnumerateArray())


{
if (student.TryGetProperty("Grade", out JsonElement gradeElement))
{
sum += gradeElement.GetDouble();
}
else
{
sum += 70;
}
}
}

double average = sum / count;


Console.WriteLine($"Average grade : {average}");

Voici un exemple du JSON traité par ce code :

{
"Class Name": "Science",
"Teacher\u0027s Name": "Jane",
"Semester": "2019-01-01",
"Students": [
{
"Name": "John",
"Grade": 94.3
},
{
"Name": "James",
"Grade": 81.0
},
{
"Name": "Julia",
"Grade": 91.9
},
{
"Name": "Jessica",
"Grade": 72.4
},
{
"Name": "Johnathan"
}
],
"Final": true
}

Utiliser JsonDocument pour écrire du code JSON


L’exemple suivant montre comment écrire du code JSON à partir d’un JsonDocument :
string jsonString = File.ReadAllText(inputFileName);

var writerOptions = new JsonWriterOptions


{
Indented = true
};

var documentOptions = new JsonDocumentOptions


{
CommentHandling = JsonCommentHandling.Skip
};

using FileStream fs = File.Create(outputFileName);


using var writer = new Utf8JsonWriter(fs, options: writerOptions);
using JsonDocument document = JsonDocument.Parse(jsonString, documentOptions);

JsonElement root = document.RootElement;

if (root.ValueKind == JsonValueKind.Object)
{
writer.WriteStartObject();
}
else
{
return;
}

foreach (JsonProperty property in root.EnumerateObject())


{
property.WriteTo(writer);
}

writer.WriteEndObject();

writer.Flush();

Le code précédent :
Lit un fichier JSON, charge les données dans un JsonDocument et écrit le format JSON (Pretty-imprimed) dans
un fichier.
Utilise JsonDocumentOptions pour spécifier que les commentaires dans le JSON d’entrée sont autorisés mais
ignorés.
Lorsque vous avez terminé, appelle Flush sur le writer. Une alternative consiste à laisser l’enregistreur se vider
lorsqu’il est supprimé.
Voici un exemple d’entrée JSON à traiter par l’exemple de code :

{"Class Name": "Science","Teacher's Name": "Jane","Semester": "2019-01-01","Students": [{"Name":


"John","Grade": 94.3},{"Name": "James","Grade": 81.0},{"Name": "Julia","Grade": 91.9},{"Name":
"Jessica","Grade": 72.4},{"Name": "Johnathan"}],"Final": true}

Le résultat est la sortie JSON imprimée suivante :


{
"Class Name": "Science",
"Teacher\u0027s Name": "Jane",
"Semester": "2019-01-01",
"Students": [
{
"Name": "John",
"Grade": 94.3
},
{
"Name": "James",
"Grade": 81.0
},
{
"Name": "Julia",
"Grade": 91.9
},
{
"Name": "Jessica",
"Grade": 72.4
},
{
"Name": "Johnathan"
}
],
"Final": true
}

Utiliser Utf8JsonWriter
L’exemple suivant montre comment utiliser la Utf8JsonWriter classe :

var options = new JsonWriterOptions


{
Indented = true
};

using (var stream = new MemoryStream())


{
using (var writer = new Utf8JsonWriter(stream, options))
{
writer.WriteStartObject();
writer.WriteString("date", DateTimeOffset.UtcNow);
writer.WriteNumber("temp", 42);
writer.WriteEndObject();
}

string json = Encoding.UTF8.GetString(stream.ToArray());


Console.WriteLine(json);
}

Utiliser Utf8JsonReader
L’exemple suivant montre comment utiliser la Utf8JsonReader classe :
var options = new JsonReaderOptions
{
AllowTrailingCommas = true,
CommentHandling = JsonCommentHandling.Skip
};
Utf8JsonReader reader = new Utf8JsonReader(jsonUtf8Bytes, options);

while (reader.Read())
{
Console.Write(reader.TokenType);

switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
case JsonTokenType.String:
{
string text = reader.GetString();
Console.Write(" ");
Console.Write(text);
break;
}

case JsonTokenType.Number:
{
int intValue = reader.GetInt32();
Console.Write(" ");
Console.Write(intValue);
break;
}

// Other token types elided for brevity


}
Console.WriteLine();
}

Le code précédent suppose que la jsonUtf8 variable est un tableau d’octets qui contient un JSON valide, encodé
au format UTF-8.
Filtrer les données à l’aide de Utf8JsonReader
L’exemple suivant montre comment lire un fichier de façon synchrone et rechercher une valeur :
using System;
using System.IO;
using System.Text;
using System.Text.Json;

namespace SystemTextJsonSamples
{
public class Utf8ReaderFromFile
{
private static readonly byte[] s_nameUtf8 = Encoding.UTF8.GetBytes("name");
private static ReadOnlySpan<byte> Utf8Bom => new byte[] { 0xEF, 0xBB, 0xBF };
public static void Run()
{
// ReadAllBytes if the file encoding is UTF-8:
string fileName = "UniversitiesUtf8.json";
ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);

// Read past the UTF-8 BOM bytes if a BOM exists.


if (jsonReadOnlySpan.StartsWith(Utf8Bom))
{
jsonReadOnlySpan = jsonReadOnlySpan.Slice(Utf8Bom.Length);
}

// Or read as UTF-16 and transcode to UTF-8 to convert to a ReadOnlySpan<byte>


//string fileName = "Universities.json";
//string jsonString = File.ReadAllText(fileName);
//ReadOnlySpan<byte> jsonReadOnlySpan = Encoding.UTF8.GetBytes(jsonString);

int count = 0;
int total = 0;

var reader = new Utf8JsonReader(jsonReadOnlySpan);

while (reader.Read())
{
JsonTokenType tokenType = reader.TokenType;

switch (tokenType)
{
case JsonTokenType.StartObject:
total++;
break;
case JsonTokenType.PropertyName:
if (reader.ValueTextEquals(s_nameUtf8))
{
// Assume valid JSON, known schema
reader.Read();
if (reader.GetString().EndsWith("University"))
{
count++;
}
}
break;
}
}
Console.WriteLine($"{count} out of {total} have names that end with 'University'");
}
}
}

Le code précédent :
Suppose que le JSON contient un tableau d’objets et que chaque objet peut contenir une propriété
« Name » de type chaîne.
Compte les objets et les valeurs de propriété « nom » qui se terminent par « University ».
Suppose que le fichier est encodé au format UTF-16 et le transcode en UTF-8. Un fichier encodé au format
UTF-8 peut être lu directement dans un ReadOnlySpan<byte> , à l’aide du code suivant :

ReadOnlySpan<byte> jsonReadOnlySpan = File.ReadAllBytes(fileName);

Si le fichier contient une marque d’ordre d’octet (BOM) UTF-8, supprimez-le avant de passer les octets au
Utf8JsonReader , puisque le lecteur attend du texte. Dans le cas contraire, la marque d’octet est considérée
comme JSON non valide et le lecteur lève une exception.
Voici un exemple JSON que le code précédent peut lire. Le message de synthèse obtenu est « 2 sur 4 ont des noms
qui se terminent par «University » :

[
{
"web_pages": [ "https://contoso.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "contoso.edu" ],
"name": "Contoso Community College"
},
{
"web_pages": [ "http://fabrikam.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "fabrikam.edu" ],
"name": "Fabrikam Community College"
},
{
"web_pages": [ "http://www.contosouniversity.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "contosouniversity.edu" ],
"name": "Contoso University"
},
{
"web_pages": [ "http://www.fabrikamuniversity.edu/" ],
"alpha_two_code": "US",
"state-province": null,
"country": "United States",
"domains": [ "fabrikamuniversity.edu" ],
"name": "Fabrikam University"
}
]

Lire à partir d’un flux à l’aide de Utf8JsonReader


Lors de la lecture d’un fichier volumineux (un gigaoctet ou plus en taille, par exemple), vous pouvez éviter d’avoir à
charger la totalité du fichier en mémoire en même temps. Pour ce scénario, vous pouvez utiliser un FileStream .
Lorsque vous utilisez le Utf8JsonReader pour lire un flux, les règles suivantes s’appliquent :
La mémoire tampon contenant la charge utile JSON partielle doit être au moins aussi grande que le plus grand
jeton JSON au sein de celle-ci afin que le lecteur puisse avancer la progression.
La mémoire tampon doit être au moins aussi grande que la plus grande séquence d’espaces blancs dans le
JSON.
Le lecteur n’effectue pas le suivi des données qu’il a lues jusqu’à ce qu’il Lise complètement le suivant
TokenType dans la charge utile JSON. Ainsi, lorsqu’il y a des octets restants dans la mémoire tampon, vous
devez les transmettre à nouveau au lecteur. Vous pouvez utiliser BytesConsumed pour déterminer le nombre
d’octets restants.
Le code suivant illustre comment lire à partir d’un flux. L’exemple montre un MemoryStream . Un code similaire
fonctionne avec un FileStream , sauf lorsque FileStream contient une nomenclature UTF-8 au début. Dans ce cas,
vous devez supprimer ces trois octets de la mémoire tampon avant de passer les octets restants à Utf8JsonReader
. Dans le cas contraire, le lecteur lèvera une exception, puisque la nomenclature n’est pas considérée comme une
partie valide du JSON.
L’exemple de code commence par une mémoire tampon de 4 Ko et double la taille de la mémoire tampon chaque
fois qu’il détecte que la taille n’est pas assez grande pour tenir un jeton JSON complet, ce qui est nécessaire pour
que le lecteur fasse progresser la progression sur la charge utile JSON. L’exemple JSON fourni dans l’extrait de
code déclenche une augmentation de la taille de la mémoire tampon uniquement si vous définissez une très petite
taille de mémoire tampon initiale, par exemple 10 octets. Si vous définissez la taille de la mémoire tampon initiale
sur 10, les Console.WriteLine instructions illustrent la cause et l’effet de la taille de la mémoire tampon augmente.
Au niveau de la taille de la mémoire tampon initiale de 4 Ko, la totalité de l’exemple JSON est affichée par chaque
Console.WriteLine , et la taille de la mémoire tampon ne doit jamais être augmentée.

using System;
using System.IO;
using System.Text;
using System.Text.Json;

namespace SystemTextJsonSamples
{
public class Utf8ReaderPartialRead
{
public static void Run()
{
var jsonString = @"{
""Date"": ""2019-08-01T00:00:00-07:00"",
""Temperature"": 25,
""TemperatureRanges"": {
""Cold"": { ""High"": 20, ""Low"": -10 },
""Hot"": { ""High"": 60, ""Low"": 20 }
},
""Summary"": ""Hot"",
}";

byte[] bytes = Encoding.UTF8.GetBytes(jsonString);


var stream = new MemoryStream(bytes);

var buffer = new byte[4096];

// Fill the buffer.


// For this snippet, we're assuming the stream is open and has data.
// If it might be closed or empty, check if the return value is 0.
stream.Read(buffer);

var reader = new Utf8JsonReader(buffer, isFinalBlock: false, state: default);


Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");

// Search for "Summary" property name


while (reader.TokenType != JsonTokenType.PropertyName || !reader.ValueTextEquals("Summary"))
{
if (!reader.Read())
{
// Not enough of the JSON is in the buffer to complete a read.
GetMoreBytesFromStream(stream, ref buffer, ref reader);
}
}

// Found the "Summary" property name.


// Found the "Summary" property name.
Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");
while (!reader.Read())
{
// Not enough of the JSON is in the buffer to complete a read.
GetMoreBytesFromStream(stream, ref buffer, ref reader);
}
// Display value of Summary property, that is, "Hot".
Console.WriteLine($"Got property value: {reader.GetString()}");
}

private static void GetMoreBytesFromStream(MemoryStream stream, ref byte[] buffer, ref Utf8JsonReader
reader)
{
int bytesRead;
if (reader.BytesConsumed < buffer.Length)
{
ReadOnlySpan<byte> leftover = buffer.AsSpan((int)reader.BytesConsumed);

if (leftover.Length == buffer.Length)
{
Array.Resize(ref buffer, buffer.Length * 2);
Console.WriteLine($"Increased buffer size to {buffer.Length}");
}

leftover.CopyTo(buffer);
bytesRead = stream.Read(buffer.AsSpan(leftover.Length));
}
else
{
bytesRead = stream.Read(buffer);
}
Console.WriteLine($"String in buffer is: {Encoding.UTF8.GetString(buffer)}");
reader = new Utf8JsonReader(buffer, isFinalBlock: bytesRead == 0, reader.CurrentState);
}
}
}

L’exemple précédent n’affecte aucune limite à la taille maximale de la mémoire tampon. Si la taille du jeton est trop
grande, le code peut échouer avec une OutOfMemoryException exception. Cela peut se produire si le JSON
contient un jeton d’une taille d’environ 1 Go, car le doublement de la taille de 1 Go donne une taille trop
importante pour tenir dans une int32 mémoire tampon.

Ressources supplémentaires
System.Text.Jsonvue
Guide pratique pour écrire des convertisseurs personnalisés
Migration à partir deNewtonsoft.Json
Prise en charge des valeurs DateTime et DateTimeOffset dansSystem.Text.Json
System.Text.JsonRéférence d’API
Comment écrire des convertisseurs personnalisés
pour la sérialisation JSON (marshaling) dans .NET
18/07/2020 • 28 minutes to read • Edit Online

Cet article explique comment créer des convertisseurs personnalisés pour les classes de sérialisation JSON
fournies dans l’espace de System.Text.Json noms. Pour une introduction à System.Text.Json , consultez comment
sérialiser et désérialiser JSON dans .net.
Un convertisseur est une classe qui convertit un objet ou une valeur vers et à partir de JSON. L'
System.Text.Json espace de noms contient des convertisseurs intégrés pour la plupart des types primitifs qui
mappent aux primitives JavaScript. Vous pouvez écrire des convertisseurs personnalisés :
Pour remplacer le comportement par défaut d’un convertisseur intégré. Par exemple, vous pouvez souhaiter
que DateTime les valeurs soient représentées par le format mm/jj/aaaa au lieu du format ISO 8601-1:2019
par défaut.
Pour prendre en charge un type valeur personnalisé. Par exemple, un PhoneNumber struct.
Vous pouvez également écrire des convertisseurs personnalisés pour personnaliser ou étendre
System.Text.Json des fonctionnalités qui ne sont pas incluses dans la version actuelle. Les scénarios suivants
sont abordés plus loin dans cet article :
Désérialiser les types inférés en propriétés d’objet.
Dictionnaire de prise en charge avec clé non-chaîne.
Prendre en charge la désérialisation polymorphe.
Prenez en charge l’aller- <T> retour pour la pile.

Modèles de convertisseurs personnalisés


Il existe deux modèles pour créer un convertisseur personnalisé : le modèle de base et le modèle de fabrique. Le
modèle de fabrique est destiné aux convertisseurs qui gèrent le type Enum ou les génériques ouverts. Le modèle
de base concerne les types génériques non génériques et fermés. Par exemple, les convertisseurs pour les types
suivants requièrent le modèle de fabrique :
Dictionary<TKey, TValue>
Enum
List<T>

Voici quelques exemples de types qui peuvent être gérés par le modèle de base :
Dictionary<int, string>
WeekdaysEnum
List<DateTimeOffset>
DateTime
Int32

Le modèle de base crée une classe qui peut gérer un type. Le modèle de fabrique crée une classe qui détermine,
au moment de l’exécution, le type spécifique requis et crée dynamiquement le convertisseur approprié.

Exemple de convertisseur de base


L’exemple suivant est un convertisseur qui remplace la sérialisation par défaut d’un type de données existant. Le
convertisseur utilise le format mm/jj/aaaa pour les DateTimeOffset Propriétés.

using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class DateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
public override DateTimeOffset Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) =>
DateTimeOffset.ParseExact(reader.GetString(),
"MM/dd/yyyy", CultureInfo.InvariantCulture);

public override void Write(


Utf8JsonWriter writer,
DateTimeOffset dateTimeValue,
JsonSerializerOptions options) =>
writer.WriteStringValue(dateTimeValue.ToString(
"MM/dd/yyyy", CultureInfo.InvariantCulture));
}
}

Exemple de convertisseur de modèle de fabrique


Le code suivant illustre un convertisseur personnalisé qui fonctionne avec Dictionary<Enum,TValue> . Le code
suit le modèle de fabrique, car le premier paramètre de type générique est Enum et le second est ouvert. La
CanConvert méthode retourne true uniquement pour un Dictionary avec deux paramètres génériques, le
premier étant un Enum type. Le convertisseur interne obtient un convertisseur existant pour gérer le type fourni
au moment de l’exécution pour TValue .

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class DictionaryTKeyEnumTValueConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
if (!typeToConvert.IsGenericType)
{
return false;
}

if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>))
{
return false;
}

return typeToConvert.GetGenericArguments()[0].IsEnum;
}

public override JsonConverter CreateConverter(


Type type,
JsonSerializerOptions options)
{
Type keyType = type.GetGenericArguments()[0];
Type valueType = type.GetGenericArguments()[1];

JsonConverter converter = (JsonConverter)Activator.CreateInstance(


typeof(DictionaryEnumConverterInner<,>).MakeGenericType(
new Type[] { keyType, valueType }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { options },
culture: null);

return converter;
}

private class DictionaryEnumConverterInner<TKey, TValue> :


JsonConverter<Dictionary<TKey, TValue>> where TKey : struct, Enum
{
private readonly JsonConverter<TValue> _valueConverter;
private Type _keyType;
private Type _valueType;

public DictionaryEnumConverterInner(JsonSerializerOptions options)


{
// For performance, use the existing converter if available.
_valueConverter = (JsonConverter<TValue>)options
.GetConverter(typeof(TValue));

// Cache the key and value types.


_keyType = typeof(TKey);
_valueType = typeof(TValue);
}

public override Dictionary<TKey, TValue> Read(


ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}

Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return dictionary;
}

// Get the key.


if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

string propertyName = reader.GetString();

// For performance, parse with ignoreCase:false first.


if (!Enum.TryParse(propertyName, ignoreCase: false, out TKey key) &&
!Enum.TryParse(propertyName, ignoreCase: true, out key))
{
throw new JsonException(
$"Unable to convert \"{propertyName}\" to Enum \"{_keyType}\".");
}
// Get the value.
TValue v;
if (_valueConverter != null)
{
reader.Read();
v = _valueConverter.Read(ref reader, _valueType, options);
}
else
{
v = JsonSerializer.Deserialize<TValue>(ref reader, options);
}

// Add to dictionary.
dictionary.Add(key, v);
}

throw new JsonException();


}

public override void Write(


Utf8JsonWriter writer,
Dictionary<TKey, TValue> dictionary,
JsonSerializerOptions options)
{
writer.WriteStartObject();

foreach (KeyValuePair<TKey, TValue> kvp in dictionary)


{
writer.WritePropertyName(kvp.Key.ToString());

if (_valueConverter != null)
{
_valueConverter.Write(writer, kvp.Value, options);
}
else
{
JsonSerializer.Serialize(writer, kvp.Value, options);
}
}

writer.WriteEndObject();
}
}
}
}

Le code précédent est identique à ce qui est affiché dans le dictionnaire de prise en charge avec une clé non-
chaîne plus loin dans cet article.

Étapes pour suivre le modèle de base


Les étapes suivantes expliquent comment créer un convertisseur en suivant le modèle de base :
Créez une classe qui dérive de JsonConverter<T> où T est le type à sérialiser et à désérialiser.
Substituez la Read méthode pour désérialiser le JSON entrant et le convertir en type T . Utilisez le
Utf8JsonReader passé à la méthode pour lire le JSON.
Substituez la Write méthode pour sérialiser l’objet entrant de type T . Utilisez le Utf8JsonWriter passé à la
méthode pour écrire le JSON.
Substituez la CanConvert méthode uniquement si nécessaire. L’implémentation par défaut retourne true
lorsque le type à convertir est de type T . Par conséquent, les convertisseurs qui prennent en charge
uniquement T le type n’ont pas besoin de substituer cette méthode. Pour obtenir un exemple de
convertisseur qui doit substituer cette méthode, consultez la section sur la désérialisation polymorphe plus
loin dans cet article.
Vous pouvez faire référence au code source des convertisseurs intégrés en tant qu’implémentations de référence
pour l’écriture de convertisseurs personnalisés.

Étapes pour suivre le modèle de fabrique


Les étapes suivantes expliquent comment créer un convertisseur en suivant le modèle de fabrique :
Créez une classe qui dérive de JsonConverterFactory.
Substituez la CanConvert méthode pour retourner la valeur true lorsque le type à convertir est un qui peut
être géré par le convertisseur. Par exemple, si le convertisseur est destiné à List<T> lui, il peut uniquement
gérer List<int> , List<string> et List<DateTime> .
Substituez la CreateConverter méthode pour retourner une instance d’une classe de convertisseur qui gérera
le type-conversion fourni au moment de l’exécution.
Créez la classe de convertisseur CreateConverter instanciée par la méthode.

Le modèle de fabrique est requis pour les génériques ouverts, car le code permettant de convertir un objet vers
et à partir d’une chaîne n’est pas le même pour tous les types. Un convertisseur pour un type générique ouvert (
List<T> , par exemple) doit créer un convertisseur pour un type générique fermé ( List<DateTime> , par
exemple) en arrière-plan. Le code doit être écrit pour gérer chaque type de générique fermé que le convertisseur
peut gérer.
Le Enum type est similaire à un type générique ouvert : un convertisseur pour Enum doit créer un convertisseur
pour un spécifique Enum ( WeekdaysEnum , par exemple) en arrière-plan.

Gestion des erreurs


Si vous devez lever une exception dans le code de gestion des erreurs, envisagez de lever un JsonException sans
message. Ce type d’exception crée automatiquement un message qui comprend le chemin d’accès à la partie du
JSON qui a provoqué l’erreur. Par exemple, l’instruction throw new JsonException(); génère un message d’erreur
comme dans l’exemple suivant :

Unhandled exception. System.Text.Json.JsonException:


The JSON value could not be converted to System.Object.
Path: $.Date | LineNumber: 1 | BytePositionInLine: 37.

Si vous fournissez un message (par exemple, throw new JsonException("Error occurred") , l’exception fournit
toujours le chemin d’accès dans la Path propriété.

Inscrire un convertisseur personnalisé


Inscrire un convertisseur personnalisé pour que les Serialize méthodes et l' Deserialize utilisent. Choisissez
l’une des approches suivantes :
Ajoutez une instance de la classe de convertisseur à la JsonSerializerOptions.Converters collection.
Appliquez l’attribut [JsonConverter] aux propriétés qui requièrent le convertisseur personnalisé.
Appliquez l’attribut [JsonConverter] à une classe ou un struct qui représente un type valeur personnalisé.

Exemple d’inscription-collection Converters


Voici un exemple qui rend la DateTimeOffsetConverter valeur par défaut pour les propriétés de type
DateTimeOffset :
var serializeOptions = new JsonSerializerOptions();
serializeOptions.Converters.Add(new DateTimeOffsetConverter());
serializeOptions.WriteIndented = true;
jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);

Supposons que vous sérialisez une instance du type suivant :

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Voici un exemple de sortie JSON qui montre que le convertisseur personnalisé a été utilisé :

{
"Date": "08/01/2019",
"TemperatureCelsius": 25,
"Summary": "Hot"
}

Le code suivant utilise la même approche pour désérialiser à l’aide du DateTimeOffset convertisseur
personnalisé :

var deserializeOptions = new JsonSerializerOptions();


deserializeOptions.Converters.Add(new DateTimeOffsetConverter());
weatherForecast = JsonSerializer.Deserialize<WeatherForecast>(jsonString, deserializeOptions);

Exemple d’inscription-[JsonConverter] sur une propriété


Le code suivant sélectionne un convertisseur personnalisé pour la Date propriété :

public class WeatherForecastWithConverterAttribute


{
[JsonConverter(typeof(DateTimeOffsetConverter))]
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Le code à sérialiser WeatherForecastWithConverterAttribute ne requiert pas l’utilisation de


JsonSerializeOptions.Converters :

var serializeOptions = new JsonSerializerOptions();


serializeOptions.WriteIndented = true;
jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);

Le code à désérialiser ne nécessite pas non plus l’utilisation de Converters :

weatherForecast = JsonSerializer.Deserialize<WeatherForecastWithConverterAttribute>(jsonString);
Exemple d’inscription-[JsonConverter] sur un type
Voici le code qui crée un struct et lui applique l' [JsonConverter] attribut :

using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
[JsonConverter(typeof(TemperatureConverter))]
public struct Temperature
{
public Temperature(int degrees, bool celsius)
{
_degrees = degrees;
_isCelsius = celsius;
}
private bool _isCelsius;
private int _degrees;
public int Degrees => _degrees;
public bool IsCelsius => _isCelsius;
public bool IsFahrenheit => !_isCelsius;
public override string ToString() =>
$"{_degrees.ToString()}{(_isCelsius ? "C" : "F")}";
public static Temperature Parse(string input)
{
int degrees = int.Parse(input.Substring(0, input.Length - 1));
bool celsius = (input.Substring(input.Length - 1) == "C");
return new Temperature(degrees, celsius);
}
}
}

Voici le convertisseur personnalisé pour le struct précédent :

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class TemperatureConverter : JsonConverter<Temperature>
{
public override Temperature Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) =>
Temperature.Parse(reader.GetString());

public override void Write(


Utf8JsonWriter writer,
Temperature temperature,
JsonSerializerOptions options) =>
writer.WriteStringValue(temperature.ToString());
}
}

L' [JsonConvert] attribut sur le struct inscrit le convertisseur personnalisé comme valeur par défaut pour les
propriétés de type Temperature . Le convertisseur est automatiquement utilisé sur la TemperatureCelsius
propriété du type suivant lors de la sérialisation ou de la désérialisation de ce dernier :
public class WeatherForecastWithTemperatureStruct
{
public DateTimeOffset Date { get; set; }
public Temperature TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Priorité d’inscription du convertisseur


Lors de la sérialisation ou de la désérialisation, un convertisseur est choisi pour chaque élément JSON dans
l’ordre suivant, de la priorité la plus élevée à la plus faible :
[JsonConverter] appliqué à une propriété.
Convertisseur ajouté à la Converters collection.
[JsonConverter] appliqué à un type valeur personnalisé ou POCO.

Si plusieurs convertisseurs personnalisés pour un type sont inscrits dans la Converters collection, le premier
convertisseur qui retourne la valeur true pour CanConvert est utilisé.
Un convertisseur intégré est choisi uniquement si aucun convertisseur personnalisé applicable n’est inscrit.

Exemples de convertisseurs pour les scénarios courants


Les sections suivantes fournissent des exemples de convertisseurs qui traitent de certains scénarios courants
que les fonctionnalités intégrées ne gèrent pas.
Désérialiser les types inférés en propriétés d’objet
Dictionnaire de prise en charge avec clé non-chaîne
Prendre en charge la désérialisation polymorphe
Prenez en charge l’aller- <T> retour pour la pile.
Désérialiser les types inférés en propriétés d’objet
Lors de la désérialisation vers une propriété de type object , un JsonElement objet est créé. Cela est dû au fait
que le désérialiseur ne sait pas quel type CLR créer, et qu’il ne tente pas de deviner. Par exemple, si une propriété
JSON a la valeur « true », le désérialiseur ne déduit pas que la valeur est un Boolean et, si un élément a
« 01/01/2019 », le désérialiseur ne déduit pas qu’il s’agit d’un DateTime .
L’inférence de type peut être inexacte. Si le désérialiseur analyse un nombre JSON qui n’a pas de virgule
décimale comme un long , cela peut entraîner des problèmes hors limites si la valeur a été sérialisée à l’origine
en tant que ulong ou BigInteger . L’analyse d’un nombre qui a une virgule décimale double peut perdre la
précision si le nombre a été initialement sérialisé en tant que decimal .
Pour les scénarios qui requièrent l’inférence de type, le code suivant montre un convertisseur personnalisé pour
les object Propriétés. Le code convertit :
true et false pour Boolean
Nombres sans décimal pour long
Nombres avec un nombre décimal à double
Dates jusqu’à DateTime
Chaînes à string
Tout le reste vers JsonElement
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class ObjectToInferredTypesConverter
: JsonConverter<object>
{
public override object Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.True)
{
return true;
}

if (reader.TokenType == JsonTokenType.False)
{
return false;
}

if (reader.TokenType == JsonTokenType.Number)
{
if (reader.TryGetInt64(out long l))
{
return l;
}

return reader.GetDouble();
}

if (reader.TokenType == JsonTokenType.String)
{
if (reader.TryGetDateTime(out DateTime datetime))
{
return datetime;
}

return reader.GetString();
}

// Use JsonElement as fallback.


// Newtonsoft uses JArray or JObject.
using JsonDocument document = JsonDocument.ParseValue(ref reader);
return document.RootElement.Clone();
}

public override void Write(


Utf8JsonWriter writer,
object objectToWrite,
JsonSerializerOptions options) =>
throw new InvalidOperationException("Should not get here.");
}
}

Le code suivant inscrit le convertisseur :

var deserializeOptions = new JsonSerializerOptions();


deserializeOptions.Converters.Add(new ObjectToInferredTypesConverter());

Voici un exemple de type avec des object Propriétés :


public class WeatherForecastWithObjectProperties
{
public object Date { get; set; }
public object TemperatureCelsius { get; set; }
public object Summary { get; set; }
}

L’exemple suivant de JSON à désérialiser contient des valeurs qui seront désérialisées en tant que DateTime ,
long et string :

{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
}

Sans le convertisseur personnalisé, la désérialisation place un JsonElement dans chaque propriété.


Le dossier tests unitaires de l' System.Text.Json.Serialization espace de noms contient plus d’exemples de
convertisseurs personnalisés qui gèrent la désérialisation des object Propriétés.
Dictionnaire de prise en charge avec clé non-chaîne
La prise en charge intégrée pour les collections de dictionnaires concerne Dictionary<string, TValue> .
Autrement dit, la clé doit être une chaîne. Pour prendre en charge un dictionnaire avec un entier ou un autre type
en tant que clé, un convertisseur personnalisé est requis.
Le code suivant illustre un convertisseur personnalisé qui fonctionne avec Dictionary<Enum,TValue> :

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class DictionaryTKeyEnumTValueConverter : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
if (!typeToConvert.IsGenericType)
{
return false;
}

if (typeToConvert.GetGenericTypeDefinition() != typeof(Dictionary<,>))
{
return false;
}

return typeToConvert.GetGenericArguments()[0].IsEnum;
}

public override JsonConverter CreateConverter(


Type type,
JsonSerializerOptions options)
{
Type keyType = type.GetGenericArguments()[0];
Type valueType = type.GetGenericArguments()[1];

JsonConverter converter = (JsonConverter)Activator.CreateInstance(


typeof(DictionaryEnumConverterInner<,>).MakeGenericType(
typeof(DictionaryEnumConverterInner<,>).MakeGenericType(
new Type[] { keyType, valueType }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { options },
culture: null);

return converter;
}

private class DictionaryEnumConverterInner<TKey, TValue> :


JsonConverter<Dictionary<TKey, TValue>> where TKey : struct, Enum
{
private readonly JsonConverter<TValue> _valueConverter;
private Type _keyType;
private Type _valueType;

public DictionaryEnumConverterInner(JsonSerializerOptions options)


{
// For performance, use the existing converter if available.
_valueConverter = (JsonConverter<TValue>)options
.GetConverter(typeof(TValue));

// Cache the key and value types.


_keyType = typeof(TKey);
_valueType = typeof(TValue);
}

public override Dictionary<TKey, TValue> Read(


ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}

Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return dictionary;
}

// Get the key.


if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

string propertyName = reader.GetString();

// For performance, parse with ignoreCase:false first.


if (!Enum.TryParse(propertyName, ignoreCase: false, out TKey key) &&
!Enum.TryParse(propertyName, ignoreCase: true, out key))
{
throw new JsonException(
$"Unable to convert \"{propertyName}\" to Enum \"{_keyType}\".");
}

// Get the value.


TValue v;
if (_valueConverter != null)
{
reader.Read();
v = _valueConverter.Read(ref reader, _valueType, options);
}
}
else
{
v = JsonSerializer.Deserialize<TValue>(ref reader, options);
}

// Add to dictionary.
dictionary.Add(key, v);
}

throw new JsonException();


}

public override void Write(


Utf8JsonWriter writer,
Dictionary<TKey, TValue> dictionary,
JsonSerializerOptions options)
{
writer.WriteStartObject();

foreach (KeyValuePair<TKey, TValue> kvp in dictionary)


{
writer.WritePropertyName(kvp.Key.ToString());

if (_valueConverter != null)
{
_valueConverter.Write(writer, kvp.Value, options);
}
else
{
JsonSerializer.Serialize(writer, kvp.Value, options);
}
}

writer.WriteEndObject();
}
}
}
}

Le code suivant inscrit le convertisseur :

var serializeOptions = new JsonSerializerOptions();


serializeOptions.Converters.Add(new DictionaryTKeyEnumTValueConverter());

Le convertisseur peut sérialiser et désérialiser la TemperatureRanges propriété de la classe suivante qui utilise les
éléments suivants Enum :

public class WeatherForecastWithEnumDictionary


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
public Dictionary<SummaryWordsEnum, int> TemperatureRanges { get; set; }
}

public enum SummaryWordsEnum


{
Cold, Hot
}

La sortie JSON de la sérialisation ressemble à l’exemple suivant :


{
"Date": "2019-08-01T00:00:00-07:00",
"TemperatureCelsius": 25,
"Summary": "Hot",
"TemperatureRanges": {
"Cold": 20,
"Hot": 40
}
}

Le dossier tests unitaires de l' System.Text.Json.Serialization espace de noms contient des exemples de
convertisseurs personnalisés qui gèrent des dictionnaires non-clés.
Prendre en charge la désérialisation polymorphe
Les fonctionnalités intégrées offrent une plage limitée de sérialisation polymorphe , mais aucune prise en charge
de la désérialisation. La désérialisation requiert un convertisseur personnalisé.
Supposons, par exemple, que vous avez une Person classe de base abstraite, avec des Employee Customer
classes dérivées et. La désérialisation polymorphe signifie qu’au moment du design vous pouvez spécifier
Person en tant que cible de désérialisation, et les Customer Employee objets et dans le JSON sont correctement
désérialisés au moment de l’exécution. Pendant la désérialisation, vous devez trouver des indices qui identifient
le type requis dans le JSON. Les types d’indices disponibles varient en fonction de chaque scénario. Par exemple,
une propriété de discriminateur peut être disponible ou vous devrez peut-être vous appuyer sur la présence ou
l’absence d’une propriété particulière. La version actuelle de System.Text.Json ne fournit pas d’attributs pour
spécifier comment gérer les scénarios de désérialisation polymorphe, donc les convertisseurs personnalisés sont
requis.
Le code suivant illustre une classe de base, deux classes dérivées et un convertisseur personnalisé pour eux. Le
convertisseur utilise une propriété de discriminateur pour effectuer une désérialisation polymorphe. Le
discriminateur de type ne figure pas dans les définitions de classe, mais il est créé au cours de la sérialisation et
est lu pendant la désérialisation.

public class Person


{
public string Name { get; set; }
}

public class Customer : Person


{
public decimal CreditLimit { get; set; }
}

public class Employee : Person


{
public string OfficeNumber { get; set; }
}

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class PersonConverterWithTypeDiscriminator : JsonConverter<Person>
{
enum TypeDiscriminator
{
Customer = 1,
Employee = 2
}
}

public override bool CanConvert(Type typeToConvert) =>


typeof(Person).IsAssignableFrom(typeToConvert);

public override Person Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions


options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}

reader.Read();
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

string propertyName = reader.GetString();


if (propertyName != "TypeDiscriminator")
{
throw new JsonException();
}

reader.Read();
if (reader.TokenType != JsonTokenType.Number)
{
throw new JsonException();
}

TypeDiscriminator typeDiscriminator = (TypeDiscriminator)reader.GetInt32();


Person person = typeDiscriminator switch
{
TypeDiscriminator.Customer => new Customer(),
TypeDiscriminator.Employee => new Employee(),
_ => throw new JsonException()
};

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return person;
}

if (reader.TokenType == JsonTokenType.PropertyName)
{
propertyName = reader.GetString();
reader.Read();
switch (propertyName)
{
case "CreditLimit":
decimal creditLimit = reader.GetDecimal();
((Customer)person).CreditLimit = creditLimit;
break;
case "OfficeNumber":
string officeNumber = reader.GetString();
((Employee)person).OfficeNumber = officeNumber;
break;
case "Name":
string name = reader.GetString();
person.Name = name;
break;
}
}
}

throw new JsonException();


}

public override void Write(Utf8JsonWriter writer, Person person, JsonSerializerOptions options)


{
writer.WriteStartObject();

if (person is Customer customer)


{
writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.Customer);
writer.WriteNumber("CreditLimit", customer.CreditLimit);
}
else if (person is Employee employee)
{
writer.WriteNumber("TypeDiscriminator", (int)TypeDiscriminator.Employee);
writer.WriteString("OfficeNumber", employee.OfficeNumber);
}

writer.WriteString("Name", person.Name);

writer.WriteEndObject();
}
}
}

Le code suivant inscrit le convertisseur :

var serializeOptions = new JsonSerializerOptions();


serializeOptions.Converters.Add(new PersonConverterWithTypeDiscriminator());

Le convertisseur peut désérialiser JSON qui a été créé à l’aide du même convertisseur pour sérialiser, par
exemple :

[
{
"TypeDiscriminator": 1,
"CreditLimit": 10000,
"Name": "John"
},
{
"TypeDiscriminator": 2,
"OfficeNumber": "555-1234",
"Name": "Nancy"
}
]

Dans l’exemple précédent, le code de convertisseur lit et écrit chaque propriété manuellement. Une alternative
consiste à appeler Deserialize ou Serialize à effectuer une partie du travail. Pour obtenir un exemple,
consultez cette publication StackOverflow.
Prendre en charge l’aller-retour pour la pile<T>
Si vous désérialisez une chaîne JSON dans un Stack<T> objet et que vous sérialisez ensuite cet objet, le contenu
de la pile est dans l’ordre inverse. Ce comportement s’applique aux types et à l’interface suivants, ainsi qu’aux
types définis par l’utilisateur qui dérivent de ceux-ci :
Stack
Stack<T>
ConcurrentStack<T>
ImmutableStack<T>
IImmutableStack<T>
Pour prendre en charge la sérialisation et la désérialisation qui conservent l’ordre d’origine dans la pile, un
convertisseur personnalisé est requis.
Le code suivant illustre un convertisseur personnalisé qui permet l’aller-retour vers et à partir d' Stack<T>
objets :

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class JsonConverterFactoryForStackOfT : JsonConverterFactory
{
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert.IsGenericType &&
typeToConvert.GetGenericTypeDefinition() == typeof(Stack<>);
}

public override JsonConverter CreateConverter(


Type typeToConvert, JsonSerializerOptions options)
{
Debug.Assert(typeToConvert.IsGenericType &&
typeToConvert.GetGenericTypeDefinition() == typeof(Stack<>));

Type elementType = typeToConvert.GetGenericArguments()[0];

JsonConverter converter = (JsonConverter)Activator.CreateInstance(


typeof(JsonConverterForStackOfT<>)
.MakeGenericType(new Type[] { elementType }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: null,
culture: null)!;

return converter;
}
}

public class JsonConverterForStackOfT<T> : JsonConverter<Stack<T>>


{
public override Stack<T> Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartArray || !reader.Read())
{
throw new JsonException();
}

var elements = new Stack<T>();

while (reader.TokenType != JsonTokenType.EndArray)


{
elements.Push(JsonSerializer.Deserialize<T>(ref reader, options));

if (!reader.Read())
{
throw new JsonException();
}
}

return elements;
}
public override void Write(
Utf8JsonWriter writer, Stack<T> value, JsonSerializerOptions options)
{
writer.WriteStartArray();

var reversed = new Stack<T>(value);

foreach (T item in reversed)


{
JsonSerializer.Serialize(writer, item, options);
}

writer.WriteEndArray();
}
}
}

Le code suivant inscrit le convertisseur :

var options = new JsonSerializerOptions


{
Converters = { new JsonConverterFactoryForStackOfT() },
};

Autres exemples de convertisseurs personnalisés


L’article migrer de Newtonsoft.Json vers System.Text.Json contient des exemples supplémentaires de
convertisseurs personnalisés.
Le dossier tests unitaires du System.Text.Json.Serialization code source comprend d’autres exemples de
convertisseurs personnalisés, tels que :
Convertisseur Int32 qui convertit la valeur null en valeur 0 lors de la désérialisation
Convertisseur Int32 qui autorise les valeurs de chaîne et de nombre lors de la désérialisation
Convertisseur enum
<T>Convertisseur de liste qui accepte les données externes
Long [] convertisseur qui fonctionne avec une liste de nombres délimités par des virgules
Si vous devez créer un convertisseur qui modifie le comportement d’un convertisseur intégré existant, vous
pouvez faire en sorte que le code source du convertisseur existant serve de point de départ pour la
personnalisation.

Ressources supplémentaires
Code source pour les convertisseurs intégrés
Prise en charge des valeurs DateTime et DateTimeOffset dansSystem.Text.Json
System.Text.Jsonvue
Procédure d’utilisationSystem.Text.Json
Migration à partir deNewtonsoft.Json
System.Text.JsonRéférence d’API
System.Text.Json. Référence de l’API de sérialisation
Comment migrer de Newtonsoft.Json
versSystem.Text.Json
18/07/2020 • 63 minutes to read • Edit Online

Cet article explique comment migrer de Newtonsoft.Json vers System.Text.Json .


L' System.Text.Json espace de noms fournit des fonctionnalités pour sérialiser vers et désérialiser à partir de
JavaScript Object Notation (JSON). La System.Text.Json bibliothèque est incluse dans l’infrastructure partagée
.net Core 3,0 . Pour les autres frameworks cibles, installez le System.Text.Json package NuGet. Le package prend
en charge :
.NET Standard 2,0 et versions ultérieures
.NET Framework 4.7.2 et versions ultérieures
.NET Core 2,0, 2,1 et 2,2
System.Text.Json se concentre principalement sur les performances, la sécurité et la conformité aux normes. Il
présente certaines différences clés dans le comportement par défaut et n’a pas pour but d’avoir une parité des
fonctionnalités avec Newtonsoft.Json . Pour certains scénarios, System.Text.Json ne dispose pas de
fonctionnalités intégrées, mais il existe des solutions de contournement recommandées. Pour les autres
scénarios, les solutions de contournement ne sont pas pratiques. Si votre application dépend d’une
fonctionnalité manquante, envisagez de signaler un problème pour déterminer si la prise en charge de votre
scénario peut être ajoutée.
La majeure partie de cet article concerne l’utilisation de l' JsonSerializer API, mais elle fournit également des
conseils sur l’utilisation de JsonDocument (qui représente les types Document Object Model ou DOM),
Utf8JsonReader et Utf8JsonWriter .

Tableau des différences entre Newtonsoft.Json etSystem.Text.Json


Le tableau suivant répertorie les Newtonsoft.Json fonctionnalités et les System.Text.Json équivalents. Les
équivalents sont classés dans les catégories suivantes :
Pris en charge par les fonctionnalités intégrées. L’obtention d’un comportement similaire de
System.Text.Json peut nécessiter l’utilisation d’un attribut ou d’une option globale.
Non pris en charge, une solution de contournement est possible. Les solutions de contournement sont des
convertisseurs personnalisés, qui peuvent ne pas fournir une parité complète avec les Newtonsoft.Json
fonctionnalités. Pour certains d’entre eux, un exemple de code est fourni comme exemples. Si vous utilisez
ces Newtonsoft.Json fonctionnalités, la migration nécessitera des modifications de vos modèles d’objet .net
ou d’autres modifications de code.
Non pris en charge, la solution de contournement n’est pas pratique ou possible. Si vous utilisez ces
Newtonsoft.Json fonctionnalités, la migration n’est pas possible sans modification significative.

F O N C T IO N N A L IT É N EW TO N SO F T. JSO N SY ST EM . T EXT. JSO N IDEN T IQ UE

Désérialisation non sensible à la casse par défaut ️ paramètre global PropertyNameCaseInsensitive


Noms des propriétés de casse mixte ️ paramètre global PropertyNamingPolicy



F O N C T IO N N A L IT É N EW TO N SO F T. JSO N SY ST EM . T EXT. JSO N IDEN T IQ UE

Échappement de caractères minimal ✔ caractère strict de la séquence d’échappement,



configurable

NullValueHandling.Ignore paramètre global option globale ✔


️ IgnoreNullValues

Autoriser les commentaires ️ paramètre global ReadCommentHandling


Autoriser les virgules de fin ️ paramètre global AllowTrailingCommas


Inscription de convertisseur personnalisé ️ ordre de priorité différent


Aucune profondeur maximale par défaut ️ profondeur maximale par défaut 64, configurable

Prise en charge d’un large éventail de types ⚠Certains types nécessitent des convertisseurs

personnalisés

Désérialiser des chaînes sous forme de nombres ️ Non pris en charge, solution de contournement, exemple

Désérialiser Dictionary avec une clé qui n’est pas une ️ Non pris en charge, solution de contournement, exemple

chaîne

Sérialisation polymorphe ️ Non pris en charge, solution de contournement, exemple


Désérialisation polymorphe ️ Non pris en charge, solution de contournement, exemple


Désérialiser le type inféré en object Propriétés ️ Non pris en charge, solution de contournement, exemple

Désérialiser le null LITTÉRAL JSON en types valeur non ️ Non pris en charge, solution de contournement, exemple

Nullable

Désérialiser en classes et structs immuables ️ Non pris en charge, solution de contournement, exemple

Attribut [JsonConstructor] ️ Non pris en charge, solution de contournement, exemple


Required définir sur l' [JsonProperty] attribut ️ Non pris en charge, solution de contournement, exemple

NullValueHandling définir sur l' [JsonProperty] attribut ️ Non pris en charge, solution de contournement, exemple

DefaultValueHandling définir sur l' [JsonProperty] ️ Non pris en charge, solution de contournement, exemple

attribut

DefaultValueHandling paramètre global ️ Non pris en charge, solution de contournement, exemple


DefaultContractResolver pour exclure des propriétés ️ Non pris en charge, solution de contournement, exemple

DateTimeZoneHandling , DateFormatString paramètres ️ Non pris en charge, solution de contournement, exemple


Rappels ️ Non pris en charge, solution de contournement, exemple


Prise en charge des champs publics et non publics ️ Non pris en charge, solution de contournement

F O N C T IO N N A L IT É N EW TO N SO F T. JSO N SY ST EM . T EXT. JSO N IDEN T IQ UE

Prise en charge des accesseurs set et des accesseurs get de ️ Non pris en charge, solution de contournement

propriété internes et privées

Méthode JsonConvert.PopulateObject ️ Non pris en charge, solution de contournement


ObjectCreationHandling paramètre global ️ Non pris en charge, solution de contournement


Ajouter aux collections sans Setters ️ Non pris en charge, solution de contournement

PreserveReferencesHandling paramètre global ❌Non pris en charge

ReferenceLoopHandling paramètre global ❌Non pris en charge

Prise en charge des System.Runtime.Serialization ❌Non pris en charge


attributs

MissingMemberHandling paramètre global ❌Non pris en charge

Autoriser les noms de propriété sans guillemets ❌Non pris en charge

Autoriser les guillemets simples autour des valeurs de chaîne ❌Non pris en charge

Autoriser les valeurs non-chaîne JSON pour les propriétés de ❌Non pris en charge
chaîne

Il ne s’agit pas d’une liste exhaustive des Newtonsoft.Json fonctionnalités. La liste comprend un grand nombre
des scénarios qui ont été demandés dans les problèmes GitHub ou les publications StackOverflow . Si vous
implémentez une solution de contournement pour l’un des scénarios répertoriés ici qui n’a pas d’exemple de
code, et si vous souhaitez partager votre solution, sélectionnez cette page dans la section Commentaires en
bas de cette page. Cela crée un problème dans le référentiel GitHub de cette documentation et le répertorie
également dans la section Commentaires sur cette page.

Différences dans le comportement de JsonSerializer par défaut par


rapport àNewtonsoft.Json
System.Text.Jsonest strict par défaut et évite toute estimation ou interprétation au nom de l’appelant, en mettant
l’accent sur le comportement déterministe. La bibliothèque est intentionnellement conçue de cette façon pour
les performances et la sécurité. Newtonsoft.Json est flexible par défaut. Cette différence fondamentale en
matière de conception repose sur la plupart des différences spécifiques suivantes dans le comportement par
défaut.
Désérialisation non sensible à la casse
Pendant la désérialisation, Newtonsoft.Json ne respecte pas la casse par défaut. La System.Text.Json valeur par
défaut est sensible à la casse, ce qui offre de meilleures performances, car elle fait une correspondance exacte.
Pour plus d’informations sur la façon d’effectuer une correspondance qui ne respecte pas la casse, consultez
correspondance de propriéténe respectant pas la casse.
Si vous utilisez System.Text.Json indirectement à l’aide de ASP.net Core, vous n’avez rien à faire pour avoir un
comportement similaire Newtonsoft.Json . ASP.NET Core spécifie les paramètres pour les noms de propriété en
casse mixte et la correspondance ne respectant pas la casse lorsqu’il utilise System.Text.Json .
Échappement de caractères minimal
Pendant la sérialisation, Newtonsoft.Json est relativement permissif en ce qui concerne les caractères sans les
placer dans une séquence d’échappement. Autrement dit, il ne les remplace pas par \uxxxx où xxxx est le
point de code du caractère. Lorsqu’il les échappe, il émet un \ avant le caractère (par exemple, " devient \"
). System.Text.Jsonéchappe les caractères par défaut pour fournir des protections en profondeur contre les
attaques de script entre sites (XSS) ou de divulgation d’informations, et utilise la séquence de six caractères.
System.Text.Json échappe par défaut tous les caractères non-ASCII. vous n’avez donc pas besoin de faire quoi
que ce soit si vous utilisez StringEscapeHandling.EscapeNonAscii dans Newtonsoft.Json . System.Text.Json
échappe également les caractères HTML, par défaut. Pour plus d’informations sur la façon de remplacer le
comportement par défaut System.Text.Json , consultez personnaliser l’encodage des caractères.
Commentaires
Pendant la désérialisation, Newtonsoft.Json ignore par défaut les commentaires dans le JSON. La
System.Text.Json valeur par défaut consiste à lever des exceptions pour les commentaires, car la spécification
RFC 8259 ne les inclut pas. Pour plus d’informations sur l’autorisation des commentaires, consultez autoriser les
commentaires et les virgules de fin.
Virgules de fin
Pendant la désérialisation, Newtonsoft.Json ignore les virgules de fin par défaut. Elle ignore également plusieurs
virgules de fin (par exemple, [{"Color":"Red"},{"Color":"Green"},,] ). La System.Text.Json valeur par défaut
consiste à lever des exceptions pour les virgules de fin, car la spécification RFC 8259 ne les autorise pas. Pour
plus d’informations sur la façon de System.Text.Json les accepter, consultez autoriser les commentaires et les
virgules de fin. Il n’existe aucun moyen d’autoriser plusieurs virgules de fin.
Priorité d’inscription du convertisseur
La Newtonsoft.Json priorité d’inscription pour les convertisseurs personnalisés est la suivante :
Attribut sur la propriété
Attribut sur le type
Converters , collection
Dans cet ordre, un convertisseur personnalisé dans la Converters collection est substitué par un convertisseur
qui est inscrit en appliquant un attribut au niveau du type. Ces deux inscriptions sont remplacées par un attribut
au niveau de la propriété.
La System.Text.Json priorité d’inscription pour les convertisseurs personnalisés est différente :
Attribut sur la propriété
Converterscollecte
Attribut sur le type
La différence réside dans le fait qu’un convertisseur personnalisé dans la Converters collection remplace un
attribut au niveau du type. L’objectif derrière cet ordre de priorité est de faire en sorte que les modifications au
moment de l’exécution remplacent les choix au moment de la conception. Il n’existe aucun moyen de modifier la
précédence.
Pour plus d’informations sur l’inscription d’un convertisseur personnalisé, consultez inscrire un convertisseur
personnalisé.
Profondeur maximale
Newtonsoft.Json n’a pas de limite de profondeur maximale par défaut. Pour System.Text.Json une limite par
défaut de 64, elle peut être configurée en définissant JsonSerializerOptions.MaxDepth .
Chaînes JSON (noms de propriété et valeurs de chaîne )
Pendant la désérialisation, Newtonsoft.Json accepte les noms de propriété placés entre guillemets doubles,
apostrophes ou sans guillemets. Il accepte les valeurs de chaîne entourées de guillemets doubles ou de
guillemets simples. Par exemple, Newtonsoft.Json accepte le code JSON suivant :

{
"name1": "value",
'name2': "value",
name3: 'value'
}

System.Text.Json accepte uniquement les noms de propriété et les valeurs de chaîne dans des guillemets
doubles, car ce format est requis par la spécification RFC 8259 et est le seul format considéré comme un JSON
valide.
Une valeur placée entre guillemets simples donne un JsonException avec le message suivant :

''' is an invalid start of a value.

Valeurs qui ne sont pas des chaînes pour les propriétés de chaîne
Newtonsoft.Json accepte les valeurs qui ne sont pas des chaînes, telles qu’un nombre ou les littéraux true et
false , pour la désérialisation des propriétés de type chaîne. Voici un exemple de JSON qui Newtonsoft.Json
désérialise avec succès la classe suivante :

{
"String1": 1,
"String2": true,
"String3": false
}

public class ExampleClass


{
public string String1 { get; set; }
public string String2 { get; set; }
public string String3 { get; set; }
}

System.Text.Json ne désérialise pas les valeurs qui ne sont pas des chaînes dans les propriétés de chaîne. Une
valeur qui n’est pas une chaîne reçue pour un champ de chaîne génère un JsonException avec le message
suivant :

The JSON value could not be converted to System.String.

Scénarios utilisant des JsonSerializer qui requièrent des solutions de


contournement
Les scénarios suivants ne sont pas pris en charge par les fonctionnalités intégrées, mais les solutions de
contournement sont possibles. Les solutions de contournement sont des convertisseurs personnalisés, qui
peuvent ne pas fournir une parité complète avec les Newtonsoft.Json fonctionnalités. Pour certains d’entre eux,
un exemple de code est fourni comme exemples. Si vous utilisez ces Newtonsoft.Json fonctionnalités, la
migration nécessitera des modifications de vos modèles d’objet .net ou d’autres modifications de code.
Types sans prise en charge intégrée
System.Text.Jsonne fournit pas de prise en charge intégrée pour les types suivants :
DataTableet types associés
Types F #, tels que les unions discriminées, les types d’enregistrementset les types d' enregistrements
anonymes.
ExpandoObject
TimeZoneInfo
BigInteger
TimeSpan
DBNull
Type
ValueTupleet ses types génériques associés
Les convertisseurs personnalisés peuvent être implémentés pour les types qui n’ont pas de prise en charge
intégrée.
Nombres entre guillemets
Newtonsoft.Json peut sérialiser ou désérialiser des nombres représentés par des chaînes JSON (entourées de
guillemets). Par exemple, il peut accepter : {"DegreesCelsius":"23"} au lieu de {"DegreesCelsius":23} . Pour
activer ce comportement dans System.Text.Json , implémentez un convertisseur personnalisé comme dans
l’exemple suivant. Le convertisseur gère les propriétés définies comme long suit :
Il les sérialise en tant que chaînes JSON.
Il accepte les nombres et nombres JSON dans les guillemets lors de la désérialisation.

using System;
using System.Buffers;
using System.Buffers.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class LongToStringConverter : JsonConverter<long>
{
public override long Read(ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
ReadOnlySpan<byte> span = reader.HasValueSequence ? reader.ValueSequence.ToArray() :
reader.ValueSpan;
if (Utf8Parser.TryParse(span, out long number, out int bytesConsumed) && span.Length ==
bytesConsumed)
return number;

if (Int64.TryParse(reader.GetString(), out number))


return number;
}

return reader.GetInt64();
}

public override void Write(Utf8JsonWriter writer, long longValue, JsonSerializerOptions options)


{
writer.WriteStringValue(longValue.ToString());
}
}
}
Inscrivez ce convertisseur personnalisé à l’aide d’un attribut sur des long propriétés individuelles ou en
ajoutant le convertisseur à la Converters collection.
Dictionnaire avec clé non-chaîne
Newtonsoft.Json prend en charge les collections de type Dictionary<TKey, TValue> . La prise en charge intégrée
pour les collections de dictionnaires dans System.Text.Json est limitée à Dictionary<string, TValue> . Autrement
dit, la clé doit être une chaîne.
Pour prendre en charge un dictionnaire avec un entier ou un autre type en tant que clé, créez un convertisseur
comme l’exemple dans Comment écrire des convertisseurs personnalisés.
Sérialisation polymorphe
Newtonsoft.Json effectue automatiquement la sérialisation polymorphe. Pour plus d’informations sur les
fonctionnalités de sérialisation polymorphes limitées de System.Text.Json , consultez sérialisation des propriétés
de classes dérivées.
La solution de contournement décrite ici permet de définir des propriétés qui peuvent contenir des classes
dérivées comme type object . Si cela n’est pas possible, une autre option consiste à créer un convertisseur avec
une Write méthode pour l’ensemble de la hiérarchie des types d’héritage, comme dans l’exemple de la rubrique
Comment écrire des convertisseurs personnalisés.
Désérialisation polymorphe
Newtonsoft.Json a un TypeNameHandling paramètre qui ajoute des métadonnées de nom de type au JSON lors
de la sérialisation. Elle utilise les métadonnées lors de la désérialisation pour effectuer une désérialisation
polymorphe. System.Text.Jsonpeut effectuer une plage limitée de sérialisation polymorphe , mais pas de
désérialisation polymorphe.
Pour prendre en charge la désérialisation polymorphe, créez un convertisseur comme l’exemple dans Comment
écrire des convertisseurs personnalisés.
Désérialisation des propriétés de l’objet
Lors Newtonsoft.Json de la désérialisation vers Object , il :
Déduit le type de valeurs primitives dans la charge utile JSON (autre que null ) et retourne le stocké
string , long ,, double boolean ou DateTime en tant qu’objet boxed. Les valeurs primitives sont des
valeurs JSON uniques, telles qu’un nombre JSON, une chaîne,, true false ou null .
Retourne JObject ou JArray pour les valeurs complexes dans la charge utile JSON. Les valeurs complexes
sont des collections de paires clé-valeur JSON entre accolades ( {} ) ou des listes de valeurs entre crochets (
[] ). Les propriétés et les valeurs entre accolades ou crochets peuvent avoir des propriétés ou des valeurs
supplémentaires.
Retourne une référence Null lorsque la charge utile a le null littéral JSON.

System.Text.Jsonstocke un boxed JsonElement pour les valeurs primitives et complexes lors de la désérialisation
de en Object , par exemple :
Propriété object .
object Valeur de dictionnaire.
object Valeur de tableau.
Racine object .

Toutefois, System.Text.Json traite de null la même façon que Newtonsoft.Json et retourne une référence Null
lorsque la charge utile contient le null littéral JSON.
Pour implémenter l’inférence de type pour les object Propriétés, créez un convertisseur comme l’exemple dans
Comment écrire des convertisseurs personnalisés.
Désérialiser la valeur null en type non Nullable
Newtonsoft.Json ne lève pas d’exception dans le scénario suivant :

NullValueHandling a la valeur Ignore , et


Pendant la désérialisation, le JSON contient une valeur null pour un type valeur qui n’autorise pas les valeurs
NULL.
Dans le même scénario, System.Text.Json lève une exception. (Le paramètre de gestion null correspondant est
JsonSerializerOptions.IgnoreNullValues .)
Si vous êtes propriétaire du type de cible, la meilleure solution consiste à rendre la propriété en question
Nullable (par exemple, remplacer par int int? ).
Une autre solution consiste à créer un convertisseur pour le type, comme dans l’exemple suivant qui gère les
valeurs NULL pour les DateTimeOffset types :

using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class DateTimeOffsetNullHandlingConverter : JsonConverter<DateTimeOffset>

{
public override DateTimeOffset Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
return default;
}
return reader.GetDateTimeOffset();
}

public override void Write(


Utf8JsonWriter writer,
DateTimeOffset dateTimeValue,
JsonSerializerOptions options)
{
writer.WriteStringValue(dateTimeValue);
}
}
}

Inscrivez ce convertisseur personnalisé à l' aide d’un attribut sur la propriété ou en ajoutant le convertisseur à la
Converters collection.
Remarque : Le convertisseur précédent gère les valeurs null de la même façon que Newtonsoft.Json pour
les poco qui spécifient des valeurs par défaut. Par exemple, supposons que le code suivant représente votre
objet cible :
public class WeatherForecastWithDefault
{
public WeatherForecastWithDefault()
{
Date = DateTimeOffset.Parse("2001-01-01");
Summary = "No summary";
}
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Et supposons que le code JSON suivant est désérialisé à l’aide du convertisseur précédent :

{
"Date": null,
"TemperatureCelsius": 25,
"Summary": null
}

Après la désérialisation, la Date propriété a 1/1/0001 ( default(DateTimeOffset) ), autrement dit, la valeur


définie dans le constructeur est remplacée. Étant donné les mêmes POCO et JSON, la Newtonsoft.Json
désérialisation laisse 1/1/2001 dans la Date propriété.
Désérialiser en classes et structs immuables
Newtonsoft.Json peut désérialiser des classes et des structs immuables, car il peut utiliser des constructeurs qui
ont des paramètres. System.Text.Jsonprend en charge uniquement les constructeurs sans paramètre public. En
guise de solution de contournement, vous pouvez appeler un constructeur avec des paramètres dans un
convertisseur personnalisé.
Voici un struct immuable avec plusieurs paramètres de constructeur :

public readonly struct ImmutablePoint


{
public ImmutablePoint(int x, int y)
{
X = x;
Y = y;
}

public int X { get; }


public int Y { get; }
}

Et voici un convertisseur qui sérialise et désérialise ce struct :

using System;
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class ImmutablePointConverter : JsonConverter<ImmutablePoint>
{
private readonly JsonEncodedText XName = JsonEncodedText.Encode("X");
private readonly JsonEncodedText YName = JsonEncodedText.Encode("Y");

private readonly JsonConverter<int> _intConverter;


public ImmutablePointConverter(JsonSerializerOptions options)
{
if (options?.GetConverter(typeof(int)) is JsonConverter<int> intConverter)
{
_intConverter = intConverter;
}
else
{
throw new InvalidOperationException();
}
}

public override ImmutablePoint Read(


ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
};

int x = default;
bool xSet = false;

int y = default;
bool ySet = false;

// Get the first property.


reader.Read();
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

if (reader.ValueTextEquals(XName.EncodedUtf8Bytes))
{
x = ReadProperty(ref reader, options);
xSet = true;
}
else if (reader.ValueTextEquals(YName.EncodedUtf8Bytes))
{
y = ReadProperty(ref reader, options);
ySet = true;
}
else
{
throw new JsonException();
}

// Get the second property.


reader.Read();
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}

if (xSet && reader.ValueTextEquals(YName.EncodedUtf8Bytes))


{
y = ReadProperty(ref reader, options);
}
else if (ySet && reader.ValueTextEquals(XName.EncodedUtf8Bytes))
{
x = ReadProperty(ref reader, options);
}
else
{
throw new JsonException();
}
}

reader.Read();

if (reader.TokenType != JsonTokenType.EndObject)
{
throw new JsonException();
}

return new ImmutablePoint(x, y);


}

private int ReadProperty(ref Utf8JsonReader reader, JsonSerializerOptions options)


{
Debug.Assert(reader.TokenType == JsonTokenType.PropertyName);

reader.Read();
return _intConverter.Read(ref reader, typeof(int), options);
}

private void WriteProperty(Utf8JsonWriter writer, JsonEncodedText name, int intValue,


JsonSerializerOptions options)
{
writer.WritePropertyName(name);
_intConverter.Write(writer, intValue, options);
}

public override void Write(


Utf8JsonWriter writer,
ImmutablePoint point,
JsonSerializerOptions options)
{
writer.WriteStartObject();
WriteProperty(writer, XName, point.X, options);
WriteProperty(writer, YName, point.Y, options);
writer.WriteEndObject();
}
}
}

Inscrivez ce convertisseur personnalisé en ajoutant le convertisseur à la Converters collection.


Pour obtenir un exemple de convertisseur similaire qui gère les propriétés génériques ouvertes, consultez le
convertisseur intégré pour les paires clé-valeur.
Spécifier le constructeur à utiliser
L' Newtonsoft.Json [JsonConstructor] attribut vous permet de spécifier le constructeur à appeler lors de la
désérialisation vers un poco. System.Text.Jsonprend en charge uniquement les constructeurs sans paramètre. En
guise de solution de contournement, vous pouvez appeler le constructeur dont vous avez besoin dans un
convertisseur personnalisé. Consultez l’exemple pour désérialiser des classes et des structs immuables.
Propriétés requises
Dans Newtonsoft.Json , vous spécifiez qu’une propriété est requise en définissant Required sur l'
[JsonProperty] attribut. Newtonsoft.Json lève une exception si aucune valeur n’est reçue dans le JSON pour une
propriété marquée comme obligatoire.
System.Text.Jsonne lève pas d’exception si aucune valeur n’est reçue pour l’une des propriétés du type cible. Par
exemple, si vous avez une WeatherForecast classe :
public class WeatherForecast
{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

Le code JSON suivant est désérialisé sans erreur :

{
"TemperatureCelsius": 25,
"Summary": "Hot"
}

Pour faire échouer la désérialisation si aucune Date propriété n’est dans le JSON, implémentez un
convertisseur personnalisé. L’exemple de code de convertisseur suivant lève une exception si la Date propriété
n’est pas définie une fois que la désérialisation est terminée :

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class WeatherForecastRequiredPropertyConverter : JsonConverter<WeatherForecast>
{
public override WeatherForecast Read(
ref Utf8JsonReader reader,
Type type,
JsonSerializerOptions options)
{
// Don't pass in options when recursively calling Deserialize.
WeatherForecast forecast = JsonSerializer.Deserialize<WeatherForecast>(ref reader);

// Check for required fields set by values in JSON


if (forecast.Date == default)
{
throw new JsonException("Required property not received in the JSON");
}
return forecast;
}

public override void Write(


Utf8JsonWriter writer,
WeatherForecast forecast, JsonSerializerOptions options)
{
// Don't pass in options when recursively calling Serialize.
JsonSerializer.Serialize(writer, forecast);
}
}
}

Inscrivez ce convertisseur personnalisé à l' aide d’un attribut sur la classe POCO ou en ajoutant le convertisseur
à la Converters collection.
Si vous suivez ce modèle, ne transmettez pas l’objet d’options lors de l’appel récursif Serialize ou Deserialize .
L’objet d’options contient la Converters collection. Si vous le transmettez à Serialize ou Deserialize , le
convertisseur personnalisé s’appelle lui-même, en effectuant une boucle infinie qui provoque une exception de
dépassement de capacité de la pile. Si les options par défaut ne sont pas réalisables, créez une nouvelle instance
des options avec les paramètres dont vous avez besoin. Cette approche est lente puisque chaque nouvelle
instance est mise en cache de façon indépendante.
Le code de convertisseur précédent est un exemple simplifié. Une logique supplémentaire est nécessaire si vous
avez besoin de gérer des attributs (tels que [JsonIgnore] ou des options différentes (telles que des encodeurs
personnalisés). En outre, l’exemple de code ne gère pas les propriétés pour lesquelles une valeur par défaut est
définie dans le constructeur. Cette approche ne fait pas la différence entre les scénarios suivants :
Une propriété est absente du JSON.
Une propriété pour un type non Nullable est présente dans le JSON, mais la valeur est la valeur par défaut
pour le type, par exemple zéro pour un int .
Une propriété pour un type valeur Nullable est présente dans le JSON, mais la valeur est null.
Ignorer une propriété de façon conditionnelle
Newtonsoft.Json offre plusieurs méthodes pour ignorer de manière conditionnelle une propriété lors de la
sérialisation ou de la désérialisation :
DefaultContractResolver vous permet de sélectionner les propriétés à inclure ou exclure, en fonction de
critères arbitraires.
Les NullValueHandling DefaultValueHandling paramètres et sur JsonSerializerSettings vous permettent de
spécifier que toutes les propriétés de valeur null ou de valeur par défaut doivent être ignorées.
Les NullValueHandling DefaultValueHandling paramètres et de l' [JsonProperty] attribut vous permettent
de spécifier des propriétés individuelles qui doivent être ignorées lorsqu’elles sont définies sur null ou sur la
valeur par défaut.
System.Text.Jsonfournit les méthodes suivantes pour omettre des propriétés lors de la sérialisation :
L’attribut [JsonIgnore] sur une propriété provoque l’omission de la propriété du JSON pendant la
sérialisation.
L’option IgnoreNullValues global vous permet d’exclure toutes les propriétés de valeur null.
L’option IgnoreReadOnlyProperties global vous permet d’exclure toutes les propriétés en lecture seule.
Ces options ne vous permettent pas de :
Ignore toutes les propriétés qui ont la valeur par défaut pour le type.
Ignorer les propriétés sélectionnées qui ont la valeur par défaut pour le type.
Ignorer les propriétés sélectionnées si leur valeur est null.
Ignorer les propriétés sélectionnées en fonction de critères arbitraires évalués au moment de l’exécution.
Pour cette fonctionnalité, vous pouvez écrire un convertisseur personnalisé. Voici un exemple de modèle POCO
et un convertisseur personnalisé qui illustre cette approche :

public class WeatherForecast


{
public DateTimeOffset Date { get; set; }
public int TemperatureCelsius { get; set; }
public string Summary { get; set; }
}

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class WeatherForecastRuntimeIgnoreConverter : JsonConverter<WeatherForecast>
{
public override WeatherForecast Read(
public override WeatherForecast Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}

var wf = new WeatherForecast();

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return wf;
}

if (reader.TokenType == JsonTokenType.PropertyName)
{
string propertyName = reader.GetString();
reader.Read();
switch (propertyName)
{
case "Date":
DateTimeOffset date = reader.GetDateTimeOffset();
wf.Date = date;
break;
case "TemperatureCelsius":
int temperatureCelsius = reader.GetInt32();
wf.TemperatureCelsius = temperatureCelsius;
break;
case "Summary":
string summary = reader.GetString();
wf.Summary = string.IsNullOrWhiteSpace(summary) ? "N/A" : summary;
break;
}
}
}

throw new JsonException();


}

public override void Write(Utf8JsonWriter writer, WeatherForecast wf, JsonSerializerOptions options)


{
writer.WriteStartObject();

writer.WriteString("Date", wf.Date);
writer.WriteNumber("TemperatureCelsius", wf.TemperatureCelsius);
if (!string.IsNullOrWhiteSpace(wf.Summary) && wf.Summary != "N/A")
{
writer.WriteString("Summary", wf.Summary);
}

writer.WriteEndObject();
}
}
}

Le convertisseur fait en sorte que la Summary propriété soit omise de la sérialisation si sa valeur est null, une
chaîne vide ou « N/A ».
Inscrivez ce convertisseur personnalisé à l' aide d’un attribut sur la classe ou en ajoutant le convertisseur à la
Converters collection.
Cette approche nécessite une logique supplémentaire dans les cas suivants :
Le POCO comprend des propriétés complexes.
Vous devez gérer des attributs tels que [JsonIgnore] ou des options telles que des encodeurs personnalisés.
Spécifier le format de la date
Newtonsoft.Json offre plusieurs moyens de contrôler la façon dont les propriétés des DateTime DateTimeOffset
types et sont sérialisées et désérialisées :
Le DateTimeZoneHandling paramètre peut être utilisé pour sérialiser toutes les DateTime valeurs en tant que
dates UTC.
Le DateFormatString paramètre et les DateTime convertisseurs peuvent être utilisés pour personnaliser le
format des chaînes de date.
Dans System.Text.Json , le seul format qui offre une prise en charge intégrée est ISO 8601-1:2019, car il est
largement adopté, sans ambiguïté, et effectue des allers-retours avec précision. Pour utiliser n’importe quel
autre format, créez un convertisseur personnalisé. Pour plus d’informations, consultez prise en charge des
System.Text.Json valeurs DateTime et DateTimeOffset dans .
Rappels
Newtonsoft.Json vous permet d’exécuter du code personnalisé à plusieurs points dans le processus de
sérialisation ou de désérialisation :
OnDeserializing (au début de la désérialisation d’un objet)
OnDeserialized (à la fin de la désérialisation d’un objet)
OnSerializing (au début de la sérialisation d’un objet)
OnSerialized (à la fin de la sérialisation d’un objet)
Dans System.Text.Json , vous pouvez simuler des rappels en écrivant un convertisseur personnalisé. L’exemple
suivant montre un convertisseur personnalisé pour un POCO. Le convertisseur comprend du code qui affiche un
message à chaque point qui correspond à un Newtonsoft.Json rappel.
using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SystemTextJsonSamples
{
public class WeatherForecastCallbacksConverter : JsonConverter<WeatherForecast>
{
public override WeatherForecast Read(
ref Utf8JsonReader reader,
Type type,
JsonSerializerOptions options)
{
// Place "before" code here (OnDeserializing),
// but note that there is no access here to the POCO instance.
Console.WriteLine("OnDeserializing");

// Don't pass in options when recursively calling Deserialize.


WeatherForecast forecast = JsonSerializer.Deserialize<WeatherForecast>(ref reader);

// Place "after" code here (OnDeserialized)


Console.WriteLine("OnDeserialized");

return forecast;
}

public override void Write(


Utf8JsonWriter writer,
WeatherForecast forecast, JsonSerializerOptions options)
{
// Place "before" code here (OnSerializing)
Console.WriteLine("OnSerializing");

// Don't pass in options when recursively calling Serialize.


JsonSerializer.Serialize(writer, forecast);

// Place "after" code here (OnSerialized)


Console.WriteLine("OnSerialized");
}
}
}

Inscrivez ce convertisseur personnalisé à l' aide d’un attribut sur la classe ou en ajoutant le convertisseur à la
Converters collection.
Si vous utilisez un convertisseur personnalisé qui suit l’exemple précédent :
Le OnDeserializing code n’a pas accès à la nouvelle instance poco. Pour manipuler la nouvelle instance
POCO au début de la désérialisation, placez ce code dans le constructeur POCO.
Ne transmettez pas l’objet d’options lors d’un appel récursif Serialize ou Deserialize . L’objet d’options
contient la Converters collection. Si vous le transmettez à Serialize ou Deserialize , le convertisseur sera
utilisé, en effectuant une boucle infinie qui entraîne une exception de dépassement de capacité de la pile.
Champs publics et non publics
Newtonsoft.Json peut sérialiser et désérialiser des champs ainsi que des propriétés. System.Text.Jsonfonctionne
uniquement avec les propriétés publiques. Les convertisseurs personnalisés peuvent fournir cette fonctionnalité.
Accesseurs set et getters de propriétés internes et privées
Newtonsoft.Json peut utiliser des accesseurs set et des accesseurs set de propriété privés et internes via l'
JsonProperty attribut. System.Text.Jsonprend uniquement en charge les accesseurs set publics. Les
convertisseurs personnalisés peuvent fournir cette fonctionnalité.
Remplir les objets existants
La JsonConvert.PopulateObject méthode de Newtonsoft.Json désérialise un document JSON vers une instance
existante d’une classe, au lieu de créer une nouvelle instance. System.Text.Jsoncrée toujours une nouvelle
instance du type cible à l’aide du constructeur sans paramètre public par défaut. Les convertisseurs
personnalisés peuvent être désérialisés en une instance existante.
Réutiliser plutôt que remplacer les propriétés
Le Newtonsoft.Json ObjectCreationHandling paramètre vous permet de spécifier que les objets dans les
propriétés doivent être réutilisés au lieu d’être remplacés lors de la désérialisation. System.Text.Jsonremplace
toujours les objets dans les propriétés. Les convertisseurs personnalisés peuvent fournir cette fonctionnalité.
Ajouter aux collections sans Setters
Pendant la désérialisation, Newtonsoft.Json ajoute des objets à une collection même si la propriété n’a pas
d’accesseur Set. System.Text.Jsonignore les propriétés qui n’ont pas de méthode setter. Les convertisseurs
personnalisés peuvent fournir cette fonctionnalité.

Les scénarios que JsonSerializer ne prend pas en charge actuellement


Pour les scénarios suivants, les solutions de contournement ne sont pas pratiques ou possibles. Si vous utilisez
ces Newtonsoft.Json fonctionnalités, la migration n’est pas possible sans modification significative.
Conserver les références d’objet et gérer les boucles
Par défaut, Newtonsoft.Json sérialise par valeur. Par exemple, si un objet contient deux propriétés qui
contiennent une référence au même Person objet, les valeurs des propriétés de cet Person objet sont
dupliquées dans le JSON.
Newtonsoft.Json a un PreserveReferencesHandling paramètre sur JsonSerializerSettings qui vous permet de
sérialiser par référence :
Les métadonnées d’identificateur sont ajoutées au JSON créé pour le premier Person objet.
Le JSON créé pour le deuxième Person objet contient une référence à cet identificateur à la place des valeurs
de propriété.
Newtonsoft.Json a également un ReferenceLoopHandling paramètre qui vous permet d’ignorer les références
circulaires au lieu de lever une exception.
System.Text.Jsonprend uniquement en charge la sérialisation par valeur et lève une exception pour les
références circulaires.
Attributs System. Runtime. Serialization
System.Text.Jsonne prend pas en charge les attributs de l' System.Runtime.Serialization espace de noms, tels
que DataMemberAttribute et IgnoreDataMemberAttribute .
Nombres octaux
Newtonsoft.Json traite les nombres avec un zéro non significatif comme des nombres octaux.
System.Text.Jsonn’autorise pas les zéros non significatifs, car la spécification RFC 8259 ne les autorise pas.
MissingMemberHandling
Newtonsoft.Json peut être configuré pour lever des exceptions pendant la désérialisation si le JSON comprend
des propriétés qui sont manquantes dans le type cible. System.Text.Jsonignore les propriétés supplémentaires
dans le JSON, sauf lorsque vous utilisez l' attribut [JsonExtensionData]. Il n’existe aucune solution de
contournement pour la fonctionnalité de membre manquant.
TraceWriter
Newtonsoft.Json vous permet de déboguer à l’aide d’un TraceWriter pour afficher les journaux qui sont
générés par la sérialisation ou la désérialisation. System.Text.Jsonn’effectue pas de journalisation.

JsonDocument et JsonElement comparés à JToken (comme JObject,


JArray)
System.Text.Json.JsonDocumentpermet d’analyser et de générer un Document Object Model en lecture seule
(DOM) à partir de charges utiles JSON existantes. Le DOM fournit un accès aléatoire aux données dans une
charge utile JSON. Les éléments JSON qui composent la charge utile sont accessibles via le JsonElement type. Le
JsonElement type fournit des API pour convertir du texte JSON en types .net courants. JsonDocument expose une
RootElement propriété.
JsonDocument est IDisposable
JsonDocument crée une vue en mémoire des données dans une mémoire tampon regroupée. Par conséquent,
JObject contrairement JArray à ou Newtonsoft.Json , le JsonDocument type implémente IDisposable et doit
être utilisé à l’intérieur d’un bloc using.
Retournez uniquement un JsonDocument à partir de votre API si vous souhaitez transférer la propriété de la
durée de vie et supprimer la responsabilité de l’appelant. Dans la plupart des scénarios, ce n’est pas nécessaire.
Si l’appelant doit travailler avec l’ensemble du document JSON, retournez le Clone du RootElement , qui est un
JsonElement . Si l’appelant doit travailler avec un élément particulier dans le document JSON, retournez le Clone
de ce JsonElement . Si vous retournez RootElement directement le ou un sous-élément sans créer de Clone ,
l’appelant ne pourra pas accéder au retourné JsonElement après que le qui l’a détenu JsonDocument est
supprimé.
Voici un exemple qui vous oblige à créer un Clone :

public JsonElement LookAndLoad(JsonElement source)


{
string json = File.ReadAllText(source.GetProperty("fileName").GetString());

using (JsonDocument doc = JsonDocument.Parse(json))


{
return doc.RootElement.Clone();
}
}

Le code précédent attend un JsonElement qui contient une fileName propriété. Il ouvre le fichier JSON et crée
un JsonDocument . La méthode suppose que l’appelant souhaite travailler avec l’ensemble du document, de sorte
qu’il retourne l' Clone du RootElement .
Si vous recevez un JsonElement et que vous retournez un sous-élément, il n’est pas nécessaire de retourner un
Clone du sous-élément. L’appelant est chargé de conserver les actifs JsonDocument auxquels appartient le
passé JsonElement . Par exemple :

public JsonElement ReturnFileName(JsonElement source)


{
return source.GetProperty("fileName");
}

JsonDocument est en lecture seule


Le System.Text.Json DOM ne peut pas ajouter, supprimer ou modifier des éléments JSON. Cette méthode est
conçue pour les performances et pour réduire les allocations pour l’analyse des tailles de charge utile JSON
courantes (c’est-à-dire < 1 Mo). Si votre scénario utilise actuellement un modèle DOM modifiable, l’une des
solutions de contournement suivantes peut être possible :
Pour créer un JsonDocument à partir de zéro (autrement dit, sans passer une charge utile JSON existante à la
Parse méthode), écrivez le texte JSON à l’aide de Utf8JsonWriter et analysez la sortie de cette pour créer un
nouveau JsonDocument .
Pour modifier un existant JsonDocument , utilisez-le pour écrire du texte JSON, apporter des modifications
pendant que vous écrivez et analyser la sortie de celle-ci pour en créer une nouvelle JsonDocument .
Pour fusionner des documents JSON existants, équivalents aux JObject.Merge JContainer.Merge API ou de
Newtonsoft.Json , consultez ce problème GitHub.

JsonElement est un struct d’Union


JsonDocument expose le RootElement en tant que propriété de type JsonElement , qui est une Union, type struct
qui englobe tout élément JSON. Newtonsoft.Json utilise des types hiérarchiques dédiés comme JObject ,,
JArray JToken , et ainsi de suite. JsonElement est ce que vous pouvez rechercher et énumérer, et vous pouvez
utiliser JsonElement pour matérialiser des éléments JSON dans des types .net.
Comment rechercher des sous-éléments dans un JsonDocument et JsonElement
Les recherches de jetons JSON utilisant JObject ou JArray de Newtonsoft.Json ont tendance à être
relativement rapides parce qu’il s’agit de recherches dans un dictionnaire. Par comparaison, les recherches sur
JsonElement requièrent une recherche séquentielle des propriétés et sont donc relativement lentes (par
exemple, lors de l’utilisation de TryGetProperty ). System.Text.Jsonest conçu pour réduire le temps d’analyse
initial plutôt que le temps de recherche. Par conséquent, utilisez les approches suivantes pour optimiser les
performances lors de la recherche à l’aide d’un JsonDocument objet :
Utilisez les énumérateurs intégrés ( EnumerateArray et EnumerateObject ) au lieu d’effectuer votre propre
indexation ou boucles.
N’effectuez pas de recherche séquentielle sur l’ensemble de JsonDocument chaque propriété à l’aide de
RootElement . Au lieu de cela, recherchez des objets JSON imbriqués en fonction de la structure connue des
données JSON. Par exemple, si vous recherchez une Grade propriété dans des objets, parcourez Student les
Student objets et récupérez la valeur de Grade pour chaque, plutôt que de rechercher dans tous les objets à
la JsonElement recherche de Grade Propriétés. Si vous procédez ainsi, vous obtiendrez des passes inutiles
sur les mêmes données.
Pour obtenir un exemple de code, consultez utiliser JsonDocument pour accéder aux données.

Utf8JsonReader comparé à JsonTextReader


System.Text.Json.Utf8JsonReaderest un lecteur haute performance, à faible allocation et en avant uniquement
pour le texte JSON encodé en UTF-8, lu à partir d’un ReadOnlySpan <byte> ou d’un ReadOnlySequence <byte>
. Le Utf8JsonReader est un type de bas niveau qui peut être utilisé pour créer des analyseurs et des
désérialiseurs personnalisés.
Les sections suivantes expliquent les modèles de programmation recommandés pour l’utilisation de
Utf8JsonReader .

Utf8JsonReader est un struct Ref


Étant donné que le Utf8JsonReader type est un struct de référence, il présente certaines limitations. Par exemple,
il ne peut pas être stocké en tant que champ sur une classe ou un struct autre qu’un struct Ref. Pour obtenir des
performances élevées, ce type doit être un, ref struct car il doit mettre en cache le ReadOnlySpan <byte>
d’entrée, qui est lui-même un struct de référence. En outre, ce type est mutable puisqu’il contient l’État. Par
conséquent, transmettez-le par référence plutôt que par valeur. Le fait de le passer par valeur génère une
copie de struct et les modifications d’État ne sont pas visibles par l’appelant. Cela diffère de Newtonsoft.Json
puisque Newtonsoft.Json JsonTextReader est une classe. Pour plus d’informations sur l’utilisation des structs de
référence, consultez écrire du code C# sécurisé et efficace.
Lire du texte UTF -8
Pour obtenir les meilleures performances possibles lors de l’utilisation de Utf8JsonReader , lisez les charges
utiles JSON déjà encodées en tant que texte UTF-8 plutôt qu’en tant que chaînes UTF-16. Pour obtenir un
exemple de code, consultez Filtrer les données à l’aide de Utf8JsonReader.
Lire avec un flux ou un PipeReader
Utf8JsonReader Prend en charge la lecture à partir d’un ReadOnlySpan <byte> ou d’un ReadOnlySequence
<byte> encodé UTF-8 (qui est le résultat de la lecture à partir d’un PipeReader ).
Pour la lecture synchrone, vous pouvez lire la charge utile JSON jusqu’à la fin du flux dans un tableau d’octets et
la passer dans le lecteur. Pour lire à partir d’une chaîne (qui est encodée au format UTF-16), appelez UTF8
.GetBytes pour tout d’abord transcoder la chaîne en un tableau d’octets encodé en UTF-8. Ensuite, transmettez-le
à Utf8JsonReader .
Étant donné que le Utf8JsonReader considère que l’entrée est du texte JSON, une marque d’ordre d’octet (BOM)
UTF-8 est considérée comme un JSON non valide. L’appelant doit filtrer ce dernier avant de passer les données
au lecteur.
Pour obtenir des exemples de code, consultez use Utf8JsonReader.
Lecture avec ReadOnlySequence à plusieurs segments
Si votre entrée JSON est un ReadOnlySpan <byte> , chaque élément JSON est accessible à partir de la
ValueSpan propriété sur le lecteur au fur et à mesure que vous parcourez la boucle de lecture. Toutefois, si votre
entrée est un ReadOnlySequence <byte> (qui est le résultat de la lecture à partir d’un PipeReader ), certains
éléments JSON peuvent chevaucher plusieurs segments de l' ReadOnlySequence<byte> objet. Ces éléments ne
sont pas accessibles à partir d' ValueSpan un bloc de mémoire contigu. Au lieu de cela, chaque fois que vous
avez un segment multiple ReadOnlySequence<byte> comme entrée, interrogez la HasValueSequence propriété sur
le lecteur pour déterminer comment accéder à l’élément JSON actuel. Voici un modèle recommandé :

while (reader.Read())
{
switch (reader.TokenType)
{
// ...
ReadOnlySpan<byte> jsonElement = reader.HasValueSequence ?
reader.ValueSequence.ToArray() :
reader.ValueSpan;
// ...
}
}

Utiliser ValueTextEquals pour les recherches de nom de propriété


N’utilisez pas ValueSpan pour effectuer des comparaisons octet par octet en appelant SequenceEqual pour les
recherches de nom de propriété. Appelez ValueTextEquals à la place, car cette méthode annule l’échappement
des caractères échappés dans le JSON. Voici un exemple qui montre comment rechercher une propriété
nommée « Name » :

private static readonly byte[] s_nameUtf8 = Encoding.UTF8.GetBytes("name");


while (reader.Read())
{
JsonTokenType tokenType = reader.TokenType;

switch (tokenType)
{
case JsonTokenType.StartObject:
total++;
break;
case JsonTokenType.PropertyName:
if (reader.ValueTextEquals(s_nameUtf8))
{
count++;
}
break;
}

Lire les valeurs NULL dans les types valeur Nullable


Newtonsoft.Json fournit des API qui retournent Nullable<T> , telles que ReadAsBoolean , qui gère un Null
TokenType pour vous en retournant un bool? . Les System.Text.Json API intégrées retournent uniquement des
types valeur n’acceptant pas les valeurs NULL. Par exemple, Utf8JsonReader.GetBoolean retourne un bool . Elle
lève une exception si elle trouve Null dans le JSON. Les exemples suivants illustrent deux façons de gérer les
valeurs NULL, l’une en retournant un type valeur Nullable et l’autre en retournant la valeur par défaut :

public bool? ReadAsNullableBoolean()


{
_reader.Read();
if (_reader.TokenType == JsonTokenType.Null)
{
return null;
}
if (_reader.TokenType != JsonTokenType.True && _reader.TokenType != JsonTokenType.False)
{
throw new JsonException();
}
return _reader.GetBoolean();
}

public bool ReadAsBoolean(bool defaultValue)


{
_reader.Read();
if (_reader.TokenType == JsonTokenType.Null)
{
return defaultValue;
}
if (_reader.TokenType != JsonTokenType.True && _reader.TokenType != JsonTokenType.False)
{
throw new JsonException();
}
return _reader.GetBoolean();
}

Multi-ciblage
Si vous devez continuer à utiliser Newtonsoft.Json pour certains frameworks cibles, vous pouvez effectuer
plusieurs cibles et avoir deux implémentations. Toutefois, cela n’est pas trivial et nécessiterait une #ifdefs
duplication source. Une façon de partager autant de code que possible consiste à créer un ref struct wrapper
autour de Utf8JsonReader et Newtonsoft.Json JsonTextReader . Ce wrapper unifie la surface d’exposition
publique tout en isolant les différences de comportement. Cela vous permet d’isoler les modifications
principalement à la construction du type, ainsi que de passer le nouveau type par référence. Il s’agit du modèle
que la bibliothèque Microsoft. extensions. DependencyModel suit :
UnifiedJsonReader.JsonTextReader.cs
UnifiedJsonReader.Utf8JsonReader.cs

Utf8JsonWriter comparé à JsonTextWriter


System.Text.Json.Utf8JsonWriterest une méthode très performante pour écrire du texte JSON encodé en UTF-8 à
partir de types .NET courants tels que String , Int32 et DateTime . Le writer est un type de bas niveau qui peut
être utilisé pour créer des sérialiseurs personnalisés.
Les sections suivantes expliquent les modèles de programmation recommandés pour l’utilisation de
Utf8JsonWriter .

Écrire avec du texte UTF -8


Pour obtenir les meilleures performances possibles lors de l’utilisation de Utf8JsonWriter , écrivez les charges
utiles JSON déjà encodées en tant que texte UTF-8 plutôt qu’en tant que chaînes UTF-16. Utilisez
JsonEncodedText pour mettre en cache et précoder des noms et des valeurs de propriété de chaîne connus
comme statiques, et les transmettre au writer, plutôt que d’utiliser des littéraux de chaîne UTF-16. Cela est plus
rapide que la mise en cache et l’utilisation des tableaux d’octets UTF-8.
Cette approche fonctionne également si vous devez effectuer des séquences d’échappement personnalisées.
System.Text.Json ne vous permet pas de désactiver l’échappement lors de l’écriture d’une chaîne. Toutefois,
vous pouvez transmettre votre propre option personnalisée JavaScriptEncoder à l’enregistreur, ou créer votre
propre option JsonEncodedText qui utilise votre JavascriptEncoder pour effectuer l’échappement, puis écrire à
la JsonEncodedText place de la chaîne. Pour plus d’informations, consultez personnaliser l’encodage des
caractères.
Écrire des valeurs brutes
La Newtonsoft.Json WriteRawValue méthode écrit un JSON brut dans lequel une valeur est attendue.
System.Text.Jsonn’a pas d’équivalent direct, mais voici une solution de contournement qui garantit que seul le
JSON valide est écrit :

using JsonDocument doc = JsonDocument.Parse(string);


doc.WriteTo(writer);

Personnaliser l’échappement des caractères


Le paramètre StringEscapeHandling de JsonTextWriter propose des options permettant d’échapper tous les
caractères non-ASCII ou les caractères HTML. Par défaut, place dans une Utf8JsonWriter séquence
d’échappement tous les caractères non-ASCII et html. Cette séquence d’échappement est effectuée pour des
raisons de sécurité de défense en profondeur. Pour spécifier une autre stratégie d’échappement, créez un
JavaScriptEncoder et définissez JsonWriterOptions.Encoder . Pour plus d’informations, consultez personnaliser
l’encodage des caractères.
Personnaliser le format JSON
JsonTextWriter inclut les paramètres suivants, pour lesquels Utf8JsonWriter n’a pas d’équivalent :
Mise en retrait : spécifie le nombre de caractères à mettre en retrait. Utf8JsonWriter effectue toujours une
mise en retrait à 2 caractères.
IndentChar : spécifie le caractère à utiliser pour la mise en retrait. Utf8JsonWriter utilise toujours un espace
blanc.
QuoteChar : spécifie le caractère à utiliser pour entourer les valeurs de chaîne. Utf8JsonWriter utilise toujours
des guillemets doubles.
QUOTENAME : spécifie si les noms de propriété doivent être placés entre guillemets. Utf8JsonWriter les
entourent toujours de guillemets.
Il n’existe aucune solution de contournement qui vous permettrait de personnaliser le JSON généré par à l’aide
Utf8JsonWriter de ces méthodes.

Écrire des valeurs null


Pour écrire des valeurs NULL à l’aide de Utf8JsonWriter , appelez :
WriteNullpour écrire une paire clé-valeur avec NULL comme valeur.
WriteNullValuepour écrire NULL comme élément d’un tableau JSON.
Pour une propriété de type chaîne, si la chaîne est null, WriteString et WriteStringValue sont équivalents à
WriteNull et WriteNullValue .

Écrire les valeurs TimeSpan, Uri ou char


JsonTextWriter fournit WriteValue des méthodes pour les valeurs TimeSpan, URIet char . Utf8JsonWriter n’a
pas de méthode équivalente. Au lieu de cela, mettez en forme ces valeurs en tant que chaînes (en appelant
ToString() , par exemple) et appelez WriteStringValue .

Multi-ciblage
Si vous devez continuer à utiliser Newtonsoft.Json pour certains frameworks cibles, vous pouvez effectuer
plusieurs cibles et avoir deux implémentations. Toutefois, cela n’est pas trivial et nécessiterait une #ifdefs
duplication source. Une façon de partager autant de code que possible consiste à créer un wrapper autour de
Utf8JsonWriter et Newtonsoft JsonTextWriter . Ce wrapper unifie la surface d’exposition publique tout en
isolant les différences de comportement. Cela vous permet d’isoler les modifications principalement de la
construction du type. La bibliothèque Microsoft. extensions. DependencyModel est la suivante :
UnifiedJsonWriter.JsonTextWriter.cs
UnifiedJsonWriter.Utf8JsonWriter.cs

Ressources supplémentaires
System.Text.Jsonvue
Procédure d’utilisationSystem.Text.Json
Guide pratique pour écrire des convertisseurs personnalisés
Prise en charge des valeurs DateTime et DateTimeOffset dansSystem.Text.Json
System.Text.JsonRéférence d’API
System.Text.Json. Référence de l’API de sérialisation
Sérialisation binaire
18/07/2020 • 17 minutes to read • Edit Online

La sérialisation peut être définie comme le processus de stockage de l'état d'un objet sur un support de stockage.
Pendant ce processus, les champs publics et privés de l'objet et le nom de la classe, y compris l'assembly
contenant la classe, sont convertis en un flux de données d'octets, écrit ensuite dans un flux de données. Lorsque
l'objet est désérialisé par la suite, un clone exact de l'objet d'origine est créé.
Lorsque vous implémentez un mécanisme de sérialisation dans un environnement orienté objet, vous devez faire
plusieurs compromis entre facilité d'utilisation et souplesse. Le processus peut être automatisé en grande partie,
à condition que vous puissiez suffisamment le contrôler. Par exemple, dans certaines situations, la sérialisation
binaire simple n'est pas suffisante ou une raison particulière peut exiger la définition des champs à sérialiser. Les
sections suivantes étudient le mécanisme de sérialisation fiable fourni avec .NET et mettent en évidence plusieurs
fonctionnalités importantes qui vous permettent de personnaliser le processus en fonction de vos besoins.

NOTE
L'état d'un objet encodé UTF-8 ou UTF-7 n'est pas préservé si l'objet est sérialisé et désérialisé à l'aide de différentes
versions du .NET Framework.

WARNING
La sérialisation binaire peut être dangereuse. Pour plus d’informations, consultez le Guide de la sécurité BinaryFormatter.

La sérialisation binaire permet de modifier les membres privés à l’intérieur d’un objet et par conséquent de
modifier l’état de celui-ci. Pour cette raison, d’autres infrastructures de sérialisation, comme System.Text.Json , qui
opèrent sur la surface de l’API publique sont recommandées.

.NET Core
.NET Core prend en charge la sérialisation binaire pour un sous-ensemble de types. Vous pouvez voir la liste des
types pris en charge dans la section types sérialisables qui suit. Les types répertoriés sont garantis comme étant
sérialisables entre .NET Framework 4.5.1 et les versions ultérieures et entre .NET Core 2,0 et versions ultérieures.
D’autres implémentations de .NET, telles que mono, ne sont pas officiellement prises en charge, mais doivent
également fonctionner.
Types sérialisables
TYPE N OT ES

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException À partir de .NET Core 2.0.4.

Microsoft.CSharp.RuntimeBinder.RuntimeBinderInternalComp À partir de .NET Core 2.0.4.


ilerException

System.AccessViolationException À partir de .NET Core 2.0.4.

System.AggregateException À partir de .NET Core 2.0.4.


TYPE N OT ES

System.AppDomainUnloadedException À partir de .NET Core 2.0.4.

System.ApplicationException À partir de .NET Core 2.0.4.

System.ArgumentException À partir de .NET Core 2.0.4.

System.ArgumentNullException À partir de .NET Core 2.0.4.

System.ArgumentOutOfRangeException À partir de .NET Core 2.0.4.

System.ArithmeticException À partir de .NET Core 2.0.4.

System.Array

System.ArraySegment<T>

System.ArrayTypeMismatchException À partir de .NET Core 2.0.4.

System.Attribute

System.BadImageFormatException À partir de .NET Core 2.0.4.

System.Boolean

System.Byte

System.CannotUnloadAppDomainException À partir de .NET Core 2.0.4.

System.Char

System.Collections.ArrayList

System.Collections.BitArray

System.Collections.Comparer

System.Collections.DictionaryEntry

System.Collections.Generic.Comparer<T>

System.Collections.Generic.Dictionary<TKey,TValue>

System.Collections.Generic.EqualityComparer<T>

System.Collections.Generic.HashSet<T>

System.Collections.Generic.KeyNotFoundException À partir de .NET Core 2.0.4.

System.Collections.Generic.KeyValuePair<TKey,TValue>
TYPE N OT ES

System.Collections.Generic.LinkedList<T>

System.Collections.Generic.List<T>

System.Collections.Generic.Queue<T>

System.Collections.Generic.SortedDictionary<TKey,TValue>

System.Collections.Generic.SortedList<TKey,TValue>

System.Collections.Generic.SortedSet<T>

System.Collections.Generic.Stack<T>

System.Collections.Hashtable

System.Collections.ObjectModel.Collection<T>

System.Collections.ObjectModel.KeyedCollection<TKey,TItem
>

System.Collections.ObjectModel.ObservableCollection<T>

System.Collections.ObjectModel.ReadOnlyCollection<T>

System.Collections.ObjectModel.ReadOnlyDictionary<TKey,T
Value>

System.Collections.ObjectModel.ReadOnlyObservableCollecti
on<T>

System.Collections.Queue

System.Collections.SortedList

System.Collections.Specialized.HybridDictionary

System.Collections.Specialized.ListDictionary

System.Collections.Specialized.OrderedDictionary

System.Collections.Specialized.StringCollection

System.Collections.Specialized.StringDictionary

System.Collections.Stack

À partir de .NET Core 2.0.4.


System.Collections.Generic.NonRandomizedStringEqualityComparer

System.ComponentModel.BindingList<T>
TYPE N OT ES

System.ComponentModel.DataAnnotations.ValidationExcepti À partir de .NET Core 2.0.4.


on

System.ComponentModel.Design.CheckoutException À partir de .NET Core 2.0.4.

System.ComponentModel.InvalidAsynchronousStateExceptio À partir de .NET Core 2.0.4.


n

System.ComponentModel.InvalidEnumArgumentException À partir de .NET Core 2.0.4.

System.ComponentModel.LicenseException À partir de .NET Core 2.0.4.


La sérialisation de .NET Framework à .NET Core n’est pas
prise en charge.

System.ComponentModel.WarningException À partir de .NET Core 2.0.4.

System.ComponentModel.Win32Exception À partir de .NET Core 2.0.4.

System.Configuration.ConfigurationErrorsException À partir de .NET Core 2.0.4.

System.Configuration.ConfigurationException À partir de .NET Core 2.0.4.

System.Configuration.Provider.ProviderException À partir de .NET Core 2.0.4.

System.Configuration.SettingsPropertyIsReadOnlyException À partir de .NET Core 2.0.4.

System.Configuration.SettingsPropertyNotFoundException À partir de .NET Core 2.0.4.

System.Configuration.SettingsPropertyWrongTypeException À partir de .NET Core 2.0.4.

System.ContextMarshalException À partir de .NET Core 2.0.4.

System.DBNull À compter de .NET Core 2.0.2 et versions ultérieures.

System.Data.Common.DbException À partir de .NET Core 2.0.4.

System.Data.ConstraintException À partir de .NET Core 2.0.4.

System.Data.DBConcurrencyException À partir de .NET Core 2.0.4.

System.Data.DataException À partir de .NET Core 2.0.4.

System.Data.DataSet

System.Data.DataTable Si vous définissez RemotingFormat sur


SerializationFormat.Binary , il ne peut être échangé
qu’avec .net Core 2,1 et versions ultérieures.

System.Data.DeletedRowInaccessibleException À partir de .NET Core 2.0.4.

System.Data.DuplicateNameException À partir de .NET Core 2.0.4.


TYPE N OT ES

System.Data.EvaluateException À partir de .NET Core 2.0.4.

System.Data.InRowChangingEventException À partir de .NET Core 2.0.4.

System.Data.InvalidConstraintException À partir de .NET Core 2.0.4.

System.Data.InvalidExpressionException À partir de .NET Core 2.0.4.

System.Data.MissingPrimaryKeyException À partir de .NET Core 2.0.4.

System.Data.NoNullAllowedException À partir de .NET Core 2.0.4.

System.Data.Odbc.OdbcException À partir de .NET Core 2.0.4.

System.Data.OperationAbortedException À partir de .NET Core 2.0.4.

System.Data.PropertyCollection

System.Data.ReadOnlyException À partir de .NET Core 2.0.4.

System.Data.RowNotInTableException À partir de .NET Core 2.0.4.

System.Data.SqlClient.SqlException À partir de .NET Core 2.0.4.


La sérialisation de .NET Framework à .NET Core n’est pas
prise en charge

System.Data.SqlTypes.SqlAlreadyFilledException À partir de .NET Core 2.0.4.

System.Data.SqlTypes.SqlBoolean

System.Data.SqlTypes.SqlByte

System.Data.SqlTypes.SqlDateTime

System.Data.SqlTypes.SqlDouble

System.Data.SqlTypes.SqlGuid

System.Data.SqlTypes.SqlInt16

System.Data.SqlTypes.SqlInt32

System.Data.SqlTypes.SqlInt64

System.Data.SqlTypes.SqlNotFilledException À partir de .NET Core 2.0.4.

System.Data.SqlTypes.SqlNullValueException À partir de .NET Core 2.0.4.

System.Data.SqlTypes.SqlString
TYPE N OT ES

System.Data.SqlTypes.SqlTruncateException À partir de .NET Core 2.0.4.

System.Data.SqlTypes.SqlTypeException À partir de .NET Core 2.0.4.

System.Data.StrongTypingException À partir de .NET Core 2.0.4.

System.Data.SyntaxErrorException À partir de .NET Core 2.0.4.

System.Data.VersionNotFoundException À partir de .NET Core 2.0.4.

System.DataMisalignedException À partir de .NET Core 2.0.4.

System.DateTime

System.DateTimeOffset

System.Decimal

System.Diagnostics.Contracts.ContractException À partir de .NET Core 2.0.4.

System.Diagnostics.Tracing.EventSourceException À partir de .NET Core 2.0.4.

System.IO.DirectoryNotFoundException À partir de .NET Core 2.0.4.

System.DirectoryServices.AccountManagement.MultipleMatc À partir de .NET Core 2.0.4.


hesException

System.DirectoryServices.AccountManagement.NoMatchingP À partir de .NET Core 2.0.4.


rincipalException

System.DirectoryServices.AccountManagement.PasswordExce À partir de .NET Core 2.0.4.


ption

System.DirectoryServices.AccountManagement.PrincipalExcep À partir de .NET Core 2.0.4.


tion

System.DirectoryServices.AccountManagement.PrincipalExists À partir de .NET Core 2.0.4.


Exception

System.DirectoryServices.AccountManagement.PrincipalOper À partir de .NET Core 2.0.4.


ationException

System.DirectoryServices.AccountManagement.PrincipalServe À partir de .NET Core 2.0.4.


rDownException

System.DirectoryServices.ActiveDirectory.ActiveDirectoryObje À partir de .NET Core 2.0.4.


ctExistsException

System.DirectoryServices.ActiveDirectory.ActiveDirectoryObje À partir de .NET Core 2.0.4.


ctNotFoundException
TYPE N OT ES

System.DirectoryServices.ActiveDirectory.ActiveDirectoryOper À partir de .NET Core 2.0.4.


ationException

System.DirectoryServices.ActiveDirectory.ActiveDirectoryServ À partir de .NET Core 2.0.4.


erDownException

System.DirectoryServices.ActiveDirectory.ForestTrustCollision À partir de .NET Core 2.0.4.


Exception

System.DirectoryServices.ActiveDirectory.SyncFromAllServers À partir de .NET Core 2.0.4.


OperationException

System.DirectoryServices.DirectoryServicesCOMException À partir de .NET Core 2.0.4.

System.DirectoryServices.Protocols.BerConversionException À partir de .NET Core 2.0.4.

System.DirectoryServices.Protocols.DirectoryException À partir de .NET Core 2.0.4.

System.DirectoryServices.Protocols.DirectoryOperationExcepti À partir de .NET Core 2.0.4.


on

System.DirectoryServices.Protocols.LdapException À partir de .NET Core 2.0.4.

System.DirectoryServices.Protocols.TlsOperationException À partir de .NET Core 2.0.4.

System.DivideByZeroException À partir de .NET Core 2.0.4.

System.DllNotFoundException À partir de .NET Core 2.0.4.

System.Double

System.Drawing.Color

System.Drawing.Point

System.Drawing.PointF

System.Drawing.Rectangle

System.Drawing.RectangleF

System.Drawing.Size

System.Drawing.SizeF

System.DuplicateWaitObjectException À partir de .NET Core 2.0.4.

System.EntryPointNotFoundException À partir de .NET Core 2.0.4.

System.Enum
TYPE N OT ES

System.EventArgs À partir de .NET Core 2.0.6.

System.Exception

System.ExecutionEngineException À partir de .NET Core 2.0.4.

System.FieldAccessException À partir de .NET Core 2.0.4.

System.FormatException À partir de .NET Core 2.0.4.

System.Globalization.CompareInfo

System.Globalization.CultureNotFoundException À partir de .NET Core 2.0.4.

System.Globalization.SortVersion

System.Guid

System.IO.Compression.ZLibException À partir de .NET Core 2.0.4.

System.IO.DriveNotFoundException À partir de .NET Core 2.0.4.

System.IO.EndOfStreamException À partir de .NET Core 2.0.4.

System.IO.FileFormatException À partir de .NET Core 2.0.4.

System.IO.FileLoadException À partir de .NET Core 2.0.4.

System.IO.FileNotFoundException À partir de .NET Core 2.0.4.

System.IO.IOException À partir de .NET Core 2.0.4.

System.IO.InternalBufferOverflowException À partir de .NET Core 2.0.4.

System.IO.InvalidDataException À partir de .NET Core 2.0.4.

System.IO.IsolatedStorage.IsolatedStorageException À partir de .NET Core 2.0.4.

System.IO.PathTooLongException À partir de .NET Core 2.0.4.

System.IndexOutOfRangeException À partir de .NET Core 2.0.4.

System.InsufficientExecutionStackException À partir de .NET Core 2.0.4.

System.InsufficientMemoryException À partir de .NET Core 2.0.4.

System.Int16

System.Int32
TYPE N OT ES

System.Int64

System.IntPtr

System.InvalidCastException À partir de .NET Core 2.0.4.

System.InvalidOperationException À partir de .NET Core 2.0.4.

System.InvalidProgramException À partir de .NET Core 2.0.4.

System.InvalidTimeZoneException À partir de .NET Core 2.0.4.

System.MemberAccessException À partir de .NET Core 2.0.4.

System.MethodAccessException À partir de .NET Core 2.0.4.

System.MissingFieldException À partir de .NET Core 2.0.4.

System.MissingMemberException À partir de .NET Core 2.0.4.

System.MissingMethodException À partir de .NET Core 2.0.4.

System.MulticastNotSupportedException À partir de .NET Core 2.0.4.

System.Net.Cookie

System.Net.CookieCollection

System.Net.CookieContainer

System.Net.CookieException À partir de .NET Core 2.0.4.

System.Net.HttpListenerException À partir de .NET Core 2.0.4.

System.Net.Mail.SmtpException À partir de .NET Core 2.0.4.

System.Net.Mail.SmtpFailedRecipientException À partir de .NET Core 2.0.4.

System.Net.Mail.SmtpFailedRecipientsException À partir de .NET Core 2.0.4.

System.Net.NetworkInformation.NetworkInformationExceptio À partir de .NET Core 2.0.4.


n

System.Net.NetworkInformation.PingException À partir de .NET Core 2.0.4.

System.Net.ProtocolViolationException À partir de .NET Core 2.0.4.

System.Net.Sockets.SocketException À partir de .NET Core 2.0.4.

System.Net.WebException À partir de .NET Core 2.0.4.


TYPE N OT ES

System.Net.WebSockets.WebSocketException À partir de .NET Core 2.0.4.

System.NotFiniteNumberException À partir de .NET Core 2.0.4.

System.NotImplementedException À partir de .NET Core 2.0.4.

System.NotSupportedException À partir de .NET Core 2.0.4.

System.NullReferenceException À partir de .NET Core 2.0.4.

System.Nullable<T>

System.Numerics.BigInteger

System.Numerics.Complex

System.Object

System.ObjectDisposedException À partir de .NET Core 2.0.4.

System.OperationCanceledException À partir de .NET Core 2.0.4.

System.OutOfMemoryException À partir de .NET Core 2.0.4.

System.OverflowException À partir de .NET Core 2.0.4.

System.PlatformNotSupportedException À partir de .NET Core 2.0.4.

System.RankException À partir de .NET Core 2.0.4.

System.Reflection.AmbiguousMatchException À partir de .NET Core 2.0.4.

System.Reflection.CustomAttributeFormatException À partir de .NET Core 2.0.4.

System.Reflection.InvalidFilterCriteriaException À partir de .NET Core 2.0.4.

System.Reflection.ReflectionTypeLoadException À partir de .NET Core 2.0.4.


La sérialisation de .NET Framework à .NET Core n’est pas
prise en charge.

System.Reflection.TargetException À partir de .NET Core 2.0.4.

System.Reflection.TargetInvocationException À partir de .NET Core 2.0.4.

System.Reflection.TargetParameterCountException À partir de .NET Core 2.0.4.

System.Resources.MissingManifestResourceException À partir de .NET Core 2.0.4.

System.Resources.MissingSatelliteAssemblyException À partir de .NET Core 2.0.4.


TYPE N OT ES

System.Runtime.CompilerServices.RuntimeWrappedException À partir de .NET Core 2.0.4.

System.Runtime.InteropServices.COMException À partir de .NET Core 2.0.4.

System.Runtime.InteropServices.ExternalException À partir de .NET Core 2.0.4.

System.Runtime.InteropServices.InvalidComObjectException À partir de .NET Core 2.0.4.

System.Runtime.InteropServices.InvalidOleVariantTypeExcepti À partir de .NET Core 2.0.4.


on

System.Runtime.InteropServices.MarshalDirectiveException À partir de .NET Core 2.0.4.

System.Runtime.InteropServices.SEHException À partir de .NET Core 2.0.4.

System.Runtime.InteropServices.SafeArrayRankMismatchExce À partir de .NET Core 2.0.4.


ption

System.Runtime.InteropServices.SafeArrayTypeMismatchExce À partir de .NET Core 2.0.4.


ption

System.Runtime.Serialization.InvalidDataContractException À partir de .NET Core 2.0.4.

System.Runtime.Serialization.SerializationException À partir de .NET Core 2.0.4.

System.SByte

System.Security.AccessControl.PrivilegeNotHeldException À partir de .NET Core 2.0.4.

System.Security.Authentication.AuthenticationException À partir de .NET Core 2.0.4.

System.Security.Authentication.InvalidCredentialException À partir de .NET Core 2.0.4.

System.Security.Cryptography.CryptographicException À partir de .NET Core 2.0.4.

System.Security.Cryptography.CryptographicUnexpectedOpe À partir de .NET Core 2.0.4.


rationException

À partir de .NET Core 2.0.4.


System.Security.Cryptography.Xml.CryptoSignedXmlRecursionException

System.Security.HostProtectionException À partir de .NET Core 2.0.4.

System.Security.Policy.PolicyException À partir de .NET Core 2.0.4.

System.Security.Principal.IdentityNotMappedException À partir de .NET Core 2.0.4.

System.Security.SecurityException À partir de .NET Core 2.0.4.


Données de sérialisation limitées.

System.Security.VerificationException À partir de .NET Core 2.0.4.


TYPE N OT ES

System.Security.XmlSyntaxException À partir de .NET Core 2.0.4.

System.ServiceProcess.TimeoutException À partir de .NET Core 2.0.4.

System.Single

System.StackOverflowException À partir de .NET Core 2.0.4.

System.String

System.StringComparer

System.SystemException À partir de .NET Core 2.0.4.

System.Text.DecoderFallbackException À partir de .NET Core 2.0.4.

System.Text.EncoderFallbackException À partir de .NET Core 2.0.4.

System.Text.RegularExpressions.RegexMatchTimeoutException À partir de .NET Core 2.0.4.

System.Text.StringBuilder

System.Threading.AbandonedMutexException À partir de .NET Core 2.0.4.

System.Threading.BarrierPostPhaseException À partir de .NET Core 2.0.4.

System.Threading.LockRecursionException À partir de .NET Core 2.0.4.

System.Threading.SemaphoreFullException À partir de .NET Core 2.0.4.

System.Threading.SynchronizationLockException À partir de .NET Core 2.0.4.

System.Threading.Tasks.TaskCanceledException À partir de .NET Core 2.0.4.

System.Threading.Tasks.TaskSchedulerException À partir de .NET Core 2.0.4.

System.Threading.ThreadAbortException À partir de .NET Core 2.0.4.

System.Threading.ThreadInterruptedException À partir de .NET Core 2.0.4.

System.Threading.ThreadStartException À partir de .NET Core 2.0.4.

System.Threading.ThreadStateException À partir de .NET Core 2.0.4.

System.Threading.WaitHandleCannotBeOpenedException À partir de .NET Core 2.0.4.

System.TimeSpan

System.TimeZoneInfo.AdjustmentRule
TYPE N OT ES

System.TimeZoneInfo

System.TimeZoneNotFoundException À partir de .NET Core 2.0.4.

System.TimeoutException À partir de .NET Core 2.0.4.

System.Transactions.TransactionAbortedException À partir de .NET Core 2.0.4.

System.Transactions.TransactionException À partir de .NET Core 2.0.4.

System.Transactions.TransactionInDoubtException À partir de .NET Core 2.0.4.

System.Transactions.TransactionManagerCommunicationExce À partir de .NET Core 2.0.4.


ption

System.Transactions.TransactionPromotionException À partir de .NET Core 2.0.4.

System.Tuple

System.TypeAccessException À partir de .NET Core 2.0.4.

System.TypeInitializationException À partir de .NET Core 2.0.4.

System.TypeLoadException À partir de .NET Core 2.0.4.

System.TypeUnloadedException À partir de .NET Core 2.0.4.

System.UInt16

System.UInt32

System.UInt64

System.UIntPtr

System.UnauthorizedAccessException À partir de .NET Core 2.0.4.

System.Uri

System.UriFormatException À partir de .NET Core 2.0.4.

System.ValueTuple Non sérialisable dans .NET Framework 4,7 et versions


antérieures.

System.ValueType

System.Version

System.WeakReference<T>
TYPE N OT ES

System.WeakReference

System.Xml.Schema.XmlSchemaException À partir de .NET Core 2.0.4.

System.Xml.Schema.XmlSchemaInferenceException À partir de .NET Core 2.0.4.

System.Xml.Schema.XmlSchemaValidationException À partir de .NET Core 2.0.4.

System.Xml.XPath.XPathException À partir de .NET Core 2.0.4.

System.Xml.XmlException À partir de .NET Core 2.0.4.

System.Xml.Xsl.XsltCompileException À partir de .NET Core 2.0.4.

System.Xml.Xsl.XsltException À partir de .NET Core 2.0.4.

Voir aussi
System.Runtime.Serialization
Contient des classes qui peuvent être utilisées pour sérialiser et désérialiser des objets.
Sérialisation XML et SOAP
Décrit le mécanisme de sérialisation XML inclus avec le Common Language Runtime.
Sécurité et sérialisation
Décrit les indications de codage sécurisé à suivre lors de l'écriture du code qui exécute la sérialisation.
.NET Remoting
Décrit les différentes méthodes à partir de .NET Framework pour les communications à distance.
Services Web XML créés à l’aide des clients de service Web XML et ASP.NET
Articles qui décrivent et expliquent comment programmer des services Web XML créés à l’aide de
ASP.NET.
Concepts de la sérialisation
18/07/2020 • 6 minutes to read • Edit Online

Pourquoi utiliser la sérialisation ? L'une des deux raisons majeures est que vous pouvez, d'une part, rendre
persistant l'état d'un objet sur un support de stockage afin qu'une copie exacte puisse être recréée ultérieurement.
D'autre part, vous pouvez envoyer cet objet par valeur d'un domaine d'application à l'autre. Par exemple, la
sérialisation est utilisée pour enregistrer l'état de session dans ASP.NET et copier des objets vers le Presse-papiers
dans les Windows Forms. Elle est également utilisée par la communication à distance pour passer des objets par
valeur d'un domaine d'application à un autre.

WARNING
La sérialisation binaire peut être dangereuse. Pour plus d’informations, consultez le Guide de la sécurité BinaryFormatter.

Stockage persistant
Il est souvent nécessaire de stocker la valeur des champs d'un objet sur un disque pour pouvoir récupérer ces
données ultérieurement. Bien que cela soit facile à accomplir sans utiliser la sérialisation, cette méthode est souvent
fastidieuse et susceptible d'entraîner des erreurs. Elle devient en outre de plus en plus complexe lorsque vous devez
suivre une hiérarchie d'objets. Imaginez que vous deviez enregistrer des données au sein d'une grande application
d'entreprise qui contient des milliers d'objets et que vous deviez écrire du code pour enregistrer et restaurer des
champs et propriétés sur et à partir d'un disque pour chaque objet. La sérialisation offre un mécanisme pratique
permettant d'atteindre cet objectif.
Le Common Language Runtime gère la manière dont les objets sont stockés en mémoire et fournit un mécanisme
de sérialisation automatisé par le biais de la réflexion. Lorsqu'un objet est sérialisé, le nom de la classe, l'assembly
et tous les membres de données de l'instance de classe sont écrits sur le support de stockage. Les objets stockent
souvent des références à d'autres instances dans les variables membres. Lorsque la classe est sérialisée, le moteur
de sérialisation effectue un suivi des objets référencés et déjà sérialisés, pour garantir qu'un même objet n'est pas
sérialisé plusieurs fois. L’architecture de sérialisation fournie avec le .NET Framework gère correctement les
graphiques d’objets et les références circulaires. La seule spécification définie pour les graphiques d’objets est que
tous les objets référencés par l’objet sérialisé doivent également être marqués comme Serializable (pour plus
d’informations, consultez Sérialisation de base). Si tel n'est pas le cas, une exception est levée lorsque le sérialiseur
tente de sérialiser l'objet non marqué.
Quand la classe sérialisée est désérialisée, elle est recréée et les valeurs de toutes les données membres sont
restaurées automatiquement.

Marshaler par valeur


Les objets sont uniquement valides dans le domaine d'application au sein duquel ils ont été créés. Toute tentative
de passer l’objet comme paramètre ou de le retourner comme résultat échoue si l’objet dérive de
MarshalByRefObject ou est marqué comme Serializable . Si l’objet est marqué comme Serializable , il est
automatiquement sérialisé, passé du domaine d’application à l’autre, puis désérialisé pour en générer une copie
exacte dans le deuxième domaine d’application. Ce processus est en général connu sous le nom de marshaling par
valeur.
Quand un objet dérive de MarshalByRefObject , une référence de l’objet (et non l’objet lui-même) est passée d’un
domaine d’application à un autre. Vous pouvez également marquer un objet qui dérive de MarshalByRefObject
comme Serializable . Quand cet objet est utilisé avec la communication à distance, le formateur responsable de la
sérialisation, préconfiguré avec un sélecteur de substitut ( SurrogateSelector ), prend le contrôle du processus de
sérialisation et remplace tous les objets dérivés de MarshalByRefObject par un proxy. Si SurrogateSelector n’est pas
défini, l’architecture de sérialisation suit les règles de sérialisation standard décrites dans Étapes du processus de
sérialisation.

Sections connexes
Sérialisation binaire
Décrit le mécanisme de sérialisation binaire inclus avec le Common Language Runtime.
.NET Remoting
Décrit les différentes méthodes de communication disponibles dans le .NET Framework pour les communications
distantes.
Sérialisation XML et SOAP
Décrit le mécanisme de sérialisation XML et SOAP inclus avec le Common Language Runtime.
Sérialisation de base
18/07/2020 • 5 minutes to read • Edit Online

WARNING
La sérialisation binaire peut être dangereuse. Pour plus d’informations, consultez le Guide de la sécurité BinaryFormatter.

La façon la plus simple de rendre une classe sérialisable est de la marquer avec l’attribut SerializableAttribute
comme suit.

[Serializable]
public class MyObject {
public int n1 = 0;
public int n2 = 0;
public String str = null;
}

L’exemple de code suivant illustre comment une instance de cette classe peut être sérialisée dans un fichier.

MyObject obj = new MyObject();


obj.n1 = 1;
obj.n2 = 24;
obj.str = "Some String";
IFormatter formatter = new BinaryFormatter();
Stream stream = new FileStream("MyFile.bin", FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, obj);
stream.Close();

Cet exemple utilise un formateur binaire pour effectuer la sérialisation. Il vous suffit de créer une instance du flux
de données et du formateur que vous envisagez d’utiliser, puis d’appeler la méthode Serialize sur le formateur. Le
flux de données et l'objet à sérialiser sont fournis comme paramètres de cet appel. Bien que cela n'apparaisse pas
explicitement dans cet exemple, toutes les variables membres d'une classe seront sérialisées, même celles
marquées comme privées. Dans ce cadre, la sérialisation binaire diffère de la classe XmlSerializer, qui sérialise
uniquement des champs publics. Pour plus d’informations sur l’exclusion de variables membres de la sérialisation
binaire, consultez Sérialisation sélective.
La restauration de l'objet à son état précédent est très facile. En premier lieu, créez un flux servant à la lecture et un
Formatter, puis demandez au formateur de désérialiser l’objet. L'exemple de code suivant illustre cette procédure.

IFormatter formatter = new BinaryFormatter();


Stream stream = new FileStream("MyFile.bin", FileMode.Open, FileAccess.Read, FileShare.Read);
MyObject obj = (MyObject) formatter.Deserialize(stream);
stream.Close();

// Here's the proof.


Console.WriteLine("n1: {0}", obj.n1);
Console.WriteLine("n2: {0}", obj.n2);
Console.WriteLine("str: {0}", obj.str);

Le BinaryFormatter utilisé ci-dessus est très efficace et génère un flux d’octets compact. Ce formateur permet aussi
bien de sérialiser que de désérialiser des objets, ce qui en fait l'outil idéal pour sérialiser des objets qui seront
désérialisés sur le .NET Framework. Il est important de noter que les constructeurs ne sont pas appelés lorsqu'un
objet est désérialisé. Pour des raisons de performances, la désérialisation repose sur cette contrainte. Toutefois, cela
enfreint les termes de certains contrats habituels que l'exécution passe avec le writer d'objets et les développeurs
doivent s'assurer qu'ils comprennent les ramifications lorsqu'ils marquent un objet comme sérialisable.
Si la portabilité est une exigence, utilisez plutôt le SoapFormatter. Remplacez simplement le Binar yFormatter
dans le code ci-dessus par SoapFormatter et appelez Serialize et Deserialize comme précédemment. Ce
formateur génère le résultat suivant pour l'exemple utilisé ci-dessus.

<SOAP-ENV:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:a1="http://schemas.microsoft.com/clr/assem/ToFile">

<SOAP-ENV:Body>
<a1:MyObject id="ref-1">
<n1>1</n1>
<n2>24</n2>
<str id="ref-3">Some String</str>
</a1:MyObject>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Il est important de noter que l’attribut Serializable ne peut pas être hérité. Si vous dérivez une nouvelle classe à
partir de MyObject , elle doit également être marquée avec l'attribut. Sinon, elle ne peut pas être sérialisée. Par
exemple, quand vous tentez de sérialiser une instance de la classe ci-dessous, vous obtenez un
SerializationException vous informant que le type MyStuff n’est pas marqué comme sérialisable.

public class MyStuff : MyObject


{
public int n3;
}

L’utilisation de l’attribut Serializable est pratique, mais comporte des limites comme illustré précédemment.
Reportez-vous aux Indications concernant la sérialisation pour savoir à quel moment il convient de marquer une
classe en vue de la sérialisation. La sérialisation ne peut pas être ajoutée à une classe une fois que cette dernière a
été compilée.

Voir aussi
Sérialisation binaire
Sérialisation XML et SOAP
Sérialisation sélective
18/07/2020 • 2 minutes to read • Edit Online

Une classe contient souvent des champs qui ne doivent pas être sérialisés. Par exemple, supposez qu'une classe
stocke un ID de thread dans une variable membre. Quand la classe est désérialisée, il se peut que le thread stocké
pour l’ID au moment de la sérialisation de la classe ne s’exécute plus. La sérialisation de cette valeur est donc
inutile. Vous pouvez empêcher des variables membres d’être sérialisées en les marquant avec l’attribut
NonSerialized comme suit.

[Serializable]
public class MyObject
{
public int n1;
[NonSerialized] public int n2;
public String str;
}

Si possible, faites en sorte qu'un objet pouvant contenir des données de sécurité sensibles soit non sérialisable. Si
l’objet doit être sérialisé, appliquez l’attribut NonSerialized aux champs spécifiques qui stockent des données
sensibles. Si vous n’excluez pas ces champs de la sérialisation, notez que les données qu’ils stockent sont exposées
à tout code autorisé à sérialiser. Pour plus d’informations sur l’écriture de code de sérialisation sécurisé, consultez
Sécurité et sérialisation.

WARNING
La sérialisation binaire peut être dangereuse. Pour plus d’informations, consultez le Guide de la sécurité BinaryFormatter.

Voir aussi
Sérialisation binaire
Sérialisation XML et SOAP
Sécurité et sérialisation
Sérialisation personnalisée
18/07/2020 • 14 minutes to read • Edit Online

La sérialisation personnalisée est le processus de contrôle de la sérialisation et désérialisation d'un type. En


contrôlant la sérialisation, il est possible de garantir la compatibilité de sérialisation (capacité de sérialisation et
désérialisation entre des versions d’un type) sans interrompre la fonctionnalité principale du type. Par exemple, il
peut y avoir uniquement deux champs dans la première version d'un type. Dans la version suivante d'un type,
plusieurs champs supplémentaires sont ajoutés. La deuxième version d'une application doit néanmoins être en
mesure de sérialiser et désérialiser les deux types. Les sections suivantes décrivent comment contrôler la
sérialisation.

WARNING
La sérialisation binaire peut être dangereuse. Pour plus d’informations, consultez le Guide de la sécurité BinaryFormatter.

IMPORTANT
Dans les versions antérieures au .NET Framework 4.0, la sérialisation de données utilisateur personnalisées dans un assembly
d’un niveau de confiance partiel était obtenue à l’aide de GetObjectData. Depuis la version 4.0, cette méthode est marquée
avec l'attribut SecurityCriticalAttribute, ce qui empêche l'exécution dans les assemblys d'un niveau de confiance partiel. Pour
contourner cette condition, implémentez l'interface ISafeSerializationData.

Exécution de méthodes personnalisées pendant et après la sérialisation


La méthode conseillée la plus simple (présentée dans la version 2.0 du .NET Framework) consiste à appliquer les
attributs suivants aux méthodes utilisées pour corriger des données pendant et après la sérialisation :
OnDeserializedAttribute
OnDeserializingAttribute
OnSerializedAttribute
OnSerializingAttribute
Ces attributs permettent au type de participer à l'une ou à l'ensemble des quatre phases des processus de
sérialisation et de désérialisation. Les attributs spécifient les méthodes du type qui doivent être invoquées pendant
chaque phase. Les méthodes n'accèdent pas au flux de données de sérialisation mais vous permettent en revanche
de modifier l'objet avant et après la sérialisation, ou avant et après la désérialisation. Les attributs peuvent être
appliqués à tous les niveaux de la hiérarchie d'héritage de type, et chaque méthode est appelée dans la hiérarchie
de la base au plus dérivé. Ce mécanisme évite la complexité et tous les problèmes résultant de l'implémentation de
l'interface ISerializable en rendant l'implémentation la plus dérivée responsable de la sérialisation et de la
désérialisation. En outre, ce mécanisme permet aux formateurs d'ignorer le remplissage de champs et la
récupération du flux de données de sérialisation. Pour obtenir des informations et des exemples concernant le
contrôle de la sérialisation et de la désérialisation, cliquez sur l'un des liens précédents.
De plus, lorsque vous ajoutez un nouveau champ à un type sérialisable existant, appliquez l'attribut
OptionalFieldAttribute au champ. BinaryFormatter et SoapFormatter ignorent l'absence du champ lorsqu'un flux de
données dont le nouveau champ est manquant est traité.
Implémentation de l’interface ISerializable
L’autre méthode permettant de contrôler la sérialisation s’effectue en implémentant l’interface ISerializable sur un
objet. Toutefois, notez que la méthode de la section précédente remplace cette méthode pour contrôler la
sérialisation.
De plus, vous ne devez pas utiliser la sérialisation par défaut sur une classe marquée avec l’attribut Serializable et
présentant une sécurité déclarative ou impérative au niveau de la classe ou sur ses constructeurs. En revanche, ces
classes doivent toujours implémenter l'interface ISerializable.
L’implémentation de ISerializable implique l’implémentation de la méthode GetObjectData et l’utilisation d’un
constructeur spécial quand l’objet est désérialisé. L'exemple de code suivant indique comment implémenter
ISerializable sur la classe MyObject d'une section précédente.

[Serializable]
public class MyObject : ISerializable
{
public int n1;
public int n2;
public String str;

public MyObject()
{
}

protected MyObject(SerializationInfo info, StreamingContext context)


{
n1 = info.GetInt32("i");
n2 = info.GetInt32("j");
str = info.GetString("k");
}

[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]


public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("i", n1);
info.AddValue("j", n2);
info.AddValue("k", str);
}
}
<Serializable()> _
Public Class MyObject
Implements ISerializable
Public n1 As Integer
Public n2 As Integer
Public str As String

Public Sub New()


End Sub

Protected Sub New(ByVal info As SerializationInfo, ByVal context As StreamingContext)


n1 = info.GetInt32("i")
n2 = info.GetInt32("j")
str = info.GetString("k")
End Sub 'New

<SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter := True)> _


Public Overridable Sub GetObjectData(ByVal info As SerializationInfo, ByVal context As StreamingContext)
info.AddValue("i", n1)
info.AddValue("j", n2)
info.AddValue("k", str)
End Sub
End Class

Quand GetObjectData est appelé pendant la sérialisation, vous êtes chargé de remplir le SerializationInfo fourni
avec l’appel à méthode. Ajoutez les variables à sérialiser sous forme de paires de noms et de valeurs. Tout texte
peut être utilisé comme nom. Vous pouvez décider quelles variables membres sont ajoutées au SerializationInfo, à
condition qu'il y ait suffisamment de données sérialisées pour restaurer l'objet pendant la désérialisation. Les
classes dérivées doivent appeler la méthode GetObjectData sur l’objet de base si ce dernier implémente
ISerializable.
Notez que la sérialisation peut permettre à un autre code d'afficher ou de modifier des données d'instance d'objet
qui seraient autrement inaccessibles. Par conséquent, le code qui exécute la sérialisation exige SecurityPermission
avec l’indicateur SerializationFormatter défini. Dans le cadre de la stratégie par défaut, cette autorisation n'est pas
accordée à du code téléchargé depuis Internet ou un intranet ; seul le code sur l'ordinateur local reçoit cette
autorisation. La méthode GetObjectData doit être protégée explicitement en exigeant SecurityPermission avec
l’indicateur SerializationFormatter défini ou en exigeant d’autres autorisations qui permettent de protéger
spécifiquement des données privées.
Si un champ privé stocke des informations sensibles, vous devez exiger les autorisations appropriées sur
GetObjectData pour protéger les données. Rappelez-vous que le code auquel a été attribuée l’autorisation
SecurityPermission avec l’indicateur SerializationFormatter défini peut afficher et modifier les données stockées
dans les champs privés. Un appelant malveillant qui a obtenu l’autorisation SecurityPermission peut consulter des
données telles que les emplacements de répertoire cachés ou les autorisations accordées, et utiliser ces données
pour exploiter une faille de sécurité sur l’ordinateur. Pour obtenir une liste complète des indicateurs d’autorisation
de sécurité que vous pouvez spécifier, consultez l’énumération SecurityPermissionFlag.
Il convient de souligner que quand ISerializable est ajouté à une classe, vous devez implémenter GetObjectData et
le constructeur spécial. Le compilateur vous avertit si GetObjectData est manquant. Toutefois, parce qu'il est
impossible de mettre en œuvre l'implémentation d'un constructeur, aucun avertissement n'est fourni si le
constructeur est absent, et une exception est levée lorsqu'une tentative est faite pour désérialiser une classe sans le
constructeur.
La conception actuelle a priorité sur une méthode SetObjectData pour contourner les problèmes potentiels de
sécurité et de versioning. Par exemple, une méthode SetObjectData doit être publique si elle est définie dans le
cadre d’une interface. Ainsi, les utilisateurs doivent écrire du code pour éviter que la méthode SetObjectData soit
appelée plusieurs fois. Sinon, une application malveillante qui appelle la méthode SetObjectData sur un objet lors
de l’exécution d’une opération peut provoquer des problèmes potentiels.
Pendant la désérialisation, SerializationInfo est transmis à la classe à l'aide du constructeur prévu à cette fin. Toutes
les contraintes de visibilité imposées au constructeur sont ignorées lorsque l'objet est désérialisé. Vous pouvez
donc marquer la classe comme publique, protégée, interne ou privée. Toutefois, la méthode conseillée consiste à
protéger le constructeur tant que la classe n'est pas scellée. Le cas échéant, le constructeur doit être marqué comme
privé. Le constructeur doit également exécuter une validation de saisie complète. Pour éviter une utilisation
incorrecte par un code malveillant, le constructeur doit appliquer les mêmes vérifications de sécurité et
autorisations nécessaires pour obtenir une instance de la classe utilisant tout autre constructeur. Si vous ne
respectez pas cette recommandation, le code malveillant peut présérialiser un objet, obtenir le contrôle avec
SecurityPermission avec l’indicateur SerializationFormatter défini, et désérialiser l’objet sur un ordinateur client en
contournant toute sécurité qui aurait été appliquée pendant la construction d’instance standard à l’aide d’un
constructeur public.
Pour restaurer l'état de l'objet, récupérez simplement les valeurs des variables de SerializationInfo à l'aide des
noms utilisés pendant la sérialisation. Si la classe de base implémente ISerializable, le constructeur de base doit être
appelé pour permettre à l'objet de base de restaurer ses variables.
Quand vous dérivez une nouvelle classe d’une classe qui implémente ISerializable, la classe dérivée doit
implémenter aussi bien le constructeur que la méthode GetObjectData si elle a des variables qui doivent être
sérialisées. L'exemple de code suivant illustre la procédure à l'aide de la classe MyObject montrée précédemment.

[Serializable]
public class ObjectTwo : MyObject
{
public int num;

public ObjectTwo()
: base()
{
}

protected ObjectTwo(SerializationInfo si, StreamingContext context)


: base(si, context)
{
num = si.GetInt32("num");
}

[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]


public override void GetObjectData(SerializationInfo si, StreamingContext context)
{
base.GetObjectData(si,context);
si.AddValue("num", num);
}
}
<Serializable()> _
Public Class ObjectTwo
Inherits MyObject
Public num As Integer

Public Sub New()

End Sub

Protected Sub New(ByVal si As SerializationInfo, _


ByVal context As StreamingContext)
MyBase.New(si, context)
num = si.GetInt32("num")
End Sub

<SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter := True)> _


Public Overrides Sub GetObjectData(ByVal si As SerializationInfo, ByVal context As StreamingContext)
MyBase.GetObjectData(si, context)
si.AddValue("num", num)
End Sub
End Class

N’oubliez pas d’appeler la classe de base dans le constructeur de désérialisation. Sinon, le constructeur de la classe
de base n’est jamais appelé et l’objet n’est pas entièrement construit après la désérialisation.
Les objets sont reconstruits à l'envers et les méthodes d'appel au cours de la désérialisation peuvent avoir des
effets secondaires indésirables, car les méthodes appelées peuvent se rapporter aux références d'objet qui n'ont
pas été désérialisées au moment de l'appel. Si la classe qui est désérialisée implémente IDeserializationCallback, la
méthode OnDeserialization est appelée automatiquement une fois le graphique d’objets entier désérialisé. À ce
stade, tous les objets enfants référencés ont été restaurés complètement. Une table de hachage est un exemple
typique d'une classe difficile à désérialiser sans utiliser l'écouteur d'événements. Il est facile de récupérer les clés et
valeurs paires pendant la désérialisation, mais l'ajout de ces objets à la table de hachage peut provoquer des
problèmes. En effet, il n'y a aucune garantie que les classes dérivées de la table de hachage sont désérialisées. Par
conséquent, les méthodes d'appel sur une table de hachage ne sont pas recommandées à ce stade.

Voir aussi
Sérialisation binaire
Sérialisation XML et SOAP
Sécurité et sérialisation
Étapes du processus de sérialisation
18/07/2020 • 2 minutes to read • Edit Online

Quand la méthode Serialize est appelée sur un formateur, la sérialisation de l’objet s’effectue d’après la séquence
de règles suivante :
Un contrôle est effectué pour déterminer si le formateur possède un sélecteur de substitut. Si tel est le cas,
vérifiez si le sélecteur de substitut gère des objets du type donné. Si le sélecteur gère le type d’objet,
ISerializable.GetObjectData est appelée sur le sélecteur de substitution.
S’il n’existe aucun sélecteur de substitution ou s’il ne gère pas ce type d’objet, un contrôle est effectué pour
déterminer si l’objet est marqué avec l’attribut Serializable. S’il ne l’est pas, une exception
SerializationException est levée.
Si l’objet est marqué convenablement, vérifiez s’il implémente l’interface ISerializable. Si tel est le cas,
GetObjectData est appelée sur l’objet.
Si l’objet n’implémente pas ISerializable, la stratégie de sérialisation par défaut est utilisée. Elle permet de
sérialiser tous les champs non marqués comme NonSerialized.

WARNING
La sérialisation binaire peut être dangereuse. Pour plus d’informations, consultez le Guide de la sécurité BinaryFormatter.

Voir aussi
Sérialisation binaire
Sérialisation XML et SOAP
Sérialisation avec tolérance de version
18/07/2020 • 12 minutes to read • Edit Online

Dans les versions 1.0 et 1.1 du .NET Framework, la création de types sérialisables pouvant être réutilisés d'une
version d'application à l'autre était problématique. Si un type était modifié via l'ajout de champs supplémentaires,
les problèmes suivants étaient susceptibles de se produire :
Les versions antérieures d'une application pouvaient lever des exceptions lorsque l'utilisateur tentait de
désérialiser les nouvelles versions du type précédent.
Les versions plus récentes d'une application pouvaient lever des exceptions lors de la désérialisation de versions
antérieures d'un type ayant des données manquantes.
La Sérialisation avec tolérance de version (VTS) comprend un ensemble de fonctionnalités introduites dans .NET
Framework 2.0 et qui simplifient, au fil du temps, la modification des types sérialisables. Plus précisément, les
fonctions VTS sont activées pour les classes auxquelles l'attribut SerializableAttribute a été appliqué, y compris des
types génériques. VTS permet d'ajouter de nouveaux champs à ces classes en assurant la compatibilité avec
d'autres versions du type. Pour obtenir un exemple d’application opérationnelle, consultez Sérialisation avec
tolérance de version, exemple de technologie.
Les fonctions VTS sont activées lors de l'utilisation de BinaryFormatter. En outre, toutes les fonctionnalités, hormis
la tolérance de données étrangères, sont également activées lors de l'utilisation de SoapFormatter. Pour plus
d’informations sur l’utilisation de ces classes pour la sérialisation, consultez Sérialisation binaire.

WARNING
La sérialisation binaire peut être dangereuse. Pour plus d’informations, consultez le Guide de la sécurité BinaryFormatter.

Liste des fonctionnalités


Cette liste comprend les fonctionnalités suivantes :
Tolérance de données étrangères ou inattendues. Cette fonctionnalité permet aux versions plus récentes du type
d'envoyer des données aux versions antérieures.
Tolérance de données facultatives manquantes. Cette fonctionnalité permet aux versions antérieures d'envoyer
des données aux versions plus récentes.
Rappels de sérialisation. Cette fonctionnalité active le paramètre de valeur par défaut intelligent en cas de
données manquantes.
Une fonctionnalité permet en outre de générer une déclaration lors de l'ajout d'un nouveau champ facultatif. Il
s'agit de la propriété VersionAdded de l'attribut OptionalFieldAttribute.
Ces fonctionnalités sont abordées plus en détail dans les sections suivantes.
Tolérance de données étrangères ou inattendues
Auparavant, lors de la désérialisation, toute donnée étrangère ou inattendue entraînait la levée d'exceptions.
Avec VTS, dans la même situation, toutes les données étrangères ou inattendues sont ignorées au lieu de
provoquer la levée d'exceptions. Cela permet aux applications qui utilisent des versions plus récentes d'un type
(autrement dit, une version qui inclut plus de champs) d'envoyer des informations aux applications qui nécessitent
des versions antérieures du même type.
Dans l'exemple suivant, les données supplémentaires contenues dans le champ CountryField de la version 2.0 de
la classe Address sont ignorées lorsqu'une application plus ancienne désérialise la version plus récente.

// Version 1 of the Address class.


[Serializable]
public class Address
{
public string Street;
public string City;
}
// Version 2.0 of the Address class.
[Serializable]
public class Address
{
public string Street;
public string City;
// The older application ignores this data.
public string CountryField;
}

' Version 1 of the Address class.


<Serializable> _
Public Class Address
Public Street As String
Public City As String
End Class

' Version 2.0 of the Address class.


<Serializable> _
Public Class Address
Public Street As String
Public City As String
' The older application ignores this data.
Public CountryField As String
End Class

Tolérance de données manquantes


Les champs peuvent être marqués comme facultatifs en leur appliquant l'attribut OptionalFieldAttribute. Lors de la
désérialisation, si des données facultatives sont manquantes, le moteur de sérialisation ignore leur absence et ne
lève aucune exception. Les applications qui nécessitent des versions antérieures d'un type peuvent ainsi envoyer
des données aux applications qui nécessitent des versions plus récentes du même type.
L'exemple suivant illustre la version 2.0 de la classe Address , avec le champ CountryField marqué comme
facultatif. Si une application plus ancienne envoie la version 1 à une application plus récente qui nécessite la
version 2.0, l'absence des données est ignorée.

[Serializable]
public class Address
{
public string Street;
public string City;

[OptionalField]
public string CountryField;
}
<Serializable> _
Public Class Address
Public Street As String
Public City As String

<OptionalField> _
Public CountryField As String
End Class

Rappels de sérialisation
Les rappels de sérialisation correspondent à un mécanisme qui fournit des raccordements lors du processus de
sérialisation/désérialisation au niveau de quatre points.

LO RSQ UE L A M ÉT H O DE A SSO C IÉE EST


AT T RIB UT A P P EL ÉE UT IL ISAT IO N C O URA N T E

OnDeserializingAttribute Avant la désérialisation.* Initialisez des valeurs par défaut pour


les champs facultatifs.

OnDeserializedAttribute Après la désérialisation. Corrigez des valeurs de champ


facultatives selon le contenu d'autres
champs.

OnSerializingAttribute Avant la sérialisation. Préparez la sérialisation. Par exemple,


créez des structures de données
facultatives.

OnSerializedAttribute Après la sérialisation. Enregistrez des événements de


sérialisation.

* Ce rappel est appelé avant le constructeur de désérialisation, le cas échéant.


Utilisation de rappels
Pour utiliser des rappels, appliquez l'attribut approprié à une méthode qui accepte un paramètre StreamingContext.
Une seule méthode par classe peut être marquée avec chacun de ces attributs. Par exemple :

[OnDeserializing]
private void SetCountryRegionDefault(StreamingContext sc)
{
CountryField = "Japan";
}

<OnDeserializing>
Private Sub SetCountryRegionDefault(sc As StreamingContext)
CountryField = "Japan"
End Sub

Ces méthodes sont destinées à être utilisées pour le versioning. Pendant la désérialisation, un champ facultatif ne
peut pas être initialisé correctement si les données du champ sont manquantes. Cela peut être corrigé en créant la
méthode qui assigne la valeur correcte, puis en appliquant l’attribut OnDeserializingAttribute ou
OnDeserializedAttribute à la méthode.
L'exemple suivant illustre la méthode dans le cadre d'un type. Si une version antérieure d'une application envoie
une instance de la classe Address à une version ultérieure de l'application, les données du champ CountryField
sont omises. Mais après la désérialisation, le champ est défini sur une valeur par défaut « Japan ».
[Serializable]
public class Address
{
public string Street;
public string City;
[OptionalField]
public string CountryField;

[OnDeserializing]
private void SetCountryRegionDefault(StreamingContext sc)
{
CountryField = "Japan";
}
}

<Serializable> _
Public Class Address
Public Street As String
Public City As String
<OptionalField> _
Public CountryField As String

<OnDeserializing> _
Private Sub SetCountryRegionDefault(sc As StreamingContext)
CountryField = "Japan"
End Sub
End Class

Propriété VersionAdded
OptionalFieldAttribute a la propriété VersionAdded . Dans la version 2.0 de .NET Framework, cette propriété
n’est pas utilisée. Toutefois, il est important de définir correctement cette propriété pour garantir la compatibilité du
type avec les futurs moteurs de sérialisation.
La propriété indique quelle version d'un type a été ajoutée à un champ donné. Elle doit être incrémentée d'une
seule valeur (à partir de 2) à chaque fois que le type est modifié, comme illustré dans l'exemple suivant :
// Version 1.0
[Serializable]
public class Person
{
public string FullName;
}

// Version 2.0
[Serializable]
public class Person
{
public string FullName;

[OptionalField(VersionAdded = 2)]
public string NickName;
[OptionalField(VersionAdded = 2)]
public DateTime BirthDate;
}

// Version 3.0
[Serializable]
public class Person
{
public string FullName;

[OptionalField(VersionAdded=2)]
public string NickName;
[OptionalField(VersionAdded=2)]
public DateTime BirthDate;

[OptionalField(VersionAdded=3)]
public int Weight;
}

' Version 1.0


<Serializable> _
Public Class Person
Public FullName
End Class

' Version 2.0


<Serializable> _
Public Class Person
Public FullName As String

<OptionalField(VersionAdded := 2)> _
Public NickName As String
<OptionalField(VersionAdded := 2)> _
Public BirthDate As DateTime
End Class

' Version 3.0


<Serializable> _
Public Class Person
Public FullName As String

<OptionalField(VersionAdded := 2)> _
Public NickName As String
<OptionalField(VersionAdded := 2)> _
Public BirthDate As DateTime

<OptionalField(VersionAdded := 3)> _
Public Weight As Integer
End Class
SerializationBinder
Certains utilisateurs doivent contrôler la classe à sérialiser et désérialiser, car une version différente de la classe est
requise sur le serveur et le client. SerializationBinder est une classe abstraite utilisée pour contrôler les types réels
utilisés lors de la sérialisation et de la désérialisation. Pour tirer parti de cette classe, dérivez une classe de
SerializationBinder et substituez une méthode aux deux suivantes : BindToName et BindToType. Pour plus
d’informations, consultez contrôle de la sérialisation et de la désérialisation avec SerializationBinder.

Meilleures pratiques
Pour garantir un comportement de versioning correct, suivez les règles ci-dessous lors de la modification d'un type
d'une version à l'autre :
Ne supprimez jamais un champ sérialisé.
N'appliquez jamais l'attribut NonSerializedAttribute à un champ si cet attribut n'a pas été appliqué au champ
dans la version antérieure.
Ne modifiez jamais le nom ou le type d'un champ sérialisé.
Quand vous ajoutez un nouveau champ sérialisé, appliquez l’attribut OptionalFieldAttribute .
Quand vous supprimez un attribut NonSerializedAttribute d’un champ (qui n’était pas sérialisable dans une
version antérieure), appliquez l’attribut OptionalFieldAttribute .
Assignez des valeurs par défaut significatives à tous les champs facultatifs à l’aide des rappels de sérialisation,
sauf si les valeurs par défaut 0 ou null sont acceptables.
Pour garantir la compatibilité d'un type avec les futurs moteurs de sérialisation, suivez ces indications :
Définissez toujours la propriété VersionAdded sur l’attribut OptionalFieldAttribute de manière appropriée.
Évitez le versioning avec des branches.

Voir aussi
SerializableAttribute
BinaryFormatter
SoapFormatter
VersionAdded
OptionalFieldAttribute
OnDeserializingAttribute
OnDeserializedAttribute
OnSerializingAttribute
OnSerializedAttribute
StreamingContext
NonSerializedAttribute
Sérialisation binaire
Indications concernant la sérialisation
18/07/2020 • 19 minutes to read • Edit Online

Ce document répertorie les indications à prendre en compte lors de la conception d'une API à sérialiser.

WARNING
La sérialisation binaire peut être dangereuse. Pour plus d’informations, consultez le Guide de la sécurité BinaryFormatter.

.NET offre trois technologies de sérialisation principales qui sont optimisées pour différents scénarios de
sérialisation. Le tableau suivant répertorie ces technologies et les principaux types .NET qui leur sont associés.

T EC H N O LO GY C L A SSES C O N C ERN ÉES N OT ES

Sérialisation du contrat de données DataContractAttribute Persistance générale

DataMemberAttribute Services Web

DataContractSerializer JSON

NetDataContractSerializer

DataContractJsonSerializer

ISerializable

Sérialisation XML XmlSerializer Format XML


avec contrôle total

Sérialisation du runtime (binaire et SerializableAttribute .NET Remoting


SOAP)
ISerializable

BinaryFormatter

SoapFormatter

Lorsque vous concevez de nouveaux types, vous devez décider laquelle de ces technologies, le cas échéant, doit
être prise en charge par ces types. Les indications suivantes expliquent comment effectuer ce choix et fournir cette
prise en charge. Elles ne sont pas destinées à vous aider à choisir la technologie de sérialisation que vous devez
utiliser dans l'implémentation de votre application ou bibliothèque. Elles ne sont pas non plus directement liées à
la conception d'une API et par conséquent, elles ne relèvent pas du propos de cette rubrique.

Recommandations
PENSEZ à la sérialisation lorsque vous concevez de nouveaux types.
La sérialisation constitue une considération de conception importante pour tout type, car les programmes
devront peut-être rendre persistantes ou transmettre des instances du type.
Choix de la technologie de sérialisation appropriée à prendre en charge
Tout type donné peut prendre en charge une ou plusieurs des technologies de sérialisation ou aucune d'entre elles.
ENVISAGEZ la prise en charge de la sérialisation du contrat de données si des instances de votre type
doivent être rendues persistantes ou utilisées dans des services web.
ENVISAGEZ la prise en charge de la sérialisation XML à la place ou en plus de la sérialisation du contrat de
données si vous devez davantage contrôler le format XML généré lors de la sérialisation du type.
Cela peut être nécessaire dans certains scénarios d'interopérabilité où vous devez utiliser une construction
XML qui n'est pas prise en charge par la sérialisation du contrat de données, par exemple, pour générer des
attributs XML.
ENVISAGEZ la prise en charge de la sérialisation du runtime si des instances de votre type doivent passer
des limites .NET Remoting.
ÉVITEZ de prendre en charge la sérialisation XML ou du runtime uniquement pour des raisons de
persistance. Préférez plutôt la sérialisation du contrat de données.
Prise en charge de la sérialisation du contrat de données
Les types peuvent prendre en charge la sérialisation du contrat de données en appliquant DataContractAttribute au
type et DataMemberAttribute aux membres (champs et propriétés) du type.

[DataContract]
class Person
{

[DataMember]
string LastName { get; set; }
[DataMember]
string FirstName { get; set; }

public Person(string firstNameValue, string lastNameValue)


{
FirstName = firstNameValue;
LastName = lastNameValue;
}
}

<DataContract()> Public Class Person


<DataMember()> Public Property LastName As String
<DataMember()> Public Property FirstName As String

Public Sub New(ByVal firstNameValue As String, ByVal lastNameValue As String)


FirstName = firstNameValue
LastName = lastNameValue
End Sub

End Class

1. ENVISAGEZ de marquer les données membres de votre type public si le type peut être utilisé en mode de
confiance partielle. En mode de confiance totale, les sérialiseurs de contrat de données peuvent sérialiser et
désérialiser les types et les membres non publics, mais seuls les membres publics peuvent être sérialisés et
désérialisés en mode de confiance partielle.
2. IMPLÉMENTEZ un accesseur Get et Set sur toutes les propriétés pour lesquelles Data-MemberAttribute est
défini. Les sérialiseurs de contrat de données nécessitent à la fois l'accesseur Get et Set pour que le type soit
considéré comme étant sérialisable. Si le type n'est pas utilisé en mode de confiance partielle, un des
accesseurs de propriété, ou les deux, peuvent être non publics.
[DataContract]
class Person2
{

string lastName;
string firstName;

public Person2(string firstName, string lastName)


{
this.lastName = lastName;
this.firstName = firstName;
}

[DataMember]
public string LastName
{
// Implement get and set.
get { return lastName; }
private set { lastName = value; }
}

[DataMember]
public string FirstName
{
// Implement get and set.
get { return firstName; }
private set { firstName = value; }
}
}

<DataContract()> Class Person2


Private lastNameValue As String
Private firstNameValue As String

Public Sub New(ByVal firstName As String, ByVal lastName As String)


Me.lastNameValue = lastName
Me.firstNameValue = firstName
End Sub

<DataMember()> Property LastName As String


Get
Return lastNameValue
End Get

Set(ByVal value As String)


lastNameValue = value
End Set

End Property

<DataMember()> Property FirstName As String


Get
Return firstNameValue

End Get
Set(ByVal value As String)
firstNameValue = value
End Set
End Property

End Class

3. ENVISAGEZ d'utiliser les rappels de sérialisation pour l'initialisation des instances désérialisées.
Les constructeurs ne sont pas appelés lorsque les objets sont désérialisés. Par conséquent, toute logique qui
s’exécute pendant une construction normale doit être implémentée comme l’un des rappels de sérialisation.

[DataContract]
class Person3
{
[DataMember]
string lastName;
[DataMember]
string firstName;
string fullName;

public Person3(string firstName, string lastName)


{
// This constructor is not called during deserialization.
this.lastName = lastName;
this.firstName = firstName;
fullName = firstName + " " + lastName;
}

public string FullName


{
get { return fullName; }
}

// This method is called after the object


// is completely deserialized. Use it instead of the
// constructror.
[OnDeserialized]
void OnDeserialized(StreamingContext context)
{
fullName = firstName + " " + lastName;
}
}

<DataContract()> _
Class Person3
<DataMember()> Private lastNameValue As String
<DataMember()> Private firstNameValue As String
Dim fullNameValue As String

Public Sub New(ByVal firstName As String, ByVal lastName As String)


lastNameValue = lastName
firstNameValue = firstName
fullNameValue = firstName & " " & lastName
End Sub

Public ReadOnly Property FullName As String


Get
Return fullNameValue
End Get
End Property

<OnDeserialized()> Sub OnDeserialized(ByVal context As StreamingContext)


fullNameValue = firstNameValue & " " & lastNameValue
End Sub
End Class

L'attribut OnDeserializedAttribute est l'attribut de rappel le plus communément utilisé. Les autres attributs
de la famille sont OnDeserializingAttribute , OnSerializingAttribute et OnSerializedAttribute . Ils peuvent être
utilisés pour marquer les rappels exécutés avant la désérialisation, avant la sérialisation et enfin, après la
sérialisation, respectivement.
4. ENVISAGEZ d'utiliser KnownTypeAttribute pour indiquer les types concrets qui doivent être utilisés lors de
la désérialisation d'un graphique d'objet complexe.
Par exemple, si un type de données membres désérialisées est représenté par une classe abstraite, le
sérialiseur a besoin des informations sur le type connu pour décider quel type concret doit être instancié et
assigné au membre. Si le type connu n'est pas spécifié à l'aide de l'attribut, il devra être passé au sérialiseur
explicitement (par exemple, en passant des types connus au constructeur du sérialiseur) ou être spécifié
dans le fichier de configuration.

// The KnownTypeAttribute specifies types to be


// used during serialization.
[KnownType(typeof(USAddress))]
[DataContract]
class Person4
{

[DataMember]
string fullNameValue;
[DataMember]
Address address; // Address is abstract

public Person4(string fullName, Address address)


{
this.fullNameValue = fullName;
this.address = address;
}

public string FullName


{
get { return fullNameValue; }
}
}

[DataContract]
public abstract class Address
{
public abstract string FullAddress { get; }
}

[DataContract]
public class USAddress : Address
{

[DataMember]
public string Street { get; set; }
[DataMember]
public string City { get; set; }
[DataMember]
public string State { get; set; }
[DataMember]
public string ZipCode { get; set; }

public override string FullAddress


{
get
{
return Street + "\n" + City + ", " + State + " " + ZipCode;
}
}
}
<KnownType(GetType(USAddress)), _
DataContract()> Class Person4
<DataMember()> Property fullNameValue As String
<DataMember()> Property addressValue As USAddress ' Address is abstract

Public Sub New(ByVal fullName As String, ByVal address As Address)


fullNameValue = fullName
addressValue = address
End Sub

Public ReadOnly Property FullName() As String


Get
Return fullNameValue
End Get

End Property
End Class

<DataContract()> Public MustInherit Class Address


Public MustOverride Function FullAddress() As String
End Class

<DataContract()> Public Class USAddress


Inherits Address
<DataMember()> Public Property Street As String
<DataMember()> Public City As String
<DataMember()> Public State As String
<DataMember()> Public ZipCode As String

Public Overrides Function FullAddress() As String


Return Street & "\n" & City & ", " & State & " " & ZipCode
End Function
End Class

Dans les cas où la liste de types connus n’est pas connue de façon statique (lorsque la classe Person est
compilée), KnownTypeAttribute peut également pointer vers une méthode qui retourne une liste de types
connus au moment de l’exécution.
5. TENEZ COMPTE de la compatibilité ascendante et descendante lors de la création ou modification de types
sérialisables.
Gardez à l'esprit que les flux sérialisés de futures versions de votre type peuvent être désérialisés dans la
version actuelle du type, et inversement. Assurez-vous que vous avez bien assimilé que les données
membres, même privées et internes, ne peuvent pas modifier leurs nom, type ou ordre dans les futures
versions du type, sauf si vous veillez à conserver le contrat à l'aide de paramètres explicites dans les
attributs du contrat de données. Testez la compatibilité de la sérialisation lorsque vous apportez des
modifications aux types sérialisables. Essayez de désérialiser la nouvelle version dans une ancienne version,
et inversement.
6. ENVISAGEZ d'implémenter l'interface IExtensibleDataObject pour permettre l'aller-retour entre les
différentes versions du type.
L'interface permet au sérialiseur de garantir qu'aucune donnée n'est perdue lors de l'aller-retour. La
propriété ExtensionData stocke les données de la future version du type qui est inconnu de la version
actuelle. Quand la version actuelle est par la suite sérialisée et désérialisée dans une version ultérieure, les
données supplémentaires sont disponibles dans le flux sérialisé via la valeur de la propriété
ExtensionData .
// Implement the IExtensibleDataObject interface.
[DataContract]
class Person5 : IExtensibleDataObject
{
ExtensionDataObject serializationData;
[DataMember]
string fullNameValue;

public Person5(string fullName)


{
this.fullNameValue = fullName;
}

public string FullName


{
get { return fullNameValue; }
}

ExtensionDataObject IExtensibleDataObject.ExtensionData
{
get
{
return serializationData;
}
set { serializationData = value; }
}
}

<DataContract()> Class Person5


Implements IExtensibleDataObject
<DataMember()> Dim fullNameValue As String

Public Sub New(ByVal fullName As String)


fullName = fullName
End Sub

Public ReadOnly Property FullName


Get
Return fullNameValue
End Get
End Property
Private serializationData As ExtensionDataObject
Public Property ExtensionData As ExtensionDataObject Implements IExtensibleDataObject.ExtensionData
Get
Return serializationData
End Get
Set(ByVal value As ExtensionDataObject)
serializationData = value
End Set
End Property
End Class

Pour plus d’informations, consultez Contrats de données compatibles avec des versions ultérieures.
Prise en charge de la sérialisation XML
La sérialisation du contrat de données est la principale technologie de sérialisation (par défaut) dans le .NET
Framework. Il existe cependant des scénarios de sérialisation qui ne sont pas pris en charge par cette technologie.
Par exemple, vous n'avez pas le contrôle total sur la forme du XML généré ou consommé par le sérialiseur. Si ce
contrôle renforcé est nécessaire, vous devez utiliser la sérialisation XML, et concevoir vos types pour prendre en
charge cette technologie de sérialisation.
1. ÉVITEZ de concevoir vos types spécifiquement pour la sérialisation XML, sauf si vous avez une bonne raison
de contrôler la forme du XML généré. Cette technologie de sérialisation a été remplacée par la sérialisation
du contrat de données présentée dans la section précédente.
En d'autres termes, n'appliquez pas d'attributs de l'espace de noms System.Xml.Serialization aux nouveaux
types, sauf si vous savez que le type doit être utilisé avec la sérialisation XML. L’exemple suivant montre
comment utiliser System.Xml.Serialization pour contrôler la forme du XML généré.

public class Address2


{
[System.Xml.Serialization.XmlAttribute] // Serialize as XML attribute, instead of an element.
public string Name { get { return "Poe, Toni"; } set { } }
[System.Xml.Serialization.XmlElement(ElementName = "StreetLine")] // Explicitly name the element.
public string Street = "1 Main Street";
}

Public Class Address2


' Supports XML Serialization.
<System.Xml.Serialization.XmlAttribute()> _
Public ReadOnly Property Name As String ' Serialize as XML attribute, instead of an element.
Get
Return "Poe, Toni"
End Get
End Property
<System.Xml.Serialization.XmlElement(ElementName:="StreetLine")> _
Public Street As String = "1 Main Street" ' Explicitly names the element 'StreetLine'.
End Class

2. ENVISAGEZ d'utiliser l'interface IXmlSerializable si vous souhaitez davantage de contrôle sur la forme du
XML sérialisé que celui proposé en appliquant les attributs de sérialisation XML. Deux méthodes de
l’interface, ReadXml et WriteXml , vous permettent de contrôler entièrement le flux XML sérialisé. Vous
pouvez aussi contrôler le schéma XML qui est généré pour le type en appliquant l'attribut
XmlSchemaProviderAttribute.
Prise en charge de la sérialisation du runtime
La technologie de sérialisation du runtime est utilisée par .NET Remoting. Si vous pensez que vos types vont être
transportés à l'aide de .NET Remoting, assurez-vous qu'ils prennent en charge la sérialisation du runtime.
La prise en charge de base de la sérialisation du runtime peut être fournie en appliquant l’attribut
SerializableAttribute. Des scénarios plus avancés nécessitent l’implémentation d’un modèle sérialisable du runtime
simple (implémentez -ISerializable et fournissez un constructeur de sérialisation).
1. ENVISAGEZ la prise en charge de la sérialisation du runtime si vos types doivent être utilisés avec .NET
Remoting. Par exemple, l’espace de noms System.AddIn utilise .NET Remoting et, par conséquent, tous les
types échangés entre les compléments System.AddIn doivent prendre en charge la sérialisation du
runtime.

// Apply SerializableAttribute to support runtime serialization.


[Serializable]
public class Person6
{
// Code not shown.
}

<Serializable()> Public Class Person6 ' Support runtime serialization with the SerializableAttribute.

' Code not shown.


End Class
2. ENVISAGEZ d’implémenter le modèle sérialisable du runtime si vous souhaitez avoir un contrôle total sur le
processus de sérialisation. Par exemple, si vous souhaitez transformer des données à mesure qu'elles sont
sérialisées ou désérialisées.
Le modèle est très simple. Il suffit d'implémenter l'interface ISerializable et de fournir un constructeur
spécial qui est utilisé lorsque l'objet est désérialisé.

// Implement the ISerializable interface for more control.


[Serializable]
public class Person_Runtime_Serializable : ISerializable
{
string fullName;

public Person_Runtime_Serializable() { }
protected Person_Runtime_Serializable(SerializationInfo info, StreamingContext context){
if (info == null) throw new System.ArgumentNullException("info");
fullName = (string)info.GetValue("name", typeof(string));
}
[SecurityPermission(SecurityAction.LinkDemand,
Flags = SecurityPermissionFlag.SerializationFormatter)]
void ISerializable.GetObjectData(SerializationInfo info,
StreamingContext context) {
if (info == null) throw new System.ArgumentNullException("info");
info.AddValue("name", fullName);
}

public string FullName


{
get { return fullName; }
set { fullName = value; }
}
}
' Implement the ISerializable interface for more control.
<Serializable()> Public Class Person_Runtime_Serializable
Implements ISerializable

Private fullNameValue As String

Public Sub New()


' empty constructor.
End Sub
Protected Sub New(ByVal info As SerializationInfo, _
ByVal context As StreamingContext)
If info Is Nothing Then
Throw New System.ArgumentNullException("info")
FullName = CType(info.GetValue("name", GetType(String)), String)
End If
End Sub

Private Sub GetObjectData(ByVal info As SerializationInfo, _


ByVal context As StreamingContext) _
Implements ISerializable.GetObjectData
If info Is Nothing Then
Throw New System.ArgumentNullException("info")
info.AddValue("name", FullName)
End If
End Sub

Public Property FullName As String

Get
Return fullNameValue
End Get
Set(ByVal value As String)
fullNameValue = value

End Set
End Property

End Class

3. SÉCURISEZ le constructeur de sérialisation et fournissez deux paramètres typés et nommés de la même


façon que dans l'exemple disponible ici.

protected Person_Runtime_Serializable(SerializationInfo info, StreamingContext context){

Protected Sub New(ByVal info As SerializationInfo, _


ByVal context As StreamingContext)

4. IMPLÉMENTEZ explicitement les membres ISerializable.

void ISerializable.GetObjectData(SerializationInfo info,


StreamingContext context) {

Private Sub GetObjectData(ByVal info As SerializationInfo, _


ByVal context As StreamingContext) _
Implements ISerializable.GetObjectData

5. EFFECTUEZ une demande de liaison pour l’implémentation ISerializable.GetObjectData . Cela garantit


que seuls le code d'un niveau de confiance totale et le sérialiseur du runtime ont accès au membre.
[SecurityPermission(SecurityAction.LinkDemand,
Flags = SecurityPermissionFlag.SerializationFormatter)]

<Serializable()> Public Class Person_Runtime_Serializable2


Implements ISerializable
<SecurityPermission(SecurityAction.LinkDemand,
Flags:=SecurityPermissionFlag.SerializationFormatter)> _
Private Sub GetObjectData(ByVal info As System.Runtime.Serialization.SerializationInfo, _
ByVal context As System.Runtime.Serialization.StreamingContext) _
Implements System.Runtime.Serialization.ISerializable.GetObjectData
' Code not shown.
End Sub
End Class

Voir aussi
Using Data Contracts
Sérialiseur de contrat de données
Types pris en charge par le sérialiseur de contrat de données
Sérialisation binaire
.NET Remoting
Sérialisation XML et SOAP
Sécurité et sérialisation
Guide pratique pour segmenter des données
sérialisées
18/07/2020 • 6 minutes to read • Edit Online

WARNING
La sérialisation binaire peut être dangereuse. Pour plus d’informations, consultez le Guide de la sécurité BinaryFormatter.

Les deux problèmes qui se produisent lors de l'envoi de grands ensembles de données dans des messages de
services Web sont les suivants :
1. Un grand jeu de travail (mémoire) en raison d'une mise en mémoire tampon effectuée par le moteur de
sérialisation.
2. Une consommation de bande passante importante due à une inflation de 33 % après un encodage Base64.
Pour résoudre ces problèmes, implémentez l'interface IXmlSerializable pour contrôler la sérialisation et la
désérialisation. Plus précisément, implémentez les méthodes WriteXml et ReadXml pour segmenter les données.
Pour implémenter la segmentation côté serveur
1. Sur l'ordinateur serveur, la méthode Web doit désactiver la mise en mémoire tampon ASP.NET et retourner
un type qui implémente IXmlSerializable.
2. Le type qui implémente IXmlSerializable segmente les données dans la méthode WriteXml.
Pour implémenter le traitement côté client
1. Modifiez la méthode Web sur le proxy client pour retourner le type qui implémente IXmlSerializable. Vous
pouvez utiliser un SchemaImporterExtension pour effectuer cette opération automatiquement (non illustrée
ici).
2. Implémentez la méthode ReadXml pour lire le flux de données segmenté et écrire les octets sur le disque.
Cette implémentation déclenche également des événements de progression qui peuvent être utilisés par un
contrôle graphique, tel qu'une barre de progression.

Exemple
L'exemple de code suivant illustre la méthode Web sur le client qui désactive la mise en mémoire tampon ASP.NET.
Il affiche également l'implémentation côté client de l'interface IXmlSerializable qui segmente les données dans la
méthode WriteXml.
[WebMethod]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute
(ParameterStyle= SoapParameterStyle.Bare)]
public SongStream DownloadSong(DownloadAuthorization Authorization, string filePath)
{

// Turn off response buffering.


System.Web.HttpContext.Current.Response.Buffer = false;
// Return a song.
SongStream song = new SongStream(filePath);
return song;
}

<WebMethod(),
System.Web.Services.Protocols.SoapDocumentMethodAttribute(ParameterStyle:=SoapParameterStyle.Bare)> _
Public Function DownloadSong(ByVal Authorization As DownloadAuthorization, ByVal filePath As String) As
SongStream

' Turn off response buffering.


System.Web.HttpContext.Current.Response.Buffer = False
' Return a song.
Dim song As New SongStream(filePath)
Return song

End Function
End Class

[XmlSchemaProvider("MySchema")]
public class SongStream : IXmlSerializable
{

private const string ns = "http://demos.Contoso.com/webservices";


private string filePath;

public SongStream(){ }

public SongStream(string filePath)


{
this.filePath = filePath;
}

// This is the method named by the XmlSchemaProviderAttribute applied to the type.


public static XmlQualifiedName MySchema(XmlSchemaSet xs)
{
// This method is called by the framework to get the schema for this type.
// We return an existing schema from disk.

XmlSerializer schemaSerializer = new XmlSerializer(typeof(XmlSchema));


string xsdPath = null;
// NOTE: replace the string with your own path.
xsdPath = System.Web.HttpContext.Current.Server.MapPath("SongStream.xsd");
XmlSchema s = (XmlSchema)schemaSerializer.Deserialize(
new XmlTextReader(xsdPath), null);
xs.XmlResolver = new XmlUrlResolver();
xs.Add(s);

return new XmlQualifiedName("songStream", ns);


}

void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)


{
// This is the chunking code.
// ASP.NET buffering must be turned off for this to work.

int bufferSize = 4096;


int bufferSize = 4096;
char[] songBytes = new char[bufferSize];
FileStream inFile = File.Open(this.filePath, FileMode.Open, FileAccess.Read);

long length = inFile.Length;

// Write the file name.


writer.WriteElementString("fileName", ns, Path.GetFileNameWithoutExtension(this.filePath));

// Write the size.


writer.WriteElementString("size", ns, length.ToString());

// Write the song bytes.


writer.WriteStartElement("song", ns);

StreamReader sr = new StreamReader(inFile, true);


int readLen = sr.Read(songBytes, 0, bufferSize);

while (readLen > 0)


{
writer.WriteStartElement("chunk", ns);
writer.WriteChars(songBytes, 0, readLen);
writer.WriteEndElement();

writer.Flush();
readLen = sr.Read(songBytes, 0, bufferSize);
}

writer.WriteEndElement();
inFile.Close();
}

System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
throw new System.NotImplementedException();
}

void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)


{
throw new System.NotImplementedException();
}
}

<XmlSchemaProvider("MySchema")> _
Public Class SongStream
Implements IXmlSerializable

Private Const ns As String = "http://demos.Contoso.com/webservices"


Private filePath As String

Public Sub New()

End Sub

Public Sub New(ByVal filePath As String)


Me.filePath = filePath
End Sub

' This is the method named by the XmlSchemaProviderAttribute applied to the type.
Public Shared Function MySchema(ByVal xs As XmlSchemaSet) As XmlQualifiedName
' This method is called by the framework to get the schema for this type.
' We return an existing schema from disk.
Dim schemaSerializer As New XmlSerializer(GetType(XmlSchema))
Dim xsdPath As String = Nothing
' NOTE: replace SongStream.xsd with your own schema file.
xsdPath = System.Web.HttpContext.Current.Server.MapPath("SongStream.xsd")
Dim s As XmlSchema = CType(schemaSerializer.Deserialize(New XmlTextReader(xsdPath)), XmlSchema)
Dim s As XmlSchema = CType(schemaSerializer.Deserialize(New XmlTextReader(xsdPath)), XmlSchema)
xs.XmlResolver = New XmlUrlResolver()
xs.Add(s)

Return New XmlQualifiedName("songStream", ns)

End Function

Sub WriteXml(ByVal writer As System.Xml.XmlWriter) Implements IXmlSerializable.WriteXml


' This is the chunking code.
' ASP.NET buffering must be turned off for this to work.

Dim bufferSize As Integer = 4096


Dim songBytes(bufferSize) As Char
Dim inFile As FileStream = File.Open(Me.filePath, FileMode.Open, FileAccess.Read)

Dim length As Long = inFile.Length

' Write the file name.


writer.WriteElementString("fileName", ns, Path.GetFileNameWithoutExtension(Me.filePath))

' Write the size.


writer.WriteElementString("size", ns, length.ToString())

' Write the song bytes.


writer.WriteStartElement("song", ns)

Dim sr As New StreamReader(inFile, True)


Dim readLen As Integer = sr.Read(songBytes, 0, bufferSize)

While readLen > 0


writer.WriteStartElement("chunk", ns)
writer.WriteChars(songBytes, 0, readLen)
writer.WriteEndElement()

writer.Flush()
readLen = sr.Read(songBytes, 0, bufferSize)
End While

writer.WriteEndElement()
inFile.Close()
End Sub

Function GetSchema() As System.Xml.Schema.XmlSchema Implements IXmlSerializable.GetSchema


Throw New System.NotImplementedException()
End Function

Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements IXmlSerializable.ReadXml


Throw New System.NotImplementedException()
End Sub
End Class

public class SongFile : IXmlSerializable


{
public static event ProgressMade OnProgress;

public SongFile()
{ }

private const string ns = "http://demos.teched2004.com/webservices";


public static string MusicPath;
private string filePath;
private double size;

void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)


void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
{
reader.ReadStartElement("DownloadSongResult", ns);
ReadFileName(reader);
ReadSongSize(reader);
ReadAndSaveSong(reader);
reader.ReadEndElement();
}

void ReadFileName(XmlReader reader)


{
string fileName = reader.ReadElementString("fileName", ns);
this.filePath =
Path.Combine(MusicPath, Path.ChangeExtension(fileName, ".mp3"));
}

void ReadSongSize(XmlReader reader)


{
this.size = Convert.ToDouble(reader.ReadElementString("size", ns));
}

void ReadAndSaveSong(XmlReader reader)


{
FileStream outFile = File.Open(
this.filePath, FileMode.Create, FileAccess.Write);

string songBase64;
byte[] songBytes;
reader.ReadStartElement("song", ns);
double totalRead=0;
while(true)
{
if (reader.IsStartElement("chunk", ns))
{
songBase64 = reader.ReadElementString();
totalRead += songBase64.Length;
songBytes = Convert.FromBase64String(songBase64);
outFile.Write(songBytes, 0, songBytes.Length);
outFile.Flush();

if (OnProgress != null)
{
OnProgress(100 * (totalRead / size));
}
}

else
{
break;
}
}

outFile.Close();
reader.ReadEndElement();
}

[PermissionSet(SecurityAction.Demand, Name="FullTrust")]
public void Play()
{
System.Diagnostics.Process.Start(this.filePath);
}

System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
{
throw new System.NotImplementedException();
}

public void WriteXml(XmlWriter writer)


{
throw new System.NotImplementedException();
throw new System.NotImplementedException();
}
}

Public Class SongFile


Implements IXmlSerializable
Public Shared Event OnProgress As ProgressMade

Public Sub New()

End Sub

Private Const ns As String = "http://demos.teched2004.com/webservices"


Public Shared MusicPath As String
Private filePath As String
Private size As Double

Sub ReadXml(ByVal reader As System.Xml.XmlReader) Implements IXmlSerializable.ReadXml


reader.ReadStartElement("DownloadSongResult", ns)
ReadFileName(reader)
ReadSongSize(reader)
ReadAndSaveSong(reader)
reader.ReadEndElement()
End Sub

Sub ReadFileName(ByVal reader As XmlReader)


Dim fileName As String = reader.ReadElementString("fileName", ns)
Me.filePath = Path.Combine(MusicPath, Path.ChangeExtension(fileName, ".mp3"))

End Sub

Sub ReadSongSize(ByVal reader As XmlReader)


Me.size = Convert.ToDouble(reader.ReadElementString("size", ns))

End Sub

Sub ReadAndSaveSong(ByVal reader As XmlReader)


Dim outFile As FileStream = File.Open(Me.filePath, FileMode.Create, FileAccess.Write)

Dim songBase64 As String


Dim songBytes() As Byte
reader.ReadStartElement("song", ns)
Dim totalRead As Double = 0
While True
If reader.IsStartElement("chunk", ns) Then
songBase64 = reader.ReadElementString()
totalRead += songBase64.Length
songBytes = Convert.FromBase64String(songBase64)
outFile.Write(songBytes, 0, songBytes.Length)
outFile.Flush()
RaiseEvent OnProgress((100 * (totalRead / size)))
Else
Exit While
End If
End While

outFile.Close()
reader.ReadEndElement()
End Sub

<PermissionSet(SecurityAction.Demand, Name:="FullTrust")> _
Public Sub Play()
System.Diagnostics.Process.Start(Me.filePath)
End Sub

Function GetSchema() As System.Xml.Schema.XmlSchema Implements IXmlSerializable.GetSchema


Function GetSchema() As System.Xml.Schema.XmlSchema Implements IXmlSerializable.GetSchema
Throw New System.NotImplementedException()
End Function

Public Sub WriteXml(ByVal writer As XmlWriter) Implements IXmlSerializable.WriteXml


Throw New System.NotImplementedException()
End Sub
End Class

Compilation du code
Le code utilise les espaces de noms suivants : System, System.Runtime.Serialization, System.Web.Services,
System.Web.Services.Protocols, System.Xml, System.Xml.Serialization et System.Xml.Schema.

Voir aussi
Sérialisation personnalisée
Comment déterminer si un objet .NET Standard est
sérialisable
18/07/2020 • 3 minutes to read • Edit Online

Le .NET Standard est une spécification qui définit les types et les membres qui doivent être présents sur des
implémentations .NET spécifiques qui se conforment à cette version de la norme. Toutefois, le .NET Standard ne
définit pas si un type est sérialisable. Les types définis dans la bibliothèque de .NET Standard ne sont pas marqués
avec l' SerializableAttribute attribut. Au lieu de cela, des implémentations .NET spécifiques, telles que les .NET
Framework et .NET Core, sont libres de déterminer si un type particulier est sérialisable.
Si vous avez développé une bibliothèque qui cible le .NET Standard, votre bibliothèque peut être consommée par
n’importe quelle implémentation .NET qui prend en charge le .NET Standard. Cela signifie que vous ne pouvez pas
savoir à l’avance si un type particulier est sérialisable ; vous pouvez uniquement déterminer si elle est sérialisable
au moment de l’exécution.
Vous pouvez déterminer si un objet est sérialisable au moment de l’exécution en extrayant la valeur de la
IsSerializable propriété d’un Type objet qui représente le type de cet objet. L’exemple suivant fournit une
implémentation. Il définit une IsSerializable(Object) méthode d’extension qui indique si une Object instance de
peut être sérialisée.

namespace Libraries
{
using System;

public static class UtilityLibrary


{
public static bool IsSerializable(this object obj)
{
if (obj == null)
return false;

Type t = obj.GetType();
return t.IsSerializable;
}
}
}

Imports System.Runtime.CompilerServices

Namespace Global.Libraries

Public Module UtilityLibrary


<Extension>
Public Function IsSerializable(obj As Object) As Boolean
If obj Is Nothing Then Return False

Dim t As Type = obj.GetType()


Return t.IsSerializable
End Function
End Module
End Namespace

Vous pouvez ensuite passer n’importe quel objet à la méthode pour déterminer s’il peut être sérialisé et désérialisé
sur l’implémentation .NET actuelle, comme le montre l’exemple suivant :
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Libraries;

namespace test_serialization
{
class Program
{
static void Main()
{
var value = ValueTuple.Create("03244562", DateTime.Now, 13452.50m);
if (value.IsSerializable())
{
// Serialize the value tuple.
var formatter = new BinaryFormatter();
using (var stream = new FileStream("data.bin", FileMode.Create,
FileAccess.Write, FileShare.None))
{
formatter.Serialize(stream, value);
}
// Deserialize the value tuple.
using (var readStream = new FileStream("data.bin", FileMode.Open))
{
object restoredValue = formatter.Deserialize(readStream);
Console.WriteLine($"{restoredValue.GetType().Name}: {restoredValue}");
}
}
else
{
Console.WriteLine($"{nameof(value)} is not serializable");
}
}
}
}
// The example displays output like the following:
// ValueTuple`3: (03244562, 10/18/2017 5:25:22 PM, 13452.50)

Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary
Imports Libraries

Module Program
Sub Main()
Dim value = ValueTuple.Create("03244562", DateTime.Now, 13452.50d)
If value.IsSerializable() Then
Dim formatter As New BinaryFormatter()
' Serialize the value tuple.
Using stream As New FileStream("data.bin", FileMode.Create,
FileAccess.Write, FileShare.None)
formatter.Serialize(stream, value)
End Using
' Deserialize the value tuple.
Using readStream As New FileStream("data.bin", FileMode.Open)
Dim restoredValue = formatter.Deserialize(readStream)
Console.WriteLine($"{restoredValue.GetType().Name}: {restoredValue}")
End Using
Else
Console.WriteLine($"{nameof(value)} is not serializable")
End If
End Sub
End Module
' The example displays output like the following:
' ValueTuple`3: (03244562, 10/18/2017 5:25:22 PM, 13452.50)
Voir aussi
Sérialisation binaire
System.SerializableAttribute
Type.IsSerializable
Sérialisation de base, exemple de technologie
18/07/2020 • 5 minutes to read • Edit Online

Charger l’exemple
Cet exemple montre la capacité du Common Language Runtime à sérialiser un graphique d'objets en mémoire
dans un flux. Cet exemple peut utiliser SoapFormatter ou BinaryFormatter pour la sérialisation. Une liste liée,
remplie de données, est sérialisée ou désérialisée dans un flux de fichiers ou à partir de celui-ci. Dans les deux cas,
la liste est affichée afin que vous puissiez consulter les résultats. Il s'agit d'une liste liée de type LinkedList , un
type défini par cet exemple.
Pour plus d'informations sur la sérialisation, consultez les commentaires inclus dans les fichiers de code source et
dans les fichiers build-proj.
Pour générer l'exemple à partir de l'invite de commandes
1. Accédez à l'un des sous-répertoires spécifiques aux différents langages dans le répertoire
Technologies\Serialization\Runtime Serialization\Basic, à l'aide de l'invite de commandes.
2. Tapez msbuild SerializationCS.sln , msbuild SerializationJSL.sln ou msbuild SerializationVB.sln
sur la ligne de commande, selon votre choix de langage de programmation.
Pour générer l'exemple à l'aide de Visual Studio
1. Ouvrez l’Explorateur de fichiers et accédez à l’un des sous-répertoires spécifiques au langage de l’exemple.
2. Double-cliquez sur l'icône du fichier SerializationCS.sln, SerializationJSL.sln ou SerializationVB.sln, selon
votre choix de langage de programmation, pour ouvrir le fichier dans Visual Studio.
3. Dans le menu Générer , sélectionnez Générer la solution .
L'exemple d'application est généré dans le sous-répertoire \bin ou \bin\Debug par défaut.
Exécution de l'exemple
1. Accédez au répertoire qui contient le fichier exécutable créé.
2. Tapez Serialization.exe , ainsi que les valeurs de paramètres souhaitées, sur la ligne de commande.

NOTE
Cet exemple génère une application console. Vous devez la lancer à l'aide de l'invite de commandes pour pouvoir afficher sa
sortie.

Remarques
L'exemple d'application accepte des paramètres de ligne de commande indiquant le test à exécuter. Pour sérialiser
une liste de 10 nœuds dans un fichier nommé Test.xml à l’aide du formateur SOAP, utilisez les paramètres sx
Test.xml 10 .
Par exemple :

Serialize.exe -sx Test.xml 10

Pour désérialiser le fichier Test.xml de l’exemple précédent, utilisez les paramètres dx Test.xml .
Par exemple :

Serialize.exe -dx Test.xml

Dans les deux exemples précités, la lettre "x" dans le commutateur de ligne de commande indique que vous
souhaitez effectuer une sérialisation SOAP XML. Vous pouvez utiliser à la place la lettre "b" pour effectuer une
sérialisation binaire. Si vous souhaitez effectuer une sérialisation avec un très grand nombre de nœuds, il peut être
intéressant de rediriger la sortie de console vers un fichier.
Par exemple :

Serialize.exe -sb Test.bin 10000 >somefile.txt

Les éléments de la liste suivante décrivent brièvement les classes et les technologies utilisées par cet exemple.
Sérialisation du runtime
IFormatterUtilisé pour faire référence à un BinaryFormatter objet ou SoapFormatter .
BinaryFormatterUtilisé pour sérialiser une liste liée dans un flux au format binaire. Le formateur
binaire utilise un format reconnu uniquement par le type BinaryFormatter. Toutefois, les données
sont concises.
SoapFormatterUtilisé pour sérialiser une liste liée dans un flux au format SOAP. SOAP est un format
standard.
E/S de flux
Stream Utilisé pour effectuer la sérialisation et la désérialisation. Le flux spécifique utilisé dans cet
exemple est de type FileStream. Toutefois, la sérialisation peut être utilisée avec n'importe quel type
dérivé de Stream.
File Utilisé pour créer des objets FileStream afin de lire et de créer des fichiers sur le disque.
FileStream Utilisé pour sérialiser et désérialiser des listes liées.

Voir aussi
System.IO
File
FileStream
Stream
Random
System.Runtime.Serialization
BinaryFormatter
SoapFormatter
IFormatter
SerializableAttribute
System.Xml.Serialization
Sérialisation de base
Sérialisation binaire
Contrôle de la sérialisation XML à l’aide d’attributs
Introduction à la sérialisation XML
Sérialisation
Sérialisation XML et SOAP
Sérialisation XML et SOAP
18/07/2020 • 2 minutes to read • Edit Online

La sérialisation XML convertit (sérialise) les champs et les propriétés publics d’un objet, ainsi que les
paramètres et les valeurs de retour des méthodes, en un flux XML conforme à un document en langage XSD
(XML Schema Definition) spécifique. La sérialisation XML permet d'obtenir des classes fortement typées avec
des propriétés et des champs publics convertis au format série (dans ce cas, XML) pour le stockage ou le
transport.
XML étant une norme ouverte, le flux de données XML peut être traité si nécessaire par toute application, quelle
que soit la plateforme. Par exemple, les services Web XML créés à l'aide d'ASP.NET utilisent la classe
XmlSerializer pour créer des flux de données XML qui passent des données entre des applications de
services Web XML sur Internet ou des intranets. Inversement, la désérialisation utilise le flux de données XML et
reconstruit l'objet.
La sérialisation XML peut également être utilisée pour sérialiser des objets en flux XML se conformant à la
spécification SOAP. SOAP est un protocole basé sur XML, conçu spécifiquement pour transporter des appels de
procédure à l'aide de XML.
Pour sérialiser ou désérialiser des objets, utilisez la classe XmlSerializer. Pour créer les classes à sérialiser,
utilisez l'outil XML Schema Definition.

Voir aussi
Sérialisation binaire
Services Web XML créés à l’aide des clients de service Web XML et ASP.NET
sérialisation XML
18/07/2020 • 20 minutes to read • Edit Online

La sérialisation correspond au processus de conversion d'un objet en un formulaire facilement transportable. Par
exemple, vous pouvez sérialiser un objet et le transporter par Internet via HTTP entre un client et un serveur. À
l'autre extrémité, la désérialisation reconstruit l'objet du flux de données.
La sérialisation XML sérialise uniquement les champs publics et les valeurs de propriété d'un objet dans un flux de
données XML. La sérialisation XML n'inclut pas d'informations de type. Par exemple, si un objet Book se trouve
dans l’espace de noms Librar y , il n’y a aucune garantie qu’il soit désérialisé dans un objet du même type.

NOTE
La sérialisation XML ne convertit pas les méthodes, les indexeurs, les champs privés ni les propriétés en lecture seule (à
l'exception des collections en lecture seule). Pour sérialiser la totalité des champs et des propriétés d'un objet, publics et
privés, utilisez DataContractSerializer au lieu de la sérialisation XML.

La classe centrale de la sérialisation XML est la classe XmlSerializer, et les méthodes les plus importantes de cette
classe sont les méthodes Serialize et Deserialize . XmlSerializer crée des fichiers C# et les compile dans des
fichiers .dll pour exécuter cette sérialisation. Dans .NET Framework 2.0, l’outil XML Serializer Generator (Sgen.exe)
est conçu pour générer préalablement les assemblys de sérialisation à déployer avec votre application et pour
améliorer les performances de démarrage. Le flux de données XML généré par XmlSerializer est conforme à la
recommandation du langage XSD (XML Schema definition language)World Wide Web Consortium (W3C) 1,0. En
outre, les types de données générés sont conformes au document intitulé « XML Schema Part 2: Datatypes ».
Les données de vos objets sont décrites à l’aide des constructions du langage de programmation, telles que les
classes, les champs, les propriétés, les types primitifs, les tableaux et même du code XML incorporé sous forme
d’objets XmlElement ou XmlAttribute . Vous pouvez créer vos propres classes, annotées avec des attributs ou
utiliser l'outil XML Schema Definition pour générer des classes à partir d'un schéma XML existant.
Si vous disposez d'un schéma XML, vous pouvez exécuter l'outil XML Schema Definition pour générer un
ensemble de classes fortement typées sur le schéma et annotées avec des attributs. Lorsqu'une instance d'une
telle classe est sérialisée, le code XML généré respecte le schéma XML. Lorsque vous disposez d'une telle classe,
vous pouvez programmer à l'aide d'un modèle d'objet manipulé facilement tout en garantissant que le code XML
généré se conforme au schéma XML. Vous pouvez également utiliser d’autres classes dans .NET Framework, telles
que les classes XmlReader et XmlWriter , pour analyser et écrire un flux de données XML. Pour plus
d’informations, consultez Documents et données XML. Ces classes vous permettent d'analyser tout flux de
données XML. À l’inverse, utilisez XmlSerializer si le flux de données XML est supposé se conformer à un
schéma XML connu.
Les attributs contrôlent le flux de données XML généré par la classe XmlSerializer , en vous permettant de définir
l’espace de noms XML, le nom d’élément, le nom de l’attribut, etc. du flux de données XML. Pour plus
d’informations sur ces attributs et sur la manière dont ils contrôlent la sérialisation XML, consultez Contrôle de la
sérialisation XML à l’aide d’attributs. Pour obtenir une liste des attributs utilisés pour contrôler le code XML
généré, consultez Attributs qui contrôlent la sérialisation XML.
De plus, la classe XmlSerializer peut sérialiser un objet et générer un flux de données XML encodé selon le
protocole SOAP. Le XML généré est conforme à la section 5 du document World Wide Web Consortium intitulé
« Protocole SOAP (Simple Object Access Protocol) 1.1 ». Pour plus d’informations sur ce processus, consultez
Guide pratique pour sérialiser un objet en tant que flux XML encodé selon le protocole SOAP. Pour obtenir une
liste des attributs utilisés pour contrôler le code XML généré, consultez Attributs qui contrôlent la sérialisation
encodée selon le protocole SOAP.
La classe XmlSerializer génère les messages SOAP créés par les services web XML et passés à ces derniers. Pour
contrôler les messages SOAP, vous pouvez appliquer des attributs aux classes, valeurs de retour, paramètres et
champs trouvés dans un fichier de services Web XML (.asmx). Vous pouvez utiliser les attributs répertoriés dans
« Attributs qui contrôlent la sérialisation XML » et « Attributs qui contrôlent la sérialisation encodée selon le
protocole SOAP » car un service Web XML peut utiliser le style SOAP littéral ou encodé. Pour plus d’informations
sur l’utilisation d’attributs pour contrôler le code XML généré par un service web XML, consultez Sérialisation XML
avec les services web XML. Pour plus d’informations sur les services Web SOAP et XML, consultez
Personnalisation de la mise en forme des messages SOAP.

Considérations relatives à la sécurité des applications XmlSerializer


Lorsque vous créez une application qui utilise le XmlSerializer , tenez compte des éléments suivants et de leurs
implications :
XmlSerializer crée des fichiers C# (.cs) et les compile dans des fichiers .dll dans le répertoire nommé par
la variable d’environnement TEMP. La sérialisation s’effectue ensuite avec ces DLL.

NOTE
Ces assemblys de sérialisation peuvent être générés en avance et signés à l'aide de l'outil SGen.exe. Cela ne
fonctionne pas avec un serveur de services Web. En d'autres termes, ces assemblys sont réservés à une utilisation
par le client et à une sérialisation manuelle.

Le code et les dll sont vulnérables face à un processus malveillant lors de leur création et de leur
compilation. Lors de l'utilisation d'un ordinateur qui exécute Microsoft Windows NT 4.0 ou version
ultérieure, deux utilisateurs ou plus peuvent partager le répertoire TEMP. Le partage d’un répertoire TEMP
est dangereux si les deux comptes ont des privilèges de sécurité différents et que le compte doté des
privilèges les plus élevés exécute une application utilisant XmlSerializer . Dans ce cas, un utilisateur peut
percer la sécurité de l'ordinateur en remplaçant le fichier .cs ou .dll compilé. Pour éliminer ce problème,
vérifiez toujours que chaque compte de l'ordinateur dispose de son propre profil. Par défaut, la variable
d'environnement TEMP pointe vers un répertoire différent pour chaque compte.
Si un utilisateur malveillant envoie un flux de données XML continu à un serveur web (attaque par déni de
service), XmlSerializer continue de traiter les données jusqu’à ce que l’ordinateur manque de ressources.
Ce type d'attaque est éliminé si vous utilisez un ordinateur qui exécute des Services Internet (IIS) et que
votre application s'exécute dans IIS. IIS comprend une porte qui ne traite pas les flux de données
supérieurs à une quantité définie (la valeur par défaut correspond à 4 Ko). Si vous créez une application qui
n’utilise pas IIS et qui désérialise les données à l’aide de XmlSerializer , vous devez implémenter une porte
semblable pour empêcher une attaque par déni de service.
XmlSerializer sérialise les données et exécute tout type de code.
Un objet malveillant présente une menace dans deux situations. Il peut exécuter du code malveillant ou
injecter du code malveillant dans le fichier C# créé par XmlSerializer . Dans le premier cas, si un objet
malveillant essaie d'exécuter une procédure destructrice, la sécurité d'accès du code empêche tout
préjudice éventuel. Dans le deuxième cas, il est possible, en théorie, qu’un objet malveillant puisse injecter
d’une façon ou d’une autre du code dans le fichier C# créé par XmlSerializer . Bien que ce problème ait été
étudié de manière approfondie et qu'une telle attaque soit considérée comme improbable, veillez à ne
jamais sérialiser de données de type inconnu et non fiable.
Les données sensibles sérialisées peuvent être vulnérables.
Une fois que le XmlSerializer a sérialisé des données, il peut être stocké sous la forme d’un fichier XML
ou d’un autre magasin de données. Si d'autres processus peuvent accéder à votre magasin de données, ou
que celui-ci est visible sur un intranet ou sur Internet, les données peuvent être volées et utilisées de
manière malveillante. Par exemple, si vous créez une application qui sérialise des commandes qui incluent
des numéros de carte de crédit, les données sont très sensibles. Pour empêcher cela, protégez toujours
votre magasin de données et prenez les mesures nécessaires pour assurer sa confidentialité.

Sérialisation d'une classe simple


L'exemple de code suivant affiche une classe de base avec un champ public.

Public Class OrderForm


Public OrderDate As DateTime
End Class

public class OrderForm


{
public DateTime OrderDate;
}

Lorsqu'une instance de cette classe est sérialisée, elle peut se présenter comme suit.

<OrderForm>
<OrderDate>12/12/01</OrderDate>
</OrderForm>

Pour obtenir plus d’exemples de sérialisation, consultez Exemples de sérialisation XML.

Éléments qui peuvent être sérialisés


Les éléments suivants peuvent être sérialisés à l’aide de la classe XmlSerializer :
Propriétés publiques de lecture/écriture et champs de classes publiques.
Classes qui implémentent ICollection ou IEnumerable .

NOTE
Seules les collections sont sérialisées, pas les propriétés publiques.

Objets XmlElement .
Objets XmlNode .
Objets DataSet .
Pour plus d’informations sur la sérialisation et la désérialisation d’objets, consultez Guide pratique pour sérialiser
un objet et Guide pratique pour désérialiser un objet.

Avantages de l'utilisation de la sérialisation XML


La classe XmlSerializer vous donne un contrôle complet et flexible lorsque vous sérialisez un objet au format
XML. Si vous créez un service Web XML, vous pouvez appliquer des attributs qui contrôlent la sérialisation des
classes et des membres pour garantir que le résultat XML se conforme à un schéma spécifique.
Par exemple, XmlSerializer vous permet d’effectuer les opérations suivantes :
Spécifier si un champ ou une propriété doit être encodé comme un attribut ou un élément.
Spécifier un espace de noms XML à utiliser.
Spécifier le nom d'un élément ou d'un attribut si le nom d'un champ ou d'une propriété n'est pas
approprié.
Un autre avantage de la sérialisation XML est qu'aucune contrainte n'est définie sur les applications que vous
développez, tant que le flux de données XML généré se conforme à un schéma donné. Imaginez un schéma utilisé
pour décrire des livres. Il comprend un titre, un auteur, un éditeur et un élément de numéro ISBN. Vous pouvez
développer une application qui traite les données XML de la manière dont vous le souhaitez, par exemple, comme
une commande ou un inventaire de livres. Dans les deux cas, la seule spécification consiste en ce que le flux de
données XML se conforme au schéma XSD spécifié.

Considérations sur la sérialisation XML


Si vous utilisez la classe XmlSerializer , tenez compte des points suivants :
L'outil Sgen.exe est conçu expressément pour générer des assemblys de sérialisation en vue d'obtenir des
performances optimales.
Les données sérialisées ne contiennent que les données elles-mêmes et la structure de vos classes. Les
informations relatives à l'identité et aux assemblys ne sont pas incluses.
Seuls les champs et les propriétés publics peuvent être sérialisés. Les propriétés doivent disposer
d'accesseurs publics (méthodes get et set). Si vous devez sérialiser des données non publiques, utilisez la
classe DataContractSerializer plutôt que la sérialisation XML.
Une classe doit avoir un constructeur sans paramètre pour être sérialisé par XmlSerializer .
Les méthodes ne peuvent pas être sérialisées.
XmlSerializer peut traiter des classes qui implémentent différemment IEnumerable ou ICollection si
elles répondent aux exigences ci-après.
Une classe qui implémente IEnumerable doit implémenter une méthode Add publique qui accepte un
seul paramètre. Le paramètre de la méthode Add doit être cohérent (polymorphe) avec le type retourné
par la propriété IEnumerator.Current qui est retournée par la méthode GetEnumerator .
Une classe qui implémente ICollection en plus de IEnumerable (comme CollectionBase ) doit avoir une
propriété Item publique indexée (un indexeur dans C#) qui accepte un entier et avoir une propriété Count
publique de type integer . Le paramètre passé à la méthode Add doit être du même type que celui
retourné par la propriété Item ou correspondre à l’une des bases de ce type.
Dans le cas de classes qui implémentent ICollection , les valeurs à sérialiser sont récupérées à partir de la
propriété Item indexée et non en appelant GetEnumerator . Par ailleurs, les propriétés et les champs
publics ne sont pas sérialisés, à l’exception des champs publics qui retournent une autre classe de collection
(une classe qui implémente ICollection ). Pour obtenir un exemple, consultez Exemples de
sérialisation XML.

Mappage de type de données XSD


Le document W3C intitulé XML Schema Part 2 : Datatypes spécifie les types de données simples qui sont
autorisés dans un schéma en langage XSD (XML Schema Definition). Pour la plupart d’entre eux (par exemple, int
et decimal ), il existe un type de données correspondant dans .NET Framework. Toutefois, ce n’est pas le cas pour
certains types de données XML (par exemple, le type de données NMTOKEN ). Dans de tels cas, si vous utilisez
l’outil XML Schema Definition (Xsd.exe) pour créer des classes à partir d’un schéma, un attribut approprié est
appliqué à un membre de type String et sa propriété DataType a pour valeur le nom du type de données XML.
Par exemple, si un schéma contient un élément nommé « MyToken » et ayant le type de données XML
NMTOKEN , la nouvelle classe peut contenir un membre tel qu’illustré dans l’exemple suivant.

<XmlElement(DataType:="NMTOKEN")> _
Public MyToken As String

[XmlElement(DataType = "NMTOKEN")]
public string MyToken;

De la même façon, si vous créez une classe qui doit se conformer à un schéma XML spécifique (XSD), vous devez
appliquer l’attribut approprié et assigner à sa propriété DataType le nom de type de données XML souhaité.
Pour obtenir une liste complète des mappages de types, consultez la propriété DataType pour chacune des
classes d’attributs suivantes :
SoapAttributeAttribute
SoapElementAttribute
XmlArrayItemAttribute
XmlAttributeAttribute
XmlElementAttribute
XmlRootAttribute

Voir aussi
XmlSerializer
DataContractSerializer
FileStream
Sérialisation XML et SOAP
Sérialisation binaire
Sérialisation
XmlSerializer
Exemples de sérialisation XML
Guide pratique pour sérialiser un objet
Guide pratique pour désérialiser un objet
Exemples de sérialisation XML
18/07/2020 • 18 minutes to read • Edit Online

La sérialisation XML peut prendre plusieurs formes, de la plus simple à la plus complexe. Par exemple, vous
pouvez sérialiser une classe qui se compose simplement de champs et de propriétés publics, comme illustré dans
Introduction à la sérialisation XML. Les exemples de code suivants abordent différents scénarios avancés, y
compris la manière d'utiliser la sérialisation XML pour générer un flux de données XML qui se conforme à un
document de schéma XML (XSD) spécifique.

Sérialisation d'un DataSet


En plus de sérialiser une instance d'une classe publique, une instance d'un DataSet peut également être sérialisée,
comme illustré dans l'exemple de code suivant.

Private Sub SerializeDataSet(filename As String)


Dim ser As XmlSerializer = new XmlSerializer(GetType(DataSet))
' Creates a DataSet; adds a table, column, and ten rows.
Dim ds As DataSet = new DataSet("myDataSet")
Dim t As DataTable = new DataTable("table1")
Dim c As DataColumn = new DataColumn("thing")
t.Columns.Add(c)
ds.Tables.Add(t)
Dim r As DataRow
Dim i As Integer
for i = 0 to 10
r = t.NewRow()
r(0) = "Thing " & i
t.Rows.Add(r)
Next
Dim writer As TextWriter = new StreamWriter(filename)
ser.Serialize(writer, ds)
writer.Close()
End Sub

private void SerializeDataSet(string filename){


XmlSerializer ser = new XmlSerializer(typeof(DataSet));

// Creates a DataSet; adds a table, column, and ten rows.


DataSet ds = new DataSet("myDataSet");
DataTable t = new DataTable("table1");
DataColumn c = new DataColumn("thing");
t.Columns.Add(c);
ds.Tables.Add(t);
DataRow r;
for(int i = 0; i<10;i++){
r = t.NewRow();
r[0] = "Thing " + i;
t.Rows.Add(r);
}
TextWriter writer = new StreamWriter(filename);
ser.Serialize(writer, ds);
writer.Close();
}

Sérialisation de XmlElement et XmlNode


Vous pouvez également sérialiser des instances d' XmlElement une XmlNode classe ou, comme indiqué dans
l’exemple de code suivant.

private Sub SerializeElement(filename As String)


Dim ser As XmlSerializer = new XmlSerializer(GetType(XmlElement))
Dim myElement As XmlElement = _
new XmlDocument().CreateElement("MyElement", "ns")
myElement.InnerText = "Hello World"
Dim writer As TextWriter = new StreamWriter(filename)
ser.Serialize(writer, myElement)
writer.Close()
End Sub

Private Sub SerializeNode(filename As String)


Dim ser As XmlSerializer = _
new XmlSerializer(GetType(XmlNode))
Dim myNode As XmlNode = new XmlDocument(). _
CreateNode(XmlNodeType.Element, "MyNode", "ns")
myNode.InnerText = "Hello Node"
Dim writer As TextWriter = new StreamWriter(filename)
ser.Serialize(writer, myNode)
writer.Close()
End Sub

private void SerializeElement(string filename){


XmlSerializer ser = new XmlSerializer(typeof(XmlElement));
XmlElement myElement=
new XmlDocument().CreateElement("MyElement", "ns");
myElement.InnerText = "Hello World";
TextWriter writer = new StreamWriter(filename);
ser.Serialize(writer, myElement);
writer.Close();
}

private void SerializeNode(string filename){


XmlSerializer ser = new XmlSerializer(typeof(XmlNode));
XmlNode myNode= new XmlDocument().
CreateNode(XmlNodeType.Element, "MyNode", "ns");
myNode.InnerText = "Hello Node";
TextWriter writer = new StreamWriter(filename);
ser.Serialize(writer, myNode);
writer.Close();
}

Sérialisation d'une classe qui contient un champ retournant un objet


complexe
Si une propriété ou un champ retourne un objet complexe (tel qu'un tableau ou une instance de classe),
XmlSerializer le convertit en élément imbriqué dans le document XML principal. Par exemple, la première classe
de l'exemple de code ci-dessous retourne une instance de la deuxième classe.

Public Class PurchaseOrder


Public MyAddress As Address
End Class

Public Class Address


Public FirstName As String
End Class
public class PurchaseOrder
{
public Address MyAddress;
}
public class Address
{
public string FirstName;
}

Le résultat XML sérialisé peut se présenter comme suit.

<PurchaseOrder>
<MyAddress>
<FirstName>George</FirstName>
</MyAddress>
</PurchaseOrder>

Sérialisation d'un tableau d'objets


Vous pouvez également sérialiser un champ qui retourne un tableau d'objets, comme illustré dans l'exemple de
code suivant.

Public Class PurchaseOrder


public ItemsOrders () As Item
End Class

Public Class Item


Public ItemID As String
Public ItemPrice As decimal
End Class

public class PurchaseOrder


{
public Item [] ItemsOrders;
}

public class Item


{
public string ItemID;
public decimal ItemPrice;
}

L'instance de classe sérialisée peut se présenter comme suit, si deux éléments sont commandés.

<PurchaseOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<ItemsOrders>
<Item>
<ItemID>aaa111</ItemID>
<ItemPrice>34.22</ItemPrice>
</Item>
<Item>
<ItemID>bbb222</ItemID>
<ItemPrice>2.89</ItemPrice>
</Item>
</ItemsOrders>
</PurchaseOrder>
Sérialisation d'une classe qui implémente l'interface ICollection
Vous pouvez créer vos propres classes de collection en implémentant l'interface ICollection et utiliser
XmlSerializer pour sérialiser les instances de ces classes. Notez que lorsqu'une classe implémente l'interface
ICollection, seule la collection que contient cette classe est sérialisée. Tous les champs ou propriétés publics
ajoutés à la classe ne sont pas sérialisés. La classe doit inclure une méthode Add et une propriété Item
(indexeur C#) à sérialiser.

Imports System.Collections
Imports System.IO
Imports System.Xml.Serialization

Public Class Test


Shared Sub Main()
Dim t As Test= new Test()
t.SerializeCollection("coll.xml")
End Sub

Private Sub SerializeCollection(filename As String)


Dim Emps As Employees = new Employees()
' Note that only the collection is serialized -- not the
' CollectionName or any other public property of the class.
Emps.CollectionName = "Employees"
Dim John100 As Employee = new Employee("John", "100xxx")
Emps.Add(John100)
Dim x As XmlSerializer = new XmlSerializer(GetType(Employees))
Dim writer As TextWriter = new StreamWriter(filename)
x.Serialize(writer, Emps)
writer.Close()
End Sub
End Class

Public Class Employees


Implements ICollection
Public CollectionName As String
Private empArray As ArrayList = new ArrayList()

Public ReadOnly Default Overloads _


Property Item(index As Integer) As Employee
get
return CType (empArray(index), Employee)
End Get
End Property

Public Sub CopyTo(a As Array, index As Integer) _


Implements ICollection.CopyTo
empArray.CopyTo(a, index)
End Sub

Public ReadOnly Property Count () As integer Implements _


ICollection.Count
get
Count = empArray.Count
End Get

End Property

Public ReadOnly Property SyncRoot ()As Object _


Implements ICollection.SyncRoot
get
return me
End Get
End Property

Public ReadOnly Property IsSynchronized () As Boolean _


Implements ICollection.IsSynchronized
get
return false
End Get
End Property

Public Function GetEnumerator() As IEnumerator _


Implements IEnumerable.GetEnumerator

return empArray.GetEnumerator()
End Function

Public Function Add(newEmployee As Employee) As Integer


empArray.Add(newEmployee)
return empArray.Count
End Function
End Class

Public Class Employee


Public EmpName As String
Public EmpID As String

Public Sub New ()


End Sub

Public Sub New (newName As String , newID As String )


EmpName = newName
EmpID = newID
End Sub
End Class
using System;
using System.Collections;
using System.IO;
using System.Xml.Serialization;

public class Test {


static void Main(){
Test t = new Test();
t.SerializeCollection("coll.xml");
}

private void SerializeCollection(string filename){


Employees Emps = new Employees();
// Note that only the collection is serialized -- not the
// CollectionName or any other public property of the class.
Emps.CollectionName = "Employees";
Employee John100 = new Employee("John", "100xxx");
Emps.Add(John100);
XmlSerializer x = new XmlSerializer(typeof(Employees));
TextWriter writer = new StreamWriter(filename);
x.Serialize(writer, Emps);
}
}
public class Employees:ICollection {
public string CollectionName;
private ArrayList empArray = new ArrayList();

public Employee this[int index]{


get{return (Employee) empArray[index];}
}

public void CopyTo(Array a, int index){


empArray.CopyTo(a, index);
}
public int Count{
get{return empArray.Count;}
}
public object SyncRoot{
get{return this;}
}
public bool IsSynchronized{
get{return false;}
}
public IEnumerator GetEnumerator(){
return empArray.GetEnumerator();
}

public void Add(Employee newEmployee){


empArray.Add(newEmployee);
}
}

public class Employee {


public string EmpName;
public string EmpID;
public Employee(){}
public Employee(string empName, string empID){
EmpName = empName;
EmpID = empID;
}
}

Exemple de bon de commande


Vous pouvez couper et coller l'exemple de code suivant dans un fichier texte renommé avec une extension de
nom de fichier en .cs ou .vb. Utilisez le compilateur C# ou Visual Basic pour compiler le fichier. Puis exécutez-le à
l'aide du nom du fichier exécutable.
Cet exemple utilise un scénario simple pour illustrer comment l'instance d'un objet est créée et sérialisée dans un
flux de données de fichier à l'aide de la méthode Serialize. Le flux de données XML est enregistré dans un fichier,
qui est ensuite relu et reconstruit dans une copie de l'objet d'origine à l'aide de la méthode Deserialize.
Dans cet exemple, une classe nommée PurchaseOrder est sérialisée puis désérialisée. Une deuxième classe
nommée Address est également incluse car le champ public nommé ShipTo doit avoir la valeur Address . De la
même façon, une classe OrderedItem est incluse car un tableau d'objets OrderedItem doit avoir pour valeur le
champ OrderedItems . Enfin, une classe nommée Test contient le code qui sérialise et désérialise les classes.
La méthode CreatePO crée les objets de classe PurchaseOrder , Address et OrderedItem et définit les valeurs de
champs publics. La méthode construit également une instance de la classe XmlSerializer utilisée pour sérialiser et
désérialiser PurchaseOrder . Notez que le code passe au constructeur le type de la classe qui sera sérialisée. Le
code crée également un FileStream utilisé pour écrire le flux XML dans un document XML.
La méthode ReadPo est un peu plus simple. Elle crée juste des objets à désérialiser et lit leurs valeurs. Comme
avec la CreatePo méthode, vous devez d’abord construire un XmlSerializer , en passant le type de la classe à
désérialiser dans le constructeur. De même, un FileStream est requis pour lire le document XML. Pour désérialiser
les objets, appelez la méthode Deserialize avec le FileStream en tant qu'argument. L'objet désérialisé doit être
converti en une variable d'objet de type PurchaseOrder . Le code lit ensuite les valeurs du PurchaseOrder
désérialisé. Notez que vous pouvez également lire le fichier PO.xml créé pour consulter le résultat XML réel.

Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization
Imports Microsoft.VisualBasic

' The XmlRoot attribute allows you to set an alternate name


' (PurchaseOrder) for the XML element and its namespace. By
' default, the XmlSerializer uses the class name. The attribute
' also allows you to set the XML namespace for the element. Lastly,
' the attribute sets the IsNullable property, which specifies whether
' the xsi:null attribute appears if the class instance is set to
' a null reference.
<XmlRoot("PurchaseOrder", _
Namespace := "http://www.cpandl.com", IsNullable := False)> _
Public Class PurchaseOrder
Public ShipTo As Address
Public OrderDate As String
' The XmlArrayAttribute changes the XML element name
' from the default of "OrderedItems" to "Items".
<XmlArray("Items")> _
Public OrderedItems() As OrderedItem
Public SubTotal As Decimal
Public ShipCost As Decimal
Public TotalCost As Decimal
End Class

Public Class Address


' The XmlAttribute attribute instructs the XmlSerializer to serialize the
' Name field as an XML attribute instead of an XML element (XML element is
' the default behavior).
<XmlAttribute()> _
Public Name As String
Public Line1 As String

' Setting the IsNullable property to false instructs the


' XmlSerializer that the XML attribute will not appear if
' the City field is set to a null reference.
<XmlElement(IsNullable := False)> _
Public City As String
Public City As String
Public State As String
Public Zip As String
End Class

Public Class OrderedItem


Public ItemName As String
Public Description As String
Public UnitPrice As Decimal
Public Quantity As Integer
Public LineTotal As Decimal

' Calculate is a custom method that calculates the price per item
' and stores the value in a field.
Public Sub Calculate()
LineTotal = UnitPrice * Quantity
End Sub
End Class

Public Class Test


Public Shared Sub Main()
' Read and write purchase orders.
Dim t As New Test()
t.CreatePO("po.xml")
t.ReadPO("po.xml")
End Sub

Private Sub CreatePO(filename As String)


' Creates an instance of the XmlSerializer class;
' specifies the type of object to serialize.
Dim serializer As New XmlSerializer(GetType(PurchaseOrder))
Dim writer As New StreamWriter(filename)
Dim po As New PurchaseOrder()

' Creates an address to ship and bill to.


Dim billAddress As New Address()
billAddress.Name = "Teresa Atkinson"
billAddress.Line1 = "1 Main St."
billAddress.City = "AnyTown"
billAddress.State = "WA"
billAddress.Zip = "00000"
' Set ShipTo and BillTo to the same addressee.
po.ShipTo = billAddress
po.OrderDate = System.DateTime.Now.ToLongDateString()

' Creates an OrderedItem.


Dim i1 As New OrderedItem()
i1.ItemName = "Widget S"
i1.Description = "Small widget"
i1.UnitPrice = CDec(5.23)
i1.Quantity = 3
i1.Calculate()

' Inserts the item into the array.


Dim items(0) As OrderedItem
items(0) = i1
po.OrderedItems = items
' Calculates the total cost.
Dim subTotal As New Decimal()
Dim oi As OrderedItem
For Each oi In items
subTotal += oi.LineTotal
Next oi
po.SubTotal = subTotal
po.ShipCost = CDec(12.51)
po.TotalCost = po.SubTotal + po.ShipCost
' Serializes the purchase order, and close the TextWriter.
serializer.Serialize(writer, po)
writer.Close()
End Sub
End Sub

Protected Sub ReadPO(filename As String)


' Creates an instance of the XmlSerializer class;
' specifies the type of object to be deserialized.
Dim serializer As New XmlSerializer(GetType(PurchaseOrder))
' If the XML document has been altered with unknown
' nodes or attributes, handles them with the
' UnknownNode and UnknownAttribute events.
AddHandler serializer.UnknownNode, AddressOf serializer_UnknownNode
AddHandler serializer.UnknownAttribute, AddressOf _
serializer_UnknownAttribute

' A FileStream is needed to read the XML document.


Dim fs As New FileStream(filename, FileMode.Open)
' Declare an object variable of the type to be deserialized.
Dim po As PurchaseOrder
' Uses the Deserialize method to restore the object's state
' with data from the XML document.
po = CType(serializer.Deserialize(fs), PurchaseOrder)
' Reads the order date.
Console.WriteLine(("OrderDate: " & po.OrderDate))

' Reads the shipping address.


Dim shipTo As Address = po.ShipTo
ReadAddress(shipTo, "Ship To:")
' Reads the list of ordered items.
Dim items As OrderedItem() = po.OrderedItems
Console.WriteLine("Items to be shipped:")
Dim oi As OrderedItem
For Each oi In items
Console.WriteLine((ControlChars.Tab & oi.ItemName & _
ControlChars.Tab & _
oi.Description & ControlChars.Tab & oi.UnitPrice & _
ControlChars.Tab & _
oi.Quantity & ControlChars.Tab & oi.LineTotal))
Next oi
' Reads the subtotal, shipping cost, and total cost.
Console.WriteLine((ControlChars.Cr & New String _
(ControlChars.Tab, 5) & _
" Subtotal" & ControlChars.Tab & po.SubTotal & ControlChars.Cr & _
New String(ControlChars.Tab, 5) & " Shipping" & ControlChars.Tab & _
po.ShipCost & ControlChars.Cr & New String(ControlChars.Tab, 5) & _
" Total" & New String(ControlChars.Tab, 2) & po.TotalCost))
End Sub

Protected Sub ReadAddress(a As Address, label As String)


' Reads the fields of the Address.
Console.WriteLine(label)
Console.Write((ControlChars.Tab & a.Name & ControlChars.Cr & _
ControlChars.Tab & a.Line1 & ControlChars.Cr & ControlChars.Tab & _
a.City & ControlChars.Tab & a.State & ControlChars.Cr & _
ControlChars.Tab & a.Zip & ControlChars.Cr))
End Sub

Protected Sub serializer_UnknownNode(sender As Object, e As _


XmlNodeEventArgs)
Console.WriteLine(("Unknown Node:" & e.Name & _
ControlChars.Tab & e.Text))
End Sub

Protected Sub serializer_UnknownAttribute(sender As Object, _


e As XmlAttributeEventArgs)
Dim attr As System.Xml.XmlAttribute = e.Attr
Console.WriteLine(("Unknown attribute " & attr.Name & "='" & _
attr.Value & "'"))
End Sub 'serializer_UnknownAttribute
End Class 'Test
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

// The XmlRoot attribute allows you to set an alternate name


// (PurchaseOrder) for the XML element and its namespace. By
// default, the XmlSerializer uses the class name. The attribute
// also allows you to set the XML namespace for the element. Lastly,
// the attribute sets the IsNullable property, which specifies whether
// the xsi:null attribute appears if the class instance is set to
// a null reference.
[XmlRoot("PurchaseOrder", Namespace="http://www.cpandl.com",
IsNullable = false)]
public class PurchaseOrder
{
public Address ShipTo;
public string OrderDate;
// The XmlArray attribute changes the XML element name
// from the default of "OrderedItems" to "Items".
[XmlArray("Items")]
public OrderedItem[] OrderedItems;
public decimal SubTotal;
public decimal ShipCost;
public decimal TotalCost;
}

public class Address


{
// The XmlAttribute attribute instructs the XmlSerializer to serialize the
// Name field as an XML attribute instead of an XML element (XML element is
// the default behavior).
[XmlAttribute]
public string Name;
public string Line1;

// Setting the IsNullable property to false instructs the


// XmlSerializer that the XML attribute will not appear if
// the City field is set to a null reference.
[XmlElement(IsNullable = false)]
public string City;
public string State;
public string Zip;
}

public class OrderedItem


{
public string ItemName;
public string Description;
public decimal UnitPrice;
public int Quantity;
public decimal LineTotal;

// Calculate is a custom method that calculates the price per item


// and stores the value in a field.
public void Calculate()
{
LineTotal = UnitPrice * Quantity;
}
}

public class Test


{
public static void Main()
{
// Read and write purchase orders.
Test t = new Test();
t.CreatePO("po.xml");
t.CreatePO("po.xml");
t.ReadPO("po.xml");
}

private void CreatePO(string filename)


{
// Creates an instance of the XmlSerializer class;
// specifies the type of object to serialize.
XmlSerializer serializer =
new XmlSerializer(typeof(PurchaseOrder));
TextWriter writer = new StreamWriter(filename);
PurchaseOrder po=new PurchaseOrder();

// Creates an address to ship and bill to.


Address billAddress = new Address();
billAddress.Name = "Teresa Atkinson";
billAddress.Line1 = "1 Main St.";
billAddress.City = "AnyTown";
billAddress.State = "WA";
billAddress.Zip = "00000";
// Sets ShipTo and BillTo to the same addressee.
po.ShipTo = billAddress;
po.OrderDate = System.DateTime.Now.ToLongDateString();

// Creates an OrderedItem.
OrderedItem i1 = new OrderedItem();
i1.ItemName = "Widget S";
i1.Description = "Small widget";
i1.UnitPrice = (decimal) 5.23;
i1.Quantity = 3;
i1.Calculate();

// Inserts the item into the array.


OrderedItem [] items = {i1};
po.OrderedItems = items;
// Calculate the total cost.
decimal subTotal = new decimal();
foreach(OrderedItem oi in items)
{
subTotal += oi.LineTotal;
}
po.SubTotal = subTotal;
po.ShipCost = (decimal) 12.51;
po.TotalCost = po.SubTotal + po.ShipCost;
// Serializes the purchase order, and closes the TextWriter.
serializer.Serialize(writer, po);
writer.Close();
}

protected void ReadPO(string filename)


{
// Creates an instance of the XmlSerializer class;
// specifies the type of object to be deserialized.
XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrder));
// If the XML document has been altered with unknown
// nodes or attributes, handles them with the
// UnknownNode and UnknownAttribute events.
serializer.UnknownNode+= new
XmlNodeEventHandler(serializer_UnknownNode);
serializer.UnknownAttribute+= new
XmlAttributeEventHandler(serializer_UnknownAttribute);

// A FileStream is needed to read the XML document.


FileStream fs = new FileStream(filename, FileMode.Open);
// Declares an object variable of the type to be deserialized.
PurchaseOrder po;
// Uses the Deserialize method to restore the object's state
// with data from the XML document. */
po = (PurchaseOrder) serializer.Deserialize(fs);
// Reads the order date.
// Reads the order date.
Console.WriteLine ("OrderDate: " + po.OrderDate);

// Reads the shipping address.


Address shipTo = po.ShipTo;
ReadAddress(shipTo, "Ship To:");
// Reads the list of ordered items.
OrderedItem [] items = po.OrderedItems;
Console.WriteLine("Items to be shipped:");
foreach(OrderedItem oi in items)
{
Console.WriteLine("\t"+
oi.ItemName + "\t" +
oi.Description + "\t" +
oi.UnitPrice + "\t" +
oi.Quantity + "\t" +
oi.LineTotal);
}
// Reads the subtotal, shipping cost, and total cost.
Console.WriteLine(
"\n\t\t\t\t\t Subtotal\t" + po.SubTotal +
"\n\t\t\t\t\t Shipping\t" + po.ShipCost +
"\n\t\t\t\t\t Total\t\t" + po.TotalCost
);
}

protected void ReadAddress(Address a, string label)


{
// Reads the fields of the Address.
Console.WriteLine(label);
Console.Write("\t"+
a.Name +"\n\t" +
a.Line1 +"\n\t" +
a.City +"\t" +
a.State +"\n\t" +
a.Zip +"\n");
}

protected void serializer_UnknownNode


(object sender, XmlNodeEventArgs e)
{
Console.WriteLine("Unknown Node:" + e.Name + "\t" + e.Text);
}

protected void serializer_UnknownAttribute


(object sender, XmlAttributeEventArgs e)
{
System.Xml.XmlAttribute attr = e.Attr;
Console.WriteLine("Unknown attribute " +
attr.Name + "='" + attr.Value + "'");
}
}

Le résultat XML peut se présenter comme suit.


<?xml version="1.0" encoding="utf-8"?>
<PurchaseOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.cpandl.com">
<ShipTo Name="Teresa Atkinson">
<Line1>1 Main St.</Line1>
<City>AnyTown</City>
<State>WA</State>
<Zip>00000</Zip>
</ShipTo>
<OrderDate>Wednesday, June 27, 2001</OrderDate>
<Items>
<OrderedItem>
<ItemName>Widget S</ItemName>
<Description>Small widget</Description>
<UnitPrice>5.23</UnitPrice>
<Quantity>3</Quantity>
<LineTotal>15.69</LineTotal>
</OrderedItem>
</Items>
<SubTotal>15.69</SubTotal>
<ShipCost>12.51</ShipCost>
<TotalCost>28.2</TotalCost>
</PurchaseOrder>

Voir aussi
Introduction à la sérialisation XML
Contrôle de la sérialisation XML à l’aide d’attributs
Attributs qui contrôlent la sérialisation XML
XmlSerializer, classe
Guide pratique pour sérialiser un objet
Guide pratique pour désérialiser un objet
Outil XML Schema Definition et sérialisation XML
18/07/2020 • 2 minutes to read • Edit Online

L’outil XML Schema Definition (outil XML Schema Definition (XSD. exe)) est installé avec les outils de .NET
Framework dans le cadre du ® Kit de développement logiciel (SDK) Windows. L’outil a deux principaux objectifs :
Générer des fichiers de classe C# ou Visual Basic conformes à un schéma de langage XSD (XML Schema
Definition) spécifique. L'outil considère un schéma XML comme un argument et génère un fichier qui
contient plusieurs classes qui, lorsqu'elles sont sérialisées avec XmlSerializer, se conforment au schéma.
Pour plus d’informations sur la façon d’utiliser l’outil pour générer des classes qui se conforment à un
schéma spécifique, consultez How to: Use the XML Schema Definition Tool to Generate Classes and XML
Schema Documents.
Générer un document de schéma XML à partir d’un fichier .dll ou .exe. Pour consulter le schéma d’un
ensemble de fichiers que vous avez créé ou d’un ensemble qui a été modifié avec des attributs, passez le
fichier DLL ou EXE comme argument à l’outil pour générer le schéma XML. Pour plus d’informations sur la
façon d’utiliser l’outil pour générer un document de schéma XML à partir d’un ensemble de classes,
consultez How to: Use the XML Schema Definition Tool to Generate Classes and XML Schema Documents.
Pour plus d’informations sur l’utilisation de l’outil, consultez outil XML Schema Definition (XSD. exe).

Voir aussi
DataSet
Introduction à la sérialisation XML
Outil XML Schema Definition (XSD. exe)
XmlSerializer
Guide pratique pour sérialiser un objet
Guide pratique pour désérialiser un objet
Guide pratique pour utiliser l’outil XML Schema Definition pour la génération de classes et de documents de
schéma XML
Prise en charge de la liaison de schéma XML
Contrôle de la sérialisation XML à l'aide d'attributs
18/07/2020 • 11 minutes to read • Edit Online

Les attributs peuvent être utilisés pour contrôler la sérialisation XML d'un objet ou pour créer un flux de
données XML différent à partir du même ensemble de classes. Pour plus d’informations sur la création d’un flux
de données XML différent, consultez Guide pratique pour spécifier un nom d’élément différent pour un flux XML.

NOTE
Si le code XML généré doit se conformer à la section 5 du document World Wide Web Consortium (W3C) intitulé simple
Object Access Protocol (SOAP) 1,1, utilisez les attributs listés dans attributs qui contrôlent la sérialisation encodéeselon le
protocole SOAP.

Par défaut, un nom d'élément XML est déterminé par le nom de la classe ou du membre. Dans une classe simple
nommée Book , un champ nommé ISBN génère une balise d’élément XML <ISBN> , comme illustré dans
l’exemple suivant.

Public Class Book


Public ISBN As String
End Class
' When an instance of the Book class is serialized, it might
' produce this XML:
' <ISBN>1234567890</ISBN>.

public class Book


{
public string ISBN;
}
// When an instance of the Book class is serialized, it might
// produce this XML:
// <ISBN>1234567890</ISBN>.

Ce comportement par défaut peut être modifié si vous souhaitez donner un nouveau nom à l'élément. Le code
suivant affiche la manière dont un attribut active cette option en définissant la propriété ElementName d'un
XmlElementAttribute.

Public Class TaxRates


< XmlElement(ElementName = "TaxRate")> _
Public ReturnTaxRate As Decimal
End Class

public class TaxRates {


[XmlElement(ElementName = "TaxRate")]
public decimal ReturnTaxRate;
}

Pour plus d’informations sur les attributs, consultez Attributs. Pour obtenir une liste complète des attributs qui
contrôlent la sérialisation XML, consultez Attributs qui contrôlent la sérialisation XML.
Contrôle de la sérialisation de tableau
Les attributs XmlArrayAttribute et XmlArrayItemAttribute sont conçus pour contrôler la sérialisation de tableaux.
À l'aide de ces attributs, vous pouvez contrôler le type de données de nom d'élément, d'espace de noms et de
schéma XML (XSD) (comme défini dans le document du World Wide Web Consortium [www.w3.org] intitulé
« XML Schema Part 2: Datatypes »). Vous pouvez également spécifier les types qui peuvent être inclus dans un
tableau.
XmlArrayAttribute détermine les propriétés de l'élément XML englobant obtenu lorsqu'un tableau est sérialisé.
Par exemple, la sérialisation du tableau suivant génère par défaut un élément XML nommé Employees . L'élément
Employees contient une série d'éléments nommée d'après le type de tableau Employee .

Public Class Group


Public Employees() As Employee
End Class
Public Class Employee
Public Name As String
End Class

public class Group {


public Employee[] Employees;
}
public class Employee {
public string Name;
}

Une instance sérialisée peut se présenter comme suit.

<Group>
<Employees>
<Employee>
<Name>Haley</Name>
</Employee>
</Employees>
</Group>

En appliquant un XmlArrayAttribute, vous pouvez modifier le nom de l'élément XML, comme suit.

Public Class Group


<XmlArray("TeamMembers")> _
Public Employees() As Employee
End Class

public class Group {


[XmlArray("TeamMembers")]
public Employee[] Employees;
}

Le code XML obtenu peut se présenter comme suit.


<Group>
<TeamMembers>
<Employee>
<Name>Haley</Name>
</Employee>
</TeamMembers>
</Group>

En revanche, XmlArrayItemAttribute contrôle la manière dont les éléments contenus dans le tableau sont
sérialisés. Notez que l'attribut est appliqué au champ retournant le tableau.

Public Class Group


<XmlArrayItem("MemberName")> _
Public Employee() As Employees
End Class

public class Group {


[XmlArrayItem("MemberName")]
public Employee[] Employees;
}

Le code XML obtenu peut se présenter comme suit.

<Group>
<Employees>
<MemberName>Haley</MemberName>
</Employees>
</Group>

Sérialisation de classes dérivées


XmlArrayItemAttribute permet également de sérialiser des classes dérivées. Par exemple, une autre classe
nommée Manager , dérivée de Employee , peut être ajoutée à l'exemple précédent. Si vous n'appliquez pas
XmlArrayItemAttribute, le code ne pourra pas s'exécuter car le type de classe dérivée ne sera pas reconnu. Pour
résoudre ce problème, appliquez l'attribut deux fois, en définissant à chaque fois la propriété Type pour chaque
type acceptable (type de base et dérivé).

Public Class Group


<XmlArrayItem(Type:=GetType(Employee)), _
XmlArrayItem(Type:=GetType(Manager))> _
Public Employees() As Employee
End Class
Public Class Employee
Public Name As String
End Class
Public Class Manager
Inherits Employee
Public Level As Integer
End Class
public class Group {
[XmlArrayItem(Type = typeof(Employee)),
XmlArrayItem(Type = typeof(Manager))]
public Employee[] Employees;
}
public class Employee {
public string Name;
}
public class Manager:Employee {
public int Level;
}

Une instance sérialisée peut se présenter comme suit.

<Group>
<Employees>
<Employee>
<Name>Haley</Name>
</Employee>
<Employee xsi:type = "Manager">
<Name>Ann</Name>
<Level>3</Level>
</Employee>
</Employees>
</Group>

Sérialisation d'un tableau sous forme de séquence d'éléments


Vous pouvez également sérialiser un tableau sous forme de séquence en deux dimensions d'éléments XML en
appliquant XmlElementAttribute au champ retournant le tableau suivant.

Public Class Group


<XmlElement> _
Public Employees() As Employee
End Class

public class Group {


[XmlElement]
public Employee[] Employees;
}

Une instance sérialisée peut se présenter comme suit.

<Group>
<Employees>
<Name>Haley</Name>
</Employees>
<Employees>
<Name>Noriko</Name>
</Employees>
<Employees>
<Name>Marco</Name>
</Employees>
</Group>

Pour différencier les deux flux de données XML, vous pouvez également utiliser l'outil XML Schema Definition
pour générer des fichiers de document de schéma XML (XSD) à partir du code compilé. (Pour plus d’informations
sur l’utilisation de l’outil, consultez l’outil XML Schema Definition et SÉRIALISATION XML.) Quand aucun attribut
n’est appliqué au champ, le schéma décrit l’élément de la manière suivante.

<xs:element minOccurs="0" maxOccurs ="1" name="Employees" type="ArrayOfEmployee" />

Lorsque XmlElementAttribute est appliqué au champ, le schéma obtenu décrit l'élément comme suit.

<xs:element minOccurs="0" maxOccurs="unbounded" name="Employees" type="Employee" />

Sérialisation d'un ArrayList


La classe ArrayList peut contenir une collection d'objets divers. Par conséquent, vous pouvez utiliser un ArrayList
plus souvent qu'un tableau. Au lieu de créer un champ qui retourne un tableau d'objets typés, vous pouvez
cependant créer un champ qui retourne un ArrayList unique. Toutefois, comme avec les tableaux, vous devez
indiquer à XmlSerializer les types d'objets que contient l'ArrayList. Pour ce faire, assignez plusieurs instances de
XmlElementAttribute à ce champ, comme illustré dans l'exemple suivant.

Public Class Group


<XmlElement(Type:=GetType(Employee)), _
XmlElement(Type:=GetType(Manager))> _
Public Info As ArrayList
End Class

public class Group {


[XmlElement(Type = typeof(Employee)),
XmlElement(Type = typeof(Manager))]
public ArrayList Info;
}

Contrôle de la sérialisation de classes à l'aide de XmlRootAttribute et


XmlTypeAttribute
Il est possible d'appliquer deux attributs à une seule et même classe : XmlRootAttribute et XmlTypeAttribute. Ces
attributs sont très semblables. XmlRootAttribute peut être appliqué à une seule classe : celle qui, une fois
sérialisée, représente l'élément ouvrant et fermant du document XML, c'est-à-dire l'élément racine. En revanche,
XmlTypeAttribute peut être appliqué à n'importe quelle classe, y compris la classe racine.
Par exemple, la classe Group correspond à la classe racine dans les exemples précédents, et tous ses champs et
propriétés publics deviennent les éléments XML recherchés dans le document XML. Par conséquent, il ne peut y
avoir qu'une seule classe racine. En appliquant XmlRootAttribute, vous pouvez contrôler le flux de données XML
généré par XmlSerializer. Par exemple, vous pouvez modifier le nom de l'élément et l'espace de noms.
XmlTypeAttribute vous permet de contrôler le schéma du code XML généré. Cette fonction est utile lorsque vous
devez publier le schéma via un service Web XML. L'exemple suivant applique XmlTypeAttribute et
XmlRootAttribute à la même classe.

<XmlRoot("NewGroupName"), _
XmlType("NewTypeName")> _
Public Class Group
Public Employees() As Employee
End Class
[XmlRoot("NewGroupName")]
[XmlType("NewTypeName")]
public class Group {
public Employee[] Employees;
}

Si cette classe est compilée et que l'outil XML Schema Definition est utilisé pour générer son schéma, vous
obtenez le code XML suivant qui décrit Group .

<xs:element name="NewGroupName" type="NewTypeName" />

En revanche, si vous deviez sérialiser une instance de la classe, vous obtiendriez uniquement NewGroupName dans
le document XML.

<NewGroupName>
. . .
</NewGroupName>

Empêcher la sérialisation avec XmlIgnoreAttribute


Dans certains cas, une propriété ou un champ public ne doit pas être sérialisé. Par exemple, un champ ou une
propriété peut servir à contenir des métadonnées. Dans de tels cas, appliquez XmlIgnoreAttribute au champ ou à
la propriété pour que XmlSerializer puisse l'ignorer.

Voir aussi
Attributs qui contrôlent la sérialisation XML
Attributs qui contrôlent la sérialisation encodée selon le protocole SOAP
Introduction à la sérialisation XML
Exemples de sérialisation XML
Guide pratique pour spécifier un nom d’élément différent pour un flux XML
Guide pratique pour sérialiser un objet
Guide pratique pour désérialiser un objet
Attributs qui contrôlent la sérialisation XML
18/07/2020 • 4 minutes to read • Edit Online

Vous pouvez appliquer les attributs du tableau suivant à des classes et des membres de classe pour contrôler la
manière dont XmlSerializer sérialise ou désérialise une instance de la classe. Pour comprendre comment ces
attributs contrôlent la sérialisation XML, consultez Contrôle de la sérialisation XML à l’aide d’attributs.
Ces attributs peuvent également être utilisés pour contrôler les messages SOAP de style littéral générés par un
service Web XML. Pour plus d’informations sur l’application de ces attributs à une méthode de services web XML,
consultez Sérialisation XML avec les services Web XML.
Pour plus d’informations sur les attributs, consultez Attributs.

AT T RIB UT S’A P P L IQ UE À SP ÉC IF IE

XmlAnyAttributeAttribute Champ public, propriété, paramètre ou Lors de la désérialisation, le tableau est


valeur de retour qui retourne un rempli avec les objets XmlAttribute qui
tableau d'objets XmlAttribute. représentent tous les attributs XML
inconnus du schéma.

XmlAnyElementAttribute Champ public, propriété, paramètre ou Lors de la désérialisation, le tableau est


valeur de retour qui retourne un rempli avec les objets XmlElement qui
tableau d'objets XmlElement. représentent tous les éléments XML
inconnus du schéma.

XmlArrayAttribute Champ public, propriété, paramètre ou Les membres du tableau sont générés
valeur de retour qui retourne un en tant que membres d'un
tableau d'objets complexes. tableau XML.

XmlArrayItemAttribute Champ public, propriété, paramètre ou Types dérivés qui peuvent être insérés
valeur de retour qui retourne un dans un tableau. S'applique
tableau d'objets complexes. habituellement avec XmlArrayAttribute.

XmlAttributeAttribute Champ public, propriété, paramètre ou Le membre est sérialisé en tant


valeur de retour. qu'attribut XML.

XmlChoiceIdentifierAttribute Champ public, propriété, paramètre ou L'ambiguïté du membre peut être levée
valeur de retour. à l'aide d'une énumération.

XmlElementAttribute Champ public, propriété, paramètre ou Le champ ou la propriété est sérialisé


valeur de retour. en tant qu'élément XML.

XmlEnumAttribute Champ public qui est un identificateur Nom d'élément d'un membre
d'énumération. d'énumération.

XmlIgnoreAttribute Champs et propriétés publics. La propriété ou le champ doit être


ignoré lorsque la classe conteneur est
sérialisée.
AT T RIB UT S’A P P L IQ UE À SP ÉC IF IE

XmlIncludeAttribute Déclarations de classe dérivée La classe doit être incluse lors de la


publiques et valeurs de retour de génération de schémas (afin d'être
méthodes publiques pour les reconnue en cas de sérialisation).
documents WSDL (Web Services
Description Language).

XmlRootAttribute Déclarations de classe publiques. Contrôle la sérialisation XML de


l'attribut cible en tant qu'élément
racine XML. Utilisez l'attribut pour
préciser l'espace de noms et le nom
d'élément.

XmlTextAttribute Champs et propriétés publics. La propriété ou le champ doit être


sérialisé en tant que texte XML.

XmlTypeAttribute Déclarations de classe publiques. Nom et espace de noms du type XML.

En plus de ces attributs, qui se trouvent tous dans l'espace de noms System.Xml.Serialization, vous pouvez
également appliquer l'attribut DefaultValueAttribute à un champ. DefaultValueAttribute définit la valeur qui
sera assignée automatiquement au membre si aucune valeur n’est spécifiée.
Pour contrôler la sérialisation XML encodée selon le protocole SOAP, consultez Attributs qui contrôlent la
sérialisation encodée selon le protocole SOAP.

Voir aussi
Sérialisation XML et SOAP
XmlSerializer
Contrôle de la sérialisation XML à l’aide d’attributs
Guide pratique pour spécifier un nom d’élément différent pour un flux XML
Guide pratique pour sérialiser un objet
Guide pratique pour désérialiser un objet
Sérialisation XML avec les services Web XML
18/07/2020 • 8 minutes to read • Edit Online

La sérialisation XML est le mécanisme de transport sous-jacent utilisé dans l'architecture de services Web XML,
exécutée par la classe XmlSerializer. Pour contrôler le code XML généré par un service web XML, vous pouvez
appliquer les attributs répertoriés dans Attributs qui contrôlent la sérialisation XML et Attributs qui contrôlent la
sérialisation encodée selon le protocole SOAP aux classes, valeurs de retour, paramètres et champs d’un fichier
utilisé pour créer un service web XML (.asmx). Pour plus d’informations sur la création d’un service Web XML,
consultez services Web XML à l’aide de ASP.net.

Styles littéral et encodé


Le code XML généré par un service Web XML peut être mis en forme de deux manières : littéral ou encodé,
comme expliqué dans personnalisation de la mise en forme des messages SOAP. Par conséquent, il existe deux
ensembles d'attributs qui contrôlent la sérialisation XML. Les attributs répertoriés dans Attributs qui contrôlent la
sérialisation XML sont conçus pour contrôler le code XML de style littéral. Les attributs répertoriés dans Attributs
qui contrôlent la sérialisation encodée selon le protocole SOAP contrôlent le style de code encodé. En appliquant
ces attributs de manière sélective, vous pouvez personnaliser une application afin qu'elle retourne l'un ou l'autre
de ces styles, voire les deux. En outre, ces attributs peuvent être appliqués (le cas échéant) aux valeurs de retour et
aux paramètres.
Exemple d'utilisation des deux styles
Lorsque vous créez un service Web XML, vous pouvez utiliser les deux ensembles d'attributs sur les méthodes.
Dans l'exemple de code suivant, la classe intitulée MyService contient deux méthodes de services Web XML,
MyLiteralMethod et MyEncodedMethod . Ces deux méthodes exécutent la même fonction : retourner une instance de
la classe Order . Dans la classe Order , les attributs XmlTypeAttribute et SoapTypeAttribute s’appliquent tous deux
au champ OrderID , mais leur propriété ElementName n’a pas la même valeur.
Pour exécuter l'exemple, collez le code dans un fichier portant une extension .asmx et placez ce fichier dans un
répertoire virtuel géré par les Services Internet (IIS). Dans un navigateur HTML, tel qu'Internet Explorer, tapez le
nom de l'ordinateur, du répertoire virtuel et du fichier.
<%@ WebService Language="VB" Class="MyService" %>
Imports System
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Xml.Serialization
Public Class Order
' Both types of attributes can be applied. Depending on which type
' the method used, either one will affect the call.
<SoapElement(ElementName:= "EncodedOrderID"), _
XmlElement(ElementName:= "LiteralOrderID")> _
public OrderID As String
End Class

Public Class MyService


<WebMethod, SoapDocumentMethod> _
public Function MyLiteralMethod() As Order
Dim myOrder As Order = New Order()
return myOrder
End Function
<WebMethod, SoapRpcMethod> _
public Function MyEncodedMethod() As Order
Dim myOrder As Order = New Order()
return myOrder
End Function
End Class

<%@ WebService Language="C#" Class="MyService" %>


using System;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Serialization;
public class Order {
// Both types of attributes can be applied. Depending on which type
// the method used, either one will affect the call.
[SoapElement(ElementName = "EncodedOrderID")]
[XmlElement(ElementName = "LiteralOrderID")]
public String OrderID;
}
public class MyService {
[WebMethod][SoapDocumentMethod]
public Order MyLiteralMethod(){
Order myOrder = new Order();
return myOrder;
}
[WebMethod][SoapRpcMethod]
public Order MyEncodedMethod(){
Order myOrder = new Order();
return myOrder;
}
}

L'exemple de code suivant appelle MyLiteralMethod . Le nom d'élément est remplacé par "LiteralOrderID."
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<MyLiteralMethodResponse xmlns="http://tempuri.org/">
<MyLiteralMethodResult>
<LiteralOrderID>string</LiteralOrderID>
</MyLiteralMethodResult>
</MyLiteralMethodResponse>
</soap:Body>
</soap:Envelope>

L'exemple de code suivant appelle MyEncodedMethod . Le nom d'élément est "EncodedOrderID."

<?xml version="1.0" encoding="utf-8"?>


<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:tns="http://tempuri.org/" xmlns:types="http://tempuri.org/encodedTypes"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<tns:MyEncodedMethodResponse>
<MyEncodedMethodResult href="#id1" />
</tns:MyEncodedMethodResponse>
<types:Order id="id1" xsi:type="types:Order">
<EncodedOrderID xsi:type="xsd:string">string</EncodedOrderID>
</types:Order>
</soap:Body>
</soap:Envelope>

Application d'attributs aux valeurs de retour


Vous pouvez également appliquer des attributs aux valeurs de retour pour contrôler l'espace de noms, le nom
d'élément, etc. L'exemple de code suivant applique l'attribut XmlElementAttribute à la valeur de retour de la
méthode MyLiteralMethod . Ainsi, vous pouvez contrôler l'espace de noms et le nom d'élément.

<WebMethod, SoapDocumentMethod> _
public Function MyLiteralMethod() As _
<XmlElement(Namespace:="http://www.cohowinery.com", _
ElementName:= "BookOrder")> _
Order
Dim myOrder As Order = New Order()
return myOrder
End Function

[return: XmlElement(Namespace = "http://www.cohowinery.com",


ElementName = "BookOrder")]
[WebMethod][SoapDocumentMethod]
public Order MyLiteralMethod(){
Order myOrder = new Order();
return myOrder;
}

Lorsqu'il est appelé, le code retourne du code XML qui se présente comme suit.
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<MyLiteralMethodResponse xmlns="http://tempuri.org/">
<BookOrder xmlns="http://www.cohowinery.com">
<LiteralOrderID>string</LiteralOrderID>
</BookOrder>
</MyLiteralMethodResponse>
</soap:Body>
</soap:Envelope>

Attributs appliqués à des paramètres


Vous pouvez également appliquer des attributs à des paramètres pour spécifier l'espace de noms, le nom
d'élément, etc. L'exemple de code suivant ajoute un paramètre à la méthode MyLiteralMethodResponse et applique
l'attribut XmlAttributeAttribute au paramètre. Le nom d'élément et l'espace de noms sont tous deux définis pour
le paramètre.

<WebMethod, SoapDocumentMethod> _
public Function MyLiteralMethod(<XmlElement _
("MyOrderID", Namespace:="http://www.microsoft.com")>ID As String) As _
<XmlElement(Namespace:="http://www.cohowinery.com", _
ElementName:= "BookOrder")> _
Order
Dim myOrder As Order = New Order()
myOrder.OrderID = ID
return myOrder
End Function

[return: XmlElement(Namespace = "http://www.cohowinery.com",


ElementName = "BookOrder")]
[WebMethod][SoapDocumentMethod]
public Order MyLiteralMethod([XmlElement("MyOrderID",
Namespace="http://www.microsoft.com")] string ID){
Order myOrder = new Order();
myOrder.OrderID = ID;
return myOrder;
}

La demande SOAP peut se présenter comme suit.

<?xml version="1.0" encoding="utf-8"?>


<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<MyLiteralMethod xmlns="http://tempuri.org/">
<MyOrderID xmlns="http://www.microsoft.com">string</MyOrderID>
</MyLiteralMethod>
</soap:Body>
</soap:Envelope>

Application d'attributs à des classes


Si vous devez contrôler l'espace de noms des éléments qui correspondent aux classes, vous pouvez appliquer
XmlTypeAttribute , XmlRootAttribute et SoapTypeAttribute , si nécessaire. Les exemples de code suivants
s'appliquent tous trois à la classe Order .
<XmlType("BigBookService"), _
SoapType("SoapBookService"), _
XmlRoot("BookOrderForm")> _
Public Class Order
' Both types of attributes can be applied. Depending on which
' the method used, either one will affect the call.
<SoapElement(ElementName:= "EncodedOrderID"), _
XmlElement(ElementName:= "LiteralOrderID")> _
public OrderID As String
End Class

[XmlType("BigBooksService", Namespace = "http://www.cpandl.com")]


[SoapType("SoapBookService")]
[XmlRoot("BookOrderForm")]
public class Order {
// Both types of attributes can be applied. Depending on which
// the method used, either one will affect the call.
[SoapElement(ElementName = "EncodedOrderID")]
[XmlElement(ElementName = "LiteralOrderID")]
public String OrderID;
}

Vous pouvez visualiser les résultats de l'application de XmlTypeAttribute et de SoapTypeAttribute lorsque vous
examinez la description de service, comme illustré dans l'exemple de code suivant.

<s:element name="BookOrderForm" type="s0:BigBookService" />


<s:complexType name="BigBookService">
<s:sequence>
<s:element minOccurs="0" maxOccurs="1" name="LiteralOrderID" type="s:string" />
</s:sequence>

<s:schema targetNamespace="http://tempuri.org/encodedTypes">
<s:complexType name="SoapBookService">
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="EncodedOrderID" type="s:string" />
</s:sequence>
</s:complexType>
</s:schema>
</s:complexType>

L'action de XmlRootAttribute est visible également dans les résultats des protocoles HTTP GET et HTTP POST,
comme suit.

<?xml version="1.0" encoding="utf-8"?>


<BookOrderForm xmlns="http://tempuri.org/">
<LiteralOrderID>string</LiteralOrderID>
</BookOrderForm>

Voir aussi
Sérialisation XML et SOAP
Attributs qui contrôlent la sérialisation encodée selon le protocole SOAP
Comment : sérialiser un objet en tant que flux XML encodé selon le protocole SOAP
Guide pratique pour remplacer la sérialisation XML encodée selon le protocole SOAP
Introduction à la sérialisation XML
Guide pratique pour sérialiser un objet
Guide pratique pour désérialiser un objet
Attributs qui contrôlent la sérialisation encodée
selon le protocole SOAP
18/07/2020 • 2 minutes to read • Edit Online

Le document World Wide Web Consortium (W3C) nommé simple Object Access Protocol (SOAP) 1,1 contient
une section facultative (section 5) qui décrit comment les paramètres SOAP peuvent être encodés. Pour se
conformer à la section 5 de la spécification, vous devez utiliser un ensemble spécial d’attributs figurant dans
l’espace de noms System.Xml.Serialization. Appliquez ces attributs en fonction des classes et membres de
classes, puis utilisez XmlSerializer pour sérialiser les instances de la ou des classes.
Le tableau suivant affiche les attributs, leurs conditions d'application et l'action qu'ils entraînent. Pour plus
d’informations sur l’utilisation de ces attributs pour contrôler la sérialisation XML, consultez Guide pratique
pour sérialiser un objet en tant que flux XML encodé selon le protocole SOAP et Guide pratique pour remplacer
la sérialisation XML encodée selon le protocole SOAP.
Pour plus d’informations sur les attributs, consultez Attributs.

AT T RIB UT S’A P P L IQ UE À SP ÉC IF IE

SoapAttributeAttribute Champ public, propriété, paramètre ou Le membre de classe est sérialisé en


valeur de retour. tant qu'attribut XML.

SoapElementAttribute Champ public, propriété, paramètre ou La classe est sérialisée en tant


valeur de retour. qu'élément XML.

SoapEnumAttribute Champ public qui est un identificateur Nom d'élément d'un membre
d'énumération. d'énumération.

SoapIgnoreAttribute Champs et propriétés publics. La propriété ou le champ doit être


ignoré lorsque la classe conteneur est
sérialisée.

SoapIncludeAttribute Déclarations de classe dérivée Le type doit être inclus lors de la


publiques et méthodes publiques pour génération de schémas (afin d'être
les documents WSDL (Web Services reconnu en cas de sérialisation).
Description Language).

SoapTypeAttribute Déclarations de classe publiques. La classe doit être sérialisée en tant


que type XML.

Voir aussi
Sérialisation XML et SOAP
Comment : sérialiser un objet en tant que flux XML encodé selon le protocole SOAP
Guide pratique pour remplacer la sérialisation XML encodée selon le protocole SOAP
Attributs
XmlSerializer
Guide pratique pour sérialiser un objet
Guide pratique pour désérialiser un objet
Comment : sérialiser un objet
18/07/2020 • 2 minutes to read • Edit Online

Pour sérialiser un objet, créez tout d'abord l'objet à sérialiser et définissez ses propriétés et champs publics. Pour
ce faire, vous devez déterminer le format de transport dans lequel le flux de données XML sera stocké, sous
forme de flux de données ou de fichier. Par exemple, si le flux de données XML doit être enregistré dans un
formulaire permanent, créez un objet FileStream.

NOTE
Pour obtenir plus d’exemples de sérialisation XML, consultez Exemples de sérialisation XML.

Pour sérialiser un objet


1. Créez l'objet et définissez ses champs et propriétés publics.
2. Construisez un XmlSerializer à l'aide du type de l'objet. Pour plus d'informations, consultez les
constructeurs de classe XmlSerializer.
3. Appelez la méthode Serialize pour générer un flux de données XML ou une représentation de fichier des
propriétés et des champs publics de l'objet. L'exemple suivant illustre la création d'un fichier.

Dim myObject As MySerializableClass = New MySerializableClass()


' Insert code to set properties and fields of the object.
Dim mySerializer As XmlSerializer = New XmlSerializer(GetType(MySerializableClass))
' To write to a file, create a StreamWriter object.
Dim myWriter As StreamWriter = New StreamWriter("myFileName.xml")
mySerializer.Serialize(myWriter, myObject)
myWriter.Close()

MySerializableClass myObject = new MySerializableClass();


// Insert code to set properties and fields of the object.
XmlSerializer mySerializer = new
XmlSerializer(typeof(MySerializableClass));
// To write to a file, create a StreamWriter object.
StreamWriter myWriter = new StreamWriter("myFileName.xml");
mySerializer.Serialize(myWriter, myObject);
myWriter.Close();

Voir aussi
Introduction à la sérialisation XML
Guide pratique pour désérialiser un objet
Comment désérialiser un objet à l’aide de
XmlSerializer
18/07/2020 • 2 minutes to read • Edit Online

Lorsque vous désérialisez un objet, le format de transport vous permet de déterminer si vous créez un flux de
données ou un objet de fichier. Après avoir déterminé le format de transport, vous pouvez appeler la méthode
Serialize ou Deserialize, si nécessaire.

Pour désérialiser un objet


1. Construisez un XmlSerializer à l'aide du type d'objet à désérialiser.
2. Appelez la méthode Deserialize pour produire un réplica de l'objet. Lors de la désérialisation, vous devez
effectuer un cast de l’objet retourné vers le type de l’original, comme indiqué dans l’exemple suivant, qui
désérialise l’objet à partir d’un fichier (même s’il peut également être désérialisé à partir d’un flux).

' Construct an instance of the XmlSerializer with the type


' of object that is being deserialized.
Dim mySerializer As New XmlSerializer(GetType(MySerializableClass))
' To read the file, create a FileStream.
Dim myFileStream As New FileStream("myFileName.xml", FileMode.Open)
' Call the Deserialize method and cast to the object type.
Dim myObject = CType( _
mySerializer.Deserialize(myFileStream), MySerializableClass)

// Construct an instance of the XmlSerializer with the type


// of object that is being deserialized.
var mySerializer = new XmlSerializer(typeof(MySerializableClass));
// To read the file, create a FileStream.
var myFileStream = new FileStream("myFileName.xml", FileMode.Open);
// Call the Deserialize method and cast to the object type.
var myObject = (MySerializableClass) mySerializer.Deserialize(myFileStream)

Voir aussi
Introduction à la sérialisation XML
Guide pratique pour sérialiser un objet
Comment : utiliser l'outil XML Schema Definition
pour générer des classes et des documents de
schéma XML
18/07/2020 • 3 minutes to read • Edit Online

L'outil XML Schema Definition (Xsd.exe) vous permet de générer un schéma XML qui décrit une classe ou de
générer la classe définie par un schéma XML. Les procédures suivantes indiquent comment exécuter ces
opérations.
L’outil XML Schema Definition (XSD. exe) se trouve généralement à l’emplacement suivant :
C : \ Program Files (x86) \ Microsoft SDK \ Windows \ {version} \ bin \ netfx {version} Tools\
Pour générer des classes qui se conforment à un schéma spécifique
1. Ouvrez une invite de commandes.
2. Passez par exemple le schéma XML en tant qu'argument à l'outil XML Schema Definition, qui crée un
ensemble de classes correspondant précisément au schéma XML :

xsd mySchema.xsd

L'outil ne peut traiter que les schémas qui référencent la spécification XML du W3C (World Wide Web
Consortium) du 16 mars 2001. En d’autres termes, l’espace de noms du schéma XML doit être «
http://www.w3.org/2001/XMLSchema », comme indiqué dans l’exemple suivant.

<?xml version="1.0" encoding="utf-8"?>


<xs:schema attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace=""
xmlns:xs="http://www.w3.org/2001/XMLSchema" />

3. Modifiez si nécessaire les classes avec les méthodes, les propriétés ou les champs. Pour plus d’informations
sur la modification d’une classe avec des attributs, consultez Contrôle de la sérialisation XML à l’aide
d’attributs et Attributs qui contrôlent la sérialisation encodée selon le protocole SOAP.
Il est souvent utile d'examiner le schéma du flux de données XML généré lorsque les instances d'une ou de
plusieurs classes sont sérialisées. Par exemple, vous pouvez publier votre schéma pour d'autres utilisateurs ou le
comparer à un schéma auquel vous tentez de vous conformer.
Pour générer un document de schéma XML à partir d'un ensemble de classes
1. Compilez la ou les classes dans une DLL.
2. Ouvrez une invite de commandes.
3. Passez la DLL en tant qu'argument dans Xsd.exe, par exemple :

xsd MyFile.dll

Le ou les schémas sont écrits et commencent par le nom "schema0.xsd."

Voir aussi
DataSet
Outil XML Schema Definition et sérialisation XML
Introduction à la sérialisation XML
Outil XML Schema Definition (XSD. exe)
XmlSerializer
Guide pratique pour sérialiser un objet
Guide pratique pour désérialiser un objet
Comment : contrôler la sérialisation de classes
dérivées
18/07/2020 • 5 minutes to read • Edit Online

L’utilisation de l’attribut XmlElementAttribute pour modifier le nom d’un élément XML ne constitue pas l’unique
moyen de personnaliser la sérialisation d’un objet. Vous pouvez également personnaliser le flux de données XML
en effectuant une dérivation à partir d'une classe existante et en indiquant à l'instance XmlSerializer comment
sérialiser la nouvelle classe.
Par exemple, d'après une classe Book , vous pouvez effectuer une dérivation et créer une classe ExpandedBook qui
dispose de quelques propriétés supplémentaires. Toutefois, vous devez demander à XmlSerializer d’accepter le
type dérivé lors de la sérialisation ou de la désérialisation. Pour ce faire, créez une instance XmlElementAttribute et
affectez à sa propriété Type le type de la classe dérivée. Ajoutez XmlElementAttribute à une instance
XmlAttributes. Ajoutez ensuite XmlAttributes à une instance XmlAttributeOverrides, en spécifiant le type qui est
substitué et le nom du membre qui accepte la classe dérivée. Cela est illustré par l'exemple suivant.

Exemple
Public Class Orders
public Books() As Book
End Class

Public Class Book


public ISBN As String
End Class

Public Class ExpandedBook


Inherits Book
public NewEdition As Boolean
End Class

Public Class Run


Shared Sub Main()
Dim t As Run = New Run()
t.SerializeObject("Book.xml")
t.DeserializeObject("Book.xml")
End Sub

Public Sub SerializeObject(filename As String)


' Each overridden field, property, or type requires
' an XmlAttributes instance.
Dim attrs As XmlAttributes = New XmlAttributes()

' Creates an XmlElementAttribute instance to override the


' field that returns Book objects. The overridden field
' returns Expanded objects instead.
Dim attr As XmlElementAttribute = _
New XmlElementAttribute()
attr.ElementName = "NewBook"
attr.Type = GetType(ExpandedBook)

' Adds the element to the collection of elements.


attrs.XmlElements.Add(attr)

' Creates the XmlAttributeOverrides.


Dim attrOverrides As XmlAttributeOverrides = _
New XmlAttributeOverrides()
' Adds the type of the class that contains the overridden
' member, as well as the XmlAttributes instance to override it
' with, to the XmlAttributeOverrides instance.
attrOverrides.Add(GetType(Orders), "Books", attrs)

' Creates the XmlSerializer using the XmlAttributeOverrides.


Dim s As XmlSerializer = _
New XmlSerializer(GetType(Orders), attrOverrides)

' Writing the file requires a TextWriter instance.


Dim writer As TextWriter = New StreamWriter(filename)

' Creates the object to be serialized.


Dim myOrders As Orders = New Orders()

' Creates an object of the derived type.


Dim b As ExpandedBook = New ExpandedBook()
b.ISBN= "123456789"
b.NewEdition = True
myOrders.Books = New ExpandedBook(){b}

' Serializes the object.


s.Serialize(writer,myOrders)
writer.Close()
End Sub

Public Sub DeserializeObject(filename As String)


Dim attrOverrides As XmlAttributeOverrides = _
New XmlAttributeOverrides()
Dim attrs As XmlAttributes = New XmlAttributes()

' Creates an XmlElementAttribute to override the


' field that returns Book objects. The overridden field
' returns Expanded objects instead.
Dim attr As XmlElementAttribute = _
New XmlElementAttribute()
attr.ElementName = "NewBook"
attr.Type = GetType(ExpandedBook)

' Adds the XmlElementAttribute to the collection of objects.


attrs.XmlElements.Add(attr)

attrOverrides.Add(GetType(Orders), "Books", attrs)

' Creates the XmlSerializer using the XmlAttributeOverrides.


Dim s As XmlSerializer = _
New XmlSerializer(GetType(Orders), attrOverrides)

Dim fs As FileStream = New FileStream(filename, FileMode.Open)


Dim myOrders As Orders = CType( s.Deserialize(fs), Orders)
Console.WriteLine("ExpandedBook:")

' The difference between deserializing the overridden


' XML document and serializing it is this: To read the derived
' object values, you must declare an object of the derived type
' and cast the returned object to it.
Dim expanded As ExpandedBook
Dim b As Book
for each b in myOrders.Books
expanded = CType(b, ExpandedBook)
Console.WriteLine(expanded.ISBN)
Console.WriteLine(expanded.NewEdition)
Next
End Sub
End Class

public class Orders


public class Orders
{
public Book[] Books;
}

public class Book


{
public string ISBN;
}

public class ExpandedBook:Book


{
public bool NewEdition;
}

public class Run


{
public void SerializeObject(string filename)
{
// Each overridden field, property, or type requires
// an XmlAttributes instance.
XmlAttributes attrs = new XmlAttributes();

// Creates an XmlElementAttribute instance to override the


// field that returns Book objects. The overridden field
// returns Expanded objects instead.
XmlElementAttribute attr = new XmlElementAttribute();
attr.ElementName = "NewBook";
attr.Type = typeof(ExpandedBook);

// Adds the element to the collection of elements.


attrs.XmlElements.Add(attr);

// Creates the XmlAttributeOverrides instance.


XmlAttributeOverrides attrOverrides = new XmlAttributeOverrides();

// Adds the type of the class that contains the overridden


// member, as well as the XmlAttributes instance to override it
// with, to the XmlAttributeOverrides.
attrOverrides.Add(typeof(Orders), "Books", attrs);

// Creates the XmlSerializer using the XmlAttributeOverrides.


XmlSerializer s =
new XmlSerializer(typeof(Orders), attrOverrides);

// Writing the file requires a TextWriter instance.


TextWriter writer = new StreamWriter(filename);

// Creates the object to be serialized.


Orders myOrders = new Orders();

// Creates an object of the derived type.


ExpandedBook b = new ExpandedBook();
b.ISBN= "123456789";
b.NewEdition = true;
myOrders.Books = new ExpandedBook[]{b};

// Serializes the object.


s.Serialize(writer,myOrders);
writer.Close();
}

public void DeserializeObject(string filename)


{
XmlAttributeOverrides attrOverrides =
new XmlAttributeOverrides();
XmlAttributes attrs = new XmlAttributes();

// Creates an XmlElementAttribute to override the


// field that returns Book objects. The overridden field
// field that returns Book objects. The overridden field
// returns Expanded objects instead.
XmlElementAttribute attr = new XmlElementAttribute();
attr.ElementName = "NewBook";
attr.Type = typeof(ExpandedBook);

// Adds the XmlElementAttribute to the collection of objects.


attrs.XmlElements.Add(attr);

attrOverrides.Add(typeof(Orders), "Books", attrs);

// Creates the XmlSerializer using the XmlAttributeOverrides.


XmlSerializer s =
new XmlSerializer(typeof(Orders), attrOverrides);

FileStream fs = new FileStream(filename, FileMode.Open);


Orders myOrders = (Orders) s.Deserialize(fs);
Console.WriteLine("ExpandedBook:");

// The difference between deserializing the overridden


// XML document and serializing it is this: To read the derived
// object values, you must declare an object of the derived type
// and cast the returned object to it.
ExpandedBook expanded;
foreach(Book b in myOrders.Books)
{
expanded = (ExpandedBook)b;
Console.WriteLine(
expanded.ISBN + "\n" +
expanded.NewEdition);
}
}
}

Voir aussi
XmlSerializer
XmlElementAttribute
XmlAttributes
XmlAttributeOverrides
Sérialisation XML et SOAP
Guide pratique pour sérialiser un objet
Guide pratique pour spécifier un nom d’élément différent pour un flux XML
Comment : spécifier un nom d'élément différent
pour un flux XML
18/07/2020 • 3 minutes to read • Edit Online

Via XmlSerializer, vous pouvez générer plusieurs flux de données XML avec un même ensemble de classes. Vous
pouvez procéder ainsi car deux services Web XML différents nécessitent les mêmes informations de base, avec
seulement de légères différences. Par exemple, imaginez deux services Web XML qui traitent des commandes de
livres. Ils nécessitent donc tous les deux des numéros ISBN. Un service utilise la balise <ISBN> , tandis que la
seconde utilise la balise <BookID> . Vous disposez d'une classe nommée Book qui contient un champ nommé
ISBN . Lorsqu'une instance de la classe Book est sérialisée, elle utilise par défaut le nom de membre (ISBN)
comme nom d'élément de balise. Pour le premier service Web XML, c'est ce qui est prévu. Toutefois, pour envoyer
le flux de données XML au deuxième service Web XML, vous devez substituer la sérialisation afin que le nom
d'élément de la balise soit BookID .

Pour créer un flux de données XML avec un nom d'élément différent


1. Créez une instance de la classe XmlElementAttribute.
2. Affectez "BookID" à ElementName de XmlElementAttribute.
3. Créez une instance de la classe XmlAttributes.
4. Ajoutez l'objet XmlElementAttribute à la collection accessible via la propriété XmlElements de
XmlAttributes.
5. Créez une instance de la classe XmlAttributeOverrides.
6. Ajoutez XmlAttributes à XmlAttributeOverrides, en passant le type de l'objet à substituer et le nom du
membre substitué.
7. Créez une instance de la classe XmlSerializer avec XmlAttributeOverrides .
8. Créez une instance de la classe Book et sérialisez ou désérialisez-la.

Exemple
Public Function SerializeOverride()
' Creates an XmlElementAttribute with the alternate name.
Dim myElementAttribute As XmlElementAttribute = _
New XmlElementAttribute()
myElementAttribute.ElementName = "BookID"
Dim myAttributes As XmlAttributes = New XmlAttributes()
myAttributes.XmlElements.Add(myElementAttribute)
Dim myOverrides As XmlAttributeOverrides = New XmlAttributeOverrides()
myOverrides.Add(typeof(Book), "ISBN", myAttributes)
Dim mySerializer As XmlSerializer = _
New XmlSerializer(GetType(Book), myOverrides)
Dim b As Book = New Book()
b.ISBN = "123456789"
' Creates a StreamWriter to write the XML stream to.
Dim writer As StreamWriter = New StreamWriter("Book.xml")
mySerializer.Serialize(writer, b);
End Class
public void SerializeOverride()
{
// Creates an XmlElementAttribute with the alternate name.
XmlElementAttribute myElementAttribute = new XmlElementAttribute();
myElementAttribute.ElementName = "BookID";
XmlAttributes myAttributes = new XmlAttributes();
myAttributes.XmlElements.Add(myElementAttribute);
XmlAttributeOverrides myOverrides = new XmlAttributeOverrides();
myOverrides.Add(typeof(Book), "ISBN", myAttributes);
XmlSerializer mySerializer =
new XmlSerializer(typeof(Book), myOverrides)
Book b = new Book();
b.ISBN = "123456789"
// Creates a StreamWriter to write the XML stream to.
StreamWriter writer = new StreamWriter("Book.xml");
mySerializer.Serialize(writer, b);
}

Le flux de données XML peut se présenter comme suit.

<Book>
<BookID>123456789</BookID>
</Book>

Voir aussi
XmlElementAttribute
XmlAttributes
XmlAttributeOverrides
Sérialisation XML et SOAP
XmlSerializer
Guide pratique pour sérialiser un objet
Guide pratique pour désérialiser un objet
Qualifier des noms d’attributs et d’éléments XML
18/07/2020 • 4 minutes to read • Edit Online

Les espaces de noms XML contenus par les instances de la XmlSerializerNamespaces classe doivent être conformes
à la spécification World Wide Web Consortium (W3C) appelée Namespaces in XML.
Les espaces de noms XML offrent une méthode permettant de qualifier les noms d'éléments et d'attributs XML
dans des documents XML. Un nom qualifié se compose d'un préfixe et d'un nom local, séparés par le caractère
deux-points. Le préfixe joue uniquement le rôle d'espace réservé ; il est mappé à un URI (Universal Resource
Identifier) qui spécifie un espace de noms. L'association entre l'espace de noms URI géré de manière universelle et
le nom local génère un nom dont l'unicité universelle est garantie.
En créant une instance de XmlSerializerNamespaces et en ajoutant les paires d'espaces de noms à l'objet, vous
pouvez spécifier les préfixes utilisés dans un document XML.

Pour créer des noms qualifiés dans un document XML


1. Créez une instance de la classe XmlSerializerNamespaces .
2. Ajoutez tous les préfixes et paires d'espaces de noms à XmlSerializerNamespaces .
3. Appliquez l'attribut System.Xml.Serialization approprié à chaque membre ou classe que XmlSerializer doit
sérialiser dans un document XML.
Les attributs disponibles sont : XmlAnyElementAttribute, XmlArrayAttribute, XmlArrayItemAttribute,
XmlAttributeAttribute, XmlElementAttribute, XmlRootAttribute et XmlTypeAttribute.
4. Donnez à la propriété Namespace de chaque attribut l'une des valeurs d'espace de noms de
XmlSerializerNamespaces .
5. Passez XmlSerializerNamespaces à la méthode Serialize de XmlSerializer .

Exemple
L'exemple suivant crée un XmlSerializerNamespaces et ajoute deux paires préfixe/espace de noms à l'objet. Le code
crée un XmlSerializer utilisé pour sérialiser une instance de la classe Books . Le code appelle la méthode
Serialize avec XmlSerializerNamespaces , ce qui permet au code XML de contenir des espaces de noms préfixés.
Imports System.IO
Imports System.Xml
Imports System.Xml.Serialization

Public Module Program

Public Sub Main()


SerializeObject("XmlNamespaces.xml")
End Sub

Public Sub SerializeObject(filename As String)


Dim mySerializer As New XmlSerializer(GetType(Books))
' Writing a file requires a TextWriter.
Dim myWriter As New StreamWriter(filename)

' Creates an XmlSerializerNamespaces and adds two


' prefix-namespace pairs.
Dim myNamespaces As New XmlSerializerNamespaces()
myNamespaces.Add("books", "http://www.cpandl.com")
myNamespaces.Add("money", "http://www.cohowinery.com")

' Creates a Book.


Dim myBook As New Book()
myBook.TITLE = "A Book Title"
Dim myPrice As New Price()
myPrice.price = CDec(9.95)
myPrice.currency = "US Dollar"
myBook.PRICE = myPrice
Dim myBooks As New Books()
myBooks.Book = myBook
mySerializer.Serialize(myWriter, myBooks, myNamespaces)
myWriter.Close()
End Sub
End Module

Public Class Books


<XmlElement([Namespace] := "http://www.cohowinery.com")> _
Public Book As Book
End Class

<XmlType([Namespace] := "http://www.cpandl.com")> _
Public Class Book
<XmlElement([Namespace] := "http://www.cpandl.com")> _
Public TITLE As String
<XmlElement([Namespace] := "http://www.cohowinery.com")> _
Public PRICE As Price
End Class

Public Class Price


<XmlAttribute([Namespace] := "http://www.cpandl.com")> _
Public currency As String
<XmlElement([Namespace] := "http://www.cohowinery.com")> _
Public price As Decimal
End Class
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

public class Program


{
public static void Main()
{
SerializeObject("XmlNamespaces.xml");
}

public static void SerializeObject(string filename)


{
var mySerializer = new XmlSerializer(typeof(Books));
// Writing a file requires a TextWriter.
TextWriter myWriter = new StreamWriter(filename);

// Creates an XmlSerializerNamespaces and adds two


// prefix-namespace pairs.
var myNamespaces = new XmlSerializerNamespaces();
myNamespaces.Add("books", "http://www.cpandl.com");
myNamespaces.Add("money", "http://www.cohowinery.com");

// Creates a Book.
var myBook = new Book();
myBook.TITLE = "A Book Title";
var myPrice = new Price();
myPrice.price = (decimal) 9.95;
myPrice.currency = "US Dollar";
myBook.PRICE = myPrice;
var myBooks = new Books();
myBooks.Book = myBook;
mySerializer.Serialize(myWriter, myBooks, myNamespaces);
myWriter.Close();
}
}

public class Books


{
[XmlElement(Namespace = "http://www.cohowinery.com")]
public Book Book;
}

[XmlType(Namespace ="http://www.cpandl.com")]
public class Book
{
[XmlElement(Namespace = "http://www.cpandl.com")]
public string TITLE;
[XmlElement(Namespace ="http://www.cohowinery.com")]
public Price PRICE;
}

public class Price


{
[XmlAttribute(Namespace = "http://www.cpandl.com")]
public string currency;
[XmlElement(Namespace = "http://www.cohowinery.com")]
public decimal price;
}

Voir aussi
XmlSerializer
Outil XML Schema Definition et sérialisation XML
Introduction à la sérialisation XML
XmlSerializer, classe
Attributs qui contrôlent la sérialisation XML
Guide pratique pour spécifier un nom d’élément différent pour un flux XML
Guide pratique pour sérialiser un objet
Guide pratique pour désérialiser un objet
Comment : sérialiser un objet en tant que flux XML
encodé selon le protocole SOAP
18/07/2020 • 2 minutes to read • Edit Online

Un message SOAP étant basé sur du code XML, la classe XmlSerializer peut être utilisée pour sérialiser des classes
et générer des messages encodés selon le protocole SOAP. Le résultat XML est conforme à la section 5 du
document du World Wide Web Consortium (www.w3.org), « Simple Object Access Protocol (SOAP) 1.1 ». Lorsque
vous créez un service Web XML qui communique à l'aide de messages SOAP, vous pouvez personnaliser le flux
de données XML en appliquant un ensemble d'attributs SOAP spéciaux aux classes et membres de classes. Pour
obtenir une liste des attributs, consultez Attributs qui contrôlent la sérialisation encodée selon le protocole SOAP.
Pour sérialiser un objet en tant que flux de données XML encodé selon le protocole SOAP
1. Créez la classe à l’aide de l’outil XML Schema Definition (Xsd.exe).
2. Appliquez un ou plusieurs des attributs spéciaux se trouvant dans System.Xml.Serialization . Consultez la
liste indiquée dans « Attributs qui contrôlent la sérialisation encodée selon le protocole SOAP ».
3. Créez un XmlTypeMapping en créant un nouveau SoapReflectionImporter et en appelant la méthode
ImportTypeMapping avec le type de la classe sérialisée.
L’exemple de code suivant appelle la méthode ImportTypeMapping de la classe SoapReflectionImporter
pour créer un XmlTypeMapping .

' Serializes a class named Group as a SOAP message.


Dim myTypeMapping As XmlTypeMapping =
New SoapReflectionImporter().ImportTypeMapping(GetType(Group))

// Serializes a class named Group as a SOAP message.


XmlTypeMapping myTypeMapping =
new SoapReflectionImporter().ImportTypeMapping(typeof(Group));

4. Créez une instance de la classe XmlSerializer en passant XmlTypeMapping au constructeur


XmlSerializer(XmlTypeMapping).

Dim mySerializer As XmlSerializer = New XmlSerializer(myTypeMapping)

XmlSerializer mySerializer = new XmlSerializer(myTypeMapping);

5. Appelez la méthode Serialize ou Deserialize .

Exemple
' Serializes a class named Group as a SOAP message.
Dim myTypeMapping As XmlTypeMapping =
New SoapReflectionImporter().ImportTypeMapping(GetType(Group))
Dim mySerializer As XmlSerializer = New XmlSerializer(myTypeMapping)
// Serializes a class named Group as a SOAP message.
XmlTypeMapping myTypeMapping =
new SoapReflectionImporter().ImportTypeMapping(typeof(Group));
XmlSerializer mySerializer = new XmlSerializer(myTypeMapping);

Voir aussi
Sérialisation XML et SOAP
Attributs qui contrôlent la sérialisation encodée selon le protocole SOAP
Sérialisation XML avec les services Web XML
Guide pratique pour sérialiser un objet
Guide pratique pour désérialiser un objet
Guide pratique pour remplacer la sérialisation XML encodée selon le protocole SOAP
Comment : substituer la sérialisation XML encodée
selon le protocole SOAP
18/07/2020 • 5 minutes to read • Edit Online

Le processus permettant de substituer la sérialisation XML d'objets en tant que messages SOAP est identique au
processus permettant de substituer la sérialisation XML standard. Pour plus d’informations sur la substitution de
la sérialisation XML standard, consultez Guide pratique pour spécifier un nom d’élément différent pour un
flux XML.

Pour substituer la sérialisation d'objets en tant que messages SOAP


1. Créez une instance de la classe SoapAttributeOverrides.
2. Créez un SoapAttributes pour chaque membre de classe sérialisé.
3. Créez une instance d'un ou plusieurs des attributs qui affectent la sérialisation XML, si nécessaire, pour le
membre sérialisé. Pour plus d'informations, consultez « Attributs qui contrôlent la sérialisation codée selon
le protocole SOAP ».
4. Définissez la propriété appropriée de SoapAttributes avec l’attribut créé à l’étape 3.
5. Ajout de SoapAttributes à SoapAttributeOverrides .
6. Créez un XmlTypeMapping à l'aide de SoapAttributeOverrides . Utilisez la méthode
SoapReflectionImporter.ImportTypeMapping .
7. Créez un XmlSerializer à l'aide de XmlTypeMapping .
8. Sérialisez ou désérialisez l'objet.

Exemple
L'exemple de code suivant sérialise un fichier de deux manières différentes : premièrement, sans substituer le
comportement de la classe XmlSerializer et deuxièmement, en le substituant. L'exemple contient une classe
nommée Group disposant de plusieurs membres. Différents attributs, tels que SoapElementAttribute , ont été
appliqués aux membres de classe. Lorsque la classe est sérialisée avec la méthode SerializeOriginal , les attributs
contrôlent le contenu des messages SOAP. Lorsque la méthode SerializeOverride est appelée, le comportement
de XmlSerializer est substitué en créant différents attributs et en affectant ces attributs aux propriétés d'un
SoapAttributes (si nécessaire).

using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Schema;

public class Group


{
[SoapAttribute (Namespace = "http://www.cpandl.com")]
public string GroupName;

[SoapAttribute(DataType = "base64Binary")]
public Byte [] GroupNumber;
[SoapAttribute(DataType = "date", AttributeName = "CreationDate")]
public DateTime Today;
[SoapElement(DataType = "nonNegativeInteger", ElementName = "PosInt")]
public string PositiveInt;
// This is ignored when serialized unless it is overridden.
[SoapIgnore]
public bool IgnoreThis;

public GroupType Grouptype;

[SoapInclude(typeof(Car))]
public Vehicle myCar(string licNumber)
{
Vehicle v;
if(licNumber == "")
{
v = new Car();
v.licenseNumber = "!!!!!!";
}
else
{
v = new Car();
v.licenseNumber = licNumber;
}
return v;
}
}

public abstract class Vehicle


{
public string licenseNumber;
public DateTime makeDate;
}

public class Car: Vehicle


{
}

public enum GroupType


{
// These enums can be overridden.
small,
large
}

public class Run


{
public static void Main()
{
Run test = new Run();
test.SerializeOriginal("SoapOriginal.xml");
test.SerializeOverride("SoapOverrides.xml");
test.DeserializeOriginal("SoapOriginal.xml");
test.DeserializeOverride("SoapOverrides.xml");

}
public void SerializeOriginal(string filename)
{
// Creates an instance of the XmlSerializer class.
XmlTypeMapping myMapping =
(new SoapReflectionImporter().ImportTypeMapping(
typeof(Group)));
XmlSerializer mySerializer =
new XmlSerializer(myMapping);

// Writing the file requires a TextWriter.


TextWriter writer = new StreamWriter(filename);

// Creates an instance of the class that will be serialized.


// Creates an instance of the class that will be serialized.
Group myGroup = new Group();

// Sets the object properties.


myGroup.GroupName = ".NET";

Byte [] hexByte = new Byte[2]{Convert.ToByte(100),


Convert.ToByte(50)};
myGroup.GroupNumber = hexByte;

DateTime myDate = new DateTime(2002,5,2);


myGroup.Today = myDate;

myGroup.PositiveInt= "10000";
myGroup.IgnoreThis=true;
myGroup.Grouptype= GroupType.small;
Car thisCar =(Car) myGroup.myCar("1234566");

// Prints the license number just to prove the car was created.
Console.WriteLine("License#: " + thisCar.licenseNumber + "\n");

// Serializes the class and closes the TextWriter.


mySerializer.Serialize(writer, myGroup);
writer.Close();
}

public void SerializeOverride(string filename)


{
// Creates an instance of the XmlSerializer class
// that overrides the serialization.
XmlSerializer overRideSerializer = CreateOverrideSerializer();

// Writing the file requires a TextWriter.


TextWriter writer = new StreamWriter(filename);

// Creates an instance of the class that will be serialized.


Group myGroup = new Group();

// Sets the object properties.


myGroup.GroupName = ".NET";

Byte [] hexByte = new Byte[2]{Convert.ToByte(100),


Convert.ToByte(50)};
myGroup.GroupNumber = hexByte;

DateTime myDate = new DateTime(2002,5,2);


myGroup.Today = myDate;

myGroup.PositiveInt= "10000";
myGroup.IgnoreThis=true;
myGroup.Grouptype= GroupType.small;
Car thisCar =(Car) myGroup.myCar("1234566");

// Serializes the class and closes the TextWriter.


overRideSerializer.Serialize(writer, myGroup);
writer.Close();
}

public void DeserializeOriginal(string filename)


{
// Creates an instance of the XmlSerializer class.
XmlTypeMapping myMapping =
(new SoapReflectionImporter().ImportTypeMapping(
typeof(Group)));
XmlSerializer mySerializer =
new XmlSerializer(myMapping);

TextReader reader = new StreamReader(filename);

// Deserializes and casts the object.


// Deserializes and casts the object.
Group myGroup;
myGroup = (Group) mySerializer.Deserialize(reader);

Console.WriteLine(myGroup.GroupName);
Console.WriteLine(myGroup.GroupNumber[0]);
Console.WriteLine(myGroup.GroupNumber[1]);
Console.WriteLine(myGroup.Today);
Console.WriteLine(myGroup.PositiveInt);
Console.WriteLine(myGroup.IgnoreThis);
Console.WriteLine();
}

public void DeserializeOverride(string filename)


{
// Creates an instance of the XmlSerializer class.
XmlSerializer overRideSerializer = CreateOverrideSerializer();
// Reading the file requires a TextReader.
TextReader reader = new StreamReader(filename);

// Deserializes and casts the object.


Group myGroup;
myGroup = (Group) overRideSerializer.Deserialize(reader);

Console.WriteLine(myGroup.GroupName);
Console.WriteLine(myGroup.GroupNumber[0]);
Console.WriteLine(myGroup.GroupNumber[1]);
Console.WriteLine(myGroup.Today);
Console.WriteLine(myGroup.PositiveInt);
Console.WriteLine(myGroup.IgnoreThis);
}

private XmlSerializer CreateOverrideSerializer()


{
SoapAttributeOverrides mySoapAttributeOverrides =
new SoapAttributeOverrides();
SoapAttributes soapAtts = new SoapAttributes();

SoapElementAttribute mySoapElement = new SoapElementAttribute();


mySoapElement.ElementName = "xxxx";
soapAtts.SoapElement = mySoapElement;
mySoapAttributeOverrides.Add(typeof(Group), "PositiveInt",
soapAtts);

// Overrides the IgnoreThis property.


SoapIgnoreAttribute myIgnore = new SoapIgnoreAttribute();
soapAtts = new SoapAttributes();
soapAtts.SoapIgnore = false;
mySoapAttributeOverrides.Add(typeof(Group), "IgnoreThis",
soapAtts);

// Overrides the GroupType enumeration.


soapAtts = new SoapAttributes();
SoapEnumAttribute xSoapEnum = new SoapEnumAttribute();
xSoapEnum.Name = "Over1000";
soapAtts.SoapEnum = xSoapEnum;

// Adds the SoapAttributes to the


// mySoapAttributeOverrides.
mySoapAttributeOverrides.Add(typeof(GroupType), "large",
soapAtts);

// Creates a second enumeration and adds it.


soapAtts = new SoapAttributes();
xSoapEnum = new SoapEnumAttribute();
xSoapEnum.Name = "ZeroTo1000";
soapAtts.SoapEnum = xSoapEnum;
mySoapAttributeOverrides.Add(typeof(GroupType), "small",
soapAtts);
// Overrides the Group type.
soapAtts = new SoapAttributes();
SoapTypeAttribute soapType = new SoapTypeAttribute();
soapType.TypeName = "Team";
soapAtts.SoapType = soapType;
mySoapAttributeOverrides.Add(typeof(Group),soapAtts);

// Creates an XmlTypeMapping that is used to create an instance


// of the XmlSerializer class. Then returns the XmlSerializer.
XmlTypeMapping myMapping = (new SoapReflectionImporter(
mySoapAttributeOverrides)).ImportTypeMapping(typeof(Group));

XmlSerializer ser = new XmlSerializer(myMapping);


return ser;
}
}

Voir aussi
Sérialisation XML et SOAP
Attributs qui contrôlent la sérialisation encodée selon le protocole SOAP
Sérialisation XML avec les services Web XML
Guide pratique pour sérialiser un objet
Guide pratique pour désérialiser un objet
Comment : sérialiser un objet en tant que flux XML encodé selon le protocole SOAP
Élément <system.xml.serialization>
18/07/2020 • 2 minutes to read • Edit Online

Élément de niveau supérieur permettant de contrôler la sérialisation XML. Pour plus d’informations sur les fichiers
de configuration, consultez Schéma des fichiers de configuration.
<configuration>
<system.xml.serialization>

Syntaxe
<system.xml.serialization>
</system.xml.serialization>

Attributs et éléments
Les sections suivantes décrivent des attributs, des éléments enfants et des éléments parents.
Attributs
Aucun.
Éléments enfants
ÉL ÉM EN T DESC RIP T IO N

<dateTimeSerialization>Appartient Détermine le mode de sérialisation des objets DateTime.

<schemaImporterExtensions>Appartient Contient des types utilisés par le XmlSchemaImporter pour


mapper des types XSD en types .NET Framework.

Éléments parents
ÉL ÉM EN T DESC RIP T IO N

<configuration>Appartient Élément racine dans chaque fichier de configuration utilisé par


le Common Language Runtime et les applications .NET
Framework.

Exemple
L'exemple de code suivant illustre comment spécifier le mode de sérialisation d'un objet DateTime et l'ajout de
types utilisés par le XmlSchemaImporter lors du mappage de types XSD en types .NET Framework.
<system.xml.serialization>
<xmlSerializer checkDeserializeAdvances="false" />
<dateTimeSerialization mode = "Local" />
<schemaImporterExtensions>
<add
name = "MobileCapabilities"
type = "System.Web.Mobile.MobileCapabilities,
System.Web.Mobile, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f6f11d40a3a" />
</schemaImporterExtensions>
</system.xml.serialization>

Voir aussi
XmlSchemaImporter
DateTimeSerializationSection.DateTimeSerializationMode
Schéma des fichiers de configuration
<dateTimeSerialization>Appartient
<schemaImporterExtensions>Appartient
<add>, Élément de<schemaImporterExtensions>
Élément <dateTimeSerialization>
18/07/2020 • 2 minutes to read • Edit Online

Détermine le mode de sérialisation des objets DateTime.


<configuration>
<dateTimeSerialization>

Syntaxe
<dateTimeSerialization
mode = "Roundtrip|Local"
/>

Attributs et éléments
Les sections suivantes décrivent des attributs, des éléments enfants et des éléments parents.
Attributs
AT T RIB UT S DESC RIP T IO N

mode facultatif. Spécifie le mode de sérialisation. Affectez-le à l'une


des valeurs
DateTimeSerializationSection.DateTimeSerializationMode. La
valeur par défaut est RoundTrip .

Éléments enfants
Aucun.
Éléments parents
ÉL ÉM EN T DESC RIP T IO N

system.xml.serialization Élément de niveau supérieur permettant de contrôler la


sérialisation XML.

Remarques
Dans les versions 1,0, 1,1, 2,0 et versions ultérieures de la .NET Framework, quand cette propriété est définie sur
local , DateTime les objets sont toujours mis en forme en tant qu’heure locale. Autrement dit, les informations du
fuseau horaire local sont toujours incluses avec les données sérialisées. Affectez la valeur Local à cette propriété
pour garantir la compatibilité avec les versions antérieures du .NET Framework.
Dans la version 2,0 et les versions ultérieures du .NET Framework dont cette propriété a la valeur roundtrip , les
DateTime objets sont examinés pour déterminer s’ils se trouvent dans le fuseau horaire local, UTC ou non spécifié.
Les objets DateTime sont ensuite sérialisés de manière à ce que ces informations soient conservées. Il s'agit du
comportement par défaut, recommandé pour toutes les nouvelles applications qui ne communiquent pas avec les
versions antérieures du .NET Framework.
Voir aussi
DateTime
XmlSchemaImporter
DateTimeSerializationSection.DateTimeSerializationMode
Schéma des fichiers de configuration
<schemaImporterExtensions>Appartient
<add>, Élément de<schemaImporterExtensions>
<system.xml.serialization>Appartient
Élément <schemaImporterExtensions>
18/07/2020 • 2 minutes to read • Edit Online

Contient des types utilisés par le XmlSchemaImporter pour mapper des types XSD en types .NET Framework. Pour
plus d’informations sur les fichiers de configuration, consultez Schéma des fichiers de configuration.

Syntaxe
<schemaImporterExtensions>
<!-- Add types -->
</schemaImporterExtensions>

Éléments enfants
ÉL ÉM EN T DESC RIP T IO N

<add>, Élément de<schemaImporterExtensions> Ajoute des types utilisés par XmlSchemaImporter pour créer
des mappages.

Éléments parents
ÉL ÉM EN T DESC RIP T IO N

<system.xml.serialization>Appartient Élément de niveau supérieur permettant de contrôler la


sérialisation XML.

Exemple
L'exemple de code suivant illustre comment ajouter des types utilisés par XmlSchemaImporter lors du mappage
de types XSD en types .NET Framework.

<system.xml.serialization>
<schemaImporterExtensions>
<add name = "MobileCapabilities" type =
"System.Web.Mobile.MobileCapabilities,
System.Web.Mobile, Version - 2.0.0.0, Culture = neutral,
PublicKeyToken = b03f5f6f11d40a3a" />
</schemaImporterExtensions>
</system.xml.serialization>

Voir aussi
XmlSchemaImporter
DateTimeSerializationSection.DateTimeSerializationMode
Schéma des fichiers de configuration
<dateTimeSerialization>Appartient
<add>, Élément de<schemaImporterExtensions>
<system.xml.serialization>Appartient
<add>, élément de <schemaImporterExtensions>
18/07/2020 • 2 minutes to read • Edit Online

Ajoute des types utilisés par XmlSchemaImporter pour mapper des types XSD en types .NET Framework. Pour
plus d’informations sur les fichiers de configuration, consultez Schéma des fichiers de configuration.
<configuration>
<system.xml.serialization>
<schemaImporterExtensions>
<add>

Syntaxe
<add name = "typeName" type="fully qualified type [,Version=version number] [,Culture=culture]
[,PublicKeyToken= token]"/>

Attributs et éléments
Les sections suivantes décrivent des attributs, des éléments enfants et des éléments parents.
Attributs
AT T RIB UT DESC RIP T IO N

name Nom simple utilisé pour rechercher l'instance.

type Obligatoire. Spécifie la classe d'extension de schéma à ajouter.


La valeur d’attribut type doit figurer sur une ligne et inclure le
nom complet du type. Lorsque l'assembly est placé dans le
Global Assembly Cache (GAC), il doit également inclure la
version, la culture et le jeton de clé publique de l'assembly
signé.

Éléments enfants
Aucun.
Éléments parents
ÉL ÉM EN T DESC RIP T IO N

<schemaImporterExtensions> Contient les types utilisés par XmlSchemaImporter.

Exemple
L'exemple de code suivant ajoute un type d'extension que XmlSchemaImporter peut utiliser lors du mappage de
types.
<configuration>
<system.xml.serialization>
<schemaImporterExtensions>
<add name="contoso" type="System.Web.Mobile.MobileCapabilities,
System.Web.Mobile, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a" />
</schemaImporterExtensions>
</system.xml.serialization>
</configuration>

Voir aussi
XmlSchemaImporter
<system.xml.serialization>Appartient
<schemaImporterExtensions>Appartient
Élément <xmlSerializer>
18/07/2020 • 2 minutes to read • Edit Online

Spécifie si un contrôle supplémentaire de la progression de XmlSerializer est effectué.


<configuration>
<system.xml.serialization>

Syntaxe
<xmlSerializer checkDeserializerAdvance = "true|false" />

Attributs et éléments
Les sections suivantes décrivent des attributs, des éléments enfants et des éléments parents.
Attributs
AT T RIB UT DESC RIP T IO N

checkDeserializeAdvances Spécifie si la progression de XmlSerializer est vérifiée. Affectez


"true" ou "false" à l'attribut. La valeur par défaut est "true".

useLegacySerializationGeneration Spécifie si XmlSerializer utilise la génération de sérialisation


héritée qui génère des assemblys en écrivant le code C# dans
un fichier, puis en le compilant dans un assembly. La valeur par
défaut est false .

Éléments enfants
Aucun.
Éléments parents
ÉL ÉM EN T DESC RIP T IO N

<system.xml.serialization>Appartient Contient des paramètres de configuration pour les classes


XmlSerializer et XmlSchemaImporter.

Remarques
Par défaut, le XmlSerializer fournit une couche supplémentaire de sécurité contre des attaques par déni de service
potentielles lors de la désérialisation de données non fiables. Pour ce faire, il tente de détecter des boucles infinies
pendant la désérialisation. Si une telle condition est détectée, une exception est levée avec le message suivant :
« Erreur interne : la désérialisation n’a pas pu avancer sur le flux de données sous-jacent. ».
Le fait de recevoir ce message n'indique pas nécessairement qu'une attaque par déni de service est en cours. Dans
de rares circonstances, le mécanisme de détection de boucles infinies génère un faux positif et l'exception est levée
pour un message entrant légitime. Si vous voyez, dans votre application, que des messages légitimes sont rejetés
par cette couche de protection supplémentaire, définissez l’attribut checkDeserializeAdvances sur la valeur false.
Exemple
L’exemple de code suivant assigne la valeur false à l’attribut checkDeserializeAdvances .

<configuration>
<system.xml.serialization>
<xmlSerializer checkDeserializeAdvances="false" />
</system.xml.serialization>
</configuration>

Voir aussi
XmlSerializer
<system.xml.serialization>Appartient
Sérialisation XML et SOAP
Outil XML Serializer Generator (Sgen.exe)
18/07/2020 • 5 minutes to read • Edit Online

Le générateur de sérialiseur XML crée un assembly de sérialisation XML pour les types dans un assembly spécifié.
L’assembly de sérialisation améliore les performances de démarrage d’un XmlSerializer lorsqu’il sérialise ou
désérialise des objets des types spécifiés.

Syntaxe
Exécutez l’outil à partir de la ligne de commande.

sgen [options]

TIP
Pour que .NET Framework outils fonctionnent correctement, vous devez définir Path correctement les Include variables
d' Lib environnement, et. Définissez ces variables d’environnement en exécutant SDKVars. bat, qui se trouve dans le
<SDK> répertoire \v2.0\Bin. SDKVars.bat doit être exécuté dans chaque interpréteur de commandes.

Paramètres
O P T IO N DESC RIP T IO N

/a [ ssembly ] : nom de fichier Génère le code de sérialisation pour tous les types contenus
dans l’assembly ou le fichier exécutable spécifié par
nom_fichier. Un seul nom de fichier peut être fourni. Si cet
argument est répété, le dernier nom de fichier est utilisé.

/c [ ompiler ] : options Spécifie les options à passer au compilateur C#. Toutes les
options csc.exe sont prises en charge à mesure qu'elles sont
passées au compilateur. Cette option peut être utilisée pour
spécifier que l'assembly doit être signé et pour indiquer le
fichier de clé.

/d [ ebug] Génère une image qui peut être utilisée avec un débogueur.

/f [ Orce] Force l'écrasement par réécriture d'un assembly existant du


même nom. La valeur par défaut est false .

/Help ou/ ? Affiche la syntaxe et les options de commande de l'outil.

/k [ onser ver] Efface la suppression des fichiers source générés et d'autres


fichiers temporaires une fois qu'ils ont été compilés dans
l'assembly de sérialisation. Cette option peut être utilisée afin
de déterminer si l'outil génère le code de sérialisation pour un
type particulier.

/n [ ologo] Supprime l'affichage de la bannière de démarrage Microsoft.


O P T IO N DESC RIP T IO N

/o [ ut ] : chemin Spécifie le répertoire dans lequel enregistrer l'assembly


généré. Remarque : Le nom de l’assembly généré est
composé du nom de l’assembly d’entrée suivi de
« xmlSerializers.dll ».

/p [ roxytypes] Génère un code de sérialisation uniquement pour les types de


proxy de service Web XML.

/r [ eference ] : AssemblyFiles Spécifie les assemblys référencés par les types qui requièrent
la sérialisation XML. Accepte plusieurs fichiers d'assembly
séparés par des virgules.

/s [ ilent] Supprime l'affichage des messages indiquant la réussite des


opérations.

/t [ ype ] : type Génère un code de sérialisation uniquement pour le type


spécifié.

/v [ erbose] Affiche la sortie en clair pour le débogage. Répertorie les types


à partir de l'assembly cible qui ne peuvent pas être sérialisés
avec le XmlSerializer.

/? Affiche la syntaxe et les options de commande de l'outil.

Remarques
Lorsque l'outil XML Serializer Generator n'est pas utilisé, un XmlSerializer génère un code de sérialisation et un
assembly de sérialisation pour chacun des types chaque fois qu'une application est exécutée. Pour améliorer les
performances de démarrage de la sérialisation XML, utilisez l’outil SGen. exe pour générer ces assemblys à
l’avance. Ces assemblys peuvent ensuite être déployés avec l'application.
L'outil XML Serializer Generator peut également améliorer les performances des clients qui utilisent des proxies de
service Web XML pour communiquer avec les serveurs car le processus de sérialisation n'entraîne pas de
dégradation des performances lors du premier chargement du type.
Ces assemblys générés ne peuvent pas être utilisés du côté serveur d'un service Web. Cet outil est conçu
uniquement pour les clients de service Web et les scénarios de sérialisation manuelle.
Si l'assembly qui contient le type à sérialiser est nommé MyType.dll, l'assembly de sérialisation associé sera alors
nommé MyType.XmlSerializers.dll.

Exemples
La commande suivante crée un assembly nommé Data.XmlSerializers.dll pour sérialiser tous les types contenus
dans l'assembly nommé Data.dll.

sgen Data.dll

L'assembly Data.XmlSerializers.dll peut être référencé à partir du code qui doit sérialiser et désérialiser les types
dans Data.dll.

Voir aussi
outils
Invites de commandes
Outil XML Schema Definition (Xsd.exe)
18/07/2020 • 18 minutes to read • Edit Online

L'outil XML Schema Definition Tool (Xsd.exe) génère des classes du Common Language Runtime et du schéma
XML à partir de fichiers XDR, XML et XSD ou de classes figurant dans un assembly de runtime.
L’outil XML Schema Definition (XSD. exe) se trouve généralement à l’emplacement suivant :
C : \ Program Files (x86) \ Microsoft SDK \ Windows \ {version} \ bin \ netfx {version} Tools\

Syntaxe
Exécutez l’outil à partir de la ligne de commande.

xsd file.xdr [-outputdir:directory][/parameters:file.xml]


xsd file.xml [-outputdir:directory] [/parameters:file.xml]
xsd file.xsd {/classes | /dataset} [/element:element]
[/enableLinqDataSet] [/language:language]
[/namespace:namespace] [-outputdir:directory] [URI:uri]
[/parameters:file.xml]
xsd {file.dll | file.exe} [-outputdir:directory] [/type:typename [...]][/parameters:file.xml]

TIP
Pour que .NET Framework outils fonctionnent correctement, vous devez définir Path correctement les Include
variables d' Lib environnement, et. Définissez ces variables d’environnement en exécutant SDKVars. bat, qui se trouve
dans le <SDK> répertoire \v2.0\Bin. SDKVars.bat doit être exécuté dans chaque interpréteur de commandes.

Argument
A RGUM EN T DESC RIP T IO N
A RGUM EN T DESC RIP T IO N

fichier. extension Spécifie le fichier d'entrée à convertir. Vous devez spécifier


l’extension de l’une des manières suivantes :. XDR,. xml,. xsd,.
dll ou. exe.

Si vous spécifiez un fichier de schéma XDR (extension .xdr),


Xsd.exe convertit alors le schéma XDR en schéma XSD. Le
fichier de sortie porte le même nom que celui du schéma
XDR, mais son extension est .xsd.

Si vous spécifiez un fichier XML (extension .xml), Xsd.exe


déduit alors un schéma à partir des données d'un fichier et
génère un schéma XSD. Le fichier de sortie porte le même
nom que celui du fichier XML, mais son extension est .xsd.

Si vous spécifiez un fichier de schéma XML (extension .xsd),


Xsd.exe génère alors du code source pour les objets de
runtime correspondant au schéma XML.

Si vous spécifiez un fichier d'assembly de runtime


(extension .exe ou .dll), Xsd.exe génère alors des schémas
pour un ou plusieurs types de cet assembly. Vous pouvez
utiliser l'option /type pour spécifier les types pour lesquels
générer des schémas. Les schémas de sortie sont nommés
schema0.xsd, schema1.xsd, et ainsi de suite. Xsd.exe génère
plusieurs schémas uniquement dans le cas où les types
indiqués spécifieraient un espace de noms à l'aide de
l'attribut personnalisé XMLRoot .

Options générales
O P T IO N DESC RIP T IO N

/h [] Affiche la syntaxe et les options de commande de l'outil.

/o [ utputdir ] : répertoire Spécifie le répertoire des fichiers de sortie. Cet argument ne


peut être spécifié qu'à une seule reprise. L'emplacement par
défaut est le répertoire actif.

/? Affiche la syntaxe et les options de commande de l'outil.

/p [ arameters ] : file. xml Options de lecture pour différents modes d'opération à


partir du fichier .xml spécifié. La forme abrégée est /p: .
Pour plus d’informations, consultez la section Notes .

Options de fichier XSD


Vous ne devez spécifier qu'une seule des options suivantes pour les fichiers .xsd.

O P T IO N DESC RIP T IO N

/c [ lasses] Génère des classes correspondant au schéma spécifié. Pour


lire les données XML dans l’objet, utilisez la méthode
XmlSerializer.Deserialize.
O P T IO N DESC RIP T IO N

/d [ ataset] Génère une classe dérivée de DataSet qui correspond au


schéma spécifié. Pour lire les données XML dans la classe
dérivée, utilisez la méthode DataSet.ReadXml.

Vous pouvez également spécifier les options suivantes pour les fichiers .xsd.

O P T IO N DESC RIP T IO N

/e [ face ] : élément Spécifie l'élément figurant dans le schéma pour lequel


générer du code. Tous les éléments sont par défaut tapés.
Vous pouvez spécifier cet argument à plusieurs reprises.

/enableDataBinding Implémente l'interface INotifyPropertyChanged sur tous les


types générés pour activer la liaison de données. La forme
abrégée est /edb .

/enableLinqDataSet (Forme abrégée : /eld .) Spécifie que le DataSet généré


peut être interrogé sur l’utilisation de LINQ to DataSet. Cette
option est utilisée lorsque l'option /dataset est également
spécifiée. Pour plus d’informations, consultez Présentation de
LINQ to DataSet et Interrogation de datasets typés. Pour
obtenir des informations générales sur l’utilisation de LINQ,
consultez Language-Integrated Query (LINQ)-C# ou
Language-Integrated Query (linq)-Visual Basic.

/f [ ields] Génère des champs plutôt que des propriétés. Par défaut,
des propriétés sont générées.

/l [ anguage ] : langage Spécifie le langage de programmation à utiliser. Vous avez le


choix entre CS (C#, qui est la valeur par défaut), VB (Visual
Basic), JS (JScript) ou VJS (Visual J#). Vous pouvez
également spécifier un nom qualifié complet pour une classe
implémentant System.CodeDom.Compiler.CodeDomProvider.

/n [ amespace ] : espace de noms Spécifie l'espace de noms du runtime pour les types générés.
L'espace de noms par défaut est Schemas .

/nologo Supprime la bannière.

/Order Génère des identificateurs d'ordre explicites sur les membres


de particule.

/o [ ut ] : DirectoryName Spécifie le répertoire de sortie dans lequel placer les fichiers.


L'emplacement par défaut est le répertoire actif.

/u [ RI ] : URI Spécifie l'URI des éléments figurant dans le schéma pour


lequel générer du code. S'il existe, cet URI s'applique à tous
les éléments spécifiés avec l'option /element .

Options de fichier DLL et EXE


O P T IO N DESC RIP T IO N

/t [ ype ] : TypeName Spécifie le nom du type pour lequel créer un schéma. Vous
pouvez spécifier plusieurs arguments pour le type. Si
nom_type ne spécifie pas d’espace de noms, Xsd.exe établit
une correspondance entre tous les types de l’assembly et le
type spécifié. Si nom_type spécifie un espace de noms, une
correspondance est établie uniquement avec ce type. Si
nom_type se termine par un astérisque (*), l’outil établit une
correspondance avec tous les types commençant par la
chaîne qui précède *. Si vous omettez l'option /type ,
Xsd.exe génère alors des schémas pour tous les types de
l'assembly.

Remarques
Le tableau suivant affiche les opérations que Xsd.exe exécute.

De XDR en XSD Génère un schéma XML à partir d'un fichier de schéma XDR
(XML-Data-Reduced). XDR est l'un des premiers formats de
schéma XML.

De XML en XSD Génère un schéma XML à partir d'un fichier XML.

De XSD en DataSet Génère des classes DataSet de Common Language Runtime


à partir d'un fichier de schéma XSD. Les classes générées
proposent un modèle objet riche aux données XML
régulières.

De XSD en classes Génère des classes de runtime à partir d'un fichier de schéma
XSD. Les classes générées peuvent être associées à
System.Xml.Serialization.XmlSerializer pour lire et écrire du
code XML conforme au schéma.

De classes en XSD Génère un schéma XML à partir d'un ou de plusieurs types


dans un fichier d'assembly de runtime. Le schéma généré
définit le format XML utilisé par le XmlSerializer .

Xsd.exe vous permet uniquement de manipuler les schémas XML conformes au langage XSD (XML Schema
Definition) proposé par W3C (World Wide Web Consortium). Pour plus d’informations sur la proposition de
définition de schéma XML ou la norme XML, consultez https://w3.org .

Définition d'options avec un fichier XML


À l’aide du commutateur /parameters , vous pouvez spécifier un fichier XML qui définit plusieurs options. Les
options que vous pouvez définir dépendent de votre utilisation de l'outil XSD.exe. Les choix incluent la génération
de schémas, la génération de fichiers de code ou la génération de fichiers de code qui incluent des
fonctionnalités DataSet . Par exemple, vous pouvez définir l'élément <assembly> sur le nom d'un fichier
exécutable (.exe) ou d'un fichier bibliothèque de types (.dll) lors de la génération d'un schéma, mais pas lors de la
génération d'un fichier de code. Le code XML suivant indique comment utiliser l'élément <generateSchemas> avec
un fichier exécutable spécifié :
<!-- This is in a file named GenerateSchemas.xml. -->
<xsd xmlns='http://microsoft.com/dotnet/tools/xsd/'>
<generateSchemas>
<assembly>ConsoleApplication1.exe</assembly>
</generateSchemas>
</xsd>

Si le code XML précédent est contenu dans un fichier nommé GenerateSchemas. xml, utilisez le /parameters
commutateur en tapant ce qui suit à l’invite de commandes et en appuyant sur entrée :

xsd /p:GenerateSchemas.xml

En revanche, si vous générez un schéma pour un seul type trouvé dans l'assembly, vous pouvez utiliser le
code XML suivant :

<!-- This is in a file named GenerateSchemaFromType.xml. -->


<xsd xmlns='http://microsoft.com/dotnet/tools/xsd/'>
<generateSchemas>
<type>IDItems</type>
</generateSchemas>
</xsd>

Mais pour utiliser le code précédent, vous devez également fournir le nom de l'assembly à l'invite de
commandes. Entrez ce qui suit à l’invite de commandes (en supposant que le fichier XML se nomme
GenerateSchemaFromType. Xml) :

xsd /p:GenerateSchemaFromType.xml ConsoleApplication1.exe

Vous ne devez spécifier qu'une seule des options suivantes pour l'élément <generateSchemas> .

ÉL ÉM EN T DESC RIP T IO N

<assembly> Spécifie un assembly à partir duquel générer le schéma.

<type> Spécifie un type trouvé dans un assembly pour lequel


générer un schéma.

<xml> Spécifie un fichier XML pour lequel générer un schéma.

<xdr> Spécifie un fichier XDR pour lequel générer un schéma.

Pour générer un fichier de code, utilisez l'élément <generateClasses> . L'exemple suivant génère un fichier de
code. Notez que deux attributs sont également indiqués, ils vous permettent de définir le langage de
programmation et l'espace de noms du fichier généré.

<xsd xmlns='http://microsoft.com/dotnet/tools/xsd/'>
<generateClasses language='VB' namespace='Microsoft.Serialization.Examples'/>
</xsd>
<!-- You must supply an .xsd file when typing in the command line.-->
<!-- For example: xsd /p:genClasses mySchema.xsd -->

Les options que vous pouvez définir pour l'élément <generateClasses> incluent les éléments suivants.
ÉL ÉM EN T DESC RIP T IO N

<element> Spécifie un élément dans le fichier .xsd pour lequel générer


du code.

<schemaImporterExtensions> Spécifie un type dérivé de la classe


SchemaImporterExtension.

<schema> Spécifie un fichier de schéma XML pour lequel générer du


code. Plusieurs fichiers de schéma XML peuvent être spécifiés
à l’aide de plusieurs <schema> éléments.

Le tableau suivant affiche les attributs qui peuvent également être utilisés avec l'élément <generateClasses> .

AT T RIB UT DESC RIP T IO N

langage Spécifie le langage de programmation à utiliser. Vous avez le


choix entre CS (C#, la valeur par défaut), VB (Visual Basic),
JS (JScript) ou VJS (Visual J#). Vous pouvez également
spécifier un nom qualifié complet pour une classe qui
implémente CodeDomProvider.

espace de noms Spécifie l'espace de noms pour le code généré. L'espace de


noms doit se conformer aux normes CLR (par exemple,
aucun espace ou barre oblique inverse).

options L’une des valeurs suivantes : none , properties (génère


des propriétés au lieu de champs publics), order ou
enableDataBinding (consultez les commutateurs /order
et /enableDataBinding dans la section Options de
fichier XSD précédente).

Vous pouvez également contrôler comment le code DataSet est généré à l'aide de l'élément <generateDataSet> .
Le code XML suivant spécifie que le code généré utilise DataSet des structures (telles que la DataTable classe)
pour créer Visual Basic code pour un élément spécifié. Les structures DataSet générées prendront en charge les
requêtes LINQ.

<xsd xmlns='http://microsoft.com/dotnet/tools/xsd/'>
<generateDataSet language='VB' namespace='Microsoft.Serialization.Examples' enableLinqDataSet='true'>
</generateDataSet>
</xsd>

Les options que vous pouvez définir pour l'élément <generateDataSet> incluent les éléments suivants.

ÉL ÉM EN T DESC RIP T IO N

<schema> Spécifie un fichier de schéma XML pour lequel générer du


code. Plusieurs fichiers de schéma XML peuvent être spécifiés
à l’aide de plusieurs <schema> éléments.

Le tableau suivant affiche les attributs qui peuvent être utilisés avec l'élément <generateDataSet> .

AT T RIB UT DESC RIP T IO N


AT T RIB UT DESC RIP T IO N

enableLinqDataSet Spécifie que le DataSet généré peut être interrogé par


rapport à l'utilisation de LINQ to DataSet. La valeur par
défaut est false.

langage Spécifie le langage de programmation à utiliser. Vous avez le


choix entre CS (C#, la valeur par défaut), VB (Visual Basic),
JS (JScript) ou VJS (Visual J#). Vous pouvez également
spécifier un nom qualifié complet pour une classe qui
implémente CodeDomProvider.

espace de noms Spécifie l'espace de noms pour le code généré. L'espace de


noms doit se conformer aux normes CLR (par exemple,
aucun espace ou barre oblique inverse).

Vous pouvez définir certains attributs sur l'élément <xsd> de niveau supérieur. Ces options peuvent être
utilisées avec n'importe lequel des éléments enfants ( <generateSchemas> , <generateClasses> ou
<generateDataSet> ). Le code XML suivant génère le code pour un élément nommé "IDItems" dans le répertoire
de sortie nommé "MyOutputDirectory".

<xsd xmlns='http://microsoft.com/dotnet/tools/xsd/' output='MyOutputDirectory'>


<generateClasses>
<element>IDItems</element>
</generateClasses>
</xsd>

Le tableau suivant affiche les attributs qui peuvent également être utilisés avec l'élément <xsd> .

AT T RIB UT DESC RIP T IO N

sortie Le nom d'un répertoire dans lequel le schéma généré ou le


fichier de code sera placé.

nologo Supprime la bannière. A la valeur true ou false .

help Affiche la syntaxe et les options de commande de l'outil. A la


valeur true ou false .

Exemples
La commande suivante génère un schéma XML à partir de myFile.xdr et l'enregistre dans le répertoire actif.

xsd myFile.xdr

La commande suivante génère un schéma XML à partir de myFile.xml et l'enregistre dans le répertoire spécifié.

xsd myFile.xml /outputdir:myOutputDir

La commande suivante génère un groupe de données qui correspond au schéma spécifié dans le langage C# et
le sauvegarde comme XSDSchemaFile.cs dans le répertoire actif.
xsd /dataset /language:CS XSDSchemaFile.xsd

La commande suivante génère des schémas XML pour tous les types de l'assembly myAssembly.dll et les
enregistre sous schema0.xsd dans le répertoire actif.

xsd myAssembly.dll

Voir aussi
DataSet
System.Xml.Serialization.XmlSerializer
outils
Invites de commandes
Vue d'ensemble de LINQ to DataSet
Interrogation de DataSets typés
LINQ (Language-Integrated Query) (C#)
LINQ (Language-Integrated Query) (Visual Basic)
Développement pour plusieurs plateformes avec le
.NET Framework
12/02/2020 • 6 minutes to read • Edit Online

Vous pouvez développer des applications pour les plateformes Microsoft et non-Microsoft à l'aide du .NET
Framework et de Visual Studio.

Options pour le développement interplateforme


IMPORTANT
Étant donné que les projets de bibliothèque de classes Portable ciblent uniquement un sous-ensemble très spécifique des
implémentations de .NET, nous déconseillons fortement leur utilisation dans le développement de nouvelles applications. Le
remplacement recommandé est une bibliothèque .NET Standard, qui cible toutes les implémentations de .NET qui prennent en
charge une version spécifique de .NET Standard. Pour plus d'informations, consultez .NET Standard.

Pour développer pour plusieurs plateformes, vous pouvez partager le code source ou les binaires, et effectuer des
appels entre le code .NET Framework et les API Windows Runtime.

P O UR. . . UT IL ISEZ . . .

Partager le code source entre les applications Windows Projets par tagés (modèle applications universelles dans
Phone 8.1 et Windows 8.1 Visual Studio 2013, mise à jour 2).

-Actuellement aucune prise en charge de Visual Basic.


-Vous pouvez séparer le code spécifique à la plateforme à
l’aide d’instructions # if .

Pour plus d’informations, consultez :

- de commencer le codage
- à l’aide de Visual Studio pour générer des applications XAML
universelles (billet de blog)
- l’utilisation de Visual Studio pour générer des applications en
XAML convergées (vidéo)
P O UR. . . UT IL ISEZ . . .

Partager les binaires entre des applications qui ciblent Projets de bibliothèque de classes por tables pour le
différentes plateformes code qui est indépendant de la plateforme.

: Cette approche est généralement utilisée pour le code qui


implémente la logique métier.
-Vous pouvez utiliser Visual Basic ou C#.
-La prise en charge des API varie selon la plateforme.
-Les projets de bibliothèque de classes portables qui ciblent
Windows 8.1 et Windows Phone 8,1 prennent en charge les
API Windows Runtime et XAML. Ces fonctionnalités ne sont
pas disponibles pour les anciennes versions de la bibliothèque
de classes portables.
-Si nécessaire, vous pouvez extraire du code spécifique à la
plateforme en utilisant des interfaces ou des classes abstraites.

Pour plus d’informations, consultez :

bibliothèque de classes Portable -


- Comment rendre les bibliothèques de classes portables
opérationnelles pour vous (billet de blog)
- à l’aide d’une bibliothèque de classes portable avec MVVM
- des ressources d’application pour les bibliothèques qui
ciblent plusieurs plateformes
- de l' Analyseur de portabilité .net (extension Visual Studio)

Partager le code source entre des applications pour des Fonctionnalité Ajouter en tant que lien .
plateformes autres que Windows 8.1 et Windows Phone 8.1
: Cette approche convient pour la logique d’application qui est
commune aux deux applications, mais pas portable, pour une
raison quelconque. Vous pouvez utiliser cette fonctionnalité
pour du code C# ou Visual Basic.
Par exemple, Windows Phone 8 et Windows 8 partagent des
API Windows Runtime, mais les bibliothèques de classes
portables ne prennent pas en charge Windows Runtime pour
ces plateformes. Vous pouvez utiliser la fonctionnalité
Add as link pour partager du code Windows Runtime entre
une application Windows Phone 8 et une application Windows
Store qui cible Windows 8.

Pour plus d’informations, consultez :

- partager du code avec ajouter en tant que lien


- Comment : ajouter des éléments existants à un projet

Écrire des applications Windows Store à l'aide du .NET Windows Runtime des API à partir C# de votre code .NET
Framework ou appeler des API Windows Runtime à partir d'un Framework ou Visual Basic, et utilisez le .NET Framework pour
code .NET Framework créer des applications du Windows Store. Gardez à l'esprit que
les API diffèrent entre les deux plateformes. Toutefois,
certaines classes vous permettent de gérer ces différences.

Pour plus d’informations, consultez :

- .NET Framework prise en charge des applications du


Windows Store et Windows Runtime
- passant un URI au Windows Runtime
- WindowsRuntimeStreamExtensions
- WindowsRuntimeSystemExtensions
P O UR. . . UT IL ISEZ . . .

Créer des applications .NET Framework pour des plateformes La bibliothèque de classes por table référence les
non-Microsoft assemblys dans le .NET Framework, ainsi qu’une extension
Visual Studio ou un outil tiers tel que Xamarin.

Pour plus d’informations, consultez :

- bibliothèque de classes portables est désormais disponible


sur toutes les plateformes. (publication de blog)
documentation - Xamarin

utiliser JavaScript et HTML pour le développement Les modèles d’application universelle dans Visual Studio
interplateforme 2013, Update 2, sont développés par rapport aux API
Windows Runtime pour Windows 8.1 et Windows Phone 8,1.
Actuellement, vous ne pouvez pas utiliser JavaScript et HTML
avec des API .NET Framework pour développer des
applications interplateformes.

Pour plus d’informations, consultez :

- des modèles de projet JavaScript


- Portage d’une application Windows Runtime à l’aide de
JavaScript pour Windows Phone
Développement multiplateforme avec la
bibliothèque de classes portables
18/07/2020 • 9 minutes to read • Edit Online

Le type de projet de bibliothèque de classes portables dans Visual Studio vous permet de créer rapidement et
facilement des bibliothèques et des applications multiplateformes pour les plateformes Microsoft.

IMPORTANT
Étant donné que les projets de bibliothèque de classes Portable ciblent uniquement un sous-ensemble très spécifique des
implémentations de .NET, nous déconseillons fortement leur utilisation dans le développement de nouvelles applications. Le
remplacement recommandé est une bibliothèque .NET Standard, qui cible toutes les implémentations de .NET qui prennent
en charge une version spécifique de .NET Standard. Pour plus d'informations, consultez .NET Standard.

Les bibliothèques de classes portables vous aident à réduire le temps et les coûts de développement et de test du
code. Utilisez ce type de projet pour écrire et générer des assemblys de .NET Framework portable, puis référencez
ces assemblys à partir d’applications qui ciblent plusieurs plateformes, telles que le .NET Framework, iOS ou Mac.
Même après avoir créé un projet de bibliothèque de classes portables dans Visual Studio et commencé à le
développer, vous pouvez modifier les plateformes cibles. Visual Studio compile votre bibliothèque avec les
nouveaux assemblys, ce qui vous permet d’identifier les modifications que vous devez apporter dans votre code.

Créer un projet de bibliothèque de classes portables


Pour créer une bibliothèque de classes portables, utilisez le modèle fourni dans Visual Studio. Créez un projet
(fichier > nouveau projet ) et, dans la boîte de dialogue nouveau projet , sélectionnez votre langage de
programmation (Visual C# ou Visual Basic). Ensuite, sélectionnez le modèle bibliothèque de classes (por table
hérité) . Entrez un nom pour votre projet, puis choisissez OK .
La boîte de dialogue Ajouter une bibliothèque de classes por tables s’affiche. Choisissez au moins deux
cibles, puis choisissez OK .
Modifier les cibles
Vous pouvez modifier les plateformes cibles d’un projet de bibliothèque de classes portable lorsque vous le créez
ou après avoir démarré le développement. Si vous souhaitez modifier les cibles après avoir créé votre projet, dans
Explorateur de solutions , ouvrez le menu contextuel de votre projet de bibliothèque de classes portable (pas la
solution), puis choisissez Propriétés . Dans la page Propriétés du projet, l’onglet bibliothèque affiche les
plateformes actuellement ciblées par votre projet.
Pour ajouter ou supprimer des cibles, cliquez sur le bouton modifier , puis activez et désactivez les cases à cocher
appropriées.
Quand vous modifiez les cibles, les API disponibles pour le développement de votre projet changent en
conséquence. Visual Studio indique les erreurs et les avertissements éventuellement engendrés par la
modification des cibles.
Si vous souhaitez évaluer la portabilité de vos assemblys avant d’apporter des modifications dans Visual Studio,
vous pouvez utiliser l' Analyseur de portabilité .net.

Types et membres pris en charge


Les types et les membres disponibles dans les projets de bibliothèque de classes portables dépendent de
plusieurs facteurs de compatibilité :
Ils doivent être partagés entre les cibles que vous avez sélectionnées.
Leur comportement doit être similaire sur ces cibles.
Ils ne doivent pas être candidats pour la dépréciation.
Ils doivent s'avérer utiles dans un environnement portable, en particulier lorsque les membres qui les
prennent en charge ne sont pas portables.
Si un membre est pris en charge dans la bibliothèque de classes portables et pour les cibles que vous avez
sélectionnées, il apparaît dans votre projet dans IntelliSense. Toutefois, gardez à l'esprit que même si une API est
prise en charge dans la bibliothèque de classes portables, ce sont les cibles que vous sélectionnez qui
déterminent si vous pouvez utiliser cette API.

Différences d'API dans la bibliothèque de classes portables


Pour rendre les assemblys de bibliothèque de classes portables compatibles sur toutes les plateformes prises en
charge, certains membres ont été légèrement modifiés dans la bibliothèque de classes portables.

Utiliser la bibliothèque de classes portables


Après avoir créé votre projet de bibliothèque de classes portables, vous n'avez plus qu'à le référencer à partir
d'autres projets. Vous pouvez référencer le projet ou des assemblys spécifiques qui contiennent les classes
auxquelles vous souhaitez accéder.
Pour exécuter une application qui référence un assembly de bibliothèque de classes portables, la version requise
(ou version ultérieure) des plateformes ciblées doit être installée sur votre ordinateur. Visual Studio contient
toutes les infrastructures requises pour que vous puissiez exécuter l'application sans modification supplémentaire
sur l'ordinateur que vous avez utilisé pour développer l'application.
Déployer une application Windows universelle
Lorsque vous créez une application Windows universelle qui fait référence à un assembly de bibliothèque de
classes portable, tout ce dont vous avez besoin pour déployer l’application est inclus dans le package
d’application et aucune autre étape n’est requise.
Déployer une application .NET Framework
Quand vous déployez une application .NET Framework qui référence un assembly de bibliothèque de classes
portables, vous devez spécifier une dépendance sur la version appropriée du .NET Framework. En spécifiant cette
dépendance, vous êtes assuré que la version requise est installée avec votre application.
Pour créer une dépendance avec un déploiement ClickOnce : dans Explorateur de solutions , choisissez
le nœud de projet pour le projet que vous souhaitez publier. (Il s’agit du projet qui référence le projet de
bibliothèque de classes portables.) Dans la barre de menus, Project choisissez > Propriétés du projet, puis
choisissez l’onglet publier . Sur la page publier , choisissez composants requis . Sélectionnez la version
requise du .NET Framework en tant que composant requis.
Pour créer une dépendance avec un projet d’installation : dans Explorateur de solutions , choisissez le
projet d’installation. Dans la barre de menus, choisissez Propriétés du projet > Proper ties >
composants requis . Sélectionnez la version requise du .NET Framework en tant que composant requis.
Pour plus d’informations sur le déploiement d’applications .NET Framework, consultez le Guide de déploiement
pour les développeurs.

Voir aussi
Utilisation de la bibliothèque de classes portables avec MVVM
Ressources d’application pour les bibliothèques qui ciblent plusieurs plateformes
Analyseur de portabilité .NET
Prise en charge .NET Framework pour les applications Windows Store et Windows Runtime
Déploiement
Utilisation de la Bibliothèque de classes portable
avec le modèle d'affichage Modèle-Affichage
18/07/2020 • 10 minutes to read • Edit Online

Vous pouvez utiliser la bibliothèque de classes Portable .NET Framework pour implémenter le modèle MVVM
(Model-View-View Model) et partager des assemblys sur plusieurs plateformes.

IMPORTANT
Étant donné que les projets de bibliothèque de classes Portable ciblent uniquement un sous-ensemble très spécifique des
implémentations de .NET, nous déconseillons fortement leur utilisation dans le développement de nouvelles applications. Le
remplacement recommandé est une bibliothèque .NET Standard, qui cible toutes les implémentations de .NET qui prennent
en charge une version spécifique de .NET Standard. Pour plus d'informations, consultez .NET Standard.

MVVM est un modèle d’application qui isole l’interface utilisateur de la logique métier sous-jacente. Vous pouvez
implémenter les classes Model et View Model dans un projet de bibliothèque de classes portables dans Visual
Studio 2012, puis créer des vues personnalisées pour différentes plateformes. Cette approche vous permet d’écrire
le modèle de données et la logique métier une seule fois, et d’utiliser ce code à partir des applications du Windows
Store .NET Framework, Silverlight, Windows Phone et Windows 8. x, comme indiqué dans l’illustration suivante.

Cette rubrique ne fournit pas d’informations générales sur le modèle MVVM. Il fournit uniquement des
informations sur l’utilisation de la bibliothèque de classes portables pour implémenter MVVM. Pour plus
d’informations sur MVVM, consultez le Guide de démarrage rapide de MVVM à l’aide de la bibliothèque Prism 5,0
pour WPF.

Classes qui prennent en charge MVVM


Quand vous ciblez le .NET Framework 4,5, .NET pour les applications du Windows 8. x Store, Silverlight ou
Windows Phone 7,5 pour votre projet de bibliothèque de classes portables, les classes suivantes sont disponibles
pour implémenter le modèle MVVM :
Classe System.Collections.ObjectModel.ObservableCollection<T>
Classe System.Collections.ObjectModel.ReadOnlyObservableCollection<T>
Classe System.Collections.Specialized.INotifyCollectionChanged
Classe System.Collections.Specialized.NotifyCollectionChangedAction
Classe System.Collections.Specialized.NotifyCollectionChangedEventArgs
Classe System.Collections.Specialized.NotifyCollectionChangedEventHandler
Classe System.ComponentModel.DataErrorsChangedEventArgs
Classe System.ComponentModel.INotifyDataErrorInfo
Classe System.ComponentModel.INotifyPropertyChanged
Classe System.Windows.Input.ICommand
Toutes les classes de l' System.ComponentModel.DataAnnotations espace de noms

Implémentation de MVVM
Pour implémenter MVVM, vous créez généralement le modèle et le modèle de vue dans un projet de bibliothèque
de classes portables, car un projet de bibliothèque de classes portables ne peut pas faire référence à un projet non
portable. Le modèle et le modèle de vue peuvent se trouver dans le même projet ou dans des projets distincts. Si
vous utilisez des projets distincts, ajoutez une référence du projet de modèle de vue au projet de modèle.
Après avoir compilé le modèle et les projets de modèle de vue, vous référencez ces assemblys dans l’application
qui contient la vue. Si la vue interagit uniquement avec le modèle de vue, il vous suffit de référencer l’assembly qui
contient le modèle de vue.
Modèle
L’exemple suivant montre une classe de modèle simplifiée qui peut résider dans un projet de bibliothèque de
classes portables.

using System;

namespace SimpleMVVM.Model
{
public class Customer
{
public int CustomerID
{
get;
set;
}

public string FullName


{
get;
set;
}

public string Phone


{
get;
set;
}
}
}

Namespace SimpleMVVM.Model

Public Class Customer


Public Property CustomerID() As Integer
Public Property FullName() As String
Public Property Phone() As String
End Class
End Namespace
L’exemple suivant illustre une méthode simple pour remplir, récupérer et mettre à jour les données dans un projet
de bibliothèque de classes portable. Dans une application réelle, vous récupérez les données d’une source, par
exemple un service Windows Communication Foundation (WCF).

using System;
using System.Collections.Generic;
using System.Linq;

namespace SimpleMVVM.Model
{
public class CustomerRepository
{
private List<Customer> _customers;

public CustomerRepository()
{
_customers = new List<Customer>
{
new Customer(){ CustomerID = 1, FullName="Dana Birkby", Phone="394-555-0181"},
new Customer(){ CustomerID = 2, FullName="Adriana Giorgi", Phone="117-555-0119"},
new Customer(){ CustomerID = 3, FullName="Wei Yu", Phone="798-555-0118"}
};
}

public List<Customer> GetCustomers()


{
return _customers;
}

public void UpdateCustomer(Customer SelectedCustomer)


{
Customer customerToChange = _customers.Single(c => c.CustomerID == SelectedCustomer.CustomerID);
customerToChange = SelectedCustomer;
}
}
}

Namespace SimpleMVVM.Model
Public Class CustomerRepository
Private _customers As List(Of Customer)

Public Sub New()


_customers = New List(Of Customer) From
{
New Customer() With {.CustomerID = 1, .FullName = "Dana Birkby", .Phone = "394-555-0181"},
New Customer() With {.CustomerID = 2, .FullName = "Adriana Giorgi", .Phone = "117-555-0119"},
New Customer() With {.CustomerID = 3, .FullName = "Wei Yu", .Phone = "798-555-0118"}
}
End Sub

Public Function GetCustomers() As List(Of Customer)


Return _customers
End Function

Public Sub UpdateCustomer(SelectedCustomer As Customer)


Dim customerToChange = _customers.Single(Function(c) c.CustomerID = SelectedCustomer.CustomerID)
customerToChange = SelectedCustomer
End Sub
End Class
End Namespace

Afficher le modèle
Une classe de base pour les modèles de vue est fréquemment ajoutée lors de l’implémentation du modèle MVVM.
L’exemple suivant montre une classe de base.

using System;
using System.ComponentModel;

namespace SimpleMVVM.ViewModel
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propName)


{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
}

Imports System.ComponentModel

Namespace SimpleMVVM.ViewModel

Public MustInherit Class ViewModelBase


Implements INotifyPropertyChanged

Public Event PropertyChanged As PropertyChangedEventHandler Implements


INotifyPropertyChanged.PropertyChanged

Protected Overridable Sub OnPropertyChanged(propname As String)


RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propname))
End Sub
End Class
End Namespace

Une implémentation de l' ICommand interface est fréquemment utilisée avec le modèle MVVM. L'exemple suivant
illustre une implémentation de l'interface ICommand.
using System;
using System.Windows.Input;

namespace SimpleMVVM.ViewModel
{
public class RelayCommand : ICommand
{
private readonly Action _handler;
private bool _isEnabled;

public RelayCommand(Action handler)


{
_handler = handler;
}

public bool IsEnabled


{
get { return _isEnabled; }
set
{
if (value != _isEnabled)
{
_isEnabled = value;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}

public bool CanExecute(object parameter)


{
return IsEnabled;
}

public event EventHandler CanExecuteChanged;

public void Execute(object parameter)


{
_handler();
}
}
}
Imports System.Windows.Input

Namespace SimpleMVVM.ViewModel

Public Class RelayCommand


Implements ICommand

Private _isEnabled As Boolean


Private ReadOnly _handler As Action

Public Sub New(ByVal handler As Action)


_handler = handler
End Sub

Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged

Public Property IsEnabled() As Boolean


Get
Return _isEnabled
End Get
Set(ByVal value As Boolean)
If (value <> _isEnabled) Then
_isEnabled = value
RaiseEvent CanExecuteChanged(Me, EventArgs.Empty)
End If
End Set
End Property

Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute


Return IsEnabled
End Function

Public Sub Execute(parameter As Object) Implements ICommand.Execute


_handler()
End Sub

End Class
End Namespace

L’exemple suivant montre un modèle de vue simplifié.


using System;
using System.Collections.Generic;
using SimpleMVVM.Model;

namespace SimpleMVVM.ViewModel
{
public class CustomerViewModel : ViewModelBase
{
private List<Customer> _customers;
private Customer _currentCustomer;
private CustomerRepository _repository;

public CustomerViewModel()
{
_repository = new CustomerRepository();
_customers = _repository.GetCustomers();

WireCommands();
}

private void WireCommands()


{
UpdateCustomerCommand = new RelayCommand(UpdateCustomer);
}

public RelayCommand UpdateCustomerCommand


{
get;
private set;
}

public List<Customer> Customers


{
get { return _customers; }
set { _customers = value; }
}

public Customer CurrentCustomer


{
get
{
return _currentCustomer;
}

set
{
if (_currentCustomer != value)
{
_currentCustomer = value;
OnPropertyChanged("CurrentCustomer");
UpdateCustomerCommand.IsEnabled = true;
}
}
}

public void UpdateCustomer()


{
_repository.UpdateCustomer(CurrentCustomer);
}
}
}
Imports System.Collections.Generic
Imports SimpleMVVM.Model

Namespace SimpleMVVM.ViewModel

Public Class CustomerViewModel


Inherits ViewModelBase

Private _customers As List(Of Customer)


Private _currentCustomer As Customer
Private _repository As CustomerRepository
Private _updateCustomerCommand As RelayCommand

Public Sub New()


_repository = New CustomerRepository()
_customers = _repository.GetCustomers()

WireCommands()
End Sub

Private Sub WireCommands()


UpdateCustomerCommand = New RelayCommand(AddressOf UpdateCustomer)
End Sub

Public Property UpdateCustomerCommand() As RelayCommand


Get
Return _updateCustomerCommand
End Get
Private Set(value As RelayCommand)
_updateCustomerCommand = value
End Set
End Property

Public Property Customers() As List(Of Customer)


Get
Return _customers
End Get
Set(value As List(Of Customer))
_customers = value
End Set
End Property

Public Property CurrentCustomer() As Customer


Get
Return _currentCustomer
End Get
Set(value As Customer)
If _currentCustomer.Equals(value) Then
_currentCustomer = value
OnPropertyChanged("CurrentCustomer")
UpdateCustomerCommand.IsEnabled = True
End If
End Set
End Property

Public Sub UpdateCustomer()


_repository.UpdateCustomer(CurrentCustomer)
End Sub
End Class
End Namespace

Vue
À partir d’une application .NET Framework 4,5, d’une application Windows 8. x Store, d’une application basée sur
Silverlight ou d’une application Windows Phone 7,5, vous pouvez référencer l’assembly qui contient les projets de
modèle et de modèle de vue. Vous créez ensuite une vue qui interagit avec le modèle de vue. L’exemple suivant
montre une application simplifiée Windows Presentation Foundation (WPF) qui récupère et met à jour les données
à partir du modèle de vue. Vous pouvez créer des affichages similaires dans les applications du Windows Store
Silverlight, Windows Phone ou Windows 8. x.

<Window x:Class="SimpleWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModels="clr-namespace:SimpleMVVM.ViewModel;assembly=SimpleMVVM.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<viewModels:MainPageViewModel x:Key="ViewModel" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource ViewModel}}">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Height="23" Margin="5" HorizontalAlignment="Right" Grid.Column="0" Grid.Row="0"
Name="textBlock2"
Text="Select a Customer:" VerticalAlignment="Top" />
<ComboBox Height="23" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="0" Name="CustomersComboBox"
VerticalAlignment="Top" Width="173"
DisplayMemberPath="FullName" SelectedItem="{Binding Path=CurrentCustomer, Mode=TwoWay}"
ItemsSource="{Binding Path=Customers}" />
<TextBlock Height="23" Margin="5" HorizontalAlignment="Right" Grid.Column="0" Grid.Row="1"
Name="textBlock4" Text="Customer ID" />
<TextBlock Height="23" Margin="5" HorizontalAlignment="Right" Grid.Column="0" Grid.Row="2"
Name="textBlock5" Text="Name" />
<TextBlock Height="23" Margin="5" HorizontalAlignment="Right" Grid.Column="0" Grid.Row="3"
Name="textBlock9" Text="Phone" />
<TextBlock Height="23" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="1"
Name="CustomerIDTextBlock"
Text="{Binding ElementName=CustomersComboBox, Path=SelectedItem.CustomerID}" />
<TextBox Height="23" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="2" Width="219"
Text="{Binding Path=CurrentCustomer.FullName, Mode=TwoWay}" />
<TextBox Height="23" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="3" Width="219"
Text="{Binding Path=CurrentCustomer.Phone, Mode=TwoWay}" />
<Button
Command="{Binding UpdateCustomerCommand}"
Content="Update" Height="23" HorizontalAlignment="Right" Grid.Column="0" Grid.Row="4"
Name="UpdateButton" VerticalAlignment="Top" Width="75" />
</Grid>
</Window>

Voir aussi
Bibliothèque de classes portable
Ressources d'application pour les bibliothèques qui
ciblent des plateformes multiples
18/07/2020 • 19 minutes to read • Edit Online

Vous pouvez utiliser le type de projet de bibliothèque de classes Portable .NET Framework pour vous assurer que
les ressources de vos bibliothèques de classes sont accessibles à partir de plusieurs plateformes. Ce type de projet
est disponible dans Visual Studio 2012 et cible le sous-ensemble portable de la bibliothèque de classes .NET
Framework. L’utilisation d’une bibliothèque de classes portable garantit que votre bibliothèque est accessible à
partir des applications de bureau, des applications Silverlight, des applications de Windows Phone et des
applications du Windows 8. x Store.

IMPORTANT
Étant donné que les projets de bibliothèque de classes Portable ciblent uniquement un sous-ensemble très spécifique des
implémentations de .NET, nous déconseillons fortement leur utilisation dans le développement de nouvelles applications. Le
remplacement recommandé est une bibliothèque .NET Standard, qui cible toutes les implémentations de .NET qui prennent
en charge une version spécifique de .NET Standard. Pour plus d'informations, consultez .NET Standard.

Le projet de bibliothèque de classes portables ne met à la disposition de votre application qu’un sous-ensemble
très limité des types de l' System.Resources espace de noms, mais vous permet d’utiliser la ResourceManager
classe pour récupérer des ressources. Toutefois, si vous créez une application à l'aide de Visual Studio, vous devez
utiliser le wrapper fortement typé créé par Visual Studio, plutôt que d'utiliser directement la classe
ResourceManager.
Pour créer un wrapper fortement typé dans Visual Studio, définissez le modificateur d’accès du fichier de
ressources principal dans le concepteur de ressources de Visual Studio sur public . Cela crée un fichier
[resourceFileName].designer.cs ou [resourceFileName].designer.vb qui contient le wrapper fortement typé
ResourceManager. Pour plus d’informations sur l’utilisation d’un wrapper de ressources fortement typé, consultez
la section « génération d’une classe de ressource fortement typée » dans la rubrique Resgen. exe (Resource File
Generator) .

Gestionnaire des ressources dans la bibliothèque de classes portables


Dans un projet de bibliothèque de classes portables, tout accès aux ressources est géré par la ResourceManager
classe. Étant donné que les types dans l' System.Resources espace de noms, tels que ResourceReader et
ResourceSet , ne sont pas accessibles à partir d’un projet de bibliothèque de classes portables, ils ne peuvent pas
être utilisés pour accéder aux ressources.
Le projet de bibliothèque de classes portable comprend les quatre ResourceManager membres listés dans le
tableau suivant. Ces constructeurs et méthodes vous permettent d'instancier un objet ResourceManager et
d'extraire des ressources de chaîne.

M EM B RE RESOURCEMANAGER DESC RIP T IO N

ResourceManager(String, Assembly) Créé une instance ResourceManager pour accéder au fichier


de ressources nommé trouvé dans l'assembly spécifié.

ResourceManager(Type) Créé une instance ResourceManager qui correspond au type


spécifié.
M EM B RE RESOURCEMANAGER DESC RIP T IO N

GetString(String) Extrait une ressource nommée pour la culture actuelle.

GetString(String, CultureInfo) Extrait une ressource nommée appartenant à la culture


spécifiée.

L’exclusion d’autres ResourceManager membres de la bibliothèque de classes portables signifie que les objets
sérialisés, les données qui ne sont pas des chaînes et les images ne peuvent pas être récupérés à partir d’un fichier
de ressources. Pour utiliser des ressources d’une bibliothèque de classes portables, vous devez stocker toutes les
données d’objet sous forme de chaîne. Par exemple, vous pouvez stocker des valeurs numériques dans un fichier
de ressources en les convertissant en chaînes, et les extraire, puis, vous pouvez les reconvertir en nombres à l'aide
de la méthode Parse ou TryParse du type de données numériques. Vous pouvez convertir des images ou autres
données binaires en représentation sous forme de chaîne en appelant la méthode Convert.ToBase64String, et les
restaurer en un tableau d'octets en appelant la méthode Convert.FromBase64String.

La bibliothèque de classes portable et les applications du Windows


Store
Les projets de bibliothèque de classes portables stockent des ressources dans des fichiers. resx, qui sont ensuite
compilés dans des fichiers. Resources et incorporés dans l’assembly principal ou dans des assemblys satellites au
moment de la compilation. Les applications du Windows 8. x Store, en revanche, requièrent que les ressources
soient stockées dans des fichiers. resw, qui sont ensuite compilés dans un seul fichier d’index de ressources de
package (PRI). Toutefois, en dépit des formats de fichiers incompatibles, votre bibliothèque de classes portable
fonctionnera dans une application Windows 8. x Store.
Pour utiliser votre bibliothèque de classes à partir d’une application Windows 8. x Store, ajoutez une référence à
celle-ci dans votre projet d’application Windows Store. Visual Studio extraira en toute transparence les ressources
de votre assembly dans un fichier. resw et l’utilisera pour générer un fichier PRI à partir duquel l’Windows
Runtime peut extraire des ressources. Au moment de l’exécution, le Windows Runtime exécute le code dans votre
bibliothèque de classes portable, mais récupère les ressources de votre bibliothèque de classes portable à partir
du fichier PRI.
Si votre projet de bibliothèque de classes portables comprend des ressources localisées, vous utilisez le modèle
Hub-and-spoke pour les déployer de la même façon que pour une bibliothèque dans une application de bureau.
Pour utiliser votre fichier de ressources principal et tous les fichiers de ressources localisés dans votre application
Windows 8. x Store, vous ajoutez une référence à l’assembly principal. Au moment de la compilation, Visual Studio
extrait les ressources à partir du fichier de ressources principal et de tous les fichiers de ressources localisés dans
des fichiers .resw distincts. Il compile ensuite les fichiers. resw en un seul fichier PRI auquel le Windows Runtime
accède au moment de l’exécution.

Exemple : bibliothèque de classes portable non localisée


L’exemple de bibliothèque de classes portable simple non localisé suivant utilise des ressources pour stocker les
noms des colonnes et pour déterminer le nombre de caractères à réserver pour les données tabulaires. L'exemple
utilise un fichier nommé LibResources.resx pour stocker les ressources de chaîne répertoriées dans le tableau
suivant.

N O M DE L A RESSO URC E VA L EUR DE L A RESSO URC E

Né le Date de naissance

BornLength 12
N O M DE L A RESSO URC E VA L EUR DE L A RESSO URC E

Embauché Date d'embauche

HiredLength 12

id id

ID.Length 12

Nom Nom

NameLength 25

Intitulé Base de données des employés

Le code suivant définit une UILibrary classe qui utilise le wrapper gestionnaire des ressources nommé resources
généré par Visual Studio lorsque le modificateur d’accès pour le fichier devient public . La classe UILibrary
analyse les données de chaîne selon les besoins. . Notez que la classe est dans l'espace de noms
MyCompany.Employees .
using System;
using System.Resources;
using MyCompany.Employees;

[assembly: NeutralResourcesLanguage("en-US")]

namespace MyCompany.Employees
{
public class UILibrary
{
private const int nFields = 4;

public static string GetTitle()


{
string retval = LibResources.Born;
if (String.IsNullOrEmpty(retval))
retval = "";

return retval;
}

public static string[] GetFieldNames()


{
string[] fieldnames = new string[nFields];
fieldnames[0] = LibResources.Name;
fieldnames[1] = LibResources.ID;
fieldnames[2] = LibResources.Born;
fieldnames[3] = LibResources.Hired;
return fieldnames;
}

public static int[] GetFieldLengths()


{
int[] fieldLengths = new int[nFields];
fieldLengths[0] = Int32.Parse(LibResources.NameLength);
fieldLengths[1] = Int32.Parse(LibResources.IDLength);
fieldLengths[2] = Int32.Parse(LibResources.BornLength);
fieldLengths[3] = Int32.Parse(LibResources.HiredLength);
return fieldLengths;
}
}
}
Imports System.Resources

<Assembly: NeutralResourcesLanguage("en-US")>

Public Class UILibrary


Private Const nFields As Integer = 4

Public Shared Function GetTitle() As String


Dim retval As String = My.Resources.LibResources.Title
If String.IsNullOrEmpty(retval) Then retval = "<No value>"

Return retval
End Function

Public Shared Function GetFieldNames() As String()


Dim fieldnames(nFields - 1) As String
fieldnames(0) = My.Resources.LibResources.Name
fieldnames(1) = My.Resources.LibResources.ID
fieldnames(2) = My.Resources.LibResources.Born
fieldnames(3) = My.Resources.LibResources.Hired
Return fieldnames
End Function

Public Shared Function GetFieldLengths() As Integer()


Dim fieldLengths(nFields - 1) As Integer
fieldLengths(0) = Int32.Parse(My.Resources.LibResources.NameLength)
fieldLengths(1) = Int32.Parse(My.Resources.LibResources.IDLength)
fieldLengths(2) = Int32.Parse(My.Resources.LibResources.BornLength)
fieldLengths(3) = Int32.Parse(My.Resources.LibResources.HiredLength)
Return fieldLengths
End Function
End Class

Le code suivant illustre de quelle manière la classe UILibrary et ses ressources sont accessibles à partir d'une
application en mode console. Elle nécessite l’ajout d’une référence à UILibrary. dll au projet d’application console.
using System;
using System.Collections.Generic;
using MyCompany.Employees;

class Program
{
static void Main()
{
// Get the data from some data source.
var employees = InitializeData();

// Display application title.


string title = UILibrary.GetTitle();
int start = (Console.WindowWidth + title.Length) / 2;
string titlefmt = String.Format("{{0,{0}{1}", start, "}");
Console.WriteLine(titlefmt, title);
Console.WriteLine();

// Retrieve resources.
string[] fields = UILibrary.GetFieldNames();
int[] lengths = UILibrary.GetFieldLengths();
string fmtString = String.Empty;
// Create format string for field headers and data.
for (int ctr = 0; ctr < fields.Length; ctr++)
fmtString += String.Format("{{{0},-{1}{2}{3} ", ctr, lengths[ctr], ctr >= 2 ? ":d" : "", "}");

// Display the headers.


Console.WriteLine(fmtString, fields);
Console.WriteLine();
// Display the data.
foreach (var e in employees)
Console.WriteLine(fmtString, e.Item1, e.Item2, e.Item3, e.Item4);

Console.ReadLine();
}

private static List<Tuple<String, String, DateTime, DateTime>> InitializeData()


{
List<Tuple<String, String, DateTime, DateTime>> employees = new List<Tuple<String, String, DateTime,
DateTime>>();
var t1 = Tuple.Create("John", "16302", new DateTime(1954, 8, 18), new DateTime(2006, 9, 8));
employees.Add(t1);
t1 = Tuple.Create("Alice", "19745", new DateTime(1995, 5, 10), new DateTime(2012, 10, 17));
employees.Add(t1);
return employees;
}
}
Imports MyCompany.Employees
Imports System.Collections.Generic

Module Module1

Sub Main()
' Get the data from some data source.
Dim employees = InitializeData()

' Display application title.


Dim title As String = UILibrary.GetTitle()
Dim start As Integer = (Console.WindowWidth + title.Length) \ 2
Dim titlefmt As String = String.Format("{{0,{0}{1}", start, "}")
Console.WriteLine(titlefmt, title)
Console.WriteLine()

' Retrieve resources.


Dim fields() As String = UILibrary.GetFieldNames()
Dim lengths() As Integer = UILibrary.GetFieldLengths()
Dim fmtString As String = String.Empty
' Create format string for field headers and data.
For ctr = 0 To fields.Length - 1
fmtString += String.Format("{{{0},-{1}{2}{3} ", ctr, lengths(ctr), IIf(ctr >= 2, ":d", ""), "}")
Next
' Display the headers.
Console.WriteLine(fmtString, fields)
Console.WriteLine()
' Display the data.
For Each e In employees
Console.WriteLine(fmtString, e.Item1, e.Item2, e.Item3, e.Item4)
Next
Console.ReadLine()
End Sub

Private Function InitializeData() As List(Of Tuple(Of String, String, Date, Date))


Dim employees As New List(Of Tuple(Of String, String, Date, Date))
Dim t1 = Tuple.Create("John", "16302", #8/18/1954#, #9/8/2006#)
employees.Add(t1)
t1 = Tuple.Create("Alice", "19745", #5/10/1995#, #10/17/2012#)
employees.Add(t1)
Return employees
End Function
End Module

Le code suivant illustre comment UILibrary accéder à la classe et à ses ressources à partir d’une application
Windows 8. x Store. Elle nécessite l’ajout d’une référence à UILibrary. dll au projet d’application du Windows Store.

using System;
using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using MyCompany.Employees;

namespace ConsumerCS
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class BlankPage : Page
{
public BlankPage()
public BlankPage()
{
this.InitializeComponent();
}

/// <summary>
/// Invoked when this page is about to be displayed in a Frame.
/// </summary>
/// <param name="e">Event data that describes how this page was reached. The Parameter
/// property is typically used to configure the page.</param>
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Example.DisplayData(outputBlock);
}
}
}

public class Example


{
static public void DisplayData(Windows.UI.Xaml.Controls.TextBlock outputBlock)
{
// Get the data from some data source.
var employees = InitializeData();
outputBlock.FontFamily = new FontFamily("Courier New");
// Display application title.
string title = UILibrary.GetTitle();
outputBlock.Text += title + Environment.NewLine + Environment.NewLine;

// Retrieve resources.
string[] fields = UILibrary.GetFieldNames();
int[] lengths = UILibrary.GetFieldLengths();
string fmtString = String.Empty;
// Create format string for field headers and data.
for (int ctr = 0; ctr < fields.Length; ctr++)
fmtString += String.Format("{{{0},-{1}{2}{3} ", ctr, lengths[ctr], ctr >= 2 ? ":d" : "", "}");

// Display the headers.


outputBlock.Text += String.Format(fmtString, fields) + Environment.NewLine + Environment.NewLine;
// Display the data.
foreach (var e in employees)
outputBlock.Text += String.Format(fmtString, e.Item1, e.Item2, e.Item3, e.Item4) +
Environment.NewLine;
}

private static List<Tuple<String, String, DateTime, DateTime>> InitializeData()


{
List<Tuple<String, String, DateTime, DateTime>> employees = new List<Tuple<String, String, DateTime,
DateTime>>();
var t1 = Tuple.Create("John", "16302", new DateTime(1954, 8, 18), new DateTime(2006, 9, 8));
employees.Add(t1);
t1 = Tuple.Create("Alice", "19745", new DateTime(1995, 5, 10), new DateTime(2012, 10, 17));
employees.Add(t1);
return employees;
}
}

Exemple : bibliothèque de classes portable localisée


L’exemple de bibliothèque de classes portable localisée suivant comprend des ressources pour les cultures français
(France) et anglais (États-Unis). La culture anglais (États-Unis) est la culture par défaut de l’application ; ses
ressources sont indiquées dans le tableau de la section précédente. Le fichier de ressources de la culture française
(France) est nommé LibResources.fr-FR.resx et inclut les ressources de type de chaîne répertoriées dans le tableau
suivant. Le code source de la classe UILibrary est identique que celui présenté dans la section précédente.
N O M DE L A RESSO URC E VA L EUR DE L A RESSO URC E

Né le Date de naissance

BornLength 20

Embauché Date d'embauche

HiredLength 16

id id

Nom Nom

Intitulé Base de données des employés

Le code suivant illustre de quelle manière la classe UILibrary et ses ressources sont accessibles à partir d'une
application en mode console. Elle nécessite l’ajout d’une référence à UILibrary. dll au projet d’application console.
using System;
using System.Collections.Generic;
using System.Globalization;

using MyCompany.Employees;

class Program
{
static void Main(string[] args)
{

// Get the data from some data source.


var employees = InitializeData();

// Display application title.


string title = UILibrary.GetTitle();
int start = (Console.WindowWidth + title.Length) / 2;
string titlefmt = String.Format("{{0,{0}{1}", start, "}");
Console.WriteLine(titlefmt, title);
Console.WriteLine();

// Retrieve resources.
string[] fields = UILibrary.GetFieldNames();
int[] lengths = UILibrary.GetFieldLengths();
string fmtString = String.Empty;
// Create format string for field headers and data.
for (int ctr = 0; ctr < fields.Length; ctr++)
fmtString += String.Format("{{{0},-{1}{2}{3} ", ctr, lengths[ctr], ctr >= 2 ? ":d" : "", "}");

// Display the headers.


Console.WriteLine(fmtString, fields);
Console.WriteLine();
// Display the data.
foreach (var e in employees)
Console.WriteLine(fmtString, e.Item1, e.Item2, e.Item3, e.Item4);

Console.ReadLine();
}

private static List<Tuple<String, String, DateTime, DateTime>> InitializeData()


{
List<Tuple<String, String, DateTime, DateTime>> employees = new List<Tuple<String, String, DateTime,
DateTime>>();
var t1 = Tuple.Create("John", "16302", new DateTime(1954, 8, 18), new DateTime(2006, 9, 8));
employees.Add(t1);
t1 = Tuple.Create("Alice", "19745", new DateTime(1995, 5, 10), new DateTime(2012, 10, 17));
employees.Add(t1);
return employees;
}
}
Imports MyCompany.Employees
Imports System.Collections.Generic
Imports System.Globalization
Imports System.Threading

Module Module1
Sub Main()
Dim culture As CultureInfo = CultureInfo.CreateSpecificCulture("fr-FR")
Thread.CurrentThread.CurrentCulture = culture
Thread.CurrentThread.CurrentUICulture = culture
Console.WriteLine("Current culture is {0}", CultureInfo.CurrentCulture.Name)

' Get the data from some data source.


Dim employees = InitializeData()

' Display application title.


Dim title As String = UILibrary.GetTitle()
Dim start As Integer = (Console.WindowWidth + title.Length) \ 2
Dim titlefmt As String = String.Format("{{0,{0}{1}", start, "}")
Console.WriteLine(titlefmt, title)
Console.WriteLine()

' Retrieve resources.


Dim fields() As String = UILibrary.GetFieldNames()
Dim lengths() As Integer = UILibrary.GetFieldLengths()
Dim fmtString As String = String.Empty
' Create format string for field headers and data.
For ctr = 0 To fields.Length - 1
fmtString += String.Format("{{{0},-{1}{2}{3} ", ctr, lengths(ctr), IIf(ctr >= 2, ":d", ""), "}")
Next
' Display the headers.
Console.WriteLine(fmtString, fields)
Console.WriteLine()
' Display the data.
For Each e In employees
Console.WriteLine(fmtString, e.Item1, e.Item2, e.Item3, e.Item4)
Next
Console.ReadLine()
End Sub

Private Function InitializeData() As List(Of Tuple(Of String, String, Date, Date))


Dim employees As New List(Of Tuple(Of String, String, Date, Date))
Dim t1 = Tuple.Create("John", "16302", #8/18/1954#, #9/8/2006#)
employees.Add(t1)
t1 = Tuple.Create("Alice", "19745", #5/10/1995#, #10/17/2012#)
employees.Add(t1)
Return employees
End Function
End Module

Le code suivant illustre comment UILibrary accéder à la classe et à ses ressources à partir d’une application
Windows 8. x Store. Elle nécessite l’ajout d’une référence à UILibrary. dll au projet d’application du Windows Store.
Il utilise la propriété statique ApplicationLanguages.PrimaryLanguageOverride pour définir la langue par défaut sur
Français.

using System;
using System.Collections.Generic;
using Windows.Globalization;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using MyCompany.Employees;

namespace LocConsumerCS
{
public sealed partial class BlankPage : Page
{
public BlankPage()
{
this.InitializeComponent();
}

protected override void OnNavigatedTo(NavigationEventArgs e)


{
Example.Demo(outputBlock);
}
}

public class Example


{
public static void Demo(TextBlock outputBlock)
{
// Set the application preferences.
ApplicationLanguages.PrimaryLanguageOverride = "fr-FR";

// Get the data from some data source.


var employees = InitializeData();
outputBlock.FontFamily = new FontFamily("Courier New");
// Display application title.
string title = UILibrary.GetTitle();
outputBlock.Text += title + Environment.NewLine + Environment.NewLine;

// Retrieve resources.
string[] fields = UILibrary.GetFieldNames();
int[] lengths = UILibrary.GetFieldLengths();
string fmtString = String.Empty;
// Create format string for field headers and data.
for (int ctr = 0; ctr < fields.Length; ctr++)
fmtString += String.Format("{{{0},-{1}{2}{3} ", ctr, lengths[ctr], ctr >= 2 ? ":d" : "",
"}");

// Display the headers.


outputBlock.Text += String.Format(fmtString, fields) + Environment.NewLine + Environment.NewLine;

// Display the data.


foreach (var e in employees)
outputBlock.Text += String.Format(fmtString, e.Item1, e.Item2, e.Item3, e.Item4) +
Environment.NewLine;
}

private static List<Tuple<String, String, DateTime, DateTime>> InitializeData()


{
List<Tuple<String, String, DateTime, DateTime>> employees = new List<Tuple<String, String,
DateTime, DateTime>>();
var t1 = Tuple.Create("John", "16302", new DateTime(1954, 8, 18), new DateTime(2006, 9, 8));
employees.Add(t1);
t1 = Tuple.Create("Alice", "19745", new DateTime(1995, 5, 10), new DateTime(2012, 10, 17));
employees.Add(t1);
return employees;
}
}
}
Imports Windows.Globalization
Imports MyCompany.Employees

Public NotInheritable Class BlankPage


Inherits Page

Protected Overrides Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)


Example.Demo(outputBlock)
End Sub
End Class

Public Class Example


Public Shared Sub Demo(outputBlock As Windows.UI.Xaml.Controls.TextBlock)
' Set the application preferences.
ApplicationLanguages.PrimaryLanguageOverride = "fr-FR"

' Get the data from some data source.


Dim employees = InitializeData()
outputBlock.FontFamily = New FontFamily("Courier New")
' Display application title.
Dim title As String = UILibrary.GetTitle()
outputBlock.Text += title + vbCrLf + vbCrLf

' Retrieve resources.


Dim fields() As String = UILibrary.GetFieldNames()
Dim lengths() As Integer = UILibrary.GetFieldLengths()
Dim fmtString As String = String.Empty
' Create format string for field headers and data.
For ctr = 0 To fields.Length - 1
fmtString += String.Format("{{{0},-{1}{2}{3} ", ctr, lengths(ctr), If(ctr >= 2, ":d", ""), "}")
Next
' Display the headers.
outputBlock.Text += String.Format(fmtString, fields) + vbCrLf + vbCrLf

' Display the data.


For Each e In employees
outputBlock.Text += String.Format(fmtString, e.Item1, e.Item2, e.Item3, e.Item4) + vbCrLf
Next
End Sub

Private Shared Function InitializeData() As List(Of Tuple(Of String, String, Date, Date))
Dim employees As New List(Of Tuple(Of String, String, Date, Date))
Dim t1 = Tuple.Create("John", "16302", #8/18/1954#, #9/8/2006#)
employees.Add(t1)
t1 = Tuple.Create("Alice", "19745", #5/10/1995#, #10/17/2012#)
employees.Add(t1)
Return employees
End Function
End Class

Voir aussi
ResourceManager
Ressources dans les applications de bureau
Packaging and Deploying Resources
Prise en charge .NET Framework pour les
applications Windows Store et Windows Runtime
18/07/2020 • 24 minutes to read • Edit Online

Le .NET Framework 4,5 prend en charge un certain nombre de scénarios de développement logiciel avec le
Windows Runtime. Ces scénarios se répartissent en trois catégories :
Développement d’applications du Windows 8. x Store avec des contrôles XAML, comme décrit dans feuille
de route pour les applications du Windows Store en C# ou Visual Basic, présentation des procédures
(XAML)et .net pour les applications du Windows Store.
Développement de bibliothèques de classes à utiliser dans les applications du Windows 8. x Store que vous
créez avec le .NET Framework.
Développement de composants Windows Runtime, empaquetés dans. Fichiers WinMD, qui peuvent être
utilisés par n’importe quel langage de programmation prenant en charge l’Windows Runtime. Par exemple,
consultez création de composants Windows Runtime en C# et Visual Basic.
Cette rubrique décrit la prise en charge fournie par le .NET Framework pour les trois catégories, et décrit les
scénarios pour les composants Windows Runtime. La première section contient des informations de base sur la
relation entre le .NET Framework et le Windows Runtime, et explique certains singularités que vous pouvez
rencontrer dans le système d’aide et l’IDE. La deuxième section décrit des scénarios de développement de
composants Windows Runtime.

Concepts de base
Le .NET Framework prend en charge les trois scénarios de développement répertoriés précédemment en
fournissant .NET pour les applications du Windows 8. x Store et en prenant en charge le Windows Runtime lui-
même.
Les espaces de noms .NET Framework et Windows Runtime fournissent une vue simplifiée des
bibliothèques de classes .NET Framework et incluent uniquement les types et les membres que vous pouvez
utiliser pour créer des applications du Windows 8. x Store et des composants de Windows Runtime.
Quand vous utilisez Visual Studio (Visual Studio 2012 ou version ultérieure) pour développer une
application Windows 8. x Store ou un composant Windows Runtime, un ensemble d’assemblys de
référence garantit que seuls les types et membres pertinents sont visibles.
Ce jeu d’API rationalisé est simplifié par la suppression des fonctionnalités qui sont dupliquées dans
le .NET Framework ou qui dupliquent des fonctionnalités Windows Runtime. Par exemple, il contient
uniquement les versions génériques des types de collection et le modèle d’objet de document XML
est éliminé en faveur de l’ensemble d’API XML Windows Runtime.
Les fonctionnalités qui encapsulent simplement l’API du système d’exploitation sont également
supprimées, car le Windows Runtime est facile à appeler à partir du code managé.
Pour en savoir plus sur le .NET pour les applications du Windows 8. x Store, consultez la vue d’ensemble de
.net pour les applications du Windows Store. Pour en savoir plus sur le processus de sélection d’API,
consultez l’entrée .net for Metro style Apps dans le blog .net.
Le Windows Runtime fournit les éléments d’interface utilisateur pour la génération d’applications du
Windows 8. x Store et donne accès aux fonctionnalités du système d’exploitation. À l’instar de la .NET
Framework, le Windows Runtime a des métadonnées qui permettent aux compilateurs C# et Visual Basic
d’utiliser le Windows Runtime la manière dont ils utilisent les bibliothèques de classes .NET Framework. Le
.NET Framework simplifie l’utilisation du Windows Runtime en masquant certaines différences :
Certaines différences dans les modèles de programmation entre le .NET Framework et le Windows
Runtime, comme le modèle pour l’ajout et la suppression de gestionnaires d’événements, sont
masquées. Vous utilisez simplement le modèle .NET Framework.
Certaines différences dans les types couramment utilisés (par exemple les collections et les types
primitifs) sont masquées. Vous utilisez simplement le type de .NET Framework, comme indiqué dans
différences visibles dans l’IDE, plus loin dans cet article.
La plupart du temps, la prise en charge .NET Framework pour le Windows Runtime est transparente. La section
suivante présente certaines des différences évidentes entre le code managé et le Windows Runtime.
La .NET Framework et la documentation de référence Windows Runtime
Les Windows Runtime et les ensembles de documentation .NET Framework sont distincts. Si vous appuyez sur F1
pour afficher l’aide sur un type ou un membre, la documentation de référence de l’ensemble approprié s’affiche.
Toutefois, si vous parcourez la référence Windows Runtime , vous pouvez rencontrer des exemples qui semblent
curieuse :
Les rubriques telles que l' IIterable<T> interface n’ont pas de syntaxe de déclaration pour Visual Basic ou
C#. Au lieu de cela, une remarque s’affiche au-dessus de la section syntaxe (dans le cas présent, « .NET :
cette interface apparaît en tant que System. Collections. Generic. IEnumerable <T> »). Cela est dû au fait que
les .NET Framework et le Windows Runtime offrent des fonctionnalités similaires avec des interfaces
différentes. En outre, il existe des différences de comportement : IIterable a une méthode First plutôt
qu’une méthode GetEnumerator pour retourner l’énumérateur. Au lieu de vous forcer à apprendre une
autre façon d’effectuer une tâche courante, le .NET Framework prend en charge le Windows Runtime en
faisant apparaître votre code managé afin d’utiliser le type que vous connaissez. Vous ne verrez pas l'
IIterable interface dans l’IDE et, par conséquent, le seul moyen de le rencontrer dans la documentation de
référence Windows Runtime consiste à parcourir directement cette documentation.
La SyndicationFeed(String, String, Uri) documentation illustre un problème étroitement lié : ses types de
paramètres semblent différents dans différentes langues. Pour C# et Visual Basic, les types de paramètres
sont System.String et System.Uri. Là encore, c’est parce que le .NET Framework a ses propres types String
et Uri , et pour ces types couramment utilisés, il est inutile d’obliger les utilisateurs du .NET Framework à
apprendre une autre manière de faire les choses. Dans l’IDE, le .NET Framework masque les types de
Windows Runtime correspondants.
Dans certains cas, tels que la GridLength structure, la .NET Framework fournit un type avec le même nom,
mais davantage de fonctionnalités. Par exemple, un ensemble de rubriques sur les constructeurs et les
propriétés sont associées à GridLength , mais elles ont des blocs de syntaxe uniquement pour Visual Basic
et C#, car les membres sont disponibles uniquement dans le code managé. Dans le Windows Runtime, les
structures ont uniquement des champs. La structure Windows Runtime nécessite une classe d’assistance,
GridLengthHelper , pour fournir des fonctionnalités équivalentes. Vous ne verrez par cette classe
d’assistance dans l’IDE quand vous écrirez du code managé.
Dans l’IDE, les types de Windows Runtime s’affichent pour dériver de System.Object . Ils semblent avoir des
membres hérités de Object, tels que Object.ToString. Ces membres fonctionnent comme si les types étaient
réellement hérités de Object , et les types de Windows Runtime peuvent être convertis en Object . Cette
fonctionnalité fait partie de la prise en charge fournie par le .NET Framework pour le Windows Runtime.
Toutefois, si vous affichez les types dans la documentation de référence Windows Runtime, ces membres ne
s’affichent pas. La documentation sur ces membres hérités apparents est fournie par la documentation de
référence sur System.Object.
Différences visibles dans l’IDE
Dans des scénarios de programmation plus avancés, tels que l’utilisation d’un composant Windows Runtime écrit
en C# pour fournir la logique d’application pour une application Windows 8. x Store générée pour Windows à
l’aide de JavaScript, de telles différences sont apparentes dans l’IDE et dans la documentation. Lorsque votre
composant retourne un IDictionary<int, string> à JavaScript et que vous l’examinez dans le débogueur
JavaScript, vous verrez les méthodes de, IMap<int, string> car JavaScript utilise le type de Windows Runtime.
Certains types de collections fréquemment utilisés dont l’apparence est différente dans les deux langages sont
répertoriés dans le tableau suivant :

T Y P E W IN DO W S RUN T IM E T Y P E . N ET F RA M EW O RK C O RRESP O N DA N T

IIterable<T> IEnumerable<T>

IIterator<T> IEnumerator<T>

IVector<T> IList<T>

IVectorView<T> IReadOnlyList<T>

IMap<K, V> IDictionary<TKey, TValue>

IMapView<K, V> IReadOnlyDictionary<TKey, TValue>

IBindableIterable IEnumerable

IBindableVector IList

Windows.UI.Xaml.Data.INotifyPropertyChanged System.ComponentModel.INotifyPropertyChanged

Windows.UI.Xaml.Data.PropertyChangedEventHandler System.ComponentModel.PropertyChangedEventHandler

Windows.UI.Xaml.Data.PropertyChangedEventArgs System.ComponentModel.PropertyChangedEventArgs

Dans le Windows Runtime, IMap<K, V> et IMapView<K, V> sont itérés à l’aide de IKeyValuePair . Lorsque vous les
passez au code managé, ils apparaissent comme IDictionary<TKey, TValue> et IReadOnlyDictionary<TKey, TValue>
, donc bien sûr vous utilisez System.Collections.Generic.KeyValuePair<TKey, TValue> pour les énumérer.
La façon dont les interfaces apparaissent dans le code managé affecte la façon dont les types qui implémentent ces
interfaces apparaissent. Par exemple, la classe PropertySet implémente IMap<K, V> , qui s'affiche dans le code
managé sous la forme IDictionary<TKey, TValue> . PropertySet apparaît comme ayant implémenté
IDictionary<TKey, TValue> au lieu de IMap<K, V> , donc en code managé une méthode Add semble se comporter
comme la méthode Add sur les dictionnaires .NET Framework. Il ne semble pas avoir de méthode Insert .
Pour plus d’informations sur l’utilisation de l' .NET Framework pour créer un composant Windows Runtime et
pour obtenir une procédure pas à pas qui montre comment utiliser ce composant avec JavaScript, consultez
création de composants Windows Runtime en C# et Visual Basic.
Types primitifs
Pour permettre l’utilisation naturelle de l’Windows Runtime dans du code managé, .NET Framework types primitifs
apparaissent au lieu de Windows Runtime types primitifs dans votre code. Dans le .NET Framework, les types
primitifs, comme la structure Int32 ont de nombreuses propriétés et méthodes utiles, telles que la méthode
Int32.TryParse . En revanche, les types primitifs et les structures dans le Windows Runtime ont uniquement des
champs. Quand vous utilisez des primitives dans du code managé, elles semblent être des types .NET Framework,
et vous pouvez en utiliser les propriétés et les méthodes comme vous le faites habituellement. La liste suivante
fournit un résumé :
Pour les Windows Runtime primitives Int32 , Int64 ,,, Single Double Boolean , String (une collection
immuable de caractères Unicode), Enum ,, UInt32 UInt64 et Guid , utilisez le type du même nom dans l'
System espace de noms.

Pour UInt8 , utilisez System.Byte .


Pour Char16 , utilisez System.Char .
Pour l'interface IInspectable , utilisez System.Object .
Pour HRESULT , utilisez une structure avec un membre System.Int32 .
Comme avec les types d’interfaces, le seul moment où vous pouvez voir la preuve de cette représentation est
lorsque votre projet .NET Framework est un composant Windows Runtime utilisé par une application Windows 8.
x Store générée à l’aide de JavaScript.
D’autres types de Windows Runtime couramment utilisés qui apparaissent dans du code managé comme leurs
équivalents .NET Framework incluent la Windows.Foundation.DateTime structure, qui apparaît dans le code managé
comme System.DateTimeOffset structure et la Windows.Foundation.TimeSpan structure, qui apparaît comme
System.TimeSpan structure.
Autres différences
Dans certains cas, le fait que les types de .NET Framework apparaissent dans votre code au lieu des types de
Windows Runtime nécessite une action de votre part. Par exemple, la Windows.Foundation.Uri classe apparaît
comme System.Uri dans .NET Framework code. System.Uriautorise un URI relatif, mais Windows.Foundation.Uri
requiert un URI absolu. Par conséquent, lorsque vous transmettez un URI à une méthode Windows Runtime, vous
devez vous assurer qu’il est absolu. Voir Transmission d’un URI au Windows Runtime.

Scénarios pour le développement de composants Windows Runtime


Les scénarios pris en charge pour les composants de Windows Runtime gérés dépendent des principes généraux
suivants :
Windows Runtime composants générés à l’aide du .NET Framework n’ont aucune différence apparente par
rapport aux autres Runtimelibraries Windows. Par exemple, si vous implémentez à nouveau un composant
Windows Runtime natif à l’aide de code managé, les deux composants sont impossibles à distinguer dans le
sens inverse. Le fait que votre composant soit écrit en code managé est invisible pour le code qui l’utilise,
même si celui-ci est managé. Cependant, en interne, votre composant constitue du vrai code managé et
s’exécute sur le Common Language Runtime (CLR).
Les composants peuvent contenir des types qui implémentent la logique d’application, les contrôles
d’interface utilisateur du magasin Windows 8. x, ou les deux.

NOTE
Nous vous recommandons de séparer les éléments d’interface utilisateur de la logique d’application. En outre, vous
ne pouvez pas utiliser les contrôles d’interface utilisateur du Windows 8. x Store dans une application Windows 8. x
Store générée pour Windows en JavaScript et HTML.

Un composant peut être un projet dans une solution Visual Studio pour une application Windows 8. x Store,
ou un composant réutilisable que vous pouvez ajouter à plusieurs solutions.
NOTE
Si votre composant est utilisé uniquement avec C# ou Visual Basic, il n’y a aucune raison d’en faire un composant
Windows Runtime. Si vous en faites une bibliothèque de classes de .NET Framework ordinaire, vous n’êtes pas obligé
de limiter sa surface d’API publique aux types de Windows Runtime.

Vous pouvez libérer des versions de composants réutilisables à l’aide de l' VersionAttribute attribut
Windows Runtime pour identifier les types (et les membres d’un type) qui ont été ajoutés dans différentes
versions.
Les types de votre composant peuvent dériver des types de Windows Runtime. Les contrôles peuvent
dériver des types de contrôles primitifs de l' Windows.UI.Xaml.Controls.Primitives espace de noms ou de
contrôles plus finis tels que Button .

IMPORTANT
À compter de Windows 8 et du .NET Framework 4,5, tous les types publics dans un composant de Windows Runtime
géré doivent être sealed. Un type dans un autre composant de Windows Runtime ne peut pas en dériver. Si vous
souhaitez fournir un comportement polymorphe dans votre composant, vous pouvez créer une interface et
l’implémenter dans les types polymorphes.

Tous les types de paramètre et de retour sur les types publics de votre composant doivent être des types de
Windows Runtime (y compris les types de Windows Runtime que votre composant définit).
Les sections suivantes fournissent des exemples de scénarios courants.
Logique d’application pour une application Windows 8. x Store avec JavaScript
Lorsque vous développez une application Windows 8. x Store pour Windows à l’aide de JavaScript, vous
constaterez peut-être que certaines parties de la logique d’application fonctionnent mieux en code géré ou sont
plus faciles à développer. JavaScript ne peut pas utiliser les bibliothèques de classes .NET Framework directement,
mais vous pouvez faire de la bibliothèque de classes un fichier .WinMD. Dans ce scénario, le composant Windows
Runtime fait partie intégrante de l’application. il n’est donc pas judicieux de fournir des attributs de version.
Contrôles d’interface utilisateur de Windows 8. x Store réutilisables
Vous pouvez empaqueter un ensemble de contrôles d’interface utilisateur associés dans un composant Windows
Runtime réutilisable. Le composant peut être commercialisé seul ou utilisé comme élément dans les applications
que vous créez. Dans ce scénario, il est logique d’utiliser l' VersionAttribute attribut Windows Runtime pour
améliorer la compatibilité.
Logique d’application réutilisable à partir d’applications .NET Framework existantes
Vous pouvez empaqueter du code managé à partir de vos applications de bureau existantes en tant que
composant Windows Runtime autonome. Cela vous permet d’utiliser le composant dans les applications du
Windows 8. x Store créées à l’aide de C++ ou JavaScript, ainsi que dans les applications du Windows 8. x Store
créées à l’aide de C# ou de Visual Basic. Le contrôle de version est une option s’il existe plusieurs scénarios de
réutilisation du code.

Rubriques connexes
IN T IT UL É DESC RIP T IO N
IN T IT UL É DESC RIP T IO N

Vue d’ensemble de .NET pour les applications du Windows Décrit les types de .NET Framework et les membres que vous
Store pouvez utiliser pour créer des applications Windows 8. x Store
et des RuntimeComponents Windows. (Dans le Centre de
développement Windows.)

Feuille de route pour les applications du Windows Store en C# Fournit des ressources clés pour vous aider à commencer à
ou Visual Basic développer des applications Windows 8. x Store à l’aide de C#
ou de Visual Basic, y compris de nombreux sujets de
démarrage rapide, des instructions et des pratiques
recommandées. (Dans le Centre de développement Windows.)

Procédures (XAML) Fournit des ressources clés pour vous aider à commencer à
développer des applications Windows 8. x Store à l’aide de C#
ou de Visual Basic, y compris de nombreux sujets de
démarrage rapide, des instructions et des pratiques
recommandées. (Dans le Centre de développement Windows.)

Création de composants Windows Runtime en C# et Visual Décrit comment créer un composant Windows Runtime à
Basic l’aide du .NET Framework, explique comment l’utiliser dans le
cadre d’une application Windows 8. x Store générée pour
Windows à l’aide de JavaScript, et décrit comment déboguer la
combinaison avec Visual Studio. (Dans le Centre de
développement Windows.)

Informations de référence sur Windows Runtime La documentation de référence pour le Windows Runtime.
(Dans le Centre de développement Windows.)

Passage d’un URI au Windows Runtime Décrit un problème qui peut se produire quand vous
transmettez un URI à partir du code managé à la Windows
Runtime, et comment l’éviter.
Transmission d'un URI au Windows Runtime
18/07/2020 • 2 minutes to read • Edit Online

Les méthodes Windows Runtime n'acceptent que des URI absolus. Si vous transmettez un URI relatif à une
méthode Windows Runtime, une ArgumentException exception est levée. Voici pourquoi : quand vous utilisez la
Windows Runtime dans .NET Framework code, la Windows.Foundation.Uri classe apparaît comme System.Uri
dans IntelliSense. La System.Uri classe autorise les URI relatifs, contrairement à la Windows.Foundation.Uri classe.
Cela est également vrai pour les méthodes que vous exposez dans les composants Windows Runtime. Si votre
composant expose une méthode qui prend un URI, la signature dans votre code inclut System.Uri. Toutefois, pour
les utilisateurs de votre composant, la signature comprend Windows.Foundation.Uri . Un URI transmis à votre
composant doit être un URI absolu.
Cette rubrique montre comment détecter un URI absolu et comment en créer un pendant le référencement d'une
ressource dans le package d'application.

Détection et utilisation d'un URI absolu


Utilisez la Uri.IsAbsoluteUri propriété pour vous assurer qu’un URI est absolu avant de le passer à l’Windows
Runtime. Utiliser cette propriété est plus efficace qu'intercepter et gérer l'exception ArgumentException.

Utilisation d'un URI absolu pour une ressource dans le package


d'application
Si vous souhaitez spécifier un URI pour une ressource qui se trouve dans votre package d'application, vous
pouvez utiliser le schéma ms-appx ou ms-appx-web pour créer un URI absolu.
L’exemple suivant montre comment définir la Source propriété pour un WebView contrôle et la Source propriété
d’un Image contrôle sur les ressources contenues dans un dossier nommé pages, à l’aide de XAML et de code.

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">


<WebView Name="webview1" HorizontalAlignment="Center" Height="222"
VerticalAlignment="Top" Width="310" Margin="472,57,553,0"
Source="ms-appx-web:///Pages/HTMLPage1.html"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="322,185,0,0"
VerticalAlignment="Top" Click="Button_Click_1"/>
<Image HorizontalAlignment="Left" Height="100" Margin="208,123,0,0" VerticalAlignment="Top"
Width="100" Source="ms-appx:///Pages/weather.jpg" />

</Grid>

private void Button_Click_1(object sender, RoutedEventArgs e)


{
webview1.Source = new Uri("ms-appx-web:///Pages/HTMLPage2.html", UriKind.Absolute);
}

Private Sub Button_Click_1(sender As Object, e As RoutedEventArgs)


webview1.Source = New Uri("ms-appx-web:///Pages/HTMLPage2.html", UriKind.Absolute)
End Sub

Pour plus d’informations sur ces schémas, consultez Schémas URI dans le Centre de développement Windows.
Voir aussi
Prise en charge .NET Framework pour les applications Windows Store et Windows Runtime

S-ar putea să vă placă și