本文将告诉大家在 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. Csharp学习Linq

    Linq的学习 这里继续使用之前文章创建的学生类,首先简单介绍一下linq的使用. Student.cs public class Student { public int Id { get; set ...

  2. 记录--组件库的 Table 组件表头表体是如何实现同步滚动?

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前言 在使用 Vue 3 组件库 Naive UI 的数据表格组件 DataTable 时碰到的问题,NaiveUI 的数据表格组件 Da ...

  3. RowHammer 攻击:内存的隐形威胁

    今天看了一篇 IT 之家关于 AMD 处理器受 RowHammer 内存攻击影响的报道,心血来潮了解了一下 RowHammer 攻击的原理,把了解到的知识记录下来. RowHammer 攻击是一种相对 ...

  4. KingbaseES V8R3集群备份恢复案例之--- timingbackup备份

    案例说明: KingbaseES V8R3集群自带了timingbackup.sh的脚本,可以通过一个脚本执行逻辑和物理备份,逻辑备份采用sys_dump,物理备份适用sys_basebackup,本 ...

  5. 探索多种数据格式:JSON、YAML、XML、CSV等数据格式详解与比较

    1. 数据格式介绍 数据格式是用于组织和存储数据的规范化结构,不同的数据格式适用于不同的场景.常见的数据格式包括JSON.YAML.XML.CSV等. 数据可视化 | 一个覆盖广泛主题工具的高效在线平 ...

  6. 线段树(SegmentTree)

    对于数组应用于区间染色实现为On,而线段树是O(logn) 什么是线段树:对于一个二叉树,每一个节点存储的是一个线段或是一个区间相应的信息. 查询 更新 #pragma once #include & ...

  7. [Linux]将ArchLinux安装到U盘

    将ArchLinux安装到U盘 几个月前入门Arch的时候上网搜了不少安装教程,同时由于当时硬盘空间不太够于是就打算安装到U盘上,也因此踩了不少坑. 但128G的U盘都拿来装Arch的话未免也太浪费了 ...

  8. #欧拉回路,状压dp,Floyd#洛谷 6085 [JSOI2013]吃货 JYY

    题目传送门 分析 先用Floyd求出两点间的最短距离,包含必经边的欧拉回路, 先考虑欧拉回路的性质就是度数为偶数且连通,那如果有偶数个奇点可以两两配对. 设 \(g[S]\) 表示选择 \(S\) 中 ...

  9. #树上启发式合并,位运算#CF570D Tree Requests

    题目 给定一个以 \(1\) 为根的 \(n\) 个结点的树,每个点上有一个字母\((a-z)\),每个点的深度定义为该节点到 \(1\) 号结点路径上的点数. 每次询问 \(a, b\) 查询以 \ ...

  10. SQL 日期处理和视图创建:常见数据类型、示例查询和防范 SQL 注入方法

    SQL处理日期 在数据库操作中,处理日期是一个关键的方面.确保插入的日期格式与数据库中日期列的格式匹配至关重要.以下是一些常见的SQL日期数据类型和处理方法. SQL日期数据类型 MySQL日期数据类 ...