0
votes

I've read a lot about the mutability of structs in c#. Most of the words say mutable structs are evil. I see why. But I have a case where mutable struct seems to be legal. Of course, I'm facing problems that float around struct mutability.

Say, I have a simple struct like this, in a GIS editor application:

enum CoordinateSystem : byte { .... } // coordinate system types

struct Vertex 
{
    float m_x; // m_ is the original value of coordinate, it needs to be kept
    float m_y;  
    float c_x; // c_ is a cache of the result of expensive coordinate transformation
    float c_y;
    CoordinateSystem cachedSystem; // the current coordinate system type in c_x and c_y
    public float x { get {...} set {...}} // properties are for accessing values outside of struct
    public float y { get {...} set {...}} // and transforming m_x,m_y into c_x,c_y if necessary
}

Properties are for accessing the coordinates from outside. From an outer singleton class it is known to the struct what coordinate system is needed when accessing x and y properties. The get property would transform m_x and m_y on-demand if necessary and beside returning the value it stores the result in c_x and c_y for furher accesses. Of course, caching is necessary because coordinate transformation is very expensive. With caching, only the first access would be slow and all further access in the same coordinate system is "immediate".

Vertices live in a List, so basically a vertex list of length 10000 is allocated as a Vertex[10000] in the List.

Obviously, the problem is that accessing (getting out) a Vertex from the list gives back a copy of struct stored in the List<> so caching after transformation has no effect because it stores the result in the short-living copy, and not in the List<> itself.

I don't want to change Vertex to class because it would allocate (on a 64 bit runtime) 10000*8 bytes as reference in one block, and 10000 * 17 bytes in 10000 block, this would roughly + 1/3 memory and 10000 times of number of allocated blocks. (I know that it's a rough calculation because of alignment, ...) And this is about one vertex list, I have ten thousands of them so class would waste a lot of memory.

I would like to keep the transformation transparent for outer accessors because Vertex is a core type in an app referenced many times. Another reason is that only vertex knows about coordinate systems, all other parts look at Vertex.x and Vertex.y as "numbers", nothing more. This way x and y properties should do the transformation.

Question is: what the right approach for solving this problem?

(Please don't close this question as opinion-based. This is not one of that. I'm interested in answers based on facts, considering the aim of the application.)

1
Can you not update the list with the modified copy? - Carey Gregory
I think its better to separate immutable and mutable parts, Have separate type for actual values and cached values. I don't think CoordinateSystem could be different for every single vertex. so its better to have cached values in another list and when ever CoordinateSystem changes recreate the list with new values. - M.kazem Akhgary
Such structs are useful only if stored/exposed from arrays, which currently are the only collection structure that allows mutating a struct element through indexer. C#7 ref returns would probably allow exposing more indexers like array, but until then... Also for backward compatibility they will probably keep the List<T> as it is now. - Ivan Stoev
M.kazem Akhgary: "when ever CoordinateSystem changes recreate the list" - unfortunately it is no way, it would require 1) restructure the core of the app, 2) recalculating when user changes Coo system would take dozens of seconds what is unacceptable; my question was about 1) transparent 2) on-demand (ie on accessing cached values) solution. - Feri
@M.kazemAkhgary - You have a reply from the OP. - Enigmativity

1 Answers

0
votes

As Ivan Stoyev told us "arrays, which currently are the only collection structure that allows mutating a struct element through indexer". And List<> uses array.

So I choose a dirty, reprehensible, unexemplary and evil solution, I foresee downvotes... but I had to have a solution that fits for gigabytes of data in-memory and not requires to change too much code.

I've modified the indexer of the List<> class to mutate (recalculate) the cache part of the struct that lives in the storing array on accessing an element of the list.

Unfortunately, the storing array of List<> is private, not protected, one cannot access it through inheritance. So I went further on a more demonic way: from referencesource I copied necessary parts of the source code of List<> and (with a different name, of course) finally I have a new class with List<> functionality but with capability to update the content on accessing the indexer.

Again, it is a solution that never ever should be followed by anyone. But it works well...