PropiedadesProperties

Las propiedades son ciudadanos de primera clase en C#.Properties are first class citizens in C#. El lenguaje define la sintaxis que permite a los desarrolladores escribir código que exprese con precisión su intención de diseño.The language defines syntax that enables developers to write code that accurately expresses their design intent.

Las propiedades se comportan como campos cuando se obtiene acceso a ellas.Properties behave like fields when they are accessed. Pero, a diferencia de los campos, las propiedades se implementan con descriptores de acceso que definen las instrucciones que se ejecutan cuando se tiene acceso a una propiedad o se asigna.However, unlike fields, properties are implemented with accessors that define the statements executed when a property is accessed or assigned.

Sintaxis de las propiedadesProperty syntax

La sintaxis para propiedades es una extensión natural de los campos.The syntax for properties is a natural extension to fields. Un campo define una ubicación de almacenamiento:A field defines a storage location:

public class Person
{
    public string FirstName;
    // remaining implementation removed from listing
}

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:A property definition contains declarations for a get and set accessor that retrieves and assigns the value of that property:

public class Person
{
    public string FirstName { get; set; }

    // remaining implementation removed from listing
}

La sintaxis anterior es la sintaxis de propiedades automáticas.The syntax shown above is the auto property syntax. El compilador genera la ubicación de almacenamiento para el campo que respalda a la propiedad.The compiler generates the storage location for the field that backs up the property. El compilador también implementa el cuerpo de los descriptores de acceso get y set.The compiler also implements the body of the get and set accessors.

A veces, necesita inicializar una propiedad en un valor distinto del predeterminado para su tipo.Sometimes, you need to initialize a property to a value other than the default for its type. C# permite esto estableciendo un valor después de la llave de cierre de la propiedad.C# enables that by setting a value after the closing brace for the property. Puede que prefiera que el valor inicial para la propiedad FirstName sea la cadena vacía en lugar de null.You may prefer the initial value for the FirstName property to be the empty string rather than null. Debe especificarlo como se muestra a continuación:You would specify that as shown below:

public class Person
{
    public string FirstName { get; set; } = string.Empty;

    // remaining implementation removed from listing
}

La inicialización específica es más útil en las propiedades de solo lectura, como verá posteriormente en este artículo.Specific initialization is most useful for read-only properties, as you'll see later in this article.

También puede definir su propio almacenamiento, como se muestra a continuación:You can also define the storage yourself, as shown below:

public class Person
{
    public string FirstName
    {
        get { return firstName; }
        set { firstName = value; }
    }
    private string firstName;
    // remaining implementation removed from listing
}

Cuando una implementación de propiedad es una expresión única, puede usar miembros con forma de expresión para el captador o establecedor:When a property implementation is a single expression, you can use expression-bodied members for the getter or setter:

public class Person
{
    public string FirstName
    {
        get => firstName;
        set => firstName = value;
    }
    private string firstName;
    // remaining implementation removed from listing
}

Esta sintaxis simplificada se usará cuando sea necesario en todo el artículo.This simplified syntax will be used where applicable throughout this article.

La definición de propiedad anterior es una propiedad de lectura y escritura.The property definition shown above is a read-write property. Observe la palabra clave value en el descriptor de acceso set.Notice the keyword value in the set accessor. El descriptor de acceso set siempre tiene un único parámetro denominado value.The set accessor always has a single parameter named value. El descriptor de acceso get tiene que devolver un valor que se pueda convertir al tipo de la propiedad (string en este ejemplo).The get accessor must return a value that is convertible to the type of the property (string in this example).

Estos son los conceptos básicos de la sintaxis.That's the basics of the syntax. Hay muchas variantes distintas que admiten diversos lenguajes de diseño diferentes.There are many different variations that support a variety of different design idioms. Vamos a explorarlas y a aprender las opciones de sintaxis de cada una.Let's explore, and learn the syntax options for each.

EscenariosScenarios

Los ejemplos anteriores mostraron uno de los casos más simples de definición de propiedad: una propiedad de lectura y escritura sin validación.The examples above showed one of the simplest cases of property definition: a read-write property with no validation. Al escribir el código que quiere en los descriptores de acceso get y set, puede crear muchos escenarios diferentes.By writing the code you want in the get and set accessors, you can create many different scenarios.

ValidaciónValidation

Puede escribir código en el descriptor de acceso set para asegurarse de que los valores representados por una propiedad siempre son válidos.You can write code in the set accessor to ensure that the values represented by a property are always valid. Por ejemplo, suponga que una regla para la clase Person es que el nombre no puede estar en blanco ni tener espacios en blanco.For example, suppose one rule for the Person class is that the name cannot be blank or white space. Se escribiría de esta forma:You would write that as follows:

public class Person
{
    public string FirstName
    {
        get => firstName;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("First name must not be blank");
            firstName = value;
        }
    }
    private string firstName;
    // remaining implementation removed from listing
}

El ejemplo anterior se puede simplificar usando una expresión throw como parte de la validación del establecedor de propiedad:The preceding example can be simplified by using athrow expression as part of the property setter validation:

public class Person
{
    public string FirstName
    {
        get => firstName;
        set => firstName = (!string.IsNullOrWhiteSpace(value)) ? value : throw new ArgumentException("First name must not be blank");
    }
    private string firstName;
    // remaining implementation removed from listing
}

En el ejemplo anterior, se aplica la regla de que el nombre no debe estar en blanco ni tener espacios en blanco.The example above enforces the rule that the first name must not be blank or white space. Si un desarrollador escribe:If a developer writes

hero.FirstName = "";

Esa asignación produce una excepción ArgumentException.That assignment throws an ArgumentException. Dado que un descriptor de acceso set de propiedad debe tener un tipo de valor devuelto void, los errores se notifican en el descriptor de acceso set iniciando una excepción.Because a property set accessor must have a void return type, you report errors in the set accessor by throwing an exception.

Se puede extender esta misma sintaxis para todo lo que se necesite en el escenario.You can extend this same syntax to anything needed in your scenario. Se pueden comprobar las relaciones entre las diferentes propiedades o validar con respecto a cualquier condición externa.You can check the relationships between different properties, or validate against any external conditions. Todas las instrucciones de C# válidas son válidas en un descriptor de acceso de propiedad.Any valid C# statements are valid in a property accessor.

De sólo lecturaRead-only

Hasta ahora, todas las definiciones de propiedad que se vieron son propiedades de lectura y escritura con descriptores de acceso públicos.Up to this point, all the property definitions you have seen are read/write properties with public accessors. No es la única accesibilidad válida para las propiedades.That's not the only valid accessibility for properties. Se pueden crear propiedades de solo lectura, o proporcionar accesibilidad diferente a los descriptores de acceso set y get.You can create read-only properties, or give different accessibility to the set and get accessors. Suponga que su clase Person solo debe habilitar el cambio del valor de la propiedad FirstName desde otros métodos de esa clase.Suppose that your Person class should only enable changing the value of the FirstName property from other methods in that class. Podría asignar al descriptor de acceso set la accesibilidad private en lugar de public:You could give the set accessor private accessibility instead of public:

public class Person
{
    public string FirstName { get; private set; }

    // remaining implementation removed from listing
}

Ahora, se puede obtener acceso a la propiedad FirstName desde cualquier código, pero solo puede asignarse desde otro código de la clase Person.Now, the FirstName property can be accessed from any code, but it can only be assigned from other code in the Person class.

Puede agregar cualquier modificador de acceso restrictivo al descriptor de acceso set o get.You can add any restrictive access modifier to either the set or get accessors. Ningún modificador de acceso que se coloque en el descriptor de acceso concreto debe ser más limitado que el modificador de acceso en la definición de la propiedad.Any access modifier you place on the individual accessor must be more limited than the access modifier on the property definition. El ejemplo anterior es válido porque la propiedad FirstName es public, pero el descriptor de acceso set es private.The above is legal because the FirstName property is public, but the set accessor is private. No se puede declarar una propiedad private con un descriptor de acceso public.You could not declare a private property with a public accessor. Las declaraciones de propiedad también se pueden declarar como protected, internal, protected internal o incluso private.Property declarations can also be declared protected, internal, protected internal, or, even private.

También es válido colocar el modificador más restrictivo en el descriptor de acceso get.It is also legal to place the more restrictive modifier on the get accessor. Por ejemplo, se podría tener una propiedad public, pero restringir el descriptor de acceso get a private.For example, you could have a public property, but restrict the get accessor to private. Ese escenario raramente se aplica en la práctica.That scenario is rarely done in practice.

