Section courante

A propos

Section administrative du site

Les fondements de C#

Les fondements de C# sont les concepts, principes et éléments fondamentaux du langage de programmation C#. Ils définissent la base sur laquelle tout développement en C# repose.

Les instructions de niveau supérieur

A partir de la version 9 de C#, vous pouvez créer des applications simples sans définir d'espace de noms, déclarer une classe et définir une méthode Main. Une application «Bonjour» sur une seule ligne peut ressembler à ceci :

  1. System.Console.WriteLine("Bonjour le monde !");

Améliorons cette application en une seule ligne pour ouvrir l'espace de noms où la classe Console est définie en premier. Avec la directive using pour importer l'espace de noms System, vous pouvez utiliser la classe Console sans la préfixer avec l'espace de noms :

  1. using System;
  2. Console.WriteLine("Bonjour le monde !");

Étant donné que WriteLine est une méthode statique de la classe Console, il est même possible d'ouvrir la classe Console avec la directive static using :

  1. using static System.Console;
  2. WriteLine("Bonjour le monde !");

Dans les coulisses, avec des instructions de niveau supérieur, le compilateur crée une classe avec une méthode Main et ajoute les instructions de niveau supérieur à la méthode Main :

  1. using System;
  2.  
  3. class Program {
  4.   static void Main() {
  5.     Console.WriteLine("Bonjour le monde !");
  6.   }
  7. }

Les variables

C# propose différentes manières de déclarer et d'initialiser des variables. Une variable possède un type et une valeur pouvant changer au fil du temps. Dans l'extrait de code suivant, la variable s1 est de type chaîne de caractères telle que définie avec la déclaration de type à gauche du nom de la variable, et elle est initialisée à un nouvel objet chaîne de caractères où le littéral de chaîne de caractères "Bonjour le monde !" est passé au constructeur. Étant donné que le type chaîne de caractères est couramment utilisé, au lieu de créer un nouvel objet chaîne de caractères, la chaîne de caractères "Bonjour le monde !" peut être directement attribuée à la variable (affichée avec la variable s2).

A partir de la version 3 du C#, le mot clef var avec inférence de type, pouvant également être utilisé pour déclarer une variable. Ici, le type est requis sur le côté droit, et le côté gauche en déduirait le type. Comme le compilateur crée un objet chaîne de caractères à partir du littéral de chaîne de caractères "Bonjour le monde", s3 est de la même manière une chaîne fortement définie et de type sûr comme s1 et s2.

A partir de la version 9 du C#, le C# fournit une autre nouvelle syntaxe pour déclarer et initialiser une variable avec l'expression new typée par la cible. Au lieu d'écrire l'expression new string("Bonjour le monde!"), si le type est connu à gauche, il suffit d'utiliser simplement l'expression new("Bonjour le monde!"); vous n'avez pas besoin de spécifier le type à droite :

  1. using System;
  2.  
  3. string s1 = new string("Bonjour le monde !");
  4. string s2 = "Bonjour le monde !";
  5. var s3 = "Bonjour le monde !";
  6. string s4 = new("Bonjour le monde !");
  7.  
  8. Console.WriteLine(s1);
  9. Console.WriteLine(s2);
  10. Console.WriteLine(s3);
  11. Console.WriteLine(s4);
  12. // ...

Remarque : Déclarer le type sur le côté gauche à l'aide du mot clef var ou de l'expression new typée par la cible est souvent une simple question de goût. En coulisses, le même code est généré. Le mot clef var est disponible depuis la version 3 de C# et a réduit la quantité de code que vous deviez écrire en définissant le type à la fois sur le côté gauche pour déclarer le type et sur le côté droit lors de l'instanciation de l'objet. Avec le mot clef var, vous n'avez besoin que du type sur le côté droit. Cependant, le mot clef var ne peut pas être utilisé avec les membres de types. Avant la version 9 du C#, vous deviez écrire le type deux fois avec les membres de classe ; maintenant, vous pouvez utiliser new typé par la cible. Le new typé par la cible peut être utilisé avec des variables locales, comme vous pouvez le voir dans l'extrait de code précédent avec la variable s4. Cela ne rend pas le mot clef var inutile ; il a toujours ses avantages, par exemple, pour recevoir des valeurs d'une méthode.

Les paramètres de la ligne de commande

Lorsque vous transmettez des valeurs à l'application lors du démarrage du programme, la variable args est automatiquement déclarée avec des instructions de niveau supérieur. Dans l'extrait de code suivant, avec l'instruction foreach, la variable args est accessible pour parcourir tous les paramètres de la ligne de commande et afficher les valeurs sur la console :

  1. using System;
  2.  
  3. foreach (var arg in args) {
  4.   Console.WriteLine(arg);
  5. }

En utilisant le CLI .NET pour exécuter l'application, vous pouvez utiliser dotnet run suivi de -- puis passer les paramètres au programme. Il faut ajouter -- pour ne pas confondre les paramètres de la CLI .NET avec ceux de l'application :

dotnet run -- one two three

Lorsque vous exécutez cette commande, vous voyez les chaînes de caractères un deux trois sur la console.

Lorsque vous créez une méthode Main personnalisée, la méthode doit être déclarée pour recevoir un tableau de chaînes de caractères. Vous pouvez choisir un nom pour la variable, mais la variable nommée args est couramment utilisée, c'est pourquoi ce nom a été sélectionné pour la variable générée automatiquement avec des instructions de niveau supérieur :

  1. using System;
  2.  
  3. class Program {
  4.   static void Main(string[] args) {
  5.     foreach (var arg in args) {
  6.       Console.WriteLine(arg);
  7.     }
  8.   }
  9. }

Comprendre la portée des variables

La portée d'une variable est la région de code à partir de laquelle la variable est accessible. En général, la portée est déterminée par les règles suivantes :

Il est courant dans un programme volumineux d'utiliser le même nom de variable pour différentes variables dans différentes parties du programme. Cela ne pose aucun problème tant que les variables sont limitées à des parties complètement différentes du programme afin qu'il n'y ait aucune possibilité d'ambiguïté. Cependant, gardez à l'esprit que les variables locales portant le même nom ne peuvent pas être déclarées deux fois dans la même portée. Par exemple, vous ne pouvez pas faire ceci :

  1. int x = 20;
  2. // un peu plus de code
  3. int x = 30;

Considérez l'exemple de code suivant :

  1. using System;
  2.  
  3. for (int i = 0; i < 6; i++) {
  4.   Console.WriteLine(i);
  5. } // Il sort du cadre ici
  6.  
  7. /* Nous pouvons à nouveau déclarer une variable nommée i, car il n'y a aucune autre variable portant ce nom dans la portée */
  8. for (int i = 5; i>= 0; i--) {
  9.   Console.WriteLine(i);
  10. } // Il sort du cadre ici

Ce code affiche simplement les nombres de 0 à 5, puis de 5 à 0, en utilisant deux boucles for. Il est important de noter que vous déclarez la variable i deux fois dans ce code, dans la même méthode. Vous pouvez le faire car i est déclarée dans deux boucles distinctes, donc chaque variable i est locale à sa propre boucle.

Voici un autre exemple :

  1. int j = 15;
  2. for (int i = 0; i < 10; i++) {
  3.   int j = 20; // Impossible de faire cela - j est toujours dans le champ d'application
  4.   Console.WriteLine(j + i);
  5. }

Si vous essayez de compiler ceci, vous obtenez une erreur comme celle-ci :

error CS0136: A local or parameter named 'j' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter

Cela se produit parce que la variable j, étant définie avant le début de la boucle for, est toujours dans la portée de la boucle for et ne sortira pas de la portée tant que la méthode Main (étant créée à partir du compilateur) n'aura pas fini de s'exécuter. Le compilateur n'a aucun moyen de faire la distinction entre ces deux variables, il ne permettra donc pas à la seconde d'être déclarée.

Il n'est même pas utile de placer la variable j déclarée en dehors de la boucle for après la fin de la boucle for. Le compilateur déplace toutes les déclarations de variables au début d'une portée, quel que soit l'endroit où vous la déclarez.

Cela se produit parce que la variable j, étant définie avant le début de la boucle for, est toujours dans la portée de la boucle for et ne sortira pas de la portée tant que la méthode Main (étant créée à partir du compilateur) n'aura pas fini de s'exécuter. Le compilateur n'a aucun moyen de faire la distinction entre ces deux variables, il ne permettra donc pas à la seconde d'être déclarée.

Il n'est même pas utile de placer la variable j déclarée en dehors de la boucle for après la fin de la boucle for. Le compilateur déplace toutes les déclarations de variables au début d'une portée, quel que soit l'endroit où vous la déclarez.

Les constantes

Pour les valeurs ne changeant jamais, vous pouvez définir une constante. Pour les valeurs constantes, vous pouvez utiliser le mot-clef const.

Avec les variables déclarées avec le mot-clef const, le compilateur remplace la variable à chaque occurrence par la valeur spécifiée avec la constante.

Une constante est spécifiée avec le mot-clef const avant le type :

  1. const int a = 1974; // Cette valeur ne peut pas être modifiée.

Le compilateur remplace chaque occurrence du champ local par la valeur. Ce comportement est important en termes de gestion des versions. Si vous déclarez une constante avec une bibliothèque et utilisez la constante à partir d'une application, l'application doit être recompilée pour obtenir la nouvelle valeur ; sinon, la bibliothèque pourrait avoir une valeur différente de celle de l'application. Pour cette raison, il est préférable d'utiliser const uniquement avec des valeurs ne changeant jamais, même dans les versions futures.

Les constantes ont les caractéristiques suivantes :

Voici les avantages de l'utilisation de constantes dans vos programmes :

Méthodes et types avec instructions de niveau supérieur

Vous pouvez également ajouter des méthodes et des types au même fichier avec des instructions de niveau supérieur. Dans l'extrait de code suivant, la méthode nommée Method est définie et appelée après la déclaration et l'implémentation de la méthode :

  1. //...
  2. void Method() {
  3.  Console.WriteLine("C'est une méthode");
  4. }
  5.  
  6. Method();
  7. //...

La méthode peut être déclarée avant ou après son utilisation. Des types peuvent être ajoutés au même fichier, mais ceux-ci doivent être spécifiés après les instructions de niveau supérieur. Avec l'extrait de code suivant, la classe Book est spécifiée pour contenir une propriété Title et la méthode ToString. Avant la déclaration du type, une nouvelle instance est créée et affectée à la variable b1, la valeur de la propriété Title est définie et l'instance est écrite dans la console. Lorsque l'objet est passé en argument à la méthode WriteLine, la méthode ToString de la classe Book est à son tour appelée :

  1. Book b1 = new();
  2. b1.Title = "La Bible";
  3. Console.WriteLine(b1);
  4.  
  5. class Book {
  6.   public string Title { get; set; }
  7.   public override string ToString() => Title;
  8. }

Types nullables

Avec la première version de C#, un type de valeur ne pouvait pas avoir de valeur null, mais il était toujours possible d'attribuer null à un type de référence. Le premier changement s'est produit avec la version 2 de C# et l'invention du type de valeur nullable. La version 8 de C# a apporté un changement avec les types de référence car la plupart des exceptions se produisant avec .NET sont de type NullReferenceException. Ces exceptions se produisent lorsqu'un membre d'une référence est appelé et que null est attribué. Pour réduire ces problèmes et obtenir des erreurs de compilation à la place, des types de référence nullables ont été introduits avec la version 8 de C#.

Cette section couvre à la fois les types de valeurs nullables et les types de référence nullables. La syntaxe semble similaire, mais elle est très différente en coulisses.

Types de valeur nullables

Avec un type de valeur tel que int, vous ne pouvez pas lui attribuer null. Cela peut entraîner des difficultés lors de la cartographie vers des bases de données ou d'autres sources de données, telles que XML ou JSON. L'utilisation d'un type de référence entraîne à la place une surcharge supplémentaire : un objet est entreposé dans le tas et le ramasse-miettes doit le nettoyer lorsqu'il n'est plus utilisé. Au lieu de cela, le «?» peut être utilisé avec la définition de type, ce qui permet d'attribuer null :

  1. int? a1 = null;

Le compilateur modifie cela pour utiliser le type Nullable<T> :

  1. Nullable<int> a1 = null;

Nullable<T> n'ajoute pas la surcharge d'un type de référence. Il s'agit toujours d'une structure (un type de valeur) mais ajoute un indicateur booléen pour spécifier si la valeur est nulle.

L'extrait de code suivant illustre l'utilisation de types de valeur nullables et l'attribution de valeurs non nullables. La variable n1 est un int nullable auquel la valeur null a été attribuée. Un type de valeur nullable définit la propriété HasValue, qui peut être utilisée pour vérifier si une valeur est attribuée à la variable. Avec la propriété Value, vous pouvez accéder à sa valeur. Cela peut être utilisé pour attribuer la valeur à un type de valeur non nullable. Une valeur non nullable peut toujours être attribuée à un type de valeur nullable ; cela réussit toujours :

  1. int? x1 = null;
  2. if (x1.HasValue) {
  3.   int x2 = x1.Value;
  4. }
  5. int x3 = 42;
  6. int? x4 = x3;

Types de référence Nullable

Les types de référence Nullable ont pour objectif de réduire les exceptions de type NullReferenceException, étant l'exception la plus courante se produisant avec les applications .NET. Il a toujours été conseillé qu'une application ne génère pas de telles exceptions et qu'elle vérifie toujours la présence de valeurs null, mais sans l'aide du compilateur, de tels problèmes peuvent passer inaperçus trop facilement.

Pour obtenir de l'aide du compilateur, vous devez activer les types de référence nullables. Étant donné que cette fonctionnalité présente des modifications importantes par rapport au code existant, vous devez l'activer explicitement. Vous spécifiez l'élément Nullable et définissez la valeur d'activation dans le fichier projet :

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

Désormais, null ne peut pas être attribué aux types de référence. Lorsque vous écrivez ce code avec nullable activé :

  1. string str1 = null; // avertissement du compilateur

vous obtenez l'avertissement du compilateur :

CS8600: Converting a null literal or a possible null value to non-nullable type.

Pour attribuer la valeur null à la chaîne de caractères, le type doit être déclaré avec un point d'interrogation, comme les types de valeur nullables :

  1. string? str1 = null;

Lorsque vous utilisez la variable str1 nullable, vous devez vous assurer de vérifier qu'elle n'est pas nulle avant d'appeler des méthodes ou de l'affecter à des chaînes de caractères non nullables ; sinon, des avertissements du compilateur sont générés :

  1. string str2 = str1.ToUpper(); // Avertissement du compilateur

Au lieu de cela, vous pouvez vérifier la valeur null avant d'appeler la méthode avec l'opérateur conditionnel «null ?», appelant la méthode uniquement si l'objet n'est pas null. Le résultat ne peut pas être écrit dans une chaîne non nullable. Le résultat de l'expression de droite peut être null si s1 est null :

  1. string? str2 = str1?.ToUpper();

Vous pouvez utiliser l'opérateur de fusion ?? pour définir une valeur de retour différente dans le cas de null. Avec l'extrait de code suivant, une chaîne de caractères vide est renvoyée au cas où l'expression à gauche de ?? renvoie null. Le résultat complet de l'expression de droite est maintenant écrit dans la variable str3, ne pouvant jamais être nulle. Il s'agit soit de la version majuscule de la chaîne de caractères str1 si str1 n'est pas null, soit d'une chaîne de caractères vide si str1 est null :

  1. string str3 = str1?.ToUpper() ?? string.Empty;

Au lieu d'utiliser ces opérateurs, vous pouvez également utiliser l'instruction if pour vérifier si une variable n'est pas nulle. Avec l'instruction if dans l'extrait de code suivant, le modèle C# n'est pas utilisé pour vérifier que s1 n'est pas nul. Le bloc couvert par l'instruction if n'est appelé que lorsque s1 n'est pas nul. Ici, il n'est pas nécessaire d'utiliser l'opérateur conditionnel null pour appeler la méthode ToUpper :

  1. if (str1 is not null) {
  2.   string str4 = str1.ToUpper();
  3. }

Bien sûr, il est également possible d'utiliser l'opérateur différent != :

  1. if (str1 != null) {
  2.   string str5 = str1.ToUpper();
  3. }

L'utilisation de types de référence nullables est également importante avec les membres de types, comme illustré dans la classe Book avec les propriétés Titre et Editeur dans l'extrait de code suivant. Le titre est déclaré avec un type de chaîne non nullable ; il doit donc être initialisé lors de la création d'un nouvel objet de la classe Livre. Il est initialisé avec le constructeur de la classe Livre. La propriété Editeur peut être null, elle n'a donc pas besoin d'être initialisée :

  1. class Livre {
  2.   public Livre(string titre) => Titre = titre;
  3.  
  4.   public string Titre { get; set; }
  5.   public string? Editeur { get; set; }
  6. }

Lorsque vous déclarez une variable de la classe Livre, la variable peut être déclarée comme nullable (b1), ou elle a besoin d'un objet Livre avec la déclaration utilisant le constructeur (b2). La propriété Titre peut être affectée à un type de chaîne non nullable. Avec la propriété Editeur, vous pouvez l'affecter à une chaîne nullable ou utiliser les opérateurs comme indiqué précédemment :

  1. Livre? b1 = null;
  2. Livre b2 = new Livre("La Bible");
  3. string titre = b2.Titre;
  4. string? editeur = b2.Editeur;

Dans les coulisses avec les types de valeur nullables, le type Nullable<T> est utilisé en arrière-plan. Ce n'est pas le cas avec les types de référence nullables. Au lieu de cela, le compilateur ajoute une annotation aux types. Les types de référence nullables ont des attributs Nullables associés. Avec cela, les types de référence nullables peuvent être utilisés avec des bibliothèques pour annoter des paramètres et des membres avec nullabilité. Lorsque la bibliothèque est utilisée avec de nouvelles applications, IntelliSense peut fournir des informations indiquant si une méthode ou une propriété peut être null, et le compilateur agit en conséquence avec des avertissements du compilateur. En utilisant une ancienne version du compilateur (antérieure à la version 8 de C#), la bibliothèque peut toujours être utilisée de la même manière que les bibliothèques non annotées. Le compilateur ignore simplement les attributs qu'il ne connaît pas.

Utilisation de types prédéfinis

Maintenant que vous avez vu comment déclarer des variables et des constantes et que vous connaissez une amélioration extrêmement importante avec la nullité, examinons de plus près les types de données disponibles en C#.

Les mots-clefs C# pour les types de données, tels que int, short et string, sont cartographiés du compilateur aux types de données .NET. Par exemple, lorsque vous déclarez un int en C#, vous déclarez en fait une instance d'une structure .NET : System.Int32. Tous les types de données primitifs proposent des méthodes pouvant être invoquées. Par exemple, pour convertir un int i en chaîne de caractères, vous pouvez écrire ce qui suit :

  1. string str = i.ToString();

Nous tenons à souligner que derrière cette commodité syntaxique, les types sont réellement entreposés en tant que types primitifs, donc aucun coût de performance n'est associé à l'idée que les types primitifs sont représentés par des structures .NET.

Les sections suivantes passent en revue les types reconnus comme types intégrés dans C#. Chaque type est répertorié avec sa définition et le nom du type .NET correspondant. Je vous montre également quelques exceptions : certains types de données importants qui ne sont disponibles qu'avec leur type .NET et n'ont pas de mot-clef C# spécifique.

Commençons par les types de valeurs prédéfinis qui représentent des primitives, telles que les entiers, les nombres à virgule flottante, les caractères et les booléens.

Types d'entiers

C# prend en charge les types d'entiers avec différents nombres de bits utilisés et diffère entre les types ne prenant en charge que les valeurs positives ou les types avec une plage de valeurs négatives et positives. Huit bits sont utilisés par les types byte et sbyte. Le type byte autorise les valeurs de 0 à 255 (uniquement les valeurs positives), tandis que le s dans sbyte signifie utiliser un signe ; ce type prend en charge les valeurs de -128 à 127, ce qui est possible avec 8 bits.

Les types short et ushort utilisent 16 bits. Le type short couvre la plage de -32 768 à 32 767. Avec le type ushort, le u correspond à unsigned et couvre l'intervalle de 0 à 65 535. De même, le type int est un entier signé de 32 bits et le type uint est un entier non signé de 32 bits. long et ulong ont 64 bits disponibles. En coulisses, les mots-clefs C# sbyte, short, int et long correspondent à System.SByte, System.Int16, System.Int32 et System.Int64. Les versions non signées correspondent à System.Byte, System.UInt16, System.UInt32 et System.UInt64. Les types .NET sous-jacents répertorient clairement le nombre de bits utilisés dans le nom du type.

Pour vérifier les valeurs maximales et minimales du type, vous pouvez utiliser les propriétés MaxValue et MinValue.

Voici un tableau résumant des types de données d'entier :

Type Taille (en bits) Intervalle
sbyte 8 -128 à 127
byte 8 0 à 255
short 16 -32,768 à 32,767
ushort 16 0 à 65 535
int 32 -2 147 483 648 à 2 147 483 647
uint 32 0 à 4 294 967 295
long 64 -9 223 372 036 854 775 808 à 9 223 372 036 854 775 807
ulong 64 0 à 18 446 744 073 709 551 615

Big Integer (gros entier)

Si vous avez besoin d'une représentation numérique dont la valeur est supérieure aux 64 bits disponibles dans le type long, vous pouvez utiliser le type BigInteger. Cette structure n'a pas de limite sur le nombre de bits et peut croître jusqu'à ce qu'il n'y ait plus assez de mémoire disponible. Il n'existe pas de mot-clef C# spécifique pour ce type et vous devez utiliser BigInteger. Étant donné que ce type peut croître à l'infini, les propriétés MinValue et MaxValue ne sont pas disponibles. Ce type offre des méthodes intégrées pour le calcul telles que Add, Subtract, Divide, Multiply, Log, Log10, Pow et autres.

Types d'entiers natifs

Avec int, short et long, le nombre de bits et les tailles disponibles sont indépendants si l'application est une application 32 ou 64 bits. Cela diffère des définitions d'entiers telles que définies avec C++. La version 9 de C# a de nouveaux mots-clefs pour les valeurs spécifiques à la plate-forme : nint et nuint (entier natif et entier non signé natif, respectivement). Dans une application 64 bits, ces types d'entiers utilisent 64 bits, tandis que dans une application 32 bits, seuls 32 bits sont utilisés. Ces types sont importants avec l'accès direct à la mémoire.

Séparateurs de chiffres

Pour une meilleure lisibilité des nombres, vous pouvez utiliser des séparateurs de chiffres. Vous pouvez ajouter des traits de soulignement aux nombres, comme indiqué dans l'extrait de code suivant. Dans cet extrait de code, le préfixe 0x est également utilisé pour spécifier des valeurs hexadécimales :

  1. long long1 = 0x_987_6543_21fe_dcba;

Les traits de soulignement utilisés comme séparateurs sont simplement ignorés par le compilateur. Ces séparateurs contribuent à la lisibilité et n'ajoutent aucune fonctionnalité. Avec l'exemple précédent, en lisant à partir de la droite, un séparateur de chiffres est ajouté tous les 16 bits (ou 4 caractères hexadécimaux). C'est beaucoup plus lisible que ceci :

  1. long long2 = 0xfedcba987654321;

Bien sûr, comme le compilateur ignore les traits de soulignement, vous êtes vous-même responsable de la lisibilité. Vous pouvez placer les traits de soulignement à n'importe quelle position, ce qui n'aidera peut-être pas vraiment à la lisibilité :

  1. long long3 = 0x_fedcba_98765_432_1;

Il est utile que n'importe quelle position puisse être utilisée, ce qui permet différents cas d'utilisation tels que travailler avec des valeurs hexadécimales ou octales ou séparer différents bits nécessaires à un protocole, comme indiqué dans la section suivante.

Valeurs binaires

Outre les séparateurs de chiffres, C# permet également d'attribuer facilement des valeurs binaires aux types entiers. En utilisant le littéral 0b, il est uniquement autorisé d'attribuer des valeurs de 0 et 1, comme suit :

  1. uint binary1 = 0b_0001_0010_0011_0100_0101_0110_0111_1000;

L'extrait de code précédent utilise un entier non signé avec 32 bits disponibles. Les séparateurs de chiffres facilitent la lisibilité lors de l'utilisation de valeurs binaires. Cet extrait effectue une séparation tous les 4 bits. N'oubliez pas que vous pouvez également écrire ceci en notation hexadécimale :

  1. uint hexadecimal1 = 0x12345678;

L'utilisation du séparateur tous les 3 bits permet de travailler avec la notation octale, où les caractères sont utilisés entre 0 (000 binaire) et 7 (111 binaire) :

  1. uint binary2 = 0b_001_010_100_101_110_111_000_001;

Si vous devez définir un protocole binaire (par exemple, où 3 bits définissent la partie la plus à droite suivie de 5 bits dans la section suivante, et deux fois 4 bits pour compléter 16 bits), vous pouvez mettre des séparateurs selon ce protocole :

  1. ushort binary3 = 0b1111_0000_11111_000;

Types à virgule flottante

Le C# spécifie également des types à virgule flottante avec différents nombres de bits basés sur la norme IEEE 754. Le type Half (nouveau à partir de .NET 5) utilise 16 bits, float (Single avec .NET) utilise 32 bits et double (Double) utilise 64 bits. Avec tous ces types de données, 1 bit est utilisé pour le signe.

Selon le type, 10 à 52 bits sont utilisés pour la mantisse et 5 à 11 bits pour l'exposant. Le tableau suivant montre les détails :

Mot clef C# Type .NET Description Bit de mantisse Bit d'exposant
  System.Half 16 bits, virgule flottante simple précision 10 5
float System.Single 32 bits, virgule flottante simple précision 23 8
double System.Double 64 bits, virgule flottante double précision 52 11

Lorsque vous attribuez une valeur, si vous codez en dur un nombre non entier (comme 56,7), le compilateur suppose qu'il s'agit d'un double. Pour spécifier que la valeur est un float, ajoutez le caractère F (ou f) :

  1. float f = 56.7F;

Avec le type décimal (.NET struct Decimal), .NET dispose d'un type à virgule flottante de haute précision utilisant 128 bits et peut être utilisé pour les calculs financiers. Avec les 128 bits, 1 est utilisé pour le signe et 96 pour le nombre entier. Les bits restants spécifient un facteur d'échelle. Pour spécifier que votre nombre est de type décimal plutôt qu'un double, un float ou un entier, vous pouvez ajouter le caractère M (ou m) à la valeur :

  1. decimal d = 12.34M;

Le type Boolean

Vous utilisez le type booléen C# pour contenir des valeurs booléennes true ou false. Vous ne pouvez pas convertir implicitement des valeurs booléennes en et depuis des valeurs entières. Si une variable (ou un type de retour de fonction) est déclarée comme bool, vous ne pouvez utiliser que les valeurs true et false. Vous obtenez une erreur si vous essayez d'utiliser zéro pour false et une valeur différente de zéro pour true.

Le type caractère

La chaîne de caractères .NET se compose de caractères à deux octets. Le mot clef char de C# correspond au type Char de .NET. L'utilisation de guillemets simples, par exemple, 'A', crée un char. Avec des guillemets doubles, une chaîne de caractères est créée.

En plus de représenter les caractères comme des littéraux de caractères, vous pouvez les représenter avec des valeurs Unicode hexadécimales à quatre chiffres (par exemple, '\u0041'), des valeurs entières avec un cast (par exemple, (char)65) ou des valeurs hexadécimales (par exemple, '\x0041'). Vous pouvez également les représenter avec une séquence d'échappement, comme indiqué dans le tableau suivant :

Séquence d'échappement Caractère
\' Guillemet simple
\" Guillemets doubles
\\ Barre oblique inverse
\0 Nulle
\a Alerte
\b Retour arrière (BackSpace)
\f Saut de page
\n Nouvelle ligne
\r Retour de chariot
\t Caractère Tab
\v Caractères de tabulation vertical.

Littéraux pour les nombres

Dans les sections précédentes, des littéraux ont été présentés pour les valeurs numériques. Résumons-les ici dans le tableau suivant :

Littéral Position Description
U Postfix unsigned int
L Postfix long
UL Postfix unsigned long
F Postfix float
M Postfix decimal (argent)
0x Prefix Nombre hexadécimal ; les valeurs de 0 à F sont autorisées
0b Prefix Nombre binaire ; seuls 0 et 1 sont autorisés
true NA Valeur booléenne
false NA Valeur booléenne

Le type d'objet

Outre les types de valeur, avec les mots-clefs C#, deux types de référence sont définis : le mot-clef object correspondant à la classe Object et le mot clef string correspondant à la classe String. La classe Object est la classe de base ultime de tous les types de référence et peut être utilisée à deux fins :

Contrôler le déroulement du programme

Cette section aborde les véritables rouages du langage : les instructions vous permettant de contrôler le déroulement de votre programme plutôt que d'exécuter chaque ligne de code dans l'ordre dans lequel elle apparaît dans le programme. Avec des instructions conditionnelles telles que les instructions if et switch, vous pouvez créer des branches dans votre code en fonction du respect de certaines conditions. Vous pouvez répéter des instructions dans des boucles avec des instructions for, while et foreach. L'instruction if permet de contrôler le déroulement de votre programme plutôt que d'exécuter chaque ligne de code dans l'ordre dans lequel elle apparaît dans le programme. Avec des instructions conditionnelles telles que les instructions if et switch, vous pouvez créer des branches dans votre code en fonction du respect de certaines conditions. Vous pouvez répéter des instructions dans des boucles avec des instructions for, while et foreach.

L'instruction if

Avec l'instruction if, vous pouvez spécifier une expression entre parenthèses. Si l'expression renvoie true, le bloc spécifié avec des accolades est appelé. Si la condition n'est pas vraie, vous pouvez vérifier si une autre condition est vraie en utilisant else if. L'instruction else if peut être répétée pour vérifier d'autres conditions. Si ni les expressions spécifiées avec if ni toutes les expressions else if ne sont évaluées à true, le bloc spécifié avec le bloc else est appelé.

Avec l'extrait de code suivant, une chaîne de caractères est lue à partir de la console. Si une chaîne vide est saisie, le bloc de code suivant l'instruction if est appelé. La méthode de chaîne de caractères IsNullOrEmpty renvoie true si la chaîne de caractères est nulle ou vide. Le bloc spécifié avec l'instruction else if est appelé lorsque la longueur de l'entrée est inférieure à cinq caractères. Dans tous les autres cas, par exemple, avec une longueur d'entrée de cinq caractères ou plus, le bloc else est appelé :

  1. Console.WriteLine("Le type de données est un string");
  2. string? inputStr = Console.ReadLine();
  3.  
  4. if (string.IsNullOrEmpty(inputStr)) {
  5.   Console.WriteLine("Vous avez tapé une chaîne de caractères vide.");
  6. } else if (inputStr?.Length < 5) {
  7.   Console.WriteLine("La chaîne de caractères contenait moins de 5 caractères.");
  8. } else {
  9.   Console.WriteLine("Lire n'importe quelle autre chaîne de caractères.");
  10. }
  11. Console.WriteLine("La chaîne de caractères était " + inputStr);

Remarque : S'il n'y a qu'une seule instruction avec les blocs if / else if / else, les accolades ne sont pas nécessaires. Elles ne sont nécessaires qu'avec plusieurs instructions. Cependant, les accolades facilitent également la lisibilité des lignes de code uniques.

Avec l'instruction if, else if et else sont facultatifs. Si vous avez simplement besoin d'appeler un bloc de code en fonction d'une condition et que vous n'appelez pas de bloc de code si cette condition n'est pas remplie, vous pouvez utiliser if sans else.

Correspondance de motifs avec l'opérateur is

L'une des fonctionnalités de C# est la correspondance de motifs, que vous pouvez utiliser avec l'instruction if et l'opérateur is. La section précédente «Types de référence nullables» incluait un exemple utilisant une instruction if et le motif n'est pas nul.

L'extrait de code suivant compare le paramètre reçu de type objet avec null, en utilisant un modèle const pour comparer le paramètre avec null et lever l'exception ArgumentNullException. Avec l'expression utilisée dans else if, le modèle de type est utilisé pour vérifier si la variable o est de type Livre. Si tel est le cas, la variable o est affectée à la variable b. Étant donné que la variable b est de type Livre, avec b, la propriété Titre spécifiée par le type Livre est accessible :

  1. void PatternMatching(object o) {
  2.   if (o is null) throw new ArgumentNullException(nameof(o));
  3.   else if (o is Livre b)
  4.   {
  5.     Console.WriteLine($"A reçu un livre: {b.Titre}");
  6.   }
  7. }

Remarque : Dans cet exemple, pour générer l'exception ArgumentNullException, l'expression nameof est utilisée. L'expression nameof est résolue à partir du compilateur pour prendre le nom du paramètre (par exemple, la variable obj) et le transmettre sous forme de chaîne de caractères. throw new ArgumentNullException(nameof(obj)); renvoie le même code que throw new ArgumentNullException("obj"); . Cependant, si la variable obj est renommée avec une valeur différente, les fonctionnalités de refactorisation peuvent renommer automatiquement la variable spécifiée avec l'expression nameof. Si le paramètre de nameof n'est pas modifié lorsque la variable est renommée, une erreur du compilateur en résultera. Sans l'expression nameof, la variable et la chaîne peuvent facilement se désynchroniser.

