Neu in .NET 8.0 [6]: ref readonly in C# 12.0

In der aktuellen Version von C# lassen sich Methodenparameter als unveränderlich deklarieren.

In Pocket speichern vorlesen Druckansicht 1 Kommentar lesen

(Bild: Pincasso/Shutterstock)

Lesezeit: 1 Min.
Von
  • Dr. Holger Schwichtenberg

Seit der zwölften Sprachversion von C# in .NET 8.0 gibt es für Methodenparameter auch den Modifizierer ref readonly. Mit diesem Zusatz darf die Methode den empfangenen Wert beziehungsweise die empfangene Objektreferenz nicht ändern. Bei der Übergabe von Referenztypen per ref readonly kann die Methode aber weiterhin die einzelnen Objektinhalte ändern.

Der Dotnet-Doktor – Holger Schwichtenberg

Dr. Holger Schwichtenberg ist technischer Leiter des Expertennetzwerks www.IT-Visions.de, das mit 53 renommierten Experten zahlreiche mittlere und große Unternehmen durch Beratungen und Schulungen sowie bei der Softwareentwicklung unterstützt. Durch seine Auftritte auf zahlreichen nationalen und internationalen Fachkonferenzen sowie mehr als 90 Fachbücher und mehr als 1500 Fachartikel gehört Holger Schwichtenberg zu den bekanntesten Experten für .NET und Webtechniken in Deutschland.

Die folgende Tabelle stellt ref readonly gegenüber mit anderen Parametermodifizierern. Wichtig für das Verhalten ist, ob als Parameter ein Wertetyp oder ein Referenztyp übergeben wird:

  Parameter ist Wertetyp Parameter ist Referenztyp
  Methode kann Wert ändern Methode kann einzelne Werte im übergebenen Objekt ändern Methode kann neues Objekt zuweisen
Übergabe ohne Modifizierer Ja, aber Aufrufer bekommt den neuen Wert nicht Ja Ja, aber Aufrufer bekommt das neue Objekt nicht
Übergabe mit in Nein Ja Nein
Übergabe mit ref Ja Ja Ja
Übergabe mit ref readonly (seit C# 12.0) Nein Ja Nein
Übergabe mit out Ja Ja Ja

Die folgenden drei Listings zeigen dazu Beispiele inklusive eines Screenshots der jeweiligen Bildschirmausgaben.

Der erste Code zeigt die Wirkung der Parametermodifizierer für Parameter als Wertetyp.

/// <summary>
/// Wertetypen an Methode übergeben
/// </summary>
public void ParameterValueTypes()
 {
  CUI.H2(nameof(ParameterValueTypes));
  #region 
  int a = 10;
  int b = 20;
  int c = 30;
  int d = 40;
  int e = 50;
  CUI.H3("Der Aufrufer hat vorher folgende Werte:");
  Console.WriteLine(a + ";" + b + ";" + c + ";" + d + ";" + e); 
  string r = ParameterDemoValueTypes(a, b, ref c, ref d, out e);
  CUI.H3("Die Methode hat folgende Werte:");
  Console.WriteLine(r); // 11;20;31;40
  CUI.H3("Der Aufrufer hat nachher folgende Werte:");
  Console.WriteLine(a + ";" + b + ";" + c + ";" + d + ";" + e);
  #endregion
 }

public string ParameterDemoValueTypes(int WertValue, in int WertIn, 
                                      ref int WertRef, 
                                      ref readonly int WertRefRO, 
                                      out int WertOut)
{
  WertValue++;
  // nicht erlaubt, da in-Wert: WertIn++;
  WertRef++;
  // WertRefRO++; // nicht erlaubt, da readonly
  // nicht erlaubt, da noch nicht initialisiert: WertOut++;
  WertOut = 41;
  return WertValue.ToString() + ";" + WertIn.ToString() + ";"
       + WertRef.ToString() + ";" + WertOut.ToString();
}

Der Code erzeugt folgende Ausgabe:

(Bild: Holger Schwichtenberg)

Das folgende Listing nutzt einen Parameter als Referenztyp Parameter (class Counter). Die Methode ändert den Wert im Objekt.

class Counter
{
 public string Name { get; set; }
 public int Value { get; set; }
 public override string ToString() => Name + "=" + Value;
}

/// <summary>
/// Referenztypen an Methode übergeben, die Wert in dem Objekt ändert
/// </summary>
public void ParameterReferenceType1()
{
  CUI.H2(nameof(ParameterReferenceType1));
  Counter a = new Counter() { Name = "a", Value = 10 };
  Counter b = new Counter() { Name = "b", Value = 20 };
  Counter c = new Counter() { Name = "c", Value = 30 };
  Counter d = new Counter() { Name = "d", Value = 40 };
  Counter e = new Counter() { Name = "e", Value = 50 }; 
  CUI.H3("Der Aufrufer hat vorher folgende Werte:");
  Console.WriteLine(a);
  Console.WriteLine(b);
  Console.WriteLine(c);
  Console.WriteLine(d);
  Console.WriteLine(e);
  string r = ParameterDemoRef1(a, b, ref c, ref d, out e);
  CUI.H3("Die Methode hat folgende Werte:");
  Console.WriteLine(r);
  CUI.H3("Der Aufrufer hat nachher folgende Werte:");
  Console.WriteLine(a);
  Console.WriteLine(b);
  Console.WriteLine(c);
  Console.WriteLine(d);
  Console.WriteLine(e);
}

public string ParameterDemoRef1(Counter WertValue, in Counter WertIn,
                                ref Counter WertRef, 
                                ref readonly Counter WertRefRO, 
                                out Counter WertOut)
 {
  WertValue.Value++;
  WertIn.Value++;
  WertRef.Value++;
  WertRefRO.Value++;
  WertOut = new Counter { Name = "d", Value = 41 };
  return WertValue.ToString() + ";" + WertRef.ToString() + ";" 
       + WertIn.ToString() + ";" + WertOut.ToString();
 }

Der Code erzeugt folgende Ausgabe:

(Bild: Holger Schwichtenberg)

Im dritten Beispiel ist der Parameter ein Referenztyp (class Counter). Die Methode ändert die Objektreferenz

class Counter
{
 public string Name { get; set; }
 public int Value { get; set; }
 public override string ToString() => Name + "=" + Value;
}

/// <summary>
/// Referenztypen an Methode übergeben, die neues Objekt zuweist und ändert
/// </summary>
public void ParameterReferenceType2()
 {
  CUI.H2(nameof(ParameterReferenceType2));
  Counter a = new Counter() { Name = "a", Value = 10 };
  Counter b = new Counter() { Name = "b", Value = 20 };
  Counter c = new Counter() { Name = "c", Value = 30 };
  Counter d = new Counter() { Name = "d", Value = 40 };
  Counter e = new Counter() { Name = "e", Value = 50 }; 
  CUI.H3("Der Aufrufer hat vorher folgende Werte:");
  Console.WriteLine(a);
  Console.WriteLine(b);
  Console.WriteLine(c);
  Console.WriteLine(d);
  Console.WriteLine(e);
  string r = ParameterDemoRef2(a, b, ref c, ref d, out e);
  CUI.H3("Die Methode hat folgende Werte:");
  Console.WriteLine(r);
  CUI.H3("Der Aufrufer hat nachher folgende Werte:");
  Console.WriteLine(a);
  Console.WriteLine(b);
  Console.WriteLine(c);
  Console.WriteLine(d);
  Console.WriteLine(e);
}

public string ParameterDemoRef2(Counter WertValue, in Counter WertIn, 
                                ref Counter WertRef, 
                                ref readonly Counter WertRefRO, 
                                out Counter WertOut)
{
  WertValue = new Counter { Name = "a*", Value = 101 };
  // nicht erlaubt:
  // WertIn = new Counter { Name = "b*", Value = 100 }; 
  WertRef = new Counter { Name = "c*", Value = 102 };
  // nicht erlaubt, da readonly:
  // WertRefRO = new Counter { Name = "c*", Value = 103 }; 
  WertOut = new Counter { Name = "d*", Value = 104 };
  return WertValue.ToString() + ";" + WertRef.ToString() + ";" 
       + WertIn.ToString() + ";" + WertOut.ToString();
 }

Der Code erzeugt folgende Ausgabe:

(Bild: Holger Schwichtenberg)

(rme)