Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the point of the in modifier for classes

Tags:

c#

c#-7.2

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?

like image 803
user4388177 Avatar asked Mar 07 '18 10:03

user4388177


4 Answers

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.

like image 108
Evk Avatar answered Nov 19 '22 19:11

Evk


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.
like image 20
David Arno Avatar answered Nov 19 '22 21:11

David Arno


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.

like image 2
Zohar Peled Avatar answered Nov 19 '22 21:11

Zohar Peled


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...
    }
}
like image 1
Tim Avatar answered Nov 19 '22 19:11

Tim