C# 属性(Property)

自动属性、`init`、`required`、计算属性、以及属性 vs 字段。

Intermediate
Key Points属性是方法不是字段,可以加逻辑、控制可见性、延迟计算。

四种写法

C#
class Demo
{
    // 1. 自动属性:编译器生成隐藏字段
    public int A { get; set; }

    // 2. 只读自动属性:只能在构造函数赋值
    public int B { get; }

    // 3. init:只能在对象初始化器赋值
    public int C { get; init; }

    // 4. 完整属性:带逻辑
    private int _d;
    public int D
    {
        get => _d;
        set => _d = value < 0 ? 0 : value;
    }

    public Demo(int b) => B = b;
}

var x = new Demo(2) { A = 1, C = 3, D = -5 };
Console.WriteLine($"{x.A} {x.B} {x.C} {x.D}");
Output
1 2 3 0

计算属性

C#
class Order
{
    public decimal UnitPrice { get; set; }
    public int Quantity { get; set; }
    public decimal TaxRate { get; set; } = 0.13m;

    // 每次访问都重新算
    public decimal Subtotal => UnitPrice * Quantity;
    public decimal Tax => Subtotal * TaxRate;
    public decimal Total => Subtotal + Tax;
}

var o = new Order { UnitPrice = 100m, Quantity = 3 };
Console.WriteLine($"{o.Subtotal} {o.Tax} {o.Total}");
Output
300 39.00 339.00

A computed property has no backing field; => is shorthand for get { return ...; }. It always reflects current state — change Quantity and Total follows.

访问修饰符

C#
class Account
{
    // 外部只读,内部可写
    public decimal Balance { get; private set; }

    // 只在派生类可写
    public string Owner { get; protected set; } = "";

    // get 和 set 分别控制
    private string _name = "";
    public string Name
    {
        get => _name;
        internal set => _name = value.Trim();
    }

    public void Deposit(decimal amount) => Balance += amount;
}

var a = new Account();
a.Deposit(100);
Console.WriteLine(a.Balance);
// a.Balance = 1;      // 编译错误:set 是 private
Output
100

required 与 init

C#
class Product
{
    public required string Sku { get; init; }        // 必须赋值
    public required string Name { get; init; }
    public decimal Price { get; init; }
    public DateTime Created { get; init; } = DateTime.Now;
}

var p = new Product { Sku = "A001", Name = "键盘", Price = 199m };
Console.WriteLine($"{p.Sku} {p.Name} {p.Price}");

// var bad = new Product { Sku = "A002" };
// 编译错误:Required member 'Name' must be set
Output
A001 键盘 199

required + init replaces long constructor parameter lists: callers use an object initializer, missing fields fail at compile time, and the object stays immutable afterwards.

延迟初始化

C#
class Report
{
    private List<string>? _cache;

    // 第一次访问才计算
    public List<string> Rows => _cache ??= Load();

    private static List<string> Load()
    {
        Console.WriteLine("(加载中)");
        return new List<string> { "row1", "row2" };
    }
}

var r = new Report();
Console.WriteLine("创建完成");
Console.WriteLine(r.Rows.Count);
Console.WriteLine(r.Rows.Count);      // 不再加载
Output
创建完成
(加载中)
2
2

??= is the shortest lazy init: compute only when null. Three lines shorter than the if version with identical semantics.

属性 vs 字段

AspectFieldProperty
本质变量get/set 方法
能否加逻辑不能能
能否虚方法不能能(virtual)
接口中不能能
数据绑定不支持支持(WPF/Blazor)
序列化通常不序列化常见目标
Changing a public field to a property is a breaking change for both source and binary — start with a property. Public fields are only acceptable as internal/private implementation details.

Friends & Partners