1. 自动属性
在C#里,属性(property)其实就是字段和方法的混合体。说白了,就是语法糖,让你用起来更爽更方便封装。你用起来像字段,实际上里面可以塞各种逻辑:校验、改别的字段、调用方法,甚至给你奶奶发邮件(最后这个别学)。上一节我们还在手写属性,但如果你有一堆超简单的对象,只要“getter”和“setter”,写多了真的腻。
来,认识一下——自动属性。它能帮你省掉模板代码,让C#自己搞定存值的事,你不用操心。
以前怎么写:手动属性
想把字段藏起来,但又想通过属性访问,通常这么写:
public class Dog
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
这种代码就是典型的“啰嗦模板”(boilerplate)。其实大多数时候get/set里啥都不用加!只要能访问就行。
现在可以这样
C#现在更简单了:不用手写字段,编译器帮你搞定。你只要写:
public class Dog
{
public string Name { get; set; }
}
就搞定了。你可以直接读写Name,编译器会在背后自动生成一个私有字段,外部根本拿不到。
幕后到底发生了啥?当你写public string Name { get; set; },编译器会自动生成一个隐藏字段,比如Name__BackingField。每次访问Name,它会插入对应的getter/setter。你在代码里看不到这个字段,但它确实存在——用反射还能找到(不过那是另一个故事了)。
这种写法有啥爽点?首先,代码短多了,不用一直重复。其次,代码更清爽:少点视觉噪音,读起来更舒服。最后,更安全——没人能直接乱动字段,必须通过属性来搞。
2. 自动属性语法
语法超级简单:
public class Dog
{
public string Name { get; set; }
public int Age { get; set; }
}
现在我们的Dog看起来简洁多了!你可以像平时那样创建对象和赋值:
Dog myDog = new Dog();
myDog.Name = "巴尔博斯";
myDog.Age = 4;
图解
| 方式 | 语法 | 值的访问 |
|---|---|---|
| 字段 | |
直接访问 |
| 手动属性 | |
通过get/set访问 |
| 自动属性 | |
get/set + 隐藏字段 |
3. 访问控制:只读、只写
只读:get-only
如果属性只需要读(比如宠物的注册编号),可以只写get,set可以不写,或者设成私有。这样只能在构造函数或声明时赋值:
public class Dog
{
public string RegistrationCode { get; }
public Dog()
{
RegistrationCode = "DOG-001"; // 构造函数里赋值
}
}
现在外部只能读取RegistrationCode,不能改。很适合保护重要数据不被误改。
另一种写法:私有set
public string Name { get; private set; }
这样属性只能在类内部(比如方法或构造函数)改。
只写:不推荐
理论上可以做set-only属性(只能写),但极少见也不推荐——想象一下一个“黑洞”对象:你能往里扔东西,但永远拿不回来。
4. init-only setter
小历史
以前想做不可变对象(immutable),只能写get属性,然后在构造函数里赋值。还行,但有点麻烦。最近C#出了新语法:init-only属性。
init setter到底是啥?
- 只能在创建对象时赋值(也就是构造函数或对象初始化器)。
- 对象创建后,属性就变成只读。
- 很适合“几乎不可变”的类、DTO、配置、模型啥的。
public class Dog
{
public string Name { get; init; }
public int Age { get; init; }
}
现在你可以这样写:
Dog dog = new Dog { Name = "鲍比克", Age = 2 };
dog.Name = "雷克斯"; // 报错! 创建后只能读
构造函数里也能赋值:
public Dog(string name, int age)
{
Name = name;
Age = age;
}
图表:啥时候能赋值
| 赋值位置 | { get; set; } | { get; private set; } | { get; init; } |
|---|---|---|---|
| 类外,创建后 | ✅ | ❌ | ❌ |
| 构造函数 | ✅ | ✅ | ✅ |
| 初始化器 | ✅ | ❌ | ✅ |
| 类内部 | ✅ | ✅ | ❌(除了构造函数) |
5. 实战应用
来,把Dog类用自动属性和init-only属性重写下:
public class Dog
{
// 只能初始化时赋值的属性
public string Name { get; init; }
public int Age { get; init; }
// 自动属性,当前状态
public bool IsHungry { get; set; }
// 方法,狗的动作
public void Bark()
{
Console.WriteLine($"{Name}说:汪!");
}
}
现在你可以这样创建Dog:
Dog dog = new Dog { Name = "沙里克", Age = 3, IsHungry = true };
dog.Bark(); // 输出:沙里克说:汪!
dog.IsHungry = false; // 这个属性可以随时改
dog.Name = "巴尔博斯"; // 报错!只能初始化时赋值
6. 自动属性的初始值
C#就是贴心。你甚至可以直接给属性设初始值:
public class Dog
{
public string Name { get; set; } = "无名";
public int Age { get; set; } = 0;
}
或者配合init:
public class Dog
{
public string Name { get; init; } = "小狗";
public int Age { get; init; } = 0;
}
现在如果创建时没指定Name,它就是“小狗”。
7. 自动属性的访问级别
你可以让属性的读写权限不一样,比如:
public class Dog
{
public string Name { get; private set; }
public int Age { get; private set; }
public Dog(string name, int age)
{
Name = name;
Age = age;
}
}
这样只有Dog类内部(比如方法或构造函数)能改名字和年龄。
手动属性VS自动属性对比
| 方式 | 写起来难度 | set/get控制 | 创建后能不能改 | 模型里用 |
|---|---|---|---|---|
| 手动属性 | 啰嗦 | 完全可控 | 随便 | 需要逻辑时 |
| 自动属性 | 简短 | 不可控 | 随便或私有 | 几乎都能用 |
| init-only自动属性 | 简短 | 不可控 | 只能创建时赋值 | DTO、配置 |
8. 用属性常见的坑
坑1:试图在构造函数外给get-only属性赋值。
如果属性只有get,编译器不让你在别的地方赋值。只能在构造函数或声明时赋。忘了就编译报错。
坑2:字段和属性搞混了。
把字段换成自动属性时,要确保别的代码没直接用字段。字段和属性不是一回事,老代码没改会报错或者程序行为很迷。
坑3:私有set没搞明白。
如果属性有私有set,但别的地方还想赋值——会报错。经常是复制别人的代码时不小心。一定要确认set在你用的地方能不能用。
GO TO FULL VERSION