1. Introducción
Programar en C# sin propiedades es posible, pero es como ir en patines por la Casa Blanca — se puede, pero es incómodo. Ya nos hemos acostumbrado a la sintaxis corta sin getters y setters extra, pero cuando nuestra app empieza a trabajar con modelos “serios” (por ejemplo, una clase User que describe un usuario en el sistema), se vuelve importante garantizar que todos los datos necesarios realmente estén presentes.
Por ejemplo, si tenemos una clase:
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
Es fácil olvidarse de inicializar las propiedades:
User user = new User(); // Name será null, Age = 0
Como resultado, en algún momento después de diez pantallas y cien vueltas de lógica, te vas a topar con el famoso NullReferenceException y vas a estar buscando al culpable un buen rato.
Recordemos las propiedades solo-init
Sí, podemos usar inicialización solo al crear:
public string Name { get; init; }
Pero incluso con esta sintaxis, nadie obliga al usuario de la clase a pasar un valor si no quiere — la mayoría de los constructores por defecto inicializan los campos con valores por defecto (null, 0, etc.).
Entonces, ¿cómo obligar al programador, o incluso a ti mismo, a no olvidar poner el valor necesario? Justo para eso se inventó el modificador required, que apareció en C# 11.
2. Propiedades required: inicialización estricta
El modificador required le dice al compilador que debe asegurarse de que una propiedad específica del objeto esté explícitamente establecida al momento de su creación. O sea, si esa propiedad no está inicializada al crear el objeto — el compilador no te deja pasar, y el IDE te pinta una ola roja bien fea.
public class User
{
public required string Name { get; set; }
public int Age { get; set; }
}
Probemos crear un usuario:
// Error de compilación: la propiedad Name es obligatoria para inicializar!
User user1 = new User();
O así:
// Error de compilación: no se indicó la propiedad required Name
User user2 = new User { Age = 18 };
Y aquí la forma correcta:
User user3 = new User { Name = "Hermione", Age = 18 };
¿Cómo funciona required?
El modificador required le dice al compilador: “Después de terminar el constructor del objeto (¡cualquiera!), esta propiedad debe estar explícitamente establecida”.
Esto funciona tanto para propiedades normales como para solo-init:
public required string Name { get; init; }
Si defines un constructor personalizado que inicializa el valor de la propiedad required, la regla también se cumple.
Esquema típico de comprobación
| Escenario | ¿Compilador contento? |
|---|---|
| No se indicó la propiedad required | ❌ |
| Se indicó en el inicializador de objeto | ✔️ |
| Se indicó en el constructor | ✔️ |
Ejemplo en nuestro modelo “Perro”
public class Dog
{
public required string Name { get; set; }
public int Age { get; set; }
}
Dog dog = new Dog { Name = "Fido", Age = 5 }; // ¡Todo ok!
Dog badDog = new Dog { Age = 2 }; // ¡Error! No se indicó Name
¿Dónde ayuda realmente required?
- Transferencia de DTO entre capas: Si tienes un API, necesitas que siempre lleguen todos los campos necesarios.
- Modelos complejos con atributos obligatorios: Por ejemplo, Product con SKU obligatorio, Order con número obligatorio.
- En entrevistas y code review: Si muestras esta sintaxis, probablemente tu código será leído con respeto (y un poco de envidia).
3. ¿Cómo funciona required con constructores?
A veces escribes el constructor explícitamente. ¿Qué pasa si no inicializas la propiedad required en el constructor o en el inicializador de objeto? El compilador te da error.
public class Article
{
public required string Title { get; set; }
public required string Author { get; set; }
public Article()
{
// Si no inicializas Title y Author — error de compilación!
// Puedes hacer así:
Title = "Sin título";
Author = "Desconocido";
}
}
Si el constructor asigna valores a las propiedades required — todo bien. Si no, tienes que inicializar esos campos usando el inicializador de objeto (new Article { ... }).
Especificidades de uso
- required solo funciona con propiedades, no con campos.
- required no se hereda — si la propiedad base es required, pero en la hija no pones required, el compilador no se queja (pero es buena práctica repetir required en la hija).
- required no se puede aplicar a campos automáticos ni a nada que no sea property.
4. Sintaxis de flecha para propiedades
Con las nuevas versiones de C#, los devs buscan cada vez más código conciso y expresivo. Una de las novedades más notables para propiedades es la sintaxis de flecha (o expression-bodied properties).
A veces quieres definir una propiedad que solo devuelve un valor sin lógica extra. Antes había que escribir un getter completo con llaves:
public int Age
{
get { return birthYear > 0 ? DateTime.Now.Year - birthYear : 0; }
}
Ahora puedes escribirlo mucho más corto — usando la flecha (=>):
public int Age => birthYear > 0 ? DateTime.Now.Year - birthYear : 0;
Esta sintaxis se llama propiedad con cuerpo de expresión (expression-bodied property). Es perfecta para cálculos simples y hace el código más compacto.
Ejemplo usando get y set
public class Book
{
private string _title;
public string Title
{
get => _title;
set => _title = value.Trim();
}
}
Aquí get devuelve el valor del campo, y set — asigna, quitando los espacios de los lados.
Ejemplo usando solo get (solo lectura):
Si la propiedad es solo de lectura — puedes escribirla sin llaves, solo con =>.
public class Person
{
private string name = "Mark Twain";
// Solo lectura: propiedad calculada
public string Name => name.ToUpper();
}
Aquí la propiedad Name solo se puede leer — siempre devuelve name en mayúsculas.
5. Palabra clave field para propiedades
Hasta C# 14, si querías acceder al campo oculto de una propiedad automática directamente en el setter o getter (por ejemplo, para evitar recursión o añadir tu lógica) — no se podía. Tenías que declarar el campo explícitamente.
C# 14 permite acceder al campo oculto de una propiedad automática usando la palabra clave field:
public class Person
{
public string Name
{
get => field; // field — es el campo oculto de la propiedad Name
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("¡El nombre no puede estar vacío!");
field = value; // usamos field en vez de un _name explícito
}
}
}
Antes había que hacerlo así:
private string _name;
public string Name
{
get => _name;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("¡El nombre no puede estar vacío!");
_name = value;
}
}
Ahora es una variable menos que declarar. Cuanto más compacto el código, mejor.
¿Por qué a veces es importante acceder al campo dentro de la propiedad?
- A veces necesitas controlar explícitamente dónde y cómo se guarda el valor (por ejemplo, si quieres devolver una copia del objeto, cachear el resultado o hacer lazy-loading).
- Si quieres usar atributos o reflection — puede que necesites el campo por nombre.
- En algunos casos de (de)serialización o performance-tuning quieres tener más control sobre el almacenamiento del valor.
Opciones de uso:
Validación de datos en el setter
public double Grade
{
get => field;
set
{
if (value < 0 || value > 5)
throw new ArgumentOutOfRangeException("La nota debe ser de 0 a 5");
field = value;
}
}
Estadística de cambios de valor
public int StepCount
{
get => field;
set
{
if (value > field)
{
Console.WriteLine($"¡Genial! ¡Has hecho {value - field} pasos más!");
}
field = value;
}
}
Lazy Load
public string Data
{
get
{
if (field == null)
field = LoadDataFromDatabase();
return field;
}
set => field = value;
}
6. Errores típicos y matices
Error nº1: olvidaste inicializar la propiedad required.
El compilador no te deja pasar con ese código y da error enseguida al compilar, lo que ayuda a evitar problemas en tiempo de ejecución.
Error nº2: las propiedades required no funcionan sin inicialización completa en el constructor.
Si el constructor no recibe valores para todas las propiedades obligatorias, el compilador te recuerda que te falta algo.
Error nº3: intentar usar required con const o readonly.
Estos modificadores no son compatibles — required solo se puede usar con propiedades normales. Si intentas combinarlos, te dará error.
GO TO FULL VERSION