Utilizar propiedades (Guía de programación de C#)
Las propiedades combinan aspectos de los campos y los métodos. Para el usuario de un objeto, una propiedad que parece un campo, el acceso a la propiedad necesita 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. El bloque de código del descriptor de acceso get se ejecuta cuando se lee la propiedad; el bloque de código del descriptor de acceso set se ejecuta cuando se asigna un nuevo valor a la propiedad. Una propiedad sin un descriptor de acceso set se considera de solo lectura. Una propiedad sin un descriptor de acceso get se considera de solo escritura. Una propiedad que tiene ambos descriptores de acceso es de lectura y escritura. En C# 9 y versiones posteriores, puede usar un descriptor de acceso init en lugar de set para que la propiedad sea de solo lectura.
A diferencia de los campos, las propiedades no se clasifican como variables. Por lo tanto, no puede pasar una propiedad como un parámetro ref u out.
Las propiedades tienen muchos usos: pueden validar datos antes de permitir un cambio; pueden exponer claramente datos en una clase donde esos datos se recuperan realmente de otros orígenes, como una base de datos; pueden realizar una acción cuando los datos se cambian, como generar un evento, o cambiar el valor de otros campos.
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. Por ejemplo:
public class Date
{
private int _month = 7; // Backing store
public int Month
{
get => _month;
set
{
if ((value > 0) && (value < 13))
{
_month = value;
}
}
}
}
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. La propiedad Month usa un campo privado para realizar un seguimiento del valor actual. A menudo, a la ubicación real de los datos de una propiedad se le conoce como la "memoria auxiliar" de la propiedad. Esto es común para las propiedades que usan campos privados como una memoria auxiliar. El campo se marca como privado para asegurarse de que solo puede cambiarse llamando a la propiedad. Para obtener más información sobre las restricciones de acceso público y privado, vea Modificadores de acceso.
Las propiedades implementadas automáticamente proporcionan una sintaxis simplificada para las declaraciones de propiedad simples. Para obtener más información, vea Propiedades implementadas automáticamente.
El descriptor de acceso get
El cuerpo del descriptor de acceso get se parece al de un método. Debe devolver un valor del tipo de propiedad. La ejecución del descriptor de acceso get es equivalente a la lectura del valor del campo. Por ejemplo, cuando se devuelve la variable privada del descriptor de acceso get y se habilitan las optimizaciones, la llamada al método de descriptor de acceso get se inserta mediante el compilador, de manera que no existe ninguna sobrecarga de llamada al método. En cambio, un método de descriptor de acceso get virtual no puede insertarse porque el compilador no conoce en tiempo de compilación a qué método puede llamarse realmente en tiempo de ejecución. A continuación se muestra un descriptor de acceso get que devuelve el valor de un campo privado _name:
class Person
{
private string _name; // the name field
public string Name => _name; // the Name property
}
Cuando hace referencia a la propiedad, excepto como el destino de una asignación, el descriptor de acceso get se invoca para leer el valor de la propiedad. Por ejemplo:
Person person = new Person();
//...
System.Console.Write(person.Name); // the get accessor is invoked here
El descriptor de acceso get debe finalizar en una instrucción return o throw, y el control no puede salir del cuerpo del descriptor de acceso.
Cambiar el estado del objeto mediante el descriptor de acceso get es un estilo de programación incorrecto. Por ejemplo, el siguiente descriptor de acceso produce el efecto secundario de cambiar el estado del objeto cada vez que se tiene acceso al campo _number.
private int _number;
public int Number => _number++; // Don't do this
El descriptor de acceso get puede usarse para devolver el valor de campo o para calcularlo y devolverlo. Por ejemplo:
class Employee
{
private string _name;
public string Name => _name != null ? _name : "NA";
}
En el segmento de código anterior, si no asigna un valor a la propiedad Name, devolverá el valor NA.
El descriptor de acceso set
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 el siguiente ejemplo, se agrega un descriptor de acceso set a la propiedad Name:
class Person
{
private string _name; // the name field
public string Name // the Name property
{
get => _name;
set => _name = value;
}
}
Cuando asigna un valor a la propiedad, el descriptor de acceso set se invoca mediante un argumento que proporciona el valor nuevo. Por ejemplo:
Person person = new Person();
person.Name = "Joe"; // the set accessor is invoked here
System.Console.Write(person.Name); // the get accessor is invoked here
Es un error usar el nombre de parámetro implícito, value, para una declaración de variable local en el descriptor de acceso set.
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. La diferencia es que el descriptor de acceso init solo se puede usar en el constructor o mediante un inicializador de objeto.
Observaciones
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. 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. Para obtener más información, consulte Modificadores de acceso.
Una propiedad puede declararse como una propiedad estática mediante la palabra clave static. Esto hace que la propiedad esté disponible para los autores de la llamada en cualquier momento, aunque no exista ninguna instancia de la clase. Para más información, vea Clases estáticas y sus miembros.
Una propiedad puede marcarse como una propiedad virtual mediante la palabra clave virtual. Esto permite que las clases derivadas invaliden el comportamiento de la propiedad mediante la palabra clave override. Para obtener más información sobre estas opciones, vea Herencia.
Una propiedad que invalida una propiedad virtual también puede sellarse, que especifica que para las clases derivadas ya no es virtual. Por último, una propiedad puede declararse abstracta. Esto significa que no existe ninguna implementación en la clase, y las clases derivadas deben escribir su propia implementación. Para obtener más información sobre estas opciones, vea Clases y miembros de clase abstractos y sellados (Guía de programación de C#).
Nota
Es un error usar un modificador virtual, abstract u override en un descriptor de acceso de una propiedad static.
Ejemplos
En este ejemplo se muestran las propiedades de solo lectura, estáticas y de instancia. Acepta el nombre del empleado desde el teclado, incrementa NumberOfEmployees en 1 y muestra el nombre del empleado y el número.
public class Employee
{
public static int NumberOfEmployees;
private static int _counter;
private string _name;
// A read-write instance property:
public string Name
{
get => _name;
set => _name = value;
}
// A read-only static property:
public static int Counter => _counter;
// A Constructor:
public Employee() => _counter = ++NumberOfEmployees; // Calculate the employee's number:
}
class TestEmployee
{
static void Main()
{
Employee.NumberOfEmployees = 107;
Employee e1 = new Employee();
e1.Name = "Claude Vige";
System.Console.WriteLine("Employee number: {0}", Employee.Counter);
System.Console.WriteLine("Employee name: {0}", e1.Name);
}
}
/* Output:
Employee number: 108
Employee name: Claude Vige
*/
Ejemplo de propiedad oculta
En este ejemplo se muestra cómo tener acceso a una propiedad en una clase base que está oculta mediante otra propiedad que tiene el mismo nombre en una clase derivada:
public class Employee
{
private string _name;
public string Name
{
get => _name;
set => _name = value;
}
}
public class Manager : Employee
{
private string _name;
// Notice the use of the new modifier:
public new string Name
{
get => _name;
set => _name = value + ", Manager";
}
}
class TestHiding
{
static void Main()
{
Manager m1 = new Manager();
// Derived class property.
m1.Name = "John";
// Base class property.
((Employee)m1).Name = "Mary";
System.Console.WriteLine("Name in the derived class is: {0}", m1.Name);
System.Console.WriteLine("Name in the base class is: {0}", ((Employee)m1).Name);
}
}
/* Output:
Name in the derived class is: John, Manager
Name in the base class is: Mary
*/
A continuación se muestran puntos importantes del ejemplo anterior:
La propiedad
Namede la clase derivada oculta la propiedadNamede la clase base. En dicho caso, el modificadornewse usa en la declaración de la propiedad en la clase derivada:public new string NameLa conversión
(Employee)se usa para tener acceso a la propiedad oculta de la clase base:((Employee)m1).Name = "Mary";Para obtener más información sobre cómo ocultar miembros, vea el Modificador new.
Ejemplo de invalidación de propiedades
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. También acepta el área como una entrada y calcula el lado correspondiente para el cuadrado y el cubo.
abstract class Shape
{
public abstract double Area
{
get;
set;
}
}
class Square : Shape
{
public double side;
//constructor
public Square(double s) => side = s;
public override double Area
{
get => side * side;
set => side = System.Math.Sqrt(value);
}
}
class Cube : Shape
{
public double side;
//constructor
public Cube(double s) => side = s;
public override double Area
{
get => 6 * side * side;
set => side = System.Math.Sqrt(value / 6);
}
}
class TestShapes
{
static void Main()
{
// Input the side:
System.Console.Write("Enter the side: ");
double side = double.Parse(System.Console.ReadLine());
// Compute the areas:
Square s = new Square(side);
Cube c = new Cube(side);
// Display the results:
System.Console.WriteLine("Area of the square = {0:F2}", s.Area);
System.Console.WriteLine("Area of the cube = {0:F2}", c.Area);
System.Console.WriteLine();
// Input the area:
System.Console.Write("Enter the area: ");
double area = double.Parse(System.Console.ReadLine());
// Compute the sides:
s.Area = area;
c.Area = area;
// Display the results:
System.Console.WriteLine("Side of the square = {0:F2}", s.side);
System.Console.WriteLine("Side of the cube = {0:F2}", c.side);
}
}
/* Example Output:
Enter the side: 4
Area of the square = 16.00
Area of the cube = 96.00
Enter the area: 24
Side of the square = 4.90
Side of the cube = 2.00
*/