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#实现几种数据库的大数据批量插入

    c#实现几种数据库的大数据批量插入

    这篇文章主要介绍了c#实现几种数据库的大数据批量插入,主要包括SqlServer、Oracle、SQLite和MySQL,有兴趣的可以了解一下。
    2017-01-01
  • 深入反射生成数组的详解

    深入反射生成数组的详解

    本篇文章是对反射生成数组进行了详细的分析介绍,需要的朋友参考下
    2013-06-06
  • WPF字体或内容模糊的解决方法

    WPF字体或内容模糊的解决方法

    WPF下开发的程序字体模糊,这个问题或许大家都有遇到过,为了解决WPF字体模糊,查阅了各种资料,结果偶然发现是自己疏忽了一些细节造成的,具体是什么细节呢,通过下面的这篇文章来一起看看吧,有需要的朋友们可以参考借鉴。
    2016-12-12
  • C#实现六大设计原则之接口隔离原则

    C#实现六大设计原则之接口隔离原则

    这篇文章介绍了C#实现六大设计原则之接口隔离原则的方法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-02-02
  • unity实现摄像头跟随

    unity实现摄像头跟随

    把这个脚本赋给你的摄像机,再把游戏角色赋给character变量,之后就能实现摄像机平滑的跟随player在地球的任一角落了。有需要的小伙伴可以参考下。
    2015-03-03
  • C#中if语句使用概述

    C#中if语句使用概述

    这里介绍C#使用if语句,C#使用if语句中的表达式必须放在一对圆括号中。除此之外,表达式必须是布尔表达式
    2014-03-03
  • VS.net VSS时,编译报错:未能向文件“.csproj.FileListAbsolute.txt”写入命令行 对路径 的访问被拒绝。

    VS.net VSS时,编译报错:未能向文件“.csproj.FileListAbsolute.txt”写入命令行 对路

    在VSS上把项目的Bin和Obj目录删除,然后重新取出项目,编译成功。
    2009-06-06
  • C#实现将文件转换为XML的方法

    C#实现将文件转换为XML的方法

    这篇文章主要介绍了C#实现将文件转换为XML的方法,实例分析了office文件与xml的相互转换技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-12-12
  • C# 程序通用结构

    C# 程序通用结构

    这篇文章主要介绍了C# 程序通用结构,C# 程序由一个或多个文件组成。 每个文件均包含零个或多个命名空间。 一个命名空间包含类、结构、接口、枚举、委托等类型或其他命名空间,具体相关内容请需要的小伙伴参考下面文章的详细内容<BR>
    2021-12-12
  • 在Winform程序中使用Spire.Pdf实现页面添加印章功能的实现

    在Winform程序中使用Spire.Pdf实现页面添加印章功能的实现

    这篇文章主要介绍了在Winform程序中使用Spire.Pdf实现页面添加印章功能的实现,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09

最新评论