Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
As tuplas fornecem uma estrutura de dados leve para vários membros em uma única estrutura. Eles são a escolha preferida em relação a tipos anônimos. As tuplas oferecem melhor desempenho, dão suporte à desconstrução e oferecem uma sintaxe mais flexível.
Os tipos anônimos fornecem um modo conveniente de encapsular um conjunto de propriedades somente leitura em um único objeto sem a necessidade de primeiro definir explicitamente um tipo. O compilador gera o nome do tipo e não está disponível no nível do código-fonte. O compilador infere o tipo de cada propriedade. Use tipos anônimos principalmente quando precisar de suporte à árvore de expressão ou ao trabalhar com código que exija tipos de referência.
Tuplas versus tipos anônimos
As tuplas e os tipos anônimos permitem agrupar vários valores sem definir um tipo nomeado. No entanto, as tuplas têm melhor suporte à linguagem e são compiladas em uma estrutura de dados mais eficiente. A tabela a seguir resume as principais diferenças:
| Característica | Tipos anônimos | Tuplas |
|---|---|---|
| Tipo | Tipo de referência (class) |
Tipo de valor (struct) |
| Performance | Alocação de heap | Alocação de pilha (melhor desempenho) |
| Mutability | Propriedades somente leitura | Campos mutáveis |
| Desconstrução | Sem suporte | Suportado |
| Árvores de expressão | Suportado | Sem suporte |
| Modificador de acesso | internal |
public |
| Nomes de membro | Obrigatório ou inferido | Opcional (com nomes padrão como Item1, Item2) |
Quando usar tuplas
Use tuplas quando:
- Você precisa de um melhor desempenho por meio da alocação de pilha.
- Você deseja desconstruir valores em variáveis separadas.
- Você está retornando vários valores de um método.
- Você não precisa de suporte à árvore de expressão.
O exemplo a seguir mostra como as tuplas fornecem funcionalidade semelhante a tipos anônimos com sintaxe mais limpa:
// Tuple with named elements.
var tupleProduct = (Name: "Widget", Price: 19.99M);
Console.WriteLine($"Tuple: {tupleProduct.Name} costs ${tupleProduct.Price}");
// Equivalent example using anonymous types.
var anonymousProduct = new { Name = "Widget", Price = 19.99M };
Console.WriteLine($"Anonymous: {anonymousProduct.Name} costs ${anonymousProduct.Price}");
Desconstrução de tupla
Você pode desmembrar uma tupla em variáveis separadas, o que fornece uma maneira conveniente de trabalhar com elementos individuais de uma tupla. O C# dá suporte a várias maneiras de desconstruir tuplas:
static (string Name, int Age, string City) GetPersonInfo()
{
return ("Alice", 30, "Seattle");
}
// Deconstruct using var for all variables
var (name, age, city) = GetPersonInfo();
Console.WriteLine($"{name} is {age} years old and lives in {city}");
// Output: Alice is 30 years old and lives in Seattle
// Deconstruct with explicit types
(string personName, int personAge, string personCity) = GetPersonInfo();
Console.WriteLine($"{personName}, {personAge}, {personCity}");
// Deconstruct into existing variables
string existingName;
int existingAge;
string existingCity;
(existingName, existingAge, existingCity) = GetPersonInfo();
// Deconstruct and discard unwanted values using the discard pattern (_)
var (name2, _, city2) = GetPersonInfo();
Console.WriteLine($"{name2} lives in {city2}");
// Output: Alice lives in Seattle
A desconstrução é útil em loops e cenários de correspondência a padrões.
var people = new List<(string Name, int Age)>
{
("Bob", 25),
("Carol", 35),
("Dave", 40)
};
foreach (var (personName2, personAge2) in people)
{
Console.WriteLine($"{personName2} is {personAge2} years old");
}
Tuplas como um tipo de retorno de método
Um caso de uso comum para tuplas é como um tipo de retorno de método. Em vez de definir parâmetros, você pode agrupar out os resultados do método em uma tupla. Você não pode retornar um tipo anônimo de um método, pois ele não tem um nome e o tipo de retorno não pode ser declarado.
O exemplo a seguir demonstra como usar tuplas juntamente com buscas em dicionários para retornar intervalos de configuração.
var configLookup = new Dictionary<int, (int Min, int Max)>()
{
[2] = (4, 10),
[4] = (10, 20),
[6] = (0, 23)
};
if (configLookup.TryGetValue(4, out (int Min, int Max) range))
{
Console.WriteLine($"Found range: min is {range.Min}, max is {range.Max}");
}
// Output: Found range: min is 10, max is 20
Esse padrão é útil ao trabalhar com métodos que precisam retornar um indicador de êxito e vários valores de resultado. A tupla permite que você use campos nomeados (Min e Max) em vez de nomes genéricos como Item1 e Item2, tornando o código mais legível e autoexplicativo.
Quando usar tipos anônimos
Use tipos anônimos quando:
- Você está trabalhando com árvores de expressão (por exemplo, em alguns provedores LINQ (Microsoft Language-Integrated Query).
- Você precisa que o objeto seja um tipo de referência.
O cenário mais comum é inicializar um tipo anônimo com propriedades de outro tipo. No exemplo a seguir, suponha que existe uma classe com o nome Product. A classe Product inclui Color e Price as propriedades, juntamente com outras propriedades nas quais você não está interessado:
class Product
{
public string? Color { get; init; }
public decimal Price { get; init; }
public string? Name { get; init; }
public string? Category { get; init; }
public string? Size { get; init; }
}
A declaração de tipo anônimo começa com o new operador junto com um inicializador de objeto. A declaração inicializa um novo tipo que usa apenas duas propriedades de Product. Tipos anônimos normalmente são usados na select cláusula de uma expressão de consulta para retornar uma quantidade menor de dados. Para obter mais informações sobre consultas, consulte LINQ no C#.
Se você não especificar nomes de membro no tipo anônimo, o compilador fornecerá aos membros do tipo anônimo o mesmo nome que a propriedade usada para inicializá-los. Forneça um nome para a propriedade que está sendo inicializada com uma expressão, como mostrado no exemplo anterior.
No exemplo a seguir, os nomes das propriedades do tipo anônimo são Color e Price . As instâncias são itens da products coleção de Product tipos:
var productQuery =
from prod in products
select new { prod.Color, prod.Price };
foreach (var v in productQuery)
{
Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}
Inicializadores de projeção com tipos anônimos
Tipos anônimos dão suporte a inicializadores de projeção, que permitem usar variáveis ou parâmetros locais diretamente sem especificar explicitamente o nome do membro. O compilador infere os nomes de membro dos nomes das variáveis. O exemplo a seguir demonstra essa sintaxe simplificada:
// Explicit member names.
var personExplicit = new { FirstName = "Kyle", LastName = "Mit" };
// Projection initializers (inferred member names).
var firstName = "Kyle";
var lastName = "Mit";
var personInferred = new { firstName, lastName };
// Both create equivalent anonymous types with the same property names.
Console.WriteLine($"Explicit: {personExplicit.FirstName} {personExplicit.LastName}");
Console.WriteLine($"Inferred: {personInferred.firstName} {personInferred.lastName}");
Essa sintaxe simplificada é útil ao criar tipos anônimos com muitas propriedades:
var title = "Software Engineer";
var department = "Engineering";
var salary = 75000;
// Using projection initializers.
var employee = new { title, department, salary };
// Equivalent to explicit syntax:
// var employee = new { title = title, department = department, salary = salary };
Console.WriteLine($"Title: {employee.title}, Department: {employee.department}, Salary: {employee.salary}");
O nome do membro não é inferido nos seguintes casos:
- O nome do candidato duplica outro membro da propriedade no mesmo tipo anônimo, explícito ou implícito.
- O nome do candidato não é um identificador válido (por exemplo, ele contém espaços ou caracteres especiais).
Nesses casos, você deve especificar explicitamente o nome do membro.
Dica
Você pode usar a regra de estilo .NET IDE0037 para impor se os nomes de membros inferidos ou explícitos são preferenciais.
Você também pode definir um campo usando um objeto de outro tipo: classe, struct ou até mesmo outro tipo anônimo. Para fazer isso, use a variável que contém esse objeto. O exemplo a seguir mostra dois tipos anônimos que usam tipos já instanciados definidos pelo usuário. Em ambos os casos, o product campo nos tipos shipment anônimos e shipmentWithBonus é do tipo Product e contém os valores padrão de cada campo. O bonus campo é de um tipo anônimo criado pelo compilador.
var product = new Product();
var bonus = new { note = "You won!" };
var shipment = new { address = "Nowhere St.", product };
var shipmentWithBonus = new { address = "Somewhere St.", product, bonus };
Normalmente, ao usar um tipo anônimo para inicializar uma variável, a variável é declarada como uma variável local de tipo implícito usando var. Você não pode especificar o nome do tipo na declaração de variável porque somente o compilador tem acesso ao nome subjacente do tipo anônimo. Para obter mais informações sobre var, consulte Variáveis de local digitadas implicitamente.
Você pode criar uma matriz de elementos de tipo anônimo combinando uma variável local de tipo implícito e uma matriz de tipo implícito, como mostrado no exemplo a seguir.
var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};
Tipos anônimos são class tipos que derivam diretamente de object, e você não pode convertê-los em nenhum tipo, exceto object. O compilador fornece um nome para cada tipo anônimo, embora seu aplicativo não possa acessá-lo. Do ponto de vista do Common Language Runtime, um tipo anônimo não é diferente de qualquer outro tipo de referência.
Se dois ou mais inicializadores de objeto anônimos em um assembly especificarem uma sequência de propriedades que estão na mesma ordem e que têm os mesmos nomes e tipos, o compilador tratará os objetos como instâncias do mesmo tipo. Eles compartilham o mesmo tipo de informação gerado pelo compilador.
Tipos anônimos dão suporte a mutações não destrutivas na forma de expressões with. Esse recurso permite que você crie uma nova instância de um tipo anônimo em que uma ou mais propriedades tenham novos valores:
var apple = new { Item = "apples", Price = 1.35 };
var onSale = apple with { Price = 0.79 };
Console.WriteLine(apple);
Console.WriteLine(onSale);
Você não pode declarar um campo, uma propriedade, um evento ou o tipo de retorno de um método como tendo um tipo anônimo. Da mesma forma, você não pode declarar um parâmetro formal de um método, propriedade, construtor ou indexador como tendo um tipo anônimo. Para passar um tipo anônimo ou uma coleção que contenha tipos anônimos como um argumento para um método, você pode declarar o parâmetro como tipo object. No entanto, usar object para tipos anônimos anula o propósito da tipagem forte. Se você precisa armazenar os resultados da consulta ou passá-los fora do limite do método, considere o uso de uma estrutura ou classe com denominação comum em vez de um tipo anônimo.
Como os métodos Equals e GetHashCode em tipos anônimos são definidos em termos dos métodos das propriedades Equals e GetHashCode, duas instâncias do mesmo tipo anônimo são iguais somente se todas as suas propriedades forem iguais.
Observação
O nível de acessibilidade de um tipo anônimo é internal. Portanto, dois tipos anônimos definidos em assemblies diferentes não são do mesmo tipo.
Portanto, instâncias de tipos anônimos não podem ser iguais umas às outras quando definidas em assemblies diferentes, mesmo quando todas as suas propriedades são iguais.
Tipos anônimos substituem o método ToString, concatenando o nome e a saída ToString de cada propriedade cercada por chaves.
var v = new { Title = "Hello", Age = 24 };
Console.WriteLine(v.ToString()); // "{ Title = Hello, Age = 24 }"