.NET Academy

ref, out and params

Control how arguments are passed — by value, by reference, as outputs, or as a variable-length list.

10 min read

You will learn

  • Explain pass-by-value, the default
  • Modify a caller's variable with ref
  • Return extra results with out and the Try pattern
  • Accept any number of arguments with params

Arguments are copied by default

When you call a method, each argument is copied into the parameter. The method works on its copy, so the caller's variable is safe:

void Double(int n)
{
    n = n * 2;                 // changes the copy only
}
 
int x = 5;
Double(x);
Console.WriteLine(x);          // 5

This is called pass by value, and it's what you want most of the time: methods that return results instead of secretly changing your variables are easier to understand.

ref: let the method change the caller's variable

Sometimes a method really should modify the caller's variable. Mark the parameter — and the argument — with ref:

void Double(ref int n)
{
    n = n * 2;
}
 
int x = 5;
Double(ref x);
Console.WriteLine(x);          // 10

Now n isn't a copy; it's another name for x itself. The ref at the call site is mandatory, so readers can see that x might change.

The classic use is swapping:

void Swap(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}

out: return more than one value

A method has one return value. out parameters let it hand back extra results:

void MinMax(int[] numbers, out int min, out int max)
{
    min = numbers.Min();
    max = numbers.Max();
}
 
MinMax(new[] { 4, 1, 9 }, out int lo, out int hi);   // declare the variables inline
Console.WriteLine($"{lo}..{hi}");                    // 1..9

The compiler insists that every out parameter is assigned before the method returns.

The Try pattern

You've already met the most important use of out: methods named TryXxx that return true/false for success and put the result in an out parameter — no exceptions for expected failures.

if (int.TryParse(input, out int age))
{
    Console.WriteLine($"Next year you'll be {age + 1}");
}
else
{
    Console.WriteLine("That's not a number");
}
 
// Discard an out value you don't need with _
bool isNumber = int.TryParse(input, out _);

Dictionary.TryGetValue (Track 3) follows the same pattern, and you can write your own.

Prefer tuples for new code

For returning several values from your own methods, a tuple — (int Min, int Max) MinMax(...) — is often clearer than out. You'll meet tuples in the Modern C# features module. out remains the convention for the Try pattern.

params: any number of arguments

params lets callers pass a list of values without building an array themselves:

int Sum(params int[] numbers)
{
    int total = 0;
    foreach (int n in numbers) total += n;
    return total;
}
 
Sum();                 // 0 — numbers is an empty array
Sum(5);                // 5
Sum(1, 2, 3, 4);       // 10
Sum(new[] { 1, 2 });   // an array works too

params must be the last parameter. You use it all the time without noticing: Console.WriteLine("{0} + {1}", a, b) and string.Join(", ", a, b, c) are params methods.

Try it

Program.cs
Loading editor…

C# runtime not loaded

Key takeaways

  • Arguments are copied by default — the method can't change the caller's variables.
  • ref passes the variable itself; required at both declaration and call site.
  • out parameters are extra outputs; the Try pattern uses them to avoid exceptions.
  • params accepts any number of arguments as an array.

Exercise

Implement Swap (exchange the values of two variables using ref), TryDivide (return false when dividing by zero, otherwise true with the quotient in result) and Sum (add up any number of integers using params; Sum() with no arguments is 0).

Program.cs
Loading editor…

C# runtime not loaded

Hint 1

Swap needs a temporary variable: int temp = a; a = b; b = temp;

Hint 2

An out parameter must be assigned on every path before the method returns — set result = 0 before returning false.

Hint 3

Inside Sum, numbers is a normal int[] — loop over it.

Show solution
void Swap(ref int a, ref int b)
{
    int temp = a;
    a = b;
    b = temp;
}

bool TryDivide(int dividend, int divisor, out int result)
{
    if (divisor == 0)
    {
        result = 0;
        return false;
    }
    result = dividend / divisor;
    return true;
}

int Sum(params int[] numbers)
{
    int total = 0;
    foreach (int n in numbers) total += n;
    return total;
}

int x = 1, y = 2;
Swap(ref x, ref y);
Console.WriteLine($"x={x}, y={y}");

if (TryDivide(10, 2, out int q)) Console.WriteLine(q);
Console.WriteLine(Sum(1, 2, 3));

Quiz

4 questions · 3 correct to pass
  1. 1.`void AddOne(int n) { n++; }` — after `int x = 5; AddOne(x);` what is x?

  2. 2.What's the key difference between ref and out?

  3. 3.What does `int.TryParse("abc", out int n)` return?

  4. 4.With `int Sum(params int[] numbers)`, which call is NOT valid?

To complete this lesson

  • ○ Solve the exercise
  • ○ Pass the quiz (3/4 correct)