A csonkok és más típusok lebontása

A tuple egyszerű módot kínál több érték lekérésére egy metódushívásból. De miután lekérte a rekordot, kezelnie kell az egyes elemeket. Az elemenkénti munka nehézkes, ahogy az alábbi példa is mutatja. A QueryCityData metódus három rekordot ad vissza, és minden eleme egy változóhoz van rendelve egy külön műveletben.

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

Egy objektumból egyszerre több mező- és tulajdonságérték beolvasása is nehézkes lehet: tagonként kell hozzárendelni egy mezőt vagy tulajdonságértéket egy változóhoz.

Több elemet is lekérhet egy rekordból, vagy több mezőt, tulajdonságot és számított értéket is lekérhet egy objektumból egyetlen dekonstruálási műveletben. Egy rekord dekonstruálásához az elemeket az egyes változókhoz rendelheti. Objektum dekonstruálásakor a kijelölt értékeket az egyes változókhoz rendeli.

Rekordok

A C# beépített támogatással rendelkezik a rekordok dekonstruálásához, amely lehetővé teszi, hogy egyetlen műveletben csomagolja ki az összes elemet. A tuple dekonstruálásának általános szintaxisa hasonló a definíció szintaxisához: a hozzárendelési utasítás bal oldalán zárójelbe kell tenni azokat a változókat, amelyekhez az egyes elemeket hozzá kell rendelni. A következő utasítás például négy különböző változóhoz rendeli a négy rekord elemeit:

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

A tuple-ok bontásának három módja van:

  • Az egyes mezők típusát zárójelek között explicit módon deklarálhatja. Az alábbi példa ezt a megközelítést használja a metódus által visszaadott három rekord dekonstruálásához QueryCityData .

    public static void Main()
    {
        (string city, int population, double area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    
  • Használhatja a kulcsszót, hogy a var C# az egyes változók típusát következtethesse. A kulcsszót a var zárójeleken kívülre kell helyeznie. Az alábbi példa típuskövetkezést használ a metódus által QueryCityData visszaadott három rekord dekonstruálásakor.

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

    A kulcsszót var külön-külön is használhatja a zárójelekben található változódeklarációk bármelyikével vagy mindegyikével.

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

    Ez nehézkes, és nem ajánlott.

  • Végül deklarálhatja a rekordot olyan változókra, amelyek már deklaráltak.

    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.
    }
    
  • A C# 10-től kezdődően a változó deklarációja és hozzárendelése kombinálható a dekonstruálásban.

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

A zárójeleken kívül nem adhat meg egy adott típust, még akkor sem, ha a rekord minden mezőjének ugyanaz a típusa. Ezzel létrehoz egy CS8136-os fordítói hibát, a "Deconstruction 'var (...)' űrlap nem engedélyezi a "var" adott típusát."

A rekord minden elemét hozzá kell rendelnie egy változóhoz. Ha kihagy bármilyen elemet, a fordító a CS8132 hibát generálja: "Nem lehet "x" elemeket "y" változókká bontani."

Elvetéssel rendelkező elemek lefedése

A tuple-ok lebontásakor gyakran csak bizonyos elemek értékei érdeklik. Kihasználhatja a C#által támogatott elvetéseket, amelyek írásvédett változók, amelyek értékeit figyelmen kívül hagyta. Az elvetéseket egy aláhúzásjel ("_") választja ki egy hozzárendelésben. Annyi értéket elvethet, amennyit csak szeretne; az összeset az egyetlen elvetés jelöli. _

Az alábbi példa a kukák elvetéssel való használatát mutatja be. A QueryCityDataForYears módszer egy hat-rekordot ad vissza egy város, annak területe, egy év, az adott év lakossága, egy második év és a város népessége a második évben. A példa a két év közötti népességváltozást mutatja be. A rekordból elérhető adatok közül a városterülettel nem vagyunk tisztában, és tudjuk a város nevét és a két dátumot a tervezéskor. Ennek eredményeképpen csak a rekordban tárolt két sokaságérték érdekel minket, és a fennmaradó értékeket elvetésként tudja kezelni.

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

Felhasználó által definiált típusok

A C# nem nyújt beépített támogatást a nem rekordtípusok és a recordDictionaryEntry típusok kivételével. Az osztály, a szerkezet vagy a felület szerzőjeként azonban egy vagy több Deconstruct metódus implementálásával engedélyezheti az ilyen típusú példányok dekonstruálását. A metódus érvénytelen értéket ad vissza, és a feloldandó értékeket a metódus aláírásának egy kimenő paramétere jelzi. Egy osztály következő Deconstruct metódusa Person például az első, a középső és a vezetéknevet adja vissza:

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

Ezután a következő kódhoz hasonló hozzárendeléssel elnevezett p osztálypéldányt Person dekonstruálhat:

var (fName, mName, lName) = p;

Az alábbi példa túlterheli a metódust egy Deconstruct objektum különböző tulajdonságainak visszaadásához Person . Az egyéni túlterhelések visszatérnek:

  • Vezeték- és utónév.
  • Egy utónév, egy középső és egy vezetéknév.
  • Utónév, vezetéknév, városnév és államnév.
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!

Több Deconstruct azonos számú paraméterrel rendelkező metódus nem egyértelmű. Ügyeljen arra, hogy különböző számú paramétert vagy "aritást" tartalmazó metódusokat definiáljon Deconstruct . Deconstruct az azonos számú paraméterrel rendelkező metódusok nem különböztethetők meg a túlterhelés feloldása során.

Felhasználó által definiált típus elvetéssel

Ugyanúgy, mint a csuplok esetében, elvetéssel figyelmen kívül hagyhatja a Deconstruct metódus által visszaadott kijelölt elemeket. Minden elvetést egy "_" nevű változó határoz meg, és egyetlen dekonstruálási művelet több elvetést is tartalmazhat.

Az alábbi példa egy objektumot négy sztringre Person (az utó- és vezetéknevekre, a városra és az államra) bont, de elveti a vezetéknevet és az államot.

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

Bővítménymetelyek a felhasználó által definiált típusokhoz

Ha nem készített osztályt, szerkezetet vagy felületet, akkor is dekonstruálhatja az ilyen típusú objektumokat egy vagy több Deconstructbővítménymetódus implementálásával, hogy visszaadja azokat az értékeket, amelyekben érdekli.

Az alábbi példa két Deconstruct bővítménymetelyt határoz meg az System.Reflection.PropertyInfo osztályhoz. Az első olyan értékkészletet ad vissza, amely jelzi a tulajdonság jellemzőit, beleértve annak típusát, statikus vagy példány, írásvédett és indexelt tulajdonságot. A második a tulajdonság akadálymentességét jelzi. Mivel a beolvasási és beállítási tartozékok akadálymentessége eltérő lehet, a logikai értékek azt jelzik, hogy a tulajdonság külön beolvasási és beállítási tartozékokkal rendelkezik-e, és ha igen, akkor ugyanazzal az akadálymentességgel rendelkeznek-e. Ha csak egy tartozék van, vagy a get és a készlet tartozéka is ugyanazzal az akadálymentességgel rendelkezik, a access változó a tulajdonság egészének akadálymentességét jelzi. Ellenkező esetben a get és a set tartozékok akadálymentességét a változók és setAccess a getAccess változók jelzik.

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

Rendszertípusok bővítménymetódusa

Egyes rendszertípusok kényelmesen biztosítják a Deconstruct metódust. A típus például ezt a System.Collections.Generic.KeyValuePair<TKey,TValue> funkciót biztosítja. Ha minden System.Collections.Generic.Dictionary<TKey,TValue> elem felett iterál, az egy KeyValuePair<TKey, TValue> és fel is bontható. Vegyük a következő példát:

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

Olyan rendszertípusokhoz adhat hozzá metódust Deconstruct , amelyek nem rendelkeznek ilyen módszerrel. Fontolja meg a következő bővítménymetódust:

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

Ez a bővítménymetódus lehetővé teszi, hogy az összes Nullable<T> típust egy 1000-ra bontsa (bool hasValue, T value). Az alábbi példa az ezt a bővítménymetódust használó kódot mutatja be:

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 Típusok

Amikor két vagy több pozícióparaméter használatával deklarál egy rekordtípust, a fordító létrehoz egy metódust Deconstructout a deklaráció minden egyes pozícióparaméteréhezrecord. További információ: A tulajdonságdefiníció és a Deconstructor viselkedésének pozíciószintaxisa származtatott rekordokban.

Lásd még