-1

I have an array of structs that i need to change into an array of ints, without having to manually copy it.

I tried getting the array address but without results.

This is the equivalent C++ of what i'm trying to do:

struct MyStruct
{
    public int a;
    public int b;
} 
MyStruct* structs = new MyStruct[3];
int* ints;

ints = (int*)structs; //works

This is the code:

MyStruct[] structs = new MyStruct[3];
int[] ints = new int[6];

ints = (int[])structs; //error

I also tried this:

MyStruct[] structs = new MyStruct[3];
int[] ints = new int[6];

fixed(int* ptr = ints)
{
    ptr = (int*)&structs; //error
}

Seems like no matter what i try, i can't modify an array pointer. I can read it, but i cannot modify it. is there a way?

bbQsauce
  • 1
  • 2
  • 1
    Your C++ code leaks memory big time btw. – DeiDei Jun 26 '19 at 11:15
  • 1
    Sounds like [XY Problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Please explain why do you need this in c#. What is your actual goal? – Aleks Andreev Jun 26 '19 at 11:22
  • @bbQsauce, I have reformatted your code. In general it is easier to surround code blocks using three back ticks, this way it is easier to see where the codeblock starts and ends. – Maurits van Beusekom Jun 26 '19 at 11:23
  • @AleksAndreev I have a framework built-in function that i cannot modify or access, that wants an array of ints. However i have an array of structs that hold the ints inside. I would like to avoid having to cycle through the array to copy the whole data since it's huge in size (60-100k elements) and then pass the array to the function. – bbQsauce Jun 26 '19 at 11:45
  • Put your use of `unsafe` aside. Attack the problem in the obvious way first (i.e. copy the data). Is it fast **enough**? How long does it take? Honestly, the simple and safe way is always the best to try **first**. If it is fast enough - boom, stop right there. No point overcomplicating things unnecessarily. `unsafe` code is **much** harder to reason about. – mjwills Jun 26 '19 at 11:58
  • @mjwills it's not fast enough, that's why i'm trying to optimize it. – bbQsauce Jun 26 '19 at 12:46
  • @mjwills this is the code ` `for (int i = 0; i < vertices.Length; i++) { verticesList.Add(vertices[i].a); verticesList.Add(vertices[i].b); verticesList.Add(vertices[i].c); } ` – bbQsauce Jun 26 '19 at 12:50
  • @mjwills takes around 2-5ms depending on the length. Vertices usually holds between 40k and 120k elements. Since it's a realtime application (game) i need it to be as fast as possible, this potentially needs to run every frame. – bbQsauce Jun 26 '19 at 13:05

1 Answers1

0

Instead of doing unsafe casting consider implementing a wrapper for array: custom class with indexer that will convert part of inner array to struct and vice versa. Take a look at this class:

public class ArrayWrapper<T> where T : struct
{
    public ArrayWrapper(int structsCount)
    {
        var fields = typeof(T)
            .GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

        // do some check
        if (fields.Any(f => f.FieldType != typeof(int)))
            throw new Exception("Only int fields allowed");
        if (fields.Any(f => f.IsPrivate))
            throw new Exception("Only public fields allowed");

        InnerArray = new int[structsCount * fields.Length];

        copyFromArrayToStruct = CopyFromArrayToStructMethodBuilder(fields);
        copyFromStructToArray = CopyFromStructToArrayMethodBuilder(fields);
    }

    public int[] InnerArray { get; }

    private readonly Func<int, T> copyFromArrayToStruct;
    private readonly Action<int, T> copyFromStructToArray;
    public T this[int index]
    {
        get => copyFromArrayToStruct(index);
        set => copyFromStructToArray(index, value);
    }

    private Func<int, T> CopyFromArrayToStructMethodBuilder(FieldInfo[] fields)
    {
        var index = Expression.Parameter(typeof(int), "i");

        var actualOffset = Expression.Multiply(index, Expression.Constant(fields.Length));
        var result = Expression.Variable(typeof(T), "result");
        var lines = new List<Expression> {Expression.Assign(result, Expression.New(typeof(T)))};

        for (var i = 0; i < fields.Length; ++i)
        {
            var fieldInfo = fields[i];
            var left = Expression.Field(result, fieldInfo);
            var right = Expression.ArrayIndex(
                Expression.Constant(InnerArray),
                Expression.Add(actualOffset, Expression.Constant(i)));

            var assignment = Expression.Assign(left, right);
            lines.Add(assignment);
        }

        lines.Add(result);

        var lambda = Expression.Lambda<Func<int, T>>(Expression.Block(new []{result}, lines), index);
        return lambda.Compile();
    }

    private Action<int, T> CopyFromStructToArrayMethodBuilder(FieldInfo[] fields)
    {
        var index = Expression.Parameter(typeof(int), "i");
        var value = Expression.Parameter(typeof(T), "value");

        var actualOffset = Expression.Multiply(index, Expression.Constant(fields.Length));
        var lines = new List<Expression>();

        for (var i = 0; i < fields.Length; ++i)
        {
            var fieldInfo = fields[i];
            var left = Expression.ArrayAccess(Expression.Constant(InnerArray),
                Expression.Add(actualOffset, Expression.Constant(i)));
            var right = Expression.Field(value, fieldInfo);

            var assignment = Expression.Assign(left, right);
            lines.Add(assignment);
        }

        var lambda = Expression.Lambda<Action<int, T>>(Expression.Block(lines), index, value);
        return lambda.Compile();
    }
}

Usage:

var array = new ArrayWrapper<MyStruct>(4);

var test = array[0]; // get part of array as a structure
test.a = 10;
test.b = 42;
array[0] = test; // put modified data back to array

This wrapper actually do data copy, but only for parts of array you really want to modify. Also it can be simplified if you prefer to hard code your struct fields than use code generation. Here is live demo

Aleks Andreev
  • 7,016
  • 8
  • 29
  • 37