CodeGym /Các khóa học /C# SELF /Trả về nhiều giá trị từ function: out-parameter và tuple

Trả về nhiều giá trị từ function: out-parameter và tuple

C# SELF
Mức độ, Bài học
Có sẵn

1. Giới thiệu

Giả sử tụi mình có một function mà dựa vào user truyền vào (ví dụ tên) phải trả về luôn cả tuổi, ngày đăng ký và cờ-hoạt-động. Trong C và các version C# đời đầu, việc trả về chỉ một giá trị là chuyện bình thường. Nhưng nếu muốn trả về nhiều giá trị thì sao? Câu hỏi này đã sinh ra nhiều cách tiếp cận khác nhau, mỗi cái đều có ưu nhược điểm riêng.
Ở các bài trước tụi mình đã làm quen với tuple rồi. Giờ sẽ học về out-parameter và so sánh hai cách này.

Những cách phổ biến nhất:

  • Dùng out-parameter.
  • Trả về một object có đủ field cần thiết (kiểu anonymous type hoặc class tự định nghĩa).
  • Trả về tuple (ValueTuple).

Trong bài này tụi mình sẽ đi sâu vào hai cách – out-parameter và tuple, so sánh trực diện để hiểu ưu điểm của từng cái khi muốn trả về nhiều giá trị từ function.

2. out-parameter: phong cách cũ

Khi thấy function kiểu này:


void GetUserInfo(string userName, out int age, out DateTime registrationDate, out bool isActive)
{
    // tính toán ở đây
    age = 42;
    registrationDate = new DateTime(2010, 1, 1);
    isActive = true;
}

thì kiểu muốn hỏi: đây là function hay là trạm rửa xe mini vậy? Trả về đủ thứ ra ngoài, mà chẳng liên quan gì đến giá trị return chính!

Cách nó hoạt động

Code gọi function phải khai báo sẵn biến để function điền vào:

int age;
DateTime reg;
bool isActive;

GetUserInfo("Bob", out age, out reg, out isActive);

// Giờ tất cả biến đã có giá trị
Console.WriteLine($"{age}, {reg}, {isActive}");

Nhược điểm của out-cách

  • Khó đọc hơn: Chữ ký method dài ra, ý nghĩa dữ liệu trả về không phải lúc nào cũng rõ từ tên parameter.
  • Bất tiện khi chain function: Không dễ nhét vào chuỗi gọi hàm (ví dụ truyền kết quả ngay cho function khác).
  • Làm thay đổi biến truyền vào: Method bắt buộc phải sửa biến đã khai báo; quên out là lỗi compile liền.
  • Bắt buộc khai báo biến ngoài: Compiler bắt bạn phải khai báo biến ngoài, dù chỉ dùng kết quả một lần.
  • Khó đọc khi method lớn: Nếu có nhiều out-parameter, dễ bị rối không biết cái nào là gì.

Đây là lúc tuple bắt đầu tỏa sáng.

3. Tuple — tiến hóa của cách tiếp cận

So sánh bằng ví dụ

Dùng tuple:


public (int Age, DateTime RegistrationDate, bool IsActive) GetUserInfo(string userName)
{
    // giả lập tìm trong database
    return (42, new DateTime(2010, 1, 1), true);
}

Cách dùng:

// Lấy tất cả giá trị và đặt tên biến luôn
var (age, regDate, isActive) = GetUserInfo("Bob");
Console.WriteLine($"{age}, {regDate}, {isActive}");

Không cần khai báo biến thừa, không out, đọc cực dễ!

Bảng so sánh: out vs tuple

Tiêu chí Out-parameter Tuple (ValueTuple)
Chữ ký dài, out-parameter nằm trong danh sách ngắn gọn, trả về một "gói" duy nhất
Cách dùng phải khai báo biến rồi mới gọi method có thể destructure ngay
Dễ đọc dễ rối nếu có nhiều out-parameter tên phần tử rõ ràng ngay
Dễ kết hợp? không có, có thể lồng và truyền đi

Tuple VS. out — chuyện gì diễn ra bên trong

Với out-parameter, function thực ra thao tác với vùng nhớ bên ngoài "lãnh thổ" của nó: nói đơn giản là nó sửa biến được tạo ở nơi khác. Phải cẩn thận, vì dễ làm "bẩn" trạng thái biến (cảm ơn compiler vì bắt gán giá trị bắt buộc!).

Tuple thì là một cấu trúc dữ liệu, function tạo ra, khởi tạo đầy đủ rồi trả về ngoài. Tức là mọi thứ gói gọn trong một "gói hàng", không sợ mất mát hay quên gì cả.

Đọc và bảo trì code

Công nhận đi — sau một tuần đọc lại code người khác, ai cũng muốn thấy:

(var age, var city, var isActive) = GetUserInfo("Anna");

chứ không phải

int age;
string city;
bool isActive;
GetUserInfo("Anna", out age, out city, out isActive);

Tuple làm chữ ký method gọn hơn, kết quả dùng cũng dễ hiểu ngay.

4. Trường hợp tuple cực kỳ hữu dụng

1. Khi cần trả về kết quả và thông báo lỗi

public (bool Success, string ErrorMessage) TryProcess(string data)
{
    if (string.IsNullOrEmpty(data))
        return (false, "Không có dữ liệu");

    // xử lý dữ liệu...
    return (true, "");
}

var (ok, error) = TryProcess(input);
if (!ok)
    Console.WriteLine($"Lỗi: {error}");

2. Cho function trả về nhiều kết quả tìm kiếm

public (User? FoundUser, int Index) FindUserByName(User[] users, string name)
{
    for (int i = 0; i < users.Length; i++)
    {
        if (users[i].Name == name)
            return (users[i], i);
    }
    return (null, -1);
}

3. Cho “cặp” giá trị: tính min và max

public (int min, int max) FindMinMax(int[] numbers)
{
    int min = numbers[0], max = numbers[0];
    foreach (var n in numbers)
    {
        if (n < min) min = n;
        if (n > max) max = n;
    }
    return (min, max);
}

var (minValue, maxValue) = FindMinMax(arr);
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Tạo hàm sử dụng tuple
Tạo hàm sử dụng tuple
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Tính toán giá trị nhỏ nhất và lớn nhất bằng tuple
Tính toán giá trị nhỏ nhất và lớn nhất bằng tuple
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION