CodeGym /Các khóa học /C# SELF /Làm quen với record v...

Làm quen với record và cú pháp vị trí

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

1. Vấn đề truyền dữ liệu

Giả sử tụi mình có một app cho trường học, và cần truyền thông tin về học sinh giữa các module khác nhau: tên, năm sinh, lớp. Thường thì tụi mình làm sao?

Tụi mình tạo một class kiểu như này:


public class Student
{
    public string Name { get; set; }
    public int YearOfBirth { get; set; }
    public string Class { get; set; }
}
Class để lưu dữ liệu về học sinh (cách cũ)

Nhìn quen ghê. Nhưng cách này có mấy vấn đề:

  • Khi so sánh hai học sinh để kiểm tra bằng nhau, mặc định chỉ so sánh reference thôi. Nghĩa là hai học sinh có trường giống hệt nhau nhưng là hai object khác nhau — vẫn không bằng nhau!
  • Class có thể bị thay đổi sau khi tạo, đôi khi gây lỗi (nhất là khi object đã được dùng ở đâu đó);
  • Rất nhiều code "template": constructor, method so sánh, copy (clone), ToString.

Và chắc bạn cũng đoán ra rồi: C# có thể giúp tụi mình khỏi mấy cái phiền này! Xin giới thiệu — record.

2. record là gì?

record — là một kiểu đặc biệt trong C#, được thiết kế riêng để lưu trữ dữ liệu. Record có hai điểm mạnh chính:

  1. Tính bất biến (immutability): object kiểu record mặc định là bất biến, tức là giá trị property được set một lần khi tạo và không đổi nữa (set setter là private). Tuy có thể khai báo record cho phép thay đổi, nhưng mặc định là bất biến.
  2. So sánh theo giá trị: nếu hai object record có giá trị giống nhau ở tất cả field, chúng được coi là bằng nhau (==.Equals() hoạt động khác hẳn!).

Thật ra, record là wrapper hoàn hảo để truyền dữ liệu giữa các layer của app (ví dụ từ DB lên controller, từ controller sang view, v.v.).

3. Cú pháp record

Cách đơn giản nhất — cú pháp vị trí

Khi chỉ cần truyền một bộ giá trị, tụi mình khai báo kiểu bằng một dòng ngắn gọn:


public record Student(string Name, int YearOfBirth, string Class);
Cú pháp vị trí của record

Bên trong có gì? Compiler tự động tạo cho tụi mình:

  • Property chỉ đọc (setter private);
  • Constructor nhận tất cả tham số;
  • Method so sánh và copy;
  • ToString xịn, format output đẹp luôn!

Dùng record vị trí

Thử dùng kiểu mới này trong app trường học nhé:

var student1 = new Student("Ivan", 2008, "8A");
var student2 = new Student("Maria", 2008, "8B");

Truy cập property như bình thường (chỉ là không sửa được thôi):

Console.WriteLine($"{student1.Name}, {student1.YearOfBirth}, {student1.Class}");

Thử sửa property sau khi tạo

student1.Name = "Petr"; // Lỗi! Property chỉ đọc.

Nếu bạn bỏ comment dòng này — compiler sẽ la liền: không thể set giá trị cho property chỉ đọc.

ToString tự động trông như này

Console.WriteLine(student1); // In ra: Student { Name = Ivan, YearOfBirth = 2008, Class = 8A }

Nhìn đẹp và dễ hiểu luôn, khỏi cần format tay!

4. So sánh object record

Nhắc lại: nếu tạo hai object class truyền thống với data giống hệt, chúng vẫn không bằng nhau:

var a = new Student("Ivan", 2008, "8A");
var b = new Student("Ivan", 2008, "8A");

Console.WriteLine(a == b); // Với class: false

Còn nếu Student là record, thì so sánh đúng như bạn mong muốn:


public record Student(string Name, int YearOfBirth, string Class);

var a = new Student("Ivan", 2008, "8A");
var b = new Student("Ivan", 2008, "8A");

Console.WriteLine(a == b); // Với record: true!
So sánh object record theo giá trị

Tức là hai record có field giống nhau sẽ bằng nhau, dù là hai object khác nhau trong bộ nhớ.

5. Bên trong nó trông thế nào

Sinh viên hay bất ngờ vì compiler làm quá trời thứ cho tụi mình khi dùng record. So sánh cho dễ hình dung, nếu dùng class thì phải tự code như này, còn record thì compiler lo hết.

Class truyền thống tự viết tay

public class Student
{
    public string Name { get; }
    public int YearOfBirth { get; }
    public string Class { get; }

    public Student(string name, int yearOfBirth, string @class)
    {
        Name = name;
        YearOfBirth = yearOfBirth;
        Class = @class; //tham chiếu đến chính class của nó
    }

    public override bool Equals(object? obj)
    {
        if (obj is not Student other) return false;
        return Name == other.Name && YearOfBirth == other.YearOfBirth && Class == other.Class;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Name, YearOfBirth, Class);
    }

    public override string ToString()
    {
        return $"Student {{ Name = {Name}, YearOfBirth = {YearOfBirth}, Class = {Class} }}";
    }
}

Không lạ gì khi coder thành paranoid — tụi mình phải viết đi viết lại y chang nhau!

Record — chỉ một dòng


public record Student(string Name, int YearOfBirth, string Class);
Record — tất cả chỉ một dòng!

6. Record và bất biến: được làm gì, không được làm gì

Record mặc định property chỉ đọc, và điều này giúp tránh rất nhiều bug. Nhưng nếu bạn thực sự cần (ví dụ viết cho API cũ), vẫn có thể khai báo record cho phép thay đổi:

public record MutableStudent
{
    public string Name { get; set; }
    public int YearOfBirth { get; set; }
    public string Class { get; set; }
}

Giờ thì có thể sửa field, nhưng bạn sẽ mất một số lợi thế (ví dụ về an toàn).

7. Destructuring record

Vì cú pháp vị trí rất giống tuple, nên có thể destructure record dễ dàng:


var student = new Student("Ivan", 2008, "8A");

var (name, year, className) = student;

Console.WriteLine($"{name} - {year}, {className}"); // Ivan - 2008, 8A
Destructuring record vị trí

Compiler sẽ tạo method Deconstruct cho mỗi record vị trí, giúp làm việc với LINQ, switch-pattern và nói chung là cuộc sống coder dễ thở hơn.

8. Record là class hay struct?

Mặc định recordkiểu tham chiếu, giống class. Tức là mọi đặc điểm của reference type (lưu trên heap, copy reference, v.v.) đều hoạt động như thường.

Nếu muốn kiểu giá trị (value type), C# cũng có luôn — chỉ cần viết:


public record struct Point(int X, int Y);
Record struct — kiểu giá trị của record

Nhưng để truyền dữ liệu thì gần như luôn dùng record dạng class, tức là reference type. Về record struct sẽ nói kỹ hơn ở bài sau :P

So sánh class, struct, record

Kiểu Bất biến mặc định So sánh theo giá trị Destructuring dễ Tự động ToString
class Không Không (theo reference) Không Không
struct Không Không Không
record

9. Đặc điểm và lỗi thường gặp

Nhiều bạn mới học hay bị rối khi dùng record. Ví dụ, đôi khi nghĩ rằng sửa field của một object record sẽ làm object khác thay đổi (giống class khi copy reference). Không nha! Ngoài ra, khi dùng with, nhớ là luôn tạo bản copy mới, không sửa object gốc. Record cực hợp cho logic mà app của bạn cần dữ liệu sạch và dễ đoán.

Đúng rồi, nếu bạn khai báo field với init thay vì set, thì cũng chỉ set được lúc tạo hoặc qua with, không set sau đó được đâu.

2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
So sánh record theo giá trị
So sánh record theo giá trị
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Destructuring record
Destructuring record
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION