Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
As tuplas fornecem uma estrutura de dados leve para múltiplos membros numa única estrutura. São a escolha preferida em relação aos tipos anónimos. As tuplas oferecem melhor desempenho, suportam a desconstrução e oferecem uma sintaxe mais flexível.
Os tipos anônimos fornecem uma maneira conveniente de encapsular um conjunto de propriedades somente leitura em um único objeto sem ter que definir explicitamente um tipo primeiro. O compilador gera o nome do tipo, e este não está disponível ao nível do código-fonte. O compilador infere o tipo de cada propriedade. Use tipos anónimos principalmente quando precisa de suporte para árvores de expressão ou ao trabalhar com código que requer tipos de referência.
Tuplas vs tipos anónimos
Tanto as tuplas como os tipos anónimos permitem agrupar múltiplos valores sem definir um tipo nomeado. No entanto, as tuplas têm melhor suporte à linguagem e compilam para 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 | Não suportado | Suportado |
| Árvores de expressão | Suportado | Não suportado |
| Modificador de acesso | internal |
public |
| Nomes dos membros | Obrigatório ou deduzido | Opcional (com nomes padrão como Item1, Item2) |
Quando usar tuplas
Use tuplas quando:
- Precisa de um melhor desempenho através da alocação de stack.
- Queres desconstruir valores em variáveis separadas.
- Estás a devolver múltiplos valores de um método.
- Não precisas de suporte de árvore de expressões.
O exemplo seguinte mostra como tuplas oferecem funcionalidades semelhantes aos tipos anónimos, mas com uma 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 tuplas
Pode desconstruir uma tupla em variáveis separadas, o que proporciona uma forma conveniente de trabalhar com os elementos individuais da tupla. C# suporta várias formas 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 cenários de loops e correspondência de 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 tipo de retorno de método
Um caso de uso comum para tuplas é como tipo de retorno de método. Em vez de definir out parâmetros, pode agrupar os resultados do método numa tupla. Não pode devolver um tipo anónimo de um método, porque não tem nome, e o tipo de devolução não pode ser declarado.
O exemplo seguinte demonstra o uso de tuplas com consultas de dicionário para devolver 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
Este padrão é útil quando se trabalha com métodos que precisam de devolver tanto um indicador de sucesso como múltiplos valores de resultado. A tupla permite usar campos nomeados (Min e Max) em vez de nomes genéricos como Item1 e Item2, tornando o código mais legível e auto-documentável.
Quando usar tipos anónimos
Use tipos anónimos quando:
- Está a trabalhar com árvores de expressões (por exemplo, em alguns fornecedores de Microsoft Language-Integrated Query, ou LINQ).
- Precisas 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 chamada Product. Classe Product inclui Color e Price propriedades, juntamente com outras propriedades que você não está interessado em:
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 anónima do tipo começa com o new operador juntamente com um inicializador de objetos. A declaração inicializa um novo tipo que usa apenas duas propriedades de Product. Tipos anónimos são normalmente usados na select cláusula de uma expressão de consulta para devolver uma quantidade menor de dados. Para obter mais informações sobre consultas, consulte LINQ em C#.
Se não especificar nomes de membros no tipo anónimo, o compilador dá aos membros do tipo anónimo o mesmo nome da propriedade usada para os inicializar. Você fornece um nome para uma propriedade que está sendo inicializada com uma expressão, conforme 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 suportam 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 dos membros a partir 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 do atributo no mesmo tipo anónimo, quer explícito, quer implícito.
- O nome do candidato não é um identificador válido (por exemplo, contém espaços ou caracteres especiais).
Nesses casos, você deve especificar explicitamente o nome do membro.
Gorjeta
Você pode usar a regra de estilo .NET IDE0037 para impor se nomes de membros inferidos ou explícitos são preferidos.
Também pode definir um campo usando um objeto de outro tipo: classe, struct ou até outro tipo anónimo. Para isso, use a variável que contém este objeto. O exemplo seguinte mostra dois tipos anónimos que utilizam tipos já instanciados definidos pelo utilizador. 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, quando você usa um tipo anônimo para inicializar uma variável, você declara a variável como uma variável local digitada implicitamente usando var. Não podes especificar o nome do tipo na declaração da variável porque só o compilador tem acesso ao nome subjacente do tipo anónimo. Para obter mais informações sobre varo , consulte Variáveis locais digitadas implicitamente.
Você pode criar uma matriz de elementos digitados anonimamente combinando uma variável local digitada implicitamente e uma matriz digitada implicitamente, conforme 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 não podes convertê-los para 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 trata os objetos como instâncias do mesmo tipo. Eles compartilham as mesmas informações de tipo geradas pelo compilador.
Os tipos anónimos suportam mutações não destrutivas sob a forma de com expressões. Esta funcionalidade permite-lhe criar uma nova instância do tipo anónimo onde uma ou mais propriedades têm novos valores:
var apple = new { Item = "apples", Price = 1.35 };
var onSale = apple with { Price = 0.79 };
Console.WriteLine(apple);
Console.WriteLine(onSale);
Não é possível 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 contém tipos anônimos, como um argumento para um método, você pode declarar o parâmetro como tipo object. No entanto, o uso object para tipos anônimos derrota o propósito da digitação forte. Se você precisar armazenar os resultados da consulta ou passá-los para fora do limite do método, considere usar uma struct ou classe nomeada comum em vez de um tipo anônimo.
Como os Equals métodos e GetHashCode em tipos anônimos são definidos em termos de e EqualsGetHashCode métodos das propriedades, duas instâncias do mesmo tipo anônimo são iguais somente se todas as suas propriedades forem iguais.
Nota
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 entre si quando definidas em assemblies diferentes, mesmo quando todas as suas propriedades são iguais.
Os tipos anônimos substituem o ToString método, concatenando o nome e ToString a saída de cada propriedade cercada por chaves encaracoladas.
var v = new { Title = "Hello", Age = 24 };
Console.WriteLine(v.ToString()); // "{ Title = Hello, Age = 24 }"