Dekonstrukce řazených kolekcí členů a dalších typů
Řazená kolekce členů poskytuje jednoduchý způsob načtení více hodnot z volání metody. Jakmile ale řazenou kolekci členů načtete, musíte zpracovat její jednotlivé prvky. Práce na bázi element-by-element je těžkopádná, jak ukazuje následující příklad. Metoda QueryCityData
vrátí třířazenou kolekci členů a každý z jejích prvků je přiřazen k proměnné v samostatné operaci.
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);
}
}
Načtení více hodnot polí a vlastností z objektu může být stejně těžkopádné: je nutné přiřadit pole nebo hodnotu vlastnosti proměnné na základě člen-by-member.
Můžete načíst více prvků z řazené kolekce členů nebo načíst více polí, vlastností a vypočítaných hodnot z objektu v jedné dekonstrukční operaci. Pokud chcete dekonstruovat řazenou kolekci členů, přiřaďte její prvky jednotlivým proměnným. Při dekonstrukci objektu přiřadíte vybrané hodnoty jednotlivým proměnným.
Řazené kolekce členů
Funkce jazyka C# integrované pro dekonstrukci řazených kolekcí členů, které umožňují rozbalit všechny položky v řazené kolekci členů v jedné operaci. Obecná syntaxe pro dekonstrukci řazené kolekce členů je podobná syntaxi pro definování jedné: uzavřete proměnné, ke kterým má být každý prvek přiřazen v závorkách na levé straně příkazu assignment. Například následující příkaz přiřadí prvky čtyřřazené kolekce členů do čtyř samostatných proměnných:
var (name, address, city, zip) = contact.GetAddressInfo();
Existují tři způsoby, jak dekonstruovat řazenou kolekci členů:
Můžete explicitně deklarovat typ každého pole uvnitř závorek. Následující příklad používá tento přístup k dekonstrukci třířazené kolekce členů vrácené metodou
QueryCityData
.public static void Main() { (string city, int population, double area) = QueryCityData("New York City"); // Do something with the data. }
Klíčové slovo můžete použít
var
, aby jazyk C# odvodil typ každé proměnné. Klíčové slovo umístítevar
mimo závorky. Následující příklad používá odvození typu při dekonstrukci třítří řazené kolekce členů vrácené metodouQueryCityData
.public static void Main() { var (city, population, area) = QueryCityData("New York City"); // Do something with the data. }
Klíčové slovo můžete použít
var
také jednotlivě s libovolnou nebo všemi deklaracemi proměnných uvnitř závorek.public static void Main() { (string city, var population, var area) = QueryCityData("New York City"); // Do something with the data. }
Předchozí příklad je těžkopádný a nedoporučuje se.
Nakonec můžete dekonstruovat n-tici do již deklarovaných proměnných.
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. }
Můžete kombinovat deklaraci a přiřazení proměnné v dekonstruování.
public static void Main() { string city = "Raleigh"; int population = 458880; (city, population, double area) = QueryCityData("New York City"); // Do something with the data. }
Nemůžete zadat konkrétní typ mimo závorky, i když má každé pole v řazené kolekci členů stejný typ. Tím se vygeneruje chyba kompilátoru CS8136: "Formulář deconstruction 'var (...)' nedovoluje specifický typ pro 'var'."
Každému prvku řazené kolekce členů musíte přiřadit proměnnou. Pokud vynecháte některé prvky, kompilátor vygeneruje chybu CS8132, "Nelze dekonstruovat řazenou kolekci elementů x do proměnných y".
Prvky řazené kolekce členů s zahozením
Při dekonstrukci řazené kolekce členů vás často zajímají pouze hodnoty některých prvků. Podporu jazyka C# můžete využít pro zahození, což jsou proměnné jen pro zápis, jejichž hodnoty jste se rozhodli ignorovat. Použijete znak podtržítka („_“) jako odloženou hodnotu v přiřazovací operaci. Můžete zahodit tolik hodnot, kolik chcete; jediný zahozený prvek, _
, představuje všechny zahozené hodnoty.
Následující příklad ukazuje použití řazených kolekcí členů s zahozeními. Metoda QueryCityDataForYears
vrátí šestiřazenou kolekci členů s názvem města, jeho oblastí, rokem, počtem obyvatel města pro tento rok, druhým rokem a obyvateli města pro tento druhý rok. Příklad ukazuje změnu populace mezi těmito dvěma roky. Z dat dostupných z řazené kolekce členů jsme nespokojení s oblastí města a známe název města a dvě data v době návrhu. V důsledku toho nás zajímají pouze dvě hodnoty základního souboru uložené v řazené kolekci členů a můžeme zpracovat zbývající hodnoty jako zahozené.
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
Uživateli definované typy
Jazyk C# nabízí integrovanou podporu pro dekonstrukci typů n-tic, record
a DictionaryEntry. Jako autor třídy, struktury nebo rozhraní však můžete povolit, aby instance typu byly dekonstruovány implementací jedné nebo více Deconstruct
metod. Metoda vrátí hodnotu void. Parametr v podpisu metody představuje každou hodnotu, která se má dekonstruovat. Například následující metoda Deconstruct
třídy Person
vrátí první, prostřední a rodinný název:
public void Deconstruct(out string fname, out string mname, out string lname)
Potom můžete dekonstruovat instanci Person
třídy s názvem p
přiřazení, jako je následující kód:
var (fName, mName, lName) = p;
Následující příklad přetíží metodu Deconstruct
k vrácení různých kombinací vlastností objektu Person
. Vrácení jednotlivých přetížení:
- Jméno a příjmení.
- Křestní, prostřední a příjmení.
- Jméno, jméno rodiny, název města a název státu.
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!
Více Deconstruct
metod, které mají stejný počet parametrů, jsou nejednoznačné. Při definování Deconstruct
metod s různými čísly parametrů nebo "arity" musíte být opatrní.
Deconstruct
metody se stejným počtem parametrů se během rozlišení přetížení nerozlišují.
Uživatelem definovaný typ s zahozením
Stejně jako u řazených kolekcí členů můžete pomocí zahození ignorovat vybrané položky vrácené metodou Deconstruct
. Proměnná s názvem _představuje zahození. Jedna operace dekonstrukce může zahrnovat více zahození.
Následující příklad dekonstruuje objekt Person
do čtyř řetězců (jméno a rodina, město a stát), ale zahodí jméno rodiny a stát.
// Deconstruct the person object.
var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
// Hello John of Boston!
Metody rozšíření dekonstrukce
Pokud jste nevytvořili třídu, strukturu nebo rozhraní, můžete dekonstruovat objekty tohoto typu implementací jedné nebo více Deconstruct
rozšiřujících metod pro vrácení hodnot, které vás zajímají.
Následující příklad definuje dvě Deconstruct
rozšiřující metody pro System.Reflection.PropertyInfo třídu. První vrátí sadu hodnot, které vykazují charakteristiky nemovitosti. Druhá označuje přístupnost vlastnosti. Logické hodnoty označují, jestli má vlastnost samostatné přístupory ke čtení a zápisu, nebo různou úroveň přístupnosti. Pokud je k dispozici pouze jeden přístupový objekt nebo get i objekt set, má proměnná stejnou přístupnost, access
označuje přístupnost vlastnosti jako celek. V opačném případě jsou přístupnost přístupových objektů get a set označená getAccess
setAccess
proměnnými.
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
Metoda rozšíření pro systémové typy
Některé typy systémů poskytují metodu Deconstruct
jako pohodlí. Tento typ například System.Collections.Generic.KeyValuePair<TKey,TValue> poskytuje tuto funkci. Při iteraci přes System.Collections.Generic.Dictionary<TKey,TValue>je každý prvek KeyValuePair<TKey, TValue>
a lze ho rozložit. Představte si následující příklad:
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
typy
Když deklarujete typ záznamu pomocí dvou nebo více pozičních parametrů, kompilátor vytvoří metodu Deconstruct
out
s parametrem pro každý poziční parametr v record
deklaraci. Další informace naleznete v tématu Positional syntaxe pro definici vlastnosti a chování dekonstruktor v odvozených záznamech.