Accueil › Les fondations › Chapitre 2

Le vocabulaire de base

Les vingt mots qu'on emploie dans toutes les conversations, tous les tutoriels et tous les messages d'erreur — et qu'on n'ose plus demander au bout de six mois. Ce chapitre est fait pour être relu sans honte.

Le plus petit programme du monde

Program.cs — un programme .NET complet
Console.WriteLine("Bonjour !");

Une ligne. Pas de classe, pas de Main, pas de cérémonie. C'est ce qu'on appelle les instructions de haut niveau (top-level statements) : depuis C# 9, le compilateur écrit pour toi l'enrobage. Si tu vois du code plus long dans un vieux tutoriel, c'est exactement la même chose :

Ce que le compilateur génère en réalité
internal class Program
{
    private static void Main(string[] args)
    {
        Console.WriteLine("Bonjour !");
    }
}

Main est le point d'entrée : la première méthode exécutée. Il y en a un seul par programme exécutable.

Anatomie d'une ligne de code

var total = Calculer( prix , 3 ) ; type déduit nom de la variable affectation (« reçoit ») appel de méthode arguments passés fin d'instruction
Une instruction se termine par un point-virgule. Une expression (ici l'appel à Calculer(...)) produit une valeur.
MotDéfinition sans jargonExemple
VariableUn nom donné à une valeur qu'on veut réutiliserint age = 42;
TypeLa nature de la valeur, et donc ce qu'on peut en faireint, string, Client
LittéralUne valeur écrite en dur dans le code42, "salut", true
OpérateurUn symbole qui agit sur des valeurs+ - * / == && ??
ExpressionTout ce qui produit une valeurprix * 1.2m
InstructionTout ce qui fait quelque chose, terminé par ;total = 12;
BlocUn groupe d'instructions entre accolades. Définit une portée{ … }
Mot-cléUn mot réservé du langageif, class, return
IdentificateurUn nom que tu choisistotal, Client
Bon réflexe : var n'est pas « n'importe quoi »

var total = 12.5m; crée une variable de type decimal, définitivement. Le type est déduit une fois, à la compilation ; il ne changera jamais. Ce n'est pas le var de JavaScript ni le typage dynamique de Python. (Le vrai équivalent dynamique, dynamic, existe mais ne sert quasiment jamais.)

Méthodes : des machines à transformer

CalculerTotal la logique, à l'intérieur (encapsulée) prix : decimal quantité : int paramètres (entrées) decimal valeur de retour (sortie)
Une méthode reçoit des paramètres, fait un travail, et renvoie éventuellement une valeur. void = elle ne renvoie rien.
// ┌─ modificateur d'accès      ┌─ nom            ┌─ paramètres
// │      ┌─ type de retour     │                 │
public decimal CalculerTotal(decimal prix, int quantite)
{
    return prix * quantite;      // ce que la méthode rend
}

// Version courte, identique : « corps d'expression »
public decimal CalculerTotal2(decimal prix, int quantite) => prix * quantite;

// Ne renvoie rien : void
public void Journaliser(string message) => Console.WriteLine(message);

Paramètres : optionnels, nommés, surcharges

// valeur par défaut → l'appelant peut omettre le paramètre
public decimal Total(decimal prix, int quantite = 1, decimal remise = 0m)
    => prix * quantite * (1 - remise);

Total(10m);                          // 10
Total(10m, 3);                       // 30
Total(10m, remise: 0.5m);            // 5   ← argument nommé : on saute quantite
Total(prix: 10m, quantite: 2);       // 20  ← nommer rend l'appel lisible

Plusieurs méthodes peuvent porter le même nom si leurs paramètres diffèrent : c'est la surcharge. Le compilateur choisit selon les types passés.

Piège : la valeur par défaut est figée chez l'appelant

Une valeur par défaut est recopiée dans le code appelant à la compilation. Si tu changes remise = 0m en remise = 0.1m dans une bibliothèque, les projets déjà compilés qui l'utilisent continuent d'envoyer 0m jusqu'à leur recompilation. Pour du code public partagé, préfère une surcharge explicite.

Classe, objet, membres

L'image

La classe est le plan de la maison. L'objet est une maison construite. Un plan, trois cents maisons : même forme, adresses et habitants différents. On ne dort pas dans un plan.

public class CompteBancaire            // le plan
{
    private decimal _solde;            // CHAMP : stockage interne, caché

    public string Titulaire { get; }   // PROPRIÉTÉ : lecture seule de l'extérieur
    public decimal Solde => _solde;    // PROPRIÉTÉ calculée

    public CompteBancaire(string titulaire, decimal soldeInitial)  // CONSTRUCTEUR
    {
        Titulaire = titulaire;
        _solde = soldeInitial;
    }

    public void Deposer(decimal montant)          // MÉTHODE
    {
        if (montant <= 0) throw new ArgumentException("Montant invalide");
        _solde += montant;
    }
}

// Utilisation : on INSTANCIE avec new
var compte = new CompteBancaire("Ada", 100m);   // un OBJET
compte.Deposer(50m);
Console.WriteLine(compte.Solde);                 // 150
// compte._solde = 0;   ← impossible : _solde est private
MembreRôleConvention
ChampStocker une donnée, en interneprivate decimal _solde; — souligné + camelCase
PropriétéExposer une donnée avec contrôlepublic string Nom { get; set; } — PascalCase
MéthodeAgirpublic void Deposer(...) — un verbe
ConstructeurMettre l'objet dans un état valide dès sa naissancemême nom que la classe
Bon réflexe : propriété par défaut, champ seulement si besoin

Écris public string Nom { get; set; } plutôt qu'un champ public. Une propriété se transforme plus tard en calcul, en validation ou en journalisation sans casser le code qui l'utilise. Un champ public rendu privé, lui, casse tout le monde.

Statique ou d'instance ?

D'instance — appartient à l'objet
var c1 = new CompteBancaire("Ada", 100m);
var c2 = new CompteBancaire("Bob", 50m);
c1.Deposer(10m);        // n'affecte que c1
// chaque objet a SON solde
Statique — appartient au type
Math.Max(1, 2);            // pas de new Math()
DateTime.Now;              // partagé
CompteBancaire.TauxLivretA // un seul pour tous

Pratique, mais un état statique mutable est partagé par toute l'application, y compris entre requêtes web simultanées : source classique de bugs.

Namespace, assembly, projet : les poupées russes

Solution (.sln) — un simple regroupement Projet Boutique.Api → Boutique.Api.dll namespace Boutique.Api.Commandes class ServiceCommande public Task Creer(...) private void Valider(...) Projet Boutique.Domaine → Boutique.Domaine.dll namespace Boutique.Domaine class Commande, class Client… Frontière d'assembly : « internal » s'arrête ici. Api référence Domaine, jamais l'inverse.
Le namespace est un rangement logique (des dossiers pour les noms). L'assembly est une unité physique de livraison — et une frontière de visibilité.
namespace Boutique.Api.Commandes;   // namespace « de fichier » (C# 10+) : pas d'accolades

using Boutique.Domaine;             // « je veux pouvoir écrire Commande sans le préfixer »
using static System.Math;           // importe les membres statiques : Max(...) directement
using Journal = Serilog.Log;        // alias, en cas de conflit de noms

public class ServiceCommande { }

using n'inclut ni ne copie rien : c'est juste un raccourci d'écriture. Sans lui, il faudrait écrire Boutique.Domaine.Commande à chaque fois.

using implicites et globaux

<ImplicitUsings>enable</ImplicitUsings> ajoute automatiquement les using les plus courants (System, System.Linq, System.Collections.Generic…), d'où les fichiers sans aucun using visible. Tu peux compléter la liste une fois pour tout le projet dans un fichier GlobalUsings.cs :

global using Boutique.Domaine;
global using Microsoft.EntityFrameworkCore;

Les structures de contrôle en une page

// --- Condition ---
if (age >= 18)         { /* ... */ }
else if (age >= 16)    { /* ... */ }
else                   { /* ... */ }

var etiquette = age >= 18 ? "majeur" : "mineur";      // opérateur ternaire

// --- Aiguillage (switch expression, la forme moderne) ---
var tarif = categorie switch
{
    "enfant"  => 5m,
    "senior"  => 8m,
    "adulte"  => 12m,
    _         => throw new ArgumentException("catégorie inconnue")   // _ = tous les autres cas
};

// --- Boucles ---
for (int i = 0; i < 10; i++)          { }       // compteur maîtrisé
foreach (var client in clients)        { }      // parcourir une collection (le plus fréquent)
while (!termine)                       { }      // tant que
do { } while (!termine);                        // au moins une fois

foreach (var c in clients)
{
    if (c.Inactif) continue;    // passe au suivant
    if (c.Bloque)  break;       // sort de la boucle
}
Bon réflexe

foreach par défaut : plus lisible et impossible de se tromper d'indice. for seulement quand tu as vraiment besoin de la position. Et souvent, ni l'un ni l'autre : LINQ dit la même chose en une ligne (chapitre 7).

Les conventions de nommage (elles comptent)

ÉlémentConventionExemple
Classe, record, struct, enumPascalCaseServiceCommande
InterfacePascalCase précédé de IIEnvoyeurEmail
Méthode, propriété, événementPascalCaseCalculerTotal, Solde
Paramètre, variable localecamelCaseprixUnitaire
Champ privé_ + camelCase_solde
ConstantePascalCase (pas de MAJUSCULES en C#)const int TailleMax = 100;
Méthode asynchronesuffixe AsyncChargerAsync
Fichierun type public par fichier, même nomServiceCommande.cs

Ces règles ne sont pas de la coquetterie : tout l'écosystème les suit, donc un nom qui les respecte se lit sans effort. dotnet format et les analyseurs les vérifient automatiquement.

Commentaires et documentation

// commentaire sur une ligne

/* commentaire
   sur plusieurs lignes */

/// <summary>Calcule le total TTC d'une ligne de commande.</summary>
/// <param name="prix">Prix unitaire hors taxes.</param>
/// <returns>Le montant TTC arrondi au centime.</returns>
/// <exception cref="ArgumentOutOfRangeException">Si le prix est négatif.</exception>
public decimal TotalTtc(decimal prix, int quantite) { /* ... */ }

Les commentaires /// apparaissent dans l'autocomplétion de l'éditeur et alimentent la documentation OpenAPI de tes API (voir chapitre 14). C'est le seul type de commentaire qui « sort » du fichier.

Un bon commentaire dit POURQUOI

// on incrémente i n'apporte rien : le code le dit déjà. // TVA à 8,5 % : taux dérogatoire Corse, cf. ticket #1042 sauve la journée de celui qui relira dans deux ans. Si tu as besoin d'un commentaire pour expliquer ce que fait le code, c'est souvent que le code mérite d'être renommé ou découpé.

Lire un message d'erreur du compilateur

Les erreurs C# ont un code (CSxxxx) et sont, contrairement à leur réputation, très précises. Voici celles que tu verras cent fois :

CodeMessage (traduit)Cause réelle, en clair
CS0103Le nom « x » n'existe pas dans ce contexteFaute de frappe, ou variable déclarée dans un autre bloc (portée)
CS0246Le type ou espace de noms « Client » est introuvableusing manquant, ou référence de projet/paquet absente
CS0029 / CS1503Impossible de convertir « string » en « int »Tu passes le mauvais type ; utilise int.TryParse
CS0161Tous les chemins de code ne retournent pas de valeurUn if sans else qui oublie de return
CS8602Déréférencement possible d'une référence nullLe compilateur t'avertit d'un NullReferenceException probable (ch. 9)
CS0120Une référence d'objet est nécessaire pour un membre non statiqueTu appelles une méthode d'instance depuis un contexte statique, sans objet
CS1061« Client » ne contient pas de définition pour « Nom »Nom de membre erroné, ou membre non public depuis l'extérieur
Bon réflexe

Corrige toujours la première erreur de la liste et recompile : les suivantes sont souvent des conséquences en cascade. Et cherche le code exact (« CS0246 ») plutôt que la phrase : c'est plus efficace.

Vérifie que c'est passé

🎯 Quiz — 4 questions

1. Quelle est la différence entre un champ et une propriété ?

Le champ est le stockage brut, généralement private. La propriété est un guichet : elle peut valider, journaliser, calculer, ou devenir en lecture seule — sans casser les appelants.

2. using System.Text.Json; en haut d'un fichier…

C'est un pur raccourci d'écriture. La référence à l'assembly, elle, vient du .csproj (ou de la bibliothèque de base).

3. Math.Max(3, 7) s'appelle sans new Math(). Pourquoi ?

Un membre static existe en un seul exemplaire, attaché au type. Pas besoin d'instance — d'où l'absence de new.

4. Le compilateur dit CS0103 : le nom « total » n'existe pas dans ce contexte, alors que tu l'as bien déclaré. Que regardes-tu en premier ?

Une variable déclarée dans un if ou une boucle meurt à l'accolade fermante. Déclare-la avant le bloc si tu en as besoin après.

Fiches de révision

Expression vs instruction ?
Une expression produit une valeur (a + b). Une instruction fait quelque chose et finit par ;.
Classe vs objet ?
La classe est le plan, l'objet la maison construite. new transforme le plan en maison.
Namespace vs assembly ?
Namespace = rangement logique des noms. Assembly = fichier .dll produit par un projet, et frontière de internal.
Que fait var ?
Il laisse le compilateur déduire le type, une fois pour toutes. Le typage reste strict.
Convention champ privé ?
private readonly string _nom; — souligné puis camelCase.
Suffixe des méthodes async ?
Async : ChargerClientAsync. Convention universelle dans l'écosystème.
À retenir