属性(C# 编程指南)

属性是一种成员,它提供灵活的机制来读取、写入或计算私有字段的值。 属性可用作公共数据成员,但它们是称为“访问器”的特殊方法。 此功能使得可以轻松访问数据,还有助于提高方法的安全性和灵活性。

属性概述

  • 属性允许类公开获取和设置值的公共方法,而隐藏实现或验证代码。
  • get 属性访问器用于返回属性值,而 set 属性访问器用于分配新值。 init 属性访问器仅用于在对象构造过程中分配新值。 这些访问器可以具有不同的访问级别。 有关详细信息,请参阅限制访问器可访问性
  • value 关键字用于定义由 setinit 访问器分配的值。
  • 属性可以是读-写属性(既有 get 访问器又有 set 访问器)、只读属性(有 get 访问器,但没有 set 访问器)或只写访问器(有 set 访问器,但没有 get 访问器)。 只写属性很少出现,常用于限制对敏感数据的访问。
  • 不需要自定义访问器代码的简单属性可以作为表达式主体定义或自动实现的属性来实现。

具有支持字段的属性

有一个实现属性的基本模式,该模式使用私有支持字段来设置和检索属性值。 get 访问器返回私有字段的值,set 访问器在向私有字段赋值之前可能会执行一些数据验证。 这两个访问器还可以在存储或返回数据之前对其执行某些转换或计算。

下面的示例阐释了此模式。 在此示例中,TimePeriod 类表示时间间隔。 在内部,该类将时间间隔以秒为单位存储在名为 _seconds 的私有字段中。 名为 Hours 的读-写属性允许客户以小时为单位指定时间间隔。 getset 访问器都会执行小时与秒之间的必要转换。 此外,set 访问器还会验证数据,如果小时数无效,则引发 ArgumentOutOfRangeException

public class TimePeriod
{
    private double _seconds;

    public double Hours
    {
        get { return _seconds / 3600; }
        set
        {
            if (value < 0 || value > 24)
                throw new ArgumentOutOfRangeException(nameof(value),
                      "The valid range is between 0 and 24.");

            _seconds = value * 3600;
        }
    }
}

可以访问属性以获取和设置值,如以下示例所示:

TimePeriod t = new TimePeriod();
// The property assignment causes the 'set' accessor to be called.
t.Hours = 24;

// Retrieving the property causes the 'get' accessor to be called.
Console.WriteLine($"Time in hours: {t.Hours}");
// The example displays the following output:
//    Time in hours: 24

表达式主体定义

属性访问器通常由单行语句组成,这些语句只分配或只返回表达式的结果。 可以将这些属性作为 expression-bodied 成员来实现。 => 符号后跟用于为属性赋值或从属性中检索值的表达式,即组成了表达式主体定义。

只读属性可以将 get 访问器作为 expression-bodied 成员实现。 在这种情况下,既不使用 get 访问器关键字,也不使用 return 关键字。 下面的示例将只读 Name 属性作为 expression-bodied 成员实现。

public class Person
{
    private string _firstName;
    private string _lastName;

    public Person(string first, string last)
    {
        _firstName = first;
        _lastName = last;
    }

    public string Name => $"{_firstName} {_lastName}";
}

getset 访问器都可以作为 expression-bodied 成员实现。 在这种情况下,必须使用 getset 关键字。 下面的示例阐释如何为这两个访问器使用表达式主体定义。 return 关键字不与 get 访问器一起使用。

public class SaleItem
{
    string _name;
    decimal _cost;

    public SaleItem(string name, decimal cost)
    {
        _name = name;
        _cost = cost;
    }

    public string Name
    {
        get => _name;
        set => _name = value;
    }

    public decimal Price
    {
        get => _cost;
        set => _cost = value;
    }
}

自动实现的属性

在某些情况下,属性 getset 访问器仅向支持字段赋值或仅从其中检索值,而不包括任何附加逻辑。 通过使用自动实现的属性,既能简化代码,还能让 C# 编译器透明地提供支持字段。

如果属性具有 getset(或 getinit)访问器,则必须自动实现这两个访问器。 自动实现的属性通过以下方式定义:使用 getset 关键字,但不提供任何实现。 下面的示例与上一个示例基本相同,只不过 NamePrice 是自动实现的属性。 该示例还删除了参数化构造函数,以便通过调用无参数构造函数和对象初始值设定项立即初始化 SaleItem 对象。

public class SaleItem
{
    public string Name
    { get; set; }

    public decimal Price
    { get; set; }
}

自动实现的属性可以为 getset 访问器声明不同的可访问性。 通常声明一个公共 get 访问器和一个专用 set 访问器。 可以在有关限制访问器可访问性的文章中了解详细信息。

必需的属性

从 C# 11 开始,可以添加 required 成员以强制客户端代码初始化任何属性或字段:

public class SaleItem
{
    public required string Name
    { get; set; }

    public required decimal Price
    { get; set; }
}

若要创建 SaleItem,必须使用对象初始值设定项设置 NamePrice 属性,如以下代码所示:

var item = new SaleItem { Name = "Shoes", Price = 19.95m };
Console.WriteLine($"{item.Name}: sells for {item.Price:C2}");

C# 语言规范

有关详细信息,请参阅 C# 语言规范中的属性。 该语言规范是 C# 语法和用法的权威资料。

另请参阅