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

KeywordCaller must initialize?Method must assign?Method can read?Direction
plain parameternonoyes (a copy)in, by copy
outnoyesno (before assigning)out only
refyesnoyesin and out
inyesnoyesin 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

SituationWhat 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 paramCS0177 โ€” out must be assigned before returning
Writing to an in paramCompile error โ€” in is read-only

Which one, when?

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.