C#64 min total · 17 parts
C# Fundamentals: Value Types, Reference Types, Boxing, and the Type System
Part 11 of 17 · ~2 min
ref, out, and in Parameters
Three keywords change what a parameter is actually connected to. Instead of a fresh copy, the method gets pointed directly at wherever the caller's own variable lives — each of the three does that for a different reason.
// ref — caller must initialize the variable first; the method can read AND write it
bool TryAdjust(ref StockCount count, int delta, out string error)
{
if (count.OnHand + delta < 0)
{
error = $"{count.Sku}: adjustment of {delta} would oversell (on hand {count.OnHand})";
return false; // count is left exactly as it was
}
count.OnHand += delta; // the CALLER'S StockCount is updated, not a copy of it
error = "";
return true;
}
var stock = new StockCount { Sku = "WDG-100", OnHand = 5 };
bool ok = TryAdjust(ref stock, -3, out string err);
Console.WriteLine($"{ok}, {stock.OnHand}"); // True, 2 — the caller's variable changed in place
bool blocked = TryAdjust(ref stock, -50, out string err2);
Console.WriteLine($"{blocked}, {stock.OnHand}, {err2}"); // False, 2, "WDG-100: adjustment of -50 would oversell (on hand 2)"
Notice TryAdjust needed to say two separate things on the way out — did it work, and if not, why not — and a bool return value can only carry one of those. Bolting an out parameter onto a bool return is how the framework itself solves this everywhere, from int.TryParse down: it sidesteps throwing an exception for something that's a routine, expected outcome rather than a genuine error — an oversell attempt here is closer to "form validation failed" than to "the program broke."
in solves a completely different problem: letting a method see a large struct without the cost of duplicating it, while still making it impossible for that method to alter the caller's copy. Bring back WarehouseZoneStats — eight fields, real money to copy every time it's passed:
void PrintZoneReport(in WarehouseZoneStats stats)
{
Console.WriteLine($"Zone {stats.ZoneId}: {stats.ItemsScanned} scanned");
// stats.ItemsScanned = 0; // compile error CS8332 — an `in` parameter is read-only inside the method
}
That compile error is worth actually seeing rather than taking on faith — verified directly against the compiler while writing this: assigning to a field of an in parameter fails with CS8332: Cannot assign to a member of variable 'stats' ... because it is a readonly variable. in gets ref's "no copy" benefit for a struct this size, with the compiler itself enforcing that the callee can't be the one to mutate the caller's data.