Back to Blog
C#

C# ref vs out vs in: Parameter Modifiers Explained

c# ref vs out vs in: Understand how ref, out, and in parameter modifiers differ in C#, when each is is required,, and how they affect runtime behavior and API design.

C#parameter modifiersref out inmethod parametersstruct performance
Illustration showing three C# parameter modifier keywords ref, out, and in with arrows indicating data flow direction between caller and method.

Choosing between ref, out, and in for a method parameter is a common decision in C#. Each modifier changes the contract between the caller and the method in a different way: ref allows read and write access to the caller's variable, out requires the method to assign a value before returning, and in passes a read-only reference that avoids copying the argument. The practical question of c# ref vs out vs in comes down to knowing which contract fits the operation you are implementing.

How ref Parameters Work

A ref parameter passes a reference to the caller's variable rather than a copy. The method can read and write through that reference, and any assignment inside the method is visible to the caller.

public static void Swap(ref int left, ref int right) { int temp = left; left = right; right = temp; } int a = 3; int b = 5; Swap(ref a, ref b); // a is now 5, b is now 3

The caller must initialize the variable before passing it with ref. The compiler enforces this at the call site, so you cannot pass an unassigned local variable. This makes sense because the method may read the value immediately, and reading an uninitialized variable would be undefined behavior.

ref is the right choice when the method needs to modify the caller's value and the caller has a meaningful initial value to provide. Swapping, incrementing, and in-place mutation are typical cases.

How out Parameters Work

An out parameter also passes a reference to the caller's variable, but the contract is different: the method must assign the parameter before returning. The caller is not required to initialize the variable first.

public static bool TryParseCoordinate(string input, out double x, out double y) { string[] parts = input.Split(','); if (parts.Length != 2) { x = 0; y = 0; return false; } x = double.Parse(parts[0]); y = double.Parse(parts[1]); return true; } if (TryParseCoordinate("12.5, -3.25", out double x, out double y)) { // x and y are usable here }

The compiler requires that every out parameter is assigned on every code path before the method returns. This is what makes out safe for the caller: the variable is guaranteed to hold a value after the call, even if the method reports failure.

The TryParse pattern is the most common use of out. It combines a boolean success indicator with the parsed result, avoiding exceptions for expected invalid input. The out var declaration syntax, available since C# 7, lets you declare the variable inline at the call site, which keeps the code compact.

How in Parameters Work

The in modifier, introduced in C# 7.2, passes a read-only reference. The method receives the argument without copying it, but cannot assign to it. This is useful for large value types where copying would be expensive.

public readonly struct Vector3 { public readonly double X; public readonly double Y; public readonly double Z; public Vector3(double x, double y, double z) { X = x; Y = y; Z = z; } } public static double Dot(in Vector3 left, in Vector3 right) { return left.X * right.X + left.Y * right.Y + left.Z * right.Z; }

The caller can pass a Vector3 directly without the in keyword at the call site. The compiler infers the in modifier from the method signature. This is different from ref and out, which require the modifier to appear at both the declaration and the call site.

Because the method cannot modify the parameter, the caller does not need to worry about unexpected mutation. The in modifier is primarily a performance feature for large structs. For small value types like int or double, copying is cheaper than the indirection involved in passing a reference, so in provides no benefit there.

Key Differences at a Glance

Behaviorrefoutin
Caller must initialize before callYesNoYes
Method must assign before returnNoYesNo
Method can modify the valueYesYesNo
Keyword required at call siteYesYesNo
Primary use caseIn-place mutationMultiple return valuesAvoid copying large structs

The table highlights the three contracts. ref is bidirectional: the caller provides a value and the method may replace it. out is unidirectional from the method to the caller, with a mandatory assignment guarantee. in is unidirectional from the caller to the method, with a read-only guarantee.

Runtime and Performance Considerations

The in modifier exists because passing a large struct by value copies the entire struct onto the stack. For a struct containing several fields, that copy can be significant in hot paths. Passing a reference to the original struct avoids the copy.

However, the compiler may introduce a defensive copy when the method calls a member that could mutate the struct. If the struct is not declared readonly, the compiler cannot prove that a method call through the in parameter will not modify the struct. To preserve the read-only contract, it copies the struct into a temporary local and calls the member on the copy.

public struct MutablePoint { public int X; public int Y; public void Shift(int dx, int dy) { X += dx; Y += dy; } } public static void Process(in MutablePoint point) { // The compiler may copy point before calling Shift // because Shift can mutate the struct. point.Shift(1, 1); }

Declaring the struct as readonly eliminates the defensive copy, because the compiler knows no member can mutate it. If you use in parameters with mutable structs, be aware that the defensive copy adds allocation and copying cost, which can negate the performance benefit.

For ref and out, the runtime cost is the same as passing a reference: no data is copied, but the caller and method share the same storage location. The main cost is the requirement to use the keyword at the call site, which makes the intent explicit but adds syntactic noise.

Common Mistakes and Edge Cases

The most frequent mistake is confusing ref and out. A method declared with out must assign the parameter, while a method declared with ref may read the value before assigning. Swapping the two causes compile errors: a ref call site requires an initialized variable, and an out method body fails to compile if a code path returns without assignment.

Another edge case is overload resolution. The in modifier does not participate in overload resolution the same way ref and out do. A method with an in parameter can be called without the keyword, which can make overloads ambiguous or cause the compiler to choose a different overload than intended.

public static void Print(int value) { } public static void Print(in int value) { } int number = 42; Print(number); // Which overload is chosen?

The compiler prefers the by-value overload when both are applicable, because the by-value overload does not require a reference. If you rely on in overloads for performance, be aware that the call site may silently bind to the by-value version.

Properties cannot be passed as ref, out, or in arguments because properties are methods, not storage locations. The same restriction applies to indexed expressions. If you need to pass a property by reference, you must copy it to a local variable first.

Choosing the Right Modifier

Use ref when the method must modify the caller's variable and the caller has an initial value. Use out when the method produces one or more values and the caller does not need to provide input. Use in when the method only reads a large struct and you want to avoid copying it.

For most application code, ref and out are rare. The TryParse pattern is the main legitimate use of out. ref appears in low-level algorithms and interop scenarios. in is a performance optimization that matters only for large structs in hot paths, and it pairs with readonly struct declarations to avoid defensive copies.

If you are writing a public API, prefer returning a tuple or a custom result type over out parameters. Tuples make the return value explicit and avoid the awkward call-site syntax. The main reason to keep out is to match the established TryParse convention or to interoperate with code that already uses it.

The final consideration is maintainability. ref and out make the call site harder to read because the keyword signals that the method has side effects on the argument. in is invisible at the call site, which keeps the code clean but hides the fact that a reference is being passed. When a method takes an in parameter, the caller cannot tell from the call site that the argument is not copied, so the performance characteristic is not obvious.

c# ref vs out vs in: Practical Usage and Code Examples | RYUSLOG DEV