0

I'm new to C#, and I created a struct (MyMeshFace) and initialized an array consisting of some 40.000 of those structs, like so:

MyMeshFace[] m_meshFaces = new MyMeshFace[mesh.Faces.Count]; // ~40.000 faces

I then populate the array with data in a for-loop, debugging it all with breakpoints so that I can see with my own eyes that field objects are instantiated with empty values (i.e. edge = new Line(); etc) and after this initialization, seeing also that values are properly assigned to fields and object fields (like Line.From = Point; & Line.To = AnotherPoint; etc).

Problem: But, when I run through this list of m_meshFaces a second time, this time calling a function embedded in the struct in order to make the struct update its internal data, then the objects (Lines) no longer has any values! The Line objects in the structs really are instantiated, and thus they're valid objects and so they don't throw exceptions when accessed, but their values (X,Y,X) are zeroed (although I could via breakpoints see that the Lines were originally assigned with values in the first loop when they were initialized). And I have no idea why this is happening.

The code is straightforward:

public struct MyMeshFace
{
    public Line edgeAB;
    public Line edgeBC;
    ...
    public int facesABC[];
    public Point3d[] pointsABC;
    ...
    public void Init(Mesh m, int index)
    {
        edgeAB = new Line();
        edgeBC = new Line();
        ...
    }
    public void SetFaceData(Mesh m)
    {
        // UPDATE 3: Face corner indexes (to points) from mesh:
        var face = m.Faces[faceIndex];
        facesABC[xA] = face.A;
        facesABC[xB] = face.B;

        pointsABC[xA] = m.Vertices[facesABC[0]];
        pointsABC[xB] = m.Vertices[facesABC[1]];
        ...
        edgeAB.From = pointsABC[xA];
        edgeAB.To = pointsABC[xB];
        ...
    }
    public bool DoSomething()
    {
        if (edgeAB.From.X == 0 && edgeAB.From.Y == 0 && edgeAB.From.Z == 0)
           return false;   /* Fail ! */
        // 
        // Execution never makes it here :(
        // 
        return true;
    }
        ...
}

Main method (Update 2: Added call to Init and SetFaceData)

public void Main()
{   
    MyMeshFace[] m_meshFaces = new MyMeshFace[m_mesh.Faces.Count];
    for (var f = 0; f < m_meshFaces.GetLength(0); f++)
    {
        var face = m_meshFaces[f];
        face.Init(m_mesh, f);
        face.SetFaceData(m_mesh);
    }

    for (var x = 0; x < m_meshFaces.GetLength(0); x++)
    {
        var face = m_meshFaces[x];
        if (face.DoSomething())
            Print("Did something!");  # <-- This never happens
    }
}

I tried reading on Microsofts pages about structs, but no clue there. What am I doing wrong?


Edit FIX: This code works, according to anwers below:

public void Main()
{   
    MyMeshFace[] m_meshFaces = new MyMeshFace[m_mesh.Faces.Count];
    for (var f = 0; f < m_meshFaces.GetLength(0); f++)
    {
        // var face = m_meshFaces[f];       // <-- No no!
        // face.Init(m_mesh, f);            // <-- No no!
        // face.SetFaceData(m_mesh);        // <-- No no!

        m_meshFaces[f].Init(m_mesh, f);     // <-- OK! 
        m_meshFaces[f].SetFaceData(m_mesh); // <-- OK
    }

    for (var x = 0; x < m_meshFaces.GetLength(0); x++)
    {
        if (m_meshFaces[x].DoSomething())
            Print("Did something!");  // OK!
    }
}

// Rolf

RIL
  • 185
  • 2
  • 11
  • It's hard to say. Include a small code sample that can actually be compiled that demonstrates the problem. [See here](https://stackoverflow.com/help/mcve). Also bare in mind that mutable structs are often [problematic](https://stackoverflow.com/questions/441309/why-are-mutable-structs-evil). And although your code doesn't show how you are actually initializing, I wouldn't be surprised if the route of your problem is because you are working with a copy of your struct instead of the original somewhere. – Matt Burland Oct 15 '17 at 02:43
  • 4
    I gotta ask, any good reason `MyMeshFace` is a struct and not a class? Structs are generally good for things like a 2D or 3D point. If you need something more elaborate, classes are preferred. Read this https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/using-structs – Nik Oct 15 '17 at 02:49
  • 3
    We need a [mcve]. Where is `m_meshFaces` declared and populated? Where do your values come from? –  Oct 15 '17 at 02:49
  • No copies, no. Two for-loops, one to populate with data all properly initialized (checked), and in the second loop I'm calling the function, same number of items in the loop (loop count = 'array.GetLength(0)'), so no one single item in the list should have zero values. Very puzzling. – RIL Oct 15 '17 at 02:49
  • I added a line with the initlialization of the array to the Main function, like so: 'MyMeshFace[] m_meshFaces = new MyMeshFace[m_mesh.Faces.Count];' – RIL Oct 15 '17 at 02:52
  • @Nik, the reason for using structs is their "lightweight" nature. I also run them in Parallel.For (not while testing though, same problem in the regular foor loop), and at last, preparing for GPU-processing in Parallel (arrays and simple blittable types only, due to memory management). Massive numbers of faces in big meshes (several hundred thousand, or millions). – RIL Oct 15 '17 at 02:59
  • You need to share the code which populates the array. Also the class `MyMeshFace` has lot of code which is not visible to us so can not really say what could be the cause of issue. – Chetan Oct 15 '17 at 03:00
  • 3
    Mutable structs are a particularly bad idea. I'm guessing `Line` and `Point3d` are also structs? I think you need to carefully read [this question](https://stackoverflow.com/questions/441309/why-are-mutable-structs-evil) and its answers. I strongly suspect your problem is something to do with unexpected value-type semantics. – Blorgbeard Oct 15 '17 at 03:01
  • Init() and SetFaceData() is there. They populate the fields by retrieveing data from the mesh (m.Vertices[Index] which was retrieved from the Face index blah, blah. Meshes are all index based. But as said, all data was set once (see code), it's only not there when accessing it the second time. – RIL Oct 15 '17 at 03:07
  • 1
    I'm sure someone will be able to figure this out if you can pare your code down to a [mcve] that we can paste into Visual Studio. Otherwise, it's very hard to see what's going on. – Blorgbeard Oct 15 '17 at 03:10
  • @Blorgbeard, these are Rhino3D objects, Line: http://developer.rhino3d.com/5/api/RhinoCommonWin/html/T_Rhino_Geometry_Line.htm Point3d: http://developer.rhino3d.com/5/api/RhinoCommonWin/html/T_Rhino_Geometry_Point3d.htm . Can't see why they would have any problem.. – RIL Oct 15 '17 at 03:10
  • These types (Line and Point3d) won't compile without RhinoCommon API. – RIL Oct 15 '17 at 03:11
  • @RIL, I'm not so sure that you're getting what you think you're getting with using structs. I think this [thread](https://stackoverflow.com/questions/521298/when-to-use-struct) may give some perspective. I think in your instance, classes would be more efficient. If you need performance, you should really look at using C++ or even C. C# is great, and you can do a amazing things with it, but it's not known to be lightweight. – Nik Oct 15 '17 at 03:12
  • @Nik, I can't send classes to the GPU with the library I intend to use (Alea). I can see the point with guidelines for structs - when you have a choice. Structs to the GPU. No classes (can't copy and shuffle references around). – RIL Oct 15 '17 at 03:19
  • 3
    @RIL, I think the bug may be in your first `for` loop in the main. I think the line `var face = m_meshFaces[f];` creates a copy of the struct that you then initialize. Instead of creating the separate variable `face`, try doing this: `m_meshFaces[f].Init(m_mesh, f);` and `m_meshFaces[f].SetFaceData(m_mesh);` Does that help? – Nik Oct 15 '17 at 03:32
  • 1
    @Nik Yep, I spotted that just before your comment. I think that's the problem as well. – CosmicGiant Oct 15 '17 at 03:33
  • @Nik, yes. And to be clear; That was the *only* problem. With this fix (I updated the code) it works as designed. Perfetto! Thank you all. – RIL Oct 15 '17 at 04:26

2 Answers2

5

When you do var face = m_meshFaces[f]; in your code (added comments to hightlight):

public void Main()
{   
    MyMeshFace[] m_meshFaces = new MyMeshFace[m_mesh.Faces.Count];
    for (var f = 0; f < m_faceInfo.GetLength(0); f++)
    {
        var face = m_meshFaces[f]; // <---
        face.Init(m_mesh, f);
        face.SetFaceData(m_mesh);
    }

    for (var x = 0; x < m_meshFaces.GetLength(0); x++)
    {
        var face = m_meshFaces[x]; // <---
        if (face.DoSomething())
            Print("Did something!");  # <-- This never happens
    }
}

You get a COPY of the struct.

Then you modify the copy, and never replace the original with the copy.

On the second loop, you take a A NEW COPY of the struct.


As per solutions, you may replace the original with the copy by doing m_meshFaces[f] = face; Or you may try working with the struct in the array directly:

