# Ref-Readonly and In

The current version of C# (C# 13 at the time of writing) has `in` and `ref` keywords. In this post I investigate using these keywords as *parameter modifiers*. Specifically, the difference between `ref readonly` and the `in` parameter modifier.

Both parameter modifiers are well-suited for methods that have large `struct` parameters. Regular `struct` parameters follow copy-by-value semantics. Larger struct copies incur a performance penalty as the larger the struct is the more data has to be copied. I investigated the cost of copies in a post about [inline arrays](https://blog.ladeak.net/posts/exploring-inline-arrays). One approach to address this problem is by passing a reference to the `struct` parameters.

## In modifier

An author of a method could use the `in` parameter modifier for value type parameters. In the following example as `RegularStruct` is passed to a method named `PassByIn`, with the `in` modifier:

```csharp
var regularValue = new RegularStruct(1);
ref readonly var result = ref PassByIn(regularValue);

[MethodImpl(MethodImplOptions.NoInlining)]
ref readonly int PassByIn(in RegularStruct value) =>
    ref value._field;

struct RegularStruct(int value)
{
    public int _field = value;
    
    [MethodImpl(MethodImplOptions.NoInlining)]
    public void Increment()
    {
        _field++;
    }
}
```

The callsite does not need to declare this modifier, and the compiler will pass *regularValue* as a reference avoiding the copy. If the struct was modified by the `PassByIn` method, the compiler would execute it on a copy of the original value. In the following example the value printed on the console shows *1*:

```csharp
var regularValue = new RegularStruct(1);
var result = PassByIn(regularValue);
Console.WriteLine(result);

[MethodImpl(MethodImplOptions.NoInlining)]
ref readonly int PassByIn(in RegularStruct value)
{
    value.Increment();
    return ref value._field;
}
```

The generated assembly code shows how the defensive copy is made prior to invoking the *Increment* method. 

```asm
Program.<<Main>$>g__PassByIn|0_0(RegularStruct ByRef)
    L0000: push rbx
    L0001: sub rsp, 0x30
    L0005: xor eax, eax
    L0007: mov [rsp+0x28], rax
    L000c: mov rbx, rcx
    L000f: mov ecx, [rbx]
    L0011: mov [rsp+0x28], ecx
    L0015: lea rcx, [rsp+0x28]
    L001a: call 0x00007fff59e90048
    L001f: mov rax, rbx
    L0022: add rsp, 0x30
    L0026: pop rbx
    L0027: ret

RegularStruct.Increment()
    L0000: inc dword ptr [rcx]
    L0002: ret
```

> Note that, omitting `[MethodImpl(MethodImplOptions.NoInlining)]` attribute would generate more efficient code, as the JIT can eliminate the copy and the call to the *Incremenet* method as well.

## Ref-Readonly modifier

Replacing the `in` parameter modifier with `ref readonly` generates the same assembly code with the same behavior. The C# compiler issues a warning for the callsite of the *PassByRefRO* method indicating that `ref` modifier should be used for the `ref` parameter, as shown below.

```csharp
var regularValue = new RegularStruct(1);

// When the 👇 ref keyword is omitted a compiler warning is generated:
// warning CS9192: Argument 1 should be passed with 'ref' or 'in' keyword 
ref readonly var result = ref PassByRefRO(ref readonlyValue);
Console.WriteLine(result);

[MethodImpl(MethodImplOptions.NoInlining)]
ref readonly int PassByRefRO(ref readonly RegularStruct value)
{
    value.Increment();
    return ref value._field;
}
```

### Readonly structs

The `ref` and `in` parameters avoid the copy at the method invocation, but the *Increment* method call results in a defensive copy. This negates the previous performance gains. To better address this problem, a developer could declare the `struct` type as *readonly*. This way the compiler validates that the type is immutable.

```csharp
readonly struct ReadOnlyStruct(int value)
{
    public readonly int _field = value; // 👈 must be readonly
    //public void Increment()
    //{
        //_field++; 👈 not allowed
    //}
}
```

Adding the previous *Increment* method as a member to this type results in a compilation error: *error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer)*

This way unintentional copies can be prevented at compile time:

```csharp
var readonlyValue = new ReadOnlyStruct(1);
ref readonly var result = ref PassByRefRO(ref readonlyValue);
Console.WriteLine(result);

[MethodImpl(MethodImplOptions.NoInlining)]
ref readonly int PassByRefRO(ref readonly ReadOnlyStruct value) =>
    ref value._field;

readonly struct ReadOnlyStruct(int value)
{
    public readonly int _field = value;
}
```

## Warnings and Errors at callsite

The runtime behavior of `in` and `ref-readonly` parameters are very close. Yet at compile the C# language differentiates them:

- *ref readonly* parameters issue a warning to use `ref` or `in` modifiers at the callsite
- *ref readonly* parameters with `ref` modifier issues a compiler error when passing *rvalues*

For example, the following expression  `PassByRefRO(ref new ReadOnlyStruct(1));` generates a compiler error: *error CS1510: A ref or out value must be an assignable variable*

## Conclusion

After examining `ref readonly` and `in` parameter modifiers, we can conclude:

- Both modifiers help avoid unnecessary copying of large value types by passing them by reference
- The runtime behavior is nearly identical for both approaches
- Key differences appear at compile time:
  - `in` parameters don't require modifiers at the callsite
  - `ref readonly` parameters enforces *ref* indication at callsite by generating warnings if `ref` or `in` modifiers are missing
  - `ref readonly` with `ref` modifier at the callsite prevents using rvalues (temporary values) as arguments
- Using `readonly struct` provides additional compile-time safety by preventing unintended modifications and avoiding defensive copies

For a complete reference of modifier combinations and their behavior, consult the [official documentation](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-12.0/ref-readonly-parameters).
