限制对属性的访问

已完成

类可以指定每个成员对类外部代码的可访问性。 不打算从类或程序集外部使用的方法和变量可以隐藏,以限制编码错误或恶意攻击的可能性。

某些方法和属性旨在从类外部的代码(称为客户端代码)调用或访问。 其他方法和属性可能仅用于类本身。 必须限制代码的可访问性,以便只有预期的客户端代码才能访问它。 可以使用以下访问修饰符指定类型及其成员对客户端代码的访问方式:

  • public:类型或成员可由同一程序集或其他引用它的程序集中的其他任何代码访问。
  • protected:类型或成员只能由同一类或派生类中的代码访问。
  • internal:类型或成员可由同一程序集中的任何代码访问,但不能从另一个程序集访问。
  • protected internal:类型或成员可由同一程序集中的任何代码或另一个程序集中的任何派生类访问。
  • private:类型或成员只能通过同一类或结构中的代码访问。
  • private protected:类型或成员只能由同一程序集中的代码访问,并且只能由同一类或派生类中的代码访问。

默认情况下,private 访问权限分配类成员。

属性和访问器的访问修饰符

在本模块中,我们专注于包括 get 和 set 访问器的属性。 这些属性称为读写属性。 除了读写属性,还可以创建只读属性,或为 set 和 get 访问器提供不同的辅助功能。

默认情况下,get 和 set 访问器具有与其所属的属性相同的辅助功能级别。 但是,可以限制 get 或 set 访问器的可访问性。 如果要限制对属性本身的访问,但仍允许通过 get 访问器访问属性的值,访问修饰符非常有用。

假设你有一个 Person 类,该类只应启用从类中的其他方法更改 FirstName 属性的值。 你可以提供 set 访问器专用辅助功能,而不是内部或公共访问器:


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

    // Omitted for brevity.
}

FirstName 属性可以从任何代码读取,但只能从 Person 类中的代码分配该属性。

可以将任何限制性访问修饰符添加到 set 或 get 访问器。 单个访问器的访问修饰符必须比属性的访问限制更严格。 上述代码是合法的,因为 FirstName 属性是 public,但 set 访问器是 private。 无法使用 private 访问器声明 public 属性。 还可以 protected、internal、protected internal甚至 private声明属性声明。

set 访问器有两个特殊的访问修饰符:

  • set 访问器可以将 init 作为其访问修饰符。 该 set 访问器只能从对象初始值设定项或类型的构造函数调用。 它比 private 访问器上的 set 更严格。
  • 自动实现的属性可以在没有 get 访问器的情况下声明 set 访问器。 在这种情况下,编译器仅允许从类型的构造函数调用 set 访问器。 它比 init 访问器上的 set 访问器更严格。

请考虑对 Person 类进行以下更新:


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

    public string FirstName { get; }

    // Omitted for brevity.
}

前面的示例要求调用方使用包含 FirstName 参数的构造函数。 调用方无法使用对象初始值设定项向属性赋值。 若要支持初始值设定项,可以将 set 访问器设置为 init 访问器,如以下代码所示:


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

    public string? FirstName { get; init; }

    // Omitted for brevity.
}

这些修饰符通常用于 required 修饰符以强制进行适当的初始化。

限制访问器可访问性

在某些情况下,限制对 get 或 set 访问器的访问很有用。 通常,可以限制 set 访问器的可访问性,同时使 get 访问器保持可公开访问。

例如:


private string _name = "Hello";

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

在此示例中,名为 Name 的属性定义 get 和 set 访问器。 在这种情况下,get 访问器接收属性本身的辅助功能级别,public,而 set 访问器通过向访问器本身应用 protected 访问修饰符来显式限制。

访问器上的访问修饰符的限制

对属性或索引器使用访问器修饰符时,需要满足以下条件:

  • 仅当属性同时具有 set 和 get 访问器时,才能使用访问器修饰符。 在这种情况下,只允许在两个访问器之一上使用修饰符。
  • 访问器上的辅助功能级别必须比属性或索引器本身的辅助功能级别更严格。

只读静态属性

以下示例演示如何在类中使用实例和静态属性。

Employee 类具有一个名为 Counter 的静态属性,用于跟踪创建的类实例数。 Counter 属性是只读的,这意味着可以从类外部访问该属性,但不能对其进行修改。 Counter 属性也是 static的,这意味着你使用 Counter 类访问 Employee 值,而不是类的 employee1 或 employee2 实例。


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();
    }
}

Employee 类具有以下成员:

  • NumberOfEmployees:一个公共静态字段,用于跟踪创建的雇员实例总数。 它是静态的,它在 Employee 类的所有实例之间共享。
  • _counter:一个专用静态字段,用于为每个 Employee 实例分配唯一号码。 它还在所有实例之间共享。
  • _name:存储员工名称的专用实例字段。 它特定于 Employee 类的每个实例。
  • Name:提供对_name字段的访问权限的公共读写实例属性。 get 访问器返回 _name的值,set 访问器将新值分配给 _name。
  • Counter:提供对 _counter 字段的访问权限的公共只读静态属性。 get 访问器返回 _counter的值。 Counter 属性与类(而不是对象)相关联,因为它被定义为静态属性。

在首次将类加载到内存中之前,在创建类的任何实例之前,将初始化这两个静态字段。 在 C# 中,如果未显式初始化静态字段,则将其初始化为其默认值。 对于 int 字段,默认值为 0。

静态属性和字段属于类本身,而不是类的任何特定实例。 类的所有实例共享相同的静态属性和字段。

构造函数将 NumberOfEmployees 静态字段递增 1,并将新值分配给 _counter 静态字段。 此方法可确保每个 Employee 实例获取唯一的数字。

专用访问器

请考虑实现读写属性的以下代码:


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
*/

在此示例中,Person 类包括 Name 和 Age 属性。 这些属性是公共的,它们都包括 get 和 set 访问器。 公共访问器允许任何对象读取和写入这些属性。

有时,需要排除其中一个访问器。 可以省略 set 访问器以使属性只读:


public string Name
{
    get
    {
        return _name;
    }
}


或者,可以公开一个访问器,但使另一个访问器成为私有或受保护的访问器。


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