在.Net 7 ImmutableArray开始支持Span

前言

Span/ReadOnlySpan自从.Net Core 2.1面世后,在BCL基础库使用的地方越来越多,前一段时间在更新Runtime源码,发现在ImmutableArray中也开始支持Span了.如果对Span不熟悉的话,可以看看: 在.Net Core中使用Span 

在.Net 7 ImmutableArray开始支持Span

示例代码

int[] arr1 = { 1, 2, 3 };
Span<int> span1 = arr1.AsSpan();

ImmutableArray<int> immutableArray1 = ImmutableArray.Create(span1); //接受Span类型参数
printAction(immutableArray1);

int[] arr2 = new int[] { 4, 5, 6 };
Span<int> span2 = arr2.AsSpan();
ImmutableArray<int> immutableArray2 = immutableArray1.InsertRange(immutableArray1.Length, span2); //指定下标,将span加入ImmutableArray中
printAction(immutableArray2);

ImmutableArray<int> immutableArray3 = span2.ToImmutableArray();  //Span返回ImmutableArray
printAction(immutableArray3);

ImmutableArray<int> immutableArray4 = immutableArray3.Slice(0, 1); //切片取第一个元素
printAction(immutableArray4);

int[] arr3 = new int[] { 7, 8, 9 };
ImmutableArray<int> immutableArray5 = immutableArray3.AddRange(new ReadOnlySpan<int>(arr3)); //将Span添加到ImmutableArray
printAction(immutableArray5);

int[] arr4 = new int[3];
ImmutableArray.Create(1, 2, 3).ToBuilder().CopyTo(arr4); //从ImmutableArray的数据拷贝到数组中

源码实现

ImmutableArray源码路径:src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/

文件主要有:  ImmutableArray.cs/ImmutableArray_1.Builder.cs/ImmutableArray_1.cs/ImmutableArray_1.netcoreapp.cs

public static ImmutableArray<T> Create<T>(ReadOnlySpan<T> items)
{
    if (items.IsEmpty)
    {
        return ImmutableArray<T>.Empty;
    }

    T[] array = items.ToArray();
    return new ImmutableArray<T>(array);
}

public static ImmutableArray<T> Create<T>(Span<T> items)
{
    return Create((ReadOnlySpan<T>)items);
}

public static ImmutableArray<T> ToImmutableArray<T>(this ReadOnlySpan<T> items)
{
    return Create(items);
}

public static ImmutableArray<T> ToImmutableArray<T>(this Span<T> items)
{
    return Create((ReadOnlySpan<T>)items);
}

public void AddRange(ReadOnlySpan<T> items)
{
    int offset = this.Count;
    this.Count += items.Length;

    items.CopyTo(new Span<T>(_elements, offset, items.Length));
}

public void AddRange<TDerived>(ReadOnlySpan<TDerived> items) where TDerived : T
{
    int offset = this.Count;
    this.Count += items.Length;

    var elements = new Span<T>(_elements, offset, items.Length);
    for (int i = 0; i < items.Length; i++)
    {
        elements[i] = items[i];
    }
}

public void CopyTo(Span<T> destination)
{
    Requires.Range(this.Count <= destination.Length, nameof(destination));
    new ReadOnlySpan<T>(_elements, 0, this.Count).CopyTo(destination);
}

public ImmutableArray<T> AddRange(ReadOnlySpan<T> items)
{
    var self = this;
    return self.InsertRange(self.Length, items);
}

public ImmutableArray<T> AddRange(params T[] items)
{
    var self = this;
    return self.InsertRange(self.Length, items);
}

public ReadOnlySpan<T> AsSpan(int start, int length) => new ReadOnlySpan<T>(array, start, length);

public void CopyTo(Span<T> destination)
{
    var self = this;
    self.ThrowNullRefIfNotInitialized();
    Requires.Range(self.Length <= destination.Length, nameof(destination));

    self.AsSpan().CopyTo(destination);
}

public ImmutableArray<T> InsertRange(int index, T[] items)
{
    var self = this;
    self.ThrowNullRefIfNotInitialized();
    Requires.Range(index >= 0 && index <= self.Length, nameof(index));
    Requires.NotNull(items, nameof(items));

    if (items.Length == 0)
    {
        return self;
    }
    if (self.IsEmpty)
    {
        return new ImmutableArray<T>(items);
    }

    return self.InsertSpanRangeInternal(index, items);
}

public ImmutableArray<T> InsertRange(int index, ReadOnlySpan<T> items)
{
    var self = this;
    self.ThrowNullRefIfNotInitialized();
    Requires.Range(index >= 0 && index <= self.Length, nameof(index));

    if (items.IsEmpty)
    {
        return self;
    }
    if (self.IsEmpty)
    {
        return items.ToImmutableArray();
    }

    return self.InsertSpanRangeInternal(index, items);
}

public ImmutableArray<T> RemoveRange(ReadOnlySpan<T> items, IEqualityComparer<T>? equalityComparer = null)
{
    var self = this;
    self.ThrowNullRefIfNotInitialized();

    if (items.IsEmpty || self.IsEmpty)
    {
        return self;
    }

    if (items.Length == 1)
    {
        return self.Remove(items[0], equalityComparer);
    }

    var indicesToRemove = new SortedSet<int>();
    foreach (T item in items)
    {
        int index = -1;
        do
        {
            index = self.IndexOf(item, index + 1, equalityComparer);
        } while (index >= 0 && !indicesToRemove.Add(index) && index < self.Length - 1);
    }

    return self.RemoveAtRange(indicesToRemove);
}

public ImmutableArray<T> RemoveRange(T[] items, IEqualityComparer<T>? equalityComparer = null)
{
    var self = this;
    self.ThrowNullRefIfNotInitialized();

    Requires.NotNull(items, nameof(items));

    return self.RemoveRange(new ReadOnlySpan<T>(items), equalityComparer);
}

public ImmutableArray<T> Slice(int start, int length)
{
    var self = this;
    self.ThrowNullRefIfNotInitialized();
    return ImmutableArray.Create(self, start, length);
}

private ImmutableArray<T> InsertSpanRangeInternal(int index, ReadOnlySpan<T> items)
{
    Debug.Assert(array != null);
    Debug.Assert(!IsEmpty);
    Debug.Assert(!items.IsEmpty);

    var tmp = new T[Length + items.Length];
    if (index != 0)
    {
        Array.Copy(array!, tmp, index);
    }
    items.CopyTo(new Span<T>(tmp, index, items.Length));
    if (index != Length)
    {
        Array.Copy(array!, index, tmp, index + items.Length, Length - index);
    }

    return new ImmutableArray<T>(tmp);
}

源码还是很易读的,就没有加注释.

秋风 2022-04-04