Udostępnij za pośrednictwem


Dekonstrukcja krotek i innych typów

Krotka zapewnia uproszczony sposób pobierania wielu wartości z wywołania metody. Ale po pobraniu krotki musisz obsłużyć jego poszczególne elementy. Praca na podstawie elementu po elemerytmie jest kłopotliwa, jak pokazano w poniższym przykładzie. Metoda QueryCityData zwraca trójkropek, a każdy z jego elementów jest przypisywany do zmiennej w oddzielnej operacji.

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

Pobieranie wielu wartości pól i właściwości z obiektu może być równie kłopotliwe: należy przypisać pole lub wartość właściwości do zmiennej na podstawie składowej.

Można pobrać wiele elementów z krotki lub pobrać wiele pól, właściwości i obliczonych wartości z obiektu w jednej operacji dekonstrukcji . Aby zdekonstruować krotkę, należy przypisać jej elementy do poszczególnych zmiennych. Podczas dekonstrukcji obiektu należy przypisać wybrane wartości do poszczególnych zmiennych.

Krotki

Funkcje języka C# obsługują wbudowaną obsługę dekonstrukcji krotek, co pozwala rozpakować wszystkie elementy w krotki w ramach jednej operacji. Ogólna składnia dekonstrukcji krotki jest podobna do składni definiującej jedną: należy ująć zmienne, do których każdy element ma zostać przypisany w nawiasach po lewej stronie instrukcji przypisania. Na przykład następująca instrukcja przypisuje elementy czwórki do czterech oddzielnych zmiennych:

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

Istnieją trzy sposoby dekonstrukcji krotki:

  • Można jawnie zadeklarować typ każdego pola wewnątrz nawiasów. W poniższym przykładzie użyto tego podejścia, aby zdekonstruować trzy krotki zwrócone przez metodę QueryCityData .

    public static void Main()
    {
        (string city, int population, double area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    
  • Możesz użyć słowa kluczowego var , aby język C# wnioskował o typie każdej zmiennej. Słowo kluczowe należy var umieścić poza nawiasami. W poniższym przykładzie użyto wnioskowania typu podczas dekonstrukcji trójkropka zwróconej przez metodę QueryCityData .

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

    Możesz również użyć słowa kluczowego var indywidualnie z dowolnymi lub wszystkimi deklaracjami zmiennych wewnątrz nawiasów.

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

    Powyższy przykład jest kłopotliwy i nie jest zalecany.

  • Na koniec można rozłożyć krotkę na już zadeklarowane zmienne.

    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.
    }
    
  • Można łączyć deklarowanie zmiennej i przypisanie w dekonstrukcji.

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

Nie można określić określonego typu poza nawiasami, nawet jeśli każde pole w krotki ma ten sam typ. Powoduje to wygenerowanie błędu kompilatora CS8136: „Dekonstrukcja w formie 'var (...)' nie zezwala na użycie określonego typu dla 'var'.”

Należy przypisać każdy element krotki do zmiennej. Jeśli pominięto jakiekolwiek elementy, kompilator generuje błąd CS8132" "Nie można zdekonstruować krotki elementów "x" w zmiennych "y".

Elementy krotki z odrzuconymi elementami

Często podczas dekonstrukcji krotki interesuje Cię tylko niektóre elementy. Możesz skorzystać z obsługi języka C# dla discardów, które są zmiennymi tylko do zapisu, których wartości wybierasz, aby zignorować. Zadeklaruj pominięcie używając znaku podkreślenia ("_") w przypisaniu. Możesz odrzucić dowolną liczbę wartości; pojedyncze odrzucenie, _, reprezentuje wszystkie odrzucone wartości.

Poniższy przykład ilustruje użycie krotki z odrzuceniami. Metoda QueryCityDataForYears zwraca sześciokątną z nazwą miasta, jego obszarem, rokiem, populacją miasta w tym roku, drugim rokiem i populacją miasta w tym drugim roku. W przykładzie pokazano zmianę populacji między tymi dwoma latami. Z danych dostępnych z krotki, jesteśmy niezkonsekwowani obszarem miasta i znamy nazwę miasta i dwie daty w czasie projektowania. W związku z tym interesuje nas tylko dwie wartości populacji przechowywane w krotki i mogą obsługiwać pozostałe wartości jako odrzucenia.

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

Typy definiowane przez użytkownika

Język C# oferuje wbudowaną obsługę dekonstrukcji typów krotek, recordoraz typów DictionaryEntry i. Jednak jako autor klasy, struktury lub interfejsu można zezwolić na dekonstrukcję wystąpień typu przez zaimplementowanie co najmniej jednej Deconstruct metody. Metoda zwraca wartość void. Parametr wyjściowy w podpisie metody reprezentuje każdą wartość do dekonstrukcji. Na przykład następująca metoda Deconstruct klasy Person zwraca pierwszą, środkową i rodzinną nazwę:

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

Następnie można zdekonstruować wystąpienie Person klasy o nazwie p przy użyciu przypisania, takiego jak następujący kod:

var (fName, mName, lName) = p;

Poniższy przykład przeciąża metodę Deconstruct , aby zwrócić różne kombinacje właściwości Person obiektu. Zwracane są poszczególne przeciążenia:

  • Imię i nazwisko.
  • Imię, środkowe i nazwisko rodzinne.
  • Imię, nazwa rodziny, nazwa miasta i nazwa stanu.
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!

Wiele Deconstruct metod o tej samej liczbie parametrów jest niejednoznacznych. Należy zachować ostrożność podczas definiowania Deconstruct metod o różnych liczbach parametrów lub "arity". Deconstruct metod o tej samej liczbie parametrów nie można odróżnić podczas rozpoznawania przeciążenia.

Typ zdefiniowany przez użytkownika z odrzuceniami

Podobnie jak w przypadku krotek, można użyć odrzucań, aby zignorować wybrane elementy zwrócone przez metodę Deconstruct . Zmienna o nazwie "_" reprezentuje odrzucenie. Pojedyncza operacja dekonstrukcji może obejmować wiele odrzuceń.

Poniższy przykład dekonstrukuje obiekt Person na cztery ciągi (imię i nazwisko rodziny, miasto i stan), ale odrzuca nazwę rodziny i stan.

// 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 rozszerzenia dekonstrukcji

Jeśli nie utworzono klasy, struktury lub interfejsu, nadal można dekonstrukcji obiektów tego typu przez zaimplementowanie co najmniej jednej Deconstructmetody rozszerzenia, aby zwrócić interesujące Cię wartości.

W poniższym przykładzie zdefiniowano dwie Deconstruct metody rozszerzenia dla System.Reflection.PropertyInfo klasy . Pierwszy zwraca zestaw wartości, które wskazują cechy właściwości. Drugi wskazuje dostępność właściwości. Wartości logiczne wskazują, czy właściwość ma oddzielne metody pobierania i ustawiania lub różny poziom dostępu. Jeśli istnieje tylko jedno akcesorium lub zarówno metodę get, jak i zestaw dostępu mają taką samą dostępność, access zmienna wskazuje dostępność właściwości jako całości. W przeciwnym razie dostępność metod dostępu get i set są wskazywane przez getAccess zmienne i setAccess .

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 rozszerzenia dla typów systemowych

Niektóre typy systemów zapewniają metodę Deconstruct jako wygodę. Na przykład System.Collections.Generic.KeyValuePair<TKey,TValue> typ zapewnia tę funkcję. Podczas iteracji System.Collections.Generic.Dictionary<TKey,TValue>każdy element jest KeyValuePair<TKey, TValue> i może zostać zdekonstrukowany. Rozważmy następujący przykład:

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

W przypadku deklarowania typu rekordu przy użyciu co najmniej dwóch parametrów pozycyjnych kompilator tworzy metodę Deconstruct z parametrem out dla każdego parametru pozycyjnego record w deklaracji. Aby uzyskać więcej informacji, zobacz Składnia pozycyjna definicji właściwości i Zachowanie dekonstruktora w rekordach pochodnych.

Zobacz też