Una propiedad es un miembro que proporciona un mecanismo flexible para leer, escribir o calcular el valor de un campo de datos. Las propiedades combinan aspectos de los campos y los métodos. Para el usuario de un objeto, una propiedad parece ser un campo; se accede a la propiedad con la misma sintaxis. Para el implementador de una clase, una propiedad es uno o dos bloques de código que representa un descriptor de acceso get o un descriptor de acceso set o init. 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. 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#.
Propiedades vs. Campos de Instancia
En lenguajes de programación como Java solemos escribir los métodos de acceso y mutación de los campos de la clase. La forma convencional consiste en anteponer los prefijos get- y set- para referirse a los métodos de acceso y a los métodos de mutación, respectivamente. Semánticamente las propiedades se analogan con los campos miembro de un tipo, sin embargo, las propiedades permiten hacer validaciones sobre las asignaciones y mantener la consistencia de los datos asignados a los campos miembro (o de instancia). A diferencia de los campos, las propiedades no se clasifican como variables.
Las diferencias entre propiedades y campos de instancia se presentan en la siguiente tabla:
Nótese de la diferencia de la sintaxis requerida para construir cada uno de estos miembros de instancia. La sintaxis para propiedades es una extensión natural de los campos.
Lea también: IVA 21% Excel
| Característica | Campo de Instancia | Propiedad |
|---|---|---|
| Apariencia al usuario | Se accede directamente. | Para el usuario de un objeto, una propiedad parece ser un campo; se accede a la propiedad con la misma sintaxis. |
| Implementación | Almacena directamente un valor. | Las propiedades aparecen como miembros de datos públicos, pero se implementan como métodos especiales llamados descriptores de acceso. Para el implementador de una clase, una propiedad es uno o dos bloques de código que representa un descriptor de acceso get o un descriptor de acceso set o init. |
| Validación de datos | No intrínseca. | Semánticamente las propiedades se analogan con los campos miembro de un tipo, sin embargo, las propiedades permiten hacer validaciones sobre las asignaciones y mantener la consistencia de los datos asignados a los campos miembro (o de instancia). Parar agregar, vale decir que las propiedades permiten al programador crear lógica de validacion para la asignación de valores. |
| Clasificación | Variables. | A diferencia de los campos, las propiedades no se clasifican como variables. |
| Sintaxis | Simple declaración de tipo y nombre. | la propiedad por su lado, además de la sintaxis anterior requiere especificar un bloque de código encerrado entre llaves de apertura ({) y cierre (}). |
| Flexibilidad | Baja (acceso directo). | Una propiedad es un miembro que proporciona un mecanismo flexible para leer, escribir o calcular el valor de un campo de datos. |
Declaración de Propiedades
Las propiedades se declaran en el bloque de clase especificando el nivel de acceso del campo, seguido del tipo de la propiedad, seguido del nombre de la propiedad y seguido de un bloque de código que declara un descriptor de acceso get o un descriptor de acceso set. En este ejemplo, Month se declara como una propiedad, de manera que el descriptor de acceso set pueda estar seguro de que el valor Month se establece entre 1 y 12.
Almacenamiento Auxiliar
La ubicación real de los datos de una propiedad se conoce a menudo como el "almacenamiento auxiliar" de la propiedad. Es habitual que las propiedades usen campos privados como almacenamiento auxiliar. El campo se marca como privado para asegurarse de que solo puede cambiarse llamando a la propiedad.
Descriptores de Acceso (Accesores)
Para retornar 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 accesores pueden tener diferentes niveles de acceso.
El Descriptor de Acceso get
El bloque de código del descriptor de acceso get se ejecuta cuando se lee la propiedad. Cuando se hace referencia a la propiedad, salvo como destino para asignar un valor, se invoca el descriptor de acceso get para leer el valor de la propiedad. El cuerpo del descriptor de acceso get se parece al de un método. Debe devolver un valor del tipo de propiedad. A través de get podemos acceder al valor de la propiedad, en consecuencia del campo asociado con esta propiedad. El valor de retorno corresponde con el tipo de la propiedad. Generalmente, es un estilo de programación incorrecto para cambiar el estado del objeto mediante el get descriptor de acceso. El descriptor de acceso get puede usarse para devolver el valor de campo o para calcularlo y devolverlo.
El compilador de C# y el compilador Just-In-Time (JIT) detectan patrones comunes para implementar el descriptor de acceso get y optimiza esos patrones. Por ejemplo, es probable que un descriptor de acceso get que devuelve un campo sin realizar ningún cálculo esté optimizado para una lectura de memoria de ese campo.
Lea también: Guía IVA reducido
El Descriptor de Acceso set
El bloque de código del descriptor de acceso set o init se ejecuta cuando se asigna un valor a la propiedad. Cuando asigna un valor a la propiedad, el descriptor de acceso set se invoca mediante un argumento que proporciona el valor nuevo. El descriptor de acceso set es similar a un método cuyo tipo de valor devuelto es void. Usa un parámetro implícito denominado value, cuyo tipo es el tipo de la propiedad. En otros lenguajes se le conoce como mutador, debido a que es la construcción de acceso que modifica, cambia o muta el valor del campo asociado con la propiedad.
El compilador y el compilador JIT también reconocen patrones comunes para un descriptor de acceso set o init. Esos patrones comunes están optimizados, escribiendo directamente la memoria para el campo de respaldo.
El Descriptor de Acceso init
El código para crear un descriptor de acceso init es el mismo que para crear uno de tipo set, salvo que se usa la palabra clave init en lugar de set. Ese descriptor de acceso set solo se puede llamar desde un inicializador de objeto o los constructores del tipo.
Tipos de Propiedades
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).
- Una propiedad sin un accesor set se considera de solo lectura.
- Una propiedad sin un descriptor de acceso get se considera de escritura solamente. Cuando sólo se ha especificado set en el cuerpo de la propiedad, decimos que se trata de una propiedad de sólo escritura.
- Una propiedad que tiene ambos accesores es de lectura y escritura.
Las propiedades de sólo-lectura son aquellas de las cuales sólo podemos obtener el valor a través de la construcción sintáctica de acceso get. Una situación ejemplar en la que se utilice la propiedad de sólo lectura es la de cálculo de los valores de los campos de instancia. En los ejemplos anteriores se muestran propiedades de lectura y escritura. También se pueden crear propiedades de solo lectura o dar una accesibilidad diferente a los métodos 'set' y 'get'.
Lea también: ¿Cómo localizar tus XML del SAT?
Propiedades Implementadas Automáticamente
Las propiedades implementadas automáticamente proporcionan sintaxis simplificada para declaraciones de propiedades simples. Una propiedad automática es el resultado de la declaración de una propiedad que genera de forma automática un campo de instancia no manipulable en tiempo de compilación. 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 accesores get y set. Cualquier atributo se aplica a la propiedad implementada automáticamente.
Las propiedades implementadas automáticamente siguen este patrón y se benefician de estas optimizaciones. 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 accesor set solo desde los constructores del tipo.
Modificadores de Acceso y Comportamiento Avanzado
Las propiedades se pueden marcar como public, private, protected, internal, protected internal o private protected. Estos modificadores de acceso definen cómo los usuarios de la clase pueden obtener acceso a la propiedad.
Modificadores de Acceso para Accesores Individuales
Los descriptores de acceso get y set para la misma propiedad pueden tener diferentes modificadores de acceso. Por ejemplo, get puede ser public para permitir el acceso de solo lectura desde el exterior del tipo, y set puede ser private o protected. Puedes agregar cualquier modificador de acceso restrictivo a los accesores 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 puedes declarar una propiedad private con un accesor public.
Un accesor 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.
Si has sido observador, hemos declaradoo la propiedad con el modificador de acceso 'public'. De esta manera, desde otra clase o desde el método 'Main' principal del programa, podremos modificar el valor del atributo 'name' de la clase 'Account'.
Propiedades Estáticas
Una propiedad puede declararse como una propiedad estática mediante la palabra clave static. Las propiedades estáticas están disponibles para los llamadores en cualquier momento, incluso si no existe ninguna instancia de la clase.
Propiedades Virtuales y Abstractas
Una propiedad puede marcarse como una propiedad virtual mediante la palabra clave virtual. Las propiedades virtuales permiten que las clases derivadas invaliden el comportamiento de la propiedad mediante la palabra clave override. Una propiedad que invalida una propiedad virtual también puede sellarse, lo que especifica que para las clases derivadas ya no es virtual. Por último, una propiedad puede declararse abstracta. Las propiedades abstractas no definen ninguna implementación en la clase, y las clases derivadas deben escribir su propia implementación. En este ejemplo se muestran las propiedades de solo lectura, estáticas y de instancia.
En este ejemplo, dos clases, Cube y Square, implementan una clase abstracta, Shape, e invalidan su propiedad Area abstracta. Tenga en cuenta el uso del modificador override en las propiedades. El programa acepta el lado como una entrada y calcula las áreas del cuadrado y el cubo.
La propiedad Name de la clase derivada oculta la propiedad Name de la clase base.
Propiedades con Cuerpo de Expresión
Los descriptores de acceso de propiedad suelen constar de instrucciones de una sola línea. Los accesores asignan o devuelven el resultado de una expresión. Puede implementar estas propiedades como miembros con cuerpo de expresión. Las propiedades de solo lectura pueden implementar el descriptor de acceso get como miembro con forma de expresión. La propiedad Name es una propiedad calculada. No hay ningún campo de respaldo para Name.
Propiedades Requeridas (C# 11)
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. No confunda required con que no acepta valores NULL. Es válido establecer una propiedad required en null o default.
En el ejemplo anterior se requiere que los usuarios utilicen 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.
Propiedades Calculadas y con Almacenamiento en Caché
Se puede combinar el concepto de una propiedad calculada con un campo privado y crear una propiedad de evaluación en caché. Esta implementación funciona porque las propiedades FirstName y LastName son de solo lectura. Actualizar las propiedades FirstName y LastName para permitir que set acceda requiere invalidar cualquier valor almacenado en caché para fullName. Esta versión final evalúa la propiedad FullName solo cuando es 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.
Propiedades Parciales (C# 13)
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.
