L'écosystème .NET continue d'évoluer avec un objectif précis : réduire le code répétitif (boilerplate) sans compromettre la sécurité du typage ni la performance. Avec le lancement de .NET 8 et de C# 12, Microsoft propose des améliorations axées sur la productivité des développeurs, transformant les patrons verbeux en constructions natives élégantes.
Pour les développeurs qui conçoivent des API Web, des microservices et des applications d'entreprise, deux fonctionnalités se démarquent : les Primary Constructors dans les classes et structures ordinaires et les Collection Expressions avec l'opérateur spread (..). Si vous en avez assez de déclarer des champs privés readonly pour l'injection de dépendances ou d'utiliser des syntaxiques incohérentes pour initialiser des tableaux et listes, C# 12 répond directement à ces besoins.
Dans cet article pratique, nous allons refactoriser du code hérité écrit en .NET 6/7 vers les conventions modernes de C# 12 et .NET 8, en combinant Clean Code et Domain-Driven Design (DDD) avec l'optimisation mémoire.
1. Primary Constructors (Constructeurs Primaires)
Le Problème du Code Hérité
Dans .NET 6 et .NET 7, l'injection de dépendances dans les services d'application nécessitait la déclaration explicite de champs privés readonly et d'un constructeur verbeux.
C#
// ❌ Modèle Traditionnel (.NET 6 / .NET 7)
namespace PaymentContext.Domain.Services;
public class PaymentProcessor : IPaymentProcessor
{
private readonly IPaymentRepository _paymentRepository;
private readonly INotificationService _notificationService;
private readonly ILogger<PaymentProcessor> _logger;
public PaymentProcessor(
IPaymentRepository paymentRepository,
INotificationService notificationService,
ILogger<PaymentProcessor> logger)
{
_paymentRepository = paymentRepository ?? throw new ArgumentNullException(nameof(paymentRepository));
_notificationService = notificationService ?? throw new ArgumentNullException(nameof(notificationService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task ProcessAsync(Payment payment, CancellationToken cancellationToken)
{
_logger.LogInformation("Traitement du paiement {PaymentId}", payment.Id);
await _paymentRepository.SaveAsync(payment, cancellationToken);
await _notificationService.NotifySuccessAsync(payment.CustomerEmail);
}
}
La Solution avec C# 12 (.NET 8)
Grâce aux Primary Constructors, les paramètres sont déclarés directement dans l'en-tête de la classe et deviennent automatiquement accessibles dans tout le corps de celle-ci.
C#
// ✅ Modèle C# 12 (.NET 8)
namespace PaymentContext.Domain.Services;
public class PaymentProcessor(
IPaymentRepository paymentRepository,
INotificationService notificationService,
ILogger<PaymentProcessor> logger) : IPaymentProcessor
{
public async Task ProcessAsync(Payment payment, CancellationToken cancellationToken)
{
// Les paramètres du constructeur primaire sont accessibles directement
logger.LogInformation("Traitement du paiement {PaymentId}", payment.Id);
await paymentRepository.SaveAsync(payment, cancellationToken);
await notificationService.NotifySuccessAsync(payment.CustomerEmail);
}
}
2. Collection Expressions et l'Opérateur Spread (..)
Syntaxe Fragmentée dans .NET 6/7
Initialiser et fusionner différentes collections exigeait des méthodes distinctes selon le type de données sous-jacent (new[], List<T>, Span<T>).
C#
// ❌ Initialisation et concaténation héritées (.NET 6 / .NET 7)
int[] baseCodes = new int[] { 101, 102, 103 };
int[] extraCodes = new int[] { 201, 202 };
List<int> allCodesList = new List<int>(baseCodes);
allCodesList.AddRange(extraCodes);
allCodesList.Add(301);
int[] result = allCodesList.ToArray();
Unification avec C# 12 et les Crochets [...]
Les Collection Expressions unifient l'initialisation des collections via des crochets [...] et l'opérateur spread .. pour aplatir les structures.
C#
// ✅ Modèle C# 12 (.NET 8)
int[] baseCodes = [101, 102, 103]; int[] extraCodes = [201, 202]; // Concaténation claire et déclarative avec l'opérateur spread (..) List<int> allCodesList = [.. baseCodes, .. extraCodes, 301]; // Allocation directe en mémoire pile/assembly via ReadOnlySpan sans pression sur le Garbage Collector ReadOnlySpan<byte> activeStatus = [1, 2, 4];
Cas d'Usage Réels et Performance
Quand les Utiliser
- Primary Constructors : Idéaux pour l'Injection de Dépendances dans les services d'application, handlers MediatR, cas d'utilisation et contrôleurs d'API.
- Collection Expressions : Parfaites pour initialiser des tampons mémoire, retourner des collections immuables et manipuler ReadOnlySpan<T> avec zéro allocation sur le Tas (Heap).
Quand les ÉVITER
- Champs Mutables dans les Primary Constructors : Notez que les paramètres de constructeurs primaires dans les classes ordinaires ne sont pas automatiquement des champs readonly (contrairement aux records). Si une méthode réassigne la valeur d'un paramètre, cela peut entraîner des effets secondaires indésirables. Si l'immutabilité est obligatoire dans votre domaine (DDD), attribuez explicitement le paramètre à un champ readonly ou utilisez un type record.
- Initialisations Complexes : Si le constructeur requiert des validations élaborées ou l'exécution de méthodes d'initialisation lourdes, privilégiez un constructeur traditionnel.
Gains de Performance dans .NET 8
- Zero-Allocation Data Spans : Lors de l'initialisation d'un ReadOnlySpan<T> avec une Collection Expression, le compilateur C# 12 inscrit les octets directement dans le segment de données de l'assembly, évitant toute allocation sur le Tas.
- Optimisation de Capacité : Lors de la création d'une List<T> à l'aide d'éléments spread ([.. a, .. b]), le compilateur calcule la capacité totale requise au préalable, évitant ainsi les redimensionnements internes du tableau.
Conclusion
C# 12 et .NET 8 offrent des gains tangibles en matière de productivité et de performance d'exécution. L'adoption des Primary Constructors et des Collection Expressions permet de produire un code concis, lisible et performant.
Commencez à refactoriser vos services .NET dès aujourd'hui pour tirer parti de ces fonctionnalités!
👉 Prêt à propulser votre carrière en développement informatique? Découvrez les meilleures opportunités TI et continuez d'évoluer grâce à la communauté MasterStack!