Initialisation des systèmes...

Baptiste.Dev
Retour aux notes
LangagesDébutantSérie : C# / .NET

C# : La syntaxe

Variables, opérateurs, structures de contrôle et méthodes - comment manipuler les données en C#.

8 min de lecture
Mis à jour il y a 4 mois
c#csharpsyntaxevariablesméthodesopérateurs

C# : La syntaxe

Comment déclarer, manipuler et organiser le code. Si tu n'as pas encore lu Les types, commence par là.


# Déclaration de variables

// Typage explicite
int age = 22;
string prenom = "Baptiste";
bool estActif = true;
 
// Inférence de type avec var
var score = 100;        // le compilateur déduit : int
var nom = "Vidal";      // le compilateur déduit : string
 
// Constante  -  valeur fixée à la compilation, jamais modifiable
const double Pi = 3.14159265;
 
// Readonly  -  utilisable uniquement dans une classe
// Fixée à l'exécution (dans le constructeur), jamais modifiable après
// readonly int maxRetries = 3;  (voir les objets)

Faut-il utiliser var ? C'est un débat. var est acceptable quand le type est évident, mais tu perds l'information de nullabilité. Compare :

// var  -  on ne voit pas que GetDatas() peut retourner null
var datas = GetDatas();
 
// Mieux  -  le type et la nullabilité sont explicites
Datas? datas = GetDatas();
 
// Encore mieux  -  on force la non-nullabilité dès la déclaration
Datas datas = GetDatas() ?? throw new Exception("Datas introuvable");

Ma recommandation : vise le zéro var. Le typage explicite te force à réfléchir à la nullabilité de chaque variable, et combiné avec ?? throw, tu élimines les null dès la source au lieu de les gérer plus tard. L'opérateur ?? est détaillé dans la section Opérateurs null.


# Opérateurs

Arithmétiques

int a = 10, b = 3;
a + b    // 13
a - b    // 7
a * b    // 30
a / b    // 3 (division entière !)
a % b    // 1 (modulo  -  le reste)
 
// Pour une division décimale :
(double)a / b  // 3.333...

Incrémentation : i++ vs ++i

Les deux ajoutent 1 à la variable. La différence est quand la valeur est retournée :

int a = 5;
int b = a++;  // b = 5, puis a passe à 6 (retourne, PUIS incrémente)
int c = ++a;  // a passe à 7, puis c = 7 (incrémente, PUIS retourne)

En pratique, dans une boucle for comme for (int i = 0; i < 10; i++), ça ne change rien - l'incrémentation se fait après chaque tour. La différence ne compte que quand on utilise le résultat dans une expression.

Comparaison

a == b    // égal
a != b    // différent
a > b     // supérieur
a >= b    // supérieur ou égal
a < b     // inférieur
a <= b    // inférieur ou égal

Logiques

true && false   // ET -> false
true || false   // OU -> true
!true           // NON -> false

Opérateurs null

Très utilisés en C# moderne, ces opérateurs simplifient la gestion des valeurs null :

// Null-coalescing : si la valeur est null, utilise la valeur par défaut
string? nom = null;
string affichage = nom ?? "Inconnu";  // "Inconnu"
 
// Null-coalescing assignment
nom ??= "Défaut";  // assigne "Défaut" seulement si nom est null
 
// Null-conditional : accède au membre seulement si non-null
string? upper = nom?.ToUpper();  // null si nom est null, sinon "DÉFAUT"
 
// Ternaire
string statut = age >= 18 ? "Majeur" : "Mineur";

# Structures de contrôle

Conditions (if / else if / else)

Le principe est simple : if = si, else if = sinon si, else = sinon. Les conditions sont évaluées de haut en bas, et seule la première qui est vraie est exécutée.

if (temperature > 30)
{
    Console.WriteLine("Il fait chaud");
}
else if (temperature > 15)
{
    Console.WriteLine("Il fait bon");
}
else
{
    Console.WriteLine("Il fait froid");
}

Si temperature vaut 25, le programme affiche "Il fait bon". Le else final est un filet de sécurité - il attrape tout ce qui n'a pas matché au-dessus.

Switch classique

Le switch évite les chaînes de if/else if quand on compare une variable à plusieurs valeurs connues. Chaque case est un cas possible, et default attrape tout le reste.

switch (jour)
{
    case "lundi":
        Console.WriteLine("Début de semaine");
        break;
    case "vendredi":
        Console.WriteLine("Bientôt le week-end");
        break;
    default:
        Console.WriteLine("Jour normal");
        break;
}

Le break est obligatoire à la fin de chaque case. Sans lui, le compilateur refuse de compiler. C'est une différence avec le C/C++ où l'oubli du break provoque un "fall-through" silencieux vers le case suivant.

Switch expression (C# moderne)

La switch expression est une syntaxe plus concise introduite en C# 8. Elle retourne une valeur directement, sans break, sans case - juste des =>.

string message = jour switch
{
    "lundi" => "Début de semaine",
    "vendredi" => "Bientôt le week-end",
    _ => "Jour normal"
};

Le _ (discard) joue le rôle du default. Si aucun cas ne matche et qu'il n'y a pas de _, le programme plante avec une exception à l'exécution.

Pattern matching (C# 9+)

Le pattern matching pousse le switch expression encore plus loin : on peut matcher sur des conditions complexes, pas seulement des valeurs exactes.

string categorie = temperature switch
{
    < 0 => "Glacial",
    >= 0 and < 15 => "Froid",
    >= 15 and < 30 => "Agréable",
    >= 30 => "Chaud"
};

Les mots-clés and, or et not permettent de combiner les conditions. C'est beaucoup plus lisible qu'une chaîne de if/else if avec des &&.

Boucles

C# propose 4 types de boucles, chacune adaptée à un cas d'usage :

for - quand tu connais le nombre d'itérations à l'avance. La variable i est le compteur : elle est initialisée, testée à chaque tour, puis incrémentée.

for (int i = 0; i < 10; i++)
{
    Console.WriteLine(i);
}

foreach - pour parcourir une collection élément par élément, sans se soucier des index. C'est la boucle la plus utilisée au quotidien.

foreach (string ville in villes)
{
    Console.WriteLine(ville);
}

while - tant que la condition est vraie, on boucle. Si la condition est fausse dès le départ, le corps n'est jamais exécuté.

while (tentatives < 3)
{
    tentatives++;
}

do while - comme while, mais le corps est exécuté au moins une fois avant de vérifier la condition. Utile pour les menus ou les saisies utilisateur.

do
{
    reponse = Console.ReadLine();
} while (reponse != "oui");

# Méthodes

Avant de coder, clarifions le vocabulaire. En programmation, on utilise trois termes qui se ressemblent mais ne disent pas la même chose :

  • Procédure - un bloc de code qui fait quelque chose mais ne retourne rien. En C#, c'est une méthode avec le type de retour void.
  • Fonction - un bloc de code qui calcule et retourne une valeur. En C#, c'est une méthode avec un type de retour (int, string, etc.).
  • Méthode - le terme générique en C#. Qu'elle retourne quelque chose ou non, on parle toujours de "méthode" parce qu'en C# tout vit dans une classe (ou un struct). Le mot "fonction" au sens strict n'existe pas dans le langage - mais dans la pratique, tout le monde l'utilise.

Syntaxe de base

// Fonction  -  retourne une valeur
int Additionner(int a, int b)
{
    return a + b;
}
 
// Procédure  -  ne retourne rien (void)
void AfficherMessage(string msg)
{
    Console.WriteLine(msg);
}
 
// Expression-bodied (une seule expression)
int Doubler(int x) => x * 2;

Paramètres spéciaux

Valeurs par défaut - un paramètre peut avoir une valeur par défaut. Si l'appelant ne le fournit pas, la valeur par défaut est utilisée. Les paramètres avec défaut doivent être en fin de liste.

void Saluer(string nom, string formule = "Bonjour")
{
    Console.WriteLine($"{formule}, {nom} !");
}
Saluer("Baptiste");              // "Bonjour, Baptiste !"
Saluer("Baptiste", "Salut");     // "Salut, Baptiste !"

params - permet d'accepter un nombre variable d'arguments du même type. Le compilateur les regroupe automatiquement dans un tableau. Sans params, il faudrait passer un tableau explicitement.

int Somme(params int[] nombres)
{
    int total = 0;
    foreach (int n in nombres) total += n;
    return total;
}
Somme(1, 2, 3, 4);  // 10
Somme(10, 20);       // 30

out - permet à une méthode de retourner plusieurs valeurs. La variable est déclarée par l'appelant mais assignée par la méthode. Le pattern le plus courant est TryParse : la méthode retourne un bool (succès/échec) et remplit la variable out avec le résultat. Le fonctionnement en détail est expliqué dans l'article sur le passage de paramètres.

if (int.TryParse("42", out int nombre))
{
    Console.WriteLine(nombre);  // 42
}
else
{
    Console.WriteLine("Format invalide");
}

ref - passe une variable par référence. Contrairement à out, la variable doit déjà être initialisée. La méthode peut lire et modifier la valeur originale de l'appelant, pas une copie. Pour comprendre pourquoi ça fonctionne ainsi, voir les articles sur la mémoire et le passage de paramètres.

void Incrementer(ref int valeur)
{
    valeur++;
}
 
int compteur = 10;
Incrementer(ref compteur);
Console.WriteLine(compteur);  // 11

La différence clé : out = la méthode écrit une valeur, ref = la méthode lit et modifie une valeur existante. Dans les deux cas, le mot-clé doit apparaître côté appelant ET côté méthode.

Surcharge (overload)

Plusieurs méthodes peuvent porter le même nom tant que leurs signatures (nombre ou types de paramètres) sont différentes. Le compilateur choisit la bonne en fonction des arguments passés.

int Aire(int cote) => cote * cote;                          // carré
int Aire(int longueur, int largeur) => longueur * largeur;  // rectangle
double Aire(double rayon) => Math.PI * rayon * rayon;        // cercle

Aire(5) appelle la première, Aire(4, 3) la deuxième, Aire(2.5) la troisième. C'est le type et le nombre d'arguments qui décident, pas le nom.


# La suite

Tu maîtrises maintenant la syntaxe de base. Les prochains articles de la série :

  • Les objets - classes, constructeurs, propriétés, héritage
  • La mémoire - stack vs heap, value types vs reference types
  • C# vs .NET - ce qui est du langage et ce qui est du framework