Compartir a través de


Tuplas y tipos anónimos

Las tuplas proporcionan una estructura de datos ligera para varios miembros en una sola estructura. Son la opción preferida sobre los tipos anónimos. Las tuplas proporcionan un mejor rendimiento, admiten la deconstrucción y ofrecen una sintaxis más flexible.

Los tipos anónimos son una manera cómoda de encapsular un conjunto de propiedades de solo lectura en un único objeto sin tener que definir primero un tipo explícitamente. El compilador genera el nombre de tipo y no está disponible en el nivel de código fuente. El compilador deduce el tipo de cada propiedad. Use tipos anónimos principalmente cuando necesite compatibilidad con árboles de expresiones o cuando trabaje con código que requiera tipos de referencia.

Tuplas versus tipos anónimos

Tanto las tuplas como los tipos anónimos permiten agrupar varios valores sin definir un tipo con nombre. Sin embargo, las tuplas tienen un mejor soporte de lenguaje y se compilan en una estructura de datos más eficaz. En la tabla siguiente se resumen las diferencias clave:

Característica Tipos anónimos Tuplas
Tipo Tipo de referencia (class) Tipo de valor (struct)
Performance Asignación del heap Asignación de pila (mejor rendimiento)
Mutability Propiedades de solo lectura Campos mutables
Deconstrucción No está soportado Compatible
Árboles de expresión Compatible No está soportado
Modificador de acceso internal public
Nombres de miembro Requerido o inferido Opcional (con nombres predeterminados como Item1, Item2)

Cuándo usar tuplas

Use tuplas cuando:

  • Necesita un mejor rendimiento mediante la asignación en pila.
  • Quiere descomponer valores en variables separadas.
  • Va a devolver varios valores de un método.
  • No necesita soporte para árboles de expresiones.

En el ejemplo siguiente se muestra cómo las tuplas proporcionan una funcionalidad similar a los tipos anónimos con una sintaxis más clara.

// 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}");

Deconstrucción de tupla

Puede deconstruir una tupla en variables independientes, lo que proporciona una manera cómoda de trabajar con elementos individuales de una tupla. C# admite varias maneras de deconstruir 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

La deconstrucción es útil en bucles y escenarios de coincidencia de patrones:

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 resultado de un método

Un caso de uso común para las tuplas es como tipo de retorno de método. En lugar de definir out parámetros, puede agrupar los resultados del método en una tupla. No se puede devolver un tipo anónimo desde un método, ya que no tiene un nombre y no se puede declarar el tipo de valor devuelto.

El ejemplo siguiente demuestra el uso de tuplas en consultas a diccionarios para devolver rangos de configuración.

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 patrón es útil cuando se trabaja con métodos que necesitan devolver un indicador de éxito y varios valores de resultado. La tupla permite usar campos con nombre (Min y Max) en lugar de nombres genéricos como Item1 y Item2, lo que hace que el código sea más legible y autodocumentado.

Cuándo usar tipos anónimos

Use tipos anónimos cuando:

  • Está trabajando con árboles de expresión (por ejemplo, en algunos proveedores de Microsoft Language-Integrated Query (LINQ).
  • Necesita que el objeto sea un tipo de referencia.

El escenario más habitual es inicializar un tipo anónimo con propiedades de otro tipo. En el siguiente ejemplo, se da por hecho que existe una clase con el nombre Product. La clase Product incluye Color y Price propiedades, junto con otras propiedades que no le interesan:

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; }
}

La declaración de tipo anónimo comienza con el new operador junto con un inicializador de objeto. La declaración inicializa un nuevo tipo que solo usa dos propiedades de Product. Normalmente, los tipos anónimos se usan en la select cláusula de una expresión de consulta para devolver una cantidad menor de datos. Para más información sobre las consultas, vea LINQ en C#.

Si no especifica nombres de miembro en el tipo anónimo, el compilador proporciona a los miembros de tipo anónimo el mismo nombre que la propiedad usada para inicializarlos. Debe proporcionar un nombre para una propiedad que se está inicializando con una expresión, como se muestra en el ejemplo anterior.

En el siguiente ejemplo, los nombres de las propiedades del tipo anónimo son Color y Price. Las instancias son elementos de la products colección 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 proyección con tipos anónimos

Los tipos anónimos admiten inicializadores de proyección, que permiten usar variables locales o parámetros directamente sin especificar explícitamente el nombre del miembro. El compilador deduce los nombres de miembro de los nombres de variable. En el ejemplo siguiente se muestra esta sintaxis 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}");

Esta sintaxis simplificada es útil al crear tipos anónimos con muchas propiedades:

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}");

El nombre del miembro no se deduce en los casos siguientes:

  • El nombre del candidato duplica otro miembro de propiedad en el mismo tipo anónimo, ya sea explícito o implícito.
  • El nombre del candidato no es un identificador válido (por ejemplo, contiene espacios o caracteres especiales).

En estos casos, debe especificar explícitamente el nombre del miembro.

Sugerencia

Puede usar la regla de estilo de .NET IDE0037 para aplicar si se prefieren los nombres de miembros inferidos o explícitos.

También puede definir un campo mediante un objeto de otro tipo: clase, estructura o incluso otro tipo anónimo. Para ello, use la variable que contiene este objeto. En el ejemplo siguiente se muestran dos tipos anónimos que usan tipos definidos por el usuario ya instanciados. En ambos casos, el product campo de los tipos anónimos shipment y shipmentWithBonus es de tipo Product y contiene los valores predeterminados de cada campo. El bonus campo es de un tipo anónimo creado por el 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, cuando se usa un tipo anónimo para inicializar una variable, la variable se declara como variable local con tipo implícito mediante var. No se puede especificar el nombre de tipo en la declaración de variable porque solo el compilador tiene acceso al nombre subyacente del tipo anónimo. Para obtener más información sobre var, vea Variables locales con asignación implícita de tipos.

Puede crear una matriz de elementos con tipo anónimo combinando una variable local con tipo implícito y una matriz con tipo implícito, como se muestra en el ejemplo siguiente.

var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};

Los tipos anónimos son class tipos que derivan directamente de objecty no se pueden convertir a ningún tipo excepto object. El compilador proporciona un nombre para cada tipo anónimo, aunque la aplicación no puede acceder a él. Desde el punto de vista de Common Language Runtime, un tipo anónimo no es diferente de otros tipos de referencia.

Si dos o más inicializadores de objeto anónimo en un ensamblado especifican una secuencia de propiedades que están en el mismo orden y que tienen los mismos nombres y tipos, el compilador trata el objeto como instancias del mismo tipo. Comparten la misma información de tipo generada por el compilador.

Los tipos anónimos admiten la mutación no destructiva en forma de con expresiones. Esta característica permite crear una nueva instancia de un tipo anónimo donde una o varias propiedades tienen nuevos valores:

var apple = new { Item = "apples", Price = 1.35 };
var onSale = apple with { Price = 0.79 };
Console.WriteLine(apple);
Console.WriteLine(onSale);

No se puede declarar un campo, una propiedad, un evento o el tipo de valor devuelto de un método como tener un tipo anónimo. Del mismo modo, no se puede declarar un parámetro formal de un método, propiedad, constructor o indexador como tener un tipo anónimo. Para pasar un tipo anónimo, o una colección que contiene tipos anónimos, como un argumento a un método, puede declarar el parámetro como object de tipo. Sin embargo, el uso de object para tipos anónimos anula el propósito de la coincidencia segura. Si tiene que almacenar resultados de consulta o pasarlos fuera del límite del método, considere la posibilidad de usar un struct o una clase con nombre normal en lugar de un tipo anónimo.

Como los métodos Equals y GetHashCode de tipos anónimos se definen en términos de los métodos Equals y GetHashCode de las propiedades, dos instancias del mismo tipo anónimo son iguales solo si todas sus propiedades son iguales.

Nota:

El nivel de accesibilidad de un tipo anónimo es internal. Por lo tanto, dos tipos anónimos definidos en ensamblados diferentes no son del mismo tipo. Por lo tanto, las instancias de tipos anónimos no pueden ser iguales entre sí cuando se definen en ensamblados diferentes, incluso cuando tienen todas sus propiedades iguales.

Los tipos anónimos invalidan el método ToString, concatenando el nombre y la salida ToString de cada propiedad rodeada de llaves.

var v = new { Title = "Hello", Age = 24 };

Console.WriteLine(v.ToString()); // "{ Title = Hello, Age = 24 }"

Consulte también