Quelques exemples supplémentaires de modèles const et type sont présentés dans l'extrait de code suivant :

  1. if (obj is 42) // modèle constant
  2. if (obj is "42") // modèle constant
  3. if (obj is int i) // modèle de type

L'instruction switch

L'instruction switch / case est utile pour sélectionner une branche d'exécution parmi un ensemble de branches mutuellement exclusives. Elle prend la forme d'un paramètre switch suivi d'une série de clauses case. Lorsque l'expression dans le paramètre switch correspond à l'une des valeurs spécifiées par une clause case, le code suivant immédiatement la clause case s'exécute. Il s'agit d'un exemple pour lequel vous n'avez pas besoin d'utiliser des accolades pour joindre des instructions en blocs ; à la place, vous marquez la fin du code pour chaque cas à l'aide de l'instruction break. Vous pouvez également inclure un cas par défaut dans l'instruction switch, s'exécutant si l'expression ne correspond à aucun des autres cas. L'instruction switch suivante teste la valeur de la variable x :

  1. void SwitchSample(int x) {
  2.  switch (x) {
  3.     case 1:
  4.       Console.WriteLine("L'entier x = 1");
  5.       break;
  6.     case 2:
  7.       Console.WriteLine("L'entier x = 2");
  8.       break;
  9.     case 3:
  10.       Console.WriteLine("L'entier x = 3");
  11.       break;
  12.     default:
  13.       Console.WriteLine("L'entier x n'est pas 1, 2 ou 3");
  14.       break;
  15.   }
  16. }

Avec l'instruction switch, vous ne pouvez pas supprimer la rupture des différents cas. Contrairement aux langages de programmation C++ et Java, avec C#, le basculement automatique d'une implémentation de cas à une autre n'est pas effectué. Au lieu d'un basculement automatique, vous pouvez utiliser le mot-clef goto pour un basculement explicite et sélectionner un autre cas. Voici un exemple :

  1. goto case 3; 

Si l'implémentation est complètement la même avec plusieurs cas, vous pouvez spécifier plusieurs cas avant de spécifier une implémentation :

  1. switch(country)
  2. {
  3.   case "fr":
  4.     language = "Français";
  5.     break;  
  6.   case "au":
  7.   case "uk":
  8.   case "us":
  9.     language = "Anglais";
  10.     break;
  11.   case "at":
  12.   case "de":
  13.     language = "Allemand";
  14.     break;
  15. }

Correspondance de motifs avec l'instruction switch

La correspondance de motifs peut également être utilisée avec l'instruction switch. L'extrait de code suivant montre différentes options de cas avec const et type, ainsi que des modèles relationnels. La méthode SwitchWithPatternMatching reçoit un paramètre de type objet. case null est un modèle const comparant o pour null. Les trois cas suivants spécifient un modèle de type. case int i utilise un modèle de type créant la variable i si o est un int, mais uniquement en combinaison avec la clause when. La clause when utilise un modèle relationnel pour vérifier s'il est supérieur à 42. Le cas suivant correspond à tous les types int restants. Ici, aucune variable n'est spécifiée à l'endroit où l'objet o doit être affecté. La spécification d'une variable n'est pas nécessaire si vous n'avez pas besoin de cette variable et que vous avez juste besoin de savoir qu'elle est de ce type. Avec la correspondance pour un type Livre, la variable b est utilisée. En déclarant une variable ici, cette variable est de type Livre :

  1. void SwitchWithPatternMatching(object o) {
  2.   switch (o) {
  3.     case null:
  4.       Console.WriteLine("Modèle const avec null");
  5.       break;
  6.     case int i when i> 42
  7.       Console.WriteLine("Modèle de type avec quand et un modèle relationnel");   
  8.     case int:
  9.       Console.WriteLine("Modèle de type avec un int");
  10.       break;
  11.     case Book b:
  12.       Console.WriteLine($"Modèle de type avec un livre {b.Title}");
  13.       break;
  14.     default: 
  15.        break;
  16.   }
  17. }

L'expression switch

L'exemple suivant montre un switch basé sur un type enum. Le type enum est basé sur un entier mais donne des noms aux différentes valeurs. Le type FeuDeCirculation définit les différentes valeurs pour les couleurs d'un feu de circulation :

  1. enum FeuDeCirculation {
  2.   Rouge,
  3.   Jaune,
  4.   Vert
  5. }

Jusqu'à présent, avec l'instruction switch, vous n'avez vu que l'appel de certaines actions dans chaque cas. Lorsque vous utilisez l'instruction return pour effectuer un retour à partir d'une méthode, vous pouvez également renvoyer directement une valeur à partir du cas sans continuer avec les cas suivants. La méthode ProchainFeuDeCirculationClassique reçoit un FeuDeCirculation avec son paramètre et renvoie un FeuDeCirculation. Si le feu de circulation transmis a la valeur FeuDeCirculation.Vert, la méthode renvoie FeuDeCirculation.Jaune. Lorsque la valeur du feu actuel est FeuDeCirculation.Jaune, FeuDeCirculation.Rouge est renvoyé :

  1. FeuDeCirculation ProchainFeuDeCirculationClassique(FeuDeCirculation lumiere) {
  2.   switch (lumiere) {
  3.     case FeuDeCirculation.Vert:
  4.       return FeuDeCirculation.Jaune;
  5.     case FeuDeCirculation.Jaune:
  6.       return FeuDeCirculation.Rouge;
  7.     case FeuDeCirculation.Rouge:
  8.       return FeuDeCirculation.Vert;
  9.     default:
  10.        throw new InvalidOperationException();            
  11.   }
  12. }

Dans un tel scénario, si vous devez renvoyer une valeur basée sur différentes options, vous pouvez utiliser l'expression switch étant nouvelle à partir de C# 8. La méthode NextLight reçoit et renvoie une valeur FeuDeCirculation similaire à la méthode précédemment présentée. L'implémentation est maintenant effectuée avec un membre d'expression car l'implémentation est effectuée dans une seule instruction. Les accolades et l'instruction return sont inutiles dans ce cas. Lorsque vous utilisez une expression switch au lieu de l'instruction switch, la variable et le mot-clef switch sont inversés. Avec l'instruction switch, la valeur sur le commutateur suit entre accolades le mot-clef switch. Avec l'expression switch, la variable est suivie du mot-clef switch. Un bloc avec des accolades définit les différents cas. Au lieu d'utiliser le mot-clef case, le jeton => est utilisé pour définir ce qui est renvoyé. La fonctionnalité est la même qu'avant, mais vous avez besoin de moins de lignes de code :

  1. FeuDeCirculation ProchainFeuDeCirculation(FeuDeCirculation lumiere) =>
  2.   lumiere switch
  3.   {
  4.     FeuDeCirculation.Vert => FeuDeCirculation.Jaune,
  5.     FeuDeCirculation.Jaune => FeuDeCirculation.Rouge,
  6.     FeuDeCirculation.Rouge => FeuDeCirculation.Vert,
  7.     _ => throw new InvalidOperationException()
  8.   };

Si le type d'énumération FeuDeCirculation est importé avec la directive statique using, vous pouvez simplifier encore plus l'implémentation en utilisant simplement les définitions de valeur d'énumération sans le nom de type :

  1. using static FeuDeCirculation;
  2.  
  3. FeuDeCirculation ProchainFeuDeCirculation(FeuDeCirculation lumiere) =>
  4.   lumiere switch
  5.   {
  6.     Vert => Jaune,
  7.     Jaune => Rouge,
  8.     Rouge => Vert,
  9.     _ => throw new InvalidOperationException()
  10.   };

Dans l'exemple suivant, un combinateur de motifs est utilisé pour combiner plusieurs motifs. Tout d'abord, l'entrée est récupérée à partir de la console. Si la chaîne de caractères 1 ou 2 est saisie, la même correspondance s'applique, en utilisant le modèle de combinateur :

  1. string? input = Console.ReadLine();
  2.  
  3. string result = input switch {
  4.   "un" => "L'entrée a la valeur un",
  5.   "deux" or "trois" => "L'entrée a la valeur deux ou trois",
  6.   _ => "Toute autre valeur"
  7. };

Avec les combinateurs de motifs, vous pouvez combiner des motifs en utilisant les mots-clefs and, or et not.

La boucle for

Le C# propose quatre boucles différentes (for, while, do - while et foreach) vous permettant d'exécuter un bloc de code de manière répétée jusqu'à ce qu'une certaine condition soit remplie. Avec le mot-clef for, vous parcourez une boucle dans laquelle vous testez si une condition particulière est vraie avant d'effectuer une autre itération :

  1. for (int i = 0; i < 16; i++) {
  2.   Console.WriteLine(i);
  3. }

La première expression de l'instruction for est l'initialiseur. Elle est évaluée avant l'exécution de la première boucle. En général, vous l'utilisez pour initialiser une variable locale en tant que compteur de boucle.

La deuxième expression est la condition. Elle est vérifiée avant chaque itération du bloc for. Si cette expression est évaluée à vrai, le bloc est exécuté. Si elle est évaluée à faux, l'instruction for se termine et le programme continue avec l'instruction suivante après l'accolade fermante du corps for.

Une fois le corps exécuté, la troisième expression, l'itérateur, est évaluée. En général, vous incrémentez le compteur de boucle. Avec i++, une valeur de 1 est ajoutée à la variable i. Après la troisième expression, l'expression de condition est à nouveau évaluée pour vérifier si une autre itération avec le bloc for doit être effectuée.

La boucle for est une boucle dite de pré-test car la condition de boucle est évaluée avant l'exécution des instructions de boucle ; par conséquent, le contenu de la boucle ne sera pas exécuté du tout si la condition de boucle est fausse.

Il n'est pas inhabituel d'imbriquer des boucles for de sorte qu'une boucle interne s'exécute une fois complètement pour chaque itération d'une boucle externe. Cette approche est généralement utilisée pour parcourir chaque élément d'un tableau multidimensionnel rectangulaire. La boucle la plus externe parcourt chaque ligne et la boucle interne parcourt chaque colonne d'une ligne particulière. Le code suivant affiche des lignes de nombres. Il utilise également une autre méthode de la console, Console.Write, faisant la même chose que Console.WriteLine mais n'envoie pas de retour chariot à la sortie :

  1. // Cette boucle parcourt les lignes
  2. for (int i = 0; i < 80; i += 10) {
  3.   // Cette boucle parcourt les colonnes
  4.   for (int j = i; j < i + 10; j++) Console.Write($" {j}");
  5.   Console.WriteLine();
  6. }

Cet exemple génère ce résultat :

0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59
60 61 62 63 64 65 66 67 68 69
70 71 72 73 74 75 76 77 78 79

Il est techniquement possible d'évaluer autre chose qu'une variable de compteur dans la condition de test d'une boucle for, mais ce n'est certainement pas courant. Il est également possible d'omettre une (ou même toutes) des expressions de la boucle for. Dans de telles situations, cependant, vous devriez envisager d'utiliser la boucle while.

La boucle while

Comme la boucle for, while est une boucle de pré-test. La syntaxe est similaire, mais les boucles while n'acceptent qu'une seule expression :

while(condition)
   instruction(s);

Contrairement à la boucle for, la boucle while est le plus souvent utilisée pour répéter une instruction ou un bloc d'instructions un nombre de fois inconnu avant le début de la boucle. En général, une instruction à l'intérieur du corps de la boucle while définit un drapeau booléen sur false lors d'une certaine itération, déclenchant la fin de la boucle, comme dans l'exemple suivant :

  1. bool condition = false;
  2. while (!condition) {
  3.   // Cette boucle tourne jusqu'à ce que la condition soit true.
  4.   FaireUnPeuDeTravail();
  5.   condition = VerifeLaCondition(); // Suppose que VerifeLaCondition() renvoie un booléen
  6. }

La boucle do-while

La boucle do-while est la version post-test de la boucle while. Cela signifie que la condition de test de la boucle est évaluée après l'exécution du corps de la boucle. Par conséquent, les boucles do-while sont utiles dans les situations où un bloc d'instructions doit être exécuté au moins une fois, comme dans cet exemple :

  1. bool condition;
  2. do {
  3.   // Cette boucle s'exécutera au moins une fois, même si la condition est fausse.
  4.   DoitEtreAppeleAuMoinsUneFois();
  5.   condition = VerifeLaCondition();
  6. } while (condition);

La boucle foreach

La boucle foreach vous permet d'effectuer une itération sur chaque élément d'une collection. Pour l'instant, ne vous souciez pas de savoir exactement ce qu'est une collection; comprenez simplement qu'il s'agit d'un objet représentant une liste d'objets. Techniquement, pour qu'un objet soit considéré comme une collection, il doit prendre en charge une interface appelée IEnumerable. Les exemples de collections incluent les tableaux C#, les classes de collection dans les espaces de noms System.Collections et les classes de collection définies par l'utilisateur. Vous pouvez vous faire une idée de la syntaxe de foreach à partir du code suivant, si vous supposez que arrayOfInts est (sans surprise) un tableau d'int :

  1. foreach (int temporaire in arrayOfInts) {
  2.  Console.WriteLine(temporaire);
  3. }

Ici, foreach parcourt le tableau un élément à la fois. Avec chaque élément, il place la valeur de l'élément dans la variable int appelée temporaire, puis effectue une itération de la boucle. Voici une autre situation dans laquelle vous pouvez utiliser l'inférence de type. La boucle foreach deviendrait la suivante :

  1. foreach (var temporaire in arrayOfInts) {
  2.   // ...
  3. }

int serait déduit de temporaire car c'est le type d'élément de la collection. Un point important à noter avec foreach est que vous ne pouvez pas modifier la valeur de l'élément dans la collection (temporaire dans le code précédent), donc un code tel que le suivant ne sera pas compilé :

  1. foreach (int temporaire in arrayOfInts) {
  2.   temporaire++;
  3.   Console.WriteLine(temporaire);
  4. }

Si vous devez parcourir les éléments d'une collection et modifier leurs valeurs, vous devez utiliser une boucle for à la place.

Sortie des boucles

Dans une boucle, vous pouvez arrêter les itérations avec l'instruction break ou terminer l'itération en cours et continuer avec l'itération suivante avec l'instruction continue. Avec l'instruction return, vous pouvez quitter la méthode en cours et donc également quitter une boucle.

Organisation avec espaces de noms

Avec de petites applications d'échantillons, vous n'avez pas besoin de spécifier un espace de noms. Lorsque vous créez des bibliothèques dans lesquelles des classes sont utilisées dans des applications, pour éviter toute ambiguïté, vous devez spécifier des espaces de noms. La classe Console utilisée précédemment est définie dans l'espace de noms System. Pour utiliser la classe Console, vous devez soit la préfixer avec l'espace de noms, soit importer l'espace de noms à partir de cette classe.

Les espaces de noms peuvent être définis de manière hiérarchique. Par exemple, la classe ServiceCollection est spécifiée dans l'espace de noms Microsoft.Extensions.DependencyInjection. Pour définir la classe Echantillon dans l'espace de noms Gladir.CSharp.CoreCSharp, vous pouvez spécifier cette hiérarchie d'espaces de noms avec le mot-clef namespace :

  1. namespace Gladir {
  2.  namespace CSharp {
  3.   namespace CoreCSharp {
  4.    public class Echantillon {
  5.    }
  6.   }
  7.  }
  8. }

Vous pouvez également utiliser la notation pointée pour spécifier l'espace de noms :

  1. namespace Gladir.CSharp.CoreCSharp {
  2.   public class Echantillon {
  3.   }
  4. }

Un espace de noms est une construction logique et complètement indépendante des fichiers ou composantes physiques. Un assemblage peut contenir plusieurs espaces de noms et un seul espace de noms peut être réparti sur plusieurs assemblages. Il s'agit d'une construction logique permettant de regrouper différents types.

Chaque nom d'espace de noms est composé des noms des espaces de noms dans lesquels il réside, séparés par des points, en commençant par l'espace de noms le plus externe et en terminant par son propre nom court. Par conséquent, le nom complet de l'espace de noms CSharp est Gladir.CSharp et le nom complet de la classe Echantillon est Gladir.CSharp.CoreCSharp.Echantillon.

La directive using

De toute évidence, les espaces de noms peuvent devenir assez longs et fastidieux à saisir, et la capacité d'indiquer une classe particulière avec une telle spécificité n'est pas toujours nécessaire. Heureusement, comme indiqué plus haut dans cette page, le C# vous permet d'abréger le nom complet d'une classe. Pour ce faire, répertoriez l'espace de noms de la classe en haut du fichier, préfixé par le mot-clef using. Dans le reste du fichier, vous pouvez faire référence aux types de l'espace de noms par leurs noms de type.

Si deux espaces de noms référencés par des déclarations using contiennent un type du même nom, vous devez utiliser la forme complète (ou au moins une forme plus longue) du nom pour vous assurer que le compilateur sait à quel type accéder. Par exemple, supposons que des classes appelées Test existent dans les espaces de noms CSharp.CoreCSharp et CSharp.OOP. Si vous créez ensuite une classe appelée Test et que les deux espaces de noms sont importés, le compilateur réagit avec une erreur de compilation d'ambiguïté. Dans ce cas, vous devez spécifier le nom de l'espace de noms pour le type.

Alias d'espace de noms

Au lieu de spécifier le nom d'espace de noms complet de la classe pour résoudre les problèmes d'ambiguïté, vous pouvez spécifier un alias avec la directive using, comme illustré avec différentes classes Timer de deux espaces de noms :

  1. using MinuterieSysteme = System.Timers.Timer;
  2. using MinuterieWeb = System.Web.UI.Timer;

Travailler avec des chaînes de caractères

Le code de cette page a déjà utilisé le type string plusieurs fois. string est un type de référence important offrant de nombreuses fonctionnalités. Bien qu'il s'agisse d'un type de référence, il est immuable : il ne peut pas être modifié. Toutes les méthodes que ce type propose ne modifient pas le contenu de la chaîne de caractères mais renvoient une nouvelle chaîne de caractères. Par exemple, pour concaténer des chaînes de caractères, l'opérateur + est surchargé. L'expression s1 + " " + s2 crée d'abord une nouvelle chaîne de caractères combinant s1 et la chaîne contenant le caractère espace. Une autre nouvelle chaîne de caractères est créée en combinant la chaîne de caractères de résultat avec s2 pour créer une autre nouvelle chaîne de caractères. Enfin, la chaîne de caractères résultat est référencée à partir de la variable s3 :

  1. string s1 = "Bonjour";
  2. string s2 = "le monde";
  3. string s3 = s1 + " " + s2;

Avec de nombreuses chaînes créées, vous devez savoir que les objets n'étant plus nécessaires doivent être nettoyés par le récupérateur de mémoire. Le récupérateur de mémoire libère de la mémoire dans le tas géré à partir des objets n'étant sont plus nécessaires. Cela ne se produit pas lorsque la référence n'est plus utilisée; cela est basé sur certaines limites de mémoire. Lisez la page pour plus d'informations sur le récupérateur de mémoire. Il est préférable d'éviter l'allocation d'objet, ce qui peut être fait lors du travail dynamique avec des chaînes en utilisant la classe StringBuilder.

Utilisation de StringBuilder

Le StringBuilder permet à un programme de travailler dynamiquement avec des chaînes de caractères à l'aide des méthodes Append, Insert, Remove et Replace sans créer de nouveaux objets. Au lieu de cela, le StringBuilder utilise un tampon mémoire et modifie ce tampon selon les besoins. Lorsque vous créez un StringBuilder, la capacité par défaut est de 16 caractères. Si des chaînes de caractères sont ajoutées comme indiqué dans l'extrait de code suivant et que davantage de mémoire est nécessaire, la capacité est doublée à 32 caractères :

  1. void UsingStringBuilder() {
  2.   StringBuilder sb = new("Premièrement");  
  3.   sb.Append(' ');
  4.   sb.Append("Troisièmement ");
  5.   sb.Append("Quatrièment");
  6.   string s = sb.ToString();
  7.   Console.WriteLine(s);
  8. }

Si la capacité est trop petite, la taille de la mémoire tampon double toujours, par exemple de 16 à 32 puis à 64 puis à 128 caractères. La longueur de la chaîne est accessible via la propriété Length. La capacité du StringBuilder est renvoyée par la propriété Capacity. Après avoir créé la chaîne de caractères nécessaire, vous pouvez utiliser la méthode ToString, allouant une nouvelle chaîne de caractères contenant le contenu du StringBuilder.

Interpolation de chaîne de caractères

Les extraits de code de cette page ont déjà inclus des chaînes de caractères avec le préfixe $. Ce préfixe permet d'évaluer des expressions dans la chaîne de caractères et est connu sous le nom d'interpolation de chaîne de caractères. Par exemple, avec la chaîne de caractères s2, le contenu de la chaîne de caractères s1 est intégré dans s2 pour obtenir le résultat final «Bonjour le monde!» :

  1. string s1 = "le monde";
  2. string s2 = $"Bonjour {s1}!";

Vous pouvez écrire des expressions de code entre les accolades pour obtenir l'évaluation de l'expression et l'ajout du résultat à la chaîne de caractères. Dans l'extrait de code suivant, une chaîne de caractères est spécifiée avec trois espaces réservés où la valeur de x, la valeur de y et le résultat de l'addition de x et y sont placés dans la chaîne de caractères :

  1. int x = 7, y = 4;
  2. string s3 = $"Le résultat de {x} et {y} est {x + y}";
  3. Console.WriteLine(s3);

La chaîne de caractères résultante est Le résultat de 7 et 4 est 11.

Le compilateur traduit la chaîne de caractères interpolée pour appeler la méthode Format de la chaîne de caractères, transmet une chaîne de caractères avec des espaces réservés numérotés et transmet des paramètres supplémentaires après la chaîne de caractères. Le résultat des arguments supplémentaires provient de l'implémentation de la méthode Format transmise aux espaces réservés en fonction des nombres. Le premier paramètre suivant la chaîne de caractères est transmis à l'espace réservé 0, le deuxième paramètre à l'espace réservé 1, et ainsi de suite :

  1. string s3 = string.Format("Le résultat de {0} et {1} est {2}", x, y, x + y);

FormattableString

La manière dont la chaîne de caractères interpolée est traduite peut être facilement vue en attribuant une chaîne à un FormattableString. La chaîne interpolée peut être directement attribuée à ce type car elle correspond mieux que la chaîne de caractères normale. Ce type définit la propriété Format renvoyant la chaîne de caractères de format résultante, une propriété ArgumentCount et la méthode GetArgument renvoyant les valeurs de paramètre :

  1. void UsingFormattableString()
  2. {
  3.     int x = 7, y = 4;
  4.     FormattableString s = $"Le résultat de {x} + {y} est {x + y}";
  5.     Console.WriteLine($"format: {s.Format}");
  6.     for (int i = 0; i < s.ArgumentCount; i++)
  7.     {
  8.         Console.WriteLine($"Paramètre: {i}:{s.GetArgument(i)}");
  9.     }
  10.     Console.WriteLine();
  11. }
  12.  
  13. UsingFormattableString();

L'exécution de ce code génère ce résultat :

format: Le résultat de {0} + {1} est {2}
Paramètre: 0:7
Paramètre: 1:4
Paramètre: 2:11

Formats de chaîne de caractères

Avec une chaîne de caractères interpolée, vous pouvez ajouter un format de chaîne de caractères à l'expression. .NET définit des formats par défaut pour les nombres, les dates et l'heure en fonction des paramètres régionaux de l'ordinateur. L'extrait de code suivant montre une date, une valeur int et un double avec différentes représentations de format. D est utilisé pour afficher la date au format de date longue, d au format de date courte. Le nombre est affiché avec des chiffres entiers et décimaux (n), en utilisant une notation exponentielle (e), une conversion en hexadécimal (x) et une devise (c). Avec la valeur double, le premier résultat est affiché arrondi après la virgule décimale à trois chiffres (###.###) ; avec la deuxième version, les trois chiffres avant la virgule décimale sont également affichés (000.000) :

  1. void UseStringFormat() {
  2.   DateTime day = new(2024, 2, 16);
  3.   Console.WriteLine($"{day:D}");
  4.   Console.WriteLine($"{day:d}");
  5.  
  6.   int i = 3777;
  7.   Console.WriteLine($"{i:n} {i:e} {i:x} {i:c}");
  8.  
  9.   double d = 3.1415;
  10.   Console.WriteLine($"{d:###.###}");
  11.   Console.WriteLine();
  12.   Console.WriteLine($"{d:000.000}");
  13.   Console.WriteLine();
  14. }
  15.  
  16. UseStringFormat();

Lorsque vous exécutez l'application, ceci s'affiche :

16 février 2024
2024-02-16
3 777,00 3,777000e+003 ec1 3 777,00 $
3,142

003,142

Chaînes de caractères textuelles

Les extraits de code de la section «Le type de caractère» plus haut dans cette page incluent des caractères spéciaux tels que \t pour une tabulation ou \r\n pour un retour chariot à la ligne. Vous pouvez utiliser ces caractères dans une chaîne complète pour obtenir la signification spécifique. Si vous avez besoin d'une barre oblique inverse dans la sortie de la chaîne de caractères, vous pouvez l'échapper avec une double barre oblique inverse \\. Cela peut être ennuyeux si des barres obliques inverses sont nécessaires plusieurs fois, car elles peuvent rendre le code illisible. Pour de tels scénarios, comme lors de l'utilisation d'expressions régulières, vous pouvez utiliser des chaînes de caractères textuelles. Une chaîne textuelle est préfixée par le caractère @ :

  1. string s = @"Une tabulation : \t, un retour chariot : \r, une nouvelle ligne : \n";
  2. Console.WriteLine(s);

L'exécution du code précédent produit ce résultat :

Une tabulation : \t, un retour chariot : \r, une nouvelle ligne : \n

Intervalle avec des chaînes de caractères

Le type String propose une méthode Substring pour récupérer une partie d'une chaîne de caractères. Au lieu d'utiliser la méthode Substring, à partir de C# 8, vous pouvez utiliser les opérateurs hat et range. L'opérateur range utilise la notation .. pour spécifier un intervalle. Avec la chaîne de caractères, vous pouvez utiliser l'indexeur pour accéder à un caractère ou l'utiliser avec l'opérateur range pour accéder à une sous-chaîne de caractères. Les nombres à gauche et à droite de l'opérateur .. spécifient l'intervalle. Le nombre de gauche spécifie la première valeur indexée à 0 de la chaîne de caractères, étant incluse à partir de la chaîne de caractères jusqu'à la dernière valeur indexée à 0 étant exclue. L'intervalle 0..2 couvrirait la chaîne de caractères un. Pour commencer à partir du premier caractère de la chaîne de caractères, le 0 peut être omis comme indiqué dans l'extrait de code suivant. L'intervalle 4..9 commence par le cinquième caractère et va jusqu'au huitième caractère. Pour compter à partir de la fin, vous pouvez utiliser l'opérateur hat ^ :

  1. void IntervalleDansStrings() {
  2.     string s = "Un deux " + "trois";
  3.     string un = s[..2];
  4.     string deux = s[3..7];
  5.     string trois = s[^5..^0];
  6.     Console.WriteLine(un);
  7.     Console.WriteLine(deux);
  8.     Console.WriteLine(trois);
  9.     Console.WriteLine();
  10. }
  11.  
  12. IntervalleDansStrings();

Commentaires

Le sujet suivant, l'ajout de commentaires à votre code, semble simple à première vue, mais il peut s'avérer complexe. Les commentaires peuvent être utiles aux autres développeurs susceptibles de consulter votre code. De plus, comme vous le verrez, vous pouvez utiliser les commentaires pour générer une documentation pour votre code que d'autres développeurs peuvent utiliser.

Commentaires internes dans les fichiers sources

Le C# utilise les commentaires traditionnels de type C sur une seule ligne (//..) et sur plusieurs lignes (/* .. */) :

  1. // Ceci est un commentaire sur une seule ligne
  2.  
  3. /* Ce commentaire
  4. s'étend sur plusieurs lignes. */

Tout ce qui se trouve dans un commentaire sur une seule ligne, du // à la fin de la ligne, est ignoré par le compilateur, et tout ce qui se trouve dans une combinaison de commentaires sur plusieurs lignes, du /* d'ouverture au */ suivant, est ignoré. Il est possible de placer des commentaires sur plusieurs lignes dans une ligne de code :

  1. Console.WriteLine(/* Voici un commentaire ! */ "Ceci va être compilé.");

Les commentaires en ligne peuvent être utiles lors du débogage si, par exemple, vous souhaitez temporairement essayer d'exécuter le code avec une valeur différente quelque part, comme dans l'extrait de code suivant. Cependant, les commentaires en ligne peuvent rendre le code difficile à lire, utilisez-les donc avec précaution.

  1. FaireQuelqueChose(Largeur, /*Hauteur*/ 100);

Documentation XML

En plus des commentaires de type C illustrés dans la section précédente, C# dispose d'une fonctionnalité très intéressante : la possibilité de produire automatiquement une documentation au format XML à partir de commentaires spéciaux. Ces commentaires sont des commentaires sur une seule ligne, mais ils commencent par trois barres obliques (///) au lieu de deux. Dans ces commentaires, vous pouvez placer des balises XML contenant la documentation des types et des membres de type dans votre code.

Les balises du tableau suivant sont reconnues par le compilateur :

Balise Description
<c> Marque le texte dans une ligne sous forme de code, par exemple, <c>int i = 74;</c>.
<code> Marque plusieurs lignes comme code.
<example> Marque un exemple de code.
<exception> Documente une classe d'exception. (La syntaxe est vérifiée par le compilateur.)
<include> Inclut des commentaires provenant d'un autre fichier de documentation. (La syntaxe est vérifiée par le compilateur.)
<list> Insère une liste dans la documentation.
<para> Donne une structure au texte.
<param> Marque un paramètre de méthode. (La syntaxe est vérifiée par le compilateur.)
<paramref> Indique qu'un mot est un paramètre de méthode. (La syntaxe est vérifiée par le compilateur.)
<permission> Accès aux documents à un membre. (La syntaxe est vérifiée par le compilateur.)
<remarks> Ajoute une description pour un membre.
<returns> Documente la valeur de retour d'une méthode.
<see> Fournit une référence croisée à un autre paramètre. (La syntaxe est vérifiée par le compilateur.)
<seealso> Fournit une section «voir aussi» dans une description. (La syntaxe est vérifiée par le compilateur.)
<summary> Fournit un bref résumé d'un type ou d'un membre.
<typeparam> Décrit un paramètre de type dans le commentaire d'un type générique.
<typeparamref> Fournit le nom du paramètre de type.
<value> Décrit une propriété.

L'extrait de code suivant montre la classe Calculatrice avec la documentation spécifiée pour la classe et la documentation pour la méthode Ajouter :

  1. namespace CSharp.MathLib
  2. {
  3.   ///<summary>
  4.   /// Classe CSharp.MathLib.Calculatrice.
  5.   /// Fournit une méthode pour ajouter deux doubles.
  6.   ///</summary>
  7.   public static class Calculatrice
  8.     ///<summary>
  9.     /// La méthode Ajouter nous permet d'additionner deux réels de doubles précision.
  10.     ///</summary>
  11.     ///<returns>Résultat de l'addition (double)</returns>
  12.     ///<param name="x">Premier nombre à ajouter</param>
  13.     ///<param name="y">Deuxième nombre à ajouter</param>
  14.     public static double Ajouter(double x, double y) => x + y;
  15.   }
  16. }

Pour générer la documentation XML, vous pouvez ajouter le GenerateDocumentationFile au fichier projet :

<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
    <OutputType>exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <Nullable>enable</Nullable>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
</Project>

Avec ce paramètre, le fichier de documentation est créé dans le même répertoire où le binaire du programme apparaîtra lorsque vous compilerez l'application. Vous pouvez également spécifier l'élément DocumentationFile pour définir un nom différent de celui du fichier projet, et vous pouvez également spécifier un répertoire absolu dans lequel la documentation doit être générée.

À l'aide d'outils tels que Visual Studio, IntelliSense affichera des info-bulles contenant les informations de la documentation au fur et à mesure que les classes et les membres sont utilisés.

Directives de préprocesseur C#

Outre les mots-clefs C#, dont vous avez déjà rencontré la plupart, C# inclut un certain nombre de commandes appelées directives de préprocesseur. Ces commandes ne sont jamais réellement traduites en commandes dans votre code exécutable, mais elles affectent certains aspects du processus de compilation. Par exemple, vous pouvez utiliser des directives de préprocesseur pour empêcher le compilateur de compiler certaines parties de votre code. Vous pouvez le faire si vous ciblez différents cadres d'applications et gérez les différences. Dans un autre scénario, vous souhaiterez peut-être activer ou désactiver les types de référence nullables, car la modification des bases de code existantes ne peut pas être corrigée à court terme.

Les directives du préprocesseur se distinguent toutes par le fait qu'elles commencent par le symbole #.

Voici la liste des directives de préprocesseur du C# :

