How to initialize a list/array in a pure C# class

Hi everyone,

I have a question about the initialization of a list in a pure C# class. Here is my class:

[System.Serializable]
public class BaseValue
{
    [SerializeField] float _baseValue;

    public float Value => baseValue + CalculateModifiers();
    public float baseValue => _baseValue;

    List<Modifier> modifiers = new List<Modifier>();

    public BaseValue(float baseValue)
    {
        _baseValue = baseValue;
    }

    public void AddModifier(Modifier modifier)
    {
        if (modifier == null) return;

        modifiers.Add(modifier);
    }

    public void RemoveModifier(Modifier modifier)
    {
        modifiers.Remove(modifier);
    }

    float CalculateModifiers()
    {
        float value = 0;
        foreach (var modifier in modifiers) // here is where the null ref occurs, on the modifiers list
            value += modifier.value;
        return value;
    }
}

public class Modifier
{
    public float value;

    public Modifier(float value)
    {
        this.value = value;
    }
}

Here is a simple class using it:

public class Engine
{
    [SerializeField] BaseValue maxForce;

    Modifier modifier;

    void Awake()
    {
        maxForce.AddModifier(modifier);
    }

    void Update()
    {
        bool isPressed = Input.GetKey(KeyCode.Escape);

        modifier.value = isPressed ? 20 : 0;
    }
}

But I get errors because the list of modifiers in the BaseValue class is null and not instantiated when the game is launched, I don’t understand why.

If you have any ideas, I would be happy to read them.

So where is the null-ref occuring exactly?

In your example code, in the last code segment, you will get a null-ref because you never instance anything into modifier.

1 Like

Hi and thanks for your reply.

The null reference occurs on the following line:

foreach (var modifier in modifiers)

By saying that “modifiers” is null. I thought that declaring the following inside the class would initialize the modifiers list:

List<Modifier> modifiers = new List<Modifier>();

As it does for a simple float/int/string/… variable.

Are you sure that’s where the null-ref occurs? And not because you pass in a null modifier?

In your example code nothing would actually cause that code to execute.

try moving the

List<Modifier> modifiers = new List<Modifier>();

before

public float Value => baseValue + CalculateModifiers();

because it looks like the code tries to access the “modifiers” before is created (in that CalculateModifiers() function)

but maybe try to rethink the logic of the code to not jump around from functions to variables like that

That… is not how C# works.

The order of members has no bearing on execution.

3 Likes

There is no null check in the loop because you can also add null as a modifier to the list, “Modifier” in the Engine class is never instantiated and is null, and Awake and Update do nothing in a pure C# class if you don’t call them manually (unless the posted code is not complete).

2 Likes

I just stumbled upon this edge case deserialization pitfall myself a few months ago…

The source of the issue is that Unity’s deserialization process can’t create an instance of BaseValue and assign one to maxForce using the normal flow, because the class has no parameterless constructor.

What it does instead is create an uninitialized object, which means that none of the constructors nor field initializers of the object will be executed!

At first it was very surprising to me that even field initializers don’t get executed when an uninitialized object is created - but it makes sense considering that field initialization code also gets moved inside the constructors when the code is compiled to IL.

This fix this, you have to either add a field initializer to create the instance manually:

[SerializeField] BaseValue maxForce = new(0f);

Or add a parameterless constructor to BaseValue:

public BaseValue(float baseValue)
{
    _baseValue = baseValue;
}

private BaseValue() { }
4 Likes

Completely forgot about that weird nugget of Unity’s serialisation.

And on that note, it’s probably good practice to always have an inline initialiser when serialising plain C# objects in Unity objects.

1 Like

well yeah but who knows??

let him try you never know

also WHO uses calls to functions inside member declarations? did you this also?

We do, based on learned understanding about how C# works.

This is done quite commonly. Perhaps you’re confused: Value here is a get-only property. Whenever you attempt to read its value, you use the generated getter accessor method, which computes the value every time. There are also auto-properties like public X Value { get; } which does little more than abstract away access to a compiler-generated field. Initializing a property (like public int Value { get; } = 100;) is done once, and this is where order can matter (this is only for auto-properties, and really just sets the compiler generated field under the hood). But in the case of OP’s code, order does not matter because the value returned is computed when you access the property, every time, and this is not a matter of using a property initializer.

2 Likes

Sometimes I wonder if you actually use Unity, or actually code in C# at all.

I know for a fact it won’t change anything. Inline initialisers are called as part of the constructor. The list will be initialised long before the property can be accessed.

And they’re accessing a method in a Property, not a ‘member declaration’. It’s a normal thing to do.

1 Like

Hi everyone and thanks for all of your comments!

I forgot the null condition on the modifier in the foreach loop so I added it:

float CalculateModifiers()
{
    float value = 0;
    foreach (var modifier in modifiers)
        if (modifier != null)
            value += modifier.value;
    return value;
}

And I added a parameterless constructor which solved the issue.

public BaseValue()
{

}

public BaseValue(float baseValue)
{
    _baseValue = baseValue;
}

I did not know about that serialization process and I am glad to know that now.

Thanks a lot!!!

2 Likes

I’m using unity + java