将字节数组转换为 C# 中的枚举集合

Convert byte array to collection of enums in C#

我有一个字节数组,它从函数 GetData() 中以小字节序接收枚举,我想将该数组转换为枚举集合。 我如何将 LE 顺序的字节复制并转换为 C# 中的枚举值?我有 C++ 背景,不太熟悉这种语言。 这是示例代码片段:

public enum BarID
{
  TAG0 = 0x0B01,
  TAG1 = 0x0B02,
}

public class TestClass
{
  List<BarID> ids;
  internal TestClass() 
  {
      ids = new List<BarID>();
      byte[] foo = GetData(); // returns  01 0b 00 00 02 0b 00 00
      // cast byte array so that ids contains the enums 'TAG0' and 'TAG1'      

  }
}

不清楚数组值是按两个还是四个分组,但模式基本相同:

public enum BarID
{
    TAG0 = 0x0B01,
    TAG1 = 0x0B02,
}

public class TestClass
{
    List<BarID> ids;
    internal TestClass()
    {
        ids = new List<BarID>();
        byte[] foo = GetData(); // returns  01 0b 00 00 02 0b 00 00
                                // cast byte array so that ids contains the enums 'TAG0' and 'TAG1'      

        //create a hash-set with all the enum-valid values
        var set = new HashSet<int>(
            Enum.GetValues(typeof(BarID)).OfType<int>()
            );

        //scan the array by 2-bytes
        for (int i = 0; i < foo.Length; i += 2)
        {
            int value = foo[i] + foo[i + 1] << 8;
            if (set.Contains(value))
            {
                ids.Add((BarID)value);
            }
        }
    }
}

该集不是强制性的,但它应该可以防止将无效值强制转换为标记。例如,如果值是基于单词的,则 00 00 值无效。

作为最终考虑,我不会在 class 构造函数中执行类似任务:最好创建实例,然后调用专用方法进行转换。

试试这个:

var foo = new byte[] {0x01, 0x0b, 0x00, 0x00, 0x02, 0x0b, 0x00, 0x00}.ToList();
IEnumerable<byte> bytes;
var result = new List<BarID>();

while ((bytes = foo.Take(4)).Any())
{
    var number = BitConverter.IsLittleEndian
        ? BitConverter.ToInt32(bytes.ToArray(), 0)
        : BitConverter.ToInt32(bytes.Reverse().ToArray(), 0);


    var enumValue = (BarID) number;

    result.Add(enumValue);

    foo = foo.Skip(4).ToList();
}

这里有趣的一步是以小端方式读取字节 reliably(这里的 "reliable" 表示 "works on any CPU, not just one that happens to be little-endian itself");幸运的是,BinaryPrimitives 使这一点变得显而易见,从 Span<byte> 给你一个 intGetData()byte[] 可以隐式转换为 Span<byte>)。然后从 int 你可以转换为 BarID:

Span<byte> foo = GetData();
var result = new BarID[foo.Length / 4];
for (int i = 0; i < result.Length; i++)
{
    result[i] = (BarID)BinaryPrimitives.ReadInt32LittleEndian(foo.Slice(4 * i));
}

此处的 Slice 步骤只是偏移了我们应该在跨度中开始阅读的位置。

尽管 Marc 的回答既好又快,但它仅适用于 .NET Core,或者如果您使用其他 nugets。如果这可能是个问题(或者您的目标是较旧的 Framework 版本),您可以使用这样的解决方案:

var bytes = new byte[] { 0x01, 0x0b, 0x00, 0x00, 0x02, 0x0b, 0x00, 0x00 };

return BitConverter.IsLittleEndian
    ? ConvertLittleEndian(bytes)
    : ConvertBigEndian(bytes);

其中转换方法:

private static unsafe BarID[] ConvertLittleEndian(byte[] bytes)
{
    var barIds = new BarID[bytes.Length / 4];
    fixed (byte* pBytes = bytes)
    {
        BarID* asIds = (BarID*)pBytes;
        for (int i = 0; i < barIds.Length; i++)
            barIds[i] = asIds[i];
    }

    return barIds;
}

如果您知道您的代码将在小端 CPU 上使用(例如,它是一个 Windows 应用程序),那么您甚至不需要大端版本:

private static BarID[] ConvertBigEndian(byte[] bytes)
{
    var barIds = new BarID[bytes.Length / 4];
    for (int i = 0; i < barIds.Length; i++)
    {
        int offset = i * 4;
        barIds[i] = (BarID)((bytes[offset] << 3) | (bytes[offset + 1] << 2)
            | (bytes[offset + 2] << 1) | bytes[offset + 3]);
    }

    return barIds;
}