Desconstrução de tuplas e outros tipos
Uma tupla fornece uma maneira leve de recuperar vários valores de uma chamada de método. Mas uma vez que você recuperar a tupla, você tem que lidar com seus elementos individuais. Trabalhar elemento a elemento é complicado, como mostra o exemplo a seguir. O QueryCityData
método retorna uma tripla e cada um de seus elementos é atribuído a uma variável em uma operação separada.
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);
}
}
Recuperar vários valores de campo e propriedade de um objeto pode ser igualmente complicado: você deve atribuir um valor de campo ou propriedade a uma variável membro por membro.
Você pode recuperar vários elementos de uma tupla ou recuperar vários campos, propriedades e valores computados de um objeto em uma única operação de desconstrução . Para desconstruir uma tupla, você atribui seus elementos a variáveis individuais. Ao desconstruir um objeto, você atribui valores selecionados a variáveis individuais.
Tuplas
O C# possui suporte interno para desconstruir tuplas, o que permite descompactar todos os itens em uma tupla em uma única operação. A sintaxe geral para desconstruir uma tupla é semelhante à sintaxe para definir uma: você coloca as variáveis às quais cada elemento deve ser atribuído entre parênteses no lado esquerdo de uma instrução de atribuição. Por exemplo, a instrução a seguir atribui os elementos de uma quatro tuplas a quatro variáveis separadas:
var (name, address, city, zip) = contact.GetAddressInfo();
Existem três maneiras de desconstruir uma tupla:
Você pode declarar explicitamente o tipo de cada campo entre parênteses. O exemplo a seguir usa essa abordagem para desconstruir as três tuplas retornadas
QueryCityData
pelo método.public static void Main() { (string city, int population, double area) = QueryCityData("New York City"); // Do something with the data. }
Você pode usar a
var
palavra-chave para que o C# infera o tipo de cada variável. Coloque avar
palavra-chave fora dos parênteses. O exemplo a seguir usa inferência de tipo ao desconstruir as três tuplas retornadasQueryCityData
pelo método.public static void Main() { var (city, population, area) = QueryCityData("New York City"); // Do something with the data. }
Você também pode usar a
var
palavra-chave individualmente com qualquer uma ou todas as declarações de variáveis dentro dos parênteses.public static void Main() { (string city, var population, var area) = QueryCityData("New York City"); // Do something with the data. }
Isso é complicado e não é recomendado.
Por fim, você pode desconstruir a tupla em variáveis que já foram declaradas.
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. }
A partir do C# 10, você pode misturar declaração de variável e atribuição em uma desconstrução.
public static void Main() { string city = "Raleigh"; int population = 458880; (city, population, double area) = QueryCityData("New York City"); // Do something with the data. }
Não é possível especificar um tipo específico fora dos parênteses, mesmo que todos os campos na tupla tenham o mesmo tipo. Isso gera erro de compilador CS8136, "Desconstrução 'var (...)' forma não permite um tipo específico para 'var'.".
Você deve atribuir cada elemento da tupla a uma variável. Se você omitir quaisquer elementos, o compilador gerará o erro CS8132, "Não é possível desconstruir uma tupla de elementos 'x' em variáveis 'y'."
Elementos de tupla com devoluções
Muitas vezes, ao desconstruir uma tupla, você está interessado nos valores de apenas alguns elementos. Você pode aproveitar o suporte do C# para descartes que são variáveis somente gravação cujos valores você escolheu ignorar. Um descarte é escolhido por um caractere de sublinhado ("_") em uma atribuição. Você pode descartar quantos valores quiser; todos são representados pelo descarte único, _
.
O exemplo a seguir ilustra o uso de tuplas com devoluções. O QueryCityDataForYears
método retorna uma tupla de seis com o nome de uma cidade, sua área, um ano, a população da cidade para esse ano, um segundo ano, e a população da cidade para esse segundo ano. O exemplo mostra a mudança da população entre esses dois anos. Dos dados disponíveis da tupla, não estamos preocupados com a área da cidade, e sabemos o nome da cidade e as duas datas no momento do projeto. Como resultado, estamos interessados apenas nos dois valores populacionais armazenados na tupla e podemos lidar com seus valores restantes como descartáveis.
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
Tipos definidos pelo usuário
O C# não oferece suporte interno para desconstruir tipos não tuplos record
diferentes dos tipos e DictionaryEntry . No entanto, como o autor de uma classe, um struct ou uma interface, você pode permitir que instâncias do tipo sejam desconstruídas implementando um ou mais Deconstruct
métodos. O método retorna void, e cada valor a ser desconstruído é indicado por um parâmetro out na assinatura do método. Por exemplo, o seguinte Deconstruct
método de uma Person
classe retorna o nome, o nome do meio e o sobrenome:
public void Deconstruct(out string fname, out string mname, out string lname)
Em seguida, você pode desconstruir uma instância da Person
classe nomeada p
com uma atribuição como o código a seguir:
var (fName, mName, lName) = p;
O exemplo a seguir sobrecarrega o Deconstruct
método para retornar várias combinações de propriedades de um Person
objeto. Sobrecargas individuais retornam:
- Um nome e um sobrenome.
- Um nome, meio e sobrenome.
- Um nome, um sobrenome, um nome de cidade e um nome de estado.
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ários Deconstruct
métodos com o mesmo número de parâmetros são ambíguos. Você deve ter cuidado para definir Deconstruct
métodos com diferentes números de parâmetros, ou "aridade". Deconstruct
métodos com o mesmo número de parâmetros não podem ser distinguidos durante a resolução de sobrecarga.
Tipo definido pelo usuário com descartes
Assim como você faz com tuplas, você pode usar descartáveis para ignorar itens selecionados retornados por um Deconstruct
método. Cada descarte é definido por uma variável chamada "_", e uma única operação de desconstrução pode incluir vários descartes múltiplos.
O exemplo a seguir desconstrói um Person
objeto em quatro cadeias de caracteres (o nome e o sobrenome, a cidade e o estado), mas descarta o sobrenome e o estado.
// Deconstruct the person object.
var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
// Hello John of Boston!
Métodos de extensão para tipos definidos pelo usuário
Se você não criou uma classe, struct ou interface, ainda pode desconstruir objetos desse tipo implementando um ou mais Deconstruct
métodos de extensão para retornar os valores nos quais você está interessado.
O exemplo a seguir define dois Deconstruct
métodos de extensão para a System.Reflection.PropertyInfo classe. O primeiro retorna um conjunto de valores que indicam as características da propriedade, incluindo seu tipo, se é estático ou instância, se é somente leitura e se está indexado. O segundo indica a acessibilidade do imóvel. Como a acessibilidade dos acessadores get e set pode diferir, os valores booleanos indicam se a propriedade tem acessadores get e set separados e, se tiver, se eles têm a mesma acessibilidade. Se houver apenas um acessador ou ambos o get e o set accessor tiverem a mesma acessibilidade, a access
variável indica a acessibilidade da propriedade como um todo. Caso contrário, a acessibilidade dos acessadores get e set são indicadas getAccess
pelas variáveis e 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
Método de extensão para tipos de sistema
Alguns tipos de sistema fornecem o Deconstruct
método como uma conveniência. Por exemplo, o System.Collections.Generic.KeyValuePair<TKey,TValue> tipo fornece essa funcionalidade. Quando você está iterando sobre um System.Collections.Generic.Dictionary<TKey,TValue> , cada elemento é um KeyValuePair<TKey, TValue>
e pode ser desconstruído. Considere o seguinte exemplo:
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.");
}
Você pode adicionar um Deconstruct
método aos tipos de sistema que não têm um. Considere o seguinte método de extensão:
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();
}
}
Este método de extensão permite que todos os Nullable<T> tipos sejam desconstruídos em uma tupla de (bool hasValue, T value)
. O exemplo a seguir mostra o código que usa esse método de extensão:
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
tipos
Quando você declara um tipo de registro usando dois ou mais parâmetros posicionais, o compilador cria um Deconstruct
método com um out
parâmetro para cada parâmetro posicional na record
declaração. Para obter mais informações, consulte Sintaxe posicional para definição de propriedade e comportamento do desconstrutor em registros derivados.