如何将字节数组转换为通用数组?
How can I convert from a Byte Array to Generic Array?
免责声明 - 因为我有一个可行的解决方案,所以这个问题可能越过了代码审查的界限,但是我确信我正在重新发明轮子并且存在更好的解决方案.
上下文
我正在使用低级通信协议,据此我收到一个 byte[]
作为已知类型的序列化数组。数据类型将始终是 unmanaged
值类型,通常是 UInt16
、char
等
问题
我如何(应该)从 byte[]
通用地转换为 T[]
以便不为每种情况提供实现或类型特定的转换器?
工作代码
我在 byte[]
:
上写了一个扩展方法 ToArray<T>
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;
}
这也取决于另一个小扩展,Slice<T>
,在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);
}
}
输出
10000
20000
30000
老实说,如果这是我:我不会将它作为一个数组 - 我只是在跨度之间进行强制转换。数组可以隐式转换为跨度,因此输入不会改变。 Span 作为输出是 不同的 API,但在所有方面都非常具有可比性,除了一个(作为字段的存储)。
考虑
public static Span<T> Coerce<T>(this byte[] input)
where T: unmanaged
=> MemoryMarshal.Cast<byte, T>(input);
这是零分配和零处理 - 它只是重新解释现有数据的跨度,这意味着它从根本上做的正是 BitConverter
在幕后做的事情。如果消费者需要读取但不需要能够写入数据,那么还有 ReadOnlySpan<T>
的概念。跨度允许您处理数组的部分,而无需单独传达边界。
如果您不能将 span 用作 return,您仍然可以对代码使用此方法:
public static T[] Convert<T>(this byte[] input)
where T: unmanaged
=> MemoryMarshal.Cast<byte, T>(input).ToArray();
免责声明 - 因为我有一个可行的解决方案,所以这个问题可能越过了代码审查的界限,但是我确信我正在重新发明轮子并且存在更好的解决方案.
上下文
我正在使用低级通信协议,据此我收到一个 byte[]
作为已知类型的序列化数组。数据类型将始终是 unmanaged
值类型,通常是 UInt16
、char
等
问题
我如何(应该)从 byte[]
通用地转换为 T[]
以便不为每种情况提供实现或类型特定的转换器?
工作代码
我在 byte[]
:
ToArray<T>
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;
}
这也取决于另一个小扩展,Slice<T>
,在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);
}
}
输出
10000
20000
30000
老实说,如果这是我:我不会将它作为一个数组 - 我只是在跨度之间进行强制转换。数组可以隐式转换为跨度,因此输入不会改变。 Span 作为输出是 不同的 API,但在所有方面都非常具有可比性,除了一个(作为字段的存储)。
考虑
public static Span<T> Coerce<T>(this byte[] input)
where T: unmanaged
=> MemoryMarshal.Cast<byte, T>(input);
这是零分配和零处理 - 它只是重新解释现有数据的跨度,这意味着它从根本上做的正是 BitConverter
在幕后做的事情。如果消费者需要读取但不需要能够写入数据,那么还有 ReadOnlySpan<T>
的概念。跨度允许您处理数组的部分,而无需单独传达边界。
如果您不能将 span 用作 return,您仍然可以对代码使用此方法:
public static T[] Convert<T>(this byte[] input)
where T: unmanaged
=> MemoryMarshal.Cast<byte, T>(input).ToArray();