首页 / C# 入门教程 / 属性与自动属性

C# 入门教程

属性与自动属性

本教程共 100 篇 · 第 34 篇 · 更新于 2026-07-31 · 约 12 分钟阅读

C#C# 入门教程属性自动属性封装

34. 属性

本节目标:学完你能用属性封装字段,并区分属性与字段各自的用途。

直接把字段 public 暴露出去,外部可以随意赋值,连负数年龄都能塞进来。C# 用**属性(property)**解决这个问题:它像字段一样被读写,却能在内部加逻辑、做校验。

属性包装私有字段

一个属性通常由 getset 两个**访问器(accessor)**组成。get 负责读,set 里的 value 代表外部赋进来的值。字段保持 private,由属性把门。

namespace CSharpDemo;

class Person
{
    private int age;                 // 真正的存储
    public int Age
    {
        get { return age; }
        set
        {
            if (value < 0) value = 0;   // 负数直接归零
            age = value;
        }
    }
}

var p = new Person();
p.Age = -5;
Console.WriteLine(p.Age);    // 0,非法值被拦下
Note

valueset 访问器里的关键字,代表「赋值时传进来的那个值」,类型是属性的类型。

自动属性:少写一堆样板

如果每个属性只是简单地存取值,手写私有字段很啰嗦。C# 3 起提供自动属性(auto-property):写 { get; set; },编译器自动帮你生成背后那块隐藏字段。

namespace CSharpDemo;

class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Score { get; set; }
}

var s = new Student { Id = 1, Name = "小美", Score = 88 };
Console.WriteLine($"{s.Name}{s.Score} 分");

你看不见那个私有字段,但它真实存在,C# 替你管着。初学阶段绝大多数属性都用这种写法。

只读属性:只给 get

有些数据创建后就不许改,比如出生年份、订单号。只留 get(或 { get; })就成了只读属性(read-only property)。配合构造或初始化器赋值最为常见。

namespace CSharpDemo;

class Product
{
    public int Id { get; }
    public string Name { get; }
    public Product(int id, string name)
    {
        Id = id;
        Name = name;
    }
}

var p = new Product(1, "鼠标");
// p.Id = 2;   // 报错:只读属性不能改
Tip

现代写法 { get; } 比老式的 private set; 更明确表达「真的只读」。若只想让类内部能改、外部只能读,用 { get; private set; }

只写属性与计算属性

只写属性(只有 set)很少见,多用于敏感写入。更有用的是计算属性(computed property)get 里现算现返回,背后根本没有字段。

namespace CSharpDemo;

class Rectangle
{
    public int Width { get; set; }
    public int Height { get; set; }
    public int Area => Width * Height;   // 计算属性,无字段
}

var r = new Rectangle { Width = 4, Height = 5 };
Console.WriteLine(r.Area);   // 20

=> ... 是表达式体写法,等价于 get { return Width * Height; },更短。

属性 vs 字段:怎么选

场景用字段用属性
内部临时变量适合没必要
对外公开的数据不推荐推荐
需要校验/计算做不到适合
将来可能加逻辑改起来麻烦加访问器即可
Note

经验法则:对外公开的「数据」一律用属性。字段留给类内部自己用,或者确实是单纯的内部状态。

C# 14 的 field 关键字

C# 14 引入了 field 关键字,让你在属性里直接引用编译器生成的那个隐藏字段,既保留自动属性的简洁,又能写校验,不必再手动声明私有字段。

namespace CSharpDemo;

class Person
{
    public string Name
    {
        get => field;
        set => field = value.Trim();   // 赋值时自动去空格
    }
}

var p = new Person { Name = "  阿强  " };
Console.WriteLine($"[{p.Name}]");   // [阿强]
Tip

field 是 C# 14 新特性(本书基线即 C# 14)。若你的项目还停在老版本,请退回「属性包装私有字段」的传统写法。

属性的底层是什么

属性并不是字段,它编译后变成一对方法:get_X()set_X(value)。你写 p.Age = 18,编译器实际调用 set_Age(18)。所以属性本质是「伪装成字段的方法」,既能像字段一样好用,又能藏着方法逻辑。

Note

因为属性是方法,频繁在 get 里做重计算会有性能代价。计算属性适合轻量派生(如面积),重活别塞进 get

属性还是方法:怎么选

一条实用区分:长得像「数据」、无参数、读起来很快的,用属性;需要参数、可能耗时、或有明显副作用(改了别的状态)的,用方法。

情况用属性用方法
obj.Name适合
obj.CalcTotal(items) 带参数适合
读起来像「取一个值」适合
会连数据库/读文件适合

自动属性也能给初值

自动属性可以直接在声明处赋默认初值,等价于「字段初始化器」。

namespace CSharpDemo;

class Product
{
    public string Name { get; set; } = "未命名";
    public decimal Price { get; set; } = 9.99m;
}

var p = new Product();
Console.WriteLine($"{p.Name} {p.Price}");   // 未命名 9.99

访问器上的不同访问修饰符

getset 可以分别设不同的可见性。常见写法是 public get; private set;:外部能读,只有类内部能改,比纯只读灵活。

namespace CSharpDemo;

class Student
{
    public string Name { get; private set; }
    public Student(string name) => Name = name;
}

var s = new Student("小刚");
Console.WriteLine(s.Name);
// s.Name = "x";   // 报错:set 是 private
Tip

新手踩坑:把「应该只读」的属性写成 { get; set; },外部随手改坏数据。凡是创建后不该变的,就用 { get; }{ get; private set; }

小心暴露集合属性

若属性返回内部集合的引用,外部拿到后能绕过你的逻辑直接改集合内容,封装就破了。常见做法是暴露只读集合(IReadOnlyList),或返回副本。

namespace CSharpDemo;

class Team
{
    private List<string> members = ["小明", "小红"];
    public IReadOnlyList<string> Members => members.AsReadOnly();
    public void Add(string n) => members.Add(n);   // 改集合走方法
}

var t = new Team();
Console.WriteLine(string.Join(",", t.Members));
// t.Members.Add("x");   // 报错:只读集合不能加
Note

暴露可变集合是新手高频 bug:以为属性只读就安全,结果别人改了底层列表。返回 IReadOnlyListIEnumerable 更稳。

表达式体属性的多种形态

除了 get => ...,整个属性都能用表达式体写,包括只读、只写、甚至带逻辑。短小清晰时优先用它。

namespace CSharpDemo;

class Point
{
    public int X { get; set; }
    public int Y { get; set; }
    public double Distance => Math.Sqrt(X * X + Y * Y);   // 表达式体计算属性
}

Console.WriteLine(new Point { X = 3, Y = 4 }.Distance);   // 5

自动属性背后到底是什么

public string Name { get; set; } 编译后等价于「一个私有隐藏字段 + get_Name/set_Name 方法」。你省了打字,编译器替你补齐。所以自动属性既简洁又不损失封装能力,是日常首选。

Tip

该用自动属性就用自动属性;只有需要在 get/set 里写校验或计算时,才退回「私有字段 + 属性」的传统写法。

只读自动属性的常见写法

只读自动属性最顺手的两种写法:一是 { get; } 配合构造或初始化器赋值;二是 { get; private set; } 让类内部后续还能改、外部只读。两者按需取用。

namespace CSharpDemo;

class Book
{
    public string Isbn { get; } = "000";          // 创建后不变
    public string Title { get; private set; }       // 内部可改,外部只读
    public Book(string title) => Title = title;
}

属性命名的约定

属性用 PascalCase(如 UserNameOrderDate),和字段(camelCase)区分开。外部调用时看到大写开头的成员,基本就是在用属性,这是 C# 的通用默契。

Tip

新手容易把属性和字段的命名搞混,导致代码风格不一致。记住:对外公开的数据成员一律 PascalCase 属性,内部私有字段用 camelCase。

方法体属性与简写的选择

get/set 只有一行赋值或返回,用表达式体(=> ...)最清爽;需要多行逻辑、参数校验时,再用传统的花括号写法。可读性优先,不必为了短而短。

小结

属性对外像字段、对内可加逻辑,编译后是 get_X/set_X 方法,自动属性背后就是「隐藏字段 + 访问方法」。传统写法用私有字段 + get/set;日常用自动属性 { get; set; } 最省事,还能直接赋初值。只读用 { get; },想让类内能改外部只读用 { get; private set; }。计算属性用 get 现算,背后无字段。对外数据优先用属性而非公开字段,暴露集合属性时用只读接口防篡改;属性统一用 PascalCase 命名。C# 14 的 field 关键字让校验和自动属性兼得。