Skip to content

How to adopt PolySharp to support sub arrays #104

Description

@apm1grb

According to the documentation PolySharp supports "Index and Range (see indices and ranges)".

The sample of the referenced Microsoft site tries to create sub-arrays:

string[] secondThirdFourth = words[1..4];

Unfortunately, this sample gets complaints:

image

I'm confused about the generic statement regarding PolySharp's support for "Index and Range". Do I miss something?

Activity

  1. felix-mohr commented on Aug 12, 2024

    @felix-mohr

    What data type is your _words ? I actually had the exact same issue. Turns our this only works on Arrays and Spans, as those are know to the compiler. I tried it with List, which works in NET5+, but not NET48 with any of the Polyfill libs.

    image

    Edit: Sorry, correction - I have been trying this with diverse packages as well. My tests:

    • this PolySharp package: Your error
    • Polyfill package: Same error
    • PolyShim package: Works like I described above
    • PolyKit: Your error
    • Meziantou.Polyfill: Your Error
    • No package, using https://www.meziantou.net/how-to-use-csharp-8-indices-and-ranges-in-dotnet-standard-2-0-and-dotn.htm (Look for "You can copy the implementation from this snippet" at the end, download and include that file) works. But that is only this specific part and does not work together with another polyfill package after a quick test, as the classes are then implemented twice.

    I cannot say anything about the quality of the PolyShim package, which seems to be the only one where this works 🤔

    Will also look more into this, but maybe this helps you :-)

  2. felix-mohr commented on Aug 12, 2024

    @felix-mohr

    And one more update: Seem to work with this and the other package if you just add that missing method, I got that from the blog link I posted. You only need this class / method

    namespace System.Runtime.CompilerServices
    {
        internal static class RuntimeHelpers
        {
            /// <summary>
            /// Slices the specified array using the specified range.
            /// </summary>
            public static T[] GetSubArray<T>(T[] array, Range range)
            {
                if (array == null)
                {
                    throw new ArgumentNullException(nameof(array));
                }
    
                (int offset, int length) = range.GetOffsetAndLength(array.Length);
    
                if (default(T) != null || typeof(T[]) == array.GetType())
                {
                    // We know the type of the array to be exactly T[].
    
                    if (length == 0)
                    {
                        return Array.Empty<T>();
                    }
    
                    var dest = new T[length];
                    Array.Copy(array, offset, dest, 0, length);
                    return dest;
                }
                else
                {
                    // The array is actually a U[] where U:T.
                    var dest = (T[])Array.CreateInstance(array.GetType().GetElementType(), length);
                    Array.Copy(array, offset, dest, 0, length);
                    return dest;
                }
            }
        }
    }
    
    
  3. felix-mohr commented on Aug 14, 2024

    @felix-mohr

    Last update (for now). I modified the method a bit to be closer to the original from https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs.
    I did some quick tests including casting the array to object[] etc., seems to work. Maybe this helps you :-)

    Not sure what the default(T) != null is for in the previously posted version. I can only assume some shortcut for value and struct types that cannot be null and cannot be inherited. May be a quick faster with that all around, I did not benchmark this.

    using System.Security.Cryptography;
    
    namespace System.Runtime.CompilerServices
    {
        internal static class RuntimeHelpers
        {
            /// <summary>
            /// Slices the specified array using the specified range.
            /// Adapted from source: https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs.
            /// Required for be able to use `array[10..20] ` syntax in .NET Standard 2.0 and net48.
            /// </summary>
            public static T[] GetSubArray<T>(T[] array, Range range)
            {
                if (array == null)
                {
                    throw new ArgumentNullException(nameof(array));
                }
    
                (int offset, int length) = range.GetOffsetAndLength(array.Length);
    
                if (length == 0)
                {
                    return Array.Empty<T>();
                }
    
                T[] dest;
    
                if (typeof(T[]) == array.GetType())
                {
                    // We know the type of the array to be exactly T[].
                    dest = new T[length];
                }
                else
                {
                    // The array is actually a U[] where U:T. We'll make sure to create
                    // an array of the exact same backing type. The cast to T[] will
                    // never fail.
                    dest = (T[])Array.CreateInstance(array.GetType().GetElementType(), length);
                }
    
                // In either case, the newly-allocated array is the exact same type as the
                // original incoming array. It's safe for us to Array.Copy the contents
                // from the source array to the destination array, otherwise the contents
                // wouldn't have been valid for the source array in the first place.
                Array.Copy(array, offset, dest, 0, length);
                return dest;
            }
        }
    }
    
  4. rezanid commented on Oct 10, 2024

    @rezanid

    @Sergio0694 @xilefius why not add this to the code base? ;)

  5. dmitry-shechtman commented on Feb 3, 2025

    @dmitry-shechtman

    The problem is RuntimeHelpers already exists in netstandard2.0.

  6. Adahel commented on Apr 26, 2025

    @Adahel

    Yes, it is possible to overlay the type only for an assembly for compiler use. It cannot be an external library. The problem is that the compiler does not only use the “GetSubArray” method. Depending on the program, the compiler can also use methods like “InitializeArray”, “RunClassConstructor”, “RunModuleConstructor”, “CreateSpan”, “GetObjectValue”, and “GetUninitializedObject”. I don't know if this list is accurate, but if there is really an intention to implement “GetSubArray,” I recommend using reflection for the original methods. Here is a very simplified example:
    “typeof(object).Assembly.GetType(“System.Runtime.CompilerServices.RuntimeHelpers”, true).GetMethod(“InitializeArray”).Invoke(null, new object[] { array, fldHandle });” Since they are public methods, parameters like BindingFlags can be ignored, but in the case of methods with the same name but different parameters, assigning Type[] is necessary. A production reflection would create delegates for speed. This is a simple example, and it is necessary to get the type from another assembly since there is a type with the same name in the code to compile, overlying the original type. I would be happy to see it implemented, but I'll be honest that it's quite unlikely to happen since it involves some reflection…

  7. cremor commented on Sep 11, 2025

    @cremor

    Maybe this will be possible with C# 14 because then it will be possible to create static extension methods.

  8. smaillet commented on Nov 13, 2025

    @smaillet

    Maybe this will be possible with C# 14 because then it will be possible to create static extension methods.

    Sadly, no it isn't. I've tried but the compiler is ignoring the extension for it's "hidden" use. That is it works fine for anything that is using it directly from YOUR code, but the compiler only looks at the RuntimeHelpers type itself (ignoring the work to resolve an extension). If the static method isn't directly on the type, it can't inject a call to that and fails.😭

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions