What is the point of the in modifier for classes

in is compiled to IL in exactly the same way as ref, except in argument is marked with IsReadOnly attribute.

That means in behaves exactly as ref, but compiler (not runtime) enforces that you don't assign value to in argument.

So, as you correctly pointed out - in referenece-type argument is passed by reference (which means reference is not copied and points to original location), but compiler prevents you from changing it. I don't really see much use for it for reference types, but it won't hurt to have that, at least for consistency.


From what I understand from official documentation, it means that arguments passed to the method will not be changed inside the method itself:

The in keyword specifies that you are passing the parameter by reference and the called method does not modify the value passed to it.

when using the in keyword with value types, it means that instead of passing the argument by value (meaning creating a new copy of the value), it is passed by reference - so it avoids the unnecessary copying.


Whilst the other two answers are correct that in parameters end up as ref parameters in the resultant IL, care should be taken with the claim that this prevents the value being copied. This only holds true for readonly structs.

To demonstrate this, consider the following piece of code:

using System;

public struct S1
{
    public int A;

    public void ChangeA(int a) => A = a;
}

public static class Program
{
    static void Main()
    {
        var s1 = new S1 { A = 1 };
        S1Foo(in s1);
        Console.WriteLine(s1.A);
    }

    private static void S1Foo(in S1 s) => s.ChangeA(2);
}

Since we are passing s1 by reference, one might reasonably assume that S1Foo, in calling ChangeA would then change the contents of s1. This doesn't happen though. The reason being that the s1 value is copied and a copy is passed by reference, to prevent such modifications of structs via in parameters.

If we decompile the resultant IL, you see that the code ends up as:

public static class Program
{
    private static void Main()
    {
        S1 s = default(S1);
        s.A = 1;
        S1 s2 = s;
        Program.S1Foo(ref s2);
        Console.WriteLine(s2.A);
    }

    private static void S1Foo([IsReadOnly] [In] ref S1 s)
    {
        S1 s2 = s;
        s2.ChangeA(2);
    }
}

However, if we write similar code using a readonly struct, then no copying occurs. I say similar as it isn't possible to write the same code as fields and property have to be readonly in a readonly struct (the clue is in the name):

using System;

public readonly struct S2
{
    private readonly int _a;
    public int A => _a;
    public S2(int a) => _a = a;

    public void ChangeA(int a) { }
}

public static class Program
{
    static void Main()
    {
        var s2 = new S2(1);
        S2Foo(in s2);
        Console.WriteLine(s2.A);
    }

    private static void S2Foo(in S2 s) => s.ChangeA(2);
}

Then no copy occurs in the resultant IL.

So in summary:

  1. in is effectively a readonly ref,
  2. The value (or reference) is passed by reference,
  3. The compiler prevents modifying fields and properties of that reference to help enforce its readonly-ness,
  4. To further enforce the readonly nature of the parameter, then non-readonly structs are copied before a reference to the copy is passed to the method. This doesn't occur for readonly structs.

Tags:

C#

C# 7.2