Vor ein paar Wochen saß ich bei einem Fertigungsbetrieb, rund 80 Mitarbeitende, ERP-gestütztes Produktmanagement. Der Produktkatalog lief über ein selbst gebastelte .NET-Webanwendung, und der Vertriebsleiter hat mich zu sich gerufen mit den Worten: „Bernhard, unsere Kunden beschweren sich, dass die Artikelnummern falsch sortiert sind." Ich schaue drauf: Artikel 10 erscheint vor Artikel 2. Klassischer Lexikografischer-Sortierung-Fehler. Der Entwickler, der das ursprünglich gebaut hatte, war seit zwei Jahren weg. Im Code stand ein schlichtes OrderBy(x => x.Name). Die Behebung hat uns insgesamt drei Stunden gekostet — davon zwei Stunden Diskussion, was eigentlich die richtige Sortierung für diesen Kunden sein soll.
Das war der Moment, wo mir klar wurde: Das Problem ist nicht das Sortieren. Das Problem ist, dass jeder eine Meinung dazu hat, was "richtig" bedeutet.
Was Khalid Abuhakmeh beschreibt
Khalid Abuhakmeh, Developer Advocate bei JetBrains, hat in seinem Artikel einen eigenen NumericOrderer für .NET 9 implementiert — als IComparer"Windows 10" vor "Windows 7" zu stellen (wie es ein einfacher String-Vergleich täte), soll das Ergebnis "Windows 7" → "Windows 10" → "Windows 11" lauten. Die Implementierung nutzt CommonPrefixLength und int.TryParse auf Spans — das bedeutet: keine unnötigen Heap-Allokationen, direktes Arbeiten auf dem Speicher. Khalid selbst räumt ein, dass ein generischer NumericOrderer ein Herkulesprojekt ist, und empfiehlt stattdessen, die Lösung auf den konkreten Use Case zuzuschneiden. Das ist eine ehrliche, nüchterne Schlussfolgerung — ich schätze das.
Wie das in einem KMU wirklich landet
Lass mich direkt sein: Die Span-API-Diskussion ist für 90 % der KMU-Projekte, die ich begleite, akademisch. Nicht weil die Teams schlecht sind — sondern weil das Bottleneck bei diesen Projekten nicht im Allocator-Druck liegt, sondern in der Anforderungsklärung.
Das ist der Teil, den technische Artikel fast immer überspringen.
In der Praxis bekommst du als Entwickler nicht die saubere Liste aus dem Artikel ("Godfather", "Godfather 2", "Godfather 3"). Du bekommst einen Export aus einem 15 Jahre alten ERP-System, in dem Artikelnummern so aussehen: "A-100", "A-100b", "A-100-NEU", "A100 (alt)", "A 100". Und wenn du dann anfängst, einen generischen NumericOrderer darüber zu legen, wirst du nach drei Iterationen feststellen, dass jede Fachabteilung andere Erwartungen hat — Einkauf will alphabetisch, Vertrieb will nach Produktlinie, Lager will nach Einlagerungsdatum.
Das ist kein .NET-Problem. Das ist ein Anforderungsproblem.
Was ich in solchen Projekten tatsächlich mache:
Erstens: Ich trenne immer zwischen dem Datenobjekt und seiner Anzeige. Wenn eine Artikelnummer wie "Produkt 10" sortiert werden muss, ist das ein Signal, dass die Datenbasis kaputt ist — nämlich dass eine semantische Information (die Versionsnummer) in einem Freitext-Feld steckt, statt in einem eigenen sortierbaren Feld. Die eigentliche Empfehlung in 70 % der Fälle, die ich sehe: Datenmodell reparieren, nicht Sortierlogik verkomplizieren.
Zweitens: Wenn das Datenmodell nicht anfassbar ist — weil Legacy, weil keine Migration geplant ist, weil Budget — dann ja, ein Custom Comparer wie Khalids Implementierung ist der richtige Weg. Aber ich baue den nicht generisch. Ich schreibe ihn für genau einen Use Case, mit genau den Testdaten, die der Kunde mir liefert, und mit einem Kommentar im Code, der erklärt, warum diese Sortierregel gilt. Nicht aus Schönheitsgründen, sondern damit der nächste Entwickler in drei Jahren nicht wieder von vorne anfängt.
Drittens: Khalids Hinweis auf die Span-APIs ist trotzdem relevant — aber aus einem anderen Grund als Performance. Er demonstriert, dass .NET mittlerweile Werkzeuge bietet, mit denen man solche Operationen lesbar schreiben kann, ohne auf externe NuGet-Pakete zurückzugreifen. Für KMU-Projekte, die ich betreue, ist die Abhängigkeitsminimierung oft wichtiger als jede Mikro-Optimierung. Ein Custom Comparer mit 40 Zeilen .NET-Standardbibliothek ist besser wartbar als eine Drittbibliothek, die in 18 Monaten kein Update mehr bekommt.
Meine konkrete Empfehlung
Wenn du in einem KMU-Projekt auf dieses Problem stößt, geh in dieser Reihenfolge vor:
1. Erst klären, ob das Problem echt ist. Wie oft wird diese Sortierung aufgerufen? Von wem? Beeinflusst eine "falsche" Sortierung eine Entscheidung oder nur die Optik? Ich erlebe regelmäßig, dass ein Feature-Request "falsche Sortierung" in Wirklichkeit ein "wir haben das System nie richtig eingerichtet"-Problem ist.
2. Datenmodell prüfen. Kann die Versionsnummer oder Sequenznummer in ein eigenes Datenbankfeld? Dann ist ein ORDER BY version_number ASC in SQL die sauberste, wartbarste Lösung — und kein C#-Code, den du in zwei Jahren erklären musst.
3. Erst dann Custom Comparer. Und dann exakt so, wie Khalid es beschreibt: use-case-spezifisch, mit konkreten Testdaten, ohne Überengineering für hypothetische Fälle. Die Span-Implementierung ist dabei tatsächlich einen Blick wert — nicht wegen der Performance, sondern weil sie zeigt, dass das mit Bordmitteln sauber lösbar ist.
4. Keine römischen Ziffern. Khalid erwähnt das als Edge Case. Ich sage: Wenn du in einem KMU-Projekt anfängst, römische Ziffern in einer Sortierlogik zu unterstützen, hast du ein Scope-Problem, kein Sortierproblem.
Eines noch zum Abschluss, weil es mir wichtig ist: Khalids Schlussfolgerung — dass ein general-purpose NumericOrderer wahrscheinlich ein Herkulesprojekt ist und man sich auf den konkreten Datensatz konzentrieren soll — ist aus meiner Sicht die wertvollste Aussage im ganzen Artikel. In der KMU-Beratung nenne ich das "Fitnesstraining für das Richtige": Es ist verlockend, eine elegante generische Lösung zu bauen. Aber in Projekten mit 2-3 Entwicklern und echtem Zeitdruck ist die spezifische, testbare Lösung fast immer der bessere Trade-off.
Der vollständige Original-Beitrag von Khalid Abuhakmeh: Writing a String Numeric Comparer with .NET 9
Hast du in deinem Projekt gerade so ein Sortierungs- oder Datenmodell-Chaos? Dann lass uns 30 Minuten reden — ohne Verkaufspitch, ohne PowerPoint.
— Bernhard