Programmiersprache 25.04.2017, 10:41 Uhr

C# 7: Der richtige Einsatz von ref

Die neue Version von C# unterstützt nun Referenzen mittels des Schlüsselwortes ref. Damit lassen sich etwa schnelle Zugriffe auf Arrays programmieren, die noch dazu sicher sind.
(Quelle: dotnetpro)
Wer schon mal in C oder C++ entwickelt hat, kennt die Pointer-Spielereien, die man dort macht. Statt den Wert einer Variablen über den Stack zu übergeben, wird die Adresse der Speicherstelle über den Stack übergeben. Damit ist eine direkte Manipulation des Variableninhalts möglich - und allen möglichen Schweinereien Tür und Tor geöffnet. Der große Vorteil diese Vorgehens ist aber, dass damit Manipulation etwa an Array-Elementen immens schnell gehen. Die Gefahr ist aber groß, dass die falschen Speicherstellen verändert werden und das Programm dadurch fehlerhaft wird oder gar abstürzt. Diese Fehler zu finden, ist knifflig.
Auch C# bietet eine begrenzte Möglichkeit des Referenzzugriffs. Darüber hat Achim Oellers schon 2004 in der dotnetpro geschrieben. Das Zauberwort heißt unsafe.
Was ist das Resultat von der Ausführung von folgendem Code?

class Program
{
  unsafe static void Main(string[] args)
  {
    int i = 42;
    MIncrement(&i);
    Console.WriteLine(i);
  }

  static unsafe void MIncrement(int* x)
  {
    *x = *x + 1;
  }
}
Richtig: 43. Da nicht der Wert, sondern die Referenz, also die Adresse der Variablen übergeben wird, verändert MIncrement den Inhalt dieser Variablen. Wie geschrieben, funktionierte das schon mit den ersten Versionen von .NET.

Und dann kam ref

Die Problematik von unsafe-Code hebt Marc Gravell in einem Blogpost hervor, in dem er auch die Möglichkeiten und Grenzen von ref erklärt.
  • Der Garbage Collector weigert sich auch nur zu versuchen, Pointern zu folgen.
  • Es kann unter Umständen beliebiger Code verändert werden.
  • das Schlüsselwort unsafe muss verwendet werden, was zu schwer aufzufindenden Fehlern führen kann.
  • außerdem funktionieren Pointer nur mit einer kleinen Untermenge an Typen.
Aus diesen Gründen sollte man - wenn benötigt - den Code mittels des neuen Schlüsselworts ref formulieren:
class Program
{
  static void Main(string[] args)
  {
    int i = 42;
    MIncrement(ref i);
    Console.WriteLine(i);
  }

  static unsafe void MIncrement(ref int x)
  {
    x = x + 1;
  }
}
Es kommt das gleich Ergebnis heraus. Trotzdem kann der Garbage Collector aktiv werden und die Gefahr von Bugs ist nicht so groß.

Allerdings muss man - wie bei Referenzen immer - genau aufpassen, was man als Referenz übergibt. Handelt es sich hierbei etwa um eine lokale Variable, auf die außerhalb des Gültigkeitsbereich zugegriffen wird, wird der Compiler das nicht zulassen. Der folgende Code führt zu einem Fehler bei der Kompilierung: Das lokale Element i kann nicht als Verweis zurückgegeben werden, weil es kein lokales ref-Elelement ist.
ref int ReturnInvalidStackReference()
{
    int i = 32;
    return ref i; // can't do this
}
void WhatHappensHere()
{
    ref int v = ref ReturnInvalidStackReference();
    CallSomeOtherMethods(); // to use the stack
    int i = v; // dereference the ref
}
Besonders interessant sind aber Referenzen in Bezug auf Arrays:
ref int GetArrayReference(int[] items, int index)
    => ref items[index];

void IncrementInsideArrayByRef()
{
    int[] values = { 1, 2, 3 };

    ref int item = ref GetArrayReference(values, 1);
    IncrementByRef(ref item);
    // what does this line print, and why?
    Console.WriteLine(string.Join(",", values));
}
Damit lassen sich Array-Elemente manipulieren, ohne sie erst auf den Stack legen zu müssen.

Zu den Möglichkeiten schreibt Marc Gravell in seinem Blogpost.


Das könnte Sie auch interessieren