También puede restringir las modificaciones de una propiedad, de manera que solo pueda establecerse en un constructor o en un inicializador de propiedades.You can also restrict modifications to a property so that it can only be set in a constructor or a property initializer. Puede modificar la clase Person de la manera siguiente:You can modify the Person class so as follows:

public class Person
{
    public Person(string firstName) => this.FirstName = firstName;

    public string FirstName { get; }

    // remaining implementation removed from listing
}

Esta característica se usa normalmente para inicializar colecciones que están expuestas como propiedades de solo lectura:This feature is most commonly used for initializing collections that are exposed as read-only properties:

public class Measurements
{
    public ICollection<DataPoint> points { get; } = new List<DataPoint>();
}

Propiedades calculadasComputed properties

Una propiedad no tiene por qué devolver únicamente el valor de un campo de miembro.A property does not need to simply return the value of a member field. Se pueden crear propiedades que devuelvan un valor calculado.You can create properties that return a computed value. Vamos a ampliar el objeto Person para que devuelva el nombre completo, que se calcula mediante la concatenación del nombre y el apellido:Let's expand the Person object to return the full name, computed by concatenating the first and last names:

public class Person
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string FullName { get { return $"{FirstName} {LastName}"; } }
}

En el ejemplo anterior se usa la característica de interpolación de cadenas para crear la cadena con formato para el nombre completo.The example above uses the string interpolation feature to create the formatted string for the full name.

También se pueden usar un miembro con forma de expresión, que proporciona una manera más concisa de crear la propiedad FullName calculada:You can also use an expression-bodied member, which provides a more succinct way to create the computed FullName property:

public class Person
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string FullName => $"{FirstName} {LastName}";
}

Los miembros con forma de expresión usan la sintaxis de expresión lambda para definir métodos que contienen una única expresión.Expression-bodied members use the lambda expression syntax to define methods that contain a single expression. En este caso, esa expresión devuelve el nombre completo para el objeto person.Here, that expression returns the full name for the person object.

Propiedades de evaluación en cachéCached evaluated properties

Se puede combinar el concepto de una propiedad calculada con almacenamiento de información y crear una propiedad de evaluación en caché.You can mix the concept of a computed property with storage and create a cached evaluated property. Por ejemplo, se podría actualizar la propiedad FullName para que la cadena de formato solo apareciera la primera vez que se obtuvo acceso a ella:For example, you could update the FullName property so that the string formatting only happened the first time it was accessed:

public class Person
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    private string fullName;
    public string FullName
    {
        get
        {
            if (fullName == null)
                fullName = $"{FirstName} {LastName}";
            return fullName;
        }
    }
}

Pero el código anterior contiene un error.The above code contains a bug though. Si el código actualiza el valor de la propiedad FirstName o LastName, el campo evaluado previamente fullName no es válido.If code updates the value of either the FirstName or LastName property, the previously evaluated fullName field is invalid. Hay que modificar los descriptores de acceso set de la propiedad FirstName y LastName para que el campo fullName se calcule de nuevo:You modify the set accessors of the FirstName and LastName property so that the fullName field is calculated again:

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 == null)
                fullName = $"{FirstName} {LastName}";
            return fullName;
        }
    }
}

Esta versión final da como resultado la propiedad FullName solo cuando sea necesario.This final version evaluates the FullName property only when needed. Si la versión calculada previamente es válida, es la que se usa.If the previously calculated version is valid, it's used. Si otro cambio de estado invalida la versión calculada previamente, se vuelve a calcular.If another state change invalidates the previously calculated version, it will be recalculated. No es necesario que los desarrolladores que usan esta clase conozcan los detalles de la implementación.Developers that use this class do not need to know the details of the implementation. Ninguno de estos cambios internos afectan al uso del objeto Person.None of these internal changes affect the use of the Person object. Es el motivo principal para usar propiedades para exponer los miembros de datos de un objeto.That's the key reason for using Properties to expose data members of an object.

Asociar atributos a propiedades implementadas automáticamenteAttaching attributes to auto-implemented properties

A partir de C# 7.3, los atributos de campo se pueden conectar al campo de respaldo generado por el compilador en las propiedades implementadas automáticamente.Beginning with C# 7.3, field attributes can be attached to the compiler generated backing field in auto-implemented properties. Por ejemplo, pensemos en una revisión de la clase Person que agrega una propiedad Id de entero único.For example, consider a revision to the Person class that adds a unique integer Id property. Escribimos la propiedad Id usando una propiedad implementada automáticamente, pero el diseño no requiere que la propiedad Id se conserve.You write theId property using an auto-implemented property, but your design does not call for persisting the Id property. NonSerializedAttribute solo se puede asociar a campos, no a propiedades.The NonSerializedAttribute can only be attached to fields, not properties. NonSerializedAttribute se puede asociar al campo de respaldo de la propiedad Id usando el especificador field: en el atributo, como se muestra en el siguiente ejemplo:You can attach the NonSerializedAttribute to the backing field for the Id property by using the field: specifier on the attribute, as shown in the following example:

public class Person
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    [field:NonSerialized]
    public int Id { get; set; }

    public string FullName => $"{FirstName} {LastName}";
}

Esta técnica funciona con cualquier atributo que se asocie al campo de respaldo en la propiedad implementada automáticamente.This technique works for any attribute you attach to the backing field on the auto-implemented property.

Implementar INotifyPropertyChangedImplementing INotifyPropertyChanged

Un último escenario donde se necesita escribir código en un descriptor de acceso de propiedad es para admitir la interfaz INotifyPropertyChanged que se usa para notificar a los clientes de enlace de datos el cambio de un valor.A final scenario where you need to write code in a property accessor is to support the INotifyPropertyChanged interface used to notify data binding clients that a value has changed. Cuando se cambia el valor de una propiedad, el objeto genera el evento INotifyPropertyChanged.PropertyChanged para indicar el cambio.When the value of a property changes, the object raises the INotifyPropertyChanged.PropertyChanged event to indicate the change. A su vez, las bibliotecas de enlace de datos actualizan los elementos de visualización en función de ese cambio.The data binding libraries, in turn, update display elements based on that change. El código siguiente muestra cómo se implementaría INotifyPropertyChanged para la propiedad FirstName de esta clase person.The code below shows how you would implement INotifyPropertyChanged for the FirstName property of this person class.

public class Person : INotifyPropertyChanged
{
    public string FirstName
    {
        get => firstName;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("First name must not be blank");
            if (value != firstName)
            {
                PropertyChanged?.Invoke(this,
                    new PropertyChangedEventArgs(nameof(FirstName)));
            }
            firstName = value;
        }
    }
    private string firstName;

    public event PropertyChangedEventHandler PropertyChanged;
    // remaining implementation removed from listing
}

El operador ?. se denomina operador condicional NULL.The ?. operator is called the null conditional operator. Comprueba si existe una referencia nula antes de evaluar el lado derecho del operador.It checks for a null reference before evaluating the right side of the operator. El resultado final es que si no hay ningún suscriptor para el evento PropertyChanged, no se ejecuta el código para generar el evento.The end result is that if there are no subscribers to the PropertyChanged event, the code to raise the event doesn't execute. En ese caso, se producirá una NullReferenceException sin esta comprobación.It would throw a NullReferenceException without this check in that case. Para obtener más información, vea events.For more information, see events. En este ejemplo también se usa el nuevo operador nameof para convertir el símbolo de nombre de propiedad en su representación de texto.This example also uses the new nameof operator to convert from the property name symbol to its text representation. Con nameof se pueden reducir los errores en los que no se escribió correctamente el nombre de la propiedad.Using nameof can reduce errors where you have mistyped the name of the property.

De nuevo, la implementación de INotifyPropertyChanged es un ejemplo de un caso en el que se puede escribir código en los descriptores de acceso para admitir los escenarios que se necesitan.Again, implementing INotifyPropertyChanged is an example of a case where you can write code in your accessors to support the scenarios you need.

ResumenSumming up

Las propiedades son una forma de campos inteligentes en una clase o un objeto.Properties are a form of smart fields in a class or object. Desde fuera del objeto, parecen campos en el objeto.From outside the object, they appear like fields in the object. Pero las propiedades pueden implementarse mediante la paleta completa de funcionalidad de C#.However, properties can be implemented using the full palette of C# functionality. Se puede proporcionar validación, tipos diferentes de accesibilidad, evaluación diferida o los requisitos que se necesiten para cada escenario.You can provide validation, different accessibility, lazy evaluation, or any requirements your scenarios need.