dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.45k stars 4.76k forks source link

Add "slicing" APIs to ArraySegment<T> #18007

Closed KrzysztofCwalina closed 4 years ago

KrzysztofCwalina commented 8 years ago

ArraySegment could be much more useful if it had the following additional APIs. I propose that we add these members to ArraySegment:

namespace System
{
    partial struct ArraySegment<T>
    {
        public T this[int index] { get; set;  }

        public static implicit operator ArraySegment<T>(T[] array);

        public static explicit operator T[](ArraySegment<T> segment);

        public T[] ToArray();

        public int Length { get; }

        public ArraySegment<T> Slice(int index, int count);

        public ArraySegment<T> Slice(int index);

        public void CopyTo(T[] destination);

        public void CopyTo(T[] destination, int destinationIndex);

        public void CopyTo(ArraySegment<T> destination);

        public static ArraySegment<T> Empty { get; }
    }
}
jamesqo commented 7 years ago

@karelz Sure, one thing though. It seems like from the previous discussion @KrzysztofCwalina decided in favor of having a ref T indexer, but the proposal was never updated?

jamesqo commented 7 years ago

@karelz, @terrajobst made a post further down here that had the latest proposed API at the time, so I think the current proposal looks like

namespace System
{
    partial struct ArraySegment<T>
    {
        public ref T this[int index] { get; }

        public static implicit operator ArraySegment<T>(T[] array);

        public T[] ToArray();

        public ArraySegment<T> Slice(int index, int count);

        public ArraySegment<T> Slice(int index);

        public void CopyTo(T[] destination);

        public void CopyTo(T[] destination, int destinationIndex);

        public void CopyTo(ArraySegment<T> destination);

        public static ArraySegment<T> Empty { get; }
    }
}

Correct?

jamesqo commented 7 years ago

@KrzysztofCwalina By the way, why are we having ArraySegment<T>.Length when we already have ArraySegment<T>.Count? Is that required for the compiler to recognize ArraySegment as a type that can be sliced when spans get language support? It seems really weird/confusing to have two properties that do the same thing.

KrzysztofCwalina commented 7 years ago

The point was to make it easier to switch from array, span, etc. to arraysegment and vice versa. But I agree there is a negative aspect of the addition.

jamesqo commented 7 years ago

@KrzysztofCwalina OK. I have pushed it out of the proposal for now, then; it can be added later if you change your mind.

I have a question that came up while I was working on the implementation: should Slice accept negative indices? If you do new ArraySegment<T>(new T[1], 1, 0).Slice(-1) should you get an ArraySegment that contains the first element or should an exception be thrown?

jamesqo commented 7 years ago

@karelz, regarding whether ArraySegment<T> should implement IEquatable or not, here is an issue that would have been avoided entirely if it was IEquatable. To see if 2 ArraySegments are equal, xUnit's default comparer checks first if they are IEquatable. Since they aren't, eventually it falls back to treating them like collections and calling GetEnumerator, which throws for a default ArraySegment<T>. I think there is value in implementing the interface.

karelz commented 7 years ago

@jamesqo I don't have much experience with IEquatable interface. What you say makes sense, but I let btter experts to make the call. It might be easier to move it into separate issue as it needs API approval.

@KrzysztofCwalina you said you really want this to happen in .NET Core 2.0 - can you please help drive it (incl. answering @jamesqo's questions), if it is still true? Thanks! (sorry I don't have enough expertise on this type + not enough bandwidth lately :()

danmoseley commented 7 years ago

@karelz below are currently implemented in coreclr but not exposed in corefx. Ideally we would have tests and expose them. Some of this was implemented by @jamesqo, some by another contributor who has left.

     public struct ArraySegment<T> : ICollection<T>, IEnumerable, IEnumerable<T>, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T> {
        public static ArraySegment<T> Empty { get; }
        public T this[int index] { get; set; }
        public void CopyTo(ArraySegment<T> destination);
        public void CopyTo(T[] destination);
        public void CopyTo(T[] destination, int destinationIndex);
        public ArraySegment<T>.Enumerator GetEnumerator();
        public static implicit operator ArraySegment<T> (T[] array);
        public ArraySegment<T> Slice(int index);
        public ArraySegment<T> Slice(int index, int count);
        public T[] ToArray();
        public struct Enumerator : IDisposable, IEnumerator, IEnumerator<T> {
            public T Current { get; }
            object System.Collections.IEnumerator.Current { get; }
            public void Dispose();
            public bool MoveNext();
            void System.Collections.IEnumerator.Reset();
        }
    }
danmoseley commented 7 years ago

/cc @stephentoub fyi

stephentoub commented 7 years ago

They're being exposed here: https://github.com/dotnet/corefx/pull/17033