Home > Software engineering >  Error on serialization with an "in" modifier in constructor
Error on serialization with an "in" modifier in constructor

Time:10-10

I can't figure out why a serialization of such a sample class should fail:

using System.Text.Json;

class MyClass
{
    public MyClass(in double t)
    {
        this.Temperature = t;
    }

    public double Temperature { get; set; }
}

MyClass c = new(123);
string json = JsonSerializer.Serialize(c);    //throws here
Console.WriteLine(json);

The exception says:

System.ArgumentException: The type 'System.Double&' may not be used as a type argument.

The problem seems related to the in-modifier, because by removing it the serializazion succeeds.

Any clue on why that's not possible?

CodePudding user response:

Presumably the serializer is looking for constructors that match the expected features, and either

  1. fails to find a matching MyClass(double) constructor that it is looking for and gives up, or
  2. finds the MyClass(ref double) constructor (which is what in ultimately is, just with some attributes), fails to check whether it is a by-ref, and emits the wrong IL

If you get a critical JIT failure, it is probably "2", otherwise "1".

But ultimately: this in almost certainly isn't really helping you any, unless you're on x86 and even then it is questionable whether it is actually helping anyway (I'd need receipts to be convinced). So maybe just don't use in here? All you're doing is deferring a dereference, which might actually increase the work done in many cases, if it means that instead of just using an ambient value already in the correct location on the stack, it needs to do a stloc ldloca to get the address of a stack-local. This includes when using values from properties (if we assume that you don't have ref-return-type getters, which seems unlikely) - since new MyClass(obj.SomeProp) can't use the in directly, unless you get lucky and the JIT undoes all the indirection.

We can see this in sharplab.io; with the in, the property-fetch thru constructor is:

IL_0014: callvirt instance float64 Foo::get_SomeProp()
IL_0019: stloc.0
IL_001a: ldloca.s 0
IL_001c: newobj instance void MyClass::.ctor(float64&)

and without the in, it is:

IL_0014: callvirt instance float64 Foo::get_SomeProp()
IL_0019: newobj instance void MyClass::.ctor(float64)

(again, the JIT may or may not "fix" this, depending on context)

  • Related