Freigeben über


Dekonstruieren von Tupeln und anderen Typen

Ein Tupel stellt einen einfachen Weg bereit, um mehrere Werte aus einem Methodenaufruf abzurufen. Sobald Sie den Tupel abrufen, müssen Sie jedoch seine individuellen Elemente bearbeiten. Jedes Element einzeln zu bearbeiten ist jedoch mühselig, wie das folgende Beispiel zeigt. Die Methode QueryCityData gibt ein Dreiertupel zurück, und jedes seiner Elemente wird in einem separaten Vorgang einer Variablen zugewiesen.

public class Example
{
    public static void Main()
    {
        var result = QueryCityData("New York City");

        var city = result.Item1;
        var pop = result.Item2;
        var size = result.Item3;

         // Do something with the data.
    }

    private static (string, int, double) QueryCityData(string name)
    {
        if (name == "New York City")
            return (name, 8175133, 468.48);

        return ("", 0, 0);
    }
}

Das Abrufen mehrerer Feld- und Eigenschaftswerte aus einem Objekt kann ebenso mühsam sein: Sie müssen einen Feld- oder Eigenschaftswert einer Variablen auf Member-für-Member-Basis zuweisen.

Sie können in einem einzigen deconstruct-Vorgang mehrere Elemente aus einem Tupel oder mehrere berechnete, Feld- und Eigenschaftswerte aus einem Objekt abrufen. Um ein Tupel zu dekonstruieren, weisen Sie seine Elemente einzelnen Variablen zu. Wenn Sie ein Objekt dekonstruieren, weisen Sie bestimmte Elemente einzelnen Variablen zu.

Tupel

Die Features von C# bieten eine integrierte Unterstützung für Dekonstruieren von Tupeln, sodass Sie alle Elemente in einem Tupel mit einem einzigen Vorgang entpacken können. Die allgemeine Syntax für das Dekonstruieren eines Tupel ist ähnlich der Syntax für das Definieren eines Tupel: Auf der linken Seite einer Zuweisungsanweisung umschließen Sie die Variablen, denen die Elemente zugewiesen werden sollen, mit Klammern. Die folgende Anweisung weist die Elemente eines Vierertupels beispielsweise vier einzelnen Variablen zu:

var (name, address, city, zip) = contact.GetAddressInfo();

