3

I am trying to build a simulation in C#. The model itself I want to adress as a struct. Inside the model are a lot of elements (I refer to them as cells, considering that elements also refer to the chemical elements in my program and that is confusing) Each cel is a struct. One of the data types inside is an array of floats.

Below is the code I wrote:

struct Model
{
    //model consists of an array of cells
    public Cel[] cel = new Cel[100];

    public Model() { }
}
struct Cel
{
    public float[] n = new float[3];
    public Cel() { }
}


class Programs
{
    static int Main()
    {
        Model model = new Model();

        model.cel[1].n[1] = 0.2f;
        Console.WriteLine(model.cel[1].n[1]);

        //prevent console from closing
        Console.ReadLine();

        return 0;
    }
}

When I run this, it compiles, but gives a message: System.NullReferenceException: 'Object reference not set to an instance of an object.' model.cel[].n was null. at line 24 model.cel[1].n[1] = 0.2f;

I think this is a logical way to do this and would like a solution to make it work. If this is not the way to do it, could someone point me in the right direction?

Thank you for your help.

1
  • This looks like a logical model to a human but unfortunately it will be a nightmare for a computer due to lots of heap allocations and cache misses. You're better of creating a new float[100 * 3] to store all values in memory consecutively. This initializes all values with 0.0f and you can iterate over the 3 float values of each cell. The downside is that you'll have to do index calculations inside Model. Commented Aug 8, 2022 at 12:53

3 Answers 3

4

Array creation does not initialize each element inside the array - they are all defaults; you could do something like:

    public Cel[] cel;

    public Model() {
        cel = new Cel[100];
        for (int i = 0; i < cel.Length; i++)
        {
            cel[i] = new Cel();
        }
    }

to initialize each manually, but... I'm not sure this is a great outcome, honestly. In particular, instead of a float[3], having 3 discreet float fields in the Cel struct would be a more typical implementation, perhaps making Model a class. You should also usually be very cautious of public fields and mutable structs.

Perhaps something like:

sealed class Model
{
    //model consists of an array of cells
    private readonly Cel[] cel = new Cel[100];

    public ref Cel this[int index] => ref cel[index];
}
readonly struct Cel
{
    public float X { get; }
    public float Y { get; }
    public float Z { get; }
    public Cel(float x, float y, float z)
    {
        X = x;
        Y = y;
        Z = z;
    }

    public Cel WithX(float x) => new Cel(x, Y, Z);
    public Cel WithY(float y) => new Cel(X, y, Z);
    public Cel WithZ(float z) => new Cel(X, Y, z);
}
class Programs
{
    static int Main()
    {
        Model model = new Model();

        ref var cel = ref model[1];
        cel = cel.WithY(0.2f);

        Console.WriteLine(model[1].Y);

        //prevent console from closing
        Console.ReadLine();

        return 0;
    }
}

or with a fixed buffer and span hack:

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

sealed class Model
{
    //model consists of an array of cells
    private readonly Cel[] cel = new Cel[100];

    public ref Cel this[int index] => ref cel[index];
}
unsafe struct Cel
{
    private fixed float n[3];
    public float X => n[0];
    public float Y => n[1];
    public float Z => n[2];
    public Span<float> N
    {
        get
        {
            fixed (float* ptr = n)
            {
                return MemoryMarshal.CreateSpan(ref Unsafe.AsRef<float>(ptr), 3);
            }
        }
    }
    public Cel(float x, float y, float z)
    {
        fixed (float* ptr = n)
        {
            ptr[0] = x;
            ptr[1] = y;
            ptr[2] = z;
        }
    }

    private Cel(in Cel from, int index, float value)
    {
        this = from;
        n[index] = value;
    }

    public Cel WithX(float x) => new Cel(this, 0, x);
    public Cel WithY(float y) => new Cel(this, 1, y);
    public Cel WithZ(float z) => new Cel(this, 2, z);
}
class Programs
{
    static int Main()
    {
        Model model = new Model();

        ref var cel = ref model[1];
        cel = cel.WithY(0.2f);

        Console.WriteLine(model[1].Y);

        Console.WriteLine();
        foreach (var val in model[1].N) // and iterate via span
        {
            Console.WriteLine(val);
        }

        //prevent console from closing
        Console.ReadLine();

        return 0;
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

This works. For my application it makes sense to have float[3], as I will be performing loop actions over the values in the array. Values inside the arrays will change a couple of million times over the course of the simulation. So I would like to work with a mutable structs. In the past I used pointers in C to keep it working quickly, but I was assured that modern compilers can get it to run quickly in C#.
@Kvaestr that's still incredibly expensive, though; and there are other ways to do that... perhaps with a span hack
@Kvaestr (added that as an edit, for illustration)
2

You haven't initialized a Cel yet. You have only reserved space in the array, but it is still empty.

Try this:

model.cel[1] = new Cel();
model.cel[1].n[1] = 02f;

Comments

0

The code

struct Cel
{
    public float[] n = new float[3];
    public Cel() { }
}

is not valid c# syntax. Structs cannot have parameterless constructors Cel() and cannot have field initializers.

To make a struct with an array inside you have some options

  1. Wrap the array if you want the size to be variable. The contents for the struct is just a reference to the array in memory and not the data itself.

        struct Cel
        {
            public readonly float[] n;
            public Cel(int size)
            {
                n = new float[size];
            }
        }
    

    This is pretty useless as it is as it offers no extra functionality than the array itself. You might as well create a jagged array

    float[][] model = new float[100][];
    for(int i=0; i<100; i++)
    {
        model[i] = new float[3];
    }
    // assign last value
    model[99][2] = 0.123f;    
    
  2. Declare the fields one by one to have an array of structures

    struct Cel
    {
        public readonly float a, b, c;
    
        public Cel(float a, float b, float c)
        {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    }
    

    and then create an array of structures

    Cel[] model = new Cel[100];
    model[99] = new Cel(0.1f, 0.2f, 0.3f);
    

    Note that the fields a,b,c go together and cannot (or should not be) individually assigned. This is assured with the readonly keyword making the fields read-only, assignable only by the constructor Cel(float a, float b, float c)

There are more advanced options with fixed-sized buffers (see the fixed keyword) and unsafe code.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.