Understanding in, ref, and out Parameter Modifiers in C#

By default, arguments in C# are passed to functions by value. This means.

  • For value types (structs), a copy of the value is passed to the method.
  • For reference types (classes), a copy of the reference is passed to the method.

However, C# provides parameter modifiers (in, ref, and out) to enable passing arguments by reference, which can optimize performance, manage memory, and support advanced use cases like returning multiple values or modifying data in-place.

1. in Keyword

  • The in keyword is used to pass arguments by reference in a read-only manner.
  • It means the method reads but can't write the value of the argument. The argument will be passed by reference or through a temporary variable.
  • The argument must be initialized before calling the method.
  • The in modifier is required in the method declaration but unnecessary at the call site.
  • The compiler always creates a temporary variable when the argument must be converted when 1-there's an implicit conversion from the argument type, or 2-when the argument is a value that isn't a variable (e.g., the result of a property accessor, method call, or expression)

What does it mean?

A temporary variable will be created.

  • Constants
  • Properties
  • Computed Expressions
  • Temporary Values

Use-cases

  • Ensures immutability of the data by preventing modifications within the method.
  • Using in parameters potentially optimizes performance by avoiding expensive copies of large struct arguments.
  • Optimizing Loops (readonly) - Reduces struct copying overhead in iterations.
static void Main()
{
    MyStruct myStruct = new MyStruct { Value = 10 }; // Initialize MyStruct with Value = 10.

    Print(in myStruct); // ✅ Pass MyStruct by reference explicitly, no temporary created.
    Print(myStruct);    // ✅ Pass MyStruct by reference implicitly, no temporary.

    // Print(in myStruct.Value); // ❌ Compiler error: 'in' cannot be used with fields/properties.
    Print(myStruct.Value); // ⚠️ A Temporary created due to implicit int → double conversion.

    const int constantValue = 100; // Declare a constant integer.
    Print(constantValue);  // ⚠️ A Temporary created due to implicit int → double conversion.

    Print(myStruct.Value + 5); // ⚠️ A Temporary created as expression result is int → double.

    int num = 5;
    Print(num); // ⚠️ A Temporary created due to implicit int → double conversion.

    const double constantNum = 10;
    // Print(in constantValue); ❌ fails because constantValue is a constant.
    Print(constantNum); // ⚠️ A Temporary created for const due to 'in' parameter requirement.
}

// Using 'double' as an argument for simplicity; passing it as 'in' provides no benefit
// since 'double' is no larger than a reference.
static void Print(in double value)
{
    //
}

static void Print(in MyStruct data)
{
    //
}

Internal Codes

2. ref Keyword

  • The ref keyword allows passing a variable by reference, meaning the method can read and modify its value.
  • Unlike in, where the value is read-only, ref enables bidirectional data flow.
    • The method receives the actual reference.
    • The method modifies the original variable.
  • The variable must be initialized before passing it to a method.(a valid memory location to read from)
  • Both the method definition and the calling method must explicitly use the ref.
  • ref readonly means the method reads but can't write the value of the argument. The argument should be passed by reference.
  • With ref readonly, If an implicit conversion is needed, it won't compile instead of creating a copy (CS1503).
  • When a method parameter is defined as ref readonly, we can pass arguments using either in or ref at the call site. However, if you pass the argument without specifying either in or ref, the compiler will raise a warning because the method expects an explicit reference, ensuring clarity in how the argument is being passed. (CS9192)

Use Cases for the ref in Method Parameters.

  • Modifying Arguments Inside a Method (e.g., swapping values, updating state).
  • Passing Large Structs Efficiently (avoiding copies while allowing modifications).
  • Maintaining References to Mutable Objects (e.g., reference to a struct inside a collection).
  • Optimizing Performance in Tight Loops (modifying struct fields without copying them).
MyStruct myStruct = new MyStruct { Value = 10 };

Print(ref myStruct); // ✅ Correct usage

// (CS9192) ⚠️ Compiler will raise warning: The argument 1 should be passed with ref or in
Print(myStruct);

// (CS0206) ❌ A non ref-returning property or indexer may not be used as an out or ref value
PrintValue(ref myStruct.Value);

static void PrintValue(ref int value) // For test purposes
{
    //
}

static void Print(ref readonly MyStruct data)
{
    //
}

public static class ExtensionMethods
{
    // (CS8333) ❌ Would raise error if first 'ref readonly', 'in' parameter is not a Value type
    // (CS8337) First 'ref' parameter must be a value type or a generic type constrained to struct
    public static void Print(this ref Point point)
    {
        //
    }
}

Endpoint

3. The out Keyword

  • The out keyword allows passing a variable by reference but with a focus on returning values from the method.
  • Unlike ref, the variable does not need to be initialized before passing it to the method. The method must assign a value to the out parameter before it returns.
  • out enables unidirectional data flow.
    • The method cannot read the original value of the argument.
    • The method must write a value to the argument before returning.
  • Both the method definition and the calling method must explicitly use the out keyword
  • out is commonly used for methods that need to return multiple values or indicate success/failure alongside a result.
  • A ref or out parameter cannot have a default value (CS1741)
  • Cannot pass the range variable as an out or ref parameter (CS1939)
  • The out keyword can't be used on the first argument of an extension method.()
  • Properties cannot be passed as out parameters.(CS0206)

Common Use-Cases

  • Parsing Input (e.g., int.TryParse)
  • Returning Multiple Values
  • Initializing Variables
  • Avoiding Exceptions
  • Interop with Native Code
  • Factory Methods
if (int.TryParse(input, out _)) // ✅ Correct usage
{
    //
}
else
{
    //
}

public bool TryImagineMethod(int x, int y, out MyStruct mystruct) // ✅
{
    // Initialize the struct
}

public static class MyExtensions
{
    // ❌ (CS8328): 'out' cannot be used with this.
    public static void ResetValue(this out int value)
    {
        //
    }
}

// ❌ (CS1741) A ref or out parameter cannot have a default value.
static void Calculate(out int data = 10)
{
    //
}

var myclass = new MyClass();
myclass.Value = 1;

// ❌ (CS0206) A ref or out parameter must be an assignable variable.
Calculate(out myclass.Value);

// ❌ (CS1510) A ref or out parameter must be an assignable variable.
Calculate(out numbers.First());

var numbers = new[] { 1, 2, 3 };
var query = from n in numbers
            select Calculate(out n); // ❌ (CS1939) Cannot pass range variable as ref, out

Example

Global rules: We can't use in, ref, or out in method parameters (compiler error CS1988).

async Task TestAsync(ref int x) // ❌ Not allowed
{
    await Task.Delay(100);
    x++;
}

IEnumerable<int> GetNumbers(ref int x) // ❌ Not allowed
{
    yield return x;
}

// ❌ (CS1988) Error: Async methods cannot have in, ref, out parameters.
async Task TestAsync(in MyStruct mystruct)
{
    //
}

// ❌ (CS1623) Iterator methods, which include a yield return or yield break statement,
// cannot have in, ref, out parameters.
IEnumerable<int> GetNumbers(in MyStruct x)
{
    yield return x.Value;
}

static void Print(ref MyStruct data)
{
    //
}

// ❌ (CS0663) Cannot define overloaded methods that differ only on ref, out, in.
static void Print(in MyStruct data)
{
    //
}
  • In an async method's signature: Because async methods may pause execution (await), arguments must be safely stored, which isn't compatible with passing by reference
  • Iterator Methods (yield return or yield break): Similar to async, iterator methods execute lazily (on demand), making it unsafe to use references.

There are also some restrictions on Generics.

Thanks for reading!

Up Next
    Ebook Download
    View all
    Learn
    View all