Skip to content

Fixed array helper improvements #687

@JeremyKuhne

Description

@JeremyKuhne

Is your feature request related to a problem? Please describe.

There are a few issues with the fixed array helpers structs (such as internal partial struct __char_32):

  1. They do not play nicely with C# 11 (generate new warnings).
  2. They also are bulkier than necessary.
  3. They could be faster by leveraging built-in helpers (such as .IndexOf).
  4. Equals logic is unexpected.

Describe the solution you'd like

  • Utilization of [UnscopedRef] to eliminate warnings in C# 11. (Available in RC1)
  • Provide an AsReadOnlySpan that can be marked as readonly to avoid implicit struct copies.
  • Introduce a span extensions class to help with interop span usage.
  • Fix and clarify Equals logic (it currently matches incoming spans that are longer than the content).
  • Move the structs to a shared location to avoid duplicate copies for same sized structs.
  • Consider removing methods that don't provide much value (CopyTo, ToArray, ToString(int length)).

Existing

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal partial struct __char_32
{
    internal char _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31;
    internal readonly int Length => 32;
    internal ref char this[int index] => ref AsSpan()[index];
    internal Span<char> AsSpan() => MemoryMarshal.CreateSpan(ref _0, 32);
    internal unsafe readonly void CopyTo(Span<char> target, int length = 32);
    internal readonly char[] ToArray(int length = 32);
    internal unsafe readonly bool Equals(ReadOnlySpan<char> value);
    internal readonly bool Equals(string value) => Equals(value.AsSpan());
    internal unsafe readonly string ToString(int length);
    public override readonly unsafe string ToString();
    public static implicit operator __char_32(string value) => value.AsSpan();
    public static implicit operator __char_32(ReadOnlySpan<char> value);
}

Proposed

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal partial struct __char_32
{
    private unsafe fixed char _value[32];

    internal readonly int Length => 32;

    // How useful is this?
    [UnscopedRef]
    internal ref char this[int index] => ref AsWritableSpan()[index];

    [UnscopedRef]
    internal unsafe Span<char> AsWritableSpan() => MemoryMarshal.CreateSpan(ref _value[0], 32);

    // Suggesting making the `ReadOnlySpan` method `AsSpan()` to align with `string.AsSpan()`.
    // Need a `readonly` method to allow other `readonly` methods to utilize without creating implicit struct copies.
    [UnscopedRef]
    internal unsafe readonly ReadOnlySpan<char> AsSpan()
        => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _value[0]), 32);

    // This should probably be removed. `AsSpan().CopyTo(target)` is pretty straight-forward.
    internal unsafe readonly void CopyTo(Span<char> target, int length = 32)
    {
        // If we don't remove the methods I've marked, the checks could be removed and we could allow bounds checks to fail.
        if (length > 32) throw new ArgumentOutOfRangeException(nameof(length));
        AsSpan()[..length].CopyTo(target);
    }

    // This should probably be removed. `AsSpan().ToArray()` is pretty straight-forward.
    internal readonly char[] ToArray(int length = 32)
    {
        if (length > 32) throw new ArgumentOutOfRangeException(nameof(length));
        return AsSpan()[..length].ToArray();
    }

    internal unsafe readonly bool Equals(ReadOnlySpan<char> value)
        => AsSpan().SliceAtNull().SequenceEqual(value);

    // This also is unnecessary. `string` is implicitly convertable to `ReadonlySpan<char>`.
    internal readonly bool Equals(string value) => Equals(value.AsSpan());

    // This is also something that should be removed. 
    internal unsafe readonly string ToString(int length)
    {
        if (length < 0 || length > Length) throw new ArgumentOutOfRangeException(nameof(length), length, "Length must be between 0 and the fixed array length.");
        return AsSpan()[..length].ToString();
    }

    public override readonly unsafe string ToString() => AsSpan().SliceAtNull().ToString();

    public static implicit operator __char_32(string value) => value.AsSpan();
    public static implicit operator __char_32(ReadOnlySpan<char> value)
    {
        __char_32 result = default;
        value.CopyTo(result.AsWritableSpan());
        return result;
    }
}

// NEW: Extension class to make working with spans easier.
internal static class SpanExtensions
{
    public static ReadOnlySpan<char> SliceAtNull(this ReadOnlySpan<char> value)
    {
        int length = value.IndexOf('\0');
        return new string(length < 0 ? value : value[..length]);
    }
}

Proposed without unnecessary methods

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal partial struct __char_32
{
    private unsafe fixed char _value[32];

    internal readonly int Length => 32;

    [UnscopedRef]
    internal ref char this[int index] => ref AsWritableSpan()[index];

    [UnscopedRef]
    internal unsafe Span<char> AsWritableSpan() => MemoryMarshal.CreateSpan(ref _value[0], 32);

    [UnscopedRef]
    internal unsafe readonly ReadOnlySpan<char> AsSpan()
        => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in _value[0]), 32);

    internal unsafe readonly bool Equals(ReadOnlySpan<char> value)
        => AsSpan().SliceAtNull().SequenceEqual(value);

    public override readonly unsafe string ToString() => AsSpan().SliceAtNull().ToString();

    public static implicit operator __char_32(string value) => value.AsSpan();
    public static implicit operator __char_32(ReadOnlySpan<char> value)
    {
        __char_32 result = default;
        value.CopyTo(result.AsWritableSpan());
        return result;
    }
}

cc: @lonitra

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions