Difference between in and ref readonly parameters
Asked Answered
S

3

22

What is the difference between in and ref readonly parameters?

I found both in and ref readonly parameters make the parameter readonly and it cannot be modified in the called method. Do they have a similar function?

Shani answered 18/11, 2023 at 22:53 Comment(2)
It provides better warnings when the parameter value is captured: github.com/dotnet/csharplang/blob/main/proposals/csharp-12.0/…Val
The section on the subject in the "What's new in C# 12" article gives a few examples why ref readonly might be preferable to in.Insensate
R
14

Imagine you require a reference to a value instead of a value as an argument to a method, but the struct you want to pass is a readonly struct. You have three options as of right now to pass by reference:

  1. The ref parameter: This is not an option for you, as you are passing a readonly struct which cannot be modified, but because you could technically modify a ref parameter, the compiler must generate a copy of the readonly struct to guarantee, that the readonly struct is not modified.

  2. The in parameter: This one is inapplicable in your case, because while it avoids copying the entire struct, and prohibits modificiation, there is no guarantee, that your parameter will actually be passed by reference - you can omit the in keyword when passing the parameter, to pass it by value. This means, that if you need to have a reference for an unsafe method for example, this would not work. The reason for the in keyword, is not for a parameter to be passed by readonly reference, but to avoid allocation. This often means passing a parameter by reference, but is not required.

  3. The readonly ref parameter solves the above posed issue: It is guaranteed by a compiler warning, that the value must be passed by reference and there will not be a protection copy, because you cannot modify the parameter.

To conclude: Only the readonly ref parameter will work, if you need to have a reference to a readonly struct. The in parameter does not guarantee that the parameter is passed by reference, and the ref parameter allows for modification, what means that the compiler has to copy the object to ensure, that it is not modified.

Rasbora answered 19/11, 2023 at 15:49 Comment(0)
S
6

The main difference is on the caller's side. Assume you have a fairly large struct that you absolutely don't want to copy around:

unsafe struct Foo
{
    // alternatively imagine a lot of fields being in here
    public fixed int Bar[32];
}

If you declare a method with an in parameter like this:

public unsafe int SumOverFoo(in Foo foo)
{
    foo.Bar[0] = 42; // not allowed
    int sum = 0;
    for (int i = 0; i < foo.Bar.Length; i++)
    {
        sum += foo.Bar[i];
    }
    return sum;
}

Nothing will prevent you from (accidentally) calling the method like this:

Foo foo = default;
int sum = SumOverFoo(foo); // yikes, you just copied 128 bytes around for no reason

Changing the foo parameter to be ref readonly will generate compiler warnings that you should pass foo by reference (using in or ref).

public unsafe int SumOverFoo(ref readonly Foo foo)
{
    foo.Bar[0] = 42; // still not allowed
    int sum = 0;
    for (int i = 0; i < foo.Bar.Length; i++)
    {
        sum += foo.Bar[i];
    }
    return sum;
}
Foo foo = default;
int sum = SumOverFoo(foo); // CS9192: Argument 1 should be passed With 'ref or 'in' keyword

ref readonly parameters were also introduced in a way to prevent breaking changes when migrating older APIs that were introduced before in parameters existed (and therefore used ref) to now accept readonly parameters. For example pre-.NET 8/C#12 Volatile.Read(...) required a ref parameter (although it does not need the write permission), meaning that it was impossible to read readonly fields using Volatile.Read(). They also couldn't just change it to in, because that would break existing code bases that use Volatile.Read(ref _myField). Furthermore, potentially performing non-volatile, non-atomic reads after accidentally forgetting to use the in keyword and creating accidental copies when using Volatile.Read() would have been a very nasty source of bugs.

So updating these older APIs with .NET 8 to support a wider range of use cases was one of the reasons to introduce ref readonly parameters (as they work with being passed in or ref).

See the C# language design specification on ref readonly parameters for more details.

Satiate answered 19/11, 2023 at 2:38 Comment(1)
"int sum = SumOverFoo(foo); // yikes, you just copied 128 bytes..." This only happens if you also have an overload without the "in" modifier, correct?Lavin
I
6

The two existing answers are misleading about the behavior of in. They suggest that specifying in at the call site of a method with an in parameter results in different behavior than not specifying in at the call site. However, given this API:

public unsafe struct LargeStruct
{
    public fixed int Bar[32];
}

public void M(in LargeStruct largeStruct)
{
}

The following two call sites will produce identical code:

LargeStruct largeStruct = default;
M(largeStruct);
LargeStruct largeStruct = default;
M(in largeStruct);

Compare the compiled code on SharpLab.

Though, there is some sense in which specifying in at the call site can make a difference, which is that specifying in prevents passing rvalues as arguments.

An rvalue is basically anything that can go on the right-hand side of an assignment but not the left-hand side, such as a constant or return value of a method.

For example, this will compile:

M(default);

but this will not:

M(in default);

The actual difference between in and ref readonly is only about the compile-time warnings that will be produced at call sites. There is no difference in runtime behavior or even in compile-time errors, just compile-time warnings.

Others have linked to the table in Microsoft documentation that precisely details all the differences in compile-time warnings, but the main point is that ref readonly will cause compile-time warnings for rvalue arguments, whereas in will allow rvalue arguments without warnings.

To see why this makes ref readonly useful, take a look at one example of it in a .NET runtime API:

Unsafe.IsNullRef<T>(ref readonly T source)

According to the docs, this API "determines if a given managed pointer to a value of type T is a null reference." Note that the API does NOT check whether the argument value is null, just whether the argument ref is null. A sensible use of the API might look like:

public void DoUnsafeStuff(ref string[] stringArray)
{
    if (Unsafe.IsNullRef(ref stringArray))
    {
        throw new ArgumentNullException(nameof(stringArray));
    }

    ...
}

Now, consider if Microsoft had defined the parameter of Unsafe.IsNullRef() as in instead of ref readonly. That would have allowed this to compile without warning:

public void DoUnsafeStuff(ref string[] stringArray)
{
    if (Unsafe.IsNullRef(stringArray.FirstOrDefault()))
    {
        throw new ArgumentNullException(nameof(stringArray));
    }

    ...
}

That code doesn't make conceptual sense, though, because there is no programmer-visible ref location associated with that stringArray.FirstOrDefault() return value, so the result will always be false. If someone wrote that code, they have clearly made a mistake, possibly incorrectly believing that Unsafe.IsNullRef() will check whether the value passed to it is null, as opposed to the ref passed to it.

So, ref readonly is beneficial because it enables triggering a compile-time warning in a scenario like this where the developer has clearly made a mistake.

Invent answered 24/2, 2024 at 0:28 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.