4 cách chính
| Cách | Cú pháp | Ưu điểm | Nhược điểm |
|---|---|---|---|
| ref | void M(ref int x) | Thay đổi giá trị gốc, hiệu năng tốt | Phải init trước, caller biết bị thay đổi |
| out | void M(out int x) | Không cần init trước, intent rõ ràng | Phải gán trong method, chỉ 1 chiều |
| Tuple | (int, string) M() | Clean, type-safe, multiple values, deconstruction | Alloc struct (nhẹ), không tên rõ nghĩa nếu không named |
| Array/Collection | int[] M() | Số lượng động, familiar | Alloc array, ít type-safe, boxing cho value type |
1. Ref Parameters (Tham chiếu)
public static int Calculate(int a, ref int b)
{
b = a * 2; // Thay đổi giá trị biến gốc
return a + b;
}
static void Main()
{
int x = 5;
int y = 0; // Phải khởi tạo
int result = Calculate(x, ref y);
Console.WriteLine($"Result: {result}, y: {y}"); // Result: 15, y: 10
}
2. Out Parameters (Đầu ra)
public static bool TryParseCustom(string input, out int result)
{
if (int.TryParse(input, out int value))
{
result = value;
return true;
}
result = 0; // BẮT BUỘC gán trước khi return
return false;
}
static void Main()
{
// Không cần init trước
if (TryParseCustom("123", out int number))
{
Console.WriteLine($"Parsed: {number}");
}
// C# 7+: inline out declaration
TryParseCustom("456", out var num);
3. Tuple Return (Khuyến nghị hiện đại - C# 7+)
// Named tuple return - rõ ràng, type-safe
public (int Sum, int Count, double Average) GetStats(int[] numbers)
{
if (numbers.Length == 0) return (0, 0, 0.0);
int sum = numbers.Sum();
return (sum, numbers.Length, sum / (double)numbers.Length);
}
// Sử dụng với deconstruction
var (sum, count, avg) = GetStats(new[] { 1, 2, 3, 4, 5 });
Console.WriteLine($"Sum: {sum}, Count: {count}, Avg: {avg}");
// Hoặc truy cập named members
var stats = GetStats(new[] { 10, 20 });
Console.WriteLine($"Sum: {stats.Sum}, Avg: {stats.Average}");
4. Array/Collection Return
public int[] GetNumbers()
{
return new int[] { 1, 2, 3, 4, 5 };
}
// Hoặc List
public List GetNames()
{
return new List { "A", "B", "C" };
}
So sánh chi tiết: ref vs out vs Tuple
ref - Bi-directional (Hai chiều)
- Caller: Biến phải đã khởi tạo.
- Method: Có thể đọc VÀ ghi biến.
- Dùng khi: Cần sửa đổi biến hiện có, performance critical.
out - Uni-directional (Một chiều - Out)
- Caller: Biến KHÔNG cần khởi tạo.
- Method: BẮT BUỘC gán giá trị trước khi return (trên mọi path).
- Dùng khi: TryParse pattern, multiple return values rõ ràng.
Tuple - Functional Style
- Immutability (nên dùng).
- Deconstruction tự nhiên:
var (a, b) = Method(). - Named elements:
(int Sum, int Count). - Dùng khi: Public API, multiple related values, functional style.
Advanced: Discards (_) với out
// Bỏ qua giá trị không cần
GetStats(numbers, out _, out int count, out _);
// Chỉ lấy count, bỏ qua sum và avg
Pattern Matching với out (C# 7+)
if (int.TryParse(input, out int value))
{
Console.WriteLine(value);
}
// Switch expression
string result = int.TryParse(input, out var v)
? $"Number: {v}"
: "Not a number";
Best Practice Recommendation
Default choice: Tuple (ValueTuple) cho hầu hết trường hợp.
- Clean API, type-safe, IDE support tốt, deconstruction, serialization-friendly.
Dùng out cho TryParse pattern, bool return + value.
Dùng ref hiếm, chỉ khi optimize performance critical path.
Tránh Array cho fixed multiple returns (kém type-safe).
- Clean API, type-safe, IDE support tốt, deconstruction, serialization-friendly.
Dùng out cho TryParse pattern, bool return + value.
Dùng ref hiếm, chỉ khi optimize performance critical path.
Tránh Array cho fixed multiple returns (kém type-safe).
Ví dụ thực tế: Repository Pattern
// Tuple cho kết quả phức tạp
public (bool Success, User User, string Error) GetUser(int id)
{
var user = _db.Users.Find(id);
if (user == null) return (false, null, "User not found");
return (true, user, null);
}
// Caller
var (success, user, error) = GetUser(123);
if (!success)
{
LogError(error);
return;
}
// Use user...