Bạn đã bao giờ nghe đến khái niệm ép kiểu dữ liệu chưa? Trong C# (hoặc các ngôn ngữ khác), khi bạn muốn chuyển kiểu dữ liệu này về kiểu dữ liệu khác, ví dụ float sang int, thì đó chính là ép kiểu. Việc ép kiểu này được chia làm hai loại loại: ép kiểu tường minh (explicit) và ép kiểu ngầm định (không tường minh - implicit).
Nhưng bạn có bao giờ bạn tự hỏi: “Mình có thể tự định nghĩa “cách ép kiểu” giữa hai class được không nhở?”. Câu trả lời là được - hoàn toàn có thể. Trong lập trình hướng đối tượng, chúng ta thường làm việc với các đối tượng được định nghĩa bởi class. Và C# cho phép bạn định nghĩa cách chuyển đổi ép kiểu giữa hai class – kể cả khi chúng không có quan hệ kế thừa.
Để định nghĩa “cách ép kiểu” trong C#, người ta đã tạo ra toán tử explicit/implicit. Vậy câu hỏi đặt ra tiếp theo, nó sẽ giải quyết được những vấn đề gì nhở? Bạn nghĩ ra được cái nào không?
Nghĩ ra thì để lại tui một comment nhé!
Và để mình kể một hai case ứng dụng mình đang xài 2 toán tử này nhé!
Ví dụ 1 - Chuyển model về DTO
Thường ở một số project nhỏ, khi chúng ta có các model và cần chuyển nó về DTO, thì thường mình sẽ map thủ công thay vì xài automapper. Và để map thủ công thì mình sẽ định nghĩa một static method tên là “ToDto”, để khi gọi method này và truyền vào model thì nó chuyển model về DTO cho mình. Với cách này, mỗi lần xài thì chỉ cần gọi <tên class>.ToDto(<giá trị model>) là được. Vậy có cách nào ngầu hơn cách này không? Câu trả lời là có! Có một cách khác ngầu hơn và cũng sạch hơn: dùng toán tử explicit.
Để mình ví dụ bằng code, trong code mình sẽ dùng cách 1 là xài ToDto()
, cách hai là xài explicit operator
, sau đó bonus thêm cách về implicit operator
.
Ví dụ code:
1 | namespace ConsoleApp; |
Bạn thấy công dụng của từ khóa explicit operator
chưa. Ở đây công dụng của nó là ép một kiểu về một kiểu tường minh (StudentDto)studentModel
model về dto, nhìn cũng dễ hiểu nữa mà phải không.
Ở đây có một lưu ý:
Bạn có thấy public static explicit operator StudentDto(StudentModel model)
được khai báo trong cả hai class StudentModel
và StudentDto
không? Mình đã comment method này ở class StudentDto
. Nhưng nếu bạn đặt method này ở một trong hai class, chương trình vẫn chạy được. Thật ra bạn khai báo nó class nào cũng được (nhưng chỉ một trong hai thôi nhé), nhưng dễ best practice và thuận theo tư duy tự nhiên của dev: khi ép từ A sang B thì nên khai báo trong A, vậy nên ta nên khai báo ở class StudentModel
cho thống nhất nhé.
Vậy nếu bạn không muốn ghi rõ kiểu cần ép thì sao? Hãy xài implicit operator!
Cũng bài toán trên, bây giờ bạn chỉ cần chuyển đổi public static explicit operator StudentDto(StudentModel model)
sang public static implicit operator StudentDto(StudentModel model)
là được.
1 | namespace ConsoleApp; |
Với implicit operator
bạn không thể xài từ khóa var được (trình biên dịch tưởng bạn gán giá trị bình thường, chứ không phải ép kiểu), và cần ghi rõ kiểu khai báo, nhưng đổi lại sẽ không cần ép kiểu. Với mình thấy thì việc ép kiểu rõ ràng tường minh sẽ tốt hơn.
Ví dụ 2 - Không cần override hàm ToString()
Cũng lấy ví dụ trên, giả dụ bây giờ bạn muốn “ép kiểu” DTO sang string thay vì override ToString()
, bạn có thể định nghĩa toán tử explicit như sau:
1 | public static explicit operator string(StudentDto dto) |
Và sau đó, khi cần in ra:
1 | Console.WriteLine((string)studentDto2); // Sẽ dùng explicit operator string |
Tuy nhiên, lưu ý rằng Console.WriteLine(studentDto1)
sẽ vẫn gọi ToString()
. Toán tử ép kiểu chỉ chạy khi bạn ép rõ ràng bằng (string).
Vậy từ ví dụ 1, chúng ta có thể nào xài implicit operator
để không cần ép kiểu luôn được không? Được, nhưng hãy cẩn thận.
1 | public static implicit operator string(StudentDto dto) |
Và sau đó, khi cần in ra:
1 | Console.WriteLine(studentDto2); // Sẽ dùng implicit operator string |
Sao mình bảo cẩn thận, thật ra runtime biết rõ studentDto2
đang là kiểu StudentDto
, khi không có override method ToString()
nó vẫn hiểu rằng, nó cần in ra 1 string từ StudentDto
nên đã tự động ép kiểu ngầm định. Chứ ép kiểu ngầm định kiểu này về bản chất vẫn không thay thế được ToString()
.
Code cho ví dụ này:
1 | namespace ConsoleApp; |
Ví dụ 3 - Gán giá trị
Giả dụ mình có class Student
gồm hai trường Name
và Age
, mình không muốn xài từ khóa new
, chỉ muốn gán nó với 1 chuỗi string thì nó tự động tạo object Student
cho mình. Được không nhỉ.
Ý tưởng là vậy, nếu hiểu bản chất thì chúng ta có thể biến tấu theo cách chúng ta muốn.
Code ví dụ về ý tưởng này:
1 | namespace ConsoleApp; |
Khi thực hiện gán (ép kiểu) Student student = "Nguyen Van A;25"
, trình biên dịch sẽ tìm và gọi method public static implicit operator Student(string data)
sau đó từ chuỗi string sẽ parse dữ liệu và tạo object.
Tóm lại
Dù là implicit hay explicit operator thì khi tạo method, nó sẽ tuân thủ rule như sau:
- phải là static method.
- phải để public.
- “tên hàm” là kiểu trả về.
- tham số là kiểu + dữ liệu bạn muốn ép sang kiểu trả về.
- không thể đặt hai
explicit/implicit operator
có cùng kiểu trả về và tham số trong một class.
Ví dụ public static explicit operator StudentDto(StudentModel model)
- định nghĩa public static.
- kiểu trả về
StudentDto
. - tham số là dữ liệu bạn muốn ép,
StudentModel model
; dữ liệu này cần ép về kiểu trả về.
=> bạn muốn épStudentModel
vềStudentDto
. - và ép từ
StudentModel
sangStudentDto
nên method ép này nên được đặt trongStudentModel
.
Tham khảo
- [User-defined explicit and implicit conversion operators] (https://learn.microsoft.com/vi-vn/dotnet/csharp/language-reference/operators/user-defined-conversion-operators)