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 的用法,而不是推荐大家在正式的项目跟随我这么写。如果在正式项目里面,需要确保多个结构体之间的内存布局相同或者是在各个情况下的直接内存转换是符合预期的才能这么做
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 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# 二进制数组与结构体的互转的更多相关文章
- 全面总结sizeof的用法(定义、语法、指针变量、数组、结构体、类、联合体、位域位段)
一.前言 编译环境是vs2010(32位). <span style="font-size:18px;">#include<iostream> #inclu ...
- 5、数组&字符串&结构体&共用体&枚举
程序中内存从哪里来 三种内存来源:栈(stack).堆(heap).数据区(.date): 栈(stack) 运行自动分配.自动回收,不需要程序员手工干预: 栈内存可以反复使用: 栈反复使用后,程序不 ...
- 【原创】只学到二维数组和结构体,不用链表也能写一个C贪食蛇?(四)
全系列Index: [原创]只学到二维数组和结构体,不用链表也能写一个C贪食蛇?(一) [原创]只学到二维数组和结构体,不用链表也能写一个C贪食蛇?(二) [原创]只学到二维数组和结构体,不用链表也能 ...
- C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com
原文:C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | IT宅.com C语言语法笔记 – 高级用法 指针数组 指针的指针 二维数组指针 结构体指针 链表 | I ...
- Delphi - 数组和结构体
技术交流,DH讲解. 记得很早之前我就说过,数组和结构体在内存中其实一样的,他们都是连续分布的.例如: ? 1 2 3 4 TMyStruct = record A,B,C:Integer; en ...
- matlab学习笔记12_3串联结构体,按属性创建含有元胞数组的结构体,filenames,isfield,isstruct,orderfields
一起来学matlab-matlab学习笔记12 12_3 结构体 串联结构体,按属性创建含有元胞数组的结构体,filenames,isfield,isstruct,orderfields 觉得有用的话 ...
- C语言一维数组、二维数组、结构体的初始化
C语言数组的初始化表示方法 一.C语言一维数组初始化: (1)在定义数组时对数组元素赋以初值.如: static int a[10]={0,1,2,3,4,5,6,7,8,9}; 经过上面的定义和初始 ...
- c#---部分;把数组或者结构体存入集合里,然后再从集合中取出之后,输出;foreach既可以用到提取数组重点额数据,也可以提取集合中的数据(前提是集合中的元素是相同数据类型)
1.输入班级人数,统计每个人的姓名,性别,年龄:集合与数组 //Console.Write("请输入班级人数:"); //int a = int.Parse(Console.Rea ...
- QT: QByteArray储存二进制数据(包括结构体,自定义QT对象)
因为利用QByteArray可以很方便的利用其API对内存数据进行访问和修改, 构建数据库blob字段时必不可少; 那如何向blob内写入自定义的结构体和类 1. 利用memcpy拷贝内存数据 / ...
- c语言学习之基础知识点介绍(十七):写入读取结构体、数组、结构体数组
一.结构体的写入和读取 //写入结构体 FILE *fp = fopen("/Users/ios/Desktop/1.data", "w"); if (fp) ...
随机推荐
- 工作记录:Vue项目中使用WebSocket通讯
WebSocket介绍 以下内容摘自维基百科: WebSocket是一种网络传输协议,可在单个TCP连接上进行全双工通信,位于OSI模型的应用层.WebSocket协议在2011年由IETF标准化为R ...
- 记录--uni-app中安卓包检查更新、新版本下载、下载进度条显示功能实现
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 需求描述 如果想要做一个app的话,可以有很多种选择方案,uni-app是其中的一个性价比高一些(坑多一些)的方案.本文记录一下,uni- ...
- 记录--可视化大屏-用threejs撸一个3d中国地图
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 不想看繁琐步骤的,可以直接去github下载项目,如果可以顺便来个star哈哈 本项目使用vue-cli创建,但不影响使用,主要绘制都已封 ...
- 2022北航软件研究生入学考试991考试大纲-数据结构与C
991"数据结构与C语言程序设计"考试大纲 "数据结构与C语言程序设计"考试内容包括"数据结构"与"C语言程序设计"两门 ...
- Go | 浅谈包管理模式
任何一门编程语言都离不开对各种工具包的使用,工具包的管理就显得异常重要了.Go 的包管理方式是逐渐演进的,本文介绍Go语言的两种包管理模式. GOPATH模式引包(不推荐) 在 1.5 版本之前,所有 ...
- 【UE插件DTRabbitMQ】 虚幻引擎蓝图连接RabbitMQ服务器使用插件说明
本插件可以使用蓝图连接 RabbitMQ服务器,并推送或者监听消息. 下载地址地址在文章最后. 1. 节点说明 Create RabbitMQ Client - 创建RabbitMQ客户端对象 创建一 ...
- KingbaseES Json 系列四:Json数据操作函数二
KingbaseES Json 系列四--Json数据操作函数二(JSONB_PRETTY,JSONB_STRIP_NULLS,JSON_OBJECTAGG,JSON_EQUAL,JSON_EXIST ...
- 一款比Typora更简洁优雅的Markdown编辑器神器(完全开源免费)
前言 自从Typora收费以后经常有朋友会问有没有一个好用.简洁.免费的Markdown编辑器推荐的,今天大姚给大家分享一款比Typora更简洁优雅的.完全开源免费(MIT License)Markd ...
- wordpress搭建个人博客(阿里云服务器、宝塔面板、Elementor)
wordpress搭建个人博客(阿里云服务器.宝塔面板.Elementor) 笔记根据bilibili比得汪视频教程整理 https://www.bilibili.com/video/BV1Vg411 ...
- ET介绍——分布式Actor模型
Actor模型 Actor介绍 在讨论Actor模型之前先要讨论下ET的架构,游戏服务器为了利用多核一般有两种架构,单线程多进程跟单进程多线程架构.两种架构本质上其实区别不大,因为游戏逻辑开发都需要用 ...