Restringir el acceso a las propiedades

Completado

Una clase puede especificar cómo se puede acceder a cada uno de sus miembros es codificar fuera de la clase . Los métodos y variables que no están diseñados para usarse desde fuera de la clase o el ensamblado se pueden ocultar para limitar el potencial de errores de codificación o vulnerabilidades de seguridad malintencionadas.

Algunos métodos y propiedades están diseñados para llamarse o acceder a ellas desde código fuera de una clase, conocido como código de cliente. Es posible que otros métodos y propiedades solo se usen en la propia clase. Es importante limitar la accesibilidad del código para que solo el código de cliente previsto pueda llegar a él. Especifique cómo se puede acceder a los tipos y sus miembros para el código de cliente mediante los siguientes modificadores de acceso:

  • public: el tipo o miembro es accesible por cualquier otro código del mismo ensamblado u otro ensamblado que haga referencia a él.
  • protected: el tipo o miembro solo es accesible por código de la misma clase o una clase derivada.
  • internal: cualquier código del mismo ensamblado puede acceder al tipo o al miembro, pero no desde otro ensamblado.
  • protected internal: el tipo o miembro es accesible por cualquier código del mismo ensamblado o por cualquier clase derivada de otro ensamblado.
  • private: el tipo o miembro solo es accesible por código de la misma clase o estructura.
  • private protected: el tipo o miembro solo es accesible por código en el mismo ensamblado y solo por código de la misma clase o una clase derivada.

Los miembros de clase se asignan private acceso de forma predeterminada.

Modificadores de acceso en propiedades y descriptores de acceso

Hasta este punto del módulo, nos centramos en propiedades que incluyen descriptores de acceso get y set. Estas propiedades se conocen como propiedades de lectura y escritura. Además de las propiedades de lectura y escritura, puede crear propiedades de solo lectura o proporcionar una accesibilidad diferente a los descriptores de acceso set y get.

De forma predeterminada, los descriptores de acceso get y set tienen el mismo nivel de accesibilidad que la propiedad a la que pertenecen. Sin embargo, puede restringir la accesibilidad del descriptor de acceso get o set. Los modificadores de acceso son útiles cuando desea restringir el acceso a la propia propiedad, pero siguen permitiendo el acceso al valor de la propiedad a través de un descriptor de acceso get.

Supongamos que tiene una clase Person que solo debe habilitar el cambio del valor de la propiedad FirstName de otros métodos de la clase . Puede proporcionar la accesibilidad privada del descriptor de acceso set en lugar de interna o pública:


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

    // Omitted for brevity.
}

La propiedad FirstName se puede leer desde cualquier código, pero solo se puede asignar desde el código de la clase Person.

Puede agregar cualquier modificador de acceso restrictivo a los descriptores 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 legal porque la propiedad FirstName es public, pero el descriptor de acceso set es private. No se pudo declarar una propiedad private con un descriptor de acceso public. Las declaraciones de propiedad también se pueden declarar protected, internal, protected internalo, incluso private.

Hay dos modificadores de acceso especiales para 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 descriptor de acceso get sin un descriptor de acceso set. 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.

Tenga en cuenta las siguientes actualizaciones de la clase Person:


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

    public string FirstName { get; }

    // Omitted for brevity.
}

El ejemplo anterior requiere que los autores de llamadas usen el constructor que incluye el parámetro FirstName. Los autores de llamadas no pueden usar inicializadores de objeto 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.

Restringir la accesibilidad del descriptor de acceso

En algunos casos, resulta útil restringir el acceso a los descriptores de acceso get o set. Normalmente, restringe la accesibilidad del descriptor de acceso set, al tiempo que mantiene el descriptor de acceso get accesible públicamente.

Por ejemplo:


private string _name = "Hello";

public string Name
{
    get
    {
        return _name;
    }
    protected set
    {
        _name = value;
    }
}

En este ejemplo, una propiedad denominada Name define un descriptor de acceso get y set. El descriptor de acceso get recibe el nivel de accesibilidad de la propia propiedad, public en este caso, mientras que el descriptor de acceso set está restringido explícitamente aplicando el modificador de acceso protected al propio descriptor de acceso.

Restricciones en los modificadores de acceso en los descriptores de acceso

El uso de los modificadores de descriptor de acceso en propiedades o indexadores está sujeto a estas condiciones:

  • Solo puede usar modificadores de descriptor de acceso si la propiedad tiene descriptores de acceso set y get. En este caso, el modificador solo se permite en uno de los dos descriptores de acceso.
  • El nivel de accesibilidad del descriptor de acceso debe ser más restrictivo que el nivel de accesibilidad en la propiedad o el propio indexador.

