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

Trả về nhiều giá trị từ hàm trong C#: 4 cách hiệu quả

4 cách chính

CáchCú phápƯu điểmNhược điểm
refvoid M(ref int x)Thay đổi giá trị gốc, hiệu năng tốtPhải init trước, caller biết bị thay đổi
outvoid M(out int x)Không cần init trước, intent rõ ràngPhải gán trong method, chỉ 1 chiều
Tuple(int, string) M()Clean, type-safe, multiple values, deconstructionAlloc struct (nhẹ), không tên rõ nghĩa nếu không named
Array/Collectionint[] M()Số lượng động, familiarAlloc 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).

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...