Back to Blog
C#

c# ref Keyword: Passing Arguments by Reference

Learn how the c# ref keyword passes arguments by reference, how it differs from out and in, and when to use it for mutation or performance.

C#ref keywordmethod parametersvalue typesref returns
Diagram showing two arrows pointing to the same memory location, representing the C# ref keyword passing a variable by reference

The c# ref keyword lets you pass arguments to methods by reference rather than by value. When you call a method in C#, value-type arguments are normally copied into the method's parameters. The method works on that copy, so any assignment inside the method does not affect the caller's variable. The ref keyword changes that contract: the parameter becomes an alias for the caller's variable, and reads and writes inside the method go directly to the original storage location.

public static void Increment(ref int value) { value++; } int number = 5; Increment(ref number); Console.WriteLine(number); // 6

The caller must write ref at the call site, and the variable must be definitely assigned before the call. The compiler enforces both rules, so you cannot accidentally pass a reference when you meant to pass a copy.

For reference types, ref has a different effect. A reference-type variable already holds a reference to an object on the heap. Passing it by value copies that reference, so both the caller and the method point to the same object. Passing it with ref aliases the variable itself, which means the method can reassign the variable to point to a different object, and the caller sees the new reference.

Declaring and Calling ref Parameters

A ref parameter is declared by placing ref before the parameter type in the method signature. The call site must also use ref before the argument.

public static void Swap(ref int left, ref int right) { int temp = left; left = right; right = temp; } int a = 10; int b = 20; Swap(ref a, ref b); Console.WriteLine($"{a} {b}"); // 20 10

The argument must be a variable, not a literal or a property. Properties are methods under the hood, and the runtime cannot create a stable alias to a property's backing storage without knowing its implementation. The same restriction applies to indexers and to the result of method calls.

Overload resolution treats ref as part of the method signature. You can overload on ref versus by-value parameters, but the call site's ref keyword determines which overload is selected.

ref vs out vs in

The out and in keywords also create aliases to the caller's variable, but each one changes the initialization and assignment rules.

KeywordCaller must initializeMethod may readMethod must assignTypical use
refYesYesNoMutate caller state
outNoNo (before assign)YesReturn extra values
inYesYes (read-only)NoAvoid copies without mutation

With out, the method must assign the parameter before returning, and the caller does not need to initialize the variable first. This makes out the right choice when the parameter is effectively a second return value. With in, the compiler treats the parameter as read-only, so the method cannot modify the caller's variable, but the argument is still passed by reference to avoid copying a large struct.

The in keyword also permits the compiler to create a temporary copy when the argument is not a variable, which means in is slightly more permissive than ref about what can be passed.

ref Returns and ref Locals

The ref keyword is not limited to parameters. A method can return a reference to a storage location, and the caller can store that reference in a ref local.

public static ref int FindLargest(int[] numbers) { int largestIndex = 0; for (int i = 1; i < numbers.Length; i++) { if (numbers[i] > numbers[largestIndex]) { largestIndex = i; } } return ref numbers[largestIndex]; } int[] scores = { 42, 87, 65, 91, 73 }; ref int highest = ref FindLargest(scores); highest = 100; Console.WriteLine(scores[3]); // 100

The ref local highest aliases scores[3]. Assigning through it modifies the array element directly. This pattern is useful when you need to locate an element in a collection and then update it without repeating the lookup logic.

The compiler enforces rules that keep ref returns safe. You cannot return a reference to a local variable, because that storage would be gone when the method returns. You also cannot return a reference to a field of a struct that is itself a temporary, because the temporary's lifetime does not extend beyond the expression.

Performance and Copy Avoidance

Passing a large struct by value copies the entire struct onto the evaluation stack. For a struct with many fields, that copy is real work that happens on every call. Passing the same struct with ref passes a managed pointer instead, which is the size of a pointer regardless of the struct's layout.

public readonly struct Matrix4x4 { public readonly float M11, M12, M13, M14; public readonly float M21, M22, M23, M24; // additional fields omitted for brevity } public static float Trace(ref Matrix4x4 m) { return m.M11 + m.M22 + m.M33 + m.M44; }

The ref parameter here avoids copying 64 bytes of floats on every call. In a tight loop that processes many matrices, that difference can be measurable, though the exact impact depends on the JIT compiler, the call frequency, and the struct size.

The in keyword gives the same copy-avoidance benefit while preventing the method from mutating the caller's data. If the method only reads the struct, in is usually the better choice because it communicates intent and allows the compiler to pass a temporary when needed.

Common Pitfalls and Limitations

Forgetting ref at the call site is a compile-time error. The signature and the call must agree, so there is no silent fallback to by-value passing.

ref cannot be used with async methods. An async method can return before its body finishes executing, and the runtime cannot guarantee that the caller's variable is still valid when the method resumes. The same restriction applies to iterator methods that use yield return.

ref parameters cannot be used with properties, indexers, or the result of a method call, because those expressions do not represent a stable storage location. The compiler rejects them at compile time.

Using ref with a reference type is only necessary when the method must reassign the caller's variable. If the method only mutates the object's members, passing the reference by value is sufficient and clearer.

Choosing Between ref and Alternatives

Use ref when you need to mutate a value-type variable in the caller's scope and the mutation must be visible after the call. Use out when the method's job is to produce a value that the caller did not have before. Use in when you want reference semantics for a large struct but the method must not modify it.

Avoid ref for small value types like int or bool when the only goal is mutation. The copy cost is negligible, and a method that returns the new value is often easier to reason about than one that mutates its input.

For reference types, prefer by-value passing unless the method needs to replace the reference itself. Most methods that operate on an object's state do not need ref, and adding it makes the call site harder to read for no benefit.

c# ref keyword: Passing Arguments by Reference | RYUSLOG DEV