MVAESTECH
Guide .NET · 02 · Fondations

Publié le · par Mathieu Vaes

Langage & normes C# / VB

Un code lisible par toute l'équipe vaut mieux qu'un code astucieux. Les normes, les principes SOLID et un usage maîtrisé du polymorphisme en sont la base.

Chapitres du guide
En clair

L'objectif est d'écrire du code que les autres comprendront dans un an. Les conventions de nommage sont l'orthographe commune de l'équipe. Le polymorphisme, c'est pouvoir demander « calcule ton aire » à n'importe quelle forme sans savoir si c'est un cercle ou un carré : chaque forme sait le faire à sa manière.

Les bases indispensables Junior

Avant les normes et les patterns, quatre notions reviennent dans tout le code .NET et dans tous les chapitres de ce guide.

Classe abstraite ou interface ?

InterfaceClasse abstraite
Sens« sait faire » (un contrat)« est une sorte de » (un parent commun)
Héritage multipleOui, une classe implémente plusieurs interfacesNon, un seul parent
État (champs)NonOui
Usage typiquePoint d'injection DI, test avec un faux objetCode commun à une famille de classes

Génériques

Un générique écrit un algorithme une seule fois pour plusieurs types, en gardant la vérification du compilateur. List<int> ne peut contenir que des entiers : l'erreur est détectée à la compilation, pas en production.

// T est un paramètre de type ; la contrainte "where" limite ce qu'il peut être
public static T Max<T>(T a, T b) where T : IComparable<T> => a.CompareTo(b) >= 0 ? a : b;

Max(3, 7);              // T = int  → 7
Max("pomme", "kiwi");   // T = string → "pomme"

Délégués, lambdas et événements

Un délégué est une variable qui contient une méthode. Func<…> renvoie une valeur, Action<…> ne renvoie rien, une lambda (x => x * 2) est une façon courte d'écrire la méthode. Un événement est une liste de délégués à prévenir.

Func<decimal, decimal> ttc = ht => ht * 1.20m;     // prend un decimal, renvoie un decimal
Action<string> afficher = msg => Console.WriteLine(msg);
afficher($"Prix TTC : {ttc(100m):F2}");              // Prix TTC : 120,00

public sealed class Thermostat
{
    public event EventHandler<double>? TemperatureChanged;      // les abonnés s'inscrivent avec +=
    public void Mesurer(double t) => TemperatureChanged?.Invoke(this, t);
}

LINQ et l'exécution différée

Une requête LINQ ne s'exécute pas quand on l'écrit, mais quand on la parcourt (foreach, ToList, Count…). Et la différence entre IEnumerable et IQueryable décide où le filtre s'exécute.

IQueryable<Order> q = db.Orders.Where(o => o.Total > 100);   // rien n'est exécuté
var liste = await q.ToListAsync(ct);                            // ici : SQL avec WHERE Total > 100

IEnumerable<Order> tout = db.Orders.AsEnumerable();
var gros = tout.Where(o => o.Total > 100).ToList();            // ❌ charge TOUTE la table puis filtre en mémoire

var requete = commandes.Where(o => o.EstUrgente);
var n = requete.Count();        // parcourt une fois
var l = requete.ToList();       // parcourt une deuxième fois : matérialiser une fois avec ToList()
TypeOù s'exécute le filtreExemple
IQueryable<T>Traduit et exécuté par la base (SQL)db.Orders.Where(…)
IEnumerable<T>En mémoire, dans votre processusliste.Where(…)

Conventions de nommage (Microsoft) Junior

ÉlémentConventionExemple
Classe, record, struct, enum, méthode, propriété, événementPascalCaseOrderService.PlaceOrderAsync
InterfaceI + PascalCaseIOrderRepository
Paramètre, variable localecamelCasecustomerId
Champ privé_ + camelCase_logger
Champ statique privés_ + camelCase (convention runtime)s_cache
ConstantePascalCaseMaxRetryCount
Méthode asynchroneSuffixe AsyncGetByIdAsync
Paramètre génériqueT ou T + nomTEntity, TKey

VB.NET suit les mêmes casses mais n'est pas sensible à la casse : customerId et CustomerId désignent le même identifiant, d'où l'usage du préfixe _ pour les champs de stockage.

Faire respecter les normes par le build

Directory.Build.props — à la racine de la solution

<Project>
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
    <AnalysisLevel>latest-recommended</AnalysisLevel>
    <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
  </PropertyGroup>
</Project>

Ajoutez un .editorconfig versionné (règles IDE* et CA* avec leur sévérité) : le style devient une erreur de compilation, pas un sujet de revue de code. En VB, l'équivalent indispensable est Option Strict On, Option Explicit On et Option Infer On au niveau du projet.

SOLID, version concrète Confirmé

PrincipeEn une phraseSigne de violation en .NET
S — Responsabilité uniqueUne classe a une seule raison de changerUn OrderService de 2 000 lignes qui valide, calcule, envoie des mails et écrit en base
O — Ouvert / ferméOn ajoute un comportement sans modifier l'existantUn switch sur un type qu'on modifie à chaque nouveau cas
L — Substitution de LiskovUn sous-type respecte le contrat de son parentUn override qui lève NotSupportedException
I — Ségrégation des interfacesDes interfaces petites et cibléesUn IRepository à 25 méthodes dont chaque client utilise 3
D — Inversion des dépendancesDépendre d'abstractions injectéesnew SmtpClient() au milieu d'un handler métier

Le polymorphisme sous ses quatre formes Junior

FormeMécanismeRésoluExemple
Sous-typage (héritage)virtual / override, abstractÀ l'exécution (vtable)Forme.Aire()
Par interfaceinterface + implémentationsÀ l'exécutionIPaymentGateway : Stripe, PayPal
Ad hoc (surcharge)Même nom, signatures différentesÀ la compilationLog(string), Log(Exception)
ParamétriqueGénériques avec contraintesCompilation + JITRepository<TEntity> where TEntity : Entity
Démo · Dispatch virtuelChoisissez le type réel de l'objet.
Forme f = new Cercle(2)new Rectangle(3, 4)new Carre(3);  f.Aire();
Variable : type déclaré Forme
Forme.Aire() — abstract
Forme.ToString() — virtual
Table virtuelle du type réel
Cercle.Aire() → π·r²
Rectangle.Aire() → l·h
Carre hérite de Rectangle.Aire()
→ Cercle.Aire() = π × 2² = 12,566→ Rectangle.Aire() = 3 × 4 = 12→ Rectangle.Aire() (hérité) = 3 × 3 = 9
public abstract class Forme
{
    public abstract double Aire();                       // doit être redéfinie
    public override string ToString() => $"{GetType().Name} ({Aire():F2})";
}

public sealed class Cercle(double rayon) : Forme         // constructeur primaire
{
    public override double Aire() => Math.PI * rayon * rayon;
}

public class Rectangle(double l, double h) : Forme
{
    public override double Aire() => l * h;
}

public sealed class Carre(double c) : Rectangle(c, c);   // hérite de Aire()

// Hiérarchie fermée : le pattern matching est une alternative lisible
static string Decrire(Forme f) => f switch
{
    Cercle            => "rond",
    Carre             => "carré",
    Rectangle         => "rectangle",
    _                 => throw new UnreachableException()
};
Public MustInherit Class Forme
    Public MustOverride Function Aire() As Double
    Public Overrides Function ToString() As String
        Return $"{Me.GetType().Name} ({Aire():F2})"
    End Function
End Class

Public NotInheritable Class Cercle
    Inherits Forme
    Private ReadOnly _rayon As Double
    Public Sub New(rayon As Double)
        _rayon = rayon
    End Sub
    Public Overrides Function Aire() As Double
        Return Math.PI * _rayon * _rayon
    End Function
End Class

' Correspondances : virtual = Overridable, override = Overrides,
' abstract = MustOverride / MustInherit, sealed = NotInheritable / NotOverridable,
' new (masquage) = Shadows, interface = Implements
Piège
  • new (VB : Shadows) n'est pas override : la méthode masquée est choisie selon le type déclaré, pas le type réel. Résultat différent selon la variable utilisée.
  • Appel virtuel dans un constructeur : l'override de la classe dérivée s'exécute avant que son propre constructeur ait initialisé ses champs.
  • Héritage profond : au-delà de deux niveaux, préférez la composition et les interfaces.

C# moderne à connaître Confirmé

FonctionnalitéVersionUsage
Types référence nullablesC# 8Le compilateur signale les NullReferenceException possibles
record, init, withC# 9DTO et Value Objects immuables, égalité par valeur
required, raw string literalsC# 11Propriétés obligatoires à l'initialisation ; JSON/SQL lisibles
Constructeurs primaires, expressions de collection [1, 2]C# 12Moins de code d'injection ; initialisation uniforme
Type System.Threading.Lock, params collectionsC# 13Verrou dédié plus rapide qu'un object
Mot-clé field, membres d'extension, affectation ?.=C# 14Propriétés avec logique sans champ explicite ; propriétés d'extension
// C# 14 : le mot-clé field remplace le champ de stockage explicite
public string Nom
{
    get;
    set => field = value?.Trim() ?? throw new ArgumentNullException(nameof(value));
}

// C# 14 : bloc d'extension, avec propriétés
public static class EnumerableExtensions
{
    extension<T>(IEnumerable<T> source)
    {
        public bool IsEmpty => !source.Any();
    }
}

Exceptions

  • throw; conserve la pile d'appels, throw ex; la perd.
  • Ne capturez Exception qu'aux frontières : middleware, boucle d'un worker, handler d'UI.
  • Une exception signale un cas exceptionnel. Pour un échec métier attendu (validation, introuvable), un type résultat (Result<T>, OneOf) est plus explicite et moins coûteux.
VB.NET en 2026

VB.NET est supporté sur .NET moderne pour les bibliothèques, la console, WinForms et WPF, mais Microsoft n'y ajoute plus de nouvelles fonctionnalités de langage. Il n'y a pas de Razor ni de Blazor en VB. Stratégie courante : garder le code VB existant, écrire les nouveaux modules en C# dans des projets séparés de la même solution — l'interopérabilité via l'IL est totale.

À retenir

  • Les normes sont vérifiées par le build (.editorconfig, analyseurs, TreatWarningsAsErrors), pas par les revues.
  • Polymorphisme par interface pour les points d'extension, pattern matching pour les hiérarchies fermées.
  • Composition plutôt qu'héritage ; sealed par défaut.
  • Nullable activé partout, y compris sur l'existant migré progressivement.

Un projet .NET à cadrer ou à reprendre ?

API REST documentées et testées, applications ASP.NET MVC (y compris VB.NET), migration de services anciens vers une architecture claire : parlons de votre code.