C# 二进制数组与结构体的互转方法

 更新时间:2023年09月05日 09:06:34   作者:lindexi  
本文将和大家介绍 MemoryMarshal 辅助类,通过这个辅助类用来实现结构体数组和二进制数组的相互转换,对C# 二进制数组与结构体的互转方法感兴趣的朋友一起看看吧

本文将告诉大家在 dotnet 里面的二进制基础处理知识,如何在 C# 里面将结构体数组和二进制数组进行相互转换的简单方法

尽管本文属于基础入门的知识,但是在阅读之前还请自行了解 C# 里面的结构体内存布局知识

本文将和大家介绍 MemoryMarshal 辅助类,通过这个辅助类用来实现结构体数组和二进制数组的相互转换

先演示如何从结构体数组和二进制数组的相互转换。准确来说是 Span 之间的相互转换,而不是真的转换为数组,只是 Span 的行为表现和数组十分相似

为了方便代码演示,我定义了一个 Foo1 的结构体,本文的全部代码都可以在本文末尾找到下载方法

struct Foo1
{
    public int A { get; set; }
    public int B { get; set; }
    public int C { get; set; }
}

先创建出一个 Foo1 结构体数组,为了方便演示我还给 Foo1 的各个属性分别赋值,如以下代码

        var foo1 = new Foo1()
        {
            A = 1,
            B = 2,
            C = 3,
        };
        var foo1Array = new Foo1[] { foo1 };

拿到 Foo1 的数组之后,可以非常方便转换为 Span 类型,只需要调用 foo1Array.AsSpan() 即可。接下来将 Foo1 数组转化在二进制数组,准确来说是 Span<byte> 类型,代码如下

        Span<byte> foo1ByteSpan = MemoryMarshal.AsBytes(foo1Array.AsSpan());

此时编写一个辅助方法,将 foo1ByteSpan 的内容输出到控制台,方便让大家看到这个 foo1ByteSpan 对象就确实是 Foo1 结构体的内存空间的二进制内容

        Log(foo1ByteSpan); // 01 00 00 00 02 00 00 00 03 00 00 00
    private static void Log(Span<byte> byteSpan)
    {
        var stringBuilder = new StringBuilder();
        foreach (var b in byteSpan)
        {
            stringBuilder.Append(b.ToString("X2"));
            stringBuilder.Append(' ');
        }
        Console.WriteLine(stringBuilder.ToString());
    }

可以看到以上输出的 01 02 03 就是对应 Foo1 结构体的 A 和 B 和 C 属性的值。本文这里没有对 Foo1 结构体进行固定布局等,这一点不够严谨,也就是说我只能和大家保证一定出现 Foo1 结构体的 A 和 B 和 C 属性的值,但是不能保证这些值出现的顺序。如果不了解这部分的知识,还请自行查阅 dotnet 里面的结构体的内存布局优化和内存对齐

接下来开始证明本文以上拿到的 foo1ByteSpan 和 foo1Array 指向相同的一片内存地址空间,也就是对 foo1Array 或 foo1ByteSpan 的内存修改,都会相互影响

先修改 foo1Array 里面的内容,比如修改一个属性的内容,如以下代码

        foo1Array[0].C = 5;
        Log(foo1ByteSpan); // 01 00 00 00 02 00 00 00 05 00 00 00

可以看到修改了 C 属性之后,打印出的 foo1ByteSpan 也更改了

再尝试修改 foo1ByteSpan 的内容,看看是否也能反过来影响到 foo1Array 对象

        foo1ByteSpan[0] = 6;
        Console.WriteLine(foo1Array[0].A); // 6
        var foo1Span = MemoryMarshal.Cast<byte, Foo1>(foo1ByteSpan);
        Console.WriteLine(foo1Span[0].A); // 6

通过以上代码即可证明了 foo1ByteSpan 和 foo1Array 指向相同的一片内存地址空间,也就是 MemoryMarshal.Cast 和 MemoryMarshal.AsBytes 不是重新申请一片内存空间存放数组内容,而是仅仅编写的代码上的魔法,内存都是相同的一片空间。如此减少了内存空间转换拷贝,可以极大的提升性能,同时也兼顾了安全性

通过 MemoryMarshal.Cast 方法,不仅可以支持结构体和 byte 之间的转换,也可以进行结构体之间的转换,比如再定义一个 Foo2 类型,这个 Foo2 类型和 Foo1 类型有相同的属性只是类型不相同而已,试试使用以下代码进行相互转换

        var foo2Span = MemoryMarshal.Cast<Foo1, Foo2>(foo1Span);
        Console.WriteLine(foo2Span[0].A); // 6
        Console.WriteLine(foo2Span[0].B); // 2
        Console.WriteLine(foo2Span[0].C); // 5
struct Foo2
{
    public int A { get; set; }
    public int B { get; set; }
    public int C { get; set; }
}

可以看到通过 MemoryMarshal.Cast 是可以实现多个结构体之间的直接转换的,且没有重新在堆上重新开辟数组空间

但是本文以上的代码是不严谨的,以上代码没有固定 Foo1 结构体和 Foo2 结构体的内存布局,以上的代码只是用来告诉大家 MemoryMarshal.Cast 的用法,而不是推荐大家在正式的项目跟随我这么写。如果在正式项目里面,需要确保多个结构体之间的内存布局相同或者是在各个情况下的直接内存转换是符合预期的才能这么做

本文的代码放在github 和 gitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 6bd28ceca1e9b73bfda270f9a3a3bddd7b8ebcc4

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 6bd28ceca1e9b73bfda270f9a3a3bddd7b8ebcc4

获取代码之后,进入 HallehuwearjewhoQedelqarnalar 文件夹

到此这篇关于C# 二进制数组与结构体的互转的文章就介绍到这了,更多相关C# 二进制数组与结构体互转内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解C#中yield关键字的用法

    详解C#中yield关键字的用法

    yield 关键字的用途是把指令推迟到程序实际需要的时候再执行,这个特性允许我们更细致地控制集合每个元素产生的时机,那么下面就一起来看下怎么用 yield 关键字吧
    2023-07-07
  • C#利用VS中插件打包并发布winfrom程序

    C#利用VS中插件打包并发布winfrom程序

    这篇文章主要为大家详细介绍了C#利用VS中插件打包并发布winfrom程序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-08-08
  • WPF数据绑定时出现StringFormat失效的原因和解决方法

    WPF数据绑定时出现StringFormat失效的原因和解决方法

    在数据绑定过程中,我们经常会使用StringFormat对要显示的数据进行格式化,以便获得更为直观的展示效果,但在某些情况下格式化操作并未生效,所以本文介绍了WPF数据绑定时出现StringFormat失效的原因和解决方法,需要的朋友可以参考下
    2024-12-12
  • C#中调整图像大小的步骤详解

    C#中调整图像大小的步骤详解

    这篇文章主要介绍了C#中调整图像大小的步骤详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-05-05
  • c# 两种发送邮件的方法

    c# 两种发送邮件的方法

    这篇文章主要介绍了c# 两种发送邮件的方法,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下
    2021-04-04
  • C#处理TCP数据的方法详解

    C#处理TCP数据的方法详解

    Tcp是一个面向连接的流数据传输协议,用人话说就是传输是一个已经建立好连接的管道,数据都在管道里像流水一样流淌到对端,那么数据必然存在几个问题,比如数据如何持续的读取,数据包的边界等,本文给大家介绍了C#处理TCP数据的方法,需要的朋友可以参考下
    2024-06-06
  • c# 使用Json.NET实现json序列化

    c# 使用Json.NET实现json序列化

    这篇文章主要介绍了详解C#中的JSON序列化方法,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下
    2021-05-05
  • C#面向对象实现图书管理系统

    C#面向对象实现图书管理系统

    这篇文章主要为大家详细介绍了C#面向对象实现图书管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • 使用C#实现在Excel中创建和配置数据透视表

    使用C#实现在Excel中创建和配置数据透视表

    在数据分析和业务报告场景中,数据透视表(Pivot Table)是一种强大的数据汇总工具,本文将介绍如何使用 C# 在 Excel 工作表中创建和配置数据透视表,感兴趣的小伙伴可以了解下
    2026-03-03
  • 使用快捷键在Unity中快速锁定和解锁Inspector右上角的锁功能

    使用快捷键在Unity中快速锁定和解锁Inspector右上角的锁功能

    这篇文章主要为大家介绍了使用快捷键在Unity中快速锁定和解锁Inspector右上角的锁功能详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-08-08

最新评论