Dekonstrukce n-tic a dalších typů

Řazená kolekce členů poskytuje jednoduchý způsob načtení více hodnot z volání metody. Jakmile ale řazenou kolekci členů načtete, musíte zpracovat její jednotlivé prvky. Práce na bázi element-by-element je těžkopádná, jak ukazuje následující příklad. Metoda QueryCityData vrátí trojici a každý z jejích prvků je přiřazen do proměnné v samostatné operaci.

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

Načítání více hodnot polí a vlastností z objektu může být stejně těžkopádné: je nutné přiřadit hodnotu pole nebo vlastnosti proměnné na základě měření za člen.

Můžete načíst více prvků z řazené kolekce členů nebo načíst více polí, vlastností a vypočítaných hodnot z objektu v jedné dekonstrukční operaci. Pokud chcete dekonstruovat řazenou kolekci členů, přiřaďte její prvky jednotlivým proměnným. Při dekonstrukci objektu přiřadíte vybrané hodnoty jednotlivým proměnným.

Řazené kolekce členů

C# má vestavěnou podporu pro dekonstrukci n-tic, což vám umožňuje rozbalit všechny položky v n-tici jednou operací. Obecná syntaxe pro dekonstrukci n-tice je podobná syntaxi pro její definování: proměnné, ke kterým má být každý prvek přiřazen, uzavřete v závorkách na levé straně přiřazovacího příkazu. Například následující příkaz přiřadí prvky čtyřprvkové n-tice do čtyř samostatných proměnných.

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

Existují tři způsoby, jak dekonstruovat n-tici:

  • Můžete explicitně deklarovat typ každého pole uvnitř závorek. Následující příklad používá tento přístup k dekonstrukci tříprvkové n-tice vrácené metodou QueryCityData.

    public static void Main()
    {
        (string city, int population, double area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    
  • Klíčové slovo můžete použít var , aby jazyk C# odvodil typ každé proměnné. Klíčové slovo umístíte var mimo závorky. Následující příklad používá inferenci typu při dekonstrukci trojice vrácené metodou QueryCityData.

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

    Klíčové slovo můžete použít var také jednotlivě s libovolnou nebo všemi deklaracemi proměnných uvnitř závorek.

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

    Předchozí příklad je těžkopádný a nedoporučuje se.

  • Nakonec můžete dekonstruovat n-tici do již deklarovaných proměnných.

    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.
    }
    
  • Můžete kombinovat deklaraci a přiřazení proměnné v dekonstruování.

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

Nemůžete zadat konkrétní typ mimo závorky, i když má každý prvek v n-tici stejný typ. Tím se vygeneruje chyba kompilátoru CS8136: "Formulář deconstruction 'var (...)' nedovoluje specifický typ pro 'var'."

Každému prvku n-tice musíte přiřadit proměnnou. Pokud vynecháte některé prvky, kompilátor vygeneruje chybu CS8132, "Nelze dekonstruovat řazenou kolekci elementů x do proměnných y".

Prvky řazené kolekce členů s zahozením

Při dekonstrukci řazené kolekce členů vás často zajímají pouze hodnoty některých prvků. Můžete využít podporu jazyka C# pro zahozování, což jsou proměnné pouze pro zápis, jejichž hodnoty se rozhodnete ignorovat. Použijete znak podtržítka („_“) jako odloženou hodnotu v přiřazovací operaci. Můžete zahodit tolik hodnot, kolik chcete; jediný zahozený prvek, _, představuje všechny zahozené hodnoty.

Následující příklad ukazuje použití n-tic se diskardy. Metoda QueryCityDataForYears vrátí šestici se jménem města, jeho rozlohou, rokem, počtem obyvatel města pro tento rok, druhým rokem a počtem obyvatel města pro tento druhý rok. Příklad ukazuje změnu populace mezi těmito dvěma roky. Z dat dostupných z n-tice nás nezajímá oblast města, a známe název města a dvě data při návrhu. V důsledku toho nás zajímají pouze dvě hodnoty populace uložené v n-tici a můžeme zpracovat zbývající hodnoty jako zahozené.

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

Uživateli definované typy

Jazyk C# nabízí integrovanou podporu pro dekonstrukci typů n-tic, recorda DictionaryEntry. Jako autor třídy, struktury nebo rozhraní však můžete povolit, aby instance typu byly dekonstruovány implementací jedné nebo více Deconstruct metod. Metoda vrátí hodnotu void. Parametr v podpisu metody představuje každou hodnotu, která se má dekonstruovat. Například následující metoda Deconstruct třídy Person vrátí první, prostřední a rodinný název:

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

Poté můžete pomocí přiřazení, jako je následující kód, dekonstruovat instanci třídy Person pojmenovanou p.

var (fName, mName, lName) = p;

Následující příklad přetíží metodu Deconstruct k vrácení různých kombinací vlastností objektu Person . Návrat jednotlivých přetížení:

  • Jméno a příjmení.
  • Křestní, prostřední a příjmení.
  • Jméno, jméno rodiny, název města a název státu.
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!

Více Deconstruct metod, které mají stejný počet parametrů, jsou nejednoznačné. Při definování metod Deconstruct s různým počtem parametrů neboli "aritou" musíte být opatrní. Deconstruct metody se stejným počtem parametrů se během rozlišení přetížení nerozlišují.

Uživatelem definovaný typ s zahozením

Stejně jako u n-tic můžete pomocí ignorování ignorovat vybrané položky vrácené metodou Deconstruct. Proměnná s názvem _představuje zahození. Jedna operace dekonstrukce může zahrnovat více zahození.

Následující příklad dekonstruuje objekt Person do čtyř řetězců (jméno a rodina, město a stát), ale zahodí jméno rodiny a stát.

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

Metody rozšíření dekonstrukce

Pokud jste nevytvořili třídu, strukturu nebo rozhraní, můžete dekonstruovat objekty tohoto typu implementací jedné nebo více Deconstructrozšiřujících metod pro vrácení hodnot, které vás zajímají.

Následující příklad definuje dvě Deconstruct rozšiřující metody pro System.Reflection.PropertyInfo třídu. První vrátí sadu hodnot, které vykazují charakteristiky nemovitosti. Druhá označuje přístupnost vlastnosti. Logické hodnoty označují, jestli má vlastnost samostatné přístupory ke čtení a zápisu, nebo různou úroveň přístupnosti. Pokud je k dispozici pouze jeden přístupový objekt, nebo mají jak get, tak set přístupový objekt stejnou úroveň přístupnosti, proměnná access označuje přístupnost vlastnosti jako celku. V opačném případě je přístupnost přístupových metod get a set označena proměnnými getAccess a setAccess.

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

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

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

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

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

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

        public void Deconstruct(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 (propertyInfo.CanRead)
                getter = propertyInfo.GetMethod;

            MethodInfo setter = null;
            if (propertyInfo.CanWrite)
                setter = propertyInfo.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

Metoda rozšíření pro systémové typy

Některé typy systémů poskytují metodu Deconstruct jako pohodlí. Tento typ například System.Collections.Generic.KeyValuePair<TKey,TValue> poskytuje tuto funkci. Při iteraci přes System.Collections.Generic.Dictionary<TKey,TValue>je každý prvek KeyValuePair<TKey, TValue> a lze ho rozložit. Představte si následující příklad:

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 typy

Když deklarujete typ záznamu pomocí dvou nebo více pozičních parametrů, kompilátor vytvoří metodu Deconstructout s parametrem pro každý poziční parametr v record deklaraci. Další informace naleznete v tématu Pozicní syntax pro definici vlastnosti a chování dekonstruktoru v odvozených záznamů.

Viz také