Letzten Herbst saß ich bei einem Kunden, 80 Mitarbeitende, Maschinen- und Anlagenbau. Die hatten ein Web-API, das Webhook-Events von ihrer eigenen IoT-Infrastruktur entgegennahm — mehrere hundert Maschinen, jede polt regelmäßig Statusdaten ein. Das System lief auf einem einzelnen Azure App Service Plan, S2, ungefähr 180 Euro im Monat. Der Dev-Lead fragte mich, ob er auf P-Tier hochskalieren müsse, weil die Latenz manchmal in die Knie ging. Ich bat ihn, mir erstmal Application Insights zu zeigen.
Das Problem war kein Speichermangel und keine CPU-Last. Es war ein einziger Dictionary-Lookup, der bei jedem Request neu angelegt wurde — inklusive String-Allokation für den Key. Bei 400 Requests pro Minute, jeder mit einem Lookup, hat man schnell ein paar Megabyte GC-Druck pro Minute. Nicht dramatisch, aber der GC-Pause-Spike traf immer dann, wenn eine Maschine einen Batch von 50 Events abfeuerte. Ich hab dem Lead an einem Nachmittag gezeigt, wie man das umschreibt. Kein Tier-Upgrade nötig, Kosten gleich, Problem weg.
Genau für diesen Kontext ist Steve Gordons aktueller Artikel ein Pflichtlesetext.
Was Steve zeigt
Steve Gordon erklärt in seinem Beitrag, wie man mit .NET 9 und C# 13 einen Dictionary-Lookup so umbaut, dass keine unnötigen Heap-Allokationen entstehen. Der Kernmechanismus: ein IAlternateEqualityComparer<TAlternate, T> in Kombination mit GetAlternateLookup() auf dem Dictionary. Das erlaubt es, einen ReadOnlySpan<byte> als temporären Lookup-Key zu verwenden, ohne ihn in ein ReadOnlyMemory<byte> oder einen string konvertieren zu müssen — und damit ohne Heap-Allokation zur Lookup-Zeit.
Sein konkretes Beispiel ist IP-Adress-Tracking bei GitHub-Webhooks: Er möchte für jede eingehende IP prüfen, ob sie zu den bekannten GitHub-IPs gehört, ohne dabei bei jedem Request Objekte zu allozieren. Die stackalloc-Variante für den Byte-Buffer plus TryWriteBytes auf IPAddress plus ein selbstgeschriebener ReadOnlyMemoryComparer ergibt einen Zero-Allocation-Lookup-Pfad. Der Artikel ist sauber strukturiert, der Code ist produktionsreif, und Steve erklärt auch die Details des Create-Methods, das aufgerufen wird, wenn ein neuer Key eingetragen wird — da ist die einmalige Allokation dann explizit und gewollt.
Kurz: Das ist fortgeschrittenes .NET, korrekt erklärt, mit einem echten Use Case.
Wie das im Mittelstand landet
Jetzt kommt meine Ergänzung — nicht als Kritik an Steve, sondern als Übersetzung in die Realität meiner Kunden.
Die gute Nachricht: Das Pattern ist in bestimmten Szenarien im KMU direkt anwendbar. Wenn Du ein .NET-API betreibst, das pro Minute mehrere hundert oder tausende Requests verarbeitet und dabei regelmäßig Dictionary-Lookups mit flüchtigen Daten macht — IP-Adressen, Tenant-IDs, Session-Token-Prefixes — dann ist dieser Ansatz echtes Geld wert. Nicht in Form von Entwicklerstunden, die Du einsparst, sondern in Form von Infrastrukturkosten, die Du nicht ausgibst.
Die ehrliche Einschränkung: Für einen Großteil meiner KMU-Kunden ist dieses Optimierungsniveau Overkill. Wer ein Warenwirtschafts-API mit 20 Requests pro Minute betreibt, ein Buchungsportal für eine Handvoll Filialen, oder ein klassisches ERP-Backend — der wird mit diesem Trick null Unterschied messen. Der GC in .NET ist gut. Der übernimmt das. Die Zeit, die ein mittlerer Dev investiert, um IAlternateEqualityComparer zu verstehen, zu implementieren, zu testen und zu reviewen, ist im KMU-Kontext oft nicht amortisiert.
Was in Steves Artikel fehlt: Die Frage, ab wann sich das lohnt. Er schreibt aus der Perspektive eines Performance-Engineers, dem Allokationen per se ein Dorn im Auge sind. Das ist legitim — aber es ist eine andere Ausgangslage als ein Dev-Team von drei Leuten, das Features liefern muss. Im Mittelstand ist die Hauptkostentreiberin selten der GC-Druck. Sie ist schlechtes Indexing in der Datenbank, N+1-Queries, zu große Payload-Serialisierungen, oder HTTP-Calls ohne Timeout. Da würde ich zuerst ansetzen.
Meine konkrete Empfehlung
Wenn Du .NET-APIs im Betrieb hast, mach zuerst folgendes:
-
Messe. Application Insights oder ein simples dotnet-counters-Skript zeigt Dir, ob Dein GC-Druck überhaupt ein Problem ist. Schau auf
gen-0-gc-countundgc-pause-time. Wenn die unauffällig sind, ist Gordons Artikel für Dich Zukunftswissen, kein akuter Handlungsbedarf. -
Identifiziere Hot Paths. Falls Du tatsächlich ein Szenario hast, das >500 Requests/Minute mit Dictionary-Lookups kombiniert — Webhook-Receiver, Rate-Limiter, IP-Allow/Blocklist-Prüfung — dann ist
AlternateLookupgenau das richtige Werkzeug. Der Code ist einmalig aufwendiger zu schreiben, danach aber wartbar und erklärbar. -
Kapsle es sauber. Wenn Du das Pattern einsetzt, steck den
ReadOnlyMemoryComparerin ein eigenes kleines Utility-Projekt, schreib drei Unit-Tests drüber (SequenceEqual-Verhalten, IPv4 vs IPv6, leerer Span), und dokumentiere im Code warum Ihr das macht — nicht wie. „Avoids heap alloc in hot path, ~400 req/min as of 2026-06" reicht als Kommentar. In zwei Jahren freut sich Dein Nachfolger. -
Upgrade-Pfad. Das Feature ist .NET 9+. Falls Du noch auf .NET 6 oder 8 bist, ist das ein guter Grund, die Migration zu planen — aber eben als Teil einer breiteren .NET-Versions-Strategie, nicht als isolierter „Wir wollen AlternateLookup"-Sprint.
Eine Sache, die mich an der .NET-Community manchmal mildert: Steve Gordons Artikel ist genau die Art von tiefem technischen Inhalt, der Entwickler-Teams besser macht — auch wenn 80% der Leser das konkrete Feature nie einsetzen. Das Verständnis dafür, wie Heap-Allokationen entstehen, wie Spans funktionieren, warum stackalloc sicher ist ohne unsafe-Keyword — das sind Grundlagen, die jeder .NET-Dev auf dem Zettel haben sollte. Auch wenn er in seiner täglichen Arbeit Dictionary<string, string> baut und nie zurückschaut.
Im Mittelstand ist Wissensaufbau oft das schwierigere Problem als die Infrastrukturkosten. Wenn ein Artikel wie dieser dazu führt, dass ein Dev das nächste Mal bei einem Performance-Problem überhaupt erst an GC-Druck denkt, dann hat er seinen Zweck erfüllt.
Der vollständige Original-Beitrag von Steve Gordon: An Efficient Dictionary for IPAddress Tracking using .NET 9 with AlternateLookup and IAlternateEqualityComparer
Du betreibst .NET-APIs im Mittelstand und fragst Dich, ob Deine Infrastrukturkosten und Performance-Probleme wirklich optimierungsbedürftig sind — oder ob Du an der falschen Stelle gräbst? → 30 Min Klartext-Sparring
— Bernhard