Understand ref, in and out in C#

Elijah Koulaxis

November 16, 2025

When you write c#, how you pass parameters matters, both for performance and for keeping your code readable

ref in and out are three ways to pass things by reference instead of making copies

Value vs Reference

By default:

So this:

void Increment(int number)
{
    number++;
}

won't change your original value. No surprises here

ref "You can change it"

ref lets a method read and modify the original variable. The caller also has to use the keyword, so it's very obvious something might change

void Increment(ref int n)
{
    n++;
}

int x = 5;
Increment(ref x);
Console.WriteLine(x); // 6

You want to use ref when:

but it's an overkill if you use it for types like int or bool, or when you don't really need mutation

in "You can look but don't touch"

very simple: Pass by reference, but read-only

Great when you have big structs and don't want to copy them around

int Add(in a, in b)
{
    return a + b;
}

You can't modify a or b inside the method, the compiler will smack your hand

It's best for large readonly structs, APIs where you want to guarantee immutability and performance

Here's a Gotcha you should remember: If the struct isn't readonly, the compiler sometimes makes defensive copies under the hood. So if you want true performance use readonly struct

out "Here, fill this for me"

this parameter is for output only

The method must assign it to before returning

if (int.TryParse("123", out int value))
{
    Console.WriteLine(value); // 123
}

You want to use out when:

But if you're returning 15 values.. chill. Use a tuple or a small type instead :D

Decision Making

Performance Rules of Thumb

I like to think of it as follows:

e.g.

struct Small
{
    public float X; // 4 bytes
    public float Y; // 4 bytes
    public float Z; // 4 bytes
}

e.g.

struct Medium
{
    public float X, Y, Z;          // 12 bytes
    public float RotX, RotY, RotZ; // 12 bytes
    public float Scale;            // 4 bytes

    // ~(28-32 bytes depending on padding)
}

e.g.

struct BigMatrix4x4
{
    public float M11, M12, M13, M14;
    public float M21, M22, M23, M24;
    public float M31, M32, M33, M34;
    public float M41, M42, M43, M44;
    // 16 floats -> 64 bytes
}

Because copying big structs repeatedly is basically asking the CPU to cry

Just remember to use those tools in the right situations. Otherwise, pass by value and don’t stress. Most of the time, the simplest option is the right one ;)

Tags:
Back to Home