Khám phá C# 6

Nếu tôi có một danh sách các ngôn ngữ lập trình yêu thích thì C# sẽ nằm đầu danh sách đó. Tôi từng dùng qua nhiều ngôn ngữ, nhưng chỉ có C# khiến tôi cảm thấy thoải mái, thân thuộc. Cứ mỗi phiên bản mới được phát hành, C# như hổ mọc thêm cánh. Trong dịp ra mắt Visual Studio 2015, Microsoft cũng trình làng luôn C# 6.0. Visual Studio là IDE mạnh nhất hiện nay, và khi phối hợp với C# thì ta có một cặp đôi hoàn hảo. Những tính năng mới trong C# 6.0 tuy không lớn nhưng nó làm cho mã C# trở nên rõ ràng và tinh gọn hơn. Trong bài này, tôi sẽ điểm qua 10 tính năng mới của C# 6.0.

1. Gán giá trị ban đầu cho thuộc tính

Trong những phiên bản trước, để gán giá trị ban đầu cho property, ta phải gán thông qua constructor. Hãy xem qua ví dụ sau:

 1 class Student
 2 {
 3     public int Id { get; set; }
 4     public string Name { get; set; }
 5 
 6     public Student()
 7     {
 8         Id = 0;
 9         Name = "John Doe";
10     }
11 }

Trong C# 6.0, ta có thể gán giá trị trực tiếp vào property khi khai báo chúng giống như gán giá trị cho biến.

1 class Student
2 {
3     public int Id { get; set; } = 0;
4     public string Name { get; set; } = "John Doe";
5 }

2. Thuộc tính chỉ đọc

C# không cho phép dùng từ khóa const đối với property để biến nó thành thuộc tính chỉ đọc. Ta cũng không thể khai báo property chỉ có get mà không có set. Do vậy, cách duy nhất để tạo một thuộc tính chỉ đọc là cho set thành private hoặc protected.

 1 class Student
 2 {
 3     public int Id { get; private set; }
 4     public string Name { get; private set; }
 5 
 6     public Student(int id, string name)
 7     {
 8         Id = id;
 9         Name = name;
10     }
11 }

Trong C# 6.0, ta được phép khai báo property mà chỉ có get và gán giá trị cho nó ngay khi khai báo:

1 class Student
2 {
3     public int Id { get; } = 0;
4     public string Name { get; } = "John Doe";
5 }

Hoặc ta gán giá trị trong constructor:

 1 class Student
 2 {
 3     public int Id { get; }
 4     public string Name { get; }
 5 
 6     public Student(int id, string name)
 7     {
 8         Id = id;
 9         Name = name;
10     }
11 }

3. Thay phần thân method và property bằng biểu thức

Biểu thức lambda là một bước tiến quan trọng của C#. Trong C# 6.0, ta có thể dùng cú pháp của biểu thức lambda để rút gọn phần thân method hoặc property xuống chỉ còn một dòng duy nhất. Hãy xem qua đoạn code sau:

 1 public class Student
 2 {
 3     public string Name { get; set; }
 4     public int BirthYear { get; set; }
 5     public int Age
 6     {
 7         get
 8         {
 9             return DateTime.Now.Year - BirthYear;
10         }
11     }
12 
13     public override string ToString()
14     {
15         return "Name: " + this.Name;
16     }
17 }

Trong phiên bản 6.0, đoạn code trên có thể rút gọn hơn:

1 public class Student
2 {
3     public string Name { get; set; }
4     public int BirthYear { get; set; }
5     public int Age => DateTime.Now.Year - BirthYear;
6     public override string ToString() => "Name: " + this.Name;
7 }

Ở dòng 5, Age đã được chuyển sang cú pháp lambda. Thay vì cung cấp get, ta dùng mũi tên mập (fat arrow). Thậm chí ta không cần dùng từ khóa return, C# sẽ tự động trả về giá trị của biểu thức sau dấu =>. Tương tự, tại dòng 6, ToString() cũng sử dụng cú pháp lambda.

4. Câu lệnh using static

Câu lệnh using chỉ dành cho namespace. Tuy nhiên, C# 6.0 mở rộng câu lệnh này để dùng cho cả lớp static.

1 using static System.Console;
2 
3 public class Program
4 {
5     public static void Main()
6     {
7         WriteLine("Hello world");
8     }
9 }

Ta dùng using static để chỉ ra lớp static cần dùng, ở đây là lớp Console. Trong Main(), ta không cần gõ lại tên lớp Console mà dùng ngay WriteLine().

5. Toán tử điều kiện null

Trước đây, khi cần kiểm tra một đối tượng có phải null hay không, ta dùng câu lệnh if dài dòng:

1 string name = "John Doe";
2 if (customer != null)
3 {
4     name = customer.Name;
5 }

Với C# 6.0, ta có thể dùng cú pháp đặc biệt để đơn giản hóa thao tác kiểm tra giá trị null.

1 string name = customer?.Name ?? "John Doe";

Kí hiệu ?. dùng để kiểm tra giá trị bên trái nó (customer) có phải null hay không. Nếu là null thì nó trả về null. Còn không, nó sẽ đi tiếp sang giá trị bên phải (Name) và kiểm tra null. Nếu giá trị này không phải null thì trả về giá trị đó.

Dấu ?? là toán tử null-coalescing. Nếu biểu thức bên trái có giá trị khác null thì trả về giá trị đó, nếu là null thì trả về giá trị bên phải. Toán tử này có từ các phiên bản trước của C#, và khi phối hợp với ?., ta có một dòng code thực hiện chức năng của 5 dòng code ở trên.

6. Chèn giá trị vào trong chuỗi

Để định dạng chuỗi, ta phải dùng string.Format(). Cách làm này dài dòng nên dễ dẫn đến sai sót.

1 public override string ToString() => string.Format("Name: {0}", this.Name);

Trong C# 6.0, ta dùng cú pháp đặc biệt để chèn trực tiếp giá trị vào trong chuỗi mà không cần các con số đánh dấu vị trí.

1 public override string ToString() => $"Name: {this.Name}";

Ta bắt đầu chuỗi bằng dấu $. Tiếp theo, ta chèn tên biến vào chuỗi và bao quanh bằng cặp dấu ngoặc nhọn. Kết quả của hai dòng lệnh trên là giống nhau, nhưng dòng lệnh dưới trông dễ đọc hơn.

7. Biểu thức nameof

Khi xử lý ngoại lệ, ta thường kèm tên biến vào trong thông báo lỗi để sau này dễ dàng truy lùng chúng. Tuy nhiên, vấn đề nảy sinh khi ta thay đổi tên biến. Lúc này, thông báo lỗi không còn chỉ ra đúng tên biến gây lỗi. Do vậy, C# 6.0 giới thiệu toán tử nameof nhằm lấy ra tên biến mà không phải gõ trực tiếp vào chuỗi.

1 public void Add(Student student)
2 {
3     if (student == null)
4     {
5         throw new ArgumentException("Error: " + nameof(student));
6     }
7 }

8. Gán giá trị ban đầu cho collection bằng cú pháp chỉ số index

Trong phiên bản trước của C#, ta hay dùng cú pháp sau để gán giá trị ban đầu cho một collection:

1 Dictionary<string, User> users = new Dictionary<string, User>()
2 {
3     { "manager", new User("John Doe") },
4     { "receptionist", new User("Jane Doe") }
5 };

Như ta thấy, dòng lệnh 3 và 4 rất mơ hồ. Cú pháp này không chỉ ra rõ ràng chuỗi manager là key và new User() là value. Trong C# 6.0, ta có thể dùng cú pháp chỉ số index để gán giá trị. Cú pháp này thể hiện rõ đâu là key và đâu là value.

1 Dictionary<string, User> users = new Dictionary<string, User>()
2 {
3 	["manager"] = new User("John Doe"),
4 	["receptionist"] = new User("Jane Doe")
5 };

9. Lọc ngoại lệ

Bắt ngoại lệ bằng cú pháp try...catch là thao tác quen thuộc của lập trình viên. Với cú pháp này, ta chỉ bắt được ngoại lệ dựa trên kiểu của nó. Trong C# 6.0, Microsoft bổ sung thêm tính năng lọc ngoại lệ bằng cách kiểm tra một điều kiện cho trước.

1 try
2 {
3     // Statements
4 }
5 catch (Exception ex) when (ex.Data.Count > 0)
6 {
7     // Handle exception
8 }

Ta khai báo điều kiện lọc bằng từ khóa when và theo sau là một biểu thức điều kiện (trả về true hoặc false). Nếu thỏa điều kiện, khối lệnh của catch sẽ được thực thi.

10. Dùng từ khóa await trong khối lệnh catch và finally

Nếu dùng từ khóa await trong khối lệnh catch ở các phiên bản C# trước, ta sẽ nhận thông báo lỗi bảo rằng await không thể dùng trong catch. Vì kĩ thuật lập trình bất đồng bộ đang ngày càng trở nên cần thiết trong ứng dụng hiện đại, nên việc không cho dùng await trong catch là một trở ngại lớn. Do đó, phiên bản C# 6.0 đã cho phép dùng await trong khối catch.

1 try
2 {
3     // Statements
4 }
5 catch (Exception ex)
6 {
7     await FileHelper.WriteAsync(ex.Message);
8 }

Lời kết

Những tính năng trình bày ở trên tuy không đột phá nhưng nó làm cho code C# gọn nhẹ và rõ ràng. Điều này rất quan trọng vì code càng dễ đọc thì khả năng sai sót càng ít. C# là một ngôn ngữ đã trưởng thành, các tính năng chính của nó đã ổn định nên ở bản 6.0 này, Microsoft chỉ tút lại những chỗ còn gai góc để nó mịn hơn. Trong khi viết bài này, tôi nghe phong thanh rằng đã có bản preview các tính năng C# 7.0. Trong suốt 15 năm qua, C# không ngừng cải tiến, và trong bản 7.0, chắc chắn nó sẽ có thêm nhiều tính năng mới thú vị.