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 managerkeynew User()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ị.