Demetleri ve diğer türleri ayrıştırma

Tanımlama grubu, bir yöntem çağrısından birden çok değer almak için basit bir yol sağlar. Ancak tanımlama grubu alındıktan sonra kendi öğelerini işlemeniz gerekir. Aşağıdaki örnekte gösterildiği gibi, öğeye göre bir temel üzerinde çalışmak hantaldır. QueryCityData yöntemi üç tanımlama grubu döndürür ve öğelerinin her biri ayrı bir işlemde bir değişkene atanır.

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

Bir nesneden birden çok alan ve özellik değeri almak da aynı derecede hantal olabilir: Bir değişkene üye bazında alan veya özellik değeri atamanız gerekir.

Bir tanımlama grubundan birden çok öğe alabilir veya tek bir yapı kaldırma işlemindeki bir nesneden birden çok alan, özellik ve hesaplanan değer alabilirsiniz. Bir tanımlama kümesinin yapısını çözmek için öğelerini bağımsız değişkenlere atarsınız. Bir nesnenin yapısını kaldırdığınızda, seçilen değerleri tek tek değişkenlere atarsınız.

Tanımlama grupları

C#, bir tanımlama grubundaki tüm öğeleri tek bir işlemle paketten çıkarmanıza olanak tanıyan yerleşik tanımlama grubu oluşturma desteğine sahiptir. Tanımlama grubu oluşturmanın genel söz dizimi, tanımlama söz dizimine benzer: her öğenin atanacağı değişkenleri atama deyiminin sol tarafındaki parantez içine alırsınız. Örneğin, aşağıdaki deyim dörtli bir tanımlama grubunun öğelerini dört ayrı değişkene atar:

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

Bir tanımlama kümesinin yapısını kaldırmanın üç yolu vardır:

  • Her alanın türünü parantez içinde açıkça bildirebilirsiniz. Aşağıdaki örnek, yöntemi tarafından QueryCityData döndürülen üç tanımlama grubu yapısını çözmek için bu yaklaşımı kullanır.

    public static void Main()
    {
        (string city, int population, double area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    
  • C# öğesinin her değişkenin var türünü çıkarması için anahtar sözcüğünü kullanabilirsiniz. Anahtar sözcüğü parantezlerin dışına yerleştirirsiniz var . Aşağıdaki örnek, yöntemi tarafından QueryCityData döndürülen üç tanımlama grubu kaldırıldığında tür çıkarımı kullanır.

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

    Anahtar sözcüğünü var parantez içindeki değişken bildirimlerinden herhangi biriyle veya tümüyle tek tek de kullanabilirsiniz.

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

    Bu hantaldır ve önerilmez.

  • Son olarak, tanımlama demetini önceden bildirilmiş olan değişkenlere dönüştürebilirsiniz.

    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.
    }
    
  • C# 10'da başlayarak, değişken bildirimini ve atamayı bir yapısızlaştırmada karıştırabilirsiniz.

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

Tanımlama grubundaki her alan aynı türe sahip olsa bile parantez dışında belirli bir tür belirtemezsiniz. Bunu yaptığınızda CS8136, "'var (...)' yapısızlaştırma formu 'var' için belirli bir türe izin verilmiyorsa" derleyici hatası oluşturur.

Tanımlama grubunun her öğesini bir değişkene atamanız gerekir. Herhangi bir öğeyi atlarsanız, derleyici CS8132 "'x' öğelerinin bir demetini 'y' değişkenlerine dönüştüremez" hatasını oluşturur.

Atılan öğelerle tanımlama grubu

Genellikle bir tanımlama grubu oluştururken yalnızca bazı öğelerin değerleriyle ilgilenirsiniz. Değerlerini yoksaymayı seçtiğiniz yalnızca yazma değişkenleri olan atmalar için C# desteğinden yararlanabilirsiniz. Atamadaki bir alt çizgi karakteri ("_") tarafından atma seçilir. İstediğiniz kadar değer atabilirsiniz; tümü tek atma _ile temsil edilir.

Aşağıdaki örnekte, atmalarla tanımlama demetlerinin kullanımı gösterilmektedir. QueryCityDataForYears yöntemi, bir şehir, onun alanı, bir yıl, o yıl için şehir nüfusu, ikinci yıl ve o ikinci yıl için şehrin nüfusu ile altılı bir tanımlama grubu döndürür. Örnekte, bu iki yıl arasındaki nüfus değişikliği gösterilmektedir. Tanımlama grubundaki verilerden şehir alanıyla ilgili bilgimiz yok ve tasarım zamanında şehir adını ve iki tarihi biliyoruz. Sonuç olarak, yalnızca tanımlama grubunda depolanan iki popülasyon değeriyle ilgileniyoruz ve kalan değerlerini atılmış olarak işleyebiliriz.

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

Kullanıcı tanımlı türler

C#, ve DictionaryEntry türleri dışındaki record tanımlama grubu olmayan türlerin yapısını kaldırmaya yönelik yerleşik destek sunmaz. Ancak, bir sınıfın, yapının veya arabirimin yazarı olarak, bir veya daha fazla Deconstruct yöntem uygulayarak tür örneklerinin yapılandırılmasına izin vekleyebilirsiniz. yöntemi void döndürür ve yapısı kaldırılacak her değer, yöntem imzasında bir out parametresiyle gösterilir. Örneğin, bir Person sınıfın aşağıdaki Deconstruct yöntemi ad, ikinci ve soyadını döndürür:

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

Daha sonra aşağıdaki kod gibi bir atamayla adlı p sınıfın örneğini Person dekonstrüde edebilirsiniz:

var (fName, mName, lName) = p;

Aşağıdaki örnek, bir Person nesnenin Deconstruct çeşitli özellik bileşimlerini döndürmek için yöntemini aşırı yükler. Tek tek aşırı yüklemeler döndürür:

  • Bir ad ve soyadı.
  • Ad, ikinci ve soyadı.
  • Ad, soyadı, şehir adı ve eyalet adı.
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!

Aynı sayıda parametreye sahip birden çok Deconstruct yöntem belirsizdir. Farklı sayıda parametre veya "arity" içeren yöntemleri tanımlamaya Deconstruct dikkat etmeniz gerekir. Deconstruct aynı sayıda parametreye sahip yöntemler aşırı yükleme çözümlemesi sırasında ayırt edilemez.

Atmalarla kullanıcı tanımlı tür

Tanımlama listelerinde olduğu gibi, bir Deconstruct yöntem tarafından döndürülen seçili öğeleri yoksaymak için atmaları kullanabilirsiniz. Her atma işlemi "_" adlı bir değişken tarafından tanımlanır ve tek bir yapılandırma işlemi birden çok atma içerebilir.

Aşağıdaki örnek, bir Person nesneyi dört dizeye (ad ve soyadı, şehir ve eyalet) ayırır, ancak soyadını ve eyaleti atar.

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

Kullanıcı tanımlı türler için uzantı yöntemleri

Bir sınıf, yapı veya arabirim yazmadıysanız, ilgilendiğiniz değerleri döndürmek için bir veya daha fazla Deconstructuzantı yöntemi uygulayarak bu türdeki nesnelerin yapısını yine de kaldırabilirsiniz.

Aşağıdaki örnek, sınıfı için System.Reflection.PropertyInfo iki Deconstruct uzantı yöntemini tanımlar. birincisi, türü, statik veya örnek, salt okunur olup olmadığı ve dizine eklenip dizinlenmediği dahil olmak üzere özelliğin özelliklerini gösteren bir değer kümesi döndürür. İkincisi özelliğin erişilebilirliğini gösterir. Alma ve ayarlama erişimcilerinin erişilebilirliği farklı olabileceğinden, Boole değerleri özelliğin ayrı alma ve ayarlama erişimcileri olup olmadığını ve varsa aynı erişilebilirliği olup olmadığını gösterir. Yalnızca bir erişimci varsa veya hem get hem de küme erişimcisi aynı erişilebilirliği varsa, access değişken özelliğin erişilebilirliğini bir bütün olarak gösterir. Aksi takdirde, get ve set erişimcilerinin erişilebilirliği ve setAccess değişkenleri tarafından getAccess belirtilir.

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

Sistem türleri için uzantı yöntemi

Bazı sistem türleri kolaylık sağlamak için Deconstruct yöntemini sağlar. Örneğin, System.Collections.Generic.KeyValuePair<TKey,TValue> türü bu işlevi sağlar. Bir öğe üzerinde System.Collections.Generic.Dictionary<TKey,TValue> yineleme yaptığınızda bir öğesidir KeyValuePair<TKey, TValue> ve yapısızlaştırılabilir. Aşağıdaki örneği inceleyin:

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

Olmayan sistem türlerine bir Deconstruct yöntem ekleyebilirsiniz. Aşağıdaki uzantı yöntemini göz önünde bulundurun:

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

Bu uzantı yöntemi, tüm Nullable<T> türlerin bir tanımlama grubuna (bool hasValue, T value)dönüştürülmesini sağlar. Aşağıdaki örnekte bu uzantı yöntemini kullanan kod gösterilmektedir:

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ür

İki veya daha fazla konumsal parametre kullanarak bir kayıt türü bildirdiğinizde, derleyici bildirimdeki record her konum parametresi için bir parametre içeren bir Deconstructout yöntem oluşturur. Daha fazla bilgi için bkz. Türetilmiş kayıtlarda özellik tanımı ve Yıkıcı davranışı için konumsal söz dizimi.

Ayrıca bkz.