Dekonstrukce řazených kolekcí členů a dalších typů

Řazená kolekce členů poskytuje jednoduchý způsob, jak načíst více hodnot z volání metody. Jakmile ale načítáte řazenou kolekce členů, musíte zpracovat její jednotlivé prvky. Práce na základu elementů je těžkopádná, jak ukazuje následující příklad. Metoda vrátí řazenou kolekce členů se třemi kolekcemi členů a každý z jejích prvků je přiřazen QueryCityData 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é: hodnotu pole nebo vlastnosti musíte přiřadit proměnné na základě člena po členovi.

V jazyce C# 7.0 a novějších 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é operaci dekonstrukce. Chcete-li dekonstruovat řazenou kolekce členů, přiřadíte její prvky k jednotlivým proměnným. Při dekonstrukci objektu přiřadíte vybrané hodnoty jednotlivým proměnným.

N-tice

Jazyk C# obsahuje integrovanou podporu pro dekonstrukci řazených kolekce členů, která umožňuje rozbalit všechny položky v řazené kolekce členů v jedné operaci. Obecná syntaxe pro dekonstrukci řazené kolekce členů je podobná syntaxi pro definování řazené kolekce členů: proměnné, ke kterým se mají jednotlivé prvky přiřadit, uzavřete do závorek na levé straně příkazu přiřazení. Například následující příkaz přiřadí prvky řazené kolekce čtyř členů čtyřem proměnným:

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

Existují tři způsoby dekonstrukce řazené kolekce členů:

  • Můžete explicitně deklarovat typ každého pole v závorkách. Následující příklad používá tento přístup k dekonstrukci řazené kolekce tří členů vrácené QueryCityData metodou .

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

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

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

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

    To je těžkopádné a nedoporučuje se to.

  • Nakonec můžete řazenou kolekce členů dekonstruovat na proměnné, které už byly deklarovány.

    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.
    }
    
  • Počínaje jazykem C# 10 můžete v dekonstrukci kombinovat deklaraci a přiřazení proměnných.

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

Konkrétní typ nelze zadat mimo závorky ani v případě, že každé pole v řazené řazené kolekce členů má stejný typ. Tím se vygeneruje chyba kompilátoru CS8136, "Dekonstrukce "var (...)" form zakáže konkrétní typ pro 'var'.".

Každý prvek řazené kolekce členů je nutné přiřadit proměnné. Pokud některé prvky vyjádřete, kompilátor vygeneruje chybu CS8132: Nelze dekonstruovat řazenou kolekce členů prvků x na proměnné y.

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

Při dekonstrukci řazené kolekce členů se často zajímáte o hodnoty pouze některých prvků. Počínaje jazykem C# 7.0 můžete využít podporu jazyka C# pro zahození, což jsou proměnné jen pro zápis, jejichž hodnoty jste se rozhodli ignorovat. Zahození je v přiřazení zvoleno podtržítkem _ (""). Můžete zahodit tolik hodnot, kolik chcete. Všechny jsou reprezentovány jediným zahozením , _ .

Následující příklad znázorňuje použití řazených kolekce členů s třídami discard. Metoda vrátí řazenou kolekce členů se šesti řazenou kolekcemi členů s názvem města, jeho oblastí, rokem, počtem obyvatel města pro tento rok, druhým rokem a počtem obyvatel pro QueryCityDataForYears tento druhý rok. Příklad ukazuje změnu populace mezi těmito dvěma roky. Z dat dostupných z řazené kolekce členů jsme se s oblastí města nezabýli a v době návrhu známe název města a dvě kalendářní data. V důsledku toho nás zajímají pouze dvě hodnoty základního souboru uložené v řazené kolekce členů a její zbývající hodnoty můžeme zpracovat 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# nenabízí integrovanou podporu pro dekonstrukci jiných typů než řazené kolekce členů, než jsou typy record a DictionaryEntry. Jako autor třídy, struktury nebo rozhraní však můžete povolit, aby instance typu byla vytvořena implementací jedné nebo více Deconstruct metod. Metoda vrátí void a každá hodnota, která se má dekonstruovat, je označena out parametrem v podpisu metody. Například následující metoda Deconstruct třídy Person vrátí první, prostřední a příjmení:

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

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

var (fName, mName, lName) = p;

Následující příklad přetíží Deconstruct metodu , aby vrátila různé kombinace vlastností Person objektu. Jednotlivá přetížení vrací:

  • Jméno a příjmení.
  • Jméno, prostřední a příjmení.
  • Jméno, příjmení, 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 se stejným počtem parametrů je nejednoznačných. Musíte být opatrní při definování Deconstruct metod s různým počtem parametrů neboli "arity". Deconstruct Metody se stejným počtem parametrů nelze rozlišit během řešení přetížení.

Uživatelem definovaný typ s zahozeními

Stejně jako u řazených kolekcečlenů můžete použít funkce discard, která ignoruje vybrané položky vrácené Deconstruct metodou . Každý zahození je definován proměnnou s názvem " " a jedna _ operace dekonstrukce může obsahovat více zahození.

Následující příklad dekonstruuje objekt do čtyř řetězců (jméno a příjmení, město a stát), ale zahodí příjmení a Person 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í pro uživatelsky definované typy

Pokud jste nevytvtvějí třídu, strukturování nebo rozhraní, můžete dekonstruovat objekty tohoto typu implementací jedné nebo více rozšiřujících metod, které vrátí hodnoty, které Deconstruct vás zajímají.

Následující příklad definuje dvě Deconstruct rozšiřující metody pro třídu System.Reflection.PropertyInfo . První vrátí sadu hodnot, které označují charakteristiky vlastnosti, včetně jejího typu, toho, zda je statická nebo instance, zda je jen pro čtení a zda je indexována. Druhá označuje přístupnost vlastnosti. Vzhledem k tomu, že přístupnost přístupových objektů get a set se může lišit, logické hodnoty označují, jestli má vlastnost samostatné přístupové objekty get a set, a pokud ano, jestli má stejnou přístupnost. Pokud existuje pouze jeden přístupový objekt nebo přístupový objekt get i set mají stejnou přístupnost, proměnná označuje přístupnost vlastnosti access jako celku. Jinak přístupnost přístupových přístupových objektů get a set je označena getAccess proměnnými setAccess a .

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ému poskytují Deconstruct metodu jako pohodlí. Typ například System.Collections.Generic.KeyValuePair<TKey,TValue> poskytuje tuto funkci. Při iteraci přes každý System.Collections.Generic.Dictionary<TKey,TValue> prvek je a lze jej KeyValuePair<TKey, TValue> dekonstruovat. Uvažujte 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.");
}

Metodu můžete Deconstruct přidat do typů systému, které žádné nemají. Zvažte následující rozšiřující metodu:

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

Tato metoda rozšíření Nullable<T> umožňuje, aby se všechny typy dekonstruoval na řazenou kolekce členů (bool hasValue, T value) . Následující příklad ukazuje kód, který používá tuto rozšiřující metodu:

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 Typy

Když deklarujete typ záznamu pomocí dvou nebo více pozičních parametrů, kompilátor vytvoří metodu s parametrem pro každý poziční parametr v Deconstruct out record deklaraci. Další informace najdete v tématu Poziční syntaxe pro definici vlastnosti a Chování dekonstruktoru v odvozených záznamech.

Viz také