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. Para acceder a la propiedad se 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 o init. 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 oinit se ejecuta cuando se asigna un 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. Puede usar un descriptor de acceso init en lugar de un descriptor de acceso set para permitir que la propiedad se establezca como parte de la inicialización de objetos, pero de lo contrario, conviértalo en 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 o out.

Las propiedades tienen muchos usos:

  • Pueden validar los datos antes de permitir un cambio.
  • Pueden exponer datos de forma transparente en una clase donde esos datos se recuperan de algún otro origen, como una base de datos.
  • Pueden realizar una acción cuando se cambian los datos, 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. La ubicación real de los datos de una propiedad se conoce a menudo como la "memoria auxiliar" de la propiedad. Es habitual que las propiedades usen campos privados como 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. 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. Las propiedades implementadas automáticamente siguen este patrón y se benefician de estas optimizaciones. 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 ejemplo de un descriptor de acceso get que devuelve el valor de un campo privado _name:

class Employee
{
    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:

var employee= new Employee();
//...

System.Console.Write(employee.Name);  // the get accessor is invoked here

El descriptor de acceso get debe ser un miembro con forma de expresión o finalizar en una instrucción return o throw, y el control no puede fluir fuera del cuerpo del descriptor de acceso.

Advertencia

Cambiar el estado del objeto mediante el descriptor de acceso get es un estilo de programación incorrecto.

El descriptor de acceso get puede usarse para devolver el valor de campo o para calcularlo y devolverlo. Por ejemplo:

class Manager
{
    private string _name;
    public string Name => _name != null ? _name : "NA";
}

En el ejemplo anterior, si no asigna un valor a la propiedad Name, devuelve 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. 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. En el siguiente ejemplo, se agrega un descriptor de acceso set a la propiedad Name:

class Student
{
    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:

var student = new Student();
student.Name = "Joe";  // the set accessor is invoked here

System.Console.Write(student.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. Las propiedad estáticas están disponibles 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. Las propiedades virtuales permiten 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, 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. 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:
}

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
{
    public static void Test()
    {
        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 Name de la clase derivada oculta la propiedad Name de la clase base. En dicho caso, el modificador new se usa en la declaración de la propiedad en la clase derivada:
    public new string Name
    
  • La 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
*/

Vea también