Search code examples
c#.netoptimizationc#-7.2in-parameters

Using C# 7.2 in modifier for parameters with primitive types


C# 7.2 introduced the in modifier for passing arguments by reference with the guarantee that the recipient will not modify the parameter.

This article says:

You should never use a non-readonly struct as the in parameters because it may negatively affect performance and could lead to an obscure behavior if the struct is mutable

What does this mean for built-in primitives such as int, double?

I would like to use in to express intent in code, but not at the cost of performance losses to defensive copies.

Questions

  • Is it safe to pass primitive types via in arguments and not have defensive copies made?
  • Are other commonly used framework structs such as DateTime, TimeSpan, Guid, ... considered readonly by the JIT?
    • If this varies by platform, how can we find out which types are safe in a given situation?

Solution

  • A quick test shows that, currently, yes, a defensive copy is created for built-in primitive types and structs.

    Compiling the following code with VS 2017 (.NET 4.5.2, C# 7.2, release build):

    using System;
    
    class MyClass
    {
        public readonly struct Immutable { public readonly int I; public void SomeMethod() { } }
        public struct Mutable { public int I; public void SomeMethod() { } }
    
        public void Test(Immutable immutable, Mutable mutable, int i, DateTime dateTime)
        {
            InImmutable(immutable);
            InMutable(mutable);
            InInt32(i);
            InDateTime(dateTime);
        }
    
        void InImmutable(in Immutable x) { x.SomeMethod(); }
        void InMutable(in Mutable x) { x.SomeMethod(); }
        void InInt32(in int x) { x.ToString(); }
        void InDateTime(in DateTime x) { x.ToString(); }
    
        public static void Main(string[] args) { }
    }
    

    yields the following result when decompiled with ILSpy:

    ...
    private void InImmutable([System.Runtime.CompilerServices.IsReadOnly] [In] ref MyClass.Immutable x)
    {
        x.SomeMethod();
    }
    
    private void InMutable([System.Runtime.CompilerServices.IsReadOnly] [In] ref MyClass.Mutable x)
    {
        MyClass.Mutable mutable = x;
        mutable.SomeMethod();
    }
    
    private void InInt32([System.Runtime.CompilerServices.IsReadOnly] [In] ref int x)
    {
        int num = x;
        num.ToString();
    }
    
    private void InDateTime([System.Runtime.CompilerServices.IsReadOnly] [In] ref DateTime x)
    {
        DateTime dateTime = x;
        dateTime.ToString();
    }
    ...
    

    (or, if you prefer IL:)

    IL_0000: ldarg.1
    IL_0001: ldobj [mscorlib]System.DateTime
    IL_0006: stloc.0
    IL_0007: ldloca.s 0
    IL_0009: call instance string [mscorlib]System.DateTime::ToString()
    IL_000e: pop
    IL_000f: ret