表达式体成员
本教程共 100 篇 · 第 53 篇 · 更新于 2026-07-31 · 约 7 分钟阅读
53. 表达式体成员
本节目标:学完你能用 => 把简单成员写成一行,知道哪些地方适合、哪些地方别滥用。
有些成员的逻辑只有一句话:方法算个平方、属性返回一个拼接字符串、构造器给字段赋值。C# 允许用 =>(lambda 运算符,也叫「火箭运算符」)把这些成员写成表达式体(expression-bodied),省掉大括号和 return。
可以把表达式体想象成「电梯直达」:普通成员写法像走楼梯(先进门、再上楼、再出来),表达式体像按一下直接到层——目的地一样,只是更短。但楼梯也有楼梯的好:路远、弯多时,还是走楼梯稳当。
一、表达式体方法
最常用在方法上。左边是签名,右边是一个表达式,编译器自动把它当返回值:
namespace CSharpDemo;
int Square(int x) => x * x;
void Print(string s) => Console.WriteLine(s);
Console.WriteLine(Square(6)); // 36
Print("你好");
右边只能是一个表达式,不能是一串语句。想写多步逻辑,还是得用普通方法体。
为什么省得漂亮
它本质是语法糖,编译出的 IL 和写 { return ...; } 完全一样。价值在于少噪音:一眼就能看出「这个方法就干这一件事」。对读代码的人来说,「短」降低了认知负担。
二、表达式体属性
只读属性特别适合这种写法,getter 直接跟一个表达式:
namespace CSharpDemo;
class Person
{
public string Name { get; set; } = "匿名";
public int BirthYear { get; set; }
public int Age => DateTime.Now.Year - BirthYear;
}
var p = new Person { Name = "小明", BirthYear = 2000 };
Console.WriteLine($"{p.Name} 今年 {p.Age} 岁");
Age 没有存字段,每次访问现算。它等价于 get { return ...; },只是更短。
Note表达式体属性只能是只读(只有
get)。需要set时仍用普通属性;不过 C# 14 起可以配合field关键字写set => field = value,后面会提。
三、表达式体构造器与析构器
构造器也能用 => 给字段赋值:
namespace CSharpDemo;
class Person
{
public string Name { get; }
public Person(string name) => Name = name;
}
var p = new Person("小红");
Console.WriteLine(p.Name); // 小红
终结器(finalizer,也叫析构器)同理:~Person() => Console.WriteLine("释放");。它们不带 return,右边是执行语句。
四、表达式体索引器与运算符
索引器返回元素,运算符返回新对象,写成表达式体都很自然:
namespace CSharpDemo;
class Bag
{
private string[] items = ["苹果", "香蕉", "橙子"];
public string this[int i] => items[i];
}
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) => (X, Y) = (x, y);
public static Point operator +(Point a, Point b) =>
new Point(a.X + b.X, a.Y + b.Y);
}
var bag = new Bag();
Console.WriteLine(bag[1]); // 香蕉
var p1 = new Point(1, 2);
var p2 = new Point(3, 4);
var sum = p1 + p2;
Console.WriteLine($"({sum.X}, {sum.Y})"); // (4, 6)
五、C# 14 的 field 关键字
过去要校验赋值时,属性得手写私有字段。C# 14 引入 field 关键字代表编译器生成的幕后字段,省去声明:
namespace CSharpDemo;
class Message
{
public string Text
{
get;
set => field = value ?? throw new ArgumentNullException(nameof(value));
}
}
var m = new Message { Text = "hi" };
Console.WriteLine(m.Text);
这里的 set 用表达式体配合 field,既简洁又做了空检查。如果类型里恰好也有个叫 field 的标识符,用 @field 或 this.field 区分即可。
Tip
field是 C# 14 新特性,旧版本编译器不认。写兼容老项目的代码时,仍用手动私有字段的写法。
六、什么时候别用
表达式体不是越多越好。两个反例:
第一个:逻辑一长串还硬挤成一行,可读性反而更差。超过一屏或一个表达式装不下,就用普通方法体。
第二个:带副作用的复杂操作。比如表达式体里塞进多个方法调用和分支,读者很难一眼看清。简单、纯计算、一眼能懂的才适合。
来看一个反例——把多步逻辑硬塞进表达式体:
namespace CSharpDemo;
// 不推荐:一行塞太多,读了才知在干嘛
int Score(string s) =>
s.Length > 0 ? (s[0] == 'A' ? 10 : 5) + (s.Contains('x') ? 3 : 0) : 0;
这种写法编译器能过,但人读起来要拆半天才懂。一旦逻辑超过「一个清晰的计算」,就退回普通方法体,把步骤拆开。
Note表达式体只是写法更短,不提升性能,也不改变语义。团队风格统一比「能写多短」更重要。
七、与 C# 12 主构造器、记录的搭配
C# 12 的主构造器(primary constructor)和记录(record)天然适合表达式体。比如给一个记录加个计算属性:
namespace CSharpDemo;
record Point(int X, int Y)
{
public double DistanceFromOrigin => Math.Sqrt(X * X + Y * Y);
}
var p = new Point(3, 4);
Console.WriteLine(p.DistanceFromOrigin); // 5
主构造器把参数直接变成员,DistanceFromOrigin 再用表达式体算了一步,整段非常紧凑。表达式体在「数据载体 + 少量派生属性」这种类型里尤其好用。
Tip记录本就强调「值」与「不可变」,配合只读的表达式体属性,能写出既短又安全的类型。需要可变状态时再用普通 class。
八、与其它写法的对比
表达式体 vs 普通方法体:两者编译结果相同,区别只在可读性。经验法则——「一句话能说清」的用 =>;需要多步、需要 if/循环、需要 try 的,用普通体。
表达式体 vs Lambda:别搞混。成员上的 => 是表达式体成员(成员定义的一部分);方法体里 x => x * 2 才是 Lambda(一个值)。两者都用 => 符号,但语境不同。
新手常混淆的另一点是:表达式体成员「没有自己的作用域」。它背后就是那个成员的方法体,所以里面用的变量(如 field、其他成员)和写普通方法体时完全一样,并不会因为写成了 => 就多一层闭包。把它当成普通体的「单行简写」最不容易想错,也最不容易写出意外捕获。
九、最佳实践
- 只读属性、简单计算属性优先用表达式体。
- 单行
return的方法改成表达式体,主流程更清爽。 - 构造器里「只赋一个字段」可用表达式体;多步初始化还是用普通体。
- 运算符、索引器这类「返回一个值」的成员很适合。
- 一行塞不下或带复杂分支时,果断退回普通方法体,别逞强。
十、循序渐进:从普通体到表达式体
初学时可以先全部写普通方法体,等熟悉了再「翻译成」表达式体。两者语义完全相同,没有谁更高级。一个判断标准:读这行代码时,你能不能在半秒内说出「它返回/做了什么」?能,就用 =>;犹豫,就退回大括号。
想象点奶茶:普通体是「打开菜单—选口味—确认下单」三步;表达式体是「报一个招牌组合」一步到位。熟客用一步,新手用三步都行,店员(编译器)最后都一样做出来。表达式体只是让你写顺手之后更省事,绝不是硬指标。
十一、小结
=> 适用于「一句话就能说清」的成员:只读属性、简单方法、构造器赋值、索引器和运算符。它减少样板代码,但别为了短而牺牲清晰。记住:语法糖的甜,建立在读得懂的前提上。C# 14 的 field 关键字进一步让带校验的属性也能写得很短,配合 C# 12 的主构造器/记录更是相得益彰。