本文将告诉大家在 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 里面的结构体的内存布局优化和内存对齐

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

先修改 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

通过以上代码即可证明了 foo1ByteSpanfoo1Array 指向相同的一片内存地址空间,也就是 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 的用法,而不是推荐大家在正式的项目跟随我这么写。如果在正式项目里面,需要确保多个结构体之间的内存布局相同或者是在各个情况下的直接内存转换是符合预期的才能这么做

本文的代码放在githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 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 文件夹

更多 dotnet 基础知识相关博客,请参阅我的 博客导航

C# 二进制数组与结构体的互转的更多相关文章

  1. 全面总结sizeof的用法(定义、语法、指针变量、数组、结构体、类、联合体、位域位段)

    一.前言 编译环境是vs2010(32位). <span style="font-size:18px;">#include<iostream> #inclu ...

  2. 5、数组&字符串&结构体&共用体&枚举

    程序中内存从哪里来 三种内存来源:栈(stack).堆(heap).数据区(.date): 栈(stack) 运行自动分配.自动回收,不需要程序员手工干预: 栈内存可以反复使用: 栈反复使用后,程序不 ...

  3. 【原创】只学到二维数组和结构体,不用链表也能写一个C贪食蛇?(四)

    全系列Index: [原创]只学到二维数组和结构体,不用链表也能写一个C贪食蛇?(一) [原创]只学到二维数组和结构体,不用链表也能写一个C贪食蛇?(二) [原创]只学到二维数组和结构体,不用链表也能 ...

  4. C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com

    原文:C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | I ...

  5. Delphi - 数组和结构体

    技术交流,DH讲解. 记得很早之前我就说过,数组和结构体在内存中其实一样的,他们都是连续分布的.例如: ? 1 2 3 4 TMyStruct = record   A,B,C:Integer; en ...

  6. matlab学习笔记12_3串联结构体,按属性创建含有元胞数组的结构体,filenames,isfield,isstruct,orderfields

    一起来学matlab-matlab学习笔记12 12_3 结构体 串联结构体,按属性创建含有元胞数组的结构体,filenames,isfield,isstruct,orderfields 觉得有用的话 ...

  7. C语言一维数组、二维数组、结构体的初始化

    C语言数组的初始化表示方法 一.C语言一维数组初始化: (1)在定义数组时对数组元素赋以初值.如: static int a[10]={0,1,2,3,4,5,6,7,8,9}; 经过上面的定义和初始 ...

  8. c#---部分;把数组或者结构体存入集合里,然后再从集合中取出之后,输出;foreach既可以用到提取数组重点额数据,也可以提取集合中的数据(前提是集合中的元素是相同数据类型)

    1.输入班级人数,统计每个人的姓名,性别,年龄:集合与数组 //Console.Write("请输入班级人数:"); //int a = int.Parse(Console.Rea ...

  9. QT: QByteArray储存二进制数据(包括结构体,自定义QT对象)

      因为利用QByteArray可以很方便的利用其API对内存数据进行访问和修改, 构建数据库blob字段时必不可少; 那如何向blob内写入自定义的结构体和类 1. 利用memcpy拷贝内存数据 / ...

  10. c语言学习之基础知识点介绍(十七):写入读取结构体、数组、结构体数组

    一.结构体的写入和读取 //写入结构体 FILE *fp = fopen("/Users/ios/Desktop/1.data", "w"); if (fp) ...

随机推荐

  1. Vue中组件和插件有什么区别?

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.组件是什么 回顾以前对组件的定义: 组件就是把图形.非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue中每一个. ...

  2. FPGA中与时序有关的概念

    FPGA中与时序有关的概念 一.时序分析 时序分析是对整个时序进行分析的操作.作用就是了解设计的时序性能,一般而言就是最大时钟频率.时钟频率越高,设计的工作速度越快(忽略设计实现方法的不同导致的效率提 ...

  3. RepVGG:VGG,永远的神! | CVPR 2021

    RepVGG将训练推理网络结构进行独立设计,在训练时使用高精度的多分支网络学习权值,在推理时使用低延迟的单分支网络,然后通过结构重参数化将多分支网络的权值转移到单分支网络.RepVGG性能达到了SOT ...

  4. RelationNet:学习目标间关系来增强特征以及去除NMS | CVPR 2018

    论文基于NLP的注意力机制提出了目标关系模块,通过与其它目标的比对增强当前目标的特征,而且还可以代替NMS进行端到端的重复结果去除,思想十分新颖,效果也不错   来源:晓飞的算法工程笔记 公众号 论文 ...

  5. 【Java】这条语句一共创建了多少个对象:String s="a"+"b"+"c"+"d";

    String s1 = "a"; String s2 = s1 + "b"; String s3 = "a" + "b" ...

  6. C++ 简单实现shared_ptr

    共享指针 管理指针的存储,提供有限的垃圾回收工具,并可能与其他对象共享该管理. shared_ptr类型的对象都能够获得指针的所有权并共享该所有权:一旦它们获得所有权,当最后一个所有者释放该所有权时, ...

  7. MySQL创建和操纵表

    表创建基础 CREATE TABLE customers ( cust_id int NOT NULL AUTO_INCREMENT , cust_name char(50) NOT NULL , c ...

  8. WPF实现树形表格控件(TreeListView)

    前言 本文将探讨如何利用WPF框架实现树形表格控件,该控件不仅能够有效地展示复杂的层级数据,还能够提供丰富的个性化定制选项.我们将介绍如何使用WPF提供的控件.模板.布局.数据绑定等技术来构建这样一个 ...

  9. Seaborn时间线图和热图

    lineplot() 绘制与时间相关性的线图. sns.lineplot( x=None, y=None, hue=None, size=None, style=None, data=None, pa ...

  10. C语言 05 变量与常量

    变量 变量就像在数学中学习的 x,y 一样,可以直接声明一个变量,并利用这些变量进行基本的运算,声明变量的格式为: 数据类型 变量名称 = 初始值;(其中初始值可以不用在定义变量时设定) = 是赋值操 ...