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ợp | Ví 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 object | var (x, y) = point; |
| Tạm thời gom data (throwaway) | LINQ intermediate, swap variables |
| Dictionary key phức hợp | Dictionary<(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ểm | Tuple (ValueTuple) | Class/Record | Struct |
| Syntax | Ngắn gọn, inline | Verbose | Verbose |
| Named elements | Có (C# 7+) | Có | Có |
| Methods/Behavior | Không | Có | Có |
| Equality | Structural (value-based) | Reference (class) / Value (record) | Value-based |
| Serialization | Hỗ trợ (JSON, etc.) | Full control | Full control |
| Performance | Stack allocation, no GC | Heap allocation | Stack allocation |
| Mutable | Có (struct mutable) | Tùy design | Tùy design |
| Phù hợp | Tạm thời, internal, few fields | Domain model, public API | High-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.