Contravariant [SerializeReference] array element is null on WebGL build

I want to create a custom Editor that can be used to specify a list of some kind of IInstruction. It’s just an interface that has a Run method and accepts a parameter generic.

To do this, I have an interface IInstruction that looks like this:

public interface IInstruction<in TParameter>
{
    void Run(TParameter parameter);
}

You can see that the interface is Contravariant since I want to use this interface as follows:

[Serializable]
public class TestInstructionT<TParameter> : IInstruction<TParameter>
{
    public void Run(TParameter parameter) { }
}

[Serializable]
public class TestInstruction : IInstruction<object>
{
    public void Run(object parameter) { }
}

I also have an InstructionRunner class that contains a list of instructions and just runs them in order. I have the ability to use a custom inspector to edit this array.

[Serializable]
public class InstructionRunner<TParameter>
{
    // Added from Editor:
    // [0] -> TestInstructionT
    // [1] -> TestInstruction
    [SerializeReference] private IInstruction<TParameter>[] _instructions;

    public void Run(TParameter parameter)
    {
        // In Runtime:
        // [0] -> TestInstructionT
        // [1] -> null
        foreach (IInstruction<TParameter> instruction in _instructions)
        {
            instruction.Run(parameter);
        }
    }
}

public class InstructionBehaviour : MonoBehaviour
{
    [SerializeField] private InstructionRunner<GameObject> _runner;

    private void Awake()
    {
        _runner.Run(GAME_OBJECT_PARAMETER);
    }
}

I can add both TestInstructionT (index 0) and TestInstruction (index 1) to it and it will work in the editor and in the Windows build. But when I run my game in WebGL, it seems that deserialization does not work as expected and instead of TestInstruction the array element with index 1 is null. That is, in WebGL when deserializing TestInstruction some error occurs and instead of the expected result, there is null.

The Web platform uses IL2CPP as a scripting backend instead of Mono.
Can you test what happens in a Windows build when you change the scripting backend to IL2CPP in the player settings?

My theory is that IL2CPP code stripping can’t automatically detect that the class TestInstruction is used in the code(because it is only used through reflection by the serialization code) and strips it. You can try to use the [Preserve] attribute or use a link.xml file to make sure that TestInstructionT and TestInstruction are not stripped.
There is also the [RequireImplementors] attribute. Adding that to the IInstruction<in TParameter> interface might be the best solution for your usecase.
You can read more here: Unity - Manual: Managed code stripping

1 Like