13

C# 7.2 introduces the in modifier for parameters which makes perfect sense for structs and in particular for readonly structs.

It is also allowed to use it for a reference type

void Method(in StringBuilder value) { }

As reference types are passed by reference by default, is the in in the example above just a redundant modifier?

value = null is forbidden when you use in, does it mean that it spares also the copy of the reference address by just passing the original reference to the heap location and blocking changes?

Ehsan Sajjad
  • 61,834
  • 16
  • 105
  • 160
user4388177
  • 2,433
  • 3
  • 16
  • 30
  • 4
    Calling @EricLippert... – Jonathon Reinhart Mar 07 '18 at 10:32
  • 3
    Does https://learn.microsoft.com/en-us/dotnet/csharp/reference-semantics-with-value-types help? `The in parameter designation can also be used with reference types or built in numeric values. However, the benefits in both cases are minimal, if any.` I suspect the primary benefit is allowing you to write code that uses both `generics` and `in` (and thus allowing `in` to work with reference types means you don't need to constrain the generic code to a struct). – mjwills Mar 07 '18 at 10:34
  • 2
    @mjwills that's a good guess, I was also wondering if it actually avoids copying the pointer (not for any performance gain, but just out of curiosity) – user4388177 Mar 07 '18 at 10:40
  • 2
    It is only an optimization for arguments of a value type. It makes the promise that the C# compiler does not have to generate extra code to ensure that the value is copied. Large structs can produce faster code but the copying defeats the gain. It is not generally useful for reference types. Forbidding it is not useful either, gives programmers a headache when they decide to change the type declaration. – Hans Passant Mar 07 '18 at 10:44

4 Answers4

11

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.

Evk
  • 98,527
  • 8
  • 141
  • 191
  • Thanks, I knew it was just a `ref` with an attribute, but didn't think about it when I wrote the question. It makes sense for it to have the same behaviour. – user4388177 Mar 07 '18 at 10:42
  • Passing in a reference type argument with `in` passes it as a reference to a reference, just like passing it via `ref` would. It changes how the value is retrieved and can have different behavior in some cases. – Mike Marynowski Sep 07 '18 at 13:44
4

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.
David Arno
  • 42,717
  • 16
  • 86
  • 131
  • In the first example I understand creating a copy of the struct to protect the struct for any change, but in the `S1Foo` method why does it create a second copy and call the `ChangeA` method on this copy, `s` is already a reference of the copy sent from the `Main` method. What would happen if calling the `Change` method on the incoming reference `s`? – Orhun Dec 05 '20 at 14:33
2

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.

Zohar Peled
  • 79,642
  • 10
  • 69
  • 121
1

The only useful thing I can think of for in with reference types would be generic functions with constraints.

public interface IIntContainer
{
    int Value { get; }
}

public readonly struct LargeStruct : IIntContainer
{
    public readonly int val0;
    public readonly int val1;
    // ... lots of other fields
    public readonly int val20;

    public int Value => val0;
}

public class SmallClass : IIntContainer
{
    public int val0;
    public int Value => val0;
}

public static class Program
{
    static void Main()
    {
        DoSomethingWithValue(new LargeStruct());
        DoSomethingWithValue(new SmallClass());
    }

    public static void DoSomethingWithValue<T>(in T container) where T : IIntContainer
    {
        int value = container.Value;
        // Do something with value...
    }
}
Tim
  • 91
  • 9