1

I'm new to StackOverflow so sorry if anything about this post is weird or poorly formatted, giving it my best here, I'm also infamously bad with programming linguo so I might sound incredibly dumb as I say all this.

Anyway, onto my question: My understanding of struct has always been that because of the fact that any and every struct is a value type, the members that the struct has cached also all need to be value types so that the struct's byte size can be deterministic and consistent across all "instances" of the struct (although its not really right to call them instances given they're value types but I'm not sure what other term would be used). To my current understanding, that would strike out any and all reference types from ever being members in structs even though it is permitted by my compiler and presumably all C# compilers. That's fine, that's what structs are for in my eyes, just encapsulating some related value types and reducing the performance impact of otherwise encapsulating them into a class that would require instantiation, not to mention not having to deep-copy any struct "instances" to get a second unique instance to alter. The trouble for me comes along with nullable value types. Sure, they're still value types and so still have a place in any struct, but the extra byte required by all nullable types to determine if the member is currently null or not has me wondering if it is right to allow structs to cache nullable value types as members? Will I run into any lower-level issues by having, for example, an int as a member inside of a struct acting as some access index that has the possibility to be null?

For examples sake, if I were to write a line such as structB = structA with a nullable value member as part of the definition of that struct type, would the member be a pointer to the same nullable object in memory much as it would if I were doing this with a struct containing a reference type?

Many thanks for your time spent reading this!

  • 1) A struct can contain a reference type field, however it will be **by reference**, so what the struct will actually contain is the managed reference not the object to which it refers. – dbc May 27 '23 at 22:09
  • 2) A nullable value type is not a managed reference or pointer. It is a struct of type `Nullable` where `T` is also a struct. From the [reference source](https://referencesource.microsoft.com/#mscorlib/system/nullable.cs,27) we can see it has exactly two fields: `private bool hasValue;` and `internal T value;`. So the bool field may cause the encapsulating struct to be larger than expected due to byte alignment issues but nothing more. See [Nullable value types (C# reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-value-types). – dbc May 27 '23 at 22:12
  • 3) The fact that nullable value types and [nullable reference types](https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references) both use the same `?` postfix despite being totally different in implementation can be confusing, admittedly. – dbc May 27 '23 at 22:13
  • @dbc Thanks a ton for your concise responses! Here's my thoughts: 1. I don't quite understand this. So if I put a reference type member into a `struct`, due to the fact that the compiler KNOWS the member is in a `struct`, the member is now a pointer to the the `object` rather than the `object` itself since pointers have a fixed byte size? It was my understanding previously that all reference type members were this no matter if they're in a `struct`, `class`, etc. (continued) – Spring E. Thing May 27 '23 at 22:21
  • (continued) 2. I kind of forgot entirely about how the `?` is just shorthand for `Nullable` and how that itself is a `struct`. How come the `bool` member of this `struct` causes byte alignment issues when we know a `bool` can only be `true` or `false` and is only ever 1 byte in size? 3. I thought all reference types were nullable? Seems counterintuitive for maintenance to intentionally mark all reference type members as nullable with a `?` – Spring E. Thing May 27 '23 at 22:21
  • C# isn't c++/cli. "Unsafe" code aside, a reference type is **always** referred to via a managed reference rather than embedded in some containing object. If you have some `class Foo { }` then if you do declare `Foo foo = new ();` them what you are actually doing is allocating an instance of `Foo` on the heap and storing a managed reference to it in the current context. – dbc May 27 '23 at 22:24
  • Sorry, the only "byte alignment" issues with `Nullable` is that you may use more memory than expected. For instance `double` uses 8 bytes but I believe `double?` will use 16 due to byte alignment. But things will still work fine. – dbc May 27 '23 at 22:26
  • @dbc I don't want to interrupt, but I'll assume you're done. So as for your first response: Do I have to do anything to GC that managed reference? If I were to, pass a managed reference to a `class` instance into the constructor of a `struct` for that `struct` to then store as a member, does that `struct` now possess the one remaining reference to that managed reference and therefore that managed reference will be GC'd once the `struct` is no longer itself in use anywhere? Also, since it is a managed reference and not the `object` itself, byte size of the member is certain and fixed, right? – Spring E. Thing May 27 '23 at 22:36
  • As for the second response: That clears itself up nicely, so basically its not that the byte size of a `Nullable` value type is uncertain and could vary depending on its state and its own members but rather that it will just be twice as many bytes occuppied in memory as the non-`Nullable` value type counterpart? For example, an `int` would be 4 bytes so therefore a `Nullable` `int` would be 8 bytes? – Spring E. Thing May 27 '23 at 22:36
  • *Do I have to do anything to GC that managed reference?* -- no, as long as you don't write any `unsafe` code or allocate unmanaged resources, the CLR + the GC handle everything for you. – dbc May 27 '23 at 22:50
  • @dbc Alright well thank you a ton, that should be all from me for now, learnt a lot from just this small discussion! – Spring E. Thing May 27 '23 at 22:51
  • I believe (and this is all an implementation + platform detail) that the size of a `Nullable` will be the size of the underlying value type `T` plus an extra byte rounded up to the required alignment. – dbc May 27 '23 at 22:52

0 Answers0