Es gibt drei Wege zum Dekonstruieren eines Tupels:

  • Sie können den Typ jedes Felds innerhalb der Klammern explizit deklarieren. Das folgende Beispiel verwendet diese Methode, um das Dreiertupel zu dekonstruieren, das von der QueryCityData-Methode zurückgegeben wurde.

    public static void Main()
    {
        (string city, int population, double area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    
  • Sie können das Schlüsselwort var verwenden, damit C# den Typ jeder Variable herleitet. Platzieren Sie das Schlüsselwort var außerhalb der Klammern. Im folgenden Beispiel wird ein Typrückschluss beim Dekonstruieren des von der QueryCityData-Methode zurückgegebenen Dreiertupels verwendet.

    public static void Main()
    {
        var (city, population, area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    

    Sie können das Schlüsselwort var auch einzeln mit beliebigen oder allen Variablendeklarationen innerhalb der Klammern verwenden.

    public static void Main()
    {
        (string city, var population, var area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    

    Das vorherige Beispiel ist umständlich und wird nicht empfohlen.

  • Schließlich können Sie das Tupel in bereits deklarierte Variablen zerlegen.

    public static void Main()
    {
        string city = "Raleigh";
        int population = 458880;
        double area = 144.8;
    
        (city, population, area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    
  • Sie können variable Deklaration und Zuweisung in einer Dekonstruktion kombinieren.

    public static void Main()
    {
        string city = "Raleigh";
        int population = 458880;
    
        (city, population, double area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    

Sie können keinen bestimmten Typ außerhalb der Klammern angeben, auch wenn jedes Feld im Tupel den selben Typ hat. Dadurch wird der Compilerfehler CS8136 „Durch Dekonstruktion der Form „var (...)“ wird ein bestimmter Typ für „var“ unzulässig“ generiert.

Sie müssen ebenfalls jedes Element des Tupels einer Variablen zuweisen. Wenn Sie Elemente auslassen, generiert der Compiler den Fehler: CS8132, „Tupel mit ‚x‘ Elementen kann nicht in ‚y‘ Variablen dekonstruiert werden.“

Tupelelemente mit Ausschussvariablen

Häufig sind Sie beim Dekonstruieren eines Tupel nur an den Werten mancher Elemente interessiert. Sie können die Unterstützung von C# für Ausschüsse nutzen, bei denen es sich um lesegeschützte Variablen handelt, die Sie ignorieren möchten. Sie deklarieren einen Ausschuss mit einem Unterstrich („_”) in einer Zuordnung. Sie können beliebig viele Werte verwerfen; ein einzelner Ausschuss, _, stellt alle verworfenen Werte dar.

Das folgende Beispiel veranschaulicht die Verwendung von Tupels mit Ausschüssen. Die QueryCityDataForYears-Methode gibt ein Sechsertupel mit dem Namen einer Stadt, ihrer Fläche, einer Jahreszahl, der Bevölkerung der Stadt in diesem Jahr, einer zweiten Jahreszahl und der Bevölkerung der Stadt im zweiten Jahr zurück. Das Beispiel zeigt die Veränderung der Bevölkerung zwischen diesen beiden Jahren. Von den Daten, die im Tupel verfügbar sind, ist die Fläche der Stadt nicht relevant für uns und außerdem kennen wir den Namen der Stadt und die zwei Datumswerte zur Entwurfszeit. Darum sind wir nur an den zwei Bevölkerungsgwerten interessiert, die im Tupel gespeichert sind und behandeln die restlichen Werte als Ausschuss.

using System;

public class ExampleDiscard
{
    public static void Main()
    {
        var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

        Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
    }

    private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
    {
        int population1 = 0, population2 = 0;
        double area = 0;

        if (name == "New York City")
        {
            area = 468.48;
            if (year1 == 1960)
            {
                population1 = 7781984;
            }
            if (year2 == 2010)
            {
                population2 = 8175133;
            }
            return (name, area, year1, population1, year2, population2);
        }

        return ("", 0, 0, 0, 0, 0);
    }
}
// The example displays the following output:
//      Population change, 1960 to 2010: 393,149

Benutzerdefinierte Typen

C# bietet eine integrierte Unterstützung für die Dekonstruierung von Tupel-Typen sowie DictionaryEntry-Typen record und . Als Autor einer Klasse, Struktur oder Schnittstelle können Sie jedoch den Instanzen des Typs das Dekonstruieren durch die Implementierung von mindestens einer Methode Deconstruct gestatten. Die Methode gibt „leer” zurück. Ein out-Parameter in der Methodensignatur stellt jeden Wert dar, der dekonstruiert werden soll. Die folgende Methode Deconstruct einer Person-Klasse gibt beispielsweise den Vor-, Zweit- und Nachnamen zurück:

public void Deconstruct(out string fname, out string mname, out string lname)

Sie können dann eine Instanz der Person-Klasse mit dem Namen p mit einer Zuweisung wie im folgenden Code dekonstruieren:

var (fName, mName, lName) = p;

Das folgende Beispiel überlädt die Methode Deconstruct, um verschiedene Kombinationen von Eigenschaften eines Person-Objekts zurückzugeben. Einzelne Überladungen geben Folgendes zurück:

  • Ein Vor- und Nachname.
  • Ein Vorname, ein zweiter Vorname und ein Nachname
  • Ein Vorname, ein Familienname, ein Ortsname und ein Bundeslandname.
using System;

public class Person
{
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }
    public string City { get; set; }
    public string State { get; set; }

    public Person(string fname, string mname, string lname,
                  string cityName, string stateName)
    {
        FirstName = fname;
        MiddleName = mname;
        LastName = lname;
        City = cityName;
        State = stateName;
    }

    // Return the first and last name.
    public void Deconstruct(out string fname, out string lname)
    {
        fname = FirstName;
        lname = LastName;
    }

    public void Deconstruct(out string fname, out string mname, out string lname)
    {
        fname = FirstName;
        mname = MiddleName;
        lname = LastName;
    }

    public void Deconstruct(out string fname, out string lname,
                            out string city, out string state)
    {
        fname = FirstName;
        lname = LastName;
        city = City;
        state = State;
    }
}

public class ExampleClassDeconstruction
{
    public static void Main()
    {
        var p = new Person("John", "Quincy", "Adams", "Boston", "MA");

        // Deconstruct the person object.
        var (fName, lName, city, state) = p;
        Console.WriteLine($"Hello {fName} {lName} of {city}, {state}!");
    }
}
// The example displays the following output:
//    Hello John Adams of Boston, MA!

Mehrere Deconstruct-Methoden mit der gleichen Anzahl von Parametern sind mehrdeutig. Sie müssen darauf achten, Deconstruct-Methoden mit einer unterschiedlichen Anzahl von Parametern oder „Stelligkeit“ zu definieren. Deconstruct-Methoden mit der gleichen Anzahl von Parametern können bei der Überladungsauflösung nicht unterschieden werden.

Benutzerdefinierter Typ mit Ausschussvariablen

Genau wie bei Tupels können Sie Ausschüsse verwenden, um ausgewählte Elemente zu ignorieren, die von einer Deconstruct-Methode zurückgegeben werden. Eine Variable mit dem Namen „_” repräsentiert einen Ausschuss. Ein einzelner Dekonstruktionsvorgang kann mehrere Ausschüsse enthalten.

Im folgenden Beispiel wird ein Person-Objekt in vier Zeichenfolgen (Vor- und Nachnamen, Ort und Staat) dekonstruiert, Nachname und Staat werden jedoch verworfen.

// Deconstruct the person object.
var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
//      Hello John of Boston!

Dekonstruktionserweiterungsmethoden

Wenn Sie nicht der Autor einer Klasse, Struktur oder Schnittstelle sind, können Sie die Objekte dieses Typs dennoch dekonstruieren, indem Sie eine oder mehrere Deconstructextension methods (Erweiterungsmethoden) implementieren, um die Werte zurückzugeben, an denen Sie interessiert sind.

Im folgenden Beispiel werden zwei Deconstruct-Erweiterungsmethoden für die Klasse System.Reflection.PropertyInfo definiert. Die erste Methode gibt eine Gruppe von Werten zurück, die die Merkmale der Eigenschaft angeben. Die zweite gibt die Zugriffsebene der Eigenschaft an. Boolesche Werte geben an, ob die Eigenschaft separate get- und set-Accessoren oder eine andere Zugänglichkeit hat. Wenn es nur eine Zugriffsmethode gibt oder die Get- und Set-Zugriffsmethode über dieselbe Zugriffsebene verfügen, gibt die Variable access die Zugriffsebene der Eigenschaft als Ganzes an. Andernfalls wird die Zugriffsebene der Get- und Set-Zugriffsmethoden von den Variablen getAccess und setAccess angezeigt.

using System;
using System.Collections.Generic;
using System.Reflection;

public static class ReflectionExtensions
{
    public static void Deconstruct(this PropertyInfo p, out bool isStatic,
                                   out bool isReadOnly, out bool isIndexed,
                                   out Type propertyType)
    {
        var getter = p.GetMethod;

        // Is the property read-only?
        isReadOnly = ! p.CanWrite;

        // Is the property instance or static?
        isStatic = getter.IsStatic;

        // Is the property indexed?
        isIndexed = p.GetIndexParameters().Length > 0;

        // Get the property type.
        propertyType = p.PropertyType;
    }

    public static void Deconstruct(this PropertyInfo p, out bool hasGetAndSet,
                                   out bool sameAccess, out string access,
                                   out string getAccess, out string setAccess)
    {
        hasGetAndSet = sameAccess = false;
        string getAccessTemp = null;
        string setAccessTemp = null;

        MethodInfo getter = null;
        if (p.CanRead)
            getter = p.GetMethod;

        MethodInfo setter = null;
        if (p.CanWrite)
            setter = p.SetMethod;

        if (setter != null && getter != null)
            hasGetAndSet = true;

        if (getter != null)
        {
            if (getter.IsPublic)
                getAccessTemp = "public";
            else if (getter.IsPrivate)
                getAccessTemp = "private";
            else if (getter.IsAssembly)
                getAccessTemp = "internal";
            else if (getter.IsFamily)
                getAccessTemp = "protected";
            else if (getter.IsFamilyOrAssembly)
                getAccessTemp = "protected internal";
        }

        if (setter != null)
        {
            if (setter.IsPublic)
                setAccessTemp = "public";
            else if (setter.IsPrivate)
                setAccessTemp = "private";
            else if (setter.IsAssembly)
                setAccessTemp = "internal";
            else if (setter.IsFamily)
                setAccessTemp = "protected";
            else if (setter.IsFamilyOrAssembly)
                setAccessTemp = "protected internal";
        }

        // Are the accessibility of the getter and setter the same?
        if (setAccessTemp == getAccessTemp)
        {
            sameAccess = true;
            access = getAccessTemp;
            getAccess = setAccess = String.Empty;
        }
        else
        {
            access = null;
            getAccess = getAccessTemp;
            setAccess = setAccessTemp;
        }
    }
}

public class ExampleExtension
{
    public static void Main()
    {
        Type dateType = typeof(DateTime);
        PropertyInfo prop = dateType.GetProperty("Now");
        var (isStatic, isRO, isIndexed, propType) = prop;
        Console.WriteLine($"\nThe {dateType.FullName}.{prop.Name} property:");
        Console.WriteLine($"   PropertyType: {propType.Name}");
        Console.WriteLine($"   Static:       {isStatic}");
        Console.WriteLine($"   Read-only:    {isRO}");
        Console.WriteLine($"   Indexed:      {isIndexed}");

        Type listType = typeof(List<>);
        prop = listType.GetProperty("Item",
                                    BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
        var (hasGetAndSet, sameAccess, accessibility, getAccessibility, setAccessibility) = prop;
        Console.Write($"\nAccessibility of the {listType.FullName}.{prop.Name} property: ");

        if (!hasGetAndSet | sameAccess)
        {
            Console.WriteLine(accessibility);
        }
        else
        {
            Console.WriteLine($"\n   The get accessor: {getAccessibility}");
            Console.WriteLine($"   The set accessor: {setAccessibility}");
        }
    }
}
// The example displays the following output:
//       The System.DateTime.Now property:
//          PropertyType: DateTime
//          Static:       True
//          Read-only:    True
//          Indexed:      False
//
//       Accessibility of the System.Collections.Generic.List`1.Item property: public

Erweiterungsmethode für Systemtypen

Einige Systemtypen stellen zur Vereinfachung die Deconstruct-Methode bereit. Beispielsweise stellt der Typ System.Collections.Generic.KeyValuePair<TKey,TValue> diese Funktionalität bereit. Wenn Sie über ein System.Collections.Generic.Dictionary<TKey,TValue> iterieren, ist jedes Element ein KeyValuePair<TKey, TValue> und kann dekonstruiert werden. Betrachten Sie das folgenden Beispiel:

Dictionary<string, int> snapshotCommitMap = new(StringComparer.OrdinalIgnoreCase)
{
    ["https://github.com/dotnet/docs"] = 16_465,
    ["https://github.com/dotnet/runtime"] = 114_223,
    ["https://github.com/dotnet/installer"] = 22_436,
    ["https://github.com/dotnet/roslyn"] = 79_484,
    ["https://github.com/dotnet/aspnetcore"] = 48_386
};

foreach (var (repo, commitCount) in snapshotCommitMap)
{
    Console.WriteLine(
        $"The {repo} repository had {commitCount:N0} commits as of November 10th, 2021.");
}

record-Typen

Wenn Sie einen record-Typ unter Verwendung von mindestens zwei Positionsparametern deklarieren, erstellt der Compiler eine Deconstruct-Methode mit einem out-Parameter für jeden Positionsparameter in der record-Deklaration. Weitere Informationen finden Sie unter Positionssyntax für die Eigenschaftsdefinition und Dekonstruktorverhalten in abgeleiteten Datensätzen.

Siehe auch