When you write c#, how you pass parameters matters, both for performance and for keeping your code readable
ref in and out are three ways to pass things by reference instead of making copies
Value vs Reference
By default:
- Value types (int, struct etc.) get copied
- Reference types (class objects) don't get copied, but the reference is passed by value
So this:
void Increment(int number)
{
number++;
}
won't change your original value. No surprises here
ref "You can change it"
ref lets a method read and modify the original variable. The caller also has to use the keyword, so it's very
obvious something might change
void Increment(ref int n)
{
n++;
}
int x = 5;
Increment(ref x);
Console.WriteLine(x); // 6
You want to use ref when:
- Swapping values
- Working with HUGE structs where copying would be slow
- Anything performance critical where mutating the original is intentional
but it's an overkill if you use it for types like int or bool, or when you don't really need mutation
in "You can look but don't touch"
very simple: Pass by reference, but read-only
Great when you have big structs and don't want to copy them around
int Add(in a, in b)
{
return a + b;
}
You can't modify a or b inside the method, the compiler will smack your hand
It's best for large readonly structs, APIs where you want to guarantee immutability and performance
Here's a Gotcha you should remember:
If the struct isn't readonly, the compiler sometimes makes defensive copies under the hood. So if you want true performance
use readonly struct
out "Here, fill this for me"
this parameter is for output only
The method must assign it to before returning
if (int.TryParse("123", out int value))
{
Console.WriteLine(value); // 123
}
You want to use out when:
- Follow the Try-X style
- You need a couple of extra return values
But if you're returning 15 values.. chill. Use a tuple or a small type instead :D
Decision Making
- Use ref when you want to modify the original and make that super obvious
- Use in when you want read-only, high-performance access to big structs
- Use out when you're doing "Try-X"
- BUT: Use nothing for small parameters or when reference semantics don’t matter. Don’t get clever unnecessarily
Performance Rules of Thumb
I like to think of it as follows:
- < 16 bytes: just pass by value
e.g.
struct Small
{
public float X; // 4 bytes
public float Y; // 4 bytes
public float Z; // 4 bytes
}
- 16–64 bytes: consider in/ref
e.g.
struct Medium
{
public float X, Y, Z; // 12 bytes
public float RotX, RotY, RotZ; // 12 bytes
public float Scale; // 4 bytes
// ~(28-32 bytes depending on padding)
}
-
64 bytes: definitely in or ref
e.g.
struct BigMatrix4x4
{
public float M11, M12, M13, M14;
public float M21, M22, M23, M24;
public float M31, M32, M33, M34;
public float M41, M42, M43, M44;
// 16 floats -> 64 bytes
}
Because copying big structs repeatedly is basically asking the CPU to cry
Just remember to use those tools in the right situations. Otherwise, pass by value and don’t stress. Most of the time, the simplest option is the right one ;)