CodeGym /课程 /C# SELF /record 显式主体和

record 显式主体和 record struct

C# SELF
第 19 级 , 课程 3
可用

1. 入门

record 类的定位语法对于简单场景真的很方便:

public record User(string Name, int Age);
定位 record 类

但有时候你会很想给 record 加点方法、非标准属性、改下访问修饰符、或者在构造函数里加点逻辑(比如校验或者“自动”数据转换)。可惜,定位语法没地方加这些!这时候就该用显式主体语法了,如果:

  • 你需要给 record 扩展方法、属性或额外逻辑。
  • 你想控制属性的行为(setter、getter、init、校验)。
  • 你想为不同初始化方式写多个构造函数。
  • 你要加接口或者实现特殊方法。

构建带主体的 record

语法和 class 很像。你肯定见过这种写法:


public class User
{
    /* ... */
}

现在只是把它变成 record


public record User
{
    // 显式定义的属性
    public string Name { get; init; }
    public int Age { get; init; }

    // 额外逻辑
    public string GetGreeting()
    {
        return $"你好,我叫 {Name},我 {Age} 岁!";
    }

    // 自定义构造函数
    public User(string name, int age)
    {
        Name = name;
        if (age < 0)
            throw new ArgumentException("Age 不能是负数!");
        Age = age;
    }
}

有趣的小知识: 如果你显式定义了属性,编译器不会自动为你生成定位语法的属性。一切都得自己来,完全可控。

混合写法:混搭语法

C# 允许你两种方式结合:你可以声明 定位 record,再加个主体:


public record User(string Name, int Age)
{
    public string GetGreeting()
    {
        return $"我是{Name},我{Age}岁!";
    }
}
带主体和额外方法的定位 record

这种情况下,NameAge 这两个属性还是会自动用定位语法生成,你的额外方法就舒服地放在主体里。

2. 和普通 record 的区别——主体细节

  • 显式 record 让你完全控制构造函数、属性、方法。
  • 你可以实现接口或者加自定义比较逻辑,如果对象的标识规则很复杂。
  • 和纯定位 record 不同,现在你只要在主体里声明新属性就行了。

// 新手常犯的错!
public record User(string Name, int Age)
{
    public string Name { get; init; } // ← 冲突! 属性重复声明
}

新手错误: 有些同学会同时写 public record User(string Name, int Age),又在主体里加 public string Name { get; init; },以为这是两个变量。其实不是!这样会冲突(重复声明)。要么全用定位语法,要么全显式定义属性——别混着来。

实用例子

我们继续开发一个控制台应用,用户可以创建订单。假设有个订单类 Order,之前长这样:

public record Order(string Product, int Quantity, double Price);

现在我们需要校验数量(quantity 不能小于 1),还要加个总价属性:


public record Order
{
    public string Product { get; init; }
    public int Quantity { get; init; }
    public double Price { get; init; }
    public double TotalCost => Quantity * Price;

    public Order(string product, int quantity, double price)
    {
        Product = product ?? throw new ArgumentNullException(nameof(product));
        if (quantity < 1)
            throw new ArgumentException("数量不能小于 1!");
        Quantity = quantity;
        Price = price;
    }

    public override string ToString()
        => $"商品: {Product}, 数量: {Quantity}, 总价: {TotalCost}";
}

注意,我们显式实现了属性,都是 init setter(不可变对象),还加了自动计算总价——代码更灵活了!

程序调用:

var order = new Order("自行车", 2, 15000);
Console.WriteLine(order); // 商品: 自行车, 数量: 2, 总价: 30000

3. record struct

struct 的进化

record struct 出现前,C# 的结构体就是“干活的老黄牛”——复制快,存栈里,很适合短小的数据包(比如坐标或颜色)。但它们没有 records 的那些好东西:没有定位语法、with 表达式、默认值比较等“甜点”。

现在 C# 可以用 record 风格声明结构体了:

public record struct Point(int X, int Y);
record struct 的定位语法

这到底有啥用?

  • 自动实现 EqualsGetHashCodeToString —— 你的结构体可以优雅比较和打印!
  • with 克隆语法:var p2 = p1 with { X = 10 };
  • 可以用定位语法或显式主体语法。

对比:传统 struct VS record struct

struct record struct
定位语法 没有
with 表达式 没有
不可变性 没有(默认) 有(init
值比较 没有(默认)
ToString 标准 更好看

record struct 的显式主体语法

和普通 record 一样,只不过是 struct:


public record struct Rectangle
{
    public int Width { get; init; }
    public int Height { get; init; }

    public int Area => Width * Height;

    public Rectangle(int width, int height)
    {
        Width = width > 0 ? width : throw new ArgumentException("宽度 > 0");
        Height = height > 0 ? height : throw new ArgumentException("高度 > 0");
    }

    public void Print()
    {
        Console.WriteLine($"尺寸: {Width} x {Height}, 面积: {Area}");
    }
}

使用例子:

var rect = new Rectangle(10, 7);
rect.Print(); // 尺寸: 10 x 7, 面积: 70

// 克隆并修改宽度,原对象不变
var wideRect = rect with { Width = 20 };
wideRect.Print(); // 尺寸: 20 x 7, 面积: 140

record struct 的特点

  • 它还是 struct —— value type。赋值时会复制!
  • 拥有 records 的所有优点:值比较、with 克隆、好看的 ToString
  • 推荐用于小巧、紧凑、不可变的数据集,尤其想避免堆分配时。
  • 可以用定位参数,也可以显式“展开”主体。

4. 常见错误和坑

过度可变: record struct 如果你用普通字段(比如 public int Value;),不会自动变成不可变。要用 init setter 才是真正的不可变 struct!

值比较: 如果你手动加了新字段(没写在定位语法里),注意:只有构造函数参数或 init setter 的字段才会参与自动值比较。

复制: 这是 struct,所以……全是复制!别和引用类型的 record 混淆了。

with 表达式的坑: 它们总是做浅拷贝,不会深拷贝嵌套对象。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION