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!
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];
}
}
See more on this question at Stackoverflow