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. }
To je těžkopádné a nedoporučuje se.
Nakonec můžete dekonstruovat řazenou kolekci členů do proměnných, které již byly deklarovány.
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. }
Počínaje jazykem C# 10 můžete v dekonstrukci kombinovat deklarace proměnných a přiřazení.
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, "Deconstruction 'var (...)' formulář zakáže konkrétní 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ů. Můžete využít podporu C# pro zahození, což jsou proměnné jen pro zápis, jejichž hodnoty jste se rozhodli ignorovat. Zahození je vybráno znakem podtržítka ("_") v přiřazení. Můžete zahodit tolik hodnot, kolik chcete; všechny jsou reprezentovány jediným zahozením , _
.
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# nenabízí integrovanou podporu pro dekonstrukci jiných typů než řazených kolekcí členů než typy record
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í void a každá hodnota, která se má dekonstruovat, je označena výstupním parametrem v podpisu metody. Například následující Deconstruct
metoda Person
třídy vrátí první, prostřední a příjmení:
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í.
- Jméno, prostřední jméno a příjmení.
- Jméno, příjmení, město 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ů nelze během řešení přetížení rozlišit.
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
. Každá operace zahození je definována proměnnou s názvem _a jedna operace dekonstrukce může obsahovat více zahození.
Následující příklad dekonstruuje Person
objekt do čtyř řetězců (křestní jméno a příjmení, město a stát), ale zahodí příjmení 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í pro uživatelem definované typy
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é označují vlastnosti vlastnosti, včetně jeho typu, ať už je statická nebo instance, ať už je jen pro čtení a zda je indexovaná. Druhá označuje přístupnost vlastnosti. Vzhledem k tomu, že přístupnost přístupových objektů get a set se může lišit, logické hodnoty označují, jestli má vlastnost samostatné přístupové objekty get a set, a pokud ano, zda mají stejnou přístupnost. 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. Když provádíte iteraci přes System.Collections.Generic.Dictionary<TKey,TValue> jednotlivé prvky, je možné je KeyValuePair<TKey, TValue>
vytvoř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.");
}
Můžete přidat metodu Deconstruct
do systémových typů, které ho nemají. Zvažte následující metodu rozšíření:
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();
}
}
Tato metoda rozšíření umožňuje, aby všechny Nullable<T> typy byly dekonstruovány do řazené kolekce (bool hasValue, T value)
členů . Následující příklad ukazuje kód, který používá tuto metodu rozšíření:
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
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.