Lớp Tuple trong C#

Các lớp Tuple<T> đã được giới thiệu trong .NET Framework 4.0. Tuple là một cấu trúc dữ liệu chứa một chuỗi các phần tử của các loại dữ liệu khác nhau.

Tuple có thể được sử dụng khi bạn muốn có một cấu trúc dữ liệu để lưu trữ dữ liệu của một đối tượng có thuộc tính, nhưng bạn không muốn tạo một kiểu dữ liệu riêng cho nó.

Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>

Ví dụ sau đây tạo ra một tuple với ba phần tử:

var person = new Tuple<int,string,string>(1, "Steve", "Jobs");

Trong ví dụ trên, chúng tôi đã tạo một Tuple có tên là person lưu trữ một bản ghi dữ liệu của một người. Chúng tôi đã chỉ định kiểu dữ liệu cho mỗi phần tử và chuyển các giá trị cho phương thức khởi tạo của Tuple.

Chỉ định kiểu dữ liệu của từng phần tử trong Tuple rất cồng kềnh. Vì vậy, C# cung cấp một lớp trợ giúp Tuple giúp trả về một thể hiện của Tuple<T> mà không cần chỉ định kiểu dữ liệu của từng phần tử. Hãy xem ví dụ dưới đây:

var person = Tuple.Create(1, "Steve", "Jobs");

Một tuple chỉ có tối đa tám phần tử. Nó đưa ra một lỗi biên dịch khi bạn cố gắng tạo hơn tám phần tử.

var numbers = Tuple.Create(1, 2, 3, 4, 5, 6, 7, 8);

Truy cập các phần tử của Tuple trong C#

Các phần tử của Tuple có thể được truy cập bằng các thuộc tính Item<elementNumber>. Ví dụ thuộc tính Item1, Item2, Item3, v.v. đến Item7.

Thuộc tính Item1 trả về phần tử đầu tiên, Item2 trả về phần tử thứ hai, v.v. Phần tử cuối cùng (phần tử thứ 8) sẽ được trả về bằng cách sử dụng thuộc tính Rest.

var person = Tuple.Create(1, "Steve", "Jobs");
person.Item1; // returns 1
person.Item2; // returns "Steve"
person.Item3; // returns "Jobs"


var numbers = Tuple.Create("One", 2, 3, "Four", 5, "Six", 7, 8);
numbers.Item1; // returns "One"
numbers.Item2; // returns 2
numbers.Item3; // returns 3
numbers.Item4; // returns "Four"
numbers.Item5; // returns 5
numbers.Item6; // returns "Six"
numbers.Item7; // returns 7
numbers.Rest; // returns (8)
numbers.Rest.Item1; // returns 8

Nói chung, vị trí thứ 8 dành cho bộ dữ liệu lồng nhau mà bạn có thể truy cập bằng cách sử dụng thuộc tính Rest.

Tuple lồng nhau trong C#

Nếu bạn muốn nhiều hơn tám phần tử trong một tuple, bạn có thể làm điều đó bằng cách lồng một đối tượng tuple khác làm phần tử thứ tám.

Tuple con ở vị trí cuối cùng có thể được truy cập bằng cách sử dụng thuộc tính Rest. Để truy cập phần tử của tuple con, sử dụng thuộc tính Rest.Item1.Item<elelementNumber>.

var numbers = Tuple.Create(1, 2, 3, 4, 5, 6, 7, Tuple.Create(8, 9, 10, 11, 12, 13));
numbers.Item1; // returns 1
numbers.Item7; // returns 7
numbers.Rest.Item1; //returns (8, 9, 10, 11, 12, 13)
numbers.Rest.Item1.Item1; //returns 8
numbers.Rest.Item1.Item2; //returns 9

Bạn có thể thêm tuple con ở bất cứ vị trí nào trong tuple cha. Tuy nhiên, nên đặt tuple con vào vị trí cuối cùng của tuple cha để có thể truy cập bằng thuộc tính Rest.

var numbers = Tuple.Create(1, 2, Tuple.Create(3, 4, 5, 6, 7,  8), 9, 10, 11, 12, 13 );
numbers.Item1; // returns 1
numbers.Item2; // returns 2
numbers.Item3; // returns (3, 4, 5, 6, 7,  8)
numbers.Item3.Item1; // returns 3
numbers.Item4; // returns 9
numbers.Rest.Item1; //returns 13

Tuple làm tham số phương thức

Một phương thức có thể nhận tham số là tuple.

static void Main(string[] args)
{
    var person = Tuple.Create(1, "Steve", "Jobs");
    DisplayTuple(person);
}

static void DisplayTuple(Tuple<int,string,string> person)
{
    Console.WriteLine($"Id = { person.Item1}");
    Console.WriteLine($"First Name = { person.Item2}");
    Console.WriteLine($"Last Name = { person.Item3}");
}

Trả về Tuple

Một phương thức có thể trả về kiểu dữ liệu là Tuple.

static void Main(string[] args)
{
    var person = GetPerson();
}

static Tuple<int, string, string> GetPerson() 
{
    return Tuple.Create(1, "Bill", "Gates");
}

Cách sử dụng Tuple

Tuple có thể được sử dụng trong các tình huống sau:

  • Khi bạn muốn trả về nhiều giá trị từ một phương thức mà không sử dụng tham số ref hoặc out.
  • Khi bạn muốn truyền nhiều giá trị cho một phương thức thông qua một tham số.
  • Khi bạn muốn lưu trữ một bản ghi cơ sở dữ liệu hoặc một số giá trị tạm thời mà không muốn tạo một lớp riêng biệt.

Giới hạn của Tuple:

  • Tuple là một kiểu dữ liệu tham chiếu, nó phân bổ trên heap và có thể dẫn đến các hoạt động chuyên sâu của CPU.
  • Tuple được giới hạn tối đa 8 phần tử. Bạn cần sử dụng các bộ dữ liệu lồng nhau nếu bạn cần lưu trữ nhiều phần tử hơn. Tuy nhiên, điều này có thể dẫn đến sự mơ hồ.
  • Các phần tử Tuple có thể được truy cập bằng các thuộc tính với mẫu tên Mục <ElementNumber> không có ý nghĩa.
  • Các phần tử của Tuple là chỉ đọc (read-only) nên bạn không thể thay đổi những giá trị này.

C# 7.0 bổ sung lớp ValueTuple khắc phục các hạn chế của Tuple và cũng giúp làm việc với Tuple dễ dàng hơn nữa.

ValueTuple trong C#

C # 7.0 (.NET Framework 4.7) đã giới thiệu lớp ValueTuple, một cấu trúc đại diện cho kiểu giá trị của Tuple .

ValueTuple chỉ khả dụng trong .NET Framework 4.7. Nếu bạn không thấy ValueTuple trong dự án của mình thì bạn cần cài đặt ValueTuple. (.NET Framework 4.7 trở lên hoặc .NET Standard Library 2.0 trở lên đã bao gồm ValueTuple.)

Để cài đặt các ValueTuple, nhấp chuột phải vào Project trong solution explorer và chọn Manage NuGet Packages. Điều này sẽ mở Trình quản lý gói NuGet. Nhấp vào tab Browse, gõ ValueTuple trong hộp tìm kiếm và chọn gói System.ValueTuple, như hình bên dưới.

Thêm ValueTuple vào project

Khởi tạo ValueTuple

Thật dễ dàng để tạo và khởi tạo ValueTuple bằng dấu ngoặc đơn () và chỉ định các giá trị giữa chúng.

var person = (1, "Bill", "Gates");
    
//equivalent Tuple
//var person = Tuple.Create(1, "Bill", "Gates");

ValueTuple cũng có thể được khởi tạo bằng cách chỉ định kiểu dữ liệu của từng phần tử. Hãy xem ví dụ dưới đây:

ValueTuple<int,string,string> person = (1, "Bill", "Gates");
person.Item1;  // returns 1
person.Item2;   // returns "Bill"
person.Item3;   // returns "Gates"

Sau đây là cách khai báo kiểu dữ liệu cho các phần tử.

(int, string, string) person = (1, "Bill", "Gates");
person.Item1;  // returns 1
person.Item2;   // returns "Bill"
person.Item3;   // returns "Gates"

Xin lưu ý rằng chúng tôi đã không được sử dụng từ khóa var trong lệnh khởi tạo tuple ở trên; thay vào đó chúng tôi cung cấp kiểu dữ liệu của từng phần tử bên trong ngoặc.

Tuple yêu cầu ít nhất hai giá trị. Hãy xem ví dụ sau:

var number = (1);    // NOT a tuple, int type
var numbers = (1,2); // valid tuple

Không giống như Tuple, ValueTuple có thể nhiều hơn tám phần tử.

var numbers = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14);

Đặt tên cho phần tử:

Chúng ta có thể gán tên cho các thuộc tính ValueTuple thay vì sử dụng các tên thuộc tính mặc định Item1, Item2, v.v.

(int Id, string FirstName, string LastName) person = (1, "Bill", "Gates");
person.Id;         // returns 1
person.FirstName;  // returns "Bill"
person.LastName;   // returns "Gates"

Chúng ta cũng có thể gán tên phần tử ở phía bên phải với các giá trị, như bên dưới.

var person = (Id:1, FirstName:"Bill", LastName: "Gates");

Xin lưu ý rằng chúng tôi có thể cung cấp tên phần tử ở bên trái hoặc bên phải nhưng không phải ở cả hai bên. Bên trái sẽ được ưu tiên hơn bên phải. Ví dụ sau đây sẽ bỏ qua tên ở phía bên phải.

// PersonId, FName, LName will be ignored.
(int Id, string FirstName, string LastName) person = (PersonId:1, FName:"Bill", LName: "Gates");

// PersonId, FirstName, LastName will be ignored. It will have the default names: Item1, Item2, Item3.
(string, string, int) person = (PersonId:1, FName:"Bill", LName: "Gates");

Chúng ta cũng có thể gán các biến làm giá trị cho phần tử.

string firstName = "Bill", lastName = "Gates";
var per = (FirstName: firstName, LastName: lastName);

Trả về ValueTuple trong C#

Phương thức sau đây trả về ValueTuple.

static void Main(string[] args)
{
    var person = GetPerson();
    DisplayTuple(person);
}

static (int, string, string) GetPerson() 
{
    return (1, "Bill", "Gates");
}

static void DisplayTuple((int, string, string) person)
{
    Console.WriteLine($"Id = { person.Item1}");
    Console.WriteLine($"First Name = { person.Item2}");
    Console.WriteLine($"Last Name = { person.Item3}");
}

Chúng tôi cũng có thể chỉ định các tên thành viên khác nhau cho ValueTuple được trả về từ một phương thức.

static void Main(string[] args)
{
    var person = GetPerson();
}

static (int, string, string) GetPerson() 
{
    return (Id:1, FirstName: "Bill", LastName: "Gates");
}

Tái cấu trúc

Các thành viên riêng lẻ của ValueTuple có thể được truy xuất bằng cách tái cấu trúc nó. Cú pháp khai báo tái cấu trúc phân tách một ValueTuple thành các phần và gán các phần đó riêng lẻ cho các biến mới.

static void Main(string[] args)
{
    // change property names
    (int PersonId, string FName, string LName) person = GetPerson();
}
static (int, string, string) GetPerson() 
{
    return (Id:1, FirstName: "Bill", LastName: "Gates");
}

Chúng tôi cũng có thể sử dụng var thay vì tên kiểu dữ liệu rõ ràng.

static void Main(string[] args)
{
    // use var as datatype
    (var PersonId, var FName, var LName) person = GetPerson();
}
static (int, string, string) GetPerson() 
{
    return (Id:1, FirstName: "Bill", LastName: "Gates");
}

ValueTuple cũng cho phép "loại bỏ" việc tái cấu trúc cho các thành viên bạn sẽ không sử dụng.

// use discard _ for the unused member LName
(var id, var FName, _) = GetPerson(); 
Lập Trình C#Lập Trình C# Cơ Bản
Bài Viết Liên Quan:
int[] và int[,] trong C#: Ai nhanh hơn
Trung Nguyen 10/10/2020
int[] và int[,] trong C#: Ai nhanh hơn

Hiểu được sự khác biệt giữa các loại mảng trong C# sẽ giúp bạn chọn cấu trúc dữ liệu chính xác cho mọi trường hợp.

Struct và class trong C#: Ai nhanh hơn
Trung Nguyen 09/10/2020
Struct và class trong C#: Ai nhanh hơn

Trong bài viết này, tôi sẽ so sánh sự khác biệt về hiệu suất giữa struct và class trong C#: Ai nhanh hơn.

Best practice cho performance trong C#
Trung Nguyen 03/10/2020
Best practice cho performance trong C#

Mục tiêu của bài viết này là cung cấp một danh sách không đầy đủ các code mẫu cần tránh, vì chúng rủi ro hoặc performance kém.

Đọc ghi file (File I/O) trong C#
Trung Nguyen 26/04/2020
Đọc ghi file (File I/O) trong C#

Hướng dẫn này sẽ giúp bạn tìm hiểu về đọc ghi file (File I/O) trong C# và sử dụng các lớp tiện ích để đọc ghi file.