Directive Description
#define Définit un symbole pour conditionner des blocs de code. Les symboles définis ne sont valables que pour le fichier source actuel.
#elif Spécifie une autre condition si la précédente est fausse.
#else Fournit un bloc de code alternatif si aucune condition #if ou #elif n'est satisfaite.
#endif Termine une structure conditionnelle commencée par #if. Obligatoire pour fermer les blocs conditionnels.
#endregion Permet de définir la fin d'un blocs de code repliables pour une meilleure lisibilité.
#error Génère une erreur pendant la compilation.
#if Évalue si un symbole est défini et exécute le code conditionnel correspondant.
#line Change les numéros de ligne ou spécifie un autre fichier source pour le compilateur (utile pour des outils générateurs de code).
#nullable Active ou désactive la vérification de nullabilité pour les références.
#pragma Donne des instructions spéciales au compilateur.
#region Permet de définir des blocs de code repliables pour une meilleure lisibilité.
#undef Supprime une définition de symbole précédemment déclarée avec #define.
#warning Émet un avertissement pendant la compilation.

Les sections suivantes couvrent brièvement les objectifs des directives du préprocesseur.

#define et #undef

#define est utilisé comme ceci :

  1. #define DEBUG

Cela indique au compilateur qu'un symbole portant le nom donné (dans ce cas DEBUG) existe. C'est un peu comme déclarer une variable, sauf que cette variable n'a pas vraiment de valeur, elle existe simplement. De plus, ce symbole ne fait pas partie de votre code réel ; il n'existe que pour le bénéfice du compilateur, alors que le compilateur compile le code et n'a aucune signification dans le code C# lui-même.

#undef fait l'inverse et supprime la définition d'un symbole :

  1. #undef DEBUG

Si le symbole n'existe pas en premier lieu, alors #undef n'a aucun effet. De même, #define n'a aucun effet si un symbole existe déjà.

Vous devez placer les directives #define et #undef au début du fichier source C#, avant tout code déclarant des objets à compiler.

#define n'est pas très utile en soi, mais lorsqu'il est combiné avec d'autres directives de préprocesseur, en particulier #if, il devient puissant.

Par défaut, avec une version Debug, le symbole DEBUG est défini, et avec le code Release, le symbole RELEASE est défini. Pour définir des chemins de code différents sur les versions debug et release, vous n'avez pas besoin de définir ces symboles ; tout ce que vous avez à faire est d'utiliser les directives de préprocesseur présentées dans la section suivante pour définir les chemins de code que le compilateur doit emprunter.

Remarque : Les directives de préprocesseur ne se terminent pas par des points-virgules et constituent normalement la seule commande sur une ligne. Si le compilateur voit une directive de préprocesseur, il suppose que la commande suivante se trouve sur la ligne suivante.

#if, #elif, #else et #endif

Ces directives indiquent au compilateur s'il doit compiler un bloc de code. Considérez cette méthode :

  1. int FaireDuTravail(double x) {
  2.   // Faire quelque chose
  3.   #if DEBUG
  4.   Console.WriteLine($"x est {x}");
  5.   #endif
  6. }

Ce code se compile normalement, à l'exception de l'appel de méthode Console.WriteLine contenu dans la clause #if. Cette ligne n'est exécutée que si le symbole DEBUG a été défini. Comme mentionné précédemment, il est défini avec une version Debug, ou vous l'avez défini avec une directive #define précédente. Lorsque le compilateur trouve la directive #if, il vérifie si le symbole concerné existe et compile le code à l'intérieur de la clause #if uniquement si le symbole existe. Sinon, le compilateur ignore simplement tout le code jusqu'à ce qu'il atteigne la directive #endif correspondante. La pratique courante consiste à définir le symbole DEBUG pendant que vous effectuez le débogage et à avoir divers morceaux de code liés au débogage dans les clauses #if. Ensuite, lorsque vous êtes proche de la livraison, vous commentez simplement la directive #define et tout le code de débogage disparaît miraculeusement, la taille du fichier exécutable diminue et vos utilisateurs finaux ne sont pas déroutés en voyant les informations de débogage. (Évidemment, vous feriez plus de tests pour vous assurer que votre code fonctionne toujours sans DEBUG défini.) Cette technique est courante dans la programmation C et C++ et est connue sous le nom de compilation conditionnelle.

Les directives #elif (= else if) et #else peuvent être utilisées dans les blocs #if et ont des significations intuitivement évidentes. Il est également possible d'imbriquer des blocs #if :

  1. #define ENTREPRISE
  2. #define WINDOWS10
  3. // plus loin dans le dossier
  4. #if ENTREPRISE
  5. // faire quelque chose
  6. #if WINDOWS10
  7. // un code n'étant pertinent que pour l'entreprise
  8. // édition fonctionnant sur WINDOWS10
  9. #endif
  10. #elif PROFESSIONNEL
  11. // faire autre chose
  12. #else
  13. // code pour la version allégée
  14. #endif

#if et #elif prennent également en charge une gamme limitée d'opérateurs logiques, à l'aide des opérateurs !, ==, !=, && et ||. Un symbole est considéré comme vrai s'il existe et faux s'il n'existe pas. Voici un exemple :

  1. #if WINDOWS10 && !ENTREPRISE // si WINDOWS10 est défini mais pas ENTREPRISE

#warning et #error

Deux autres directives de préprocesseur utiles, #warning et #error, provoquent respectivement l'émission d'un avertissement ou d'une erreur lorsque le compilateur les rencontre. Si le compilateur voit une directive #warning, il affiche à l'utilisateur le texte apparaissant après l'avertissement, après quoi la compilation continue. S'il rencontre une directive #error, il affiche le texte suivant à l'utilisateur comme s'il s'agissait d'un message d'erreur de compilation, puis abandonne immédiatement la compilation, de sorte qu'aucun code IL n'est généré. Vous pouvez utiliser ces directives pour vérifier que vous n'avez rien fait de stupide avec vos instructions #define ; vous pouvez également utiliser les instructions #warning pour vous rappeler de faire quelque chose :

  1. #if DEBUG && RELEASE
  2. #error "Vous avez défini DEBUG et RELEASE simultanément !"
  3. #endif
  4.  
  5. #warning "N'oubliez pas de supprimer cette ligne avant que le patron ne teste le code !"
  6. Console.WriteLine("*J'adore ce travail.*");

#region et #endregion

Les directives #region et #endregion sont utilisées pour indiquer qu'un certain bloc de code doit être traité comme un bloc unique avec un nom donné, comme ceci :

  1. #region Déclarations des membres sur le terrain
  2. int x;
  3. double dbl;
  4. decimal balance;
  5. #endregion

Les directives de région sont ignorées par le compilateur et utilisées par des outils tels que l'éditeur de code Visual Studio. L'éditeur vous permet de réduire les sections de région, de sorte que seul le texte associé à la région s'affiche. Cela facilite le défilement du code source. Cependant, vous devriez préférer écrire des fichiers de code plus courts à la place.

#line

Vous pouvez utiliser la directive #line pour modifier les informations de nom de fichier et de numéro de ligne étant générées par le compilateur dans les messages d'avertissement et d'erreur. Vous ne souhaiterez probablement pas utiliser cette directive souvent. Elle est particulièrement utile lorsque vous codez en conjonction avec un autre paquet modifiant le code que vous tapez avant de l'envoyer au compilateur. Dans cette situation, les numéros de ligne, ou peut-être les noms de fichiers rapportés par le compilateur, ne correspondent pas aux numéros de ligne dans les fichiers ou aux noms de fichiers que vous éditez. La directive #line peut être utilisée pour restaurer la correspondance. Vous pouvez également utiliser la syntaxe #line default pour restaurer la numérotation par défaut de la ligne :

  1. #line 164 "Coeur.cs" // Nous savons qu'il s'agit de la ligne 177 du fichier
  2. // Coeur.cs, avant l'intermédiaire
  3. // le paquet le déforme.
  4. // plus tard
  5. #line default // restaure la numérotation de ligne par défaut

#pragma

La directive #pragma peut supprimer ou restaurer des avertissements spécifiques du compilateur. Contrairement aux options de ligne de commande, la directive #pragma peut être implémentée au niveau de la classe ou de la méthode, ce qui permet un contrôle précis des avertissements supprimés et du moment où ils sont supprimés. L'exemple suivant désactive l'avertissement «champ non utilisé» puis le restaure après la compilation de la classe MaClasse :

  1. #pragma warning disable 169
  2. public class MaClasse {
  3.   int ChampJamaisUtilise;
  4. }
  5. #pragma warning restore 169

#nullable

Avec la directive #nullable, vous pouvez activer ou désactiver les types de référence nullables dans un fichier de code. #nullable enable active les types de référence nullables, quel que soit le paramètre dans le fichier de projet. #nullable disable le désactive. #nullable restore rétablit les paramètres sur les paramètres du fichier de projet.

Comment l'utiliser ? Si les types de référence nullables sont activés avec le fichier projet, vous pouvez les désactiver temporairement dans les sections de code où vous rencontrez des problèmes avec ce comportement du compilateur et le restaurer dans les paramètres du fichier projet après le code présentant des problèmes de nullabilité.

La ligne directrice de programmation C#

Cette dernière section de la page fournit les lignes directrices que vous devez garder à l'esprit lors de l'écriture de programmes C#. Ce sont des lignes directrices que la plupart des développeurs C# utilisent. Lorsque vous utilisez ces lignes directrices, d'autres développeurs se sentiront à l'aise pour travailler avec votre code.

Règles pour les identificateurs

Cette section examine les règles régissant les noms que vous pouvez utiliser pour les variables, les classes, les méthodes,... Notez que les règles présentées dans cette section ne sont pas de simples directives : elles sont appliquées par le compilateur C#.

Les identificateurs sont les noms que vous donnez aux variables, aux types définis par l'utilisateur tels que les classes et les structures, et aux membres de ces types. Les identifiants sont sensibles à la casse, donc, par exemple, les variables nommées tauxInteret et TauxInteret seront reconnues comme des variables différentes. Voici quelques règles déterminant les identificateurs que vous pouvez utiliser en C# :

Si vous devez utiliser l'un de ces mots comme identifiant (par exemple, si vous accédez à une classe écrite dans un autre langage), vous pouvez préfixer l'identifiant avec le symbole @ pour indiquer au compilateur que ce qui suit doit être traité comme un identifiant, et non comme un mot-clef C# (abstract n'est donc pas un identifiant valide, mais @abstract l'est).

Enfin, les identifiants peuvent également contenir des caractères Unicode, spécifiés à l'aide de la syntaxe \uXXXX, où XXXX est le code hexadécimal à quatre chiffres du caractère Unicode. Voici quelques exemples d'identifiants valides :

Les deux derniers éléments de cette liste sont identiques et interchangeables (car 005f est le code Unicode pour le caractère de soulignement), donc, évidemment, ces deux identificateurs ne peuvent pas être déclarés dans la même portée.

Conventions d'utilisation

Dans tout langage de développement, certains styles de programmation traditionnels apparaissent généralement. Les styles ne font pas partie du langage lui-même mais sont plutôt des conventions, par exemple la manière dont les variables sont nommées ou dont certaines classes, méthodes ou fonctions sont utilisées. Si la plupart des développeurs utilisant ce langage suivent les mêmes conventions, il est plus facile pour les différents développeurs de comprendre le code des autres, ce qui à son tour contribue généralement à la maintenabilité du programme. Les conventions dépendent cependant du langage et de l'environnement. Par exemple, les développeurs C++ programmant sur la plate-forme Windows ont traditionnellement utilisé les préfixes psz ou lpsz pour indiquer les chaînes de caractères (char *pszResult; char *lpszMessage;) mais sur les machines Unix, il est plus courant de ne pas utiliser de tels préfixes : char *Result; char *Message;.

Remarque : la convention selon laquelle les noms de variables sont préfixés par des lettres représentant le type de données est connue sous le nom de notation hongroise. Cela signifie que les autres développeurs lisant le code peuvent immédiatement déterminer à partir du nom de la variable quel type de données la variable représente. La notation hongroise est largement considérée comme redondante à l'ère des éditeurs intelligents et d'IntelliSense.

Alors que les conventions d'utilisation de nombreux langages de programmation ont simplement évolué au fur et à mesure de leur utilisation, pour C# et l'ensemble du .NET Framework, Microsoft a rédigé des lignes directrices d'utilisation complètes étant détaillées dans la documentation .NET/C#. Cela signifie que, dès le départ, les programmes .NET présentent un degré élevé d'interopérabilité en termes de capacité des développeurs à comprendre le code. Les lignes directrices ont également été développées avec le bénéfice d'une vingtaine d'années de recul dans la programmation orientée objet. À en juger par les groupes de discussion concernés, les lignes directrices ont été soigneusement réfléchies et sont bien accueillies par la communauté des développeurs. Par conséquent, elles valent la peine d'être suivies.

Notez cependant que les lignes directrices ne sont pas les mêmes que les spécifications du langage de programmation. Vous devez essayer de les suivre autant que possible. Néanmoins, vous ne rencontrerez pas de problèmes si vous avez une bonne raison de ne pas le faire, par exemple, vous n'obtiendrez pas d'erreur de compilation parce que vous ne suivez pas ces lignes directrices. La règle générale est que si vous ne suivez pas les lignes directrices d'utilisation, vous devez avoir une raison convaincante. Lorsque vous vous écartez des lignes directrices, vous devez prendre une décision consciente plutôt que de simplement ne pas vous en soucier. De plus, si vous comparez les lignes directrices avec les exemples du reste de cette page, vous remarquerez que dans de nombreux exemples, on a choisi de ne pas suivre les conventions. C'est généralement parce que les conventions sont conçues pour des programmes beaucoup plus volumineux que les exemples; bien que les lignes directrices soient excellentes si vous écrivez un progiciel complet, elles ne sont pas vraiment adaptées aux petits programmes autonomes de 20 lignes. Dans de nombreux cas, le respect des conventions aurait rendu les exemples plus difficiles à suivre plutôt que plus faciles.

Les lignes directrices complètes pour un bon style de programmation sont assez complètes. Cette section se limite à décrire certaines des directives les plus importantes, ainsi que celles qui sont les plus susceptibles de vous surprendre. Pour être absolument certain que votre code respecte complètement les directives d'utilisation, vous devez vous référer à la documentation Microsoft.

Conventions de dénomination

Un aspect important pour rendre vos programmes compréhensibles est la manière dont vous choisissez de nommer vos éléments, et cela inclut la dénomination des variables, des méthodes, des classes, des énumérations et des espaces de noms.

Il est intuitivement évident que vos noms doivent refléter l'objectif de l'élément et ne doivent pas entrer en conflit avec d'autres noms. La philosophie générale du .NET Framework est également que le nom d'une variable doit refléter l'objectif de cette instance de variable et non le type de données. Par exemple, hauteur est un bon nom pour une variable, alors que integerValue ne l'est pas. Cependant, vous constaterez probablement que ce principe est un idéal difficile à atteindre. En particulier lorsque vous avez affaire à des contrôles, dans la plupart des cas, vous serez probablement plus heureux de vous en tenir à des noms de variables tels que dialogueDeConfirmation et choisirEmployeeListBox, indiquant le type de données dans le nom.

Les sections suivantes examinent certains des éléments auxquels vous devez penser lors du choix des noms.

PascalCase

Dans de nombreux cas, vous devez utiliser la PascalCase pour les noms. Avec le PascalCase, la première lettre de chaque mot d'un nom est en majuscule : SalaireDesEmployes, BoiteDeDialogueDeConfirmation, CodageEnTexteBrut. Notez que presque tous les noms d'espaces de noms, de classes et de membres dans les classes de base suivent la PascalCase. En particulier, la convention de joindre des mots à l'aide du caractère de soulignement est déconseillée. Par conséquent, essayez de ne pas utiliser de noms tels que Salaire_Des_Employes. Il est également courant dans d'autres langages d'utiliser des majuscules pour les noms de constantes. Cela n'est pas conseillé en C# car ces noms sont plus difficiles à lire : la convention est d'utiliser la PascalCase partout :

  1. const int LongueurMaximum;

Pour les noms de tous les champs membres privés dans les types. Pour les noms de tous les paramètres passés aux méthodes. Pour distinguer les éléments ayant autrement le même nom. Un exemple courant est lorsqu'une propriété entoure un champ :

Remarque : Depuis .NET Core, l'équipe .NET a commencé à préfixer les noms des champs membres privés par un trait de soulignement.

Si vous encapsulez une propriété autour d'un champ, vous devez toujours utiliser la CamelCase pour le membre privé et la PascalCase pour le membre public ou protégé afin que les autres classes utilisant votre code ne voient que les noms en PascalCase (à l'exception des noms de paramètres).

Vous devez également faire attention à la sensibilité à la casse. C# est sensible à la casse, il est donc syntaxiquement correct que les noms en C# ne diffèrent que par la casse, comme dans les exemples précédents. Cependant, gardez à l'esprit que vos assemblages peuvent à un moment donné être appelés à partir d'applications Visual Basic .NET, et Visual Basic .NET n'est pas sensible à la casse. Par conséquent, si vous utilisez des noms qui ne diffèrent que par la casse, il est important de le faire uniquement dans les situations où les deux noms ne seront jamais vus en dehors de votre assemblage. (L'exemple précédent est considéré comme correct car la CamelCase est utilisée avec le nom étant attaché à une variable privée.) Sinon, vous risquez d'empêcher d'autres codes écrits en Visual Basic .NET de pouvoir utiliser correctement votre assemblage.

Styles de noms

Soyez cohérent dans le choix de vos noms. Par exemple, si l'une des méthodes d'une classe s'appelle ShowConfirmationDialog, vous ne devez pas donner à une autre méthode un nom tel que ShowDialogWarning ou WarningDialogShow. L'autre méthode doit s'appeler ShowWarningDialog.

Noms d'espaces de noms

Il est particulièrement important de choisir soigneusement les noms d'espaces de noms pour éviter le risque de vous retrouver avec le même nom pour l'un de vos espaces de noms que quelqu'un d'autre utilise. N'oubliez pas que les noms d'espaces de noms sont le seul moyen par lequel .NET distingue les noms d'objets dans les assemblages partagés. Par conséquent, si vous utilisez le même nom d'espace de noms pour votre paquet logiciel qu'un autre paquet et que les deux paquets sont utilisés par le même programme, des problèmes surviendront. Pour cette raison, il est presque toujours judicieux de créer un espace de noms de niveau supérieur avec le nom de votre entreprise, puis d'imbriquer des espaces de noms successifs qui limitent la technologie, le groupe ou le service dans lequel vous travaillez ou le nom du paquet auquel vos classes sont destinées. Microsoft recommande des noms d'espaces de noms commençant par <NomDeCompagnie>.<NomDeTechnologie>.

Noms et mots-clefs

Il est important que les noms n'entrent pas en conflit avec des mots-clefs. En fait, si vous essayez de nommer un élément de votre code avec un mot se trouvant être un mot-clef C#, vous obtiendrez presque certainement une erreur de syntaxe, car le compilateur supposera que le nom fait référence à une instruction. Cependant, en raison de la possibilité que vos classes soient accessibles par du code écrit dans d'autres langages, il est également important de ne pas utiliser de noms étant des mots-clefs dans d'autres langages de programmation .NET. En règle générale, les mots-clefs C++ sont similaires aux mots-clefs C#, donc la confusion avec C++ est peu probable, et les mots-clefs couramment rencontrés qui sont propres à Visual C++ .NET ont tendance à commencer par deux caractères de soulignement. Comme avec C#, les mots-clefs C++ sont écrits en minuscules, donc si vous respectez la convention de nommer vos classes et membres publics avec des noms de style Pascal, ils auront toujours au moins une lettre majuscule dans leurs noms, et il n'y aura aucun risque de conflit avec les mots-clefs C++. Cependant, vous êtes plus susceptible d'avoir des problèmes avec Visual Basic .NET, ayant beaucoup plus de mots-clefs que C#, et le fait de ne pas être sensible à la casse signifie que vous ne pouvez pas vous fier aux noms de style Pascal pour vos classes et méthodes.

Utilisation des propriétés et des méthodes

Un domaine pouvant prêter à confusion concernant une classe est celui de savoir si une quantité particulière doit être représentée par une propriété ou une méthode. Les règles ne sont pas strictes, mais en général, vous devez utiliser une propriété si quelque chose doit ressembler et se comporter comme une variable. Cela signifie, entre autres choses, que :

Si l'élément que vous codez satisfait tous les critères précédents, il est probablement un bon candidat pour une propriété. Sinon, vous devez utiliser une méthode.

Utilisation des champs

Les directives sont assez simples ici. Les champs doivent presque toujours être privés, même si dans certains cas, il peut être acceptable que les champs constants ou en lecture seule soient publics. Rendre un champ public peut entraver votre capacité à étendre ou modifier la classe à l'avenir.

Les directives précédentes devraient vous donner une base de bonnes pratiques, et vous devriez les utiliser en conjonction avec un bon style de programmation orientée objet.

Une dernière remarque utile à garder à l'esprit est que Microsoft a été relativement prudent en matière de cohérence et a suivi ses propres directives lors de l'écriture des classes de base .NET, donc une bonne façon d'avoir une idée intuitive des conventions à suivre lors de l'écriture du code .NET est de simplement regarder les classes de base : voyez comment les classes, les membres et les espaces de noms sont nommés, et comment fonctionne la hiérarchie des classes. La cohérence entre les classes de base et vos classes facilitera la lisibilité et la maintenabilité.

En résumé

Cette page a examiné la syntaxe de base du langage de programmation C#, couvrant les domaines nécessaires à l'écriture de programmes C# simples. Une grande partie de la syntaxe est immédiatement reconnaissable pour les développeurs connaissant n'importe quel langage de style C (ou même JavaScript). C# a ses racines avec C++, Java et Pascal (Anders Hejlsberg, l'architecte principal d'origine de C# était l'auteur original de Turbo Pascal et a également créé J++, la version Microsoft de Java). Au fil du temps, de nouvelles fonctionnalités ont été inventées étant également disponibles avec d'autres langages de programmation, et C# a également bénéficié d'améliorations déjà disponibles avec d'autres langages.



Dernière mise à jour : Dimanche, le 1er décembre 2024