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 đó. Trong dịp ra mắt Visual Studio 2015, Microsoft cũng trình làng C# 6. Trong bài này, tôi sẽ điểm qua 10 tính năng mới của C# 6.
Gán giá trị ban đầu cho property
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
2
3
4
5
6
7
8
9
10
11
class Student
{
public int Id { get; set; }
public string Name { get; set; }
public Student()
{
Id = 0;
Name = "John Doe";
}
}
Trong C# 6, 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
2
3
4
5
class Student
{
public int Id { get; set; } = 0;
public string Name { get; set; } = "John Doe";
}
Property 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
2
3
4
5
6
7
8
9
10
11
class Student
{
public int Id { get; private set; }
public string Name { get; private set; }
public Student(int id, string name)
{
Id = id;
Name = name;
}
}
Trong C# 6, 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
2
3
4
5
class Student
{
public int Id { get; } = 0;
public string Name { get; } = "John Doe";
}
Hoặc ta gán giá trị trong constructor:
1
2
3
4
5
6
7
8
9
10
11
class Student
{
public int Id { get; }
public string Name { get; }
public Student(int id, string name)
{
Id = id;
Name = name;
}
}
Định nghĩa 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, 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Student
{
public string Name { get; set; }
public int BirthYear { get; set; }
public int Age
{
get
{
return DateTime.Now.Year - BirthYear;
}
}
public override string ToString()
{
return "Name: " + this.Name;
}
}
Trong phiên bản 6, đoạn code trên có thể rút gọn hơn:
1
2
3
4
5
6
7
public class Student
{
public string Name { get; set; }
public int BirthYear { get; set; }
public int Age => DateTime.Now.Year - BirthYear;
public override string ToString() => "Name: " + this.Name;
}
Ở 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 dùng cú pháp lambda.
Lệnh using static
Câu lệnh using
chỉ dành cho namespace. Tuy nhiên, C# 6 mở rộng câu lệnh này để dùng cho cả static class
.
1
2
3
4
5
6
7
8
9
using static System.Console;
public class Program
{
public static void Main()
{
WriteLine("Hello world");
}
}
Ta dùng using static
để chỉ static class
cần dùng, trong đoạn code trên là Console
. Trong Main()
, ta không cần gõ Console
mà dùng ngay WriteLine()
.
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
2
3
4
5
string name = "John Doe";
if (customer != null)
{
name = customer.Name;
}
Với C# 6, 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.
Chèn giá trị vào 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
2
3
4
public override string ToString()
{
return string.Format("Name: {0}", this.Name);
}
Trong C# 6, ta dùng cú pháp đặc biệt để chèn trực tiếp giá trị vào chuỗi mà không cần các 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.
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 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
2
3
4
5
6
7
public void Add(Student student)
{
if (student == null)
{
throw new ArgumentException("Error: " + nameof(student));
}
}
Gán giá trị ban đầu cho collection bằng cú pháp 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
2
3
4
5
var users = new Dictionary<string, User>()
{
{ "manager", new User("John Doe") },
{ "receptionist", new User("Jane Doe") }
};
Dòng lệnh 3 và 4 không thể hiện rõ chuỗi manager
là key và new User()
là value. Trong C# 6, ta có thể dùng cú pháp index để gán giá trị. Cú pháp này thể hiện rõ đâu là key và đâu là value.
1
2
3
4
5
6
var users = new Dictionary<string, User>()
{
["manager"] = new User("John Doe"),
["receptionist"] = new User("Jane Doe"),
["manager"] = new User("Tony Fakes")
};
Đặc biệt, ở dòng 5, tôi cố tình thêm key đã tồn tại ở dòng 3 và C# compiler không hề phản đối. Với cú pháp index, nếu key đã có trong Dictionary thì nó sẽ cập nhật giá trị mới mà không quăng exception.
Lọc exception
Bắt exception 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 exception dựa trên kiểu của nó. Trong C# 6, Microsoft bổ sung tính năng lọc exception bằng cách kiểm tra điều kiện cho trước.
1
2
3
4
5
6
7
8
try
{
// Statements
}
catch (Exception ex) when (ex.Data.Count > 0)
{
// Handle exception
}
Ta dùng từ khóa when
và theo sau là biểu thức điều kiện. Nếu thỏa, khối lệnh của catch
sẽ được thực thi.
Dùng await trong catch và finally
Ở phiên bản trước, C# không cho dùng từ khóa await
trong khối lệnh catch
. Tuy nhiên, C# 6 đã cho phép làm điều này.
1
2
3
4
5
6
7
8
try
{
// Statements
}
catch (Exception ex)
{
await FileHelper.WriteAsync(ex.Message);
}
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 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. Trong suốt 15 năm qua, C# không ngừng cải tiến, và trong bản 7, chắc chắn nó sẽ có thêm nhiều tính năng mới thú vị.