Sdílet prostřednictvím


Dekonstrukce řazených kolekcí členů 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í třířazenou kolekci členů a každý z jejích prvků je přiřazen k 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čtení více hodnot polí a vlastností z objektu může být stejně těžkopádné: je nutné přiřadit pole nebo hodnotu vlastnosti proměnné na základě člen-by-member.

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ů

Funkce jazyka C# integrované pro dekonstrukci řazených kolekcí členů, které umožňují rozbalit všechny položky v řazené kolekci členů v jedné operaci. Obecná syntaxe pro dekonstrukci řazené kolekce členů je podobná syntaxi pro definování jedné: uzavřete proměnné, ke kterým má být každý prvek přiřazen v závorkách na levé straně příkazu assignment. Například následující příkaz přiřadí prvky čtyřřazené kolekce členů do čtyř samostatných proměnných:

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

Existují tři způsoby, jak dekonstruovat řazenou kolekci členů:

  • 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řířazené kolekce členů 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á odvození typu při dekonstrukci třítří řazené kolekce členů 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é pole v řazené kolekci členů stejný typ. Tím se vygeneruje chyba kompilátoru CS8136: "Formulář deconstruction 'var (...)' nedovoluje specifický typ pro 'var'."

Každému prvku řazené kolekce členů 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ů. Podporu jazyka C# můžete využít pro zahození, což jsou proměnné jen pro zápis, jejichž hodnoty jste se rozhodli 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í řazených kolekcí členů s zahozeními. Metoda QueryCityDataForYears vrátí šestiřazenou kolekci členů s názvem města, jeho oblastí, rokem, počtem obyvatel města pro tento rok, druhým rokem a obyvateli města pro tento druhý rok. Příklad ukazuje změnu populace mezi těmito dvěma roky. Z dat dostupných z řazené kolekce členů jsme nespokojení s oblastí města a známe název města a dvě data v době návrhu. V důsledku toho nás zajímají pouze dvě hodnoty základního souboru uložené v řazené kolekci členů 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)

Potom můžete dekonstruovat instanci Person třídy s názvem p přiřazení, jako je následující kód:

var (fName, mName, lName) = p;

Následující příklad přetíží metodu Deconstruct k vrácení různých kombinací vlastností objektu Person . Vrácení 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í Deconstruct metod s různými čísly parametrů nebo "arity" 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 řazených kolekcí členů můžete pomocí zahození 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 get i objekt set, má proměnná stejnou přístupnost, access označuje přístupnost vlastnosti jako celek. V opačném případě jsou přístupnost přístupových objektů get a set označená getAccesssetAccess proměnnými.

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

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 Positional syntaxe pro definici vlastnosti a chování dekonstruktor v odvozených záznamech.

Viz také