Quick Reference
기본 argument 전달은 변수 값의 복사입니다. ref, out, in, ref readonly는 caller storage의 alias를 전달하며 초기화·쓰기·호출 문법이 서로 다릅니다. by-reference API는 copy 최적화나 기존 caller와의 상호 운용이 필요한 좁은 경계에 둡니다.
static void Increment(ref int value) => value++;
static bool TryDivide(int left, int right, out int quotient)
{
quotient = right == 0 ? 0 : left / right;
return right != 0;
}
static int Area(in Rectangle rectangle) => rectangle.Width * rectangle.Height;
int score = 10;
Increment(ref score);
if (TryDivide(20, 4, out int result)) Console.WriteLine(result);| Parameter | caller 초기화 | method 쓰기 | call-site |
|---|---|---|---|
| 값 | 필요 | 복사본만 | 없음 |
ref | 필요 | 가능 | ref variable 필수 |
out | 불필요 | return 전 필수 | out variable 필수 |
in | 필요 | 불가 | 생략 가능, in variable로 alias 강제 |
ref readonly | 필요 | 불가 | in 또는 ref 권장 |
Alias와 초기화 계약
ref는 caller 변수를 읽고 다시 쓸 수 있는 alias입니다. out은 callee가 결과를 반드시 할당해야 하는 output alias입니다. in은 읽기 전용으로 전달하지만 caller가 in을 생략하면 compiler가 expression을 임시값으로 만들어 전달할 수 있습니다. API가 반드시 실제 caller storage를 참조해야 하면 ref readonly의 call-site 경고와 함께 의도를 더 강하게 드러냅니다.
static void Reset(ref int value) => value = 0;
static bool TryRead(string text, out int value)
=> int.TryParse(text, out value);
static int Sum(in Rectangle rectangle)
=> rectangle.Width + rectangle.Height;참조 타입도 기본 전달에서는 참조의 복사를 받습니다. callee는 같은 object의 member를 바꿀 수 있지만, caller variable 자체를 다른 object로 바꾸지는 못합니다. ref SomeClass는 그 caller variable을 재대입할 수 있을 때만 필요합니다.
ref, out, in, ref readonly만 다른 overload끼리는 만들 수 없습니다. by-value overload와 by-reference overload는 구별할 수 있지만, 호출자가 어떤 storage와 copy 규칙을 쓰는지 명확히 이해할 수 있을 때만 제공합니다.
ref return과 scoped
ref return은 값이 아니라 storage location을 돌려줍니다. 반환한 alias가 살아 있는 storage만 가리켜야 하므로, compiler가 local·temporary·짧은 수명의 span 내부를 잘못 반환하는 것을 막습니다.
static ref int FindFirst(int[] values, int target)
{
for (int index = 0; index < values.Length; index++)
{
if (values[index] == target) return ref values[index];
}
throw new KeyNotFoundException();
}
int[] values = [1, 2, 3];
ref int slot = ref FindFirst(values, 2);
slot = 20; // values[1]도 20scoped는 parameter 또는 local reference가 그 안전한 scope 밖으로 escape하지 못하게 하는 제한입니다. Span<T>처럼 짧은 수명의 view를 받는 helper가 field·return·closure로 reference를 저장하지 않도록 compiler에 알립니다.
static void Clear(scoped Span<int> values)
{
values.Clear();
}by-reference parameter는 async method와 iterator method의 parameter로 사용할 수 없습니다. property는 method처럼 보이지만 variable storage가 아니므로 ref argument로 넘길 수 없습니다.
tuple, Result, by-reference 선택
out은 int.TryParse, Dictionary.TryGetValue처럼 success flag와 output 하나를 묶는 기존 .NET 패턴에 잘 맞습니다. 여러 independent result를 반환하고 caller readability가 중요하면 tuple 또는 Result type이 더 분명할 수 있습니다.
in은 큰 readonly struct가 hot path에서 자주 복사되는 측정된 경우에만 검토합니다. 작은 int처럼 reference보다 작거나 비슷한 값에는 이득이 없고, non-readonly member 호출·conversion·expression 전달은 temporary 또는 defensive copy를 만들 수 있습니다.
자주 틀리는 부분
by-reference는 unsafe pointer가 아니며 compiler가 alias lifetime을 검사합니다. 그렇다고 ownership이 자동으로 정해지는 것은 아닙니다. 반환한 ref를 누가 언제까지 쓰는지 API가 명확해야 합니다.
공개 API에서 ref/out을 data transport의 기본값으로 쓰면 caller 상태와 method implementation이 강하게 결합됩니다. 성능 측정이나 기존 framework contract가 없다면 return value, tuple, Result object를 먼저 검토하세요.
참고 링크
3 sources