ref, out and in

Parameter modifiers that pass a variable itself instead of a copy: ref to read and write it, out to fill it in, in to read it without copying.

Syntaxvoid M(ref int a, out int b, in BigStruct c) // call: M(ref x, out int y, in z)

By default C# passes arguments by value: the method gets a copy. Assigning to a parameter inside the method changes only that copy. Three modifiers change this by passing the caller's variable itself, which is called passing by reference.

ref

ref means the method may read and write the caller's variable. The keyword is required on the declaration and again at the call, which keeps the effect visible where it happens. The argument must already hold a value.

out

out means the method will set the variable. The caller need not give it a value first, and the method must assign it on every path before returning. This is how the Try pattern returns two things: a bool saying whether it worked, and the result. int.TryParse(string? s, out int result) is the example everyone meets first.

in

in passes a read-only reference. Assigning to it is error CS8331. Its purpose is performance: a struct is a value type you define yourself, and a large one is expensive to copy on every call. in avoids the copy. The keyword is optional at the call site; without it the compiler may quietly pass a temporary copy instead. C# 12 added ref readonly, which demands a real variable rather than an expression.

What trips people up

None of these modifiers may appear on an async method or an iterator. A property is not a variable, so a property cannot be passed as a ref or out argument.

Example

Example · csharp
void AddOne(ref int value)
{
    value = value + 1;   // writes straight into the caller's variable
}

bool TryHalf(int input, out int half)
{
    if (input % 2 != 0)
    {
        half = 0;        // out must be assigned on this path too
        return false;
    }

    half = input / 2;
    return true;
}

int counter = 5;         // a ref argument must already have a value
AddOne(ref counter);
Console.WriteLine(counter);

if (TryHalf(counter, out int result))
{
    Console.WriteLine($"half: {result}");
}

// The same pattern from the base class library.
if (int.TryParse("1640", out int parsed))
{
    Console.WriteLine($"parsed: {parsed}");
}

// Output:
// 6
// half: 3
// parsed: 1640

When to use it

  • Write a TryGet method that reports failure without throwing and hands back the value it found.
  • Parse user input with int.TryParse instead of letting int.Parse throw.
  • Pass a large struct into a hot loop without copying it on every call.

More examples

out: the long form, the short form and the discard

The inline out declaration is pure shorthand for declaring the variable on the line above, and it keeps the variable scoped to where it is actually valid. Use the discard _ when you are testing a value rather than converting it.

Example · csharp
bool TryDivide(int left, int right, out int quotient)
{
    if (right == 0)
    {
        quotient = 0;
        return false;
    }

    quotient = left / right;
    return true;
}

// Longhand: declare the variable, then pass it.
int answer;
bool ok = TryDivide(84, 2, out answer);
Console.WriteLine($"{ok} {answer}");

// Shorthand, same thing: declare it inside the call (C# 7.0 and later).
if (TryDivide(84, 2, out int inline))
{
    Console.WriteLine(inline);
}

// _ discards the value when only success or failure matters.
Console.WriteLine(TryDivide(1, 0, out _));

// Output:
// True 42
// 42
// False

in: a read-only reference to a struct

Reach for in when a method takes a struct noticeably larger than a machine word and is called in a loop. For an int or a small struct it buys nothing, so leave it off.

Example · csharp
int Brightness(in Rgb colour)
{
    // colour is read-only here.
    // Uncommenting the next line gives error CS8332: cannot assign to a member
    // of a variable because it is a readonly variable.
    // colour.Red = 0;
    return (colour.Red + colour.Green + colour.Blue) / 3;
}

Rgb grey = new Rgb();
grey.Red = 90;
grey.Green = 120;
grey.Blue = 150;

// in at the call site is optional, but it forces the by-reference pass.
Console.WriteLine(Brightness(in grey));

// Output:
// 120

// A struct is a value type you define yourself. Stage 3 covers structs.
struct Rgb
{
    public int Red;
    public int Green;
    public int Blue;
}

ref on a reference-type variable

Without ref, a method can never make the caller's variable point at something else, even for a class or string. With ref it can. Swapping and resetting are the honest uses; most other uses are better served by returning a value.

Example · csharp
void Swap(ref string a, ref string b)
{
    string temp = a;
    a = b;
    b = temp;
}

string first = "left";
string second = "right";

Swap(ref first, ref second);
Console.WriteLine($"{first} {second}");

// Output:
// right left

Discussion

  • Be the first to comment on this lesson.