mercredi 8 octobre 2025

Q/A Performance et Optimisation



🔹 1. Utiliser le cache en mémoire en ASP.NET Core

✅ MemoryCache

  • IMemoryCache permet de stocker des objets en mémoire pour réduire les accès répétés aux sources lentes (DB, API, calculs lourds).

  • Exemple d’enregistrement et utilisation :

// Dans Program.cs
builder.Services.AddMemoryCache();

// Dans un service
public class TradeService
{
    private readonly IMemoryCache _cache;
    public TradeService(IMemoryCache cache) => _cache = cache;

    public double GetPrice(string symbol)
    {
        return _cache.GetOrCreate(symbol, entry =>
        {
            entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5);
            return FetchPriceFromMarket(symbol); // appel lourd
        });
    }
}

✅ Points clés :

  • AbsoluteExpirationRelativeToNow : durée avant expiration

  • SlidingExpiration : réinitialise le délai à chaque accès

  • Idéal pour données fréquentes mais non critiques en temps réel


🔹 2. Response Compression en ASP.NET Core

  • Objectif : réduire la taille des réponses HTTP pour améliorer le throughput et la latence.

  • Activation simple :

builder.Services.AddResponseCompression(options =>
{
    options.Providers.Add<GzipCompressionProvider>();
    options.EnableForHttps = true;
});

app.UseResponseCompression();
  • Configure le niveau de compression :

builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
    options.Level = System.IO.Compression.CompressionLevel.Fastest;
});

✅ Utile pour API REST, SignalR ou dashboards temps réel.


🔹 3. Diagnostiquer et améliorer les performances d’une application .NET Core

✅ Étapes principales :

  1. Mesurer avant d’optimiser : utiliser Stopwatch, counters, diagnostics.

  2. Profiler : identifier hot paths et contention.

  3. Optimisation mémoire :

    • Minimiser allocations temporaires

    • Réutiliser buffers (ArrayPool<T>)

    • Eviter boxing/unboxing

  4. Optimisation CPU :

    • Parallelism contrôlé (Task.Run, Parallel.ForEach)

    • Lock-free ou ConcurrentCollections pour concurrence

  5. Optimisation I/O :

    • Async/await pour appels DB, API, fichiers

    • Compression et caching

  6. Monitoring en production :

    • EventCounters, Metrics, Logging, AppInsights


🔹 4. Configurer le logging en .NET Core

  • ILogger (intégré) :

public class TradeService
{
    private readonly ILogger<TradeService> _logger;
    public TradeService(ILogger<TradeService> logger) => _logger = logger;
    public void ExecuteTrade(string symbol)
    {
        _logger.LogInformation("Trade executed for {Symbol}", symbol);
    }
}
  • Serilog :

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.File("logs/trades.txt")
    .CreateLogger();

builder.Host.UseSerilog();
  • NLog :

builder.Logging.ClearProviders();
builder.Logging.AddNLog("nlog.config");

✅ Permet de centraliser et filtrer les logs par niveau (Info, Warning, Error) pour diagnostic et analyse perf.


🔹 5. Outils pour profiler une application .NET Core

Outil Description
Visual Studio Profiler CPU, mémoire, allocation, contention, hot paths
dotTrace / dotMemory (JetBrains) Profiling avancé, analyse heap et threads
PerfView Analyse GC, allocations, CPU, thread contention
BenchmarkDotNet Micro-benchmark pour comparer implémentations
dotnet-counters / dotnet-trace / dotnet-dump Outils CLI pour diagnostics runtime
Application Insights / Prometheus / Grafana Monitoring en production

✅ Bonnes pratiques :

  • Profiler en environnement représentatif (load test ou staging)

  • Identifier hot paths avant d’optimiser

  • Mesurer l’impact des optimisations


📘 Résumé des pratiques de performance et optimisation

Domaine Bonnes pratiques
Caching IMemoryCache, expiration relative/sliding, ArrayPool
Compression Gzip/ Brotli via ResponseCompression
Monitoring Stopwatch, counters, EventCounters
Profiling Visual Studio Profiler, dotTrace, PerfView
Logging ILogger, Serilog, NLog, structured logs
Async/Parallélisme Task.Run, Parallel, async/await, lock-free
I/O Async DB/HTTP, batching, compression
Allocation mémoire Minimiser GC pressure, reuse buffers, avoid boxing


Q/A Concepts de Base sur Singleton



🔹 1. Qu'est-ce qu'un Singleton en programmation orientée objet ?

  • Définition : Le pattern Singleton garantit qu’une classe n’a qu’une seule instance et fournit un point d’accès global à cette instance.

  • Exemple conceptuel :

public class Singleton
{
    private static Singleton _instance;
    private Singleton() { }
    public static Singleton Instance => _instance ??= new Singleton();
}

🔹 2. Pourquoi utilise-t-on le pattern Singleton ?

  • Contrôler l’accès à une ressource partagée (ex : cache, configuration, moteur de calcul).

  • Assurer cohérence des données et éviter plusieurs instances coûteuses.

  • Simplifier la gestion de configuration globale ou de services centralisés.


🔹 3. Caractéristiques principales d’un Singleton

  1. Instance unique : toujours le même objet.

  2. Accès global : souvent via Singleton.Instance.

  3. Constructeur privé : empêche la création d’autres instances.

  4. Thread-safe : la création concurrente doit être sécurisée.

  5. Lazy initialization : l’instance est souvent créée uniquement à la première utilisation.


🔹 4. Différence entre Singleton et classe statique

Critère Singleton Classe statique
Instance Unique instance sur le heap Pas d’instance, tout static
Héritage Peut implémenter interface ou hériter Pas d’héritage
Interface Peut implémenter des interfaces Non
Cycle de vie Contrôlé, lazy possible Instanciée dès le chargement du type
Utilisation Ressources partagées avec état Méthodes utilitaires stateless

🔹 5. Comment garantir qu’une classe n’a qu’une seule instance ?

  • Constructeur privé pour empêcher new.

  • Static property pour stocker l’instance.

  • Lazy initialization thread-safe :

public class Singleton
{
    private static readonly Lazy<Singleton> _instance = new(() => new Singleton());
    public static Singleton Instance => _instance.Value;
    private Singleton() { }
}

🔹 6. Pourquoi le constructeur privé est-il important ?

  • Empêche toute création directe via new.

  • Garantit que l’accès à l’instance se fait uniquement via la propriété statique.

  • Essentiel pour maintenir l’unicité de l’objet.


🔹 7. Singleton en mémoire vs Singleton distribué

Type Description Exemple
Singleton en mémoire Une instance unique dans un processus Cache en mémoire locale
Singleton distribué Une instance unique accessible par plusieurs processus ou serveurs Redis cache, service central de configuration

✅ Singleton distribué nécessite un coordonnateur externe ou un store partagé pour maintenir l’unicité.


🔹 8. Fonctionnement du Garbage Collector dans .NET Core

  • GC générationnel : Gen0, Gen1, Gen2

  • Allocations sur le heap managé

  • Collections :

    • Gen0 : objets temporaires → collecte rapide

    • Gen1 : objets intermédiaires

    • Gen2 : objets long-lived → moins fréquents

  • Impact sur Singleton :

    • Une instance statique (Singleton) vit tant que l’application est active, peu de risque d’être collectée.


🔹 9. Principales nouveautés introduites dans .NET 6 / 7 / 8

Version Nouveautés clés
.NET 6 Minimal APIs, Hot Reload, C# 10 features (global using, file-scoped namespace), MAUI, performance improvements
.NET 7 Amélioration JIT et GC, LINQ enhancements, performance HTTP, DateOnly/TimeOnly
.NET 8 Native AOT, rate-limited HTTP clients, improved JSON serialization, further GC/Threading optimization

🔹 10. Exécution multi-plateforme dans .NET Core

  • .NET Core / .NET 5+ est cross-platform : Windows, Linux, macOS

  • Compilation JIT sur la plateforme cible, ou AOT (Ahead-of-Time) pour native binaries

  • Runtimes partagés : une seule base de code peut s’exécuter sur plusieurs OS sans modification

  • Utilisation des bibliothèques .NET Standard / .NET 6+ pour compatibilité multi-plateforme


📘 Synthèse des concepts Singleton et .NET

Concept Points clés
Singleton Instance unique, constructeur privé, accès global
Classe statique Pas d’instance, tout statique, utilitaires
Singleton distribué Nécessite coordination externe
Garbage Collector Générations 0/1/2, Singleton vivant tant que l’application
.NET 6/7/8 Minimal APIs, Hot Reload, MAUI, AOT, perf améliorée
Multi-plateforme JIT / AOT, Windows/Linux/macOS, .NET Standard


Q/A Multithreading et Concurrence



🔹 1. Pourquoi ConcurrentDictionary<TKey, TValue> est préféré à Dictionary en multi-thread ?

  • Thread-safe pour lecture et écriture concurrentes.

  • Evite les exceptions et incohérences de données (InvalidOperationException ou corruption de mémoire).

  • Optimisé pour opérations atomiques comme TryAdd, TryUpdate, GetOrAdd.

  • Exemple :

