简体   繁体   中英

How can I convert from a Byte Array to Generic Array?

Disclaimer - Since I have a working solution, this question perhaps crosses the line to code review, however I'm convinced I'm reinventing the wheel and a better solution exists.

Context

I am working with a low level communication protocol whereby I receive a byte[] as a serialized array of a known type. The data type will always be an unmanaged value type, typically UInt16 , char etc.

Question

How can I (should I) generically convert from a byte[] to a T[] so as not to provide implementation for each case, or type specific converters?

Working Code

I have written an Extension Method, ToArray<T> , on byte[] :

public static T[] ToArray<T>(this byte[] input)
    where T: unmanaged
{
    // Use Reflection to find the appropiate MethodInfo from BitConverter
    var converterMethod =  (from method in typeof(BitConverter).GetMethods()
                            // Double redundant selection
                            where ((method.ReturnType == typeof(T)) && (method.Name == $"To{typeof(T).Name}"))
                            select method).FirstOrDefault();
            
    // Create a Function delegate from the MethodInfo, since all BitConverter.To methods share a signiture
    var converter = converterMethod.CreateDelegate(typeof(Func<byte[], int, T>));

    // Some meta variables regarding the target type
    int typeSize = Marshal.SizeOf<T>();
    int count = input.Length / typeSize;
            
    // Error Checking - Not yet implmented
    if (input.Length % typeSize != 0) throw new Exception();
            
    // Resulting array generation
    T[] result = new T[count];
    for(int i = 0; i < count; i++)
    {
        result[i] = (T)converter.DynamicInvoke(
            input.Slice(i * typeSize, typeSize), 0);
    }
    return result;
}

This also depends on another small extension, Slice<T> , on T[] :

public static T[] Slice<T>(this T[] array, int index, int count)
{
    T[] result = new T[count];
    for (int i = 0; i < count; i++) result[i] = array[index + i];
    return result;
}

Test Case

class Program
{
    static void Main(string[] args)
    {
        byte[] test = new byte[6]
        {
            0b_0001_0000, 0b_0010_0111, // 10,000 in Little Endian
            0b_0010_0000, 0b_0100_1110, // 20,000 in Little Endian
            0b_0011_0000, 0b_0111_0101, // 30,000 in Little Endian
        };

        UInt16[] results = test.ToArray<UInt16>();

        foreach (UInt16 result in results) Console.WriteLine(result);
    }
}

Output

10000
20000
30000

Honestly, if this was me: I wouldn't get it as an array - I'd simply coerce between spans. An array is implicitly convertible to a span, so the input doesn't change. Span as an output is a different API, but very comparable in all ways except one (storage as a field).

Consider

public static Span<T> Coerce<T>(this byte[] input)
    where T: unmanaged
    => MemoryMarshal.Cast<byte, T>(input);

This is zero allocation and zero processing - it simply reinterprets the span over the existing data, which means it is fundamentally doing exactly what BitConverter does behind the scenes. There's also the concept of ReadOnlySpan<T> if the consumer needs to read but doesn't need to be able to write to the data. And spans allow you to work on portions of an array without needing to convey the bounds separately.

And if you can't use spans as the return, you can still use this approach for the code:

public static T[] Convert<T>(this byte[] input)
    where T: unmanaged
    => MemoryMarshal.Cast<byte, T>(input).ToArray();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM