Adam
Adam

Reputation: 33146

Storing a C# reference to an array of structs and retrieving it - possible without copying?

UPDATE: the next version of C# has a feature under consideration that would directly answer this issue. c.f. answers below.


Requirements:

  1. App data is stored in arrays-of-structs. There is one AoS for each type of data in the app (e.g. one for MyStruct1, another for MyStruct2, etc)
  2. The structs are created at runtime; the more code we write in the app, the more there will be.
  3. I need one class to hold references to ALL the AoS's, and allow me to set and get individual structs within those AoS's
  4. The AoS's tend to be large (1,000's of structs per array); copying those AoS's around would be a total fail - they should never be copied! (they never need to!)

I have code that compiles and runs, and it works ... but is C# silently copying the AoS's under the hood every time I access them? (see below for full source)

public Dictionary<System.Type, System.Array> structArraysByType;

public void registerStruct<T>()
{
    System.Type newType = typeof(T);
    if( ! structArraysByType.ContainsKey(newType ) )
    {
        structArraysByType.Add(newType, new T[1000] ); // allowing up to 1k
    }   
}

public T get<T>( int index )
{
    return ((T[])structArraysByType[typeof(T)])[index];
}

public void set<T>( int index, T newValue )
{
    ((T[])structArraysByType[typeof(T)])[index] = newValue;
}

Notes:

Upvotes: 5

Views: 1439

Answers (2)

Adam
Adam

Reputation: 33146

There is a much better solution that is planned for adding to next version of C#, but does not yet exist in C# - the "return ref" feature of .NET already exists, but isn't supported by the C# compiler.

Here's the Issue for tracking that feature: https://github.com/dotnet/roslyn/issues/118

With that, the entire problem becomes trivial "return ref the result".

(answer added for future, when the existing answer will become outdated (I hope), and because there's still time to comment on that proposal / add to it / improve it!)

Upvotes: 1

Alex
Alex

Reputation: 13234

As far as I can see, what you are doing should work fine, but yes it will return a copy of a struct T instance when you call Get, and perform a replacement using a stack based instance when you call Set. Unless your structs are huge, this should not be a problem.

If they are huge and you want to

  • Read (some) properties of one of a struct instance in your array without creating a copy of it.
  • Update some of it's fields (and your structs are not supposed to be immutable, which is generally a bad idea, but there are good reasons for doing it)

then you can add the following to your class:

public delegate void Accessor<T>(ref T item) where T : struct;
public delegate TResult Projector<T, TResult>(ref T item) where T : struct;

public void Access<T>(int index, Accessor<T> accessor)
{
    var array = (T[])structArraysByType[typeof(T)];
    accessor(ref array[index]);
}

public TResult Project<T, TResult>(int index, Projector<T, TResult> projector)
{
    var array = (T[])structArraysByType[typeof(T)];
    return projector(ref array[index]);
}

Or simply return a reference to the underlying array itself, if you don't need to abstract it / hide the fact that your class encapsulates them:

public T[] GetArray<T>()
{
    return (T[])structArraysByType[typeof(T)];
}

From which you can then simply access the elements:

var myThingsArray = MyStructArraysType.GetArray<MyThing>();
var someFieldValue = myThingsArray[10].SomeField;
myThingsArray[3].AnotherField = "Hello";

Alternatively, if there is no specific reason for them to be structs (i.e. to ensure sequential cache friendly fast access), you might want to simply use classes.

Upvotes: 3

Related Questions