【问题标题】:How can I convert from a Byte Array to Generic Array?如何从字节数组转换为通用数组?
【发布时间】:2021-03-23 16:09:10
【问题描述】:

免责声明 - 由于我有一个可行的解决方案,这个问题可能会越过代码审查的界限,但我确信我正在重新发明轮子并且存在更好的解决方案。

上下文

我正在使用一个低级通信协议,通过该协议我收到一个byte[] 作为已知类型的序列化数组。数据类型将始终为 unmanaged 值类型,通常为 UInt16char 等。

问题

我如何(应该)将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;
}

这也取决于T[] 上的另一个小扩展Slice&lt;T&gt;

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

【问题讨论】:

  • 你真的需要一个数组吗?您可以使用 2 行中的 span 来执行此操作,并且需要零分配/复制 - 您可以在 blittable span 类型之间进行强制转换。内存(跨度源)需要一点工作,但不多。
  • @MarcGravell 你好,谢谢。我以前没有遇到过跨度,现在简单看一下,我看到它们是如何替换我的Slice 扩展的。恐怕我看不到他们将如何提供通用转换?
  • 见下方答案

标签: c# generics type-conversion bitconverter


【解决方案1】:

老实说,如果这是我:我不会将它作为一个数组来获取 - 我只会在跨度之间进行强制转换。数组可以隐式转换为跨度,因此输入不会改变。作为输出的 Span 是一个不同的 API,但在所有方面都非常可比,除了一个(存储为字段)。

考虑

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

这是零分配和零处理——它只是重新解释了现有数据的跨度,这意味着它基本上在做BitConverter 在幕后所做的事情。如果消费者需要读取但不需要能够写入数据,还有ReadOnlySpan&lt;T&gt; 的概念。跨度允许您处理数组的部分,而无需单独传达边界。

如果你不能使用 spans 作为返回,你仍然可以在代码中使用这种方法:

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

【讨论】:

  • 看起来非常优雅,谢谢!真的很棒!
  • @george(上下文:您关于数组的问题,现在已编辑)好吧,我在底部添加了一个额外的位,这可能是您想要的,但那是额外的分配/复制。如果你想要一个数组,那就是权衡。您可以创建一个自定义的MemoryManager&lt;T&gt; 以零副本完成此操作,但这需要更多的工作 - 可能是 20 行。
  • 再次感谢您,如果我有不止一个赞成票,您会得到它!
猜你喜欢
  • 2015-08-04
  • 1970-01-01
  • 1970-01-01
  • 2012-06-04
  • 1970-01-01
  • 1970-01-01
  • 2012-01-17
  • 2010-10-08
  • 2012-07-11
相关资源
最近更新 更多