According to
Application Note: Field Presence | Protocol Buffers Documentation singular messages are always explicit presence (independant of using `optional`). According to
Application Note: Field Presence | Protocol Buffers Documentation there should be hazzers in this case to make it distinguishable.
No Nullable Setters/Getters Support | Protocol Buffers Documentation gives an example of why there aren't nullable getter/setter with showing
msg.child.grandchild.foo == 72 as example that should work. It's sadly throwing a null-pointer exception in C# even when using `syntax = "proto2"`. For `proto3` you get a note that it's not allowed to define a default value, but even for non-defaults it seems to be implemented differently than Kotlin and Rust.
C# IS offering (technically nullable) getter/setters instead of hazzers but doesn't mark it as nullable. This leads to confusion and bugs. I mentioned this at
Add option to generate C# nullable annotations · Issue #6632 · protocolbuffers/protobuf but Jon Skeet is not part of the Protocol Buffers team and has to focus on his main job. So I would love someone from the protobuf team check the implementation against the design and clean up the confusion to avoid annoying him too much.
Non-messages do the explicit presence correctly and offer hazzers when optional is added. So while I personally love NRTs, I fully do understand the semantics of the design and especially due to the semantic of `HasXxx` and `ClearXxx` making a differnce to just null-checking am absolutely fine with the way it is.
I'm fine with resolving the explicit presence on singular messages either way (fixing nullability on the message properties or adding hazzers on messages). But as it's now, messages feel broken and it leads to issues with nullability checks and also merging as empty messages with all-default fields are not considered if not explicitly requested due to it not being distinguishable.