Compartir a través de


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 tener init como modificador de acceso. Ese descriptor de acceso set solo se puede llamar desde un inicializador de objeto o los constructores del tipo. Es más restrictivo que private en el descriptor de acceso set.
  • Una propiedad implementada automáticamente puede declarar un get descriptor de acceso sin un set descriptor de acceso. En ese caso, el compilador permite llamar al descriptor de acceso set solo desde los constructores del tipo. Es más restrictivo que el descriptor de acceso init en el descriptor de acceso set.

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 o init.
  • Las propiedades pueden ser de lectura y escritura (en ambos casos tienen un descriptor de acceso get y set), de solo lectura (tienen un descriptor de acceso get, pero no set) o de solo escritura (tienen un descriptor de acceso set, pero no get). 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#.

Consulte también