C
C#9h ago
Nolram

Odd behaviour copying structs

I've created this sample now to demonstrate: https://dotnetfiddle.net/waruzF CC @hamarb123 @Aaron This is certainly user error; I'm just not sure what I'm doing wrong in all honesty, sorry! :(
C# Online Compiler | .NET Fiddle
Test your C# code online with .NET Fiddle code editor.
36 Replies
Nolram
NolramOP9h ago
This is also what tripped me up on structs vs classes being passed by reference vs by value.
hamarb123
hamarb1238h ago
you have boxed the struct so you're effectively using it as if it was a class
Nolram
NolramOP8h ago
Oh right, yeah, makes sense now - I always forget that boxing easily happens implicitly... Any suggestions for how to best address this? Thus far I've just created an explicit Clone() function on the interface that every subtype needs to implement, hence why I asked about compile-time reflection earlier
hamarb123
hamarb1238h ago
ideally just don't box - since if you're going to box anyway, there's not much point using a struct if you're going to use a boxed struct, you can use the API I linked earlier to make a copy
Nolram
NolramOP8h ago
I need to read up on boxing again, I don't think about it as much as I should
hamarb123
hamarb1238h ago
RuntimeHelpers.GetObjectValue
Nolram
NolramOP8h ago
Does that work under NativeAOT?
hamarb123
hamarb1238h ago
probably - try it and see
Nolram
NolramOP8h ago
Any suggestions to avoid boxing? (oh hey I'm blue now. neat)
hamarb123
hamarb1238h ago
it's not that complicated to see when you're boxing - whenever you convert a struct or a generic of a struct type to object, ValueType, an interface it implements, it boxes - or if you call a method that is from a base type that you didn't override like ToString(), or a default interface method implementation that you didn't override, on a struct depends on what you're trying to do - boxing can come up in many scenarios
Nolram
NolramOP8h ago
In my case, I want to keep being able to interact with entities over a generic interface
hamarb123
hamarb1238h ago
do you need to be able to have multiple types of entities in the same collection?
Nolram
NolramOP8h ago
Yeah, I just use simple structs implementing a generic interface in a sparse array as an entity system. So being able to access them generically is sort of needed
hamarb123
hamarb1238h ago
it's not necessarily needed - you could have a collection for each type if you do need them all in the 1 collection, then the easiest solution is probably to just use a class instead of boxing, since that way you make sure there's only the 1 allocation for the 1 thing & it makes it more clear that you're intentionally doing it by reference, otherwise there are more complex solutions available to avoid boxing: - you could store all the data inline (assuming it's unmanaged, otherwise it will be more difficult), along with some sort of type id (& size, if size varies between types & you don't just want to pad) - this is the basic idea behind a lot of the solutions to avoid boxing for this - instead of storing inline, you could store the data in another memory location if you wanted and manage that memory (you will still probably have to do some memory management for the inline approach, but probably not as much, but this has other benefits like making it more easy to tightly pack) - instead of storing a type id & doing switch statement on it, you could store some sort of pointer/object, that you have 1 of per type, which exposes the interface in a way that explicitly takes the memory to operate on as a parameter, so that way you can still call methods on it fully generically, but with the benefit of no allocations - etc.
Nolram
NolramOP8h ago
Hm, yeah, seems I'll have to deal with it being passed by reference. I'll change over to a class then, it's not a big deal in my case Thank you for informing me though, greatly appreciated :) I've had a look now at this, however this seems to just be used to box the type. I'm still trying to create a copy of my object (now a class). Reading up on it online, it does seem that now the only way to actually do that sort of copy (without weird hacks like serializing & deserialising) is via a clone function, which returns to my original question about being able to source-generate it. Are you randomly aware of any info or resources about that? I've heard source generation can be quite difficult & tedious, so if it ends up being too complicated I may just stick to manually writing a clone function.
hamarb123
hamarb1238h ago
I said that one was for making a shallow copy of a boxed struct - if you want to do a shallow copy of a class, you can just call MemberwiseClone (as I also mentioned lol) - you can just expose this as a public API in a base class if you just want to write it once
Nolram
NolramOP8h ago
Right, I should actually be fine with a shallow copy since I would expect my entity types to only have value types, so that might actually work - thanks! Sorry for stealing your time. Looking into MemberwiseClone - seems I unfortunately can't just have a standard implementation in my interface that calls it :( Unless there's some odd trick I don't know about.
hamarb123
hamarb1238h ago
you can put it in a base class - or if you really wanted, you could use UnsafeAccessor to call it, but there's really no good reason to do that here as there's an easy better solution
Nolram
NolramOP8h ago
So I'd need to use a baseclass instead of an interface?
hamarb123
hamarb1238h ago
you don't have to use it instead, you could use it in addition to instead would also work obviously
Nolram
NolramOP8h ago
Hm, that seems to go a bit into multi-inheritance which I generally try to avoid; so instead may be better
hamarb123
hamarb1238h ago
using an interface in the way you're using it is really just a far more complex form of multiple inheritance than using a base class
Nolram
NolramOP8h ago
Eh, is it? I just have a single interface that my entities have to implement for some basic engine callbacks
hamarb123
hamarb1238h ago
there's 2 reasons to use interfaces generally - one reason to use an interface is because you want complex "inheritance" graphs - if you want a simple graph, then using inheritance is effectively a simpler form of achieving that and it limits you to simple graphs - the other reason to use an interface is if you want to expose common functionality to a generic parameter, where T is the actual type implementing it, which you're not doing clearly as you're just boxing it
Nolram
NolramOP8h ago
Hmmm, okay. I generally considered Interfaces as essentially just a "contract" that a type has to implement in order to qualify generically as that interface. I didn't consider the interface to actually exist within itself; just to be a template to implement.
hamarb123
hamarb1238h ago
the difference between interfaces and base types is that you can implement an interface at any point, whereas base types explicitly limit you to only 1 base type (and hence force a simpler "inheritance" graph) - there are other differences too, such as base types being able to store fields if you need that - and base types generally being faster - but you generally select based on what functionality you will actually require - if you only ever plan to have entities that don't inherit from some other random class, then a base class is probably a better choice than an interface - if you will need an entity that inherits from some other type, you can either achieve that by using a wrapper or by making it an interface instead - if you were going to make them structs and never box them, then you'd obviously want to use an interface & pass them around by a constrained generic if you have some other reason to pick one over the other, that's fine obviously - but I wouldn't choose interfaces over base types to avoid multiple inheritance, you'd actually do the exact opposite more or less
Nolram
NolramOP8h ago
Hm, I see. I suppose I'll migrate over the a base class then (I'm not super familiar with them - I come from a procedural & data-oriented background with C++ primarily), I didn't know they actually prevented multi-inheritance. I always thought classes were moreso about overriding base behaviour rather than having to conform to a template
hamarb123
hamarb1238h ago
well, you can only have 1 base class, unlike c++, so it disallows it by construction whereas you can implement as many interfaces as you'd like
Nolram
NolramOP8h ago
Oh I was talking about multi-inheritance in the sense of inheritance depth, not necessarily amount of interfaces implemented at once
hamarb123
hamarb1238h ago
but tbh, I wouldn't pick one based solely on whether it disallows multiple "inheritance" or not - I would pick whatever is most logical based on what features you need, based on the performance, etc., and then just not do multiple inheritance if you don't want it lol - that's what multiple inheritance means :kekw: you can still do that with interfaces anyway
Nolram
NolramOP8h ago
I'm aware you can; I just avoid it :) Higher performance is certainly preferred in my case; but actually the Nr. 1 priority is ease of implementation for someone creating a new entity type, as that's the code on the project that won't be written by me. I.e. minimizing boilerplate and all that hence my interest in source generators, because right now there's still a bit of boilerplate here and there (such as registering a button with the editing tool to create a new entity of a new given entity type, and now also the clone function). In an ideal world, I'd be able to just create a new entity type that derives from some interface or baseclass and have the rest happen automatically, but that's sadly not possible without advanced reflection (which I can't due to NAOT) or source generation.
hamarb123
hamarb1238h ago
if anything, I'd argue that interfaces are more prone to it (with good reason generally) - e.g., look at IList<T>, it has 3 other interfaces that you need to do too. with base classes, I find you generally will just write the base class inheriting from object with all the common functionality & abstract methods needed, and then write the derived classes directly from the base class
paperClip
paperClip8h ago
have you considered something like an EC or ECS were there isn't any/very little inheritance and mainly composition? the concept of each entity having its own unique integer ID in your sample is especially reminiscent of an ECS
Nolram
NolramOP7h ago
I have, and it's not suitable for this project due to the target demographic of programmer, additional overhead to tooling creation, etc etc ECS just makes everything more complicated in this project where the total project complexity is not expected to be very large.
paperClip
paperClip7h ago
mmm i see
Nolram
NolramOP7h ago
In a larger project - ECS for the win, absolutely

Did you find this page helpful?