1. Introduction
In C#, by default, all method parameters are passed by value, meaning the method gets a copy, not the original.
But what if you need to change an external variable from inside a method? Or return several values from a method at once? Or maybe you want to pass a big struct into a method, but you don't want to copy that whole "suitcase with no handle"—you just want to pass a reference to save memory?
All of this is about parameter modifiers. They give you control over the "route" your data takes between the calling code and the method.
Types of Modifiers
| Modifier | Data Direction | Need to initialize before call? | Can read inside? | Can write inside? | Typical Usage Scenarios |
|---|---|---|---|---|---|
| ref | Both ways | Yes | Yes | Yes | Pass a variable for reading and/or changing |
| out | Only out | No | No (before assignment) | Yes (required!) | Return multiple values from a method |
| in | Only in | Yes | Yes | No | Pass big structs for "read only" with memory savings |
Don't worry, we're about to go through each one. It's way easier than it sounds.
2. Modifier ref: Pass by Reference: Read and Write
Imagine this: you bring your friend a cup of coffee and say—"Here, you can drink it, heat it up, or even add something to it." Your friend can do whatever they want with that coffee, and whatever's left (or not left) comes back to you. That's how ref works.
void DoSomething(ref int x)
{
x = x + 10; // Changing the input parameter
}
To pass a parameter as ref, you have to declare it with that modifier both in the method and when calling it:
int myNumber = 5;
DoSomething(ref myNumber);
Console.WriteLine(myNumber); // prints 15
It has to be a variable, you can't pass an expression by reference. This code won't work:
DoSomething(ref 10); // you need a variable here!
- Before the call: the variable must be initialized (assigned a value).
- Inside the method: you can read and change the value.
- After the call: changes are saved.
Example: Swapping Variable Values
Sometimes you need to swap the values of two variables. But here's the catch: in C#, value types (like int) are passed by value by default. That means when you pass them to a method, their values are copied, not the references to the variables. For the method to change the actual variables in the calling code, you need to use the ref keyword.
Example without ref—doesn't work:
// Trying to swap values—won't work
void Swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
int x = 10;
int y = 20;
Swap(x, y); // Passing value copies
Console.WriteLine($"{x}, {y}"); // → 10, 20 — nothing changed!
Inside the Swap function, the variables a and b are just copies of x and y. We're only changing those copies, the originals stay untouched.
Example with ref—variables actually swap
// Swapping variable values using ref
void Swap(ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
int x = 10;
int y = 20;
Swap(ref x, ref y); // Passing variable references
Console.WriteLine($"{x}, {y}"); // → 20, 10 — swapped correctly
ref lets you pass references to the variables themselves, not just their values.
So the Swap function works directly with x and y, changing their contents.
When to use ref?
- You need to change the passed variable (like increment it, replace it, etc.).
- You don't need to return a new value (like with return), because you want to change the argument directly.
- Don't use it to "dump" a bunch of different values—there's another modifier for that.
3. Modifier out: Passing Out
Now imagine: you come to your friend, but your cup is empty, and you ask: "Fill the cup—with whatever you want, coffee or tea!" After that, your friend has to fill it with something, or else the compiler will throw a fit.
out is made so a method can return several different values, skipping the return value. These parameters must be initialized inside the method.
void GetCoordinates(out int x, out int y)
{
x = 5;
y = 10; // Without this - error!
}
- Before the call: the variable doesn't have to be initialized, you can even just write out int x in the call.
- Inside the method: you must assign a value before exiting the method, or the compiler will go on strike.
- After the call: the variable contains the new value.
Example: Returning Two Values from a Method
void ParseNameAndAge(string input, out string name, out int age)
{
string[] parts = input.Split(','); // turn the string into an array—split by ','
name = parts[0];
age = int.Parse(parts[1]);
}
string userInput = "Ivan,25";
ParseNameAndAge(userInput, out string userName, out int userAge);
Console.WriteLine($"{userName} — {userAge} years old");
When to use out?
- When you need to return multiple values from a method (like splitting a string, as above).
- When the return type isn't known in advance (like trying to convert a string to a number).
Examples from .NET: The int.TryParse(string, out int) method—classic!
string input = "123";
if (int.TryParse(input, out int parsedNumber))
{
Console.WriteLine($"Converted: {parsedNumber}");
}
else
{
Console.WriteLine("Conversion error!");
}
The int.TryParse method returns true if it managed to convert the string to a number, and false if it didn't. The actual number value is returned via out.
Common Mistakes and Funny Situations with ref and out
- Forgot to initialize the variable for ref—the compiler gets mad.
- Didn't assign a value for out—the compiler gets even madder.
- Forgot to explicitly specify the modifier when calling: wrote DoSomething(x) instead of DoSomething(ref x).
- Using ref or out with constants or literals is not allowed! Only variables. Only they have a memory address you can reference.
4. Modifier in: Read Only, and Only by Reference
Sometimes you need to pass a big, complex object into a method. Usually, such an object is passed by reference, but then it can be accidentally changed by the function, since it has full access—we gave it the reference.
In theory, you could make a full copy of your object and pass that. Then no one can change the original. But if the object is huge, you're just wasting memory copying all that data. It's way better to pass the object into the function and ask the compiler to make sure nobody changes it.
in is a new modifier that lets you pass structs by reference, but read-only. It's like "putting a precious sword on display behind glass": you can look, but you can't touch.
void PrintPoint(in Point pt)
{
pt.X = 5; // Error! Read only.
Console.WriteLine($"Point: {pt.X}, {pt.Y}"); //This is fine
}
- Before the call: the variable must be initialized.
- Inside the method: you can only read, not change.
- Memory savings: the struct isn't copied, it's passed by reference.
Used, for example, for arrays of big structs, to avoid unnecessary copying. But with classes (reference types), in doesn't really do much.
Example: Passing a Big Struct by Reference to Avoid Copying
struct BigData
{
public int A, B, C, D, E;
}
void PrintBigData(in BigData data)
{
data.A = 10; // not allowed—read only!
Console.WriteLine(data.A + data.B + data.C + data.D + data.E);
}
BigData myData = new BigData { A = 10, B = 20, C = 30, D = 40, E = 50 };
PrintBigData(in myData);
5. Frequently Asked Questions:
Can I use ref/out/in with arrays and reference types?
Yep! But remember: arrays themselves are already reference types. Passing an array with ref lets you change the reference itself. Most often, this is needed for structs.
What's the difference between ref and out if I can get a value in both cases?
With ref, the variable must be initialized before you use it. With out, it doesn't have to be, but inside the method it MUST be assigned at least once.
What if I try to use a literal (like the number 5) with ref/out/in?
The compiler will throw an error right away. Only variables!
How many parameters can I make ref/out/in?
As many as you want, no limits! Just don't go overboard for code readability: if you have more than two or three, maybe think about returning a tuple, struct, or class.
GO TO FULL VERSION