Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> C#

Khi nào nên dùng Tuple trong C#?

Tuple là gì?

Tuple là kiểu dữ liệu light-weight gom nhiều giá trị (khác kiểu) thành một đơn vị. Từ C# 7.0 có ValueTuple (struct) với syntax sugar.

Khi nào dùng Tuple?

Trường hợpVí dụ
Trả về nhiều giá trị từ method (không muốn tạo class/struct)Parse result: (bool success, int value, string error)
Truyền nhiều giá trị qua 1 tham sốCallback, event args, message queue payload
Deconstruct objectvar (x, y) = point;
Tạm thời gom data (throwaway)LINQ intermediate, swap variables
Dictionary key phức hợpDictionary<(int, int), Value> - composite key

C# 7+ ValueTuple Syntax (Khuyến nghị)


// Khai báo
var person = (Name: "John", Age: 30, City: "Hanoi");

// Truy cập
Console.WriteLine(person.Name); // John
Console.WriteLine(person.Item2); // 30 (tương thích ngầm)

// Deconstruction
var (name, age, city) = person;
Console.WriteLine($"{name}, {age}, {city}");

// Bỏ qua giá trị với discard _
var (name, _, city) = person;

Tuple as Method Return


// Cú pháp hiện đại (C# 7+)
public (int Sum, int Count, double Average) CalculateStats(int[] numbers) 
{
    if (numbers.Length == 0) return (0, 0, 0.0);
    
    int sum = numbers.Sum();
    int count = numbers.Length;
    double avg = sum / (double)count;
    
    return (sum, count, avg);
}

// Sử dụng
var (sum, count, avg) = CalculateStats(new[] { 1, 2, 3, 4, 5 });
Console.WriteLine($"Sum: {sum}, Count: {count}, Avg: {avg}");
// Sum: 15, Count: 5, Avg: 3

Tuple as Method Parameter


public void ProcessUser((int Id, string Name, string Email) user) 
{
    Console.WriteLine($"Processing {user.Name} (ID: {user.Id})");
}

// Gọi
ProcessUser((1, "John", "john@test.com"));
ProcessUser((Id: 2, Name: "Jane", Email: "jane@test.com"));

Tuple vs Class/Struct/Record

Đặc điểmTuple (ValueTuple)Class/RecordStruct
SyntaxNgắn gọn, inlineVerboseVerbose
Named elementsCó (C# 7+)CóCó
Methods/BehaviorKhôngCóCó
EqualityStructural (value-based)Reference (class) / Value (record)Value-based
SerializationHỗ trợ (JSON, etc.)Full controlFull control
PerformanceStack allocation, no GCHeap allocationStack allocation
MutableCó (struct mutable)Tùy designTùy design
Phù hợpTạm thời, internal, few fieldsDomain model, public APIHigh-perf, value semantics

Ví dụ thực tế: TryParse Pattern


// Trả về (bool success, T value) thay vì out parameter
public (bool Success, int Value) TryParseInt(string input) 
{
    if (int.TryParse(input, out int value)) 
        return (true, value);
    return (false, 0);
}

// Sử dụng
var (success, value) = TryParseInt("123");
if (success) Console.WriteLine($"Parsed: {value}");

Tuple trong LINQ


// GroupBy trả về key phức hợp
var grouped = orders
    .GroupBy(o => (o.CustomerId, o.OrderDate.Year, o.OrderDate.Month))
    .Select(g => new 
    {
        g.Key.CustomerId,
        g.Key.Year,
        g.Key.Month,
        Total = g.Sum(o => o.Amount)
    });

Tuple trong Dictionary Key (Composite Key)


// Key = (CustomerId, ProductId)
var prices = new Dictionary<(int CustomerId, int ProductId), decimal>
{
    [(1, 100)] = 99.99m,
    [(1, 200)] = 149.99m,
    [(2, 100)] = 89.99m
};

decimal price = prices[(1, 100)]; // 99.99

Tuple vs System.Tuple (Legacy)


// Legacy (Reference type, class, immutable, Item1/Item2...)
var old = Tuple.Create(1, "John", 30);
Console.WriteLine(old.Item1); // 1

// Modern (Value type, struct, mutable, named elements)
var modern = (Id: 1, Name: "John", Age: 30);
Console.WriteLine(modern.Name); // John

Lưu ý quan trọng

  • ValueTuple là struct → copy by value, không null (nhưng nullable: (int, string)?).
  • Named elements chỉ tồn tại tại compile time, runtime thấy Item1, Item2...
  • Serialization: JSON serializer hỗ trợ tốt (Newtonsoft, System.Text.Json).
  • Không dùng Tuple cho public API phức tạp → dùng Record/Class.
  • Tuple size lớn (> 7 elements) → cân nhắc class/record.