Skip to content

Parameter Passing Techniques in C#

When you call a method, you usually send it some data to work with. The way that data is sent to the method is called parameter passing. C# gives us several techniques, and each one decides an important question: can the method change the caller's variable or not?

In this lesson we learn all of them with small, easy examples:

  • Pass by value (the default)
  • Pass by reference with ref
  • Output parameters with out
  • Read-only reference with in
  • Variable number of arguments with params
  • Optional parameters and named arguments

Parameter vs argument

These two words are often mixed up, so let's make them clear first.

static int Add(int a, int b)    // a and b are PARAMETERS (in the method definition)
{
    return a + b;
}

int result = Add(10, 20);       // 10 and 20 are ARGUMENTS (the actual values sent)
  • Parameter: the variable written in the method definition. It is like an empty box waiting for a value.
  • Argument: the real value you pass when you call the method.

Quick recap: value types and reference types

To understand parameter passing, you must remember what a variable holds:

Value typesReference types
Examplesint, double, bool, char, decimal, DateTime, struct, enumclass objects, string, arrays, List<T>
The variable holdsThe actual value (e.g. 10)An address (reference) pointing to the object in memory
Copying the variable copiesThe valueThe address, so both copies point to the same object

Think of a value type as a photocopy of a document: if someone writes on the photocopy, your original is safe. A reference type is like sharing the address of your house: whoever has the address can go and repaint your house.

1. Pass by value (the default)

When you don't write any keyword, C# passes a copy of the argument to the method. The method works on its own copy.

Pass by value with a value type

using System;

class Program
{
    static void Increase(int number)
    {
        number = number + 10;
        Console.WriteLine($"Inside method: {number}");
    }

    static void Main()
    {
        int marks = 50;
        Increase(marks);
        Console.WriteLine($"After method : {marks}");
    }
}
Output
Inside method: 60
After method : 50

The method changed only its own copy. The caller's marks is still 50.

Pass by value with a reference type

This is where many learners get confused. A copy is still passed, but the copy is a copy of the address. So both variables point to the same object.

class Student
{
    public string Name { get; set; } = "";
    public int Marks { get; set; }
}

class Program
{
    static void GiveBonus(Student s)
    {
        s.Marks = s.Marks + 10;          // changes the SAME object the caller has
    }

    static void ReplaceStudent(Student s)
    {
        s = new Student { Name = "New Student", Marks = 99 };   // only the local copy now points to a new object
    }

    static void Main()
    {
        Student rahul = new Student { Name = "Rahul", Marks = 70 };

        GiveBonus(rahul);
        Console.WriteLine($"After GiveBonus     : {rahul.Name}, {rahul.Marks}");

        ReplaceStudent(rahul);
        Console.WriteLine($"After ReplaceStudent: {rahul.Name}, {rahul.Marks}");
    }
}
Output
After GiveBonus     : Rahul, 80
After ReplaceStudent: Rahul, 80
  • Changing the object's data (s.Marks = ...) is visible to the caller, because both point to the same object.
  • Assigning a new object (s = new Student()) is not visible to the caller, because only the method's copy of the address changed.

string is a reference type, but it is immutable (it can never be changed). Any "change" like name = name + "!" creates a new string, so a method can never change the caller's string when it is passed by value.

2. Pass by reference with ref

The ref keyword passes the variable itself, not a copy. Any change inside the method changes the caller's variable.

Rules of ref:

  • Write ref in both places: the method definition and the method call.
  • The variable must be initialised before passing it.
  • The method may read it and may change it (changing is not required).
static void Increase(ref int number)
{
    number = number + 10;
}

static void Main()
{
    int marks = 50;
    Increase(ref marks);
    Console.WriteLine($"After method: {marks}");
}
Output
After method: 60

Classic example: swap two numbers

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

static void Main()
{
    int a = 5, b = 9;
    Swap(ref a, ref b);
    Console.WriteLine($"a = {a}, b = {b}");
}
Output
a = 9, b = 5

Without ref, the method would swap only its own copies and a and b would stay 5 and 9.

ref with a reference type

Remember ReplaceStudent from above? With ref, assigning a new object does reach the caller:

static void ReplaceStudent(ref Student s)
{
    s = new Student { Name = "New Student", Marks = 99 };
}

Student rahul = new Student { Name = "Rahul", Marks = 70 };
ReplaceStudent(ref rahul);
Console.WriteLine($"{rahul.Name}, {rahul.Marks}");   // New Student, 99

3. Output parameters with out

The out keyword is used when a method needs to send a value back through a parameter. It is very useful when a method must return more than one value.

Rules of out:

  • Write out in both the definition and the call.
  • The variable does not need to be initialised before the call.
  • The method must assign a value to it before it returns, otherwise you get a compile error.
static void Calculate(int a, int b, out int sum, out int product)
{
    sum = a + b;
    product = a * b;
}

static void Main()
{
    int total, multiply;                 // not initialised, and that's fine
    Calculate(4, 5, out total, out multiply);
    Console.WriteLine($"Sum = {total}, Product = {multiply}");
}
Output
Sum = 9, Product = 20

Declaring the out variable in the call (C# 7+)

Calculate(4, 5, out int total, out int multiply);   // declare inline
Calculate(4, 5, out int onlySum, out _);            // _ = "I don't need this value" (discard)

Real-world example: int.TryParse

The most famous use of out in .NET is the TryParse pattern. It returns true/false and gives the converted value through out:

string input = "125";

if (int.TryParse(input, out int age))
{
    Console.WriteLine($"Valid age: {age}");
}
else
{
    Console.WriteLine("Please enter a number");
}
Output
Valid age: 125

If the method forgets to assign an out parameter, the compiler stops you:

static void GetValue(out int x)
{
    // forgot to assign x
}
Compiler error
error CS0177: The out parameter 'x' must be assigned before control leaves the current method

4. Read-only reference with in

The in keyword (C# 7.2+) passes the variable by reference, like ref, but the method is not allowed to change it. It is mainly used to pass large structs without copying them, while still keeping them safe.

struct Point3D
{
    public double X, Y, Z;
}

static double Distance(in Point3D p)
{
    // p.X = 10;   // compile error: p is read-only
    return Math.Sqrt(p.X * p.X + p.Y * p.Y + p.Z * p.Z);
}

static void Main()
{
    Point3D point = new Point3D { X = 3, Y = 4, Z = 12 };
    Console.WriteLine(Distance(in point));   // "in" is optional at the call site
}
Output
13

For small types like int there is no benefit in using in. Use it for big structs in performance-sensitive code.

ref vs out vs in at a glance

Pointrefoutin
Passed byReferenceReferenceReference
Must be initialised before the call?YesNoYes
Method must assign a value?NoYesNot allowed
Method can change it?YesYes (must)No (read-only)
Data directionIn and out (two-way)Out onlyIn only
Keyword at the call siteRequiredRequiredOptional
Typical useSwap, update the caller's variableReturn multiple values, TryParsePass large structs without copying

5. Variable number of arguments with params

Sometimes you don't know how many values the caller will send. The params keyword lets a method accept any number of arguments (including zero) as an array.

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

static void Main()
{
    Console.WriteLine(Sum());                  // 0 arguments
    Console.WriteLine(Sum(5));                 // 1 argument
    Console.WriteLine(Sum(1, 2, 3, 4));        // 4 arguments
    Console.WriteLine(Sum(new[] { 10, 20 }));  // an array also works
}
Output
0
5
10
30

You already use params every day: Console.WriteLine("{0} {1}", a, b) and string.Join(", ", ...) use it.

Rules of params:

  • A method can have only one params parameter.
  • It must be the last parameter: void Log(string message, params object[] values)
  • It must be a single-dimensional array (from C# 13 other collection types such as List<T> and Span<T> are also allowed).
  • It can't be combined with ref, out or in.

6. Optional parameters

An optional parameter has a default value. If the caller doesn't pass it, the default is used.

static void PrintBill(string item, int quantity = 1, double discount = 0)
{
    Console.WriteLine($"{item} x {quantity}, discount {discount}%");
}

static void Main()
{
    PrintBill("Pen");               // uses both defaults
    PrintBill("Notebook", 3);       // uses default discount
    PrintBill("Bag", 2, 10);        // no defaults used
}
Output
Pen x 1, discount 0%
Notebook x 3, discount 0%
Bag x 2, discount 10%

Rules of optional parameters:

  • Optional parameters must come after all required parameters.
  • The default value must be a compile-time constant (a number, string, null, default), not something like DateTime.Now.

7. Named arguments

Named arguments let you pass a value by writing the parameter name. This makes calls easier to read and lets you skip some optional parameters.

// Skip quantity, set only discount
PrintBill("Shoes", discount: 15);

// Any order is allowed when all arguments are named
PrintBill(discount: 5, item: "Watch", quantity: 2);

// Much more readable than CreateUser("Amit", true, false)
CreateUser(name: "Amit", isAdmin: true, sendWelcomeEmail: false);
Output (of the two PrintBill calls)
Shoes x 1, discount 15%
Watch x 2, discount 5%

All techniques together

Here is one small program that uses every technique, so you can see them side by side:

using System;

class Program
{
    static void Main()
    {
        int stock = 10;
        AddStock(stock);                            // by value: no change
        Console.WriteLine($"By value : {stock}");   // 10

        AddStock(ref stock);                        // ref: changed
        Console.WriteLine($"By ref   : {stock}");   // 15

        GetPriceRange(out int min, out int max);    // out: returns two values
        Console.WriteLine($"Range    : {min} - {max}");

        Console.WriteLine($"Total    : {Total(100, 250, 75)}");   // params

        Greet("Priya");                             // optional
        Greet("Priya", greeting: "Welcome");        // named
    }

    static void AddStock(int qty) => qty += 5;
    static void AddStock(ref int qty) => qty += 5;

    static void GetPriceRange(out int min, out int max)
    {
        min = 100;
        max = 500;
    }

    static int Total(params int[] prices)
    {
        int sum = 0;
        foreach (int p in prices) sum += p;
        return sum;
    }

    static void Greet(string name, string greeting = "Hello")
    {
        Console.WriteLine($"{greeting}, {name}!");
    }
}
Output
By value : 10
By ref   : 15
Range    : 100 - 500
Total    : 425
Hello, Priya!
Welcome, Priya!

Notice that AddStock(int) and AddStock(ref int) are two different overloads. You can overload a method using ref versus no keyword, but you cannot have two overloads that differ only by ref and out.

Which technique should I use?

You want to...Use
Just give data to a method (most cases)Pass by value (no keyword)
Let the method change the caller's variableref
Get extra values back, or use a Try... patternout
Pass a large struct quickly and safelyin
Accept any number of valuesparams
Give parameters sensible defaultsOptional parameters
Make calls with many arguments readableNamed arguments

Tip: don't overuse ref and out. If a method needs to return several values, a tuple such as (int Sum, int Product) Calculate(int a, int b) or a small class is often cleaner.

Preparing for interviews? Read Parameter Passing Interview Questions and Answers.

Summary

  • By default C# passes arguments by value: the method gets a copy.
  • For reference types, the copy is a copy of the address, so the method can change the object's data but cannot replace the caller's object.
  • ref: two-way, must be initialised before the call.
  • out: one-way out, need not be initialised, must be assigned inside the method.
  • in: by reference but read-only, useful for large structs.
  • params: any number of arguments, only one and only as the last parameter.
  • Optional parameters have default values; named arguments pass values by parameter name.