Skip to main content
๐Ÿ“œ WAYPOINT LESSON

ref, out, in, params โ€” The Passing Toolbox

โญโญ intermediateโณ 16 min read๐Ÿ“ Lesson 90 of 265

What each modifier promises, when it is honest design, and the aliasing hazards it creates.

The default: by value, always

C# passes arguments by value. For value types that means a copy of the data; for reference types, a copy of the reference โ€” which is why a method can mutate the object you passed but can never repoint your variable:

static void Replace(List<int> list) => list = new List<int>();  // caller unaffected
static void Fill(List<int> list) => list.Add(1);                // caller sees the item

The modifiers, each with one job

  • out โ€” "this method will definitely assign your variable." The compiler enforces assignment-on-all-paths. One output (plus a bool success in TryX patterns). Declare inline: int.TryParse(s, out int n).
  • ref โ€” "your variable's storage is shared." The method may read, write, or repoint it; the variable must be initialized first. Use for swap-like semantics or interlocked state โ€” rarely.
  • in โ€” ref for reading: a readonly reference to (usually) a large struct. The compiler guards against mutation. Use when copying a big struct costs measurably more than the indirection.
  • params โ€” variadic tail: params int[] values, or params ReadOnlySpan<int> in modern code. Zero-or-more arguments; must be last.
static bool TryDivide(int a, int b, out int result)
{
    if (b == 0) { result = 0; return false; }   // MUST assign on all paths
    result = a / b;
    return true;
}

static void Swap(ref int a, ref int b) => (a, b) = (b, a);

readonly struct Big { public readonly long A, B, C, D, E, F, G, H; }
static long Sum(in Big b) => b.A + b.B + b.C + b.D + b.E + b.F + b.G + b.H;

Return what you mean

Before reaching for out, ask: would a tuple return read better? (bool Ok, int Value) carries the same information without aliasing โ€” but out + TryX is the idiomatic .NET shape for parse-style operations, and ref returns (ref T this[int i]) exist for genuinely in-place structures. Choose deliberately; document the choice.

Check your understanding

  • Why must out assign on every path? (The caller's variable is trusted initialized.)
  • When is in a real win? (Large structs passed in hot paths; it skips the copy.)
  • What does params expand to? (An array allocation per call โ€” or a span.)

โšก Now practice

Ready to Code
Parameter Passing PracticeOut-contracts that assign before they return, ref totals that survive the call, and params counting โ€” the passing toolbox under test.
3 challenges ยท ยท ~25 min