ref, out and in

Normally a method gets a copy of each argument. The ref, out and in modifiers pass the caller's variable itself instead. You'll use out all the time with TryParse-style methods; ref and in are more specialised but worth recognising.

ref: let the method change the caller's variable

With ref, the parameter is another name for the caller's variable. Both the method declaration and the call must say ref, so it's obvious at the call site that the variable may change:

C#
int lives = 3;
LoseLife(ref lives);
LoseLife(ref lives);
Console.WriteLine(lives);

int a = 1, b = 2;
Swap(ref a, ref b);
Console.WriteLine($"a = {a}, b = {b}");

static void LoseLife(ref int count) => count--;

static void Swap(ref int x, ref int y)
{
    int temp = x;
    x = y;
    y = temp;
}
Output
1
a = 2, b = 1

A ref argument must be a variable that already has a value — you can't pass a literal like ref 5.

out: return extra values

An out parameter is for output only. The caller doesn't need to initialise the variable, but the method must assign it before returning. This is the pattern behind int.TryParse: the bool return value says whether it worked, and the out parameter carries the result:

C#
if (TryDivide(17, 5, out int quotient, out int remainder))
{
    Console.WriteLine($"17 / 5 = {quotient} remainder {remainder}");
}

if (!TryDivide(1, 0, out _, out _))
{
    Console.WriteLine("Cannot divide by zero");
}

static bool TryDivide(int a, int b, out int quotient, out int remainder)
{
    if (b == 0)
    {
        quotient = 0;
        remainder = 0;
        return false;
    }
    quotient = a / b;
    remainder = a % b;
    return true;
}
Output
17 / 5 = 3 remainder 2
Cannot divide by zero

Two conveniences: out int quotient declares the variable right in the call, and out _ (a discard) ignores a value you don't need.

Forgetting to assign an out parameter is a compile error:

C#
static bool TryFind(string key, out string value)
{
    if (key == "a") value = "apple";
    return key == "a";
}
Build error
Program.cs(4,5): error CS0177: The out parameter 'value' must be assigned to before control leaves the current method

The Try pattern in the library

Many .NET methods follow the same "Try" pattern:

C#
var stock = new Dictionary<string, int> { ["apples"] = 12 };

if (stock.TryGetValue("apples", out int count))
    Console.WriteLine($"apples: {count}");

if (!stock.TryGetValue("pears", out _))
    Console.WriteLine("no pears");

if (DateOnly.TryParse("2026-12-25", out DateOnly christmas))
    Console.WriteLine(christmas.DayOfWeek);

if (Enum.TryParse("Friday", out DayOfWeek day))
    Console.WriteLine((int)day);
Output
apples: 12
no pears
Friday
5

in: pass read-only by reference

in passes a variable by reference but forbids the method from changing it. Its purpose is performance: a large struct isn't copied. For ordinary types like int or classes, it gives no benefit.

C#
var p = new Point3(1, 2, 3);
Console.WriteLine(Length(in p));

static double Length(in Point3 v)
{
    v = new Point3(0, 0, 0);
    return Math.Sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z);
}

record struct Point3(double X, double Y, double Z);
Build error
Program.cs(6,5): error CS8331: Cannot assign to variable 'v' or use it as the right hand side of a ref assignment because it is a readonly variable

Remove the assignment and it compiles. (record struct is covered in lesson 40.)

Comparing the modifiers

ModifierCaller must initialise?Method must assign?Method can change it?Typical use
(none)yes—only its own copyAlmost everything
refyesnoyesSwapping, updating a caller's variable
outnoyesyesTry methods, extra results
inyesnonoPassing big structs cheaply

Prefer return values and tuples

ref and out make data flow harder to follow. Outside the Try pattern, returning a value — or a tuple for several values — is usually clearer:

C#
var (q, r) = Divide(17, 5);
Console.WriteLine($"{q} remainder {r}");

Console.WriteLine(Math.DivRem(17, 5));   // the library has this built in

static (int Quotient, int Remainder) Divide(int a, int b) => (a / b, a % b);
Output
3 remainder 2
(3, 2)

Try it yourself

Write TryParseTime that turns text like "14:30" into hours and minutes, returning false for invalid input.

Try it yourself · Program.cs
Edit the code, copy it into Program.cs and run it with dotnet run Program.cs (.NET 10 or later). How to set up .NET
Output
14:30 -> 14 h 30 min
9:05 -> 9 h 5 min
25:00 -> invalid
noon -> invalid