Runtime: Add a Span.TryCast extension method

Created on 20 Jan 2018  路  12Comments  路  Source: dotnet/runtime

Split from https://github.com/dotnet/corefx/issues/26368

```C#
namespace System
{
public static partial class MemoryExtensions
{
public static bool TryCast(this ReadOnlySpan source, out ReadOnlySpan output) where TFrom : struct where TTo : struct;
public static bool TryCast(this Span source, out Span output) where TFrom : struct where TTo : struct;
}
}

We should add some TryCast extension methods that behave similar to the [NonPortableCast](https://github.com/dotnet/corefx/issues/26368) functions but that include checking beforehand to make sure the operation will be succeed with a usable Span result. To do this, they will need a structure somewhat like this:

if (Platform detect intel)
{
// do NonPortableCast
// return true;
}
else
{
// check the alignment manually.
if ( we're already aligned)
{
// do NonPortableCast
return true;
}
else
{
//do nothing
return false;
}
}
```

api-suggestion area-System.Memory

Most helpful comment

Or InvalidCastException ?

All 12 comments

From https://github.com/dotnet/corefx/issues/26644:

Consider turning it into an instance method. @mgravell, @benaadams

Question: The T in Span/ReadOnlySpan is not constrained to struct. What happens if I try to cast from Span

C# public readonly ref partial struct ReadOnlySpan<T> { public static bool TryCast<TTo>(out ReadOnlySpan<TTo> output) where TTo : struct; } public readonly ref partial struct Span<T> { public static bool TryCast<TFrom, TTo>(out Span<TTo> output) where TTo : struct; }

Personally I'd be fine with a runtime NotSupportedException with a useful message. That makes perfect sense to me.

Could also be persuaded to go for InvalidOperationException...

Makes sense.

Or InvalidCastException ?

I believe the consensus from the review was that we would return false and not throw.

What determines whether "we're already aligned"? If T is a sequential struct, for example, do we have to do the same calculation that the runtime already does to determine its alignment requirements? (Including taking into the account the Pack attribute?) If we're taking into the account the running CPU architecture as the example suggests, is there a list of supported CPU's and their alignment requriments somewhere?

Per Ian, this shouldn't actually be api-approved currently given the post-review discussions.

Possibly just being silly (wouldn't put it past me...) but a possibly solution to @mgravell dislike of having to provide the _from_ and _to_ types (other than adding the functions directly onto Span) might be to do something along the lines of:

static class SpanCasting
{
    public ref struct SpanCaster<T>
        where T : struct
    {
        private Span<T> _span;
        public SpanCaster(Span<T> span) { _span = span; }
        public Span<TTo> Cast<TTo>() where TTo : struct { return _span.NonPortableCast<T, TTo>(); }
        public bool Try<TTo>(out Span<TTo> span) where TTo : struct { ...; }
    }

    public static SpanCaster<T> NonPortable<T>(this Span<T> span)
        where T : struct
    {
        return new SpanCaster<T>(span);
    }
}

Which would mean its usage would be something like:

var span = new Span<char>();
var casted1 = span.NonPortable().Cast<uint>();
if (span.NonPortable().Try<uint>(out var casted2)) { ...}

@stephentoub is this API still on the table? I know 3.0 is the priority right now, but perhaps for 3.1?

Rather than TryCast, or perhaps in addition, it would useful to have a bool IsAligned<TFrom,TTo> method on MemoryMarshall which would return whether a given Span is aligned to the TTo type. That could be used implement the same scenarios.

E.g. to implement this code https://github.com/dotnet/corefx/issues/26368#issuecomment-359039294, we could do this:

Span<int> ints;
if(MemoryMarshal.IsAligned<byte, int>(bytes)) {
    ints =  MemoryMarshal.Cast<byte, int>(bytes);
} else {
    ints = new int[bytes.Length/sizeof(int)];
    bytes.Slice(0, ints.Length * sizeof(int)).CopyTo(ints.AsBytes()); 
}

However, if we had IsAligned then the code to implement TryCast would be trivial to achieve:

public static bool TryCast<TFrom,TTo>( Span<TFrom> span, out Span<TTo> outSpan )
{
    if (!IsAligned<TFrom,TTo>(span)) {
       outSpan = default;
       return false;
    }
    outSpan = Cast<TFrom,TTo>(span);
    return true;
}

Should this issue be modified to track the addition of an IsAligned or GetAlignment API? Since the Span<T>.Cast method was removed before shipping and replaced with MemoryMarshal.Cast, I don't think there's much of an appetite to add the API that this issue originally proposed.

Was this page helpful?
0 / 5 - 0 ratings