Delen via


Tuples en andere typen deconstrueren

Een tuple biedt een lichtgewicht manier om meerdere waarden op te halen uit een methode-aanroep. Maar zodra u de tuple hebt opgehaald, moet u de afzonderlijke elementen afhandelen. Werken op basis van elementen is lastig, zoals in het volgende voorbeeld wordt weergegeven. De QueryCityData methode retourneert een drie-tuple en elk van de elementen wordt toegewezen aan een variabele in een afzonderlijke bewerking.

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);
    }
}

Het ophalen van meerdere veld- en eigenschapswaarden uit een object kan even lastig zijn: u moet een veld- of eigenschapswaarde toewijzen aan een variabele op basis van een lid per lid.

U kunt meerdere elementen ophalen uit een tuple of meerdere veld-, eigenschaps- en berekende waarden ophalen uit een object in één deconstruct-bewerking . Als u een tuple wilt deconstrueren, wijst u de elementen toe aan afzonderlijke variabelen. Wanneer u een object deconstrueert, wijst u geselecteerde waarden toe aan afzonderlijke variabelen.

Tuples

C# biedt ingebouwde ondersteuning voor het deconstrueren van tuples, waarmee u alle items in een tuple in één bewerking kunt uitpakken. De algemene syntaxis voor het deconstrueren van een tuple is vergelijkbaar met de syntaxis voor het definiëren van een tuple: u plaatst de variabelen waaraan elk element moet worden toegewezen tussen haakjes aan de linkerkant van een toewijzingsinstructie. Met de volgende instructie worden bijvoorbeeld de elementen van een vier tuple toegewezen aan vier afzonderlijke variabelen:

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

Er zijn drie manieren om een tuple te deconstrueren:

  • U kunt expliciet het type van elk veld tussen haakjes declareren. In het volgende voorbeeld wordt deze methode gebruikt om de drie tuples te deconstrueren die door de QueryCityData methode worden geretourneerd.

    public static void Main()
    {
        (string city, int population, double area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    
  • U kunt het var trefwoord gebruiken zodat C# het type van elke variabele afstelt. U plaatst het var trefwoord buiten de haakjes. In het volgende voorbeeld wordt typedeductie gebruikt bij het deconstrueren van de drie tuples die door de QueryCityData methode worden geretourneerd.

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

    U kunt het var trefwoord ook afzonderlijk gebruiken met een of alle declaraties van variabelen tussen haakjes.

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

    Dit is omslachtig en wordt niet aanbevolen.

  • Ten slotte kunt u de tuple deconstrueren in variabelen die al zijn gedeclareerd.

    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.
    }
    
  • Vanaf C# 10 kunt u variabeledeclaratie en toewijzing combineren in een deconstructie.

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

U kunt geen specifiek type opgeven buiten de haakjes, zelfs niet als elk veld in de tuple hetzelfde type heeft. Hierdoor wordt compilerfout CS8136 gegenereerd, 'Deconstruction 'var (...)' form wordt een specifiek type voor 'var' geweigerd.'

U moet elk element van de tuple toewijzen aan een variabele. Als u elementen weglaat, genereert de compiler fout CS8132, 'Kan een tuple van 'x'-elementen niet deconstrueren in y-variabelen.'

Tuple-elementen met verwijderingen

Wanneer u een tuple deconstrueert, bent u vaak geïnteresseerd in de waarden van slechts enkele elementen. U kunt profiteren van C#-ondersteuning voor verwijderingen. Dit zijn alleen-schrijvenvariabelen waarvan u de waarden wilt negeren. Een verwijdering wordt gekozen door een onderstrepingsteken ("_") in een opdracht. U kunt zoveel waarden negeren als u wilt; alle worden vertegenwoordigd door de enkele verwijdering, _.

In het volgende voorbeeld ziet u het gebruik van tuples met verwijderingen. De QueryCityDataForYears methode retourneert een zes tuple met de naam van een stad, het gebied, een jaar, de inwoners van de stad voor dat jaar, een tweede jaar en de bevolking van de stad voor dat tweede jaar. In het voorbeeld ziet u de verandering in de populatie tussen die twee jaar. Van de gegevens die beschikbaar zijn in de tuple, hebben we geen betrekking op het gebied van de stad, en we kennen de naam van de stad en de twee datums in ontwerptijd. Als gevolg hiervan zijn we alleen geïnteresseerd in de twee populatiewaarden die zijn opgeslagen in de tuple en kunnen we de resterende waarden verwerken als verwijderingen.

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

Door de gebruiker gedefinieerde typen

C# biedt geen ingebouwde ondersteuning voor het deconstrueren van andere typen dan de record typen DictionaryEntry . Als auteur van een klasse, een struct of een interface kunt u echter toestaan dat exemplaren van het type worden gedeconstrueerd door een of meer Deconstruct methoden te implementeren. De methode retourneert ongeldigheid en elke waarde die moet worden gedeconstrueerd, wordt aangegeven door een outparameter in de methodehandtekening. De volgende Deconstruct methode van een Person klasse retourneert bijvoorbeeld de eerste, middelste en achternaam:

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

Vervolgens kunt u een exemplaar van de klasse met de Person naam p deconstrueren met een opdracht zoals de volgende code:

var (fName, mName, lName) = p;

In het volgende voorbeeld wordt de Deconstruct methode overbelast om verschillende combinaties van eigenschappen van een Person object te retourneren. Individuele overbelastingen retourneren:

  • Een voor- en achternaam.
  • Een voornaam, middelste en achternaam.
  • Een voornaam, een achternaam, een plaatsnaam en een staatsnaam.
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!

Meerdere Deconstruct methoden met hetzelfde aantal parameters zijn dubbelzinnig. U moet voorzichtig zijn met het definiëren Deconstruct van methoden met verschillende aantallen parameters of 'arity'. Deconstruct methoden met hetzelfde aantal parameters kunnen niet worden onderscheiden tijdens overbelastingsresolutie.

Door de gebruiker gedefinieerd type met verwijderingen

Net als bij tuples kunt u verwijderingen gebruiken om geselecteerde items te negeren die door een Deconstruct methode worden geretourneerd. Elke verwijdering wordt gedefinieerd door een variabele met de naam _en één deconstructiebewerking kan meerdere verwijderingen bevatten.

In het volgende voorbeeld wordt een Person object gedeconstrueerd in vier tekenreeksen (de voor- en achternamen, de plaats en de staat), maar wordt de achternaam en de staat verwijderd.

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

Extensiemethoden voor door de gebruiker gedefinieerde typen

Als u geen klasse, struct of interface hebt gemaakt, kunt u objecten van dat type nog steeds deconstrueren door een of meer Deconstruct extensiemethoden te implementeren om de waarden te retourneren waarin u geïnteresseerd bent.

In het volgende voorbeeld worden twee Deconstruct extensiemethoden voor de System.Reflection.PropertyInfo klasse gedefinieerd. De eerste retourneert een set waarden die de kenmerken van de eigenschap aangeven, inclusief het type, of het statisch of het exemplaar is, of deze alleen-lezen is en of deze is geïndexeerd. De tweede geeft de toegankelijkheid van de eigenschap aan. Omdat de toegankelijkheid van get- en set-accessors kan verschillen, geven Booleaanse waarden aan of de eigenschap afzonderlijke get- en set-accessors heeft en, als dat het geval is, of ze dezelfde toegankelijkheid hebben. Als er slechts één accessor is of zowel de get als de set accessor dezelfde toegankelijkheid heeft, geeft de access variabele de toegankelijkheid van de eigenschap als geheel aan. Anders worden de toegankelijkheid van de get- en set-accessors aangegeven door de getAccess en setAccess variabelen.

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

Extensiemethode voor systeemtypen

Sommige systeemtypen bieden de Deconstruct methode als gemak. Het type biedt bijvoorbeeld System.Collections.Generic.KeyValuePair<TKey,TValue> deze functionaliteit. Wanneer u een itereert over elk element is een System.Collections.Generic.Dictionary<TKey,TValue> KeyValuePair<TKey, TValue> en kan worden gedeconstrueerd. Kijk een naar het volgende voorbeeld:

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.");
}

U kunt een Deconstruct methode toevoegen aan systeemtypen die er geen hebben. Houd rekening met de volgende extensiemethode:

public static class NullableExtensions
{
    public static void Deconstruct<T>(
        this T? nullable,
        out bool hasValue,
        out T value) where T : struct
    {
        hasValue = nullable.HasValue;
        value = nullable.GetValueOrDefault();
    }
}

Met deze extensiemethode kunnen alle Nullable<T> typen worden gedeconstrueerd in een tuple van (bool hasValue, T value). In het volgende voorbeeld ziet u code die gebruikmaakt van deze extensiemethode:

DateTime? questionableDateTime = default;
var (hasValue, value) = questionableDateTime;
Console.WriteLine(
    $"{{ HasValue = {hasValue}, Value = {value} }}");

questionableDateTime = DateTime.Now;
(hasValue, value) = questionableDateTime;
Console.WriteLine(
    $"{{ HasValue = {hasValue}, Value = {value} }}");

// Example outputs:
// { HasValue = False, Value = 1/1/0001 12:00:00 AM }
// { HasValue = True, Value = 11/10/2021 6:11:45 PM }

record Typen

Wanneer u een recordtype declareert met behulp van twee of meer positionele parameters, maakt de compiler een methode met een Deconstruct out parameter voor elke positionele parameter in de record declaratie. Zie Positionele syntaxis voor eigenschapsdefinitie en deconstructorgedrag in afgeleide records voor meer informatie.

Zie ook