Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does ImmutableArray.Create copy an existing immutable array?

Tags:

c#

.net

I am trying to make a slice of an existing ImmutableArray<T> in a method and thought I could use the construction method Create<T>(ImmutableArray<T> a, int offset, int count)like so:

 var arr = ImmutableArray.Create('A', 'B', 'C', 'D');
 var bc ImmutableArray.Create(arr, 1, 2); 

I was hoping my two ImmutableArrays could share the underlying array here. But when double checking this I see that the implementation doesn't:

From corefx/../ImmutableArray.cs at github at 15757a8 18 Mar 2017

    /// <summary>
    /// Initializes a new instance of the <see cref="ImmutableArray{T}"/> struct.
    /// </summary>
    /// <param name="items">The array to initialize the array with.
    /// The selected array segment may be copied into a new array.</param>
    /// <param name="start">The index of the first element in the source array to include in the resulting array.</param>
    /// <param name="length">The number of elements from the source array to include in the resulting array.</param>
    /// <remarks>
    /// This overload allows helper methods or custom builder classes to efficiently avoid paying a redundant
    /// tax for copying an array when the new array is a segment of an existing array.
    /// </remarks>
    [Pure]
    public static ImmutableArray<T> Create<T>(ImmutableArray<T> items, int start, int length)
    {
        Requires.Range(start >= 0 && start <= items.Length, nameof(start));
        Requires.Range(length >= 0 && start + length <= items.Length, nameof(length));

        if (length == 0)
        {
            return Create<T>();
        }

        if (start == 0 && length == items.Length)
        {
            return items;
        }

        var array = new T[length];
        Array.Copy(items.array, start, array, 0, length);
        return new ImmutableArray<T>(array);
    }

Why does it have to make the copy of the underlying items here? And isn't the documentation for this method misleading/wrong? It says

This overload allows helper methods or custom builder classes to efficiently avoid paying a redundant tax for copying an array when the new array is a segment of an existing array.

But the segment case is exactly when it copies, and it only avoids the copy if the desired slice is empty or the whole input array.

Is there another way of accomplishing what I want, short of implementing some kind of ImmutableArraySpan?

like image 730
Anders Forsgren Avatar asked Oct 30 '22 00:10

Anders Forsgren


1 Answers

I'm going to answer my own question with the aid of the comments:

An ImmutableArray can't represent a slice of the underlying array because it doesn't have the fields for it - and obviously adding 64/128 bits of range fields that are only rarely used would be too wasteful.

So the only possibility is to have a proper Slice/Span struct, and there isn't one at the moment apart from ArraySegment (which can't use ImmutableArray as backing data).

It's probably easy to write an ImmutableArraySegment implementing IReadOnlyList<T> etc so will probably be the solution here.

Regarding the documentation - it's as correct as it can be, it avoids the few copies it can (all, none) and copies otherwise.

There are new APIs with the new Span and ReadonlySpan types which will ship with the magical language and runtime features for low level code (ref returns/locals).The types are actually already shipping as part of the System.Memory nuget package, but until they are integrated there will be no way of using them to solve the problem of slicing an ImmutableArray which requires this method on ImmutableArray (which is in System.Collections.Immutable that doesn't depend on System.Memory types, yet)

public ReadonlySpan<T> Slice(int start, int count)

I'm guessing/hoping such APIs will come once the types are in place.

like image 87
Anders Forsgren Avatar answered Nov 15 '22 04:11

Anders Forsgren