A tuple-ök és más típusok kibontása

A tuple egyszerű módot kínál több érték lekérésére egy metódushívásból. Miután lekéri az n-es (tulajdonsághalmazt), kezelnie kell annak egyes elemeit. 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.

Tupel

A C# beépített támogatással rendelkezik a tuple-ök dekonstruálásához, amely lehetővé teszi, hogy egyetlen műveletben kibontsa 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 egy négyelemes csoport elemeit rendelni négy külön változóhoz:

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

Egy tuple szétbontá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 QueryCityData metódus által visszaadott háromtagú elem dekonstruálásához.

    public static void Main()
    {
        (string city, int population, double area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    
  • A var kulcsszó használatával a C# következtetni tud az egyes változók típusára. A kulcsszót a var zárójeleken kívülre kell helyeznie. Az alábbi példa típus-következtetést használ a metódus által QueryCityData visszaadott háromértékű tupla 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.
    }
    

    Az előző példa nehézkes, és nem ajánlott.

  • Végül dekonstruálhatja a tömböt a már deklarált változókra.

    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 dekonstruálásban keverheti a változó deklarációt és hozzárendelést.

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

Zárójeleken kívül nem adhat meg egy adott típust, még akkor is, ha a tömb minden mezőjének ugyanaz a típusa. Ezzel létrehoz egy CS8136-os fordítói hibát, a "Deconstruction 'var (...)' űrlap letilt egy adott típust a "var" számára."

A tömb minden elemét hozzá kell rendelni 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ő tuple elemek

Gyakran, amikor a tuple-t lebontjuk, csak bizonyos elemek értékei érdekelnek. Kihasználhatja a C# támogatását a elvetéseknél, amelyek csak írásra szolgáló változók, amelyek értékeit figyelmen kívül hagy. Deklarálhat egy elvetést aláhúzásjellel ("_") egy hozzárendelésben. Annyi értéket elvethet, amennyit csak szeretne; egyetlen elvetés( _) az összes elvetett értéket jelöli.

Az alábbi példa az n-tuplek helyettesítésekkel való használatát mutatja be. A QueryCityDataForYears módszer egy hatértékű rekordot ad vissza a város nevével, annak területével, egy évvel, az adott év lakosságával, egy második évvel és a város népességével 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 nem foglalkozunk a városterülettel, és tudjuk a város nevét és a két dátumot a tervezési idő alatt. Ennek eredményeképpen csak a tuple-ben tárolt két populáció-érték érdekelnek minket, és a fennmaradó értékeket figyelmen kívül tudjuk hagyni.

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# beépített támogatást nyújt a tuple típusok, recordés DictionaryEntry típusok dekonstruálásához. 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 ürességet ad vissza. A és paraméterek a metódusaláírásban az egyes lebontandó értékeket jelölik. Egy Deconstruct osztály következő Person metódusa például az első, a középső és a családnevet 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 dekonstruálhat egy példányt a Person osztályból, amelynek neve p.

var (fName, mName, lName) = p;

Az alábbi példa a Deconstruct metódus túlterhelését mutatja be, hogy a Person objektum különböző tulajdonságait adja vissza. Az egyéni túlterhelések visszatérnek:

  • Utónév és családnév.
  • Első, középső és családi név.
  • Utónév, családné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 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 eldobással

Ugyanúgy, mint a tömbök esetében, elvetéssel figyelmen kívül hagyhatja a Deconstruct metódus által visszaadott kijelölt elemeket. A "_" nevű változó elvetésnek felel meg. Egyetlen bontási művelet több elvetést is tartalmazhat.

Az alábbi példa egy Person objektumot négy sztringre (az utó- és családnevekre, a városra és az államra) bont, de elveti a családnevet é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!

A dekonstruálás kiterjesztési módszerei

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 a tulajdonság jellemzőit jelzi. A második a tulajdonság elérhetőségét jelzi. A logikai értékek azt jelzik, hogy a tulajdonság külön beolvasási és beállítási tartozékokkal vagy különböző kisegítő lehetőségekkel rendelkezik-e. Ha csak egy hozzáférő van, vagy a get és a set hozzáférő is ugyanazon a hozzáférési szinten van, a access változó a tulajdonság egészének hozzáférhetőségét jelzi. Ellenkező esetben a get és set hozzáférők elérhetőségét a getAccess és setAccess változók jelzik.

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

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. Amikor egy System.Collections.Generic.Dictionary<TKey,TValue>-t iterálsz, minden elem egy KeyValuePair<TKey, TValue>, és dekonstruálható. 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.");
}

record Típusok

Amikor két vagy több pozíciós paraméter használatával deklarál egy rekordtípust, a fordító egy metódust hoz létre minden pozíciós paraméterhez egy-egy paraméterrel a deklarációban. További információ: Tulajdonságdefiníció pozíciószintaxisa és a dekonstruátor viselkedése származtatott rekordokban.

Lásd még