Share via


Dekonstruera tupplar och andra typer

En tuppeln är ett enkelt sätt att hämta flera värden från ett metodanrop. Men när du hämtar tuppeln måste du hantera dess enskilda element. Det är besvärligt att arbeta med element för element, vilket visas i följande exempel. Metoden QueryCityData returnerar en tretuppeln och vart och ett av dess element tilldelas till en variabel i en separat åtgärd.

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

Det kan vara lika besvärligt att hämta flera fält- och egenskapsvärden från ett objekt: du måste tilldela ett fält- eller egenskapsvärde till en variabel på medlemsbasis.

Du kan hämta flera element från en tuppel eller hämta flera fält, egenskaper och beräknade värden från ett objekt i en enda dekonstruktionsåtgärd . Om du vill dekonstruera en tupplar tilldelar du dess element till enskilda variabler. När du dekonstruerar ett objekt tilldelar du valda värden till enskilda variabler.

Tupplar

C# har inbyggt stöd för att dekonstruera tupplar, vilket gör att du kan packa upp alla objekt i en tuppel i en enda åtgärd. Den allmänna syntaxen för att dekonstruera en tupplar liknar syntaxen för att definiera en: du omger variablerna som varje element ska tilldelas till inom parenteser till vänster i en tilldelningsinstruktuering. Följande instruktion tilldelar till exempel elementen i en fyratupplare till fyra separata variabler:

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

Det finns tre sätt att dekonstruera en tuppeln:

  • Du kan uttryckligen deklarera typen av varje fält inom parenteser. I följande exempel används den här metoden för att dekonstruera tretuppeln som returneras av QueryCityData metoden.

    public static void Main()
    {
        (string city, int population, double area) = QueryCityData("New York City");
    
        // Do something with the data.
    }
    
  • Du kan använda nyckelordet var så att C# härleder typen av varje variabel. Du placerar nyckelordet var utanför parenteserna. I följande exempel används typinferens när du dekonstruerar tretuppeln som returneras av QueryCityData metoden.

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

    Du kan också använda nyckelordet var individuellt med valfri eller alla variabeldeklarationer inom parenteserna.

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

    Detta är besvärligt och rekommenderas inte.

  • Slutligen kan du dekonstruera tuppeln till variabler som redan har deklarerats.

    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.
    }
    
  • Från och med C# 10 kan du blanda variabeldeklaration och tilldelning i en dekonstruktion.

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

Du kan inte ange en viss typ utanför parenteserna även om varje fält i tuppeln har samma typ. Detta genererar kompilatorfelet CS8136, formuläret "Dekonstruktion var (...)" tillåter inte en specifik typ för var.

Du måste tilldela varje element i tuppeln till en variabel. Om du utelämnar några element genererar kompilatorn felet CS8132: "Det går inte att dekonstruera en tupplar med x-element i "y"-variabler."

Tupplar med borttagna

När du dekonstruerar en tuppl är du ofta intresserad av värdena för endast vissa element. Du kan dra nytta av C#:s stöd för borttagningar, som är skrivskyddade variabler vars värden du har valt att ignorera. Ett ignorerande väljs med ett understreck ("_") i en tilldelning. Du kan ta bort så många värden som du vill. alla representeras av den enskilda ignorera, _.

I följande exempel visas användningen av tupplar med borttagna. Metoden QueryCityDataForYears returnerar en sextupppel med namnet på en stad, dess område, ett år, stadens befolkning för det året, ett andra år och stadens befolkning för det andra året. Exemplet visar befolkningsförändringen mellan dessa två år. Av de data som är tillgängliga från tuppeln är vi obekymrade med stadsområdet, och vi känner till stadens namn och de två datumen vid designtiden. Därför är vi bara intresserade av de två populationsvärden som lagras i tuppeln och kan hantera dess återstående värden som borttagna.

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

Användardefinierade typer

C# erbjuder inte inbyggt stöd för att dekonstruera andra typer av icke-tuppler än typerna record DictionaryEntry och . Men som författare till en klass, en struct eller ett gränssnitt kan du tillåta att instanser av typen dekonstrueras genom att implementera en eller flera Deconstruct metoder. Metoden returnerar void och varje värde som ska dekonstrueras anges med en out-parameter i metodsignaturen. Följande Deconstruct metod för en Person klass returnerar till exempel det första, mellersta och efternamn:

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

Du kan sedan dekonstruera en instans av Person klassen med namnet p med en tilldelning som följande kod:

var (fName, mName, lName) = p;

I följande exempel överbelastas Deconstruct metoden för att returnera olika kombinationer av egenskaper för ett Person objekt. Enskilda överlagringar returnerar:

  • Ett för- och efternamn.
  • Ett förnamn, ett mellannamn och ett efternamn.
  • Ett förnamn, ett efternamn, ett ortnamn och ett delstatsnamn.
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!

Flera Deconstruct metoder med samma antal parametrar är tvetydiga. Du måste vara noga med att definiera Deconstruct metoder med olika antal parametrar eller "aritet". Deconstruct metoder med samma antal parametrar kan inte särskiljas under överlagringsmatchning.

Användardefinierad typ med ignoreranden

Precis som med tupplar kan du använda ignorera för att ignorera markerade objekt som returneras av en Deconstruct metod. Varje ignorerande definieras av en variabel med namnet "_", och en enda dekonstruktionsåtgärd kan innehålla flera borttagningar.

I följande exempel dekonstrueras ett Person objekt i fyra strängar (för- och efternamn, stad och delstat) men efternamnet och tillståndet ignoreras.

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

Tilläggsmetoder för användardefinierade typer

Om du inte har skapat en klass, struct eller ett gränssnitt kan du fortfarande dekonstruera objekt av den typen genom att implementera en eller flera Deconstructtilläggsmetoder för att returnera de värden som du är intresserad av.

I följande exempel definieras två Deconstruct tilläggsmetoder för System.Reflection.PropertyInfo klassen. Den första returnerar en uppsättning värden som anger egenskaperna för egenskapen, inklusive dess typ, oavsett om den är statisk eller instans, om den är skrivskyddad och om den är indexerad. Den andra anger egenskapens tillgänglighet. Eftersom tillgängligheten för get- och set-accessorer kan skilja sig åt anger booleska värden om egenskapen har separata get- och set-åtkomster och, om det gör det, om de har samma hjälpmedel. Om det bara finns en accessor eller både get- och set-accessorn har samma hjälpmedel anger variabeln access tillgängligheten för egenskapen som helhet. I annat fall anges tillgängligheten för get- och set-åtkomsterna av variablerna getAccess och 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

Tilläggsmetod för systemtyper

Vissa systemtyper tillhandahåller Deconstruct metoden som en bekvämlighet. Typen innehåller till exempel den System.Collections.Generic.KeyValuePair<TKey,TValue> här funktionen. När du itererar över ett System.Collections.Generic.Dictionary<TKey,TValue> varje element är en KeyValuePair<TKey, TValue> och kan dekonstrueras. Ta följande som exempel:

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

Du kan lägga till en Deconstruct metod i systemtyper som inte har någon. Överväg följande tilläggsmetod:

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

Med den här tilläggsmetoden kan alla Nullable<T> typer dekonstrueras till en tuppeln av (bool hasValue, T value). I följande exempel visas kod som använder den här tilläggsmetoden:

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 Typer

När du deklarerar en posttyp med hjälp av två eller flera positionsparametrar skapar kompilatorn en Deconstruct metod med en out parameter för varje positionsparameter i deklarationen record . Mer information finns i Positionssyntax för egenskapsdefinition och dekonstruktionsbeteende i härledda poster.

Se även