-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ImmutableArray Span-based APIs #22928
Comments
@stephentoub I also wanted to open this issue since I noticed you wanted to add the following API to namespace System
{
public delegate void StringCreateAction<TState>(TState state, Span<char> destination);
public class String
{
public static string Create<TState>(int length, TState state, StringCreateAction action);
…
}
} Would it be worthwhile to add a similar method to create |
That's because I thought we said we weren't going to add anything related to collections right now. @terrajobst, did I miss that part of the discussion? |
If I understand this part of your suggestion properly, unlike with ImmutableArray<int>.Builder builder = ImmutableArray.CreateBuilder<int>(17);
for (int i = 0; i < 17; i++)
{
builder.Add(i);
}
ImmutableArray<int> built = builder.MoveToImmutable(); It does require allocating an Also, if someone really needed a using System;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
static class Program
{
static void Main()
{
var array = CreateImmutableArray();
UseSpan(array.AsSpan());
}
static ImmutableArray<int> CreateImmutableArray()
{
return new int[] { 1, 2, 3 }.ToImmutableArray();
}
static ReadOnlySpan<T> AsSpan<T>(this ImmutableArray<T> array)
{
return Unsafe.As<ImmutableArray<T>, T[]>(ref array);
}
static void UseSpan(ReadOnlySpan<int> span)
{
for (int i = 0; i < span.Length; i++)
{
Console.WriteLine(span[i]);
}
}
} |
I think something like immutability deserves a place in the BCL. Having developers roll their own is not a great solution given that immutability should impose the strongest guarantee. I've implemented Another thing to consider is that without these types in the BCL, developers may forget that |
The nice thing is that they basically just wrap their counterparts with a couple of careful API choices. public readonly struct ImmutableMemory<T>
{
public ReadOnlyMemory<T> Memory { get; }
public ImmutableMemory(ReadOnlySpan<T> source) => Memory = source.IsEmpty ? ReadOnlyMemory<T>.Empty : source.ToArray();
private ImmutableMemory(ReadOnlyMemory<T> memory) => Memory = memory;
public ImmutableSpan<T> ImmutableSpan => new ImmutableSpan<T>(this);
public static ImmutableMemory<T> Empty { get; } = new ImmutableMemory<T>(ReadOnlySpan<T>.Empty);
public Int32 Length => Memory.Length;
public Boolean IsEmpty => Memory.IsEmpty;
public void CopyTo(Memory<T> destination) => Memory.CopyTo(destination);
[EditorBrowsable(EditorBrowsableState.Never)]
public override Boolean Equals(Object obj) => obj is ImmutableMemory<T> im && Memory.Equals(im);
public Boolean Equals(ImmutableMemory<T> other) => Memory.Equals(other.Memory);
[EditorBrowsable(EditorBrowsableState.Never)]
public override Int32 GetHashCode() => Memory.GetHashCode();
public MemoryHandle Pin() => Memory.Pin();
public ImmutableMemory<T> Slice(Int32 start, Int32 length) => new ImmutableMemory<T>(Memory.Slice(start, length));
public ImmutableMemory<T> Slice(Int32 start) => new ImmutableMemory<T>(Memory.Slice(start));
public T[] ToArray() => Memory.ToArray();
public override String ToString() => Memory.ToString();
public Boolean TryCopyTo(Memory<T> destination) => Memory.TryCopyTo(destination);
public static implicit operator ImmutableMemory<T>(ArraySegment<T> segment) => new ImmutableMemory<T>(segment.AsSpan());
public static implicit operator ImmutableMemory<T>(T[] array) => new ImmutableMemory<T>((ReadOnlySpan<T>) array);
} public readonly ref struct ImmutableSpan<T>
{
public ReadOnlySpan<T> Span { get; }
public ImmutableSpan(ImmutableMemory<T> immutableMemory) => Span = immutableMemory.Memory.Span;
private ImmutableSpan(ReadOnlySpan<T> span) => Span = span;
public static ImmutableSpan<T> Empty => new ImmutableSpan<T>(ImmutableMemory<T>.Empty);
public ref readonly T this[Int32 index] => ref Span[index];
public Int32 Length => Span.Length;
public Boolean IsEmpty => Span.IsEmpty;
public void CopyTo(Span<T> destination) => Span.CopyTo(destination);
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Equals() on ReadOnlySpan will always throw an exception. Use == instead.")]
public override Boolean Equals(Object obj) => throw new NotSupportedException();
public Enumerator GetEnumerator() => new Enumerator(this);
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("GetHashCode() on ReadOnlySpan will always throw an exception.")]
public override Int32 GetHashCode() => throw new NotSupportedException();
[EditorBrowsable(EditorBrowsableState.Never)]
public ref readonly T GetPinnableReference() => ref Span.GetPinnableReference();
public ImmutableSpan<T> Slice(Int32 start) => new ImmutableSpan<T>(Span.Slice(start));
public ImmutableSpan<T> Slice(Int32 start, Int32 length) => new ImmutableSpan<T>(Span.Slice(start, length));
public T[] ToArray() => Span.ToArray();
public override String ToString() => Span.ToString();
public Boolean TryCopyTo(Span<T> destination) => Span.TryCopyTo(destination);
public static Boolean operator ==(ImmutableSpan<T> left, ImmutableSpan<T> right) => left.Span == right.Span;
public static Boolean operator !=(ImmutableSpan<T> left, ImmutableSpan<T> right) => left.Span == right.Span;
public static implicit operator ImmutableSpan<T>(T[] array) => new ImmutableSpan<T>((ImmutableMemory<T>) array);
public static implicit operator ImmutableSpan<T>(ArraySegment<T> segment) => new ImmutableSpan<T>((ImmutableMemory<T>) segment);
public ref struct Enumerator
{
private readonly ImmutableSpan<T> span;
private int index;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Enumerator(ImmutableSpan<T> span)
{
this.span = span;
index = -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext()
{
int index = this.index + 1;
if (index < span.Length)
{
this.index = index;
return true;
}
return false;
}
public ref readonly T Current {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref span[index];
}
}
} |
|
As well as a |
Just ran into another case where |
@eiriktsarpalis, Layomi said you would be the better person to answer: what are the next steps here? Do we need a more concrete API proposal? |
Hi @333fred, next steps would be coming up with a finalized design for API review. Would you be interested in driving this? |
At a glance of the thread and existing APIs, it looks like this is the proposed design: namespace System.Collections.Immutable
{
public static partial class ImmutableArray
{
+ public static ImmutableArray<T> Create<T>(ReadOnlySpan<T> items);
}
public partial struct ImmutableArray<T>
{
public sealed partial class Builder
{
+ public void AddRange(ReadOnlySpan<T> items);
// Unsure if this one is needed, but I'll list it just in case for proper parity
+ public void AddRange<TDerived>(ReadOnlySpan<TDerived> items) where TDerived : T;
}
}
} |
@Joe4evr that's a start, but leaves out slicing (and possibly some other apis as well). It's on my plate to go through and do this, which I should get done in the next few days. |
@eiriktsarpalis quick question: for sliceability, is the runtime's preference to an an indexer that takes a |
Any thoughts on my My apologies if I'm distracting from the main topic. |
Are we going to return a span or another IA? I wonder why we didn't do that for arrays (GetSubArray), should this be consistent? |
Ah, thinking on it for another minute I realize what you're going for: you're trying to communicate that |
Shouldn't indexers that take ranges return the same type, i.e. |
Also love the idea of immutable spans. Would be great to have an immutable analogue to ROSpan that indicates the underlying list of items shouldn't change. |
I'm not convinced that However, enriching |
Right after I posted my comment I had the same thought about it being stack-only and thus perhaps overkill to have an immutable version but figured I would review my codebase more to see if I could see any actual potential benefits. It probably doesn't overcome the -100 test the more I think about it. |
I agree with @terrajobst. Let's focus on adding the {ReadOnly}Span-based members, as well as Slice(int, int) (returning an ImmutableArray). |
I agree that this issue should probably focus on improving the relationship between Regarding This concept of "contiguous block of Again, though, there should probably be a separate issue for this kind of thing. I just wanted to jot down that potential usage scenario before I go crawling back into my cave again. |
Nevermind, I have been reminded of the overload resolution implications for |
Moving to 7.0.0, since it didn't make it to preview 7. |
Hi, I would like to learn and try this issue by PR. Will notify when it is ready for review, thanks. |
Cool @lateapexearlyspeed, assigning the issue to you |
Hi, there are some questions during working on it, could anyone please help answer these, thanks :)
|
I believe the standard way of consuming Span types in netstandard2.0 projects is by consuming the |
Yeah, there is only one method that accepts a Range. Eliding that from .NET Standard 2.0 seems fine. |
Thanks @eiriktsarpalis and @terrajobst , skipped compilation for Can anyone help answer other questions ?
|
I was going to let the experienced members of the team weigh in instead of commenting myself, since I'm just some random guy, but since it's been a few days... I expect the dotnet team to reject breaking changes like the ones you're asking about, especially when they're such hard breaks as removing From my perspective, it looks like you might have misinterpreted #22928 (comment)? The methods that are commented-out in that reference description probably weren't meant to be treated as "remove these methods", but rather "these methods already exist, so you don't need to add them for this".
I could be mistaken, which is again why I was holding off on commenting, but I do have an interest in these new APIs, and I would very much appreciate the |
@airbreather's interpretation is accurate, apologies for not clarifying this earlier. |
That is correct. I often add those for context, in order to judge how the new APIs compose with (the relevant subset of) the existing APIs.
Agreed. We try to avoid source breaking changes like this, but we accept them when there is no other way to make progress. That is, we prefer creating designs that don't cause them, but we're also trying to avoid design-paralysis or frankendesigns purely because some source code would need a cast. |
Thanks @airbreather's interpretation. |
Hi, this PR is ready for review, thanks ! |
EDIT see #22928 (comment) for the full API proposal.
@stephentoub You did not open a tracking issue for this in https://github.com/dotnet/corefx/issues/21281, although from the notes at dotnet/apireviews#40 it seems like this was approved (:tada:). So I took the liberty of moving the proposal here:
Since the API seems to have been approved, this is intended to be a tracking issue for work towards implementing it.
The text was updated successfully, but these errors were encountered: