C# 14: Performance Inkrement-Operatoren

Hin und wieder sind eigene Klassen mit ++ und -- Operatoren notwendig. Das mehr oder weniger große Problem dabei ist dies bisher etwas ... naja, verschwenderisch ist.
Aber C# 14 bringt endlich eine elegante Lösung!

Das alte Problem

Stellen wir uns vor, wir haben eine Counter-Klasse und wollen sie inkrementieren können - quisi quasi das Hello World der Counter

public class Counter
{
    private int value;
    
    public static Counter operator ++(Counter c)
    {
        return new Counter(c.value + 1); // Ups, schon wieder eine neue Instanz...
    }
}

Jedes Mal wenn wir ++myCounter schreiben, wird eine komplett neue Instanz erstellt. Bei ein paar Aufrufen - Kein Problem. Aber wenn wir das in einer heißen Schleife machen, dann sammelt sich der Garbage ziemlich schnell an. 

Die neue Lösung: Instanz-Operatoren

C# 14 lässt euch die Operatoren als Instanzmethoden definieren:

public class Counter
{
    private int value;
    
    // Ta-da! Kein 'static', kein neues Objekt
    public void operator ++()
    {
        value++; // Einfach den vorhandenen Wert ändern
    }
    
    public void operator --()
    {
        value--;
    }
}

Was ist anders?

Die neuen Instanz-Operatoren haben ein paar klare Regeln:

  • public - müssen öffentlich sein (logisch)
  • Kein static - sind ja Instanzmethoden
  • void Return-Typ - geben nichts zurück, ändern nur die Instanz
  • Keine Parameter - auch keine mit Default-Werten

Warum ist das so cool?

In Abwandlung von Steve Balmer: "Performance, Performance, Performance!"

Früher:

for (int i = 0; i < 1000000; i++)
{
    ++myCounter; // 1 Million neue Counter-Objekte...
}

Jetzt:

for (int i = 0; i < 1000000; i++)
{
    ++myCounter; // Immer das gleiche Objekt, nur der Wert ändert sich
}

Besonders bei Value-ähnlichen Typen wie Koordinaten, Zählern oder mathematischen Objekten ist das ein Game-Changer!

Ein praktisches Beispiel

Hier ein Point-Typ, der jetzt endlich effizient inkrementiert werden kann:

public class Point
{
    public int X { get; private set; }
    public int Y { get; private set; }
    
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }
    
    public void operator ++()
    {
        X++;
        Y++;
    }
    
    public void operator --()
    {
        X--;
        Y--;
    }
}

// Verwendung:
var point = new Point(1, 1);
++point; // point ist jetzt (2, 2)
++point; // point ist jetzt (3, 3)


Wichtiger Hinweis:
Instanz-Operatoren ändern den Zustand des Objekts! Das bedeutet, dass wir bei Multi-Threading aufpassen müssen:

 
 
 
// Potenzielle Race-Condition:
Parallel.For(0, 1000, i => ++sharedCounter);

// Besser mit Locking:
public void operator ++()
{
    lock (lockObject)
    {
        value++;
    }
}
 

Happy Coding! 💻