5

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:

  • I need to ensure C# sees this as an array of value-types, instead of an array of objects ("don't you DARE go making an array of boxed objects around my structs!"). As I understand it: Generic T[] ensures that (as expected)
  • I couldn't figure out how to express the type "this will be an array of structs, but I can't tell you which structs at compile time" other than System.Array. System.Array works -- but maybe there are alternatives?
  • In order to index the resulting array, I have to typecast back to T[]. I am scared that this typecast MIGHT be boxing the Array-of-Structs; I know that if it were (T) instead of (T[]), it would definitely box; hopefully it doesn't do that with T[] ?
  • Alternatively, I can use the System.Array methods, which definitely boxes the incoming and outgoing struct. This is a fairly major problem (although I could workaround it if were the only way to make C# work with Array-of-struct)
9
  • Why do you use structs at the first place? Commented May 2, 2015 at 18:42
  • because classes aren't a value type. Commented May 2, 2015 at 18:58
  • 1
    Then the question is why it has to be a value type? Your questions seems to be a Xy Problem for me. You said you don't know the type at compile time, but your generic method suggests that you know it. :? Commented May 2, 2015 at 19:06
  • 1
    While the structs may be value types, arrays aren't, they're reference types. So you can pass those references around all day long without any copying. Commented May 2, 2015 at 19:10
  • 1
    @Adam gist.github.com/jdphenix/ddad90aa0289c3545e53 Commented May 2, 2015 at 19:23

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks. And IIRC we can't simply "return ref theStruct" because C# still doesn't support that (although it's been suggested/mooted/asked for in the past) ? But it looks like this makes user-land code pretty tortuous for what should be simple, direct, easy reads and writes (brief, simple, code).
"unless your structs are huge" - they aren't, BUT ... a lot of code iterates over those arrays, and doing box/unbox on every item would be a huge stinking waste of time and power :(. Aside from your delegate trick which clearly works, I was thinking of trying some kind of flyweight over the top of the arrays, maybe using unsafe code - but I've not been down that rabbit hole before, probably will lead to several new questions :)
@Adam, a simpler way is possible, if you don't need abstracting / hiding the fact that they are arrays, see my updated answer. Note, no boxing / unboxing is involved at all. just struct copying on the get/set in your original solution. The additional ways mentioned in my answer, simply prevent the copying.
I don't need to hide the existence of arrays, but I do need to hide the index of each struct (e.g. to work around fragmentation issues, some of these structs we're expecting to have clever index generation/hashing/etc). But ... I think I could make code for that relatively user-friendly. e.g. "myArray[ NextIndex() ].AnotherField = "Hello";" -- where NextIndex is one of multiple different pre-made iterator / enumerators that the class offers to user-code
2

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!)

6 Comments

Does the Verifier accept return ref in arbitrary code? If a byref to an array element or a field of a class object is passed into a function, metadata in the caller can inform the GC that the object in question must be considered strongly rooted until the method returns; returning a byref can also be safe if the caller is required to maintain a strongly-rooted reference to the containing object around as long as the returned byref exists. I don't think the GC could safely support returning a byref to an object whose last surviving reference could disappear while the byref exists.
Further, even if the GC could support that, there is no mechanism by which it could allow a method to safely return a ref that was passed into it unless there were a means by which a method could indicate whether a passed-in ref might be returned or ref returning was extremely limited to eliminate all the danger cases (but also many of the useful ones).
Agreed there's many interesting questions here - I recommend visting the linked github page (where the C# team discusses the issues in public! Good on them!). In my suite of use-cases, I'm ref'ing out of a manually-managed long-term storage, so any/all restrictions are likely to be fine. I suspect this is one of the most common cases for the feature (pointer-into-something-very-long-lived)
I would expect the most common use case would be allowing an indexed property to return a ref to an item within a collection, thus allowing MyStructs[23].X++; to work correctly even when MyStructs is a collection type other than StructType[]. The latter situation could be provided for if there were a type similar to an enhanced TypeReference that would store a special kind of byref to an immutable structure containing zero or more of byrefs and a pointer to a method that would receive that structure; such a thing might also help your usage case.
Basically, what would happen would be that MyStructs[23].X+=localVar; would generate a special ref-structure refBlob9341 {refMethod action, ref int X, ref int param1;}; whose action identified a method (ref refBlob9341 r) => r.X+=r.param1; and pass that to a ref-index method for the collection, which would in turn invoke the indicated action. For cases requiring a fixed number of ref parameters to the inner method this can be done reasonably efficiently using generic interfaces, but there's no way to express it cleanly in source code. If the special new type...
|

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.