var dict = new ConcurrentDictionary<string, double>();
dict.TryAdd("AAPL", 150.5);
dict.AddOrUpdate("AAPL", 150.5, (key, oldValue) => oldValue + 1);

✅ Dans un environnement multi-threadé, Dictionary nécessite un lock externe pour garantir la sécurité.


🔹 2. CancellationToken et utilisation dans les tâches longues

  • Permet d’annuler proprement des tâches en cours sans les forcer.

  • Méthode : passer un CancellationToken à la tâche et vérifier régulièrement token.IsCancellationRequested.

  • Exemple :

public async Task ComputeRiskAsync(CancellationToken token)
{
    for (int i = 0; i < 1_000_000; i++)
    {
        token.ThrowIfCancellationRequested();
        // calcul lourd
    }
}
  • Avantage : réactivité + nettoyage des ressources.


🔹 3. Différence entre lock, Monitor, SemaphoreSlim, Mutex

Outil Scope Usage Performance / Latence
lock(obj) Thread local Simplifie Monitor.Enter/Exit Rapide, syntaxe simple
Monitor Thread local Contrôle plus fin (TryEnter, Wait/Pulse) Flexibilité, légèrement plus verbeux
SemaphoreSlim Multi-thread Limite nombre de threads concurrents Asynchrone-friendly (WaitAsync)
Mutex Cross-process Synchronisation entre processus Plus lent, kernel-level

🔹 4. Tester et éviter les conditions de course (race conditions)

✅ Stratégies :

  • Utiliser des collections thread-safe (ConcurrentDictionary, ConcurrentQueue)

  • Utiliser des locks ou Interlocked pour opérations atomiques

  • Unit tests multi-threadés et stress tests

  • Static code analysis et tools comme Microsoft CHESS


🔹 5. Lazy pour initialisation d’objets lourds

  • Crée l’objet uniquement lorsqu’il est utilisé pour la première fois

  • Thread-safe par défaut (LazyThreadSafetyMode.ExecutionAndPublication)

  • Exemple :

Lazy<RiskEngine> engine = new Lazy<RiskEngine>(() => new RiskEngine());
var result = engine.Value; // initialisation ici

✅ Utile pour les objets de pricing ou risk engine qui consomment beaucoup de mémoire.


🔹 6. Stratégies pour éviter les deadlocks

  1. Ordre fixe pour acquérir les locks

  2. Utiliser timeout sur Monitor.TryEnter / SemaphoreSlim.Wait

  3. Éviter locks imbriqués lorsque possible

  4. Privilégier lock-free structures et ConcurrentDictionary

  5. Limiter la durée de chaque lock → lock court et ciblé


🔹 7. Utilisation de Task.WhenAll() et Task.WhenAny()

  • Task.WhenAll() : attendre plusieurs tâches parallèles et récupérer les résultats

var tasks = symbols.Select(s => ComputePriceAsync(s));
var results = await Task.WhenAll(tasks);
  • Task.WhenAny() : continuer dès qu’une tâche se termine (utile pour first response wins)

var first = await Task.WhenAny(tasks);

🔹 8. Différence entre async/await et TPL (Task Parallel Library)

Concept Description
async/await Syntaxe simplifiée pour programmation asynchrone (IO bound)
TPL (Task Parallel Library) Gestion avancée de parallelisme CPU-bound, Task.Run, Parallel.ForEach

✅ Exemple : async/await = streaming de données marché, TPL = calculs massifs sur portefeuilles.


🔹 9. Profiler et optimiser les performances multi-threading

  • Outils :

    • Visual Studio Profiler, JetBrains dotTrace, PerfView

    • Thread contention analysis, GC pauses

  • Optimisations :

    • Minimiser locks → préférer collections thread-safe

    • Pré-allocation mémoire → éviter GC dans hot path

    • Batch processing → réduire fréquence context-switch

    • Affinity threads → CPU pinning pour threads critiques


🔹 10. Utilisation de SpinLock en latence ultra-faible

  • SpinLock : boucle active au lieu de bloquer le thread

  • Idéal pour section critique très courte

  • Pas adapté aux sections longues → CPU waste

  • Exemple :

SpinLock spin = new SpinLock();
bool lockTaken = false;
try
{
    spin.Enter(ref lockTaken);
    // code ultra court et critique
}
finally
{
    if (lockTaken) spin.Exit();
}

✅ Utilisé en HFT / trading ultra-low latency pour éviter context switch du kernel.


📘 Résumé des bonnes pratiques Multi-threading / Concurrence

Concept Astuce / Usage
ConcurrentDictionary Thread-safe, opérations atomiques
CancellationToken Annulation propre, réactivité
lock / Monitor / SemaphoreSlim / Mutex Choisir selon scope, async, cross-process
Race conditions Tests multi-thread, collections thread-safe
Lazy Initialisation différée thread-safe
Deadlocks Locks courts, ordre fixe, timeout
Task.WhenAll / Task.WhenAny Synchroniser tâches parallèles
async/await vs TPL IO-bound vs CPU-bound
SpinLock Sections critiques ultra-courtes, faible latence
Profiling GC, contention, hot path, allocation pools


Q/A Outils de Restitution Temps Réel (PnL, Risks)



🔹 1. Structurer une application C# pour restituer PnL et Risques en temps réel

✅ Architecture recommandée :

  1. Data Layer (Market Data / Pricing Engine)

    • Flux continu des données de marché et calculs de PnL/Risk

    • Structures thread-safe (ConcurrentDictionary, ImmutableDictionary, BlockingCollection)

  2. Business Layer (Calculs et Risk Engine)

    • Calculs incrémentaux ou différés pour éviter recalcul complet

    • Parallélisme contrôlé (Task.Run, Parallel.ForEach)

  3. Presentation Layer (UI / Dashboard)

    • WPF ou WinForms pour visualisation

    • Bindings avec ObservableCollection ou Reactive Extensions

  4. Communication Layer

    • WebSockets, SignalR ou gRPC pour diffuser les mises à jour

  5. Cache / Buffer

    • Stocker l’état courant des positions pour éviter recalculs fréquents

✅ Séparation des couches améliore la maintenabilité, testabilité et performance.


🔹 2. Bibliothèques C# pour visualisation de données financières temps réel

Bibliothèque Usage Avantages
LiveCharts2 Graphiques WPF/WinForms Open-source, responsive, facile à lier à ObservableCollection
SciChart Graphiques haute performance Très rapide, adaptée au HFT, support zoom/pan
OxyPlot Graphiques légers Open-source, simple pour dashboards
DevExpress / Telerik Charts UI complète Widgets avancés, support commercial, live data

🔹 3. Intégrer C# avec Excel/VBA

  • Interop Excel (Microsoft.Office.Interop.Excel)

    • Lire et écrire cellules, mettre à jour graphiques

  • RTD Server (IRtdServer)

    • Fournir flux de données temps réel à Excel

  • COM / Add-in VSTO

    • Interface directe entre application C# et Excel

  • Exemple minimal RTD :

public class MarketRtdServer : IRtdServer
{
    // Implémenter ConnectData, RefreshData pour renvoyer PnL/Risk
}

🔹 4. Éviter la latence dans un moteur graphique temps réel

  • Pré-calcul des valeurs hors UI thread

  • Utiliser Dispatcher.InvokeAsync ou SynchronizationContext.Post pour update UI

  • Virtualisation des graphiques : ne rendre que ce qui est visible

  • Batching des mises à jour pour réduire appels fréquents à l’UI

  • Réutiliser les objets graphiques au lieu de recréer


🔹 5. Stratégies de buffering et batching

  • Batching temporel : grouper les mises à jour toutes les X millisecondes

  • Batching par taille : traiter N événements ensemble

  • Buffer circulaire (CircularBuffer) pour garder un historique limité

  • BlockingCollection + Task pour décorréler producteurs et consommateurs

🧩 Exemple :

var batch = new List<MarketUpdate>();
foreach(var update in updatesBuffer.Take(batchSize))
{
    batch.Add(update);
}
ProcessBatch(batch);

🔹 6. Gérer les mises à jour haute fréquence dans une UI WPF/WinForms

  • ObservableCollection avec UI virtualization

  • Reactive Extensions (Rx.NET) pour throttle / sample updates

  • DispatcherTimer pour pousser des updates régulières plutôt que chaque tick

  • VirtualizationPanel / DataGrid virtualization pour grosses tables


🔹 7. SignalR vs gRPC pour mise à jour temps réel

Critère SignalR gRPC
Protocol WebSockets / fallback HTTP/2 binary
Latence Faible mais dépend WebSocket Très faible, binaire
Bidirectional Oui Oui (gRPC bidirectional streaming)
Intégration UI Direct avec .NET / JS Nécessite client spécifique
Scalabilité Facile avec backplane Très scalable, léger

✅ SignalR = rapide pour dashboards Web/WPF simples
✅ gRPC = meilleur throughput et latence pour calculs intensifs, HFT


🔹 8. Synchroniser Excel VBA et API C# en temps réel

  • RTD Server pour push vers Excel

  • COM Add-ins pour exposer méthodes C# à VBA

  • WebSocket / HTTP Client depuis VBA si version récente

  • Polling limité : éviter polling trop fréquent → utiliser push events


🔹 9. Rôle des Design Patterns dans une application de restitution

Pattern Usage
Observer Notifier UI ou composants abonnés aux updates PnL/Risk
Factory Créer dynamiquement objets de graphique ou calculs
Singleton Accès global au cache des positions ou moteur de calcul
Decorator Ajouter dynamiquement des indicateurs ou alertes
Repository Abstraction accès données (positions, trades, quotes)

✅ Les patterns permettent extensibilité, testabilité, découplage


🔹 10. Gérer erreurs et reconnexions automatiques

  • Retry avec backoff exponentiel (Polly library)

  • Circuit breaker pour éviter surcharge serveur

  • Détection de disconnect (WebSocket.State) et reconnect automatique

  • Logging centralisé des erreurs

  • UI : afficher état du flux et derniers updates connus

🧩 Exemple avec WebSocket et reconnexion :

while(true)
{
    try
    {
        await webSocket.ConnectAsync(uri, ct);
        await ReceiveLoop(webSocket, ct);
    }
    catch
    {
        await Task.Delay(500); // backoff
    }
}

📘 Synthèse des meilleures pratiques pour restitution PnL / Risk

Domaine Bonnes pratiques
Architecture Data Layer, Business Layer, Presentation Layer, Communication Layer
Visualisation LiveCharts2, SciChart, OxyPlot, DevExpress
Excel integration Interop, RTD, VSTO, COM Add-ins
UI haute fréquence Rx.NET, batching, Dispatcher/InvokeAsync, virtualization
Communication temps réel SignalR pour Web/Desktop, gRPC pour faible latence
Concurrence Concurrent collections, lock-free, batching
Fiabilité Retry/backoff, circuit breaker, monitoring
Design Patterns Observer, Factory, Singleton, Decorator, Repository
Performance Minimiser GC, éviter allocations fréquentes, Span/Memory, ArrayPool


Q/A Conception et Développement



🔹 1. Comment mesurer et optimiser les performances d’une application en C# ?

✅ Mesure :

  • Profiler : Visual Studio Profiler, JetBrains dotTrace, PerfView

  • Benchmarking : BenchmarkDotNet pour mesurer des micro-benchmarks

  • EventCounters / Diagnostics pour mesurer l’usage CPU, GC, allocations mémoire

  • Stopwatch pour tests simples :

var sw = Stopwatch.StartNew();
// code à mesurer
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

✅ Optimisation :

  1. Réduire les allocations GC inutiles (string immutables → StringBuilder, Span<T>/Memory<T>)

  2. Préférer ValueTuple à Tuple pour éviter les heap allocations

  3. Minimiser l’usage de boxing/unboxing

  4. Optimiser LINQ critiques (éviter Select().ToList() répétitif)

  5. Profiler et cibler les hot paths

  6. Utiliser async/await pour IO bound et Parallel/Task pour CPU bound


🔹 2. Avantages et inconvénients des structs par rapport aux classes

Critère Struct Classe
Type Value type Reference type
Allocation Stack (rapide) Heap (GC)
Taille Petit (<16 octets recommandé) Illimitée
Mutation Copie sur assignation Référence partagée
Héritage Pas de polymorphisme Supporté
Avantage Pas de GC, performances Flexibilité, héritage
Inconvénient Copie fréquente peut être coûteuse Allocations sur heap

✅ Bon usage : types petits et immuables (ex: Point, Color, DateTime)


🔹 3. Pourquoi faut-il éviter les boxing et unboxing ?

  • Boxing : conversion d’un value type en object/interface → allocation sur heap

  • Unboxing : cast inverse → vérification type → overhead CPU

🧩 Exemple :

int x = 5;
object obj = x;      // boxing
int y = (int)obj;    // unboxing
  • Impacts : GC supplémentaire, perte performance

  • Solution : utiliser generics (List<int> au lieu de ArrayList)


🔹 4. Différence entre static class et sealed class

Critère static class sealed class
Instanciation Impossible Possible
Héritage Impossible Impossible à hériter
Membres Tous statiques Membres normaux ou statiques
Usage Helper, extension methods Classe normale qu’on veut empêcher d’hériter

🔹 5. Fonctionnement du pattern Decorator en C#

✅ Objectif :

Ajouter des fonctionnalités dynamiquement à un objet sans modifier sa classe.

🧩 Exemple :

public interface INotifier { void Send(string message); }
public class EmailNotifier : INotifier { public void Send(string msg) => Console.WriteLine("Email: " + msg); }

public class SmsDecorator : INotifier
{
    private readonly INotifier _inner;
    public SmsDecorator(INotifier inner) => _inner = inner;
    public void Send(string msg)
    {
        _inner.Send(msg);
        Console.WriteLine("SMS: " + msg);
    }
}

// Usage
INotifier notifier = new SmsDecorator(new EmailNotifier());
notifier.Send("Hello");

🔹 6. Différences entre abstract class et interface

Critère Abstract class Interface
Héritage Simple/multiple avec interface Multiple
Membres Méthodes, propriétés, champs, constructeurs Méthodes/propriétés/events seulement
Implémentation Partielle possible Par défaut depuis C# 8 avec default impl.
Usage Base commune avec comportement partagé Contrat pur, flexible

🔹 7. Fonctionnement du pattern Singleton et pièges en C#

✅ Objectif :

Assurer une unique instance globale

🧩 Exemple thread-safe :

public class Singleton
{
    private static readonly Lazy<Singleton> _instance = new(() => new Singleton());
    public static Singleton Instance => _instance.Value;
    private Singleton() { }
}

⚠️ Pièges :

  • Non thread-safe si static sans Lazy

  • Serialization peut créer de nouvelles instances

  • Reflection peut bypasser le constructeur privé


🔹 8. Pourquoi utiliser lock(obj) plutôt que Monitor.Enter(obj) ?

  • lock(obj) = syntactic sugar pour Monitor.Enter/Exit avec gestion automatique des exceptions

  • Monitor.Enter/Exit = plus flexible, mais risque de deadlock si Exit oublié

lock(_lockObj) { /* code thread-safe */ }

lock est recommandé pour la plupart des cas simples.


🔹 9. Fonctionnement du pattern Repository et utilité

✅ Objectif :

Abstraction de la couche accès données pour séparer logique métier et DB

🧩 Exemple :

public interface IProductRepository
{
    Product GetById(int id);
    void Add(Product p);
}

public class ProductRepository : IProductRepository
{
    private readonly DbContext _context;
    public ProductRepository(DbContext context) => _context = context;

    public Product GetById(int id) => _context.Products.Find(id);
    public void Add(Product p) => _context.Products.Add(p);
}
  • Facilite le unit testing

  • Permet de changer facilement la source de données


🔹 10. Fonctionnement de yield return et utilité

✅ Objectif :

Créer des itérateurs paresseux (lazy evaluation)

🧩 Exemple :

IEnumerable<int> GetNumbers()
{
    for (int i = 0; i < 5; i++)
        yield return i;
}

foreach(var n in GetNumbers()) Console.WriteLine(n);

✅ Avantages :

  • Mémoire optimisée (pas de liste complète)

  • Evaluation à la demande

  • Idéal pour gros jeux de données ou flux infinis


📘 Synthèse des concepts avancés

Concept Utilité / Avantage Astuces / Pièges
Performance Profiling, BenchmarkDotNet, Span/Memory Eviter allocations, LINQ sur-alloué
Struct vs Class Stack allocation, pas de GC Copier grands structs coûteux
Boxing/Unboxing Éviter allocations heap Utiliser Generics
static vs sealed Helper vs classe finale Comprendre héritage
Decorator Extensibilité dynamique Pas toucher code existant
abstract vs interface Base avec comportement vs contrat pur Choisir selon réutilisation
Singleton Unique instance Thread-safe + Lazy + attention serialization/reflection
lock vs Monitor Thread safety simple lock recommandé
Repository Isolation DB layer Testabilité, flexibilité
yield return Lazy iteration Evite création complète de liste





🔹 1. Gérer la latence et les performances dans une application de trading en temps réel

✅ Bonnes pratiques :

  1. Minimiser les allocations mémoire :

    • Utiliser Span<T>, Memory<T>, ArrayPool<T> pour éviter la pression sur le GC.

  2. Eviter le boxing/unboxing :

    • Utiliser des struct et des ValueTuple pour manipuler des données financières.

  3. Optimiser les structures de données :

    • ConcurrentDictionary, ImmutableDictionary, PriorityQueue pour accès rapide et thread-safe.

  4. Parallélisme contrôlé :

    • Distribuer les calculs sur plusieurs threads sans saturer le CPU.

  5. Priorité thread :

    • Utiliser ThreadPriority.Highest pour threads critiques (ex: matching engine).

  6. Low-latency networking :

    • Préférer Span<byte> pour buffers réseau, SocketAsyncEventArgs pour IO non-bloquant.


🔹 2. Meilleures pratiques pour développer des applications temps réel en C#

  • Éviter la GC sur hot path (pré-allocation, objets immuables)

  • Traitement parallèle : Task, Parallel.ForEach, mais attention à la contention

  • Thread affinity : conserver les threads critiques pour réduire les migrations

  • Lock-free structures : ConcurrentQueue<T>, ConcurrentDictionary<T>, Interlocked pour éviter les locks coûteux

  • Profiling et benchmarks : mesurer la latence et le throughput en continu

  • Logging asynchrone : ne pas bloquer le thread critique


🔹 3. Garbage Collector et impact sur le pricing temps réel

  • GC générationnel :

    • Gen0 : objets temporaires (allocation rapide)

    • Gen1 : objets intermédiaires

    • Gen2 : objets long-lived

  • Impact :

    • Une pause GC peut introduire des latences inacceptables pour le trading haute fréquence.

  • Solutions :

    • Utiliser structs, Span<T>, pools (ArrayPool<T>), éviter allocations sur hot path

    • GC.TryStartNoGCRegion() pour sections critiques


🔹 4. Différences : Task.Run(), Parallel.ForEach(), ThreadPool.QueueUserWorkItem()

Méthode Description Usage
Task.Run() Crée une Task sur le thread pool Pour async/await et calcul CPU-bound
Parallel.ForEach() Boucle parallèle avec partitioning Traitement batch de données de marché
ThreadPool.QueueUserWorkItem() Exécute un delegate sur un thread pool Très bas niveau, sans Task

✅ Choix dépend du niveau d’abstraction et du contrôle sur la synchronisation.


🔹 5. Optimiser les accès concurrents aux données financières

  • Concurrent collections : ConcurrentDictionary, ConcurrentQueue, ConcurrentBag

  • ReaderWriterLockSlim : lecture fréquente, écriture rare

  • Lock-free programming : Interlocked.Increment, Volatile.Read/Write

  • Immutable structures : lecture thread-safe sans verrou


🔹 6. Communication efficace entre moteur de risque et UI en temps réel

  • Event-driven architecture :

    • Le moteur de risque émet des événements (PriceChanged, RiskUpdated)

    • UI s’abonne via IObservable<T> ou Reactive Extensions (Rx.NET)

  • Queues : ConcurrentQueue<T> ou BlockingCollection<T> pour bufferiser les updates

  • WebSocket / SignalR : pour diffusion asynchrone et bidirectionnelle


🔹 7. Utilisation des WebSockets en C#

  • Rôle : diffusion continue de flux de marché (bid/ask, trades)

  • Avantages : faible latence, full-duplex, léger

  • Exemple serveur C# :

var webSocket = await context.WebSockets.AcceptWebSocketAsync();
while(webSocket.State == WebSocketState.Open)
{
    var buffer = new byte[1024];
    var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
    // traiter message
}
  • Exemple client : ClientWebSocket pour s’abonner aux flux de prix


🔹 8. Assurer la fiabilité et haute disponibilité

  • Redondance : clusters pour le moteur de trading

  • Retry/Failover : circuit breaker et backoff

  • Snapshots / persistence : journaux des transactions et state recovery

  • Monitoring & alerting : Latency, GC pause, exceptions


🔹 9. Memory-Mapped Files pour stocker des données de marché

  • Permet d’accéder à un fichier comme une mémoire partagée

  • Avantages :

    • Très faible latence (pas de parsing répétitif)

    • Partage entre processus

    • Lecture/écriture rapide sur gros volumes

  • Exemple :

using (var mmf = MemoryMappedFile.CreateFromFile("market.dat", FileMode.OpenOrCreate, "map", 1024))
{
    using var accessor = mmf.CreateViewAccessor();
    accessor.Write(0, 42);
    int value = accessor.ReadInt32(0);
}

🔹 10. Implémenter un cache performant pour l’accès aux données de marché

  • Objectif : éviter d’interroger les sources externes à chaque requête

  • Techniques :

    • MemoryCache avec MemoryCacheEntryOptions

    • ConcurrentDictionary avec expiration personnalisée

    • LRU Cache / cache basé sur LinkedList + Dictionary pour taille fixe

    • Préférer readonly structures pour thread-safety

  • Exemple simple avec MemoryCache :

var cache = new MemoryCache(new MemoryCacheOptions());
cache.Set("AAPL", 150.23, TimeSpan.FromSeconds(1));
double price = (double)cache.Get("AAPL");

📘 Résumé des meilleures pratiques pour C# temps réel (Trading / Pricing)

Concept Astuce
Allocation mémoire Span<T>, Memory<T>, ArrayPool<T>
Threading Task.Run, Parallel.ForEach, ThreadPool, lock-free
Concurrence ConcurrentDictionary, ReaderWriterLockSlim, Immutable structures
UI communication Rx.NET, ObservableCollection, WebSockets
Haute disponibilité Clustering, snapshots, failover, monitoring
Cache MemoryCache, LRU, readonly structures
Low-latency I/O MemoryMappedFile, SocketAsyncEventArgs
Garbage Collector Minimiser allocations, utiliser NoGCRegion si critique


Q/A MAP, HMAP, et Structures de Données Associées



🔹 1. Qu’est-ce qu’un Dictionary<TKey, TValue> et comment fonctionne-t-il ?

✅ Définition :

Dictionary<TKey, TValue> est une collection générique du namespace System.Collections.Generic qui stocke des paires clé-valeur.
Chaque clé doit être unique et permet un accès rapide à la valeur correspondante.

✅ Fonctionnement interne :

  • Basé sur une table de hachage (hash table).

  • Chaque clé est transformée en un code de hachage (GetHashCode()).

  • Ce code détermine le bucket (emplacement) où la paire clé-valeur est stockée.

  • Lorsqu’on recherche une clé :

    1. Le dictionnaire calcule le hash.

    2. Il accède au bucket correspondant.

    3. Il compare la clé (via Equals) pour confirmer la correspondance.

🧩 Exemple :

var dict = new Dictionary<int, string>();
dict.Add(1, "Alice");
dict.Add(2, "Bob");

Console.WriteLine(dict[1]); // "Alice"

🧠 Complexité moyenne :

  • Insertion : O(1)

  • Recherche : O(1)

  • Suppression : O(1)


🔹 2. Quelle est la différence entre Hashtable et Dictionary<TKey, TValue> ?

Critère Hashtable Dictionary<TKey, TValue>
Namespace System.Collections System.Collections.Generic
Typage Non générique (clé/valeur en object) Générique (TKey, TValue)
Type Safety Non typé (cast nécessaire) Type-sûr
Performance Boxing/unboxing pour types valeur Plus rapide (pas de boxing)
Ordre d’insertion Non garanti Non garanti
Utilisation moderne Déconseillée Recommandée

🧩 Exemple :

// Hashtable (ancienne approche)
Hashtable ht = new Hashtable();
ht["id"] = 123;  // clé en string, valeur en object

// Dictionary (moderne)
Dictionary<string, int> dict = new Dictionary<string, int>();
dict["id"] = 123;

🧠 Conclusion :

Dictionary<TKey, TValue> est plus performant, plus sûr et préféré à Hashtable dans tout nouveau développement .NET.


🔹 3. Qu’est-ce qu’un ConcurrentDictionary<TKey, TValue> et pourquoi est-il utile ?

✅ Définition :

ConcurrentDictionary<TKey, TValue> (namespace System.Collections.Concurrent) est une version thread-safe du Dictionary, conçue pour les environnements multi-thread.

✅ Caractéristiques :

  • Gère automatiquement les verrous internes (fine-grained locking).

  • Permet plusieurs accès concurrents sans lever d’exception.

  • Fournit des méthodes atomiques comme :

    • TryAdd(key, value)

    • TryUpdate(key, oldValue, newValue)

    • GetOrAdd(key, valueFactory)

🧩 Exemple :

var concurrentDict = new ConcurrentDictionary<int, string>();
concurrentDict.TryAdd(1, "Alice");

// Thread-safe access
concurrentDict.AddOrUpdate(1, "Bob", (key, oldValue) => oldValue + " Updated");

Console.WriteLine(concurrentDict[1]); // "Alice Updated"

🧠 Pourquoi utile :

Utile dans les applications multi-thread, serveurs web, ou services parallèles sans avoir à gérer manuellement les verrous (lock).


🔹 4. Comment fonctionne le HashSet et dans quel cas l’utiliser ?

✅ Définition :

HashSet<T> est une collection d’éléments uniques, basée sur une table de hachage.

✅ Fonctionnement :

  • Chaque élément est stocké en fonction de son GetHashCode().

  • Avant d’ajouter un élément, le HashSet vérifie s’il existe déjà.

  • Les doublons sont automatiquement ignorés.

🧩 Exemple :

HashSet<string> set = new HashSet<string>();
set.Add("A");
set.Add("B");
set.Add("A"); // Ignoré car déjà présent

Console.WriteLine(set.Count); // 2

🧠 Quand l’utiliser :

Quand tu veux tester l’unicité ou rechercher rapidement des valeurs sans associer de clé.
Exemples : filtrage, détection de doublons, comparaison d’ensembles.


🔹 5. Différence entre SortedDictionary<TKey, TValue> et SortedList<TKey, TValue>

Critère SortedDictionary<TKey, TValue> SortedList<TKey, TValue>
Structure interne Arbre binaire équilibré (Red-Black Tree) Tableaux triés internes
Tri Automatique par clé Automatique par clé
Insertion/Suppression O(log n) O(n)
Accès indexé Non Oui
Mémoire Plus élevée Plus compacte
Usage recommandé Données modifiées fréquemment Données stables et triées

🧩 Exemple :

var sortedDict = new SortedDictionary<int, string>();
sortedDict.Add(2, "B");
sortedDict.Add(1, "A");

var sortedList = new SortedList<int, string>();
sortedList.Add(2, "B");
sortedList.Add(1, "A");

🧠 Conclusion :

  • SortedList → petite collection, peu de modifications.

  • SortedDictionary → plus efficace pour de nombreuses insertions/suppressions.


🔹 6. Pourquoi la méthode TryGetValue() est-elle recommandée pour accéder aux valeurs d’un Dictionary<TKey, TValue> ?

✅ Explication :

L’accès direct via dict[key] lève une exception si la clé n’existe pas.
TryGetValue() permet une lecture sécurisée sans exception.

🧩 Exemple :

var dict = new Dictionary<int, string> { [1] = "Alice" };

if (dict.TryGetValue(2, out string value))
    Console.WriteLine(value);
else
    Console.WriteLine("Key not found"); // Pas d'exception

🧠 Avantages :

  • Plus performant (évite le double test).

  • Plus propre et plus sûr.

  • Bonnes pratiques dans le code de production.


🔹 7. Comment implémenter une table de hachage personnalisée en C# ?

✅ Principe :

Une table de hachage se compose :

  1. D’un tableau de buckets.

  2. D’une fonction de hachage.

  3. D’un mécanisme de gestion des collisions.

🧩 Exemple simplifié :

public class SimpleHashTable<K, V>
{
    private const int Size = 10;
    private readonly List<KeyValuePair<K, V>>[] _buckets = new List<KeyValuePair<K, V>>[Size];

    public void Add(K key, V value)
    {
        int index = Math.Abs(key.GetHashCode()) % Size;
        _buckets[index] ??= new List<KeyValuePair<K, V>>();
        _buckets[index].Add(new KeyValuePair<K, V>(key, value));
    }

    public bool TryGetValue(K key, out V value)
    {
        int index = Math.Abs(key.GetHashCode()) % Size;
        if (_buckets[index] != null)
        {
            foreach (var kv in _buckets[index])
            {
                if (kv.Key.Equals(key))
                {
                    value = kv.Value;
                    return true;
                }
            }
        }
        value = default!;
        return false;
    }
}

✅ Utilisation :

var table = new SimpleHashTable<string, int>();
table.Add("Alice", 25);
if (table.TryGetValue("Alice", out int age))
    Console.WriteLine(age); // 25

🧠 Concepts clés :

  • Fonction de hachage → distribue les clés.

  • Buckets → regroupent les collisions.

  • Comparaison → via Equals.


🔹 8. Comment gérer les collisions dans une table de hachage en C# ?

Une collision se produit lorsque deux clés différentes produisent le même code de hachage.
Plusieurs stratégies existent :

Technique Description Exemple d’utilisation
Chaînage (Chaining) Chaque bucket contient une liste de paires (LinkedList) Utilisée par Dictionary<TKey, TValue>
Ouverture linéaire (Linear Probing) Recherche du prochain emplacement vide dans le tableau Implémentation personnalisée
Double hachage (Double Hashing) Utilisation d’une deuxième fonction de hachage pour trouver le slot suivant Avancé, rarement nécessaire

🧩 Exemple de chaînage :

_bucket[index] = new List<KeyValuePair<K, V>>(); // plusieurs éléments dans un même bucket

🧠 Résumé :

En .NET, Dictionary et HashSet utilisent le chaînage séparé, garantissant des performances stables même en cas de collisions.


📘 RÉCAPITULATIF SYNTHÉTIQUE

Structure Caractéristiques principales Thread-safe Complexité recherche
Dictionary<TKey, TValue> Table de hachage clé-valeur typée O(1)
Hashtable Ancien, non générique O(1)
ConcurrentDictionary<TKey, TValue> Thread-safe O(1)
HashSet<T> Ensemble d’éléments uniques O(1)
SortedDictionary<TKey, TValue> Arbre rouge-noir, trié O(log n)
SortedList<TKey, TValue> Tableau trié O(log n) (recherche) / O(n) (insertion)


Q/A Collections



🔹 1. Quelle est la différence entre List et ArrayList ?

Critère List ArrayList
Namespace System.Collections.Generic System.Collections
Type de données Générique (type sûr) Non générique (type object)
Sécurité de type Compile-time (empêche les erreurs de cast) ✅ Runtime (requiert des cast) ❌
Performance Meilleure (pas de boxing/unboxing) Moins bonne (boxing des types valeur)
Exemple List<int> list = new List<int>(); ArrayList list = new ArrayList();

🧩 Exemple :

List<int> list = new List<int> { 1, 2, 3 };        // ✅ Type-sûr
ArrayList arr = new ArrayList { 1, "abc", 3.5 };   // ❌ Types mélangés

🧠 Conclusion :

List<T> est préférée car elle est fortement typée, performante et moderne.
ArrayList est obsolète depuis l’introduction des collections génériques.


🔹 2. Pourquoi Dictionary<TKey, TValue> est-il plus rapide que List<KeyValuePair<TKey, TValue>> pour la recherche ?

  • Dictionary<TKey, TValue> utilise une table de hachage (hash table) pour stocker les éléments.

    • La recherche (ContainsKey, TryGetValue) s’effectue en O(1) en moyenne.

  • List<KeyValuePair<TKey, TValue>> effectue une recherche séquentielle sur chaque élément.

    • La recherche est en O(n).

🧩 Exemple :

Dictionary<int, string> dict = new Dictionary<int, string>();
dict.Add(1, "Alice");
dict.Add(2, "Bob");

dict.TryGetValue(2, out var name); // O(1)

List<KeyValuePair<int, string>> list = new List<KeyValuePair<int, string>>();
list.Add(new KeyValuePair<int, string>(1, "Alice"));
list.Add(new KeyValuePair<int, string>(2, "Bob"));
var found = list.FirstOrDefault(x => x.Key == 2); // O(n)

🧠 Conclusion :

Dictionary est optimisé pour la recherche rapide grâce à son mécanisme de hachage interne.


🔹 3. Différence entre Queue et Stack

Collection Type Principe Méthodes principales
Queue File (FIFO) First In, First Out Enqueue(), Dequeue()
Stack Pile (LIFO) Last In, First Out Push(), Pop()

🧩 Exemple :

Queue<string> queue = new Queue<string>();
queue.Enqueue("A");
queue.Enqueue("B");
Console.WriteLine(queue.Dequeue()); // A (FIFO)

Stack<string> stack = new Stack<string>();
stack.Push("A");
stack.Push("B");
Console.WriteLine(stack.Pop()); // B (LIFO)

🧠 Conclusion :

Queue<T> gère les éléments dans l’ordre d’arrivée (ex : files d’attente).
Stack<T> gère les éléments en ordre inverse (ex : historique de navigation).


🔹 4. Qu'est-ce qu’un LinkedList et quand l’utiliser ?

✅ Définition :

LinkedList<T> est une liste doublement chaînée, où chaque élément contient un lien vers le précédent et le suivant.

✅ Avantages :

  • Insertion et suppression rapides en O(1) à n’importe quelle position si le nœud est connu.

  • Pas de redimensionnement comme dans un tableau.

⚠️ Inconvénients :

  • Accès à un élément par index coûteux (O(n)).

  • Consomme plus de mémoire (pointeurs supplémentaires).

🧩 Exemple :

LinkedList<string> list = new LinkedList<string>();
list.AddLast("A");
list.AddLast("B");
list.AddFirst("Start");

🧠 Quand l’utiliser :

Quand on a beaucoup d’insertion/suppression au milieu de la collection,
mais peu d’accès aléatoire par index.


🔹 5. Quelle est la complexité de recherche dans un HashSet ?

  • HashSet<T> stocke les éléments uniques en utilisant un algorithme de hachage.

  • Complexité moyenne : O(1)

  • Pire cas (collisions extrêmes) : O(n)

🧩 Exemple :

HashSet<int> set = new HashSet<int> { 1, 2, 3 };
bool exists = set.Contains(2); // O(1)

🧠 Conclusion :

HashSet<T> est idéal pour tester rapidement la présence ou l’unicité d’un élément.


🔹 6. Qu'est-ce que SortedList<TKey, TValue> et en quoi diffère-t-il de SortedDictionary<TKey, TValue> ?

Critère SortedList<TKey, TValue> SortedDictionary<TKey, TValue>
Structure interne Deux tableaux triés (TKey[], TValue[]) Arbre binaire équilibré (Red-Black Tree)
Mémoire Moins de mémoire Plus de mémoire
Insertion Plus lente pour grands ensembles (O(n)) Plus rapide (O(log n))
Accès par index Oui Non
Itération Ordonnée par clé Ordonnée par clé

🧩 Exemple :

SortedList<int, string> list = new SortedList<int, string>();
list.Add(2, "B");
list.Add(1, "A");

SortedDictionary<int, string> dict = new SortedDictionary<int, string>();
dict.Add(2, "B");
dict.Add(1, "A");

🧠 Conclusion :

  • SortedList est meilleur pour petites collections triées.

  • SortedDictionary est meilleur pour insertion/suppression fréquentes.


🔹 7. Quelle est la différence entre IEnumerable et IQueryable ?

Critère IEnumerable IQueryable
Namespace System.Collections.Generic System.Linq
Exécution In-memory (C#) Déférée (souvent base de données)
Filtrage Fait en mémoire Traduit en expression (SQL, etc.)
Source typique Collections (List, Array) Entity Framework, LINQ to SQL
Performance Moins optimisée pour DB Optimisée pour la requête distante

🧩 Exemple :

IEnumerable<User> users = db.Users.ToList().Where(u => u.Age > 30); // Filtrage en mémoire
IQueryable<User> query = db.Users.Where(u => u.Age > 30);           // Filtrage côté SQL

🧠 Conclusion :

IQueryable permet la traduction en requête distante (SQL),
alors que IEnumerable manipule les données déjà chargées.


🔹 8. Comment fonctionne la méthode LINQ Select() et Where() ?

Méthode Rôle Type de retour Exemple
Where() Filtrer les éléments selon une condition IEnumerable<T> list.Where(x => x > 10)
Select() Projeter / transformer chaque élément IEnumerable<TResult> list.Select(x => x * 2)

🧩 Exemple combiné :

List<int> numbers = new() { 1, 2, 3, 4, 5, 6 };

var result = numbers
    .Where(x => x % 2 == 0)  // Filtre (pairs)
    .Select(x => x * x);     // Projection (carré)

foreach (var n in result)
    Console.WriteLine(n); // 4, 16, 36

🧠 Fonctionnement :

Ces méthodes utilisent l’exécution différée :
le traitement n’a lieu que lors de l’itération (foreach, ToList(), etc.).


📘 RÉCAPITULATIF SYNTHÉTIQUE

Concept Description Complexité
List<T> Collection générique, accès indexé O(1)
ArrayList Ancienne collection non typée O(1)
Dictionary<TKey, TValue> Table de hachage clé-valeur O(1)
Queue<T> File FIFO O(1)
Stack<T> Pile LIFO O(1)
LinkedList<T> Liste doublement chaînée O(1) insertion
HashSet<T> Ensemble unique, basé sur hachage O(1)
SortedList vs SortedDictionary Collections triées O(log n)
IEnumerable<T> Exécution en mémoire n/a
IQueryable<T> Exécution distante (SQL) n/a





🔹 1. Quelle est la différence entre SortedSet et HashSet ?

Caractéristique HashSet<T> SortedSet<T>
Ordre Non trié Toujours trié selon Comparer<T>
Recherche O(1) en moyenne O(log n) (arbre rouge-noir)
Complexité insertion O(1) O(log n)
Usage Vérifier présence, collections uniques rapides Collections uniques avec tri automatique

🧩 Exemple :

var hashSet = new HashSet<int> { 3, 1, 2 };
var sortedSet = new SortedSet<int> { 3, 1, 2 };

Console.WriteLine(string.Join(",", hashSet));   // ordre indéfini
Console.WriteLine(string.Join(",", sortedSet)); // 1,2,3

🔹 2. Comment fonctionne un PriorityQueue en C# et quels sont ses avantages ?

✅ Définition :

PriorityQueue<TElement, TPriority> est une file avec priorité : l’élément avec la plus haute priorité (ou la plus basse selon comparaison) est servi en premier.

🧩 Exemple :

var pq = new PriorityQueue<string, int>();
pq.Enqueue("low", 5);
pq.Enqueue("high", 1);

Console.WriteLine(pq.Dequeue()); // "high" (priorité 1)

✅ Avantages :

  • Algorithme heap-based, O(log n) pour insertion et suppression

  • Idéal pour algorithmes de graphe (Dijkstra), gestion de tâches avec priorité


🔹 3. Pourquoi ImmutableDictionary<TKey, TValue> est-il utile ?

✅ Définition :

  • Dictionnaire immuable, toute modification crée une nouvelle instance.

  • Thread-safe par nature.

✅ Avantages :

  • Sécurité multi-thread

  • Prévisibilité (pas de modification inattendue)

  • Compatible avec architecture fonctionnelle / programmation réactive

🧩 Exemple :

var dict = ImmutableDictionary<string, int>.Empty;
var newDict = dict.Add("A", 1);

Console.WriteLine(dict.ContainsKey("A"));    // false
Console.WriteLine(newDict.ContainsKey("A")); // true

🔹 4. Comment optimiser la recherche dans une liste avec BinarySearch ?

✅ Conditions :

  • La liste doit être triée

  • BinarySearch utilise un algorithme diviser-pour-régner, O(log n)

🧩 Exemple :

var list = new List<int> {1, 3, 5, 7, 9};
int index = list.BinarySearch(5); // 2

Si la liste n’est pas triée, trier d’abord avec list.Sort().


🔹 5. Différence entre ConcurrentDictionary<TKey, TValue> et ImmutableDictionary<TKey, TValue>

Caractéristique ConcurrentDictionary ImmutableDictionary
Thread-safety Oui, mutable Oui, immuable
Modification Possible directement Chaque modification → nouvelle instance
Usage Multi-thread performant Multi-thread + sécurité immutabilité, architecture fonctionnelle

🔹 6. Pourquoi et comment utiliser ObservableCollection ?

✅ Objectif :

Collection notifiant les changements (Add, Remove, Reset) à l’UI ou aux observateurs.

🧩 Exemple :

ObservableCollection<string> list = new();
list.CollectionChanged += (s, e) => Console.WriteLine(e.Action);
list.Add("Hello"); // déclenche l’événement CollectionChanged

Utilisé surtout en WPF / MVVM pour liaison dynamique à l’UI.


🔹 7. Meilleure structure pour stocker des données triées fréquemment mises à jour

  • SortedDictionary<TKey, TValue> ou SortedSet : insertion O(log n), tri automatique

  • Pour recherches fréquentes sans modification, ImmutableSortedDictionary pour thread-safety

  • Pour mutations fréquentes et performance multi-thread : combiner ConcurrentDictionary + tri au besoin


🔹 8. Différence entre IDictionary<TKey, TValue> et IReadOnlyDictionary<TKey, TValue>

Caractéristique IDictionary IReadOnlyDictionary
Modification Oui Non
Lecture seule Possible mais mutable Garantie immuable
Usage Collections générales API exposant seulement lecture

🧩 Exemple :

IDictionary<int, string> dict = new Dictionary<int, string>();
IReadOnlyDictionary<int, string> readOnly = dict;

🔹 9. Comment fonctionnent ReadOnlyMemory et ReadOnlySpan ?

Type Stockage Mutable Stack-only Usage
ReadOnlySpan<T> Référence sur mémoire Non Slices rapides, parsing
ReadOnlyMemory<T> Heap ou stack Non Async-friendly, stockage plus long

🧩 Exemple :

var array = new int[] {1,2,3,4};
ReadOnlySpan<int> span = array.AsSpan(0, 2); // [1,2]
ReadOnlyMemory<int> memory = array.AsMemory(0,2); // [1,2]

ReadOnlyMemory<T> peut être stocké ou passé à async contrairement à Span<T>.


🔹 10. Comment fonctionne Tuple<T1, T2, ...> et pourquoi préférer ValueTuple<T1, T2, ...> ?

Tuple<T1,T2,...>

  • Reference type

  • Immutable

  • Syntaxe plus lourde, moins lisible (Item1, Item2)

  • Allocation sur le heap

ValueTuple<T1,T2,...>

  • Value type → pas d’allocation inutile

  • Compatible déconstruction

  • Syntaxe plus claire (var (x,y) = GetValues();)

🧩 Exemple :

// Tuple classique
Tuple<int,string> t1 = Tuple.Create(1,"A");

// ValueTuple
var t2 = (1,"A");
var (id, name) = t2;

✅ Préférer ValueTuple pour performance et lisibilité.


📘 Synthèse des Structures

Collection Usage principal Performance Particularité
HashSet Unicité rapide O(1) Non trié
SortedSet Unicité triée O(log n) Trie automatique
PriorityQueue File avec priorité O(log n) Algorithme de heap
ImmutableDictionary Sécurité thread / immutabilité O(log n) Nouvelle instance à chaque modification
ConcurrentDictionary Multi-thread mutable O(1) Thread-safe
ObservableCollection UI binding O(1) Notifie changements
ReadOnlySpan / ReadOnlyMemory Slices mémoire ⚡ très rapide Span stack-only, Memory heap-async
Tuple / ValueTuple Conteneur multi-valeurs Heap vs stack ValueTuple pour performance & lisibilité


Q/A Injection de dépendances



🔹 1. Qu'est-ce que l’injection de dépendances (Dependency Injection) et pourquoi est-elle utilisée ?

✅ Définition :

L’injection de dépendances (DI) est un design pattern qui consiste à fournir les dépendances d’un objet depuis l’extérieur, plutôt que de les créer à l’intérieur.

Autrement dit, au lieu de :

public class OrderService
{
    private EmailService _emailService = new EmailService(); // dépendance directe ❌
}

on écrit :

public class OrderService
{
    private readonly IEmailService _emailService;
    public OrderService(IEmailService emailService) // injection par constructeur ✅
    {
        _emailService = emailService;
    }
}

✅ Objectifs :

  • Découpler les composants (principe SOLID : D = Dependency Inversion).

  • Faciliter les tests unitaires (mocking, substitution).

  • Simplifier la maintenance et l’évolution du code.

  • Favoriser la réutilisation et la configuration centralisée des dépendances.

🧠 En résumé :

La DI permet d’écrire un code modulaire, testable et extensible, en confiant la création des objets à un conteneur IoC.


🔹 2. Quelle est la différence entre Service Locator et Dependency Injection ?

Critère Service Locator Dependency Injection
Principe L’objet va chercher ses dépendances dans un conteneur global Les dépendances sont fournies automatiquement à l’objet
Couplage Couplage fort au conteneur (anti-pattern) Couplage faible, respecte l’inversion de dépendances
Lisibilité Masque les dépendances réelles Les dépendances sont explicites
Testabilité Difficile à tester Facile à mocker et injecter

Exemple Service Locator (❌ à éviter) :

var emailService = ServiceLocator.GetService<IEmailService>();

Exemple Dependency Injection (✅) :

public class OrderService
{
    public OrderService(IEmailService emailService) { ... }
}

🧠 En résumé :

Le Service Locator cache les dépendances → difficile à maintenir.
La Dependency Injection les rend explicites → claire et testable.


🔹 3. Quels sont les trois types d’injection de dépendances ?

Type d’injection Description Exemple
Constructeur Dépendances fournies via le constructeur (le plus courant) public MyClass(IMailService mailService)
Propriété Dépendances assignées après création via des propriétés publiques public IMailService MailService { get; set; }
Méthode Dépendances passées en paramètre d’une méthode spécifique void Initialize(IMailService mailService)

Exemple global :

public class NotificationService
{
    // 1️⃣ Constructeur
    public NotificationService(IEmailService emailService) { ... }

    // 2️⃣ Propriété
    public ISmsService SmsService { get; set; }

    // 3️⃣ Méthode
    public void Init(ILogService logService) { ... }
}

🧠 Bonne pratique :

L’injection par constructeur est la plus recommandée car elle garantit que toutes les dépendances sont fournies dès la création de l’objet.


🔹 4. Comment configurer l'injection de dépendances dans ASP.NET Core ?

ASP.NET Core intègre un conteneur IoC natif via IServiceCollection et IServiceProvider.

Étapes :

  1. Déclarer une interface et son implémentation :

    public interface IEmailService
    {
        void SendEmail(string to, string message);
    }
    
    public class EmailService : IEmailService
    {
        public void SendEmail(string to, string message) => Console.WriteLine($"Email sent to {to}");
    }
    
  2. Enregistrer le service dans Program.cs ou Startup.cs :

    builder.Services.AddScoped<IEmailService, EmailService>();
    
  3. L’injecter dans un contrôleur ou un service :

    public class OrderController : Controller
    {
        private readonly IEmailService _emailService;
    
        public OrderController(IEmailService emailService)
        {
            _emailService = emailService;
        }
    
        public IActionResult Confirm()
        {
            _emailService.SendEmail("client@mail.com", "Order confirmed!");
            return Ok();
        }
    }
    

🧠 Résumé :

L’enregistrement se fait dans le IServiceCollection, et l’injection se fait automatiquement par le framework.


🔹 5. Quelle est la différence entre les portées de services : Transient, Scoped et Singleton ?

Portée Durée de vie Description Exemple
Transient À chaque injection Nouvelle instance à chaque appel AddTransient<IService, Impl>()
Scoped Par requête HTTP (ASP.NET) Même instance pour toute la requête AddScoped<IService, Impl>()
Singleton Unique pour toute l’application Instance partagée et persistante AddSingleton<IService, Impl>()

Exemple pratique :

builder.Services.AddTransient<IEmailService, EmailService>(); // Nouveau à chaque injection
builder.Services.AddScoped<IUserService, UserService>();      // 1 par requête
builder.Services.AddSingleton<ILogService, LogService>();     // Unique globale

🧠 Bonnes pratiques :

  • Transient → objets légers, stateless.

  • Scoped → services dépendant du contexte de requête (EF DbContext).

  • Singleton → services partagés, thread-safe (cache, logger).


🔹 6. Comment utiliser IServiceCollection pour enregistrer des dépendances ?

IServiceCollection est l’interface utilisée pour configurer le conteneur IoC dans .NET Core.

Exemple d’enregistrement :

public void ConfigureServices(IServiceCollection services)
{
    // Enregistre un service simple
    services.AddScoped<IEmailService, EmailService>();

    // Enregistre un service avec fabrique personnalisée
    services.AddSingleton<IMyService>(provider => new MyService("param"));

    // Enregistre une instance spécifique
    var settings = new AppSettings { Timeout = 30 };
    services.AddSingleton(settings);
}

Résolution manuelle :

var provider = services.BuildServiceProvider();
var emailService = provider.GetRequiredService<IEmailService>();

🧠 Résumé :

IServiceCollection permet d’enregistrer les dépendances, et IServiceProvider les résout à l’exécution.


🔹 7. Qu'est-ce que l’inversion de contrôle (IoC) ?

✅ Définition :

L’Inversion de Contrôle (Inversion of Control) est un principe architectural où le contrôle du flux d’exécution est inversé :
ce n’est plus votre code qui crée et gère les dépendances, mais le framework ou conteneur IoC.

Exemple sans IoC :

var emailService = new EmailService();
var orderService = new OrderService(emailService);

Exemple avec IoC :

public class OrderService
{
    private readonly IEmailService _emailService;
    public OrderService(IEmailService emailService)
    {
        _emailService = emailService;
    }
}

➡️ Le conteneur IoC s’occupe de fournir l’instance EmailService.

🧠 Résumé :

IoC = principe, DI = son implémentation concrète.
Le conteneur gère le cycle de vie, la création et la destruction des objets.


🔹 8. Exemple complet d’injection de dépendances avec Microsoft.Extensions.DependencyInjection

✅ Étape 1 : Ajouter le package (si projet console)

dotnet add package Microsoft.Extensions.DependencyInjection

✅ Étape 2 : Créer les interfaces et implémentations

public interface IMessageService
{
    void Send(string message);
}

public class EmailMessageService : IMessageService
{
    public void Send(string message)
    {
        Console.WriteLine($"Email envoyé : {message}");
    }
}

✅ Étape 3 : Configurer le conteneur

using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();
services.AddTransient<IMessageService, EmailMessageService>();

var provider = services.BuildServiceProvider();

✅ Étape 4 : Résoudre la dépendance

var msgService = provider.GetRequiredService<IMessageService>();
msgService.Send("Bonjour DI !");

🧠 Résumé :

Microsoft.Extensions.DependencyInjection fournit le même moteur d’injection que celui d’ASP.NET Core, utilisable dans toute application .NET (console, WPF, etc.).


📘 Récapitulatif synthétique

Concept Description
DI Fournit les dépendances depuis l’extérieur
Service Locator Anti-pattern qui cache les dépendances
Types d’injection Constructeur, Propriété, Méthode
ASP.NET Core DI Configurée via IServiceCollection
Durée de vie Transient, Scoped, Singleton
IoC Principe d’inversion de création et contrôle
Microsoft.Extensions.DependencyInjection Framework officiel pour gérer DI dans .NET




🔹 1. Qu'est-ce que l’Open-Closed Principle (OCP) et comment l’appliquer en C# ?

✅ Définition :

OCP = un principe SOLID qui dit :

“Les classes doivent être ouvertes à l’extension mais fermées à la modification.”

✅ Application en C# :

  • On ne modifie pas une classe existante pour ajouter un comportement.

  • On étend le comportement via :

    • Interfaces

    • Classes abstraites

    • Polymorphisme

🧩 Exemple :

public interface IDiscount
{
    decimal Apply(decimal amount);
}

public class SeasonalDiscount : IDiscount
{
    public decimal Apply(decimal amount) => amount * 0.9m;
}

public class BlackFridayDiscount : IDiscount
{
    public decimal Apply(decimal amount) => amount * 0.7m;
}

public class Order
{
    private readonly IDiscount _discount;
    public Order(IDiscount discount) => _discount = discount;
    public decimal FinalAmount(decimal amount) => _discount.Apply(amount);
}

✅ Pour ajouter un nouveau type de réduction, il suffit de créer une nouvelle classe implémentant IDiscount, sans toucher à Order.


🔹 2. Comment implémenter un pattern Factory avec l’injection de dépendances ?

✅ Objectif :

Créer des objets dynamquement tout en utilisant la DI pour injecter leurs dépendances.

🧩 Exemple :

public interface IShape { void Draw(); }
public class Circle : IShape { public void Draw() => Console.WriteLine("Circle"); }
public class Square : IShape { public void Draw() => Console.WriteLine("Square"); }

public class ShapeFactory
{
    private readonly IServiceProvider _provider;
    public ShapeFactory(IServiceProvider provider) => _provider = provider;

    public IShape Create(string shapeType) =>
        shapeType switch
        {
            "Circle" => _provider.GetRequiredService<Circle>(),
            "Square" => _provider.GetRequiredService<Square>(),
            _ => throw new ArgumentException()
        };
}

// Registration
services.AddTransient<Circle>();
services.AddTransient<Square>();
services.AddSingleton<ShapeFactory>();

✅ Avantage : on peut créer des objets avec leurs dépendances résolues automatiquement.


🔹 3. Différence entre Activator.CreateInstance() et new T()

Méthode Description Usage
new T() Compilation-time, type connu Direct, performant
Activator.CreateInstance<T>() Runtime, type dynamique Création via reflection, moins performant

🧩 Exemple :

var obj1 = new MyClass(); // Compile-time
var obj2 = Activator.CreateInstance(typeof(MyClass)); // Runtime

🧠 Résumé :

Activator = utile pour plugins, assemblies dynamiques, mais plus lent.


🔹 4. Pourquoi et comment utiliser Options Pattern dans ASP.NET Core ?

✅ Objectif :

Gérer la configuration fortement typée (appsettings.json) avec DI.

🧩 Exemple :

public class MySettings { public string ApiKey { get; set; } }

// Registration
services.Configure<MySettings>(Configuration.GetSection("MySettings"));

// Injection
public class MyService
{
    private readonly MySettings _settings;
    public MyService(IOptions<MySettings> options) => _settings = options.Value;
}

✅ Avantages : centralisation, validation, injectabilité et immutabilité.


🔹 5. Qu’est-ce qu’un Service Provider en C# et comment l’utiliser ?

✅ Définition :

IServiceProvider = contexte DI capable de résoudre les services enregistrés.

🧩 Exemple :

var provider = services.BuildServiceProvider();
var myService = provider.GetService<MyService>();
  • Méthodes : GetService<T>(), GetRequiredService<T>().

  • Sert à résoudre dynamiquement des dépendances, par ex. dans une factory.


🔹 6. Comment gérer les dépendances circulaires dans une application avec DI ?

✅ Problème :

A → B → A → … (boucle infinie)

✅ Solutions :

  1. Refactorer pour supprimer la circularité (meilleure pratique)

  2. Injecter via Func<T> ou Lazy<T> :

public class A { public A(Func<B> bFactory) { var b = bFactory(); } }
  1. Injection de propriété ou méthode plutôt que constructeur.

✅ L’idée : retarder la résolution pour casser le cycle.


🔹 7. Différence entre Func et IServiceProvider.GetService()

Concept Func IServiceProvider.GetService()
Résolution Retardée / lazy Résolution immédiate
Réutilisation Peut être appelée plusieurs fois Obtenu une seule fois
DI integration Injectable Usage ponctuel ou factory

🧩 Exemple :

public class Foo
{
    private readonly Func<Bar> _barFactory;
    public Foo(Func<Bar> barFactory) => _barFactory = barFactory;
}

🔹 8. Pourquoi HttpClient doit-il être réutilisé et non instancié à chaque requête ?

  • HttpClient crée un socket par instance.

  • Si instancié à chaque requête → épuisement des sockets (SocketException) + performances médiocres.

  • Solution : singleton ou HttpClientFactory.

services.AddHttpClient<MyService>();

✅ HttpClientFactory gère la longévité des sockets et les politiques de retry/retry-backoff.


🔹 9. Comment enregistrer des services conditionnellement dans IServiceCollection ?

🧩 Exemple :

if (useMock)
    services.AddSingleton<IMyService, MockService>();
else
    services.AddSingleton<IMyService, RealService>();
  • Autre approche : extension method avec condition

services.AddSingleton<IMyService>(provider => 
    useMock ? new MockService() : new RealService());

🔹 10. Différences entre MediatR et DI classique

Aspect DI classique MediatR
Objectif Résolution directe de dépendances Découplage et envoi de messages/commandes
Couplage Couplage explicite (service A → B) Couplage faible via médiateur
Cas d’usage Appels directs, services CQRS, Event-driven architecture
Avantage Simple, direct Facilite la testabilité, extensions, pipelines (behaviors)

🧩 Exemple MediatR :

public class CreateOrderCommand : IRequest<bool> { }
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, bool> { ... }
await mediator.Send(new CreateOrderCommand());

✅ Avantage : réduction du couplage, centralisation des pipelines (logging, validation, transaction).


📘 RÉCAPITULATIF SYNTHÉTIQUE

Concept Objectif / Utilité Exemple
OCP Étendre sans modifier Interface IDiscount
Factory + DI Créer objets dynamiquement ShapeFactory + IServiceProvider
Activator.CreateInstance Runtime Plugins / DLL externe
Options Pattern Config typée injectée IOptions
ServiceProvider Résoudre dynamiquement provider.GetService()
Dépendances circulaires Eviter boucle Lazy, Func
Func vs GetService Résolution retardée Lazy factory
HttpClient Réutilisation Singleton / HttpClientFactory
Enregistrement conditionnel Choisir implémentation runtime if(useMock) AddSingleton<>
MediatR Découplage et CQRS IRequest / IRequestHandler





🔹 1. Comment fonctionne le système d’injection de dépendances en .NET Core ?

  • .NET Core intègre un conteneur IoC (Inversion of Control) par défaut via l’interface IServiceProvider.

  • Les services sont enregistrés dans IServiceCollection lors de la configuration (Program.cs ou Startup.cs).

  • Lorsque le framework instancie un composant (Controller, Middleware, Razor Page, etc.), il résout automatiquement les dépendances à partir du conteneur.

  • Exemple de résolution :

public class MyController : ControllerBase
{
    private readonly IMyService _service;
    public MyController(IMyService service)
    {
        _service = service; // injecté automatiquement
    }
}

🔹 2. Différence entre Transient, Scoped et Singleton

Type Description Exemple typique Durée de vie
Transient Nouvelle instance à chaque injection Services légers, stateless Chaque requête ou chaque injection
Scoped Une instance par scope (par défaut, par requête HTTP) Services dépendant du contexte HTTP Par requête HTTP
Singleton Une seule instance pour toute l’application Cache, configuration, moteur de calcul Application entière

✅ Le choix de la durée de vie impacte mémoire, performance et sécurité des données partagées.


🔹 3. Enregistrer et utiliser un service personnalisé avec DI

  1. Définir le service et son interface :

public interface ITradeService { void ExecuteTrade(); }
public class TradeService : ITradeService
{
    public void ExecuteTrade() { /* logique trading */ }
}
  1. L’enregistrer dans Program.cs :

builder.Services.AddScoped<ITradeService, TradeService>();
  1. L’injecter dans un Controller :

public class TradeController : ControllerBase
{
    private readonly ITradeService _tradeService;
    public TradeController(ITradeService tradeService)
    {
        _tradeService = tradeService;
    }
}

🔹 4. Injecter une dépendance dans un Middleware ASP.NET Core

  • Les middlewares peuvent recevoir des services via injection dans le constructeur ou via Invoke :

public class LoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<LoggingMiddleware> _logger;

    public LoggingMiddleware(RequestDelegate next, ILogger<LoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        _logger.LogInformation("Processing request");
        await _next(context);
    }
}
  • Ensuite, on ajoute le middleware :

app.UseMiddleware<LoggingMiddleware>();

🔹 5. Configurer une usine de services (Factory Pattern) avec DI

  • On peut créer une factory pour instancier des services avec logique complexe :

builder.Services.AddSingleton<ITradeService>(sp =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    return new TradeService(config["TradeMode"]);
});
  • Permet de passer des paramètres runtime ou configurer dynamiquement des services.


🔹 6. Avantages d’utiliser l’injection de dépendances

Avantage Explication
Découplage Le code dépend d’une interface plutôt que d’une implémentation concrète
Testabilité Permet le mocking ou stub des services pour les tests unitaires
Réutilisation Services centralisés et configurés une seule fois
Flexibilité Facilité pour changer d’implémentation ou de durée de vie sans modifier le code client
Maintenance simplifiée Moins de code « new » dispersé, logique centralisée dans le conteneur DI

✅ Dans une application financière ou trading, DI permet de centraliser la configuration du moteur de risque, des services de pricing et de l’accès aux données, améliorant la robustesse et la testabilité.



Q/A Collections Lượt xem: