- [C#] Cách Sử Dụng DeviceId trong C# Để Tạo Khóa Cho Ứng Dụng
- [SQLSERVER] Loại bỏ Restricted User trên database MSSQL
- [C#] Hướng dẫn tạo mã QRcode Style trên winform
- [C#] Hướng dẫn sử dụng temp mail service api trên winform
- [C#] Hướng dẫn tạo mã thanh toán VietQR Pay không sử dụng API trên winform
- [C#] Hướng Dẫn Tạo Windows Service Đơn Giản Bằng Topshelf
- [C#] Chia sẻ source code đọc dữ liệu từ Google Sheet trên winform
- [C#] Chia sẻ source code tạo mã QR MOMO đa năng Winform
- [C#] Chia sẻ source code phần mềm lên lịch tự động chạy ứng dụng Scheduler Task Winform
- [Phần mềm] Tải và cài đặt phần mềm Sublime Text 4180 full version
- [C#] Hướng dẫn download file từ Minio Server Winform
- [C#] Hướng dẫn đăng nhập zalo login sử dụng API v4 trên winform
- [SOFTWARE] Phần mềm gởi tin nhắn Zalo Marketing Pro giá rẻ mềm nhất thị trường
- [C#] Việt hóa Text Button trên MessageBox Dialog Winform
- [DEVEXPRESS] Chia sẻ code các tạo report in nhiều hóa đơn trên XtraReport C#
- [POWER AUTOMATE] Hướng dẫn gởi tin nhắn zalo từ file Excel - No code
- [C#] Chia sẻ code lock và unlock user trong domain Window
- [DEVEXPRESS] Vẽ Biểu Đồ Stock Chứng Khoán - Công Cụ Thiết Yếu Cho Nhà Đầu Tư trên Winform
- [C#] Hướng dẫn bảo mật ứng dụng 2FA (Multi-factor Authentication) trên Winform
- [C#] Hướng dẫn convert HTML code sang PDF File trên NetCore 7 Winform
[C#] Giới thiệu và tìm hiểu những tính năng mới của C# 7.0 trong Visual Studio 2017
Microsoft vừa công bố danh sách những tính năng mới có trong C# 7.0 đi cùng với việc cho ra mắt phiên bản Preview mới nhất của Visual Studio – Visual Studio 2017
Theo Microsoft, C# 7.0 bổ sung rất nhiều tính năng tập trung vào việc xử lý dữ liệu, đơn giản việc code và tối ưu về hiệu suất. Lập trình viên quan tâm đến phiên bản mới nhất của ngôn ngữ lập trình C# này có thể trải nghiệm được những tính năng mới của C# 7.0 ngay bây giờ bằng cách tải về bản Visual Studio 2017 RC tại đây
Vậy C# 7.0 có gì mới? Hãy cũng mình điểm qua các tính năng mới này!
C# 7.0 có gì mới?
1. Tham biến out
Ở những phiên bản C# trước, việc sử dụng từ khóa out
trong các tham số (parameter) của hàm (method/function) không thực sự linh hoạt. Trước khi muốn truyền một biến (variable) vào tham số out (out parameter) của một hàm, bạn cần phải khai báo biến đấy trước rồi mới có thể truyền vào. Vì sao lại nói là không linh hoạt vì thông thường lập trình viên sẽ không khởi tạo giá trị cho các biến này trước khi truyền vào mà giá trị của các biến đó sẽ được ghi đè (overwrite) trong hàm được gọi, ngoài ra lập trình viên cũng không thể khai báo kiểu của tham số out là var
mà cần phải khai báo chính xác kiểu dữ liệu của nó. Ví dụ bên dưới:
public void PrintCoordinates(Point p)
{
int x, y; // khai báo biến trước
p.GetCoordinates(out x, out y); // truyền vào tham số out của hàm GetCoordinates
WriteLine($"({x}, {y})");
}
Tuy nhiên với C# 7.0, lập trình viên có thể khai báo biến trực tiếp trong khi truyền vào tham số out của hàm và cách viết này được gọi là khai báo tham biến out (out variable). Ví dụ bên dưới là cách viết của đoạn code trên sử dụng kiểu khai báo tham biến out trong C# 7.0:
public void PrintCoordinates(Point p)
{
p.GetCoordinates(out int x, out int y);
WriteLine($"({x}, {y})");
}
Lưu ý rằng các tham biến out chỉ được sử dụng trong phạm vi của khối câu lệnh (trong {...}
) do vậy các dòng lệnh bên dưới tham biến out trong cùng một khối câu lệnh sẽ có thể truy xuất được vào nó.
Và với cách viết mới này, C# 7.0 cũng cho phép bạn sử dụng var
thay vì khai báo trực tiếp kiểu dữ liệu của tham số out.
p.GetCoordinates(out var x, out var y);
Thông thường việc sử dụng các tham số out này được ứng dụng trong các mô hình Try...
(trả về true
nếu thực hiện thành công và false
nếu không thành công, ví dụ như int.TryParse
, double.TryParse
, …), tham số out sẽ nhận kết quả thu được:
public void PrintStars(string s)
{
if (int.TryParse(s, out var i)) { WriteLine(new string('*', i)); }
else { WriteLine("Cloudy - no stars tonight!"); }
}
Ngoài ra, Microsoft đang cân nhắc việc cho phép lập trình viên sử dụng *
để bỏ qua những tham số out mà họ không cần đến, được gọi là wildcards:
p.GetCoordinates(out int x, out *); // Chỉ quan tâm đến tham số out x
2. Đối chiếu mẫu (pattern matching)
C# 7.0 giới thiệu một khái niệm mới gọi là pattern (mẫu) cho phép lập trình viên có thể kiểm tra xem biến x
có kiểu dữ liệu là T
hay không và nếu có thì cho phép trích xuất giá trị của biến đó.
Một số ví dụ về pattern trong C# 7.0:
- Constant pattern của
c
(c
là một biểu thức hằng số trong C#), sẽ kiểm tra xem dữ liệu nhập vào có bằng hằng sốc
hay không - Type pattern của
T x
(T
là kiểu dữ liệu vàx
là một định danh cho kiểu dữ liệu đó), sẽ kiểm tra xem dữ liệu nhập vào có kiểu làT
hay không, nếu có thì sẽ trích xuất giá trị của dữ liệu nhập vào vào một biếnx
mới có kiểu dữ liệu làT
- Var pattern của
var x
(x
là một định danh), pattern này không đối chiếu gì cả, nó đơn giản thực hiện công việc là trích xuất giá trị của dữ liệu nhập vào vào một biếnx
mới có kiểu dữ liệu giống với kiểu dữ liệu của giá trị nhập vào
Bạn có thể sử dụng pattern này với 2 cú pháp lệnh sau:
- Biểu thức
is
- Mệnh đề
cause
trong câu lệnhswitch
Cùng đi vào ví dụ cho dễ hình dùng hơn nào!:
3. Biểu thức is với pattern
public void PrintStars(object o)
{
if (o is null) return; // constant pattern "null"
if (!(o is int i)) return; // type pattern "int i", nếu o có kiểu là int, sẽ trích xuất giá trị của o gán vào i
WriteLine(new string('*', i));
}
Bạn có thể thấy cách viết giống với tham biến out ở trên.
Ứng dụng vào trong mô hình Try...
:
if (o is int i || (o is string s && int.TryParse(s, out i)) { /* sử dụng i trong đây */ }
4. Câu lệnh switch với pattern
switch(shape)
{
case Circle c: // shape có kiểu dữ liệu là Circle, gán giá trị của shape vào c
WriteLine($"circle with radius {c.Radius}");
break;
case Rectangle s when (s.Length == s.Height):
WriteLine($"{s.Length} x {s.Height} square");
break;
case Rectangle r:
WriteLine($"{r.Length} x {r.Height} rectangle");
break;
default:
WriteLine("");
break;
case null:
throw new ArgumentNullException(nameof(shape));
}
Có một số lưu ý với cách viết này:
- Cần chú ý với thứ tự của mệnh đề
cause
: Giống với mệnh đềcatch
, mệnh đề nào được đối chiếu mà trùng trước thì sẽ dừng lại ở mệnh đề đó. Với ví dụ ở trên thì việc đặt trường hợpshape
là hình vuông (Rectangle s when (s.Length == s.Height)
) ở trước trường hợpshape
là hình chữ nhật (Rectangle r
) là rất quan trọng. - Mệnh đề
default
luôn luôn được đối chiếu cuối cùng: Trong ví dụ trên kể cả mệnh đềnull
được đặt dướidefault
thì mệnh đềnull
này vẫn được đối chiếu trước rồi sau đó mới đếndefault
. - Mệnh đề
null
đặt cuối cùng luôn có khả năng xảy ra: Type pattern thực chất là một ví dụ của biểu thứcis
nhưng nó không đối chiếu với trường hợpnull
. Điều này đảm bảo rằng giá trịnull
sẽ không vô tình dừng lại ở các mệnh đề đối chiếu khác trước mà bạn cần phải trực tiếp xử lý trong trường hợp rơi vào mệnh đềnull
này (hoặc để mệnh đềdefault
xử lý)
5. Tuple
Trong rất nhiều trường hợp bạn muốn một hàm trả về nhiều hơn 1 giá trị. Có một số cách để làm được điều này với phiên bản C# hiện tại:
- Sử dụng tham số out: Sử dụng tham số out này khá phiền kể cả với cải tiến ở trên, và cách viết này cũng không hỗ trợ cho các hàm
async
. - Sử dụng kiểu dữ liệu trả về
System.Tuple<...>
: Khá rườm rà để sử dụng và đòi hỏi phải khai báo một tuple object trước. - Xây dựng kiểu dữ liệu trả về mới cho từng hàm: Gây ra hiện tượng khai báo quá nhiều kiểu dữ liệu trong khi chúng chỉ được dùng tạm trong phạm vi hẹp.
- Sử dụng kiểu dữ liệu
dynamic
: Không hỗ trợ kiểm tra được kiểu dữ liệu tĩnh và high performance overhead.
Tuy nhiên với C# 7.0, ngôn ngữ này hỗ trợ tốt hơn cho những trường hợp muốn trả về nhiều hơn 1 giá trị bằng cách bổ xung thêm tuple. Cùng tham khảo ví dụ mẫu sau:
(string, string, string) LookupName(long id) // kiểu dữ liệu trả về là tuple
{
... // retrieve first, middle and last from data storage
return (first, middle, last); // tuple literal
}
Bạn có thể thấy hàm LookupName
trên hỗ trợ trả về 3 giá trị và được đóng gói trong 1 tuple
.
Hàm họi đến hàm LookupName
có thể sử dụng tuple này để trích xuất giá trị của các thành phần trong nó:
var names = LookupName(id);
WriteLine($"found {names.Item1} {names.Item3}.");
Các Item1
, Item2
và Item3
trong ví dụ là các tên thành phần mặc định của tuple
và bạn có thể thấy chúng không được rõ ràng và dễ hiểu, C# 7.0 cho phép bạn đặt tên cho các thành phần của tuple
bằng cách khai báo tên sau kiểu dữ liệu. Ví dụ:
(string first, string middle, string last) LookupName(long id) // tuple elements have names
và hàm gọi đến có thể truy xuất bằng cách gọi đích danh tên thành phần cần gọi:
return (first: first, last: last, middle: middle); // named tuple elements in a literal
Lưu ý:
Trong phiên bản Preview 4 hiện chưa hỗ trợ tuple
, để sử dụng được tuple
bạn cần phải tải gói System.ValueTuple từ NuGet về.
6. Deconstruction
Cùng xem ví dụ sau:
(string first, string middle, string last) = LookupName(id1); // deconstructing declaration
WriteLine($"found {first} {last}.");
Đoạn code trên có tác dụng tách tuple được trả về từ hàm LookupName
thành các thành phần và sẽ gán giá trị của các thành phần này sang các biến mới (string first
, string middle
, string last
). Quá trình này được gọi là deconstructing declaration.
Trong quá trình deconstructing declaration, bạn có thể sử dụng var
thay vì khai báo chính xác kiểu dữ liệu cho các biến. Có hai cách viết nếu bạn chọn sử dụng var
:
Cách 1:
(var first, var middle, var last) = LookupName(id1); // var bên trong
Cách 2: Viết rút gọn bằng cách đặt var
bên ngoài dấu ngoặc:
var (first, middle, last) = LookupName(id1); // var bên ngoài dấu ngoặc
Ngoài ra bạn cũng có thể sử dụng các biến có sẵn:
string first;
string middle;
string last;
(first, middle, last) = LookupName(id1); // deconstructing assignment
quá trình trên được gọi là deconstructing assignment.
Ngoài hỗ trợ cho tuple
, các kiểu dữ liệu đều có thể được deconstruct miễn rằng nó có khai báo hàm deconstructor hoặc có hàm deconstructor mở rộng (extension method) theo mẫu sau (các hàm này được gọi là deconstructor method):
public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }
Tham số out sẽ nhận giá trị và sẽ là kết quả của deconstruction. Cùng xem ví dụ mẫu bên dưới:
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) { X = x; Y = y; }
public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}
(var myX, var myY) = GetPoint(); // gọi đến hàm Deconstruct(out myX, out myY);
Tượng tự với tham biến out, Microsoft cũng có kế hoạch áp dụng wildcards vào deconstruction để bỏ qua những thành phần mà lập trình viên không cần đến:
(var myX, *) = GetPoint(); // Chỉ quan tâm đến myX
7. Hàm cục bộ
Trong nhiều trường hợp chúng ta viết ra những hàm mà chỉ được sử dụng duy nhất trong 1 hàm (được gọi là hàm helper) và tất cả các hàm này đều được khai báo chung trong cùng class và điều này dẫn tới việc các hàm helper này đang được coi là hàm thành viên của class đó nhưng thực chất nó chỉ là hàm phụ trợ cho vài hàm thành viên thực thụ của class đó mà thôi. Việc viết ra các hàm helper như vậy khiến class chứa nó trở lên rất lộn xộn và việc quản lý hàm của class trở nên khó khăn hơn đặc biệt với những class có nhiều hàm thành viên. Với C# 7.0, lập trình viên có thể tạo ra các hàm trong một hàm khác, như vậy giúp việc viết ra các hàm helper trở nên gọn gàng và sáng sủa hơn ngoài ra cũng giúp việc quản lý hàm được dễ dàng hơn. Tham khảo ví dụ mẫu sau:
public int Fibonacci(int x)
{
if (x < 0) throw new ArgumentException("Less negativity please!", nameof(x));
return Fib(x).current;
(int current, int previous) Fib(int i)
{
if (i == 0) return (1, 0);
var (p, pp) = Fib(i - 1);
return (p + pp, p);
}
}
Giống với biểu thức lamda (lamda expression), các hàm cục bộ có thể sử dụng được các biến cũng như các tham số của hàm chứa nó.
8. Cải tiến cho việc khai báo chuỗi số
C# 7.0 cho phép bạn thêm dấu gách dưới _
để phân cách giữa các con số. Ví dụ:
var d = 123_456;
var x = 0xAB_CD_EF;
1
2
var d = 123_456;
var x = 0xAB_CD_EF;
Bạn có thể đặt dấu _
ở bất cứ chỗ nào bạn muốn nhằm giúp việc đọc trở nên dễ dàng hơn. Thêm dấu _
không làm ảnh hưởng tới giá trị của biến.
Bạn cũng có thể sử dụng tính năng mới này vào việc khai báo các chuỗi bit để dễ đọc hơn:
var b = 0b1010_1011_1100_1101_1110_1111; // 1010.1011.1100.1101.1110.1111
Hỗ trợ trả về ref và khai báo biến ref cục bộ
Như bạn đã biết thì từ khóa ref
được sử dụng để truyền vào reference thay vì truyền vào giá trị của một biến. Từ khóa ref
này trước đây chỉ được sử dụng cho các tham số của hàm, trong trường hợp bạn muốn trả về reference thay vì trả về giá trị thì sao? Với C# 7.0, bạn có thể gán từ khóa ref
vào trước kiểu dữ liệu trả về của hàm để trả về reference. Cùng xem ví dụ mẫu dưới đây:
public ref int Find(int number, int[] numbers)
{
for (int i = 0; i < numbers.Length; i++)
{
if (numbers[i] == number)
{
return ref numbers[i]; // trả về vị trí lưu trong bộ nhớ, không phải giá trị đơn thuần
}
}
throw new IndexOutOfRangeException($"{nameof(number)} not found");
}
int[] array = { 1, 15, -39, 0, 7, 14, -12 };
ref int place = ref Find(7, array); // tìm vị trí lưu số 7 trong mảng
place = 9; // thay giá trị 7 bằng 9 trong mảng
WriteLine(array[4]); // sẽ in ra 9
Trong ví dụ mẫu trên, hàm Find
sẽ tìm kiếm trong mảng numbers
được truyền vào xem có phần tử nào có giá trị bằng giá trị của tham số number
hay không, nếu có thì sẽ trả về reference của phần tử đầu tiên được tìm thấy trong mảng numbers
, nếu không thì throw ra IndexOutOfRangeException
. Kết quả trả về của hàm Find
được gán vào một biến có tên là place
, biến này được khai báo đi kèm với từ khóa ref
trước kiểu dữ liệu của nó, đây cũng là một tính năng mới của C# 7.0 cho phép lập trình viên có thể khai báo biến lưu reference thay vì lưu giá trị (được họi là khai báo biến ref cục bộ).
9. Mở rộng kiểu trả về của hàm async
Hiện tại hàm async
trong C# chỉ chấp nhận kiểu trả về là void
, Task
hay Task
. C# 7.0 cho phép các kiểu dữ liệu khác có thể được định nghĩa theo một cách nào đấy mà chúng có thể trả về được từ hàm async. Ví dụ với struct ValueTask
mới (đang được xây dựng). Struct này được thiết kế để ngăn cản việc khởi tạo đối tượng Task
trong trường hợp kết quả của quá trình async có luôn tại thời điểm await.
Bổ sung thêm các cách viết expression bodied mới
C# 6.0 bổ sung một cách viết mới giúp đơn giản cú pháp lệnh có tên là expression bodied. Tuy nhiên phiên bản C# 6.0 chỉ hỗ trợ cách viết mới này cho một số loại thành viên như hàm, thuộc tính, … Ở phiên bản C# 7.0, ngôn ngữ này hỗ trợ nhiều hơn cụ thể là hỗ trợ hàm khởi tạo (constructor), hàm truy xuất (accessors) và hàm hủy (destructor).
class Person
{
private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();
private int id = GetId();
public Person(string name) => names.TryAdd(id, name); // constructors
~Person() => names.TryRemove(id, out *); // destructors
public string Name
{
get => names[id]; // getters
set => names[id] = value; // setters
}
}
10. Biểu thức throw
Khi viết một biểu thức lệnh, nếu muốn throw ra exception bạn chỉ cần khai báo hàm thực hiện việc throw ra exception mong muốn rồi gọi nó trong biểu thức lệnh. Tuy nhiên với C# 7.0, ngôn ngữ này cho phép bạn throw ra exception trực tiếp. Cùng xem cách viết mới này trong ví dụ mẫu bên dưới:
class Person
{
public string Name { get; }
public Person(string name) => Name = name ?? throw new ArgumentNullException(name);
public string GetFirstName()
{
var parts = Name.Split(" ");
return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");
}
public string GetLastName() => throw new NotImplementedException();
}
Theo Blog Lion Pham