Propiedades (Guía de programación de C#)
Una propiedad es un miembro que proporciona un mecanismo flexible para leer, escribir o calcular el valor de un campo de datos. Las propiedades aparecen como miembros de datos públicos, pero se implementan como métodos especiales llamados descriptores de acceso. Esta característica permite a las personas que llaman acceder fácilmente a los datos y, al mismo tiempo, contribuye a promover la seguridad y la flexibilidad de los datos. La sintaxis para propiedades es una extensión natural de los campos. Un campo define una ubicación de almacenamiento:
public class Person
{
public string? FirstName;
// Omitted for brevity.
}
Propiedades implementadas automáticamente
Una definición de propiedad contiene las declaraciones para un descriptor de acceso get
y set
que recupera y asigna el valor de esa propiedad:
public class Person
{
public string? FirstName { get; set; }
// Omitted for brevity.
}
En el ejemplo anterior se muestra una propiedad implementada automáticamente. El compilador genera un campo de respaldo oculto para la propiedad. El compilador también implementa el cuerpo de los descriptores de acceso get
y set
. Los atributos se aplican a la propiedad implementada automáticamente. Para aplicar el atributo al campo de respaldo generado por el compilador, puede especificar la etiqueta field:
en el atributo.
Para inicializar una propiedad con un valor distinto del predeterminado, puede establecer un valor después del corchete de cierre de la propiedad. Puede que prefiera que el valor inicial para la propiedad FirstName
sea la cadena vacía en lugar de null
. Debe especificarlo como se muestra en el código siguiente:
public class Person
{
public string FirstName { get; set; } = string.Empty;
// Omitted for brevity.
}
Control de acceso
En los ejemplos anteriores se muestran propiedades de lectura y escritura. También se pueden crear propiedades de solo lectura, o proporcionar accesibilidad diferente a los descriptores de acceso set y get. Suponga que su clase Person
solo debe habilitar el cambio del valor de la propiedad FirstName
desde otros métodos de esa clase. Podría asignar al descriptor de acceso set la accesibilidad private
en lugar de public
:
public class Person
{
public string? FirstName { get; private set; }
// Omitted for brevity.
}
La propiedad FirstName
se puede leer desde cualquier código, pero solo puede asignarse desde otro código de la clase Person
.
Puede agregar cualquier modificador de acceso restrictivo al descriptor de acceso set o get. Un modificador de acceso en un descriptor de acceso individual debe ser más restrictivo que el acceso de la propiedad. El código anterior es válido porque la propiedad FirstName
es public
, pero el descriptor de acceso set es private
. No se puede declarar una propiedad private
con un descriptor de acceso public
. Las declaraciones de propiedad también se pueden declarar como protected
, internal
, protected internal
o incluso private
.
Hay dos modificadores de acceso especiales para los descriptores de acceso set
:
- Un descriptor de acceso
set
puede tenerinit
como modificador de acceso. Ese descriptor de accesoset
solo se puede llamar desde un inicializador de objeto o los constructores del tipo. Es más restrictivo queprivate
en el descriptor de accesoset
. - Una propiedad implementada automáticamente puede declarar un
get
descriptor de acceso sin unset
descriptor de acceso. En ese caso, el compilador permite llamar al descriptor de accesoset
solo desde los constructores del tipo. Es más restrictivo que el descriptor de accesoinit
en el descriptor de accesoset
.
Modifique la clase Person
de la manera siguiente:
public class Person
{
public Person(string firstName) => FirstName = firstName;
public string FirstName { get; }
// Omitted for brevity.
}
En el ejemplo anterior se requieren llamadas para usar el constructor que incluye el parámetro FirstName
. Los autores de llamadas no pueden usar inicializadores de objetos para asignar un valor a la propiedad. Para admitir inicializadores, puede convertir el descriptor de acceso set
en un descriptor de acceso init
, como se muestra en el código siguiente:
public class Person
{
public Person() { }
public Person(string firstName) => FirstName = firstName;
public string? FirstName { get; init; }
// Omitted for brevity.
}
Estos modificadores se suelen usar con el modificador required
para forzar la inicialización adecuada.
Propiedades necesarias
En el ejemplo anterior se permite a un autor de la llamada crear un Person
mediante el constructor predeterminado, sin establecer la propiedad FirstName
. La propiedad cambió el tipo a una cadena que acepta valores NULL. A partir de C# 11, puede requerir que los autores de las llamadas establezcan una propiedad:
public class Person
{
public Person() { }
[SetsRequiredMembers]
public Person(string firstName) => FirstName = firstName;
public required string FirstName { get; init; }
// Omitted for brevity.
}
El código anterior realiza dos cambios en la clase Person
. En primer lugar, la declaración de la propiedad FirstName
incluye el modificador required
. Esto significa que cualquier código que cree un nuevo Person
debe establecer esta propiedad mediante un inicializador de objeto. En segundo lugar, el constructor que toma un parámetro firstName
tiene el atributo System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute. Este atributo informa al compilador de que este constructor establece todos los miembros de required
. Los autores de llamadas que usan este constructor no son necesarios para establecer propiedades required
con un inicializador de objeto.
Importante
No confunda required
con que no acepta valores NULL. Es válido establecer una propiedad required
en null
o default
. Si el tipo no acepta valores NULL, como string
en estos ejemplos, el compilador emite una advertencia.
var aPerson = new Person("John");
aPerson = new Person{ FirstName = "John"};
// Error CS9035: Required member `Person.FirstName` must be set:
//aPerson2 = new Person();
Definiciones de cuerpos de expresión
Los descriptores de acceso de propiedad suelen constar de instrucciones de una sola línea. Los descriptores de acceso asignan o devuelven el resultado de una expresión. Puede implementar estas propiedades como miembros con forma de expresión. Las definiciones de cuerpos de expresión constan del token =>
seguido de la expresión que se va a asignar a la propiedad o a recuperar de ella.
Las propiedades de solo lectura pueden implementar el descriptor de acceso get
como miembro con forma de expresión. En el ejemplo siguiente se implementa la propiedad de solo lectura Name
como miembro con forma de expresión.
public class Person
{
public Person() { }
[SetsRequiredMembers]
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public required string FirstName { get; init; }
public required string LastName { get; init; }
public string Name => $"{FirstName} {LastName}";
// Omitted for brevity.
}
La propiedad Name
es una propiedad calculada. No hay ningún campo de respaldo para Name
. La propiedad la calcula cada vez.
Propiedades con campos de respaldo
Se puede combinar el concepto de una propiedad calculada con un campo privado y crear una propiedad de evaluación en caché. Por ejemplo, actualice la propiedad FullName
para que el formato de cadena se produzca en el primer acceso:
public class Person
{
public Person() { }
[SetsRequiredMembers]
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public required string FirstName { get; init; }
public required string LastName { get; init; }
private string? _fullName;
public string FullName
{
get
{
if (_fullName is null)
_fullName = $"{FirstName} {LastName}";
return _fullName;
}
}
}
Esta implementación funciona porque las propiedades FirstName
y LastName
son de solo lectura. Las personas pueden cambiar su nombre. La actualización de las propiedades FirstName
y LastName
para permitir a los descriptores de acceso set
requiere que invalide cualquier valor almacenado en caché para fullName
. Hay que modificar los descriptores de acceso set
de la propiedad FirstName
y LastName
para que el campo fullName
se calcule de nuevo:
public class Person
{
private string? _firstName;
public string? FirstName
{
get => _firstName;
set
{
_firstName = value;
_fullName = null;
}
}
private string? _lastName;
public string? LastName
{
get => _lastName;
set
{
_lastName = value;
_fullName = null;
}
}
private string? _fullName;
public string FullName
{
get
{
if (_fullName is null)
_fullName = $"{FirstName} {LastName}";
return _fullName;
}
}
}
Esta versión final da como resultado la propiedad FullName
solo cuando sea necesario. Se usa la versión calculada anteriormente si es válida. De lo contrario, el cálculo actualiza el valor almacenado en caché. No es necesario que los desarrolladores que usan esta clase conozcan los detalles de la implementación. Ninguno de estos cambios internos afectan al uso del objeto Person.
A partir de C# 13, puede crear propiedades partial
en las clases partial
. La declaración de implementación de una partial
propiedad no puede ser una propiedad implementada automáticamente. Una propiedad implementada automáticamente usa la misma sintaxis que una declaración de propiedad parcial.
Propiedades
Las propiedades son una forma de campos inteligentes en una clase o un objeto. Desde fuera del objeto, parecen campos en el objeto. Pero las propiedades pueden implementarse mediante la paleta completa de funcionalidad de C#. Se puede proporcionar validación, tipos diferentes de accesibilidad, evaluación diferida o los requisitos que se necesiten para cada escenario.
- Las propiedades simples que no requieren código de descriptor de acceso personalizado se pueden implementar como definiciones de cuerpo de expresión o como propiedades implementadas automáticamente.
- Las propiedades permiten que una clase exponga una manera pública de obtener y establecer valores, a la vez que se oculta el código de implementación o verificación.
- Para devolver el valor de la propiedad se usa un descriptor de acceso de propiedad get, mientras que para asignar un nuevo valor se emplea un descriptor de acceso de propiedad set. Un descriptor de acceso de propiedad init se usa para asignar un nuevo valor solo durante la construcción del objeto. Estos descriptores de acceso pueden tener diferentes niveles de acceso. Para más información, vea Restringir la accesibilidad del descriptor de acceso.
- La palabra clave value se usa para definir el valor que va a asignar el descriptor de acceso
set
oinit
. - Las propiedades pueden ser de lectura y escritura (en ambos casos tienen un descriptor de acceso
get
yset
), de solo lectura (tienen un descriptor de accesoget
, pero noset
) o de solo escritura (tienen un descriptor de accesoset
, pero noget
). Las propiedades de solo escritura son poco frecuentes.
Especificación del lenguaje C#
Para obtener más información, vea la sección Propiedades de la Especificación del lenguaje C#. La especificación del lenguaje es la fuente definitiva de la sintaxis y el uso de C#.