【问题标题】:Convert byte array to collection of enums in C#将字节数组转换为 C# 中的枚举集合
【发布时间】:2024-01-20 07:20:01
【问题描述】:

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

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'      

  }
}

【问题讨论】:

  • 为什么不使用Dictionary?您可以将 KeyValuePairs 存储在一起。 docs.microsoft.com/en-us/dotnet/api/…
  • 在一个循环中,(BarID)BitConverter.ToInt32(foo, [pos]);,其中[pos] = 0, 4, ...
  • @Jimi 如果我在 32 位系统上,这会以某种方式改变吗?枚举的大小会不同吗?
  • 不,大小一样。但是,看看 Marc Gravell 写了什么。
  • BitConverter.IsLittleEndian

标签: c# casting endianness


【解决方案1】:

虽然 Marc 的回答既好又快,但仅适用于 .NET Core,或者如果您使用额外的 nuget。如果这可能是一个问题(或者您的目标是较旧的框架版本),您可以使用这样的解决方案:

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;
}

【讨论】:

  • “虽然 Marc 的回答既好又快,但仅适用于 .NET Core。” - 这是完全不正确的;它几乎可以在任何地方使用,包括 .NET Framework 低至 4.5:-您只需要对 System.Memory 的包引用:nuget.org/packages/System.Memory
  • 答案已更正。也许这只是我的错,我不想在我的库中添加许多 3rd 方依赖项,因此它们仅从 .NET Core 3/Standard 2.1 开始支持 Span 及其朋友
【解决方案2】:

这里有趣的一步是以小端方式可靠地读取字节(这里的“可靠”意味着“在任何 CPU 上工作,而不仅仅是一个恰好是小端本身的 CPU” );幸运的是,BinaryPrimitives 使这一点显而易见,从 Span&lt;byte&gt; 给你一个 intGetData()byte[] 可以隐式转换为 Span&lt;byte&gt;)。然后从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 步骤只是偏移了我们应该在跨度中开始读取的位置。

【讨论】:

  • 这比 Tomas 的回答有效得多,尽管它仅适用于 .NET Core(或 .NET Standard 2.1)。
  • @GyörgyKőszeg 不,这是不正确的;它适用于 .NET 4.5 / .NET Standard 1.1 / 其他各种 - 你只需要System.Memory
  • 啊,你说得对,我的意思是不允许使用 nuget 包。
  • @GyörgyKőszeg 好吧,是的,但由于这是 .NET 中事实上的分发机制,因此不允许使用 nuget 包非常:以愚蠢的方式做事:)
【解决方案3】:

试试这个:

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();
}

【讨论】:

  • 这不可靠 - 它是 CPU-endian;它也像任何东西一样分配
  • 现在它分配了更多(至少在大端架构上):)
【解决方案4】:

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

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 值无效。

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

【讨论】: