CodeGym /課程 /C# SELF /record 明確 body 以及

record 明確 body 以及 record struct

C# SELF
等級 19 , 課堂 3
開放

1. 前言

record 類別的 positional 語法真的很方便,適合簡單情境:

public record User(string Name, int Age);
Positional record 類別

但有時你會很想幫 record 加一些自訂方法、不一樣的屬性、改變存取修飾詞,或是在建構子裡加點邏輯(像是驗證或自動轉換資料)。可惜 positional record 沒地方加這些!這時候就該用明確 body 的語法了,如果:

  • 你想擴充 record 加方法、屬性或額外邏輯。
  • 你需要控制屬性的行為(setter、getter、init、驗證)。
  • 你想要多個不同的建構子,對應不同初始化方式。
  • 你要加 interface 或實作特殊方法。

打造有 body 的 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;
    }
}

有趣小知識: 如果你明確定義自己的屬性,編譯器不會自動幫你產生 positional 語法的屬性。一切都很明確、很可控。

混合型:hybrid 語法

C# 允許你兩種都用:你可以宣告 positional record,再加上 body:


public record User(string Name, int Age)
{
    public string GetGreeting()
    {
        return $"我是 {Name},我 {Age} 歲!";
    }
}
有 body 跟額外方法的 positional record

這種寫法 NameAge 屬性還是會自動產生,然後你自訂的方法就可以寫在 body 裡面。

2. 跟一般 record 的差異 — body 的細節

  • 明確 record 可以完全控制建構子、屬性、方法。
  • 你可以實作 interface 或加自訂比較邏輯,適合複雜的物件識別規則。
  • 跟單純 positional record 不一樣,要加新屬性只要在 body 宣告就好。

// 新手錯誤!
public record User(string Name, int Age)
{
    public string Name { get; init; } // ← 衝突! 屬性重複宣告
}

新手常犯錯: 有些同學會同時寫 public record User(string Name, int Age),又在 body 裡加 public string Name { get; init; },以為這是兩個不同變數。其實不是!這樣會衝突(重複宣告)。要嘛全用 positional,要嘛全部明確定義屬性——不要混著來。

實用範例

我們繼續開發一個 console 應用程式,讓使用者建立訂單。假設有個訂單類別 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# 的 struct 就像「簡單的工作馬」——複製快、存在 stack,很適合短小的資料包(像座標或顏色)。但它沒有 record 的好處:沒有 positional 語法、with 表達式、預設值比較、還有其他「甜甜的功能」。

現在 C# 可以用 record 風格宣告 struct:

public record struct Point(int X, int Y);
record struct 的 positional 語法

這到底有什麼好處?

  • 自動實作 EqualsGetHashCodeToString —— 你的 struct 也能漂亮比較、列印!
  • with 複製語法:var p2 = p1 with { X = 10 };
  • 可以用 positional 或明確 body 語法。

比較:傳統 struct VS record struct

struct record struct
Positional 語法 沒有
with 表達式 沒有
不可變性 沒有(預設) 有(init
值比較 沒有(預設)
ToString 預設 漂亮

record struct 的明確 body 語法

跟一般 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。賦值時會複製!
  • 有 record 的所有優點:值比較、with 複製、漂亮的 ToString
  • 推薦用在小型、精簡、不可變的資料集,想避免 heap 配置時。
  • 可以用 positional 參數或明確 body。

4. 常見錯誤與地雷

太容易變動: record struct 不會自動讓欄位不可變,如果你像 public int Value; 這樣宣告,還是 mutable。要用 init setter 才是真的 immutable struct!

比較: 如果你手動加新欄位(沒放在 positional 語法裡),要注意:只有建構子或有 init setter 的欄位,才會參與自動值比較。

複製: 這是 struct,所以... 都是複製!不要跟 reference record 混淆。

with 表達式的誤會: 它們永遠只做 shallow copy,不會深層複製巢狀物件。

2
任務
C# SELF, 等級 19, 課堂 3
上鎖
建立具有明確主體的 record
建立具有明確主體的 record
2
任務
C# SELF, 等級 19, 課堂 3
上鎖
操作 record struct
操作 record struct
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION