Tổng quan 3 loại chính
| Loại | Từ khóa | Truyền | Thay đổi gốc? | Khởi tạo trước? |
|---|---|---|---|---|
| Value | (mặc định) | Copy giá trị | Không | Có |
| Reference | ref | Reference (địa chỉ) | Có | Có |
| Output | out | Reference (địa chỉ) | Có (bắt buộc gán) | Không cần |
1. Value Parameters (Mặc định)
Truyền bản sao giá trị. Thay đổi trong method không ảnh hưởng biến gốc.
void Modify(int x)
{
x = 10; // Chỉ thay đổi local copy
}
int a = 5;
Modify(a);
Console.WriteLine(a); // 5 - không đổi
Áp dụng cho: primitive types (int, bool, double...), struct, enum.
2. Reference Parameters (ref)
Truyền tham chiếu đến biến gốc. Thay đổi trong method ảnh hưởng biến gốc.
void Modify(ref int x)
{
x = 10; // Thay đổi biến gốc
}
int a = 5;
Modify(ref a);
Console.WriteLine(a); // 10 - ĐÃ THAY ĐỔI
Yêu cầu: Biến phải khởi tạo trước khi truyền.
3. Output Parameters (out)
Tương tự ref nhưng: không cần init trước, BẮT BUỘC gán giá trị trong method.
bool TryParse(string s, out int result)
{
// Phải gán result trên TẤT CẢ code paths
if (int.TryParse(s, out int value))
{
result = value;
return true;
}
result = 0; // Bắt buộc
return false;
}
// Gọi
if (TryParse("123", out int num))
{
Console.WriteLine(num); // 123
}
4. In Parameters (C# 7.2+) - Read-only Reference
// Chỉ đọc, không cho phép sửa - tránh copy large struct
void Process(in BigStruct data)
{
// data.X = 10; // ❌ Compile error: cannot assign
Console.WriteLine(data.Value);
}
// Dùng cho large struct (> 16 bytes) để tránh copy
readonly struct BigStruct
{
public long A, B, C, D, E;
public int Value => (int)(A + B);
}
5. Params (Mảng tham số)
void PrintAll(params string[] names)
{
foreach (var n in names) Console.WriteLine(n);
}
// Gọi
PrintAll("A", "B", "C");
PrintAll(new[] { "X", "Y" });
// C# 13+: params Span, params IEnumerable
void PrintAll(params ReadOnlySpan names) { }
6. Optional Parameters (Default Value)
void Greet(string name, string greeting = "Hello")
{
Console.WriteLine($"{greeting}, {name}");
}
Greet("John"); // Hello, John
Greet("John", "Hi"); // Hi, John
Greet(name: "John"); // Named argument
7. Ref Return & Ref Local (C# 7+)
// Trả về reference đến element trong array/field
ref int FindFirst(int[] arr, int target)
{
for (int i = 0; i < arr.Length; i++)
{
if (arr[i] == target) return ref arr[i];
}
throw new ArgumentException();
}
// Sử dụng
int[] numbers = { 1, 2, 3, 4, 5 };
ref int found = ref FindFirst(numbers, 3);
found = 99; // Thay đổi trực tiếp trong array!
Console.WriteLine(numbers[2]); // 99
8. Ref Struct (C# 7.2+) - Stack-only
// Chỉ tồn tại trên stack, không thể boxing, không dùng trong async/iterator
public ref struct SpanWrapper
{
public Span Data;
public SpanWrapper(Span data) => Data = data;
}
Best Practices
- Default: Value parameters (an toàn, đơn giản).
- Large struct (>16 bytes):
inparameter tránh copy. - Multiple returns: Tuple
(T1, T2)thay vì out/ref. - TryParse pattern:
outparameter. - Modify caller variable:
ref(ít dùng, cân nhắc return value). - Params: Convenience API, logging, formatting.
Ví dụ tổng hợp
public class Calculator
{
// Value - input nhỏ
public int Add(int a, int b) => a + b;
// In - large struct input
public double Distance(in Point3D a, in Point3D b)
=> Math.Sqrt((a.X-b.X)*(a.X-b.X) + (a.Y-b.Y)*(a.Y-b.Y) + (a.Z-b.Z)*(a.Z-b.Z));
// Out - Try pattern
public bool TryDivide(int a, int b, out int result)
{
if (b == 0) { result = 0; return false; }
result = a / b;
return true;
}
// Ref - swap (hiếm dùng)
public void Swap(ref int a, ref int b)
{
(a, b) = (b, a); // C# 7+ tuple swap
}
// Params - flexible args
public int Sum(params int[] numbers) => numbers.Sum();
// Optional - default
public int Multiply(int a, int b = 2) => a * b;
}