I looked with IL disassembler into the code of the Length property of the ReadOnlyMemory struct (.NET 461) and found this strange code:
public int Length => this._length & int.MaxValue;
what is the reason of AND-ing with int.MaxValue here?
I looked with IL disassembler into the code of the Length property of the ReadOnlyMemory struct (.NET 461) and found this strange code:
public int Length => this._length & int.MaxValue;
what is the reason of AND-ing with int.MaxValue here?
Disclaimers:
This answer concerns both the original design of
Memory<T>(i.e. how it was prior to its redesign in 2018) - and its layout in the builds of the current latest version ofSystem.Memory.dllfor .NET Framework 4.x specifically and not the far more recent versions that ship with .NET 5+ - not that they're significant, though - they aren't).(As a disclaimer on style: I see the authors of
Memory<T>chose to use underscore prefixes for instance state field names - which is not something I would ever do myself - I'm just using their field-names verbatim for clarity)Note that both
Memory<T>andReadOnlyMemory<T>have identical layouts - the only difference is in their exposed interface design so I'm using the termsReadOnlyMemory<T>andMemory<T>interchangeably here.Memory<T>type is astructtype that needs to be lightweight, which means it can't have any more fields than absolutely necessary:Memory<T>this is represented by an opaqueObjectfield named_object, as it can represent not just aByte[], but also aChar[]orStringor any other type thatSpan<T>can work with.Int32 _lengthfield to store the length of the memory-area/buffer that it's pointing to.Int64either (and doing so would makeMemory<T>consume 2x more storage).Int32 indexfield to store its current read/write "head" intoMemory<T>(if packed in-memory) already consumes32 + 32 + 32 = 96 byteson x86, or64 + 32 + 32 = 128 byteson x64 - which is pushing at the limit for how big astructcan become.Memory<T>needs 2 more pices of information:_objectpoints to so it can dereference it correctly, otherwise it will need to rely on (very expensive) runtime type-tests (e.g. theisoperator) before it can safely performing any action, which would be ruinous for performance. (This is because the actual low-level operations for getting data out of aByte[]is different to aByte*- or aChar[]vs. aString)._objectis a reference to a pinned-memory object - as that tells it if it needs toPin()the memory before it can be used safely or if it can skip that step.boolfields to store - fortunately boolean values are simple 2-state values that can be represented by a single bit in-memory (whereas theSystem.Boolean(boolin C#) type uses a whole 8-bits - and four bytes when marshalled to a Win32BOOLvalue).Memory<T>is already using 2x 32-bit integers for_indexand_lengthalready and both of those fields will only ever hold a 31-bit value - it means there's 2 unused bits right there for the taking: - so it sneakily appropriates the MSBs of_lengthand_indexto store that "is pinned-memory or not" and "is-.NET-array-or-not" state.In 2018, some point after
System.Memorywas first released for .NET Core - and also made available for .NET Framework 4.x users - the design ofMemory<T>was changed to no-longer require two bit-fields (as type-information about the_objectcan be carried "for free" within the type-system). Here's the diff, where we can see the old and new versions side-by-side, with their respective explanatory comments about both designs:Quoeth the source:
In this diff, they implemented the change to reduce this down to just using
_indexfor the "is-pinned-or-not" state thus leaving_lengthalone. I speculate that because_lengthis accessed more than_indexit made sense to use_indexto store the flag-bit instead of_length.Unfortunately I'm not able to get the exact source for the .NET Framework port of
System.Memory(if it even exists). The assembly metadata for ourSystem.Memory.dllv.4.5.5says it's[assembly: AssemblyInformationalVersion("4.6.31308.01 @BuiltBy: cloudtest-841353dfc000000 @Branch: release/2.1-MSRC @SrcCode: https://github.com/dotnet/corefx/tree/32b491939fbd125f304031c35038b1e14b4e3958")]- but no such commit exists in the publiccorefxrepo.