0

I'm just returning back to C# after an extended period of C++ and Qt. I'm currently stumped by what I would have thought to be a very simple problem.

I have a struct:

struct FontGlyph {
    public uint codepoint;
    public byte[] bmp;
    ...
}

And an array of such structs:

FontGlyph[] glyphs = new FontGlyph[100];

Now, I have a couple of functions which set up and modify the fields in the structs:

static void ConvertFont(Font fnt) {
   ...
   if (fnt.IsSpaceCharacter) {
       glyphs[idx].codepoint = 0x00FF;
       glyphs[idx].bmp = null;
   }
   else {
       RenderFontGlyph(glyphs[idx]);
       // glyphs[idx].bmp is NOT ok here - it is null!
   }
   ...
}

static void RenderFontGlyph(FontGlyph glyph) {
    ...
    glyph.bmp = new byte[256];
    // bmp is fine here, and can be populated as a normal array
    ...
} 

This isn't a particularly great snippet of code, however, in the RenderFontGlyph function, I can see that the bmp array is allocated correctly yet when the RenderFontGlyph function returns, upon inspection of the glyphs[idx] variable, bmp is back to null.

I appreciate I'm probably doing something n00bish but its been a while. Am I a victim of garbage collection or am I being stupid? It had occurred to me that the struct was being passed into the RenderFontGlyph function by-value rather than by-ref but this also makes no difference!

2
  • 1
    structs, when passed by value (which is the default), are being copied, so you're modifying a copy of glyphs[idx] in your method and it has no effect on the instance stored in the array. Pass the value by reference to make it work. Commented May 19, 2017 at 19:08
  • @MarcinJuraszek, thanks for your comment. I have yet again tried passing as ref however when the RenderFontGlyph returns, the bmp field is still null - even though it is correctly initialized in the function. Commented May 19, 2017 at 19:16

2 Answers 2

4

It had occurred to me that the struct was being passed into the RenderFontGlyph function by-value rather than by-ref but this also makes no difference!

Well yes, it does. You're creating a copy of the struct, and passing that into RenderFontGlyph. Any changes made to that copy don't affect anything else.

If you pass it by reference instead, it will make a difference, because you'll be modifying the original storage location in the array:

RenderFontGlyph(ref glyphs[idx]);

...

static void RenderFontGlyph(ref FontGlyph glyph)

Or you could keep using a value parameter, and make RenderFontGlyph return the modified value which you'd need to store back in the array, as per Leonardo's answer.

I certainly wouldn't go so far as to say that you're being stupid, but it's really, really important that you understand the semantics of reference types and value types, particularly if you're creating mutable value types. (And worse, a mutable value type containing a reference to a mutable reference type - the array in this case. You can mutate the array without mutating the struct... this could all become very confusing if you're not careful.)

Unless you have a really good reason to create mutable value types, I'd strongly advise against it - just like I'd also advise against exposing public fields. You should almost certainly be modelling FontGlyph as a class - it doesn't feel like a natural value type to me. If you do want to model it as a value type, then rather than passing in a FontGlyph at all, why not just pass in the code point you want to render, and make the method return the glyph?

glyphs[0] = RenderGlyph(codePoint);

As you're claiming that pass-by-reference isn't working for you, here's a complete example demonstrating that it does work. You should compare this with your code to see what you're doing wrong:

using System;

struct FontGlyph 
{
    public uint codepoint;
    public byte[] bmp;
}

class Program
{
    static void Main()
    {
        FontGlyph[] glyphs = new FontGlyph[100];
        RenderFontGlyph(ref glyphs[0]);
        Console.WriteLine(glyphs[0].bmp.Length); // 10
    }

    static void RenderFontGlyph(ref FontGlyph glyph)
    {
        glyph.bmp = new byte[10];
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you Jon, I will give that a try. Obviously, something is a-miss
@weblar83: "passing by ref makes not a jot of difference" - that's not true. See my updated answer for a complete example that proves that it does work. We can't see what you've tried to do, so we don't know where the problem is, but ref works fine.
1

How about:

static void ConvertFont(Font fnt) {
   ...
   if (fnt.IsSpaceCharacter) {
       glyphs[idx].codepoint = 0x00FF;
       glyphs[idx].bmp = null;
   }
   else {
       glyphs[idx] = RenderFontGlyph(glyphs[idx]);
       // glyphs[idx].bmp is NOT ok here - it is null!
   }
   ...
}

static FontGlyph RenderFontGlyph(FontGlyph glyph) {
    ...
    glyph.bmp = new byte[256];
    // bmp is fine here, and can be populated as a normal array
    ...

    return glyph;
} 

or use ref like this: static void RenderFontGlyph(ref FontGlyph glyph) and then call it like this: RenderFontGlyph(ref glyphs[idx])

1 Comment

Really it just shouldn't be a struct in the first place. It's conceptually a class, and should be implemented as such.

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.