Reputation: 1306
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.
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.
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?
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;
}
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
Upvotes: 3
Views: 795
Reputation: 1063814
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();
Upvotes: 2