CodeGym /Các khóa học /C# SELF /record với body rõ rà...

record với body rõ ràng và record struct

C# SELF
Mức độ, Bài học
Có sẵn

1. Giới thiệu

Cú pháp positional cho record-class thực sự tiện cho mấy trường hợp đơn giản:

public record User(string Name, int Age);
Record-class positional

Nhưng đôi khi bạn lại cực kỳ muốn thêm method cho record, property lạ, đổi access modifier, thêm logic vào constructor (kiểu validation hoặc tự động chuyển đổi dữ liệu). Tiếc là với positional record thì không có chỗ để nhét mấy cái đó! Đến lúc chuyển sang cú pháp body rõ ràng nếu:

  • Bạn cần mở rộng record bằng method, property hoặc logic thêm.
  • Cần kiểm soát hành vi property (setter, getter, init, validation).
  • Muốn tạo nhiều constructor khác nhau cho các kiểu khởi tạo khác nhau.
  • Cần thêm interface hoặc implement method đặc biệt.

Xây record có body

Cú pháp khá giống class. Bạn chắc quen kiểu này rồi:


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

Giờ thì là record nhé:


public record User
{
    // Property định nghĩa rõ ràng
    public string Name { get; init; }
    public int Age { get; init; }

    // Logic thêm
    public string GetGreeting()
    {
        return $"Chào, mình tên là {Name} và mình {Age} tuổi!";
    }

    // Constructor custom
    public User(string name, int age)
    {
        Name = name;
        if (age < 0)
            throw new ArgumentException("Age không thể là số âm!");
        Age = age;
    }
}

Fun fact: Nếu bạn tự định nghĩa property, compiler sẽ không tự tạo property từ positional syntax cho bạn đâu. Tất cả đều rõ ràng, kiểm soát hết!

Kiểu mix: cú pháp hybrid

C# cho phép bạn kết hợp cả hai kiểu: bạn có thể khai báo positional record rồi thêm body:


public record User(string Name, int Age)
{
    public string GetGreeting()
    {
        return $"Mình là {Name}, mình {Age} tuổi!";
    }
}
Positional record có body và method thêm

Trong trường hợp này, property Name và Age vẫn tự động được tạo nhờ positional syntax, còn method thêm thì nằm gọn trong body.

2. Khác biệt với record thường — các nuance của body

  • record rõ ràng cho phép bạn kiểm soát hoàn toàn constructor, property, method.
  • Bạn có thể implement interface hoặc thêm logic so sánh riêng nếu có rule nhận diện object phức tạp.
  • Khác với record positional, muốn thêm property mới thì chỉ cần khai báo trong body record thôi.

// Lỗi newbie!
public record User(string Name, int Age)
{
    public string Name { get; init; } // ← conflict! Khai báo property trùng
}

Lỗi newbie: Một số bạn sinh viên hay vừa viết public record User(string Name, int Age) vừa thêm vào body property public string Name { get; init; }, tưởng là hai biến khác nhau. Không nhé! Sẽ bị conflict (khai báo trùng). Hoặc dùng positional syntax hoàn toàn, hoặc tự định nghĩa property — đừng lẫn lộn.

Ví dụ thực tế

Mình tiếp tục phát triển app console, nơi user tạo đơn hàng. Giả sử có class đơn hàng Order, trước giờ như này:

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

Giờ cần validation cho quantity (quantity không được nhỏ hơn 1) và thêm property tính tổng tiền:


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("Số lượng phải >= 1!");
        Quantity = quantity;
        Price = price;
    }

    public override string ToString()
        => $"Sản phẩm: {Product}, Số lượng: {Quantity}, Tổng: {TotalCost}";
}

Lưu ý là mình đã tự định nghĩa property, dùng init-setter (object bất biến), thêm tự động tính tổng tiền — code linh hoạt hơn hẳn!

Gọi trong chương trình:

var order = new Order("Xe đạp", 2, 15000);
Console.WriteLine(order); // Sản phẩm: Xe đạp, Số lượng: 2, Tổng: 30000

3. record struct

Tiến hóa của struct

Trước khi có record struct, struct trong C# chỉ là "con ngựa thồ đơn giản" — copy nhanh, lưu trên stack, rất hợp cho mấy kiểu dữ liệu nhỏ (như tọa độ hoặc màu sắc). Nhưng nó không có mấy thứ hay ho của record: không có positional syntax, with-expression, so sánh theo value mặc định và các “bánh ngọt” khác.

Giờ thì C# cho phép khai báo struct kiểu record luôn:

public record struct Point(int X, int Y);
Positional syntax cho record struct

Vậy nó cho mình gì?

  • Tự động implement Equals, GetHashCode, ToString — struct của bạn so sánh và in ra đẹp luôn!
  • Có with-clone: var p2 = p1 with { X = 10 };
  • Có thể dùng positional hoặc body rõ ràng.

So sánh: struct cổ điển VS record struct

struct record struct
Positional syntax Không Có
with-expression Không Có
Bất biến Không (mặc định) Có (init)
So sánh theo value Không (mặc định) Có
ToString Mặc định Đẹp

Cú pháp body rõ ràng cho record struct

Y chang record thường, chỉ khác là 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("Chiều rộng > 0");
        Height = height > 0 ? height : throw new ArgumentException("Chiều cao > 0");
    }

    public void Print()
    {
        Console.WriteLine($"Kích thước: {Width} x {Height}, diện tích: {Area}");
    }
}

Ví dụ dùng:

var rect = new Rectangle(10, 7);
rect.Print(); // Kích thước: 10 x 7, diện tích: 70

// Clone và đổi chiều rộng, không ảnh hưởng bản gốc
var wideRect = rect with { Width = 20 };
wideRect.Print(); // Kích thước: 20 x 7, diện tích: 140

Đặc điểm record struct

  • Vẫn là struct — value type. Copy khi gán nhé!
  • Đủ ưu điểm của record: so sánh theo value, with-clone, ToString đẹp.
  • Nên dùng cho bộ dữ liệu nhỏ, gọn, bất biến, khi muốn tránh cấp phát heap.
  • Có thể khai báo positional param hoặc body rõ ràng.

4. Lỗi phổ biến và bẫy

Quá mutable: record struct không tự làm field bất biến nếu bạn khai báo kiểu thường (ví dụ public int Value;). Hãy dùng init-setter cho truly immutable struct nhé!

So sánh: Nếu bạn thêm field mới thủ công (không nằm trong positional syntax), nhớ là: chỉ field trong constructor hoặc có init-setter mới được auto-so sánh theo value.

Copy: Đây là struct, nên... copy hết! Đừng nhầm với record reference type.

Nhầm lẫn with-expression: Nó luôn tạo shallow copy, tức là KHÔNG clone sâu các object lồng bên trong đâu.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Tạo record với body rõ ràng
Tạo record với body rõ ràng
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Làm việc với record struct
Làm việc với record struct
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION