Span<T> and ReadOnlySpan<T> were introduced in 2018 with C# 7.2 and .NET Core 2.1.
These types provide a way to work with contiguous regions of memory safely and efficiently, without copying data.
They are particularly useful for performance-critical applications, as they allow for slicing and accessing memory without the overhead of array bounds checking.
The introduction of spans marked a significant improvement in how .NET handles memory, offering developers more control and flexibility.
I have added a Github repo for the code shown in this article here:
https://github.com/toreaurstadboss/CustomSpan
ref struct and provide methods for supplying either a reference to an object or in more usual case, an arry. Inside the ref struct, with two fields :
private readonly ref T _reference;
private readonly int _length;
To provide support for passing in an array and a start index and length in the constructor of the ref struct
public CustomSpan(T[] array, int start, int length)
{
ArgumentNullException.ThrowIfNull(array);
if (!typeof(T).IsValueType && array.GetType() != typeof(T[]))
{
// Covariance guard
throw new ArgumentException($"Covariance between types {typeof(T).FullName} and {array.GetType().FullName} is not supported in CustomSpan");
}
#if TARGET_64BIT
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)array.Length)
{
throw new IndexOutOfRangeException("The index was out of bounds for the array");
}
#else
if ((uint)start + (uint)length > (uint)array.Length)
{
throw new IndexOutOfRangeException("The index was out of bounds for the array");
}
#endif
_reference = ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(array), (nint)(uint)start); //nint - native integer
_length = length;
}
MemoryMarshal class inside System.Runtime.InteropServices contains helper methods such as GetArrayDataReference, which returns a reference to the 0-th element of an array. As we can see, array variance checks are done in the method.
The Unsafe class inside System.Runtime.CompilerServices provides a method Add which is used to add an element offset to a reference, handy for getting the offset. As we see, there are no copying of memory blocks, only a reference
to the first element of the array and start, the offset. The length variable specified here just defines the length to use for bounds checking.
Note that we must handle also if the code executes inside 32-bits and 64-bits environments. We finally return a reference to the array given by an offset provided the value start. The (nint) used here is native integer.
We also want to provide an indexer, so we can retrieve a value directly. We also provide a writable indexer too, in the method GetWritable. This is not considered good practice regarding encapsulation, just to demonstrate how you could do it.
// Read-only indexer
public ref readonly T this[int index]
{
get
{
if ((uint)index >= (uint)_length)
{
throw new IndexOutOfRangeException();
}
return ref Unsafe.Add(ref _reference, index);
}
}
// Read-write indexer
public ref T GetWritable(int index)
{
if ((uint)index >= (uint)_length)
{
throw new IndexOutOfRangeException();
}
return ref Unsafe.Add(ref _reference, index);
}
We also provide a method that returns a readonly span from the custom span.
public ReadOnlySpan<T> AsReadOnlySpan()
{
return MemoryMarshal.CreateReadOnlySpan(ref _reference, _length);
}
To use this CustomSpan, demo code is shown below:
void Main(){
var nums = Enumerable.Range(0, 1000).ToArray();
var spanOfNums = new CustomSpan<int>(nums, 500, 500);
var twentyToFifty = spanOfNums.Slice(20, 5);
Console.WriteLine("Output of the twentytoFifty span:");
twentyToFifty.PrintArrayContents(); //prints 520..525
for (int i = 0; i < twentyToFifty.Length; i++)
{
twentyToFifty.GetWritable(i) = (int) Math.Pow((double)twentyToFifty[i], 2); //mutates the Span contents - squares the elements , using GetWritable
}
Console.WriteLine("\nOutput of the mutated twentytoFifty span:");
twentyToFifty.PrintArrayContents();
}
The output looks like this:
Output of the twentytoFifty span:
520
521
522
523
524
Output of the mutated twentytoFifty span:
270400
271441
272484
273529
274576
Usually, you would use the built-in spans in C#, as they contain the necessary functionality you need. This article was just a dive into how Spans are implemented, the code for Spans are available on the .NET source code web site :
https://source.dot.net https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Span.cs,d2517139cac388e8
No comments:
Post a Comment