CodeGym /Kurse /C# SELF /Mehrere Werte aus einer Funktion zurückgeben: out-Paramet...

Mehrere Werte aus einer Funktion zurückgeben: out-Parameter und Tupel

C# SELF
Level 11 , Lektion 3
Verfügbar

1. Einführung

Stell dir vor, wir haben eine Funktion, die für einen übergebenen User (zum Beispiel per Name) direkt Alter, Registrierungsdatum und einen Aktiv-Flag zurückgeben soll. In C und frühen Versionen von C# war es normal, dass der Rückgabetyp einer Funktion nur einen Wert enthält. Was tun, wenn es mehrere Werte sein sollen? Diese Frage hat verschiedene Ansätze hervorgebracht, jeder mit eigenen Vor- und Nachteilen.
In den vorherigen Vorlesungen haben wir schon Tupel kennengelernt. Jetzt schauen wir uns out-Parameter an und vergleichen diese beiden Ansätze.

Die beliebtesten Ansätze:

  • Verwendung von out-Parametern.
  • Ein Objekt mit den nötigen Feldern zurückgeben (anonymer Typ oder eigene Klasse).
  • Ein Tupel (ValueTuple) zurückgeben.

In dieser Vorlesung schauen wir uns zwei dieser Ansätze genauer an – out-Parameter und Tupel – und vergleichen sie direkt, damit du die Vorteile jedes Ansatzes beim Zurückgeben mehrerer Werte aus einer Funktion verstehst.

2. out-Parameter: ein Gruß aus der Vergangenheit

Wenn du eine Funktion wie diese siehst:


void GetUserInfo(string userName, out int age, out DateTime registrationDate, out bool isActive)
{
    // hier Berechnungen
    age = 42;
    registrationDate = new DateTime(2010, 1, 1);
    isActive = true;
}

dann fragt man sich: Ist das eine Funktion oder eine kleine Autowaschanlage? So viele Sachen werden nach außen zurückgegeben, und alles am eigentlichen Rückgabewert vorbei!

Wie das funktioniert

Der aufrufende Code muss vorher Variablen deklarieren, die die Funktion dann befüllt:

int age;
DateTime reg;
bool isActive;

GetUserInfo("Bob", out age, out reg, out isActive);

// Jetzt sind alle Variablen befüllt
Console.WriteLine($"{age}, {reg}, {isActive}");

Nachteile des out-Ansatzes

  • Schwerer zu lesen: Die Methodensignatur wird sehr breit, und die Bedeutung der Rückgabewerte ist nicht immer klar aus den Parameternamen.
  • Unpraktisch bei Call-Chains: So eine Methode kann man nicht einfach in eine Kette einbauen (z.B. das Ergebnis direkt an eine andere Methode übergeben).
  • Mutiert übergebene Variablen: Die Methode muss existierende Variablen verändern; vergisst du das out, gibt's einen Compilerfehler.
  • Initialisierungs-Overhead: Der Compiler zwingt dich, externe Variablen zu deklarieren, selbst wenn du das Ergebnis nur einmal brauchst.
  • Begrenzte Lesbarkeit bei großen Methoden: Wenn es viele out-Parameter gibt, verliert man schnell den Überblick, was wofür ist.

Hier fangen Tupel an, richtig zu glänzen.

3. Tupel — die Evolution des Ansatzes

Vergleichen wir am Beispiel

Mit Tupel:


public (int Age, DateTime RegistrationDate, bool IsActive) GetUserInfo(string userName)
{
    // Datenbanksuche simulieren
    return (42, new DateTime(2010, 1, 1), true);
}

Verwendung:

// Wir bekommen alle Werte auf einmal und geben ihnen Namen
var (age, regDate, isActive) = GetUserInfo("Bob");
Console.WriteLine($"{age}, {regDate}, {isActive}");

Keine unnötigen Deklarationen, kein out, alles super lesbar!

Vergleichstabelle: out vs tuple

Kriterium Out-Parameter Tupel (ValueTuple)
Signatur lang, out-Parameter in der Liste kompakt, alles kommt als ein "Paket"
Verwendung Variablen deklarieren, dann Methode aufrufen kann direkt deconstructed werden
Lesbarkeit geht oft verloren, wenn es viele out-Parameter gibt Elementnamen sind sofort klar
Einfach kombinierbar? nein ja, kann verschachtelt und weitergegeben werden

Tupel VS. out — was passiert unter der Haube

Mit out-Parametern arbeitet die Funktion eigentlich mit Speicher außerhalb ihres eigenen "Territoriums": vereinfacht gesagt, sie verändert Variablen, die woanders erzeugt wurden. Das erfordert Vorsicht, weil man versehentlich den Zustand von Variablen "vermüllen" kann (zum Glück zwingt der Compiler dich zum Zuweisen!).

Ein Tupel dagegen ist eine Datenstruktur, die die Funktion komplett initialisiert und dann zurückgibt. Alles ist ein Paket, das du unterwegs nicht verlierst und nicht vergisst.

Code lesen und pflegen

Seien wir ehrlich – wenn du nach einer Woche fremden Code anschaust, willst du lieber sowas sehen:

(var age, var city, var isActive) = GetUserInfo("Anna");

anstatt

int age;
string city;
bool isActive;
GetUserInfo("Anna", out age, out city, out isActive);

Tupel machen Methodensignaturen weniger noisy und das Ergebnis ihrer Verwendung ist sofort verständlich.

4. Situationen, in denen Tupel besonders praktisch sind

1. Wenn du Ergebnis und Fehlermeldung zurückgeben willst

public (bool Success, string ErrorMessage) TryProcess(string data)
{
    if (string.IsNullOrEmpty(data))
        return (false, "Keine Daten");

    // Daten verarbeiten...
    return (true, "");
}

var (ok, error) = TryProcess(input);
if (!ok)
    Console.WriteLine($"Fehler: {error}");

2. Für Funktionen, die mehrere Suchergebnisse zurückgeben

public (User? FoundUser, int Index) FindUserByName(User[] users, string name)
{
    for (int i = 0; i < users.Length; i++)
    {
        if (users[i].Name == name)
            return (users[i], i);
    }
    return (null, -1);
}

3. Für “Paare” von Werten: Minimum und Maximum berechnen

public (int min, int max) FindMinMax(int[] numbers)
{
    int min = numbers[0], max = numbers[0];
    foreach (var n in numbers)
    {
        if (n < min) min = n;
        if (n > max) max = n;
    }
    return (min, max);
}

var (minValue, maxValue) = FindMinMax(arr);
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION