Skip to content
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

With Span<T>.DangerousCreate gone, there are no APIs to create a Span<T> from a T[,] array (on .NET Standard 2.0) #27690

Closed
Sergio0694 opened this issue Oct 21, 2018 · 8 comments
Milestone

Comments

@Sergio0694
Copy link
Contributor

Hello, I'm working on a .NET Standard 2.0 library (this one) and up until now I've used the old Span<T> APIs from the System.Memory assembly version 4.0.1.0 (from what I can see by Ctrl+click on Span<T>), and everything worked great.

I went back to update the library with the latest NuGet packages, and noticed that (according to #24562) the Span<T>.DangerousCreate(object, ref T, int) API has been removed. I tried to look for it under MemoryMarshal, but it isn't there at least on .NET Standard 2.0.

As a result, simple methods like these ones are not working anymore:

public static Span<T> AsSpan<T>([NotNull] this T[,] m) where T : struct
    => Span<T>.DangerousCreate(m, ref m[0, 0], m.Length);

public static Span<T> Slice<T>([NotNull] this T[,] m, int row) where T : struct
    => Span<T>.DangerousCreate(m, ref m[row, 0], m.GetLength(1));

Note: I'm using T : struct as I wrote this library targeting C# 7.2. I'll replace struct with unmanaged when I move the lib to C# 7.3, I know that using struct isn't a perfect constraint here.

Same for similar methods to extract a chunk of contiguous rows from a T[,] array.

I'm using 2D arrays all over this library, and all the current Span<T> APIs I see only work with classic 1D arrays. I can't use the constructor that takes a void* as if I used something like new Span<T>(Unsafe.AsPointer(ref array[0, 0])) the GC would wouldn't be able to track the ref once it gets forwarded as a pointer only.

I understand that the original DangerousCreate(object, ref T, int) had to me moved/removed and that some users were not using that API correctly, but is there some other way to still do what that API did now that it's gone? I mean, I can't see a way to properly work with 2D arrays and spans now that the API in question is no longer available.

Thanks! 😄

@stephentoub
Copy link
Member

MemoryMarshal.CreateSpan is the new method name, and is available in .NET Core 2.1. It's not available in the portable implementation because it's not implementable safely without runtime support (it would have similar problems to what you cite regarding passing around pointers).

@Sergio0694
Copy link
Contributor Author

Sergio0694 commented Oct 21, 2018

Hi @stephentoub - thank you for your quick reply!
Yeah I'm aware the new method name is CreateSpan (forgot to mention that in the answer), thanks for the explanation as to why it isn't available on .NET Standard 2.0.
I have a couple more questions then:

  • This is just out of curiosity, but how can the (now gone) Span<T>.DangerousCreate method work if it lacks runtime support? I mean, I've been using it for months in the library without issues, and it still works fine if I downgrade the System.Memory assembly in the lib to use the previous version.
  • Is there no other possible workaround (maybe using some weird trick with some unsafe API, I don't mind) to get this to work in a similar manner on .NET Standard 2.0 or is it just impossible for arbitrary objects like 2D arrays due to the lack of runtime support that you mentioned? I mean, something like how we could still pin arbitrary types before C# 7.2, by using the &Unsafe.As<byte, T>(ref T[]) trick? This was just an example of how some language restrictions could be bypassed in the past using unsafe APIs, not that this snippet in particular has anything to do with this issue. I'm just asking to be 100% sure before rewriting half the lib 😁

Thanks again for your help!

@stephentoub
Copy link
Member

Sorry, I misread your question. I thought you were asking about a constructor that takes ref T, int... that's the one that's not possible without runtime support. object, ref T, int is technically possible, as long as the ref T is actually a part of the object, which it can't validate.

@stephentoub
Copy link
Member

@Sergio0694
Copy link
Contributor Author

Sergio0694 commented Oct 21, 2018

object, ref T, int is technically possible, as long as the ref T is actually a part of the object, which it can't validate.

@stephentoub no worries, and glad to hear that specific method is possible on .NET Standard 2.0 too then! It also explains why it was indeed working before. And yeah I don't mind if the validation isn't done automatically, even though I can see why that method was removed if you saw many users not using it correctly.

Is there a way to actually replicate that somehow? IIRC it was accessing some Span<T> APIs that are marked as internal (eg. one of the constructors that manually assigns the object to pin and the offset), is there some other way to still create a Span<T> with an object and a given ref inside that object, using the available APIs?

@Sergio0694
Copy link
Contributor Author

As pointed out in the referenced issue, there isn't an obvious way to replicate this with existing APIs without using reflections, so I'll just go ahead and try to refactor my code where possible.

Thanks again!

@nietras
Copy link
Contributor

nietras commented Jan 27, 2019

I think it is unfortunate the Create(object, ref T, int) or equivalent has been removed. As @Sergio0694 mentioned there are other uses of ReadOnlySpan/Span<T> outside the one-dimensional array scope.

I think the lack of this method is problematic for interoperability with the Span<T> types, basically the type is limited to the scenarios that you think is possible without regard for what others might need. I do not understand why this could not have been available in MemoryMarshal? The train seems to now have left and there are no options for creating a span given the above it seems? That is very limiting.

What if I wanted to create a span for a "section" of an object? Can't (if I can I'm all ears 😄). Managed arrays, strings and native pointers are not the only scope of spans.

Maybe I have missed a way to do this.

EDIT: Really I would have preferred for the low-level "slow" Span creation to be available (object obj, IntPtr byteOffset, int length) this can be used to create a "fast" Span too and would allow for interop with all kinds of scenarios.

@nietras
Copy link
Contributor

nietras commented Mar 24, 2019

As a side note for netcoreapp2.1 you can use the following two methods:

public static class MemoryMarshal
{
    public static ReadOnlySpan<T> CreateReadOnlySpan<T>(ref T reference, int length);
    public static Span<T> CreateSpan<T>(ref T reference, int length);
}

I think it is unfortunate that these methods were designed to work with fast Span and not all possible span versions (incl. slow Span) and hence all runtimes.

The lack of Create(object, ref T, int) and the hands holding around how spans can be created to me seems to have gone too far... and it seems there thus will never be a way to create a Span from multi-dimensional arrays, custom reference types for .NET Framework or prior to .NET Core 2.1 when there easily could have been. :| And we can apparently forget about spans on .NET Framework too 😢

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 3.0 milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 15, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants