Bagikan melalui


Mendekonstruksi tupel dan jenis lainnya

Tuple menyediakan cara ringan untuk mengambil beberapa nilai dari panggilan metode. Tetapi setelah Anda mengambil tuple, Anda harus menangani elemen individualnya. Bekerja berdasarkan elemen demi elemen rumit, seperti yang ditunjukkan contoh berikut. Metode QueryCityData mengembalikan tiga tuple, dan masing-masing elemennya ditetapkan ke variabel dalam operasi terpisah.

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

Mengambil beberapa nilai bidang dan properti dari objek bisa sama rumitnya: Anda harus menetapkan nilai bidang atau properti ke variabel berdasarkan anggota demi anggota.

Anda dapat mengambil beberapa elemen dari tuple atau mengambil beberapa bidang, properti, dan nilai komputasi dari objek dalam satu operasi dekonstruksi . Untuk mendekonstruksi tuple, Anda menetapkan elemennya ke variabel individual. Saat Anda mendekonstruksi objek, Anda menetapkan nilai yang dipilih ke variabel individual.

Tupel

C# memiliki dukungan bawaan untuk mendekonstruksi tuple, yang memungkinkan Anda membongkar semua item dalam tuple dalam satu operasi. Sintaksis umum untuk mendekonstruksi tuple mirip dengan sintaksis untuk mendefinisikan tuple tersebut: Anda menyertakan variabel tempat setiap elemen akan ditetapkan dalam tanda kurung di sisi kiri pernyataan penugasan. Misalnya, pernyataan berikut menetapkan elemen empat tuple ke empat variabel terpisah:

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

Ada tiga cara untuk mendekonstruksi tuple:

  • Anda dapat secara eksplisit mendeklarasikan jenis setiap bidang di dalam tanda kurung. Contoh berikut menggunakan pendekatan ini untuk mendekonstruksi tiga tuple yang dikembalikan oleh metode QueryCityData.

    public static void Main()
    {
        (string city, int population, double area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    
  • Anda dapat menggunakan kata kunci var sehingga C# menyimpulkan jenis setiap variabel. Anda menempatkan kata kunci var di luar tanda kurung. Contoh berikut menggunakan inferensi jenis saat mendekonstruksi tiga tuple yang dikembalikan oleh metode QueryCityData.

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

    Anda juga dapat menggunakan kata kunci var satu per satu dengan salah satu atau semua deklarasi variabel di dalam tanda kurung.

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

    Ini rumit dan tidak disarankan.

  • Terakhir, Anda dapat mendekonstruksi tuple menjadi variabel yang telah dideklarasikan.

    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.
    }
    
  • Mulai dari C# 10, Anda dapat mencampur deklarasi variabel dan penugasan dalam dekonstruksi.

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

Anda tidak dapat menentukan jenis tertentu di luar tanda kurung meskipun setiap bidang dalam tuple memiliki jenis yang sama. Melakukannya akan menghasilkan kesalahan pengompilasi CS8136, formulir "Formulir 'var (...)' dekonstruksi melarang jenis tertentu untuk 'var'.".

Anda harus menetapkan setiap elemen tuple ke variabel. Jika Anda menghilangkan elemen apa pun, pengompilasi menghasilkan kesalahan CS8132, "Tidak dapat mendekonstruksi tuple elemen 'x' ke dalam variabel 'y'."

Elemen tuple dengan buang

Sering kali, ketika mendekonstruksi tuple Anda hanya tertarik pada nilai-nilai beberapa elemen. Anda dapat memanfaatkan dukungan C#untuk membuang, yang merupakan variabel hanya-tulis yang nilainya telah Anda pilih untuk diabaikan. Buang dipilih oleh karakter garis bawah ("_") dalam penugasan. Anda dapat membuang nilai sebanyak yang Anda suka; semua diwakili oleh buang tunggal, _.

Contoh berikut mengilustrasikan penggunaan tuple dengan buang. Metode QueryCityDataForYears ini mengembalikan enam tuple dengan nama kota, wilayahnya, setahun, populasi kota untuk tahun itu, tahun kedua, dan populasi kota untuk tahun kedua tersebut. Contoh menunjukkan perubahan populasi antara dua tahun tersebut. Dari data yang tersedia dari tuple, kami tidak peduli dengan area kota, dan kami tahu nama kota dan dua tanggal pada waktu desain. Akibatnya, kami hanya tertarik pada dua nilai populasi yang disimpan dalam tuple, dan dapat menangani nilai yang tersisa sebagai buang.

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

Jenis yang ditentukan pengguna

C# tidak menawarkan dukungan bawaan untuk mendekonstruksi jenis non-tuple selain jenis record dan DictionaryEntry. Namun, sebagai penulis kelas, struct, atau antarmuka, Anda dapat mengizinkan instans jenis untuk didekonstruksi dengan menerapkan satu atau beberapa metode Deconstruct. Metode mengembalikan kekosongan, dan setiap nilai yang akan didekonstruksi ditunjukkan oleh parameter keluar dalam tanda tangan metode. Misalnya, metode Deconstruct kelas Person berikut mengembalikan nama depan, tengah, dan belakang:

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

Anda kemudian dapat mendekonstruksi instans kelas Person bernama p dengan penugasan seperti kode berikut:

var (fName, mName, lName) = p;

Contoh berikut membebani metode Deconstruct untuk mengembalikan berbagai kombinasi properti objek Person. Kelebihan beban individu mengembalikan:

  • Nama depan dan belakang.
  • Nama depan, tengah, dan belakang.
  • Nama depan, nama belakang, nama kota, dan nama negara bagian.
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!

Beberapa metode Deconstruct yang memiliki jumlah parameter yang sama ambigu. Anda harus berhati-hati untuk menentukan metode Deconstruct dengan jumlah parameter yang berbeda, atau "aritas". Metode Deconstruct dengan jumlah parameter yang sama tidak dapat dibedakan selama resolusi kelebihan beban.

Jenis yang ditentukan pengguna dengan buang

Sama seperti yang Anda lakukan dengan tuple, Anda dapat menggunakan buang untuk mengabaikan item yang dipilih yang dikembalikan oleh metode Deconstruct. Setiap pembuangan didefinisikan oleh variabel bernama "_", dan satu operasi dekonstruksi dapat mencakup beberapa pembuangan.

Contoh berikut mendekonstruksi objek Person menjadi empat string (nama depan dan belakang, kota, dan status) tetapi membuang nama belakang dan status.

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

Metode ekstensi untuk jenis yang ditentukan pengguna

Jika Anda tidak menulis kelas, struktur, atau antarmuka, Anda masih dapat mendekonstruksi objek jenis tersebut dengan menerapkan satu atau beberapa Deconstructmetode ekstensi untuk mengembalikan nilai yang Anda minati.

Contoh berikut mendefinisikan dua metode ekstensi Deconstruct untuk kelas System.Reflection.PropertyInfo. Yang pertama mengembalikan sekumpulan nilai yang menunjukkan karakteristik properti, termasuk jenisnya, baik statis atau instans, baik itu baca-saja, dan apakah itu diindeks. Yang kedua menunjukkan aksesibilitas properti. Karena aksesibilitas pengakses get dan set dapat berbeda, nilai Boolean menunjukkan apakah properti memiliki pengakses get dan set terpisah dan, jika ya, apakah mereka memiliki aksesibilitas yang sama. Jika hanya ada satu pengakses atau pengakses get dan set memiliki aksesibilitas yang sama, variabel access menunjukkan aksesibilitas properti secara keseluruhan. Jika tidak, aksesibilitas pengakses get dan set ditunjukkan oleh variabel getAccess dan 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

Metode ekstensi untuk jenis sistem

Beberapa jenis sistem menyediakan metode Deconstruct sebagai kenyamanan. Misalnya, jenis System.Collections.Generic.KeyValuePair<TKey,TValue> menyediakan fungsionalitas ini. Saat Anda mengulangi System.Collections.Generic.Dictionary<TKey,TValue> setiap elemen adalah KeyValuePair<TKey, TValue> dan dapat didekonstruksi. Pertimbangkan contoh berikut:

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

Anda dapat menambahkan metode Deconstruct ke jenis sistem yang tidak memilikinya. Pertimbangkan metode ekstensi berikut:

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

Metode ekstensi ini memungkinkan semua jenis Nullable<T> untuk didekonstruksi menjadi tuple (bool hasValue, T value). Contoh berikut menunjukkan kode yang menggunakan metode ekstensi ini:

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 }

Jenis record

Saat Anda mendeklarasikan jenis rekaman dengan menggunakan dua parameter posisi atau lebih, pengompilasi membuat metode Deconstruct dengan parameter out untuk setiap parameter posisi dalam deklarasi record. Untuk informasi selengkapnya, lihat Sintaksis posisi untuk definisi properti dan Perilaku dekonstruktor dalam rekaman turunan.

Lihat juga