PolySharp icon indicating copy to clipboard operation
PolySharp copied to clipboard

How to adopt PolySharp to support sub arrays

Open apm1grb opened this issue 1 year ago • 8 comments

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?

apm1grb avatar Jul 26 '24 13:07 apm1grb

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 :-)

felix-mohr avatar Aug 12 '24 13:08 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;
            }
        }
    }
}

felix-mohr avatar Aug 12 '24 13:08 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;
        }
    }
}

felix-mohr avatar Aug 14 '24 05:08 felix-mohr

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

rezanid avatar Oct 10 '24 11:10 rezanid

The problem is RuntimeHelpers already exists in netstandard2.0.

dmitry-shechtman avatar Feb 03 '25 03:02 dmitry-shechtman

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<T>”, “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…

Adahel avatar Apr 26 '25 23:04 Adahel

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

cremor avatar Sep 11 '25 07:09 cremor

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.😭

smaillet avatar Nov 13 '25 21:11 smaillet