Tipos de registro de C#

Sugerencia

¿No está familiarizado con el desarrollo de software? Comience primero con los tutoriales de introducción . Encontrará registros cuando necesite tipos de datos concisos con igualdad integrada.

¿Competente en otro idioma? Los registros de C# son similares a las clases de datos en Kotlin o las clases de casos de Scala. Son tipos optimizados para almacenar datos, con igualdad generada por el compilador, ToStringy semántica de copia. Revise las secciones de record class frente a record struct y with expresiones para patrones específicos de C#.

La record palabra clave es un modificador que se aplica a un class o a un struct. Indica al compilador que genere igualdad de valor, un ToString con formato, y una mutación no destructiva a través de expresiones with. El tipo subyacente, una clase o una estructura, todavía determina si las instancias usan la semántica de referencia o valor. El record modificador agrega un comportamiento compatible con los datos sobre esa semántica. Use registros cuando el rol principal de un tipo sea almacenar datos y dos instancias con los mismos valores deberían considerarse iguales.

Cuándo usar registros

Use un registro cuando se cumplan todas las condiciones siguientes:

  • El rol principal del tipo es almacenar datos.
  • Dos instancias con los mismos valores deben ser iguales.
  • Usted desea inmutabilidad (especialmente para tipos de record class).
  • Quieres que ToString pueda leerse sin tener que escribirlo manualmente.

Al elegir entre record class y record struct:

  • Use record class cuando necesite herencia o cuando el tipo sea lo suficientemente grande como para que la copia en cada asignación sea costosa.
  • Se usa record struct para valores pequeños y independientes en los que la semántica de copia y la asignación de pila son beneficiosas.

Evite registros para los tipos de entidad en Entity Framework Core, que depende de la igualdad de referencia para realizar un seguimiento de las entidades. Para obtener una comparación más amplia de las opciones de tipo, vea Elegir qué tipo de tipo.

Declarar un registro

Puede aplicar record a una clase o a una estructura. El formulario más sencillo usa parámetros posicionales que definen el constructor y las propiedades en una sola línea:

public record Person(string FirstName, string LastName);

La misma sintaxis posicional funciona para los tipos de record struct.

public record struct Coordinate(double Latitude, double Longitude);

public readonly record struct Temperature(double Celsius)
{
    public double Fahrenheit => Celsius * 9.0 / 5.0 + 32.0;
}

La escritura record por sí sola es una abreviatura de record class que es un tipo de referencia. La escritura record struct crea un tipo de valor. El compilador genera propiedades a partir de los parámetros posicionales en ambos casos, pero los valores predeterminados difieren:

  • record class: las propiedades son init-only (inmutables después de la construcción).
  • record struct: Las propiedades son de lectura y escritura por defecto. Agregue readonly (readonly record struct) para hacerlos solo init.

También puede escribir registros con sintaxis de propiedad estándar cuando necesite más control. Por ejemplo, para hacer que una propiedad sea de lectura y escritura en lugar de init-only:

public record Product
{
    public required string Name { get; init; }
    public decimal Price { get; set; }
}

record class frente a record struct

Dado que el modificador record conserva la semántica del tipo subyacente, record class y record struct se comportan de forma diferente al asignar o comparar referencias. Asignar una record class copia la referencia. Ambas variables apuntan al mismo objeto. Asignar una record struct copia de los datos significa que los cambios en una variable no afectan a la otra:

// Record class — assignment copies the reference
var p1 = new Person("Grace", "Hopper");
var p2 = p1; // p1 and p2 point to the same object:
Console.WriteLine(ReferenceEquals(p1, p2)); // True

// Record struct — assignment copies the data
var c1 = new Coordinate(47.6062, -122.3321);
var c2 = c1;
c2.Longitude = 0.0; // mutating c2 doesn't affect c1
Console.WriteLine(c1.Longitude); // -122.3321
Console.WriteLine(c2.Longitude); // 0

Los structs de registro también proporcionan igualdad de valores generados por el compilador:

var home = new Coordinate(47.6062, -122.3321);
var copy = home;

Console.WriteLine(home);           // Coordinate { Latitude = 47.6062, Longitude = -122.3321 }
Console.WriteLine(home == copy);   // True — value equality

Elija record class cuándo necesita herencia o cuando las instancias sean lo suficientemente grandes como para que la copia sea costosa. Elija record struct para datos pequeños y independientes en los que la semántica de copia de tipo de valor sea adecuada. Para más información sobre la semántica de tipos de valor, consulte Structs.

Igualdad de valores

El record modificador proporciona igualdad entre clases y estructuras generadas por el compilador, propiedad por propiedad. Así es como funciona la igualdad en los cuatro tipos de tipos:

  • Clase sin formato: usa la igualdad de referencia de forma predeterminada. El == operador comprueba si dos variables apuntan al mismo objeto, no si los datos coinciden.
  • Estructura en bruto: soporta la igualdad de valores a través de ValueType.Equals, pero la implementación predeterminada emplea reflexión, lo cual es más lento y no genera operadores ==/!=.
  • record class: el compilador genera Equals métodos y GetHashCode y ==/!= operadores que comparan todos los valores de propiedad. Dos objetos distintos con los mismos datos son iguales.
  • record struct: la misma igualdad generada por el compilador que record class, pero sin usar reflexion, lo que hace que sea más rápida que la igualdad de estructura simple.

En el ejemplo siguiente se muestra la igualdad de la clase de registro:

// Person is a record type with three properties: FirstName, LastName, and PhoneNumbers.
var phones = new string[] { "555-1234" };
var person1 = new Person("Grace", "Hopper", phones);
var person2 = new Person("Grace", "Hopper", phones);

Console.WriteLine(person1 == person2);              // True
Console.WriteLine(ReferenceEquals(person1, person2)); // False

person1.PhoneNumbers[0] = "555-9999";
Console.WriteLine(person2.PhoneNumbers[0]); // 555-9999 — same array

Las dos Person instancias son objetos diferentes, pero son iguales porque todos sus valores de propiedad coinciden. Las propiedades de matriz se comparan por referencia, no por contenido. La mutación de la matriz compartida es visible a través de ambos registros porque la propia matriz no se copia.

Mutación no destructiva con expresiones with

Los registros suelen ser inmutables, por lo que no se puede cambiar una propiedad después de la creación. Una with expresión crea una copia con una o varias propiedades modificadas, dejando sin cambios el registro original. Este enfoque funciona para tipos de record class y record struct.

var original = new Person("Grace", "Hopper");
var modified = original with { FirstName = "Margaret" };

Console.WriteLine(original); // Person { FirstName = Grace, LastName = Hopper }
Console.WriteLine(modified); // Person { FirstName = Margaret, LastName = Hopper }
Console.WriteLine(original == modified); // False

var copy = original with { };
Console.WriteLine(original == copy); // True

La misma sintaxis funciona para record struct tipos:

var shifted = home with { Longitude = -122.0 };
Console.WriteLine(shifted);        // Coordinate { Latitude = 47.6062, Longitude = -122 }
Console.WriteLine(home == shifted); // False

Una with expresión copia la instancia existente y, a continuación, aplica los cambios de propiedad especificados.

Registros posicionales y deconstrucción

Los registros posicionales generan un Deconstruct método que se usa para extraer valores de propiedad en variables individuales:

var (first, last) = person;
Console.WriteLine($"{first} {last}");
// Grace Hopper

La deconstrucción funciona tanto con record class como con record struct tipos. Puede usarlo en asignaciones, foreach bucles y coincidencias de patrones.

Herencia de registros

Un record class puede heredar de otro record class. Un registro no puede heredar de una clase normal y una clase no puede heredar de un registro:

public record Student(string FirstName, string LastName, int GradeLevel)
    : Person(FirstName, LastName);

Las comprobaciones de igualdad de valores incluyen el tipo en tiempo de ejecución, por lo que un Person y un Student con el mismo FirstName y LastName no se consideran iguales. Las estructuras de registro no admiten la herencia porque las estructuras no pueden heredar de otros tipos.

Consulte también