Reference parameter modifiers solve different problems: out returns a value through an argument, ref allows caller-visible mutation, and in (C# 7.2) passes by reference while keeping the argument readonly.
Why it matters
- Avoids tuple allocations and exception-heavy parse flows in hot paths.
- Enables explicit mutation semantics with
ref. - Improves performance for large structs via
inwithout sacrificing safety.
Cautions
Use these modifiers intentionally because they affect API ergonomics. ref and out can make call sites noisier and harder to compose. Prefer clear names and keep referenced data lifetimes straightforward.
Use out for parse-style APIs
out parameters return extra values while signaling success or failure.
Valid since C# 7.0
using System;
if (int.TryParse("123", out int value))
{
Console.WriteLine(value + 1);
}
else
{
Console.WriteLine("Invalid number");
}
Use ref when caller state should be updated
ref parameters let methods modify caller variables directly.
Valid since C# 7.0
using System;
static void Increment(ref int number)
{
number++;
}
int count = 10;
Increment(ref count);
Console.WriteLine(count); // 11
Use in for readonly pass-by-reference
in passes large value types by reference while preventing modification.
Valid since C# 7.2
using System;
public readonly struct Vector3
{
public Vector3(double x, double y, double z)
{
X = x;
Y = y;
Z = z;
}
public double X { get; }
public double Y { get; }
public double Z { get; }
}
public static class Program
{
private static double Length(in Vector3 value)
{
return Math.Sqrt((value.X * value.X) + (value.Y * value.Y) + (value.Z * value.Z));
}
public static void Main()
{
var vector = new Vector3(3, 4, 12);
Console.WriteLine(Length(in vector));
}
}