m_meshFaces[f].Init(m_mesh, f);
m_meshFaces[f].SetFaceData(m_mesh);

This is explained on Microsoft Documentation:

Structs are copied on assignment. When a struct is assigned to a new variable, all the data is copied, and any modification to the new copy does not change the data for the original copy. This is important to remember when working with collections of value types such as Dictionary.

-- Structs (C# Programming Guide)

Theraot
  • 31,890
  • 5
  • 57
  • 86
  • 1
    Yup, this is what I meant by "unexpected value type semantics" – Blorgbeard Oct 15 '17 at 03:35
  • Ah, thanks for this. I was totally lost on this one! I'll try this in a minute. – RIL Oct 15 '17 at 03:39
  • 1
    For not only linking to the official docs for structs, but quoting the relevant part of the text, +1. I wish it was done more often here on SO. (And I'm guilty of not doing so myself, as often as I could/should) – CosmicGiant Oct 15 '17 at 03:54
1

There seems to be a misconception in your part about how structs are handled when assigned to a variable. structs are value types, so when you assign var something = struct, the variable doesn't receive a reference to the struct's object, unlike a class (reference type); instead, it receives a copy of the value that the struct represents.

With that in mind, assignments like var face = m_mesh.Faces[f]; will not work the way you seem to be expecting them to. Your handling of face on the first for-loop will not affect the original m_mesh.Faces[f], unless you reassign face back to m_mesh.Faces[f] after it's modified.


Try the following code:

public void Main()
{   
    MyMeshFace[] m_meshFaces = new MyMeshFace[m_mesh.Faces.Count];
    for (var f = 0; f < m_faceInfo.GetLength(0); f++)
    {
        var face = m_mesh.Faces[f];
        face.Init(m_mesh, f);
        face.SetFaceData(m_mesh);
        m_mesh.Faces[f] = face; //<<<-----
    }

    for (var x = 0; x < m_meshFaces.GetLength(0); x++)
    {
        var face = m_meshFaces[x];
        if (face.DoSomething())
            Print("Did something!");
        m_meshFaces[x] = face; //<<<-----
    }
}

Update: Corrections to a typo in the OP made this section obsolete. It will be kept for future reference only.

Additionally: You also seem to have a misconception about array initialization and/or structs' default value.

Value types, incl. structs, cannot be null; they are always initialized to a default value. Since you are not populating the m_meshFaces array with any custom data, your command MyMeshFace[] m_meshFaces = new MyMeshFace[m_mesh.Faces.Count]; makes the array be only populated by default MyMeshFace structs. This is because array initialization new array[amount] only reserves the memory slots for a certain quantity (amount) of objects. It's not copying the original m_mesh.Faces collection; it's creating new Face objects entirely, all with default internal values.

The important thing about this is that if Line is also a struct (also initialized to defaults, which are probably 0), then...

if (edgeAB.From.X == 0 && edgeAB.From.Y == 0 && edgeAB.From.Z == 0)
           return false;   /* Fail ! */

...will always fail; as the From values are all always default, which is probably 0 on all, X & Y & Z.

CosmicGiant
  • 6,275
  • 5
  • 43
  • 58
  • Yes, this is clear from the prevoius answer. I also fixed a typo ("m_mesh.Faces" -> "m_meshFaces", and "m_faceInfo" (wrong name) -> "m_meshFaces", but that had nothing with the core problem. Thanks anyway all of you for your help. I knew I ha a problem with structs (hence my description focussing on that) but apart from that I was totally lost. Thanks! – RIL Oct 15 '17 at 04:11
  • @RIL Added a second part that may be important to your problem. Please check it out. – CosmicGiant Oct 15 '17 at 04:14
  • There's no problems with the structs, nor with the lines. I do initialize them as "zero" values yes, but I do assign values to them in the SetFaceData() methods, and now it all works just fine. The problem was all about copying the structs in the loop. – RIL Oct 15 '17 at 04:21
  • @RIL Ok then. It's just that before the edit, as far as we knew, you were dealing with two different arrays (`m_mesh.Faces` VS `m_meshFaces`), so in that scenario the second loop would always have defaults. =) – CosmicGiant Oct 15 '17 at 04:25
  • I don't know why, but initially I had m_meshFaces (no dot) in both cases, then I thought I had a typo there and modified it. But now I modified it back again. The confusing comes from the fact that when retrievening faces from the real mesh, the source mesh, it looks exactly like that: "m_mesh.Faces[index]". Arghh. It can get messy when trying to simplify code without compiling it :) I also had the wrong variable name (m_faceInfo). But was fixed as well. – RIL Oct 15 '17 at 04:57