Types ยท parameters
ref vs out vs in
A normal parameter is a copy. These three say "don't copy โ work with the caller's box directly". The differences are about direction: out writes only, ref reads and writes, in reads only.
The direction table
| Keyword | Caller must initialize? | Method must assign? | Method can read? | Direction |
|---|---|---|---|---|
| plain parameter | no | no | yes (a copy) | in, by copy |
out | no | yes | no (before assigning) | out only |
ref | yes | no | yes | in and out |
in | yes | no | yes | in only |
out: a method that produces a value
Out is for "I need to return two things". The classic is TryParse, which returns success and the parsed number:
bool TryParseHalf(string text, out int half)
{
int value;
if (!int.TryParse(text, out value))
{
half = 0;
return false;
}
half = value / 2;
return true;
}
if (TryParseHalf("84", out int first))
{
Console.WriteLine("parsed: " + first);
}
if (!TryParseHalf("nope", out int second))
{
Console.WriteLine("failed, got " + second); // out was still assigned
}
The two hard rules: the method must assign the out parameter on every path, and the caller may pass an uninitialized variable โ that's the whole point (you don't need a dummy value first).
ref: a method that changes your variable
Ref passes the variable itself, so assignments inside the method are visible outside:
void AddBonus(ref int score)
{
score += 3; // changes the CALLER's variable
}
int points = 10;
Console.WriteLine("before: " + points);
AddBonus(ref points); // note the ref at the call site too
Console.WriteLine("after: " + points);
Rules: the caller must already have a value, and the caller must write ref as well โ the call site is deliberately noisy, because a method silently changing your variable is exactly the kind of thing you want to see.
A classic ref use: swap
void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
int x = 1, y = 2;
Swap(ref x, ref y);
Console.WriteLine("a=" + x + " b=" + y);
You cannot do this with a plain parameter โ the method would only swap its own copies, and the caller would see nothing change. That's the whole reason ref exists.
in: read without copying
in passes by reference but forbids writing. Its purpose is performance with large value types (a big struct), where copying would be wasteful:
double Distance(in Point a, in Point b) // structs passed without copying
{
double dx = a.X - b.X;
double dy = a.Y - b.Y;
return Math.Sqrt(dx * dx + dy * dy);
// a.X = 0; // โ compile error: in-parameter is read-only
}
For small types like int or double, in makes no difference โ copying 4 bytes is as cheap as passing a reference. Reach for it only with genuinely large structs.
The rules that trip people up
| Situation | What happens |
|---|---|
int x; F(ref x); | CS0165 โ ref requires an initialized variable |
int x; F(out x); | โ legal โ that's what out is for |
F(ref 5); | CS1510 โ a literal has no variable to point at |
F(ref x + 1); | CS1510 โ expressions are not assignable |
Method never assigns out param | CS0177 โ out must be assigned before returning |
Writing to an in param | Compile error โ in is read-only |
Which one, when?
outโ the method's main job is producing a value, and you also need a success flag.TryParseandTryGetValueare the canonical examples.refโ the method must modify an existing variable (swap, accumulate into a caller's field, update a large struct in place).inโ you're passing a large struct and want to avoid the copy without allowing writes. Rarely worth it for everyday code.- None of them โ the default. Most methods should return a value rather than mutate through parameters.
refandoutare for the cases where a return value genuinely isn't enough.
Frequently asked
What is the difference between ref and out?
ref means the variable must be initialized before the call and the method may read and write it. out means the variable need not be initialized, but the method must assign it before returning. Both make the call site explicit.
Do I need ref to change an object passed to a method?
No โ classes are reference types, so a normal parameter already points at the same object, and mutations are visible to the caller. ref is only needed to change which object the caller's variable points to.