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?
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?
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:
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.
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.
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.
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.
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.
© 2022 - 2025 — McMap. All rights reserved.
ref readonly
might be preferable toin
. – Insensate