e.g.
C#
private Memory<byte> _buffer;
...
_buffer.TryGetArray(out ArraySegment<byte> s);
throws an ArgumentNullException, due to the implementation passing null to ArraySegment's ctor. How do I know whether I can use _buffer or not? Should TryGetArray in this case return false? What about accessing _buffer.Span... it similarly throws, but should it just return an empty span? Or do we need a new property on it that tells me whether it's valid for use with various methods? I can do _buffer.Equals(default), but that adds multiple unnecessary checks.
cc: @ahsonkhan, @KrzysztofCwalina, @terrajobst
It would be nice if default memory would be equivalent to empty memory. I wonder what the perf implication of this would be.
Yes, making it so that Empty as public static Memory<T> Empty => default; would "just work" would be the ideal, and mirrors Span's implementation.
I don't think we can ship Memory<T> in its current state, where operations like TryGetArray on default fail; TryGetArray on default should return false. Similar issues exist for the Span property; on a default Memory<T>, it should return an empty/default span. Retain has a similar problem. As does Slice (for a length of 0).
I'm hitting up against this in the System.Net.Sockets implementation. I need to store Memory<byte>, but it could be the default, and thus I'm having to find workarounds, like using _buffer.Equals(default) to compare against default as a more expensive guard check to determine whether I can call TryGetArray, or using a nullable.
Yes, making it so that
Emptyaspublic static Memory<T> Empty => default;would "just work" would be the ideal, and mirrorsSpan's implementation.
Can you please expand on this? How would that work?
Can you please expand on this? How would that work?
The various methods like TryGetArray would need to check whether _arrayOrOwnedMemory was null, e.g. the current code in TryGetArray:
```C#
else
{
arraySegment = new ArraySegment
return true;
}
would change to:
```C#
else if (_arrayOrOwnedMemory != null)
{
arraySegment = new ArraySegment<T>((T[])_arrayOrOwnedMemory, _index, _length);
return true;
}
Then Memory<T>.Empty.Equals(default) would be true, Empty.Length would continue to return 0, you could call methods like TryGetArray and access properties like Span on Empty/default, etc. It's actually strange to me today that Memory<T>.Empty.TryGetArray returns true and gives me back an ArraySegment<T> for an allocated T[].
And if we're concerned about the extra checks, there are ways to mitigate that. For example, ArraySegment<T>'s ctor is already doing a null check; if the JIT doesn't eliminate the double-check (I've not looked to see if it does), we could add an internal ctor to ArraySegment<T> that simply skipped the duplicate check. In fact, presumably we could skip all of the checks in ArraySegment<T>'s ctor, since we already validated _index and _length.
Then
Memory<T>.Empty.Equals(default)would be true
How will checking for null on various Memory\ Memory\
How will checking for null on various Memory
APIs lead to Memory .Empty == default?
Checking for null wouldn't lead to that; changing Empty to be public static Memory<T> Empty => default; would lead to that, and that would be an acceptable implementation once all of the operations can be called on default(Memory<T>) safely. It's also faster, in that you don't need to read a generic static field when accessing it, nor do we force Array.Empty<T>() generic instantiations for types we wouldn't otherwise need it for.
Most helpful comment
The various methods like((T[])_arrayOrOwnedMemory, _index, _length);
TryGetArraywould need to check whether_arrayOrOwnedMemorywas null, e.g. the current code in TryGetArray:```C#
else
{
arraySegment = new ArraySegment
return true;
}
Then
Memory<T>.Empty.Equals(default)would be true,Empty.Lengthwould continue to return0, you could call methods likeTryGetArrayand access properties likeSpanonEmpty/default, etc. It's actually strange to me today thatMemory<T>.Empty.TryGetArrayreturns true and gives me back anArraySegment<T>for an allocatedT[].And if we're concerned about the extra checks, there are ways to mitigate that. For example,
ArraySegment<T>'s ctor is already doing a null check; if the JIT doesn't eliminate the double-check (I've not looked to see if it does), we could add an internal ctor toArraySegment<T>that simply skipped the duplicate check. In fact, presumably we could skip all of the checks inArraySegment<T>'s ctor, since we already validated_indexand_length.