Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C# & .NET (C# 14, .NET 10 LTS)  Module 3 – Control flow and methods

ref, out, in and ref readonly parameters

Pass variables by reference in C# with ref, return extra results with out, pass large structs read-only with in and ref readonly, and write Try methods.

  • Intermediate
  • 15 minutes
  • Examples run with .NET 10.0.401 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 6 of 15 minutes free
Free: A plain parameter gets a copy, and ref passes the variable itself · out hands back an extra result, and the Try pattern is built on it · in and ref readonly pass a variable for reading only. With a pass: 3 in-depth parts and 3 interview answers, about 9 minutes.

What you will learn

  • Pass a variable by reference with ref, and return extra results with out
  • Write and call a Try method that returns bool and its value through out
  • Choose between in, ref readonly and a plain parameter for a struct, and spot hidden copies
  • Fix the compiler errors that ref, out and in parameters cause
  • Write a C# 14 lambda whose parameters take out or ref without their types

Before you start

What you’ll learn · Before you start

What you will learn

  • Pass a variable by reference with ref, and return extra results with out
  • Write and call a Try method that returns bool and its value through out
  • Choose between in, ref readonly and a plain parameter for a struct, and spot hidden copies
  • Fix the compiler errors that ref, out and in parameters cause
  • Write a C# 14 lambda whose parameters take out or ref without their types

Before you start

On this page

Swap(a, b) with a at 1 and b at 2 swaps nothing: the method receives copies of 1 and 2 and swaps the copies. To change a caller’s variables, or to hand back more than one result, a parameter must refer to the caller’s variable itself, and C# has four modifiers for that: ref, out, in and ref readonly.

You will swap two variables with ref, hand back extra results with out, write a Try method like int.TryParse, and choose a modifier from a table of what each one allows.

A plain parameter gets a copy, and ref passes the variable itself

By default C# passes every argument by value: the parameter is a new variable that starts with a copy of the argument, so the method can change it without touching the caller. This is passing by value: Passing an argument as a copy: the parameter is a new variable that starts with the argument's value, so assigning to it never changes the caller's variable. C# passes every argument this way unless a modifier says otherwise.. A ref parameter is passing by reference: Passing the caller's variable itself, with ref, out, in or ref readonly: the parameter is another name for that variable, so the method works on it rather than on a copy.: it is another name for the caller’s variable, so whatever the method assigns to it, the caller sees. Both the method and the call write ref, and the variable must have a value before the call. Step through the two swaps:

Interactive · step by step Two swaps, by value and by reference Step through both calls. The dashed arrows are copies made for the call; the solid ones join two names for one variable.
Two swaps, by value and by reference, step 6 of 6. Showing a (in the caller: 2), b (in the caller: 1), x (another name for a), y (another name for b).same variablesame variableain the caller: 2bin the caller: 1xanother name for ayanother name for b

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. SwapCopies(a, b). SwapCopies(a, b) is called. x gets a copy of the value of a, 1, and y a copy of b, 2: two new variables that belong to the method.
  2. The copies are swapped. (x, y) = (y, x) swaps the copies, so x is 2 and y is 1. Nothing connects them to a and b any more.
  3. The copies are gone. SwapCopies returns and its x and y disappear. a is still 1 and b is still 2, as swap.cs printed.
  4. Swap(ref a, ref b). Swap(ref a, ref b) is called. This time x is not a new variable: it is another name for a, and y is another name for b.
  5. The swap reaches the caller. The same swap now changes the variables the names refer to, so a becomes 2 and b becomes 1.
  6. Visible at the call. After the call a is 2 and b is 1. The call had to write ref twice, so anyone reading it can see that a and b may change.

swap.cs: a swap of copies and a swap by ref C# · swap/swap.cs
// A plain parameter is a copy of the caller's value; a ref parameter is the caller's variable itself.
int a = 1, b = 2;
SwapCopies(a, b);
Console.WriteLine($"after SwapCopies: a = {a}, b = {b}");
Swap(ref a, ref b);
Console.WriteLine($"after Swap:       a = {a}, b = {b}");

int[] seats = [7, 9];
Swap(ref seats[0], ref seats[1]); // array elements are variables too
Console.WriteLine($"seats after Swap: {seats[0]}, {seats[1]}");

static void SwapCopies(int x, int y)
{
    (x, y) = (y, x); // swaps the two copies, and nothing else
}

static void Swap(ref int x, ref int y)
{
    (x, y) = (y, x);
}

Output

after SwapCopies: a = 1, b = 2
after Swap:       a = 2, b = 1
seats after Swap: 9, 7

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run swap.cs

SwapCopies swapped its own x and y and left a and b as they were. Swap(ref a, ref b) made x another name for a and y for b, so the same swap changed the caller’s variables. Array elements are variables too, which is why Swap(ref seats[0], ref seats[1]) swapped the two seats. A property is not a variable, so it cannot be passed with ref.

out hands back an extra result, and the Try pattern is built on it

An out parameter is passed by reference as well, but its value flows the other way: the caller need not give it a value, and the method must assign it before it returns. The call can declare the variable on the spot, out int number, or throw the result away with out _. Every TryParse method in .NET has one:

tryparse.cs: int.TryParse and a Try method of your own C# · tryparse/tryparse.cs
// out hands back an extra result. A Try method returns whether it worked, and the value through out.
foreach (string text in new[] { "42", "4x2", "", "2147483648" })
{
    bool ok = int.TryParse(text, out int number);
    Console.WriteLine($"int.TryParse(\"{text}\"): {ok}, number = {number}");
}

if (TryParseQuantity("12 kg", out int quantity, out string unit))
{
    Console.WriteLine($"TryParseQuantity(\"12 kg\"): {quantity} and {unit}");
}
Console.WriteLine($"TryParseQuantity(\"kg\"): {TryParseQuantity("kg", out _, out _)}");

static bool TryParseQuantity(string text, out int quantity, out string unit)
{
    string[] parts = text.Split(' ', 2);
    if (parts.Length == 2 && int.TryParse(parts[0], out quantity))
    {
        unit = parts[1];
        return true;
    }
    quantity = 0;
    unit = "";
    return false;
}

Output

int.TryParse("42"): True, number = 42
int.TryParse("4x2"): False, number = 0
int.TryParse(""): False, number = 0
int.TryParse("2147483648"): False, number = 0
TryParseQuantity("12 kg"): 12 and kg
TryParseQuantity("kg"): False

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run tryparse.cs

int.TryParse returned true and 42 for “42”, and false with number set to 0 for the other three: “4x2” is not a number, the empty string has no digits, and 2147483648 is one more than int.MaxValue. This is the Try pattern: A method named TrySomething that returns true or false for success and hands back its result through an out parameter, such as int.TryParse, beside a method that throws for bad input. of Microsoft’s design guidelines: the name starts with Try, the method returns bool for success and hands back the value through out, and a method that throws exists beside it for callers that expect valid input. TryParseQuantity follows the pattern with two out parameters and assigns both on every path, because the compiler refuses a method that could return without assigning one of them.

Checkpoint

Check: Copies and ref parameters · out parameters and the Try pattern

  1. Question 1 of 2 What does this print?

    Read the code, then choose one answer.

    static void Reset(int n)
    {
        n = 0;
    }
    
    int x = 5;
    Reset(x);
    Console.WriteLine(x);
    Show the answer: Question 1 of 2

    Answer: 5

    Passing by value gives the method a new variable with a copy of the argument, so assigning the parameter never reaches the caller.

  2. Question 2 of 2 What do int.TryParse("2147483648", out int n) return and leave in n?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: false, and n is 0

    A Try method returns false for input it cannot handle and still assigns its out parameter, to a default such as 0.

in and ref readonly pass a variable for reading only

In parameters and ref readonly parameters pass by reference too, but the method may only read the variable. They exist for structs, whose every byte a copy moves, while a reference is 8 bytes on a 64-bit process. With in the call may leave the modifier out, and a constant is passed through a hidden copy. With ref readonly, new in C# 12, the call writes in or ref, and the compiler warns when it writes neither.

Modifier Caller gives it a value first Method may assign it The call writes
none yes its own copy only F(x)
ref yes yes F(ref x)
out no must, before it returns F(out x) or F(out var x)
in yes no F(x) or F(in x)
ref readonly yes no F(in x) or F(ref x)

Passing a large struct with in avoids copying it, but calling a method that may change the struct on an in parameter makes the compiler copy it anyway. Async methods and iterators cannot have ref, out or in parameters at all, so an async method returns its extra results in a tuple or a record instead. C# 14 lets a lambda’s parameters carry these modifiers without their types.

Key idea

ref lets a method read and change the caller’s variable, out makes it assign one, and in and ref readonly let it only read one, so the modifier on a parameter says what may happen to the argument.

Checkpoint

Check: in and ref readonly

  1. One question A method only reads a large struct and never changes it, and some callers pass a new value that is no variable, such as new Reading(21.5, 48.0). Which parameter fits best?

    Choose one answer.

    Show the answer: One question

    Answer: in Reading r

    in and ref readonly both pass a variable for reading only. in also takes values that are not variables, through a hidden copy; ref readonly asks the caller for a variable and warns when the call writes neither in nor ref.

in and ref readonly with structs: hidden copies shown, and when they pay Pro: opens with a Pro, Premium or Ultimate pass

in promises that the method will not change the caller’s struct, and the compiler keeps that promise even when it costs a copy. The runs below make the hidden copy visible, measure how big common structs are, and show the warnings that ref readonly adds at the call.

In this part:

  • A mutable struct behind in: the hidden copy, made visible
  • How big a struct must be before in pays
  • ref readonly: asking the caller for a variable

4 runnable examples (C#, Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

The errors that ref, out and in cause, reproduced and fixed Pro: opens with a Pro, Premium or Ultimate pass

Each of these modifiers comes with rules the compiler enforces, and five of them catch most mistakes. Each project below breaks one rule; the session builds all five, and a table pairs each error with its fix.

In this part:

  • Five builds that stop, and what each one means
  • Results from async code: a tuple instead of out

7 runnable examples (C#, Python) · about 4 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Designing Try methods, and C# 14 lambdas with out parameters Pro: opens with a Pro, Premium or Ultimate pass

A Try method is a small contract, and most bugs in them come from breaking one of its terms. The run below follows the contract with two out parameters and a throwing counterpart, then passes Try methods around as C# 14 lambdas.

In this part:

  • What a Try method promises: TryParseTime and its throwing twin
  • C# 14: out and ref on lambda parameters without their types

2 runnable examples (C#) · about 5 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise: check the format of an IFSC

The IFSC format

The Reserve Bank of India describes the Indian Financial System Code as 11 characters: 4 letters for the bank, a fifth character that is always 0 (zero), and 6 for the branch. Your TryParseIfsc accepts lower case, returns both parts in capitals, sets both outs to “” for anything else, null included, and checks the format only.

ifsc.cs starts with a method that throws, so all eight of its checks fail:

ifsc.cs: eight checks of TryParseIfsc C# · ifsc/ifsc.cs
// Practice: write TryParseIfsc, then run this file until all eight checks pass. An IFSC has 11 characters: 4 letters
// for the bank, a fifth character that is always 0 (zero), and 6 letters or digits for the branch. Accept lower case,
// return both parts in capitals, and set both outs to "" when the input is not an IFSC. This checks the format only.
(string? Input, bool Ok, string Bank, string Branch)[] checks =
[
    ("SBIN0001234", true, "SBIN", "001234"),
    ("HDFC0ABC123", true, "HDFC", "ABC123"),
    ("icic0004321", true, "ICIC", "004321"),
    ("SBIN1001234", false, "", ""),
    ("SBIN000123", false, "", ""),
    ("SB1N0001234", false, "", ""),
    ("SBIN00012$4", false, "", ""),
    (null, false, "", ""),
];

int passed = 0;
foreach (var (input, ok, bank, branch) in checks)
{
    string shown = input is null ? "null" : $"\"{input}\"";
    string verdict;
    try
    {
        bool got = TryParseIfsc(input, out string gotBank, out string gotBranch);
        verdict = got == ok && gotBank == bank && gotBranch == branch
            ? "ok"
            : $"FAIL, returned {got} with \"{gotBank}\" and \"{gotBranch}\"";
    }
    catch (Exception e)
    {
        verdict = $"FAIL, {e.GetType().Name}";
    }
    if (verdict == "ok")
    {
        passed++;
    }
    Console.WriteLine($"TryParseIfsc({shown}): {verdict}");
}
Console.WriteLine($"{passed} of {checks.Length} checks pass");
return passed == checks.Length ? 0 : 1;

static bool TryParseIfsc(string? input, out string bank, out string branch)
{
    throw new NotImplementedException("Check the length and each part, then assign bank and branch.");
}

Output (exit status 1)

TryParseIfsc("SBIN0001234"): FAIL, NotImplementedException
TryParseIfsc("HDFC0ABC123"): FAIL, NotImplementedException
TryParseIfsc("icic0004321"): FAIL, NotImplementedException
TryParseIfsc("SBIN1001234"): FAIL, NotImplementedException
TryParseIfsc("SBIN000123"): FAIL, NotImplementedException
TryParseIfsc("SB1N0001234"): FAIL, NotImplementedException
TryParseIfsc("SBIN00012$4"): FAIL, NotImplementedException
TryParseIfsc(null): FAIL, NotImplementedException
0 of 8 checks pass

Recorded with .NET SDK 10.0.401 (runtime 10.0.12) on macOS 26 arm64. To run it yourself: dotnet run ifsc.cs

Write TryParseIfsc, run dotnet run ifsc.cs until it prints 8 of 8 checks pass, then try the questions below:

Practice that adapts to you

Practise this lesson

9 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 What must be true of a variable before it is passed with ref?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: It must have a value

    ref passes a variable that the method may read and change, so it must be assigned first; out has no such rule.

  2. Question 2 of 9 int[] seats = [7, 9]; Swap(ref seats[0], ref seats[1]); What does seats hold afterwards?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: [9, 7]

    An array element is a variable, so it can be passed with ref, as swap.cs did with the two seats.

  3. Question 3 of 9 cart.Count and cart.Limit are properties. Can you write Swap(ref cart.Count, ref cart.Limit)?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: No: a property is not a variable, so it cannot be passed with ref

    Read the properties into locals, swap the locals, and assign them back.

  4. Question 4 of 9 What must a method do with an out parameter?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: Assign it before it returns, on every path

    An out parameter flows from the method to the caller, so the method must assign it whatever happens, as TryParseQuantity did on both paths.

  5. Question 5 of 9 What does bool ok = int.TryParse("12a", out _); do?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: It sets ok to false and throws the parsed value away

    Use out _ when only the bool matters, as the "kg" call in tryparse.cs did with both outs.

  6. Question 6 of 9 You add a method that reads a 6-digit postal code from text. Which signature follows the Try pattern?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: bool TryParsePostalCode(string text, out int code)

    The Try pattern is a name that starts with Try, a bool return and an out value, with a throwing counterpart for callers that expect valid input.

  7. Question 7 of 9 A method is declared static void Show(in int x). What happens with the call Show(5)?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: It compiles: the compiler stores 5 in a hidden variable and passes a reference to it

    in may leave the modifier out at the call and may take values that are not variables, through a hidden copy.

  8. Question 8 of 9 A method declares ref readonly Reading r, and a call writes Describe(reading) with no modifier. What happens?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: It compiles, and the compiler warns that the argument should be passed with ref or in

    Choose ref readonly when a method needs a real variable and wants callers to say so; choose in when any value will do.

  9. Question 9 of 9 A method must change a large struct that the caller owns, in place, without copying it. Which parameter fits?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: ref BigStruct value

    Pick the modifier that says what happens to the argument: ref to change it, in or ref readonly to read it, out to produce it.

IFSC Code Finder & Validator Look up a real branch's IFSC, or check that a code exists, beyond its format.

Interview questions

Fresher · What is the difference between ref and out parameters in C#?

Both pass a variable by reference, so the method works on the caller’s variable, not on a copy, and both must be written at the call as well as in the method. They differ in which way the value flows. A ref argument must already have a value, and the method may read it and may change it, as Swap(ref a, ref b) does. An out argument need not have a value, and the method must assign it before it returns, or the build fails; the call can declare it on the spot, out int number. Use ref when a method updates an existing value, and out when it produces an extra result, as Try methods do.

  1. Fresher to mid

    Code review finds bool TryGetDiscount(string code, out decimal discount). It returns false for unknown codes but sets discount to the last valid code's rate, and it also returns false when the pricing database times out. What contract should a Try method keep, and how would you test it?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    To save copies, a team changes Process(Reading r) to Process(in Reading r) for a 64-byte struct that is passed millions of times. The benchmark shows no gain, and in one case a loss. Why might in not help, and what would you check and change?

    Model answer, Pro. First step and follow-ups

  3. Fresher to mid

    A parsing method hands back three values through out parameters. Now it must call a web service, so it becomes async, and the build fails with CS1988. Why does the compiler refuse, and what would you return instead?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Arguments are passed by value unless a modifier says otherwise, so a method changes only its own copy.
  • ref makes a parameter another name for the caller’s variable, which must have a value first; the call writes ref too.
  • An out parameter makes the method assign the variable before it returns, which is what the Try pattern uses to hand back a value beside a bool.
  • In parameters and ref readonly parameters pass a variable by reference for reading only, which can save copying a large struct.

Terms in this lesson

passing by reference
Passing the caller's variable itself, with ref, out, in or ref readonly: the parameter is another name for that variable, so the method works on it rather than on a copy.
passing by value
Passing an argument as a copy: the parameter is a new variable that starts with the argument's value, so assigning to it never changes the caller's variable. C# passes every argument this way unless a modifier says otherwise.
Try pattern
A method named TrySomething that returns true or false for success and hands back its result through an out parameter, such as int.TryParse, beside a method that throws for bad input.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.