We use ULong in two different ways (possibly more, but this is about these two)
We use them as values coming out of data parsing or going into data unparsing. In this case, all 64 bits are used, and we don't care if the underlying stored Long integer is negative or positive.
We also use them as bit offsets/positions into the data stream. This assures that they are always non-negative when created.
However, the math that computes bit offsets calls ULong.longValue a great deal, then operates on the Long elements.
This is technically incorrect. Now, one can argue that it won't matter because you need an offset bigger than 2^63 ... before you get a ULong where the underlying Long would be negative.
But, this is the stuff of security holes. E.g., a data format can store a length that results in a bit length > 2^63. No such data can exist, but some prefix header field can be created that suggests that much data.
And then who is to say that something won't break?
So, the right thing here is for arithmetic on ULong to work directly (I'm not sure why it doesn't. There appear to be operators defined.) Or for a method other than ULong.longValue be used when what we are assuming we're getting back is a non-negative Long.
We should scrutinize all calls to ULong.longValue and see if they can/should be changed to ULong.asPosition or ULong.asLength, etc. methods that express the intent, or the conversion can just be avoided and math done directly on ULong values.