Phiên bản C# 7.0 đã được phát hành cùng với Visual Studio 2017. Phiên bản này có một số cải tiến thú vị so với phiên bản C# 6.0. Bài viết này sẽ trình bày các tính năng mới của C# 7.0.
out
trong C#Cú pháp hiện đang hỗ trợ các tham số out
đã được cải tiến trong phiên bản này. Bây giờ bạn có thể khai báo các biến out
ngay trong danh sách đối số khi gọi phương thức, thay vì viết một câu lệnh khai báo riêng:
if (int.TryParse(input, out int result))
{
Console.WriteLine(result);
}
else
{
Console.WriteLine("Could not parse input");
}
Bạn có thể muốn chỉ định kiểu dữ liệu rõ ràng cho biến out
, như đã trình bày ở ví dụ trên. Tuy nhiên, phiên bản này cũng hỗ trợ sử dụng biến cục bộ được định kiểu ngầm - sử dụng từ khóa var
như dưới đây:
if (int.TryParse(input, out var answer))
{
Console.WriteLine(answer);
}
else
{
Console.WriteLine("Could not parse input");
}
out
nơi bạn sử dụng nó, không cần phải khai báo biến trước ở một dòng khác.out
khi gọi phương thức, bạn không thể vô tình sử dụng nó trước khi nó được định nghĩa.Đôi khi bạn viết các phương thức cần một cấu trúc dữ liệu đơn giản chứa nhiều phần tử dữ liệu mà bạn không muốn định nghĩa một lớp mới để lưu trữ các phần tử dữ liệu này.
Để hỗ trợ kịch bản này, các lớp tuple đã được thêm vào C#. Tuple là cấu trúc dữ liệu nhẹ chứa nhiều trường để đại diện cho các thành viên dữ liệu. Các trường không được xác thực và bạn cũng không thể định nghĩa phương thức của riêng mình
Lưu ý: Tuples đã có sẵn trước C# 7.0, nhưng chúng không hiệu quả. Điều này có nghĩa là các phần tử tuple chỉ có thể được tham chiếu nhưItem1
,Item2
v.v. C# 7.0 cho phép sử dụng các tên cho các trường của tuple bằng cách sử dụng những kiểu khai báo mới hiệu quả hơn.
Bạn có thể tạo một tuple bằng cách gán một giá trị cho mỗi thành viên và tùy ý cung cấp tên cho từng thành viên của tuple như sau:
(string Alpha, string Beta) namedLetters = ("a", "b");
Console.WriteLine($"{namedLetters.Alpha}, {namedLetters.Beta}");
Tuple namedLetters
chứa các trường được gọi là Alpha
và Beta
. Những tên đó chỉ tồn tại ở thời gian biên dịch và không được bảo tồn.
Ngoài ra, bạn cũng có thể chỉ định tên của các trường ở phía bên phải của khai báo:
var alphabetStart = (Alpha: "a", Beta: "b");
Console.WriteLine($"{alphabetStart.Alpha}, {alphabetStart.Beta}");
Có thể đôi khi bạn muốn thay đổi tên các thành viên của một tuple được trả về từ một phương thức. Bạn có thể làm điều đó bằng cách khai báo các biến riêng biệt cho từng giá trị trong tuple. Việc làm này được gọi là giải cấu trúc (deconstructing) tuple:
(int max, int min) = Range(numbers);
Console.WriteLine(max);
Console.WriteLine(min);
Bạn cũng có thể cung cấp giải cấu trúc tương tự cho bất kỳ kiểu dữ liệu nào trong .NET.
Bạn viết một phương thức Deconstruct
như một thành viên của lớp. Phương thức Deconstruct
đó cung cấp một tập hợp các đối số out
cho mỗi thuộc tính bạn muốn trích xuất. Hãy xem lớp Point
này cung cấp một phương thức giải cấu trúc để trích xuất tọa độ X
và Y
:
public class Point
{
public Point(double x, double y)
=> (X, Y) = (x, y);
public double X { get; }
public double Y { get; }
public void Deconstruct(out double x, out double y) =>
(x, y) = (X, Y);
}
Bạn có thể trích xuất các trường riêng lẻ bằng cách gán một biến Point
cho một tuple như sau:
var p = new Point(3.14, 2.71);
(double X, double Y) = p;
Tìm hiểu thêm về lớp Tuple trong C# tại đây:
Thông thường khi giải cấu trúc (deconstructing) một tuple hoặc gọi một phương thức với các tham số out
, bạn buộc phải định nghĩa một số biến có giá trị mà bạn không quan tâm và không có ý định sử dụng.
C# đã bổ sung loại bỏ (discards) để xử lý tình huống này. Loại bỏ là một biến chỉ ghi (write-only) có tên là _
(ký tự gạch dưới); bạn có thể gán tất cả các giá trị mà bạn định loại bỏ cho một biến duy nhất.
Một loại bỏ giống như một biến không được gán; ngoài câu lệnh gán, loại bỏ không thể được sử dụng trong mã.
Loại bỏ được hỗ trợ trong các trường hợp sau:
out
.is
và switch
.Ví dụ sau định nghĩa một phương thức QueryCityDataForYears
trả về tuple chứa dữ liệu cho một thành phố trong hai năm khác nhau. Gọi phương thức trong ví dụ chỉ liên quan đến hai giá trị dân số được trả về bởi phương thức và do đó coi các giá trị còn lại trong tuple là loại bỏ khi nó giải cấu trúc tuple.
using System;
using System.Collections.Generic;
public class Example
{
public static void Main()
{
var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);
Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");
}
private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
{
int population1 = 0, population2 = 0;
double area = 0;
if (name == "New York City")
{
area = 468.48;
if (year1 == 1960)
{
population1 = 7781984;
}
if (year2 == 2010)
{
population2 = 8175133;
}
return (name, area, year1, population1, year2, population2);
}
return ("", 0, 0, 0, 0, 0);
}
}
Đây là kết quả khi biên dịch và thực thi chương trình trên:
Population change, 1960 to 2010: 393,149
Khớp mẫu (pattern matching) là một tính năng cho phép bạn triển khai phương thức trên các thuộc tính khác với kiểu của đối tượng. Có lẽ bạn đã quen với việc triển khai phương thức dựa trên kiểu của đối tượng.
Trong lập trình hướng đối tượng, các phương thức virtual
và override
cung cấp cú pháp để thực hiện triển khai phương thức dựa trên kiểu của đối tượng. Các lớp cơ sở và lớp dẫn xuất cung cấp các triển khai khác nhau.
Các biểu thức khớp mẫu mở rộng khái niệm này để bạn có thể dễ dàng triển khai các biểu thức cho các kiểu dữ liệu và các thành phần dữ liệu không cần sử dụng kế thừa.
Khớp mẫu hỗ trợ biểu thức is
và biểu thức switch
. Mỗi cái cho phép kiểm tra một đối tượng và các thuộc tính của nó để xác định xem đối tượng đó có thỏa mãn mẫu hay không. Bạn sử dụng từ khóa when
để chỉ định các quy tắc bổ sung cho mẫu.
Khớp mẫu cho toán tử is
rất quen thuộc vì nó sử dụng toán tử is
để truy vấn một đối tượng về kiểu dữ liệu của nó và gán kết quả trong một chỉ dẫn. Đoạn mã sau kiểm tra xem một biến có phải kiểu int
không và nếu có, sẽ cộng nó vào tổng hiện tại:
if (input is int count)
{
sum += count;
}
Ví dụ nhỏ trên cho thấy các cải tiến cho biểu thức is
. Bạn có thể kiểm tra các kiểu giá trị cũng như các kiểu tham chiếu và bạn có thể gán kết quả thành công cho một biến mới của kiểu chính xác.
Khớp mẫu cho toán tử switch
cũng rất quen thuộc, dựa trên câu lệnh switch
có sẵn trong C#. Câu lệnh chuyển đổi được cập nhật có một số cấu trúc mới:
switch
không còn bị giới hạn trong kiểu giá trị, kiểu Enum
, kiểu string
hoặc kiểu nullable tương ứng với các kiểu dữ liệu đó. Bây giờ bất kỳ kiểu dữ liệu nào cũng có thể được sử dụng.switch
trong mỗi case
. Như với biểu thức is
, bạn có thể gán một biến mới cho kiểu đó.when
để kiểm tra thêm các điều kiện về biến đó.case
rất quan trọng vì nhánh đầu tiên khớp sẽ được thực thi và những nhánh khác sẽ bị bỏ qua.Đoạn mã sau minh họa các tính năng mới này:
public static int SumPositiveNumbers(IEnumerable<object> sequence)
{
int sum = 0;
foreach (var i in sequence)
{
switch (i)
{
case 0:
break;
case IEnumerable<int> childSequence:
{
foreach(var item in childSequence)
sum += (item > 0) ? item : 0;
break;
}
case int n when n > 0:
sum += n;
break;
case null:
throw new NullReferenceException("Null found in sequence");
default:
throw new InvalidOperationException("Unrecognized type");
}
}
return sum;
}
case 0:
là mẫu hằng số quen thuộc.case IEnumerable<int> childSequence:
là mẫu kiểu dữ liệu.case int n when n > 0:
là mẫu kiểu dữ liệu với một điều kiện when
bổ sung.case null:
là mẫu null.default:
là trường hợp mặc định quen thuộc.ref
trong C#Tính năng này cho phép các thuật toán sử dụng và trả về các tham chiếu đến các biến được định nghĩa ở nơi khác.
Một ví dụ là làm việc với ma trận lớn và tìm một vị trí duy nhất theo điều kiện. Phương thức sau đây trả về một tham chiếu đến giá trị trong bộ lưu trữ của ma trận:
public static ref int Find(int[,] matrix, Func<int, bool> predicate)
{
for (int i = 0; i < matrix.GetLength(0); i++)
{
for (int j = 0; j < matrix.GetLength(1); j++)
{
if (predicate(matrix[i, j]))
{
return ref matrix[i, j];
}
}
}
throw new InvalidOperationException("Not found");
}
Bạn có thể khai báo giá trị trả về dưới dạng ref
và sửa đổi giá trị đó trong ma trận, như ví dụ sau:
ref var item = ref MatrixSearch.Find(matrix, (val) => val == 42);
Console.WriteLine(item);
item = 24;
Console.WriteLine(matrix[4, 2]);
Ngôn ngữ C# có một số quy tắc bảo vệ để tránh việc bạn lạm dụng trả về ref
như sau:
ref
vào chữ ký phương thức và cho tất cả các câu lệnh return
trong một phương thức.return ref
có thể được gán cho một biến giá trị hoặc một biến ref
: Người gọi kiểm soát xem giá trị trả về có được sao chép hay không. Việc bỏ qua từ khóa ref
khi gán giá trị trả về cho biết rằng người gọi muốn có một bản sao của giá trị, chứ không phải tham chiếu đến giá trị trong bộ lưu trữ.ref
cục bộ.ref
có vòng đời chỉ tồn tại trong phương thức: Điều đó có nghĩa là bạn không thể trả về một tham chiếu đến một biến cục bộ hoặc một biến có phạm vi tương tự.ref
không thể được sử dụng với các phương thức async
: Trình biên dịch không thể biết nếu biến được tham chiếu đã được đặt thành giá trị cuối cùng của nó khi phương thức async trả về.Việc bổ sung trả về ref
cho phép các thuật toán hiệu quả hơn bằng cách tránh sao chép các giá trị hoặc thực hiện tính toán nhiều lần.
Nhiều lớp có thiết kế các phương thức private
được gọi từ chỉ một phương thức khác. Các phương thức private
bổ sung này giữ cho mỗi phương thức có kích thước nhỏ và tập trung.
Các hàm cục bộ (local functions) cho phép bạn khai báo các phương thức bên trong ngữ cảnh của một phương thức khác. Các hàm cục bộ giúp người đọc dễ dàng thấy rằng phương thức cục bộ chỉ được gọi từ ngữ cảnh mà nó được khai báo.
Có hai trường hợp sử dụng phổ biến cho các hàm cục bộ: phương thức lặp public
và phương thức không đồng bộ public
. Cả hai loại phương thức đều tạo mã thông báo lỗi muộn hơn mong đợi của các lập trình viên.
Ví dụ sau đây cho thấy việc tách xác thực tham số khỏi việc thực hiện iterator bằng hàm cục bộ:
public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
if (start < 'a' || start > 'z')
{
throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
}
if (end < 'a' || end > 'z')
{
throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");
}
if (end <= start)
{
throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");
}
return alphabetSubsetImplementation();
IEnumerable<char> alphabetSubsetImplementation()
{
for (var c = start; c < end; c++)
yield return c;
}
}
Kỹ thuật tương tự có thể được sử dụng với các phương thức async
để đảm bảo rằng các ngoại lệ phát sinh từ xác thực đối số được đưa ra trước khi công việc không đồng bộ bắt đầu:
public Task<string> PerformLongRunningWork(string address, int index, string name)
{
if (string.IsNullOrWhiteSpace(address))
{
throw new ArgumentException(message: "An address is required", paramName: nameof(address));
}
if (index < 0)
{
throw new ArgumentOutOfRangeException(paramName: nameof(index), message: "The index must be non-negative");
}
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException(message: "You must supply a name", paramName: nameof(name));
}
return longRunningWorkImplementation();
async Task<string> longRunningWorkImplementation()
{
var interimResult = await FirstWork(address);
var secondResult = await SecondStep(index, name);
return $"The results are {interimResult} and {secondResult}. Enjoy.";
}
}
Lưu ý: Một số thiết kế được hỗ trợ bởi các chức năng cục bộ cũng có thể được thực hiện bằng cách sử dụng biểu thức lambda.
C# 6 đã giới thiệu biểu thức thân thành viên (expression-bodied member) cho các phương thức và các thuộc tính chỉ đọc. C# 7.0 mở rộng các thành viên được phép có thể được thực hiện dưới dạng biểu thức. Trong C# 7.0, bạn có thể sử dụng biểu thức thân thành viên cho constructor, finalizers , và bộ truy xuất get
và set
vào các thuộc tính và chỉ mục.
// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;
private string label;
// Expression-bodied get / set accessors.
public string Label
{
get => label;
set => this.label = value ?? "Default label";
}
Bạn có thể vui lòng tắt trình chặn quảng cáo ❤️ để hỗ trợ chúng tôi duy trì hoạt động của trang web.
Trong loạt bài này, tôi sẽ xem xét một số
Ngôn ngữ C# đã bật các bộ tăng áp liên
Trong bài viết này, chúng ta sẽ tìm hiểu lớp tiện ích ZipFile trong C#, cách nén tập tin và thư mục, cùng với giải nén tập tin zip.
Bài viết này sẽ giới thiệu cách đơn giản nhất mà tôi đã tìm thấy để đọc và ghi file Excel bằng C# sử dụng ExcelMapper.