Derniers tips / Mémos
Modélise nativement « exactement l'un de ces types », avec un switch exhaustif vérifié par le compilateur.
Fin des state machines générées par le compilateur : moins d'allocations et des stack traces enfin lisibles.
Recherche sémantique directement dans SQL Server 2025, sans base vectorielle séparée, en LINQ typé.
Framework UI cross-platform pour .NET : Windows, macOS, Linux, iOS, Android et WebAssembly.
Découvrez les nouvelles APIs asynchrones pour la compression ZIP et les améliorations de performance dans .NET 10
C# 14 — Dernières nouveautés
C# 14 arrive avec .NET 10 et Visual Studio 2026, apportant des améliorations majeures : propriétés d'extension, le mot-clé field pour les backing fields automatiques, l'assignation conditionnelle null, les conversions implicites pour Span<T>, et bien d'autres fonctionnalités qui simplifient le code et améliorent la productivité.
Nouveautés C# 14
- Extension members (propriétés et membres statiques)
- Mot-clé field (backing fields automatiques)
- Assignation conditionnelle null (?. et ?[])
- nameof avec types génériques non liés
- Conversions implicites pour Span<T> et ReadOnlySpan<T>
- Modificateurs sur les paramètres lambda simples
- Constructeurs et événements partiels
- Opérateurs d'assignation composée définis par l'utilisateur
Cas d'usage
Extension members (propriétés et membres statiques)
Les blocs d'extension permettent désormais de déclarer des propriétés d'extension, des méthodes statiques d'extension, et même des opérateurs définis par l'utilisateur. Tu peux étendre un type avec des membres d'instance ou statiques.
// Extension block for instance members
extension<TSource>(IEnumerable<TSource> source)
{
// Extension property
public bool IsEmpty => !source.Any();
// Extension method
public IEnumerable<TSource> Where(Func<TSource, bool> predicate)
=> source.Where(predicate);
}
// Extension block for static members
extension<TSource>(IEnumerable<TSource>)
{
// Static extension property
public static IEnumerable<TSource> Identity
=> Enumerable.Empty<TSource>();
// Static extension method
public static IEnumerable<TSource> Combine(
IEnumerable<TSource> first,
IEnumerable<TSource> second)
=> first.Concat(second);
// User-defined operator
public static IEnumerable<TSource> operator + (
IEnumerable<TSource> left,
IEnumerable<TSource> right)
=> left.Concat(right);
}
// Usage
var numbers = new[] { 1, 2, 3 };
bool empty = numbers.IsEmpty;
var identity = IEnumerable<int>.Identity;
var combined = list1 + list2;Mot-clé field (backing fields automatiques)
Le mot-clé field te permet d'accéder au backing field généré automatiquement dans les accesseurs de propriétés, sans avoir à déclarer un champ explicite. Idéal pour ajouter de la validation ou de la logique personnalisée.
// Before C# 14: explicit backing field
private string _message;
public string Message
{
get => _message;
set => _message = value ?? throw new ArgumentNullException(nameof(value));
}
// With C# 14: field keyword
public string Message
{
get;
set => field = value ?? throw new ArgumentNullException(nameof(value));
}
// Custom getter and setter
public int Age
{
get => field;
set => field = value >= 0 ? value : throw new ArgumentException("Age must be positive");
}
// Disambiguation if you have a field named 'field'
private string field = "actual field";
public string Data
{
get;
set => @field = value; // or this.field
}Assignation conditionnelle null (?. et ?[])
Les opérateurs ?. et ?[] peuvent maintenant être utilisés côté gauche d'une assignation ou d'une assignation composée. Si la cible est null, l'assignation est simplement ignorée et le côté droit n'est pas évalué.
// Before C# 14
if (customer is not null)
{
customer.Order = GetCurrentOrder();
}
// With C# 14: null-conditional assignment
customer?.Order = GetCurrentOrder();
// Works with compound assignment
cart?.TotalAmount += item.Price;
order?.Quantity -= 1;
settings?.MaxRetries *= 2;
// Right side only evaluated if left is not null
customer?.LastOrder = ExpensiveOrderLookup(); // ExpensiveOrderLookup() not called if customer is null
// Note: ++ and -- are not supported
// counter?.Value++; // Compilation errornameof avec types génériques non liés
Tu peux maintenant utiliser nameof avec des types génériques non liés comme List<> ou Dictionary<,>. Utile pour le logging, les messages d'erreur et la réflexion sans avoir à spécifier les arguments de type.
// C# 14: nameof with unbound generic types
var listName = nameof(List<>); // "List"
var dictName = nameof(Dictionary<,>); // "Dictionary"
var genericName = nameof(MyClass<,>); // "MyClass"
// Before C# 14: you needed a closed type
var oldWay = nameof(List<int>); // "List"
// Useful for logging, error messages, reflection
public void LogGenericType<T>()
{
Console.WriteLine($"Working with {nameof(List<>)}<{typeof(T).Name}>");
}
// In exception messages
throw new InvalidOperationException(
$"{nameof(Dictionary<,>)} cannot be empty");Conversions implicites pour Span<T> et ReadOnlySpan<T>
C# 14 introduit un support de première classe pour Span<T> et ReadOnlySpan<T> avec de nouvelles conversions implicites. Les tableaux peuvent être convertis automatiquement en Span, facilitant l'utilisation de ces types pour la performance.
// Implicit conversions between arrays and spans
int[] array = { 1, 2, 3, 4, 5 };
// Array to Span<T>
Span<int> span = array;
// Array to ReadOnlySpan<T>
ReadOnlySpan<int> readOnlySpan = array;
// Span as extension method receiver
public static class SpanExtensions
{
extension(Span<int> data)
{
public int Sum() => data.ToArray().Sum();
}
}
int[] numbers = { 1, 2, 3 };
int total = numbers.Sum(); // Array implicitly converted to Span
// Better generic type inference
void ProcessData<T>(ReadOnlySpan<T> data) { /* ... */ }
int[] values = { 1, 2, 3 };
ProcessData(values); // T inferred as int, array converted to ReadOnlySpan<int>
// Works in method overload resolution
public void Display(IEnumerable<int> items) => Console.WriteLine("IEnumerable");
public void Display(ReadOnlySpan<int> items) => Console.WriteLine("ReadOnlySpan");
int[] data = { 1, 2, 3 };
Display(data); // Calls ReadOnlySpan overload in C# 14Modificateurs sur les paramètres lambda simples
Tu peux ajouter des modificateurs comme ref, in, out, scoped, ou ref readonly aux paramètres lambda sans avoir à spécifier explicitement leurs types. Simplifie la déclaration de lambdas complexes.
// Before C# 14: modifiers required explicit types
delegate bool TryParse<T>(string text, out T result);
TryParse<int> oldWay = (string text, out int result)
=> Int32.TryParse(text, out result);
// C# 14: modifiers without types
TryParse<int> parse = (text, out result)
=> Int32.TryParse(text, out result);
// ref modifier
Func<ref int, int> increment = (ref value) => ++value;
// in modifier (read-only reference)
Func<in int, int> doubleValue = (in value) => value * 2;
// scoped modifier (limits lifetime)
delegate void Process(scoped ref int value);
Process processInt = (scoped ref value) => value += 10;
// ref readonly
Func<ref readonly int, int> readValue = (ref readonly value) => value;
// Note: params still requires explicit type
// Func<params int[], int> sum = (values) => values.Sum(); // Error
Func<params int[], int> sum = (params int[] values) => values.Sum(); // OKConstructeurs et événements partiels
Les constructeurs d'instance et les événements peuvent maintenant être déclarés comme membres partiels, avec une déclaration de définition et une déclaration d'implémentation. Très utile avec les source generators.
// Partial constructors
public partial class Customer
{
// Defining declaration
public partial Customer(string name);
}
public partial class Customer
{
// Implementing declaration
public partial Customer(string name)
{
Name = name;
CreatedAt = DateTime.UtcNow;
}
}
// Partial events
public partial class EventPublisher
{
// Defining declaration
public partial event EventHandler DataChanged;
}
public partial class EventPublisher
{
private EventHandler _dataChanged;
// Implementing declaration with add/remove
public partial event EventHandler DataChanged
{
add => _dataChanged += value;
remove => _dataChanged -= value;
}
}
// Useful for source generators
public partial class GeneratedModel
{
// Generated part defines constructor
public partial GeneratedModel();
// Manual part implements it
public partial GeneratedModel()
{
// Custom initialization logic
}
}Opérateurs d'assignation composée définis par l'utilisateur
Si tu définis un opérateur binaire (+, -, *, etc.), l'opérateur d'assignation composée correspondant (+=, -=, *=) fonctionne automatiquement. Inclut aussi les opérateurs ++ et -- personnalisés.
// Custom type with compound assignment operators
public struct Vector
{
public double X { get; set; }
public double Y { get; set; }
// User-defined + operator
public static Vector operator +(Vector a, Vector b)
=> new Vector { X = a.X + b.X, Y = a.Y + b.Y };
// C# 14: += automatically works based on +
// No need to define operator += separately
}
Vector v1 = new Vector { X = 1, Y = 2 };
Vector v2 = new Vector { X = 3, Y = 4 };
v1 += v2; // Works automatically! v1 is now (4, 6)
// Works for any operator you define
public struct Counter
{
public int Value { get; set; }
public static Counter operator *(Counter c, int multiplier)
=> new Counter { Value = c.Value * multiplier };
}
Counter counter = new Counter { Value = 10 };
counter *= 5; // Value is now 50
// User-defined increment/decrement
public struct Score
{
public int Points { get; set; }
public static Score operator ++(Score s)
=> new Score { Points = s.Points + 100 };
public static Score operator --(Score s)
=> new Score { Points = s.Points - 50 };
}
Score score = new Score { Points = 0 };
score++; // Points is now 100
score++; // Points is now 200
score--; // Points is now 150Résumé
C# 14 continue d'améliorer la productivité et la lisibilité du code avec des fonctionnalités ciblées. Adopte ces nouveautés progressivement sur tes projets .NET 10 pour réduire le boilerplate et améliorer les performances.
Avantages
- Moins de code boilerplate avec field et extension members
- Code plus sûr avec l'assignation conditionnelle null
- Meilleures performances avec les conversions implicites Span
- Lambdas plus expressives avec modificateurs sans types
- Meilleure intégration avec les source generators (partial members)
Inconvénients
- Nécessite .NET 10 SDK et Visual Studio 2026
- Courbe d'apprentissage pour les nouvelles syntaxes
- Risque de confusion avec le mot-clé field si tu as déjà un membre nommé 'field'
Bonnes pratiques
- Utilise field pour simplifier les propriétés avec validation personnalisée
- Préfère l'assignation conditionnelle null (?.=) aux vérifications if explicites
- Utilise nameof avec types génériques non liés pour les messages d'erreur et le logging
- Adopte les conversions implicites Span pour améliorer les performances sans sacrifier la lisibilité
- Documente les extension members clairement pour éviter la confusion avec les membres natifs