Propiedades estáticas de solo lectura

En el ejemplo siguiente se muestra el uso de instancias y propiedades estáticas en una clase .

La clase Employee tiene una propiedad estática denominada Counter que realiza un seguimiento del número de instancias de clase creadas. La propiedad Counter es de solo lectura, lo que significa que se puede tener acceso desde fuera de la clase, pero no modificarla. La propiedad Counter también se static, lo que significa que se accede al valor de Counter mediante la clase Employee, no las instancias de employee1 o employee2 de la clase .


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:
}

public class Program
{
    static void Main(string[] args)
    {
        // Create an initial instance of the Employee class
        Employee employee1 = new Employee { Name = "NameOne" };
        
        // Display the name and counter values
        Console.WriteLine($"Employee 1: Name = {employee1.Name}, Counter = {Employee.Counter}");

        // Create a second instance of the Employee class
        Employee employee2 = new Employee { Name = "NameTwo" };

        // Display the name and counter values
        Console.WriteLine($"Employee 2: Name = {employee2.Name}, Counter = {Employee.Counter}");

        employee2.Name = "NameThree";

        // Display the name and counter values
        Console.WriteLine($"Employee 2: Name = {employee2.Name}, Counter = {Employee.Counter}");

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}

La clase Employee tiene los siguientes miembros:

  • NumberOfEmployees: un campo estático público que realiza un seguimiento del número total de instancias de employee creadas. Siendo estático, se comparte entre todas las instancias de la clase Employee.
  • _counter: un campo estático privado que se usa para asignar un número único a cada instancia de Employee. También se comparte entre todas las instancias.
  • _name: un campo de instancia privada que almacena el nombre del empleado. Es específico de cada instancia de la clase Employee.
  • Name: una propiedad de instancia de lectura y escritura pública que proporciona acceso al campo _name. El descriptor de acceso get devuelve el valor de _namey el descriptor de acceso set asigna un nuevo valor a _name.
  • Counter: una propiedad estática pública de solo lectura que proporciona acceso al campo _counter. El descriptor de acceso get devuelve el valor de _counter. La propiedad Counter está asociada a la clase en lugar de a los objetos porque se define como una propiedad estática.

Los dos campos estáticos se inicializan cuando la clase se carga por primera vez en la memoria, antes de que se creen instancias de la clase. En C#, los campos estáticos se inicializan en sus valores predeterminados si no se inicializan explícitamente. En int campos, el valor predeterminado es 0.

Las propiedades estáticas y los campos pertenecen a la propia clase, no a ninguna instancia determinada de la clase. Todas las instancias de la clase comparten las mismas propiedades y campos estáticos.

El constructor incrementa el NumberOfEmployees campo estático en 1 y asigna el nuevo valor al campo estático _counter. Este enfoque garantiza que cada instancia de Employee obtiene un número único.

Descriptores de acceso privados

Tenga en cuenta el código siguiente que implementa propiedades de lectura y escritura:


class Person
{
    private string _name = "N/A";
    private int _age = 0;

    // Declare a Name property of type string:
    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
        }
    }

    // Declare an Age property of type int:
    public int Age
    {
        get
        {
            return _age;
        }
        set
        {
            _age = value;
        }
    }
}

class TestPerson
{
    static void Main()
    {
        // Create a new Person object:
        Person person = new Person();

        // Print out the name and the age associated with the person:
        Console.WriteLine($"Person details - Name = {person.Name}, Age = {person.Age}");

        // Set some values on the person object:
        person.Name = "NameOne";
        person.Age = 99;
        Console.WriteLine($"Person details - Name = {person.Name}, Age = {person.Age}");

        // Increment the Age property:
        person.Age += 1;
        Console.WriteLine($"Person details - Name = {person.Name}, Age = {person.Age}");

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}

/* Output:
Person details - Name = N/A, Age = 0
Person details - Name = NameOne, Age = 99
Person details - Name = NameOne, Age = 100
*/

En este ejemplo, la clase Person incluye propiedades Name y Age. Las propiedades son públicas y ambos incluyen descriptores de acceso get y set. Los descriptores de acceso públicos permiten que cualquier objeto lea y escriba estas propiedades.

A veces, es excluir uno de los descriptores de acceso. Puede omitir el descriptor de acceso set para que la propiedad sea de solo lectura:


public string Name
{
    get
    {
        return _name;
    }
}


Como alternativa, puede exponer un descriptor de acceso públicamente, pero hacer que el otro sea privado o protegido.


public string Name
{
    get
    {
        return _name;
    }
    private set
    {
        _name = value;
    }
}