在C#语言中struct结构体和class之间的区别主要是值类型和引用类型的区别,但实际上如果使用不当是非常要命的。从Win32时代过来的人对于struct一点不感觉陌生,但是却反而忽略了一些基本问题。我们知道C#在涉及到本地代码的地方大量使用了struct,很大程度上是为了移植代码的需要。很多时候,感觉结构比较简单的类改为struct可能会提高性能,但这种感觉在绝大多数情况下其实是错误的。那么我们自己在编写代码的时候究竟在什么情况下适合定义struct而不是class呢?

选用struct的原则

通过阅读微软的技术文章Choosing Between Class and Struct,可以了解到选择使用struct的一些准则。

考虑 定义struct而非class,如果类型的实例很小而且通常存活期都很短或者一般都嵌入到其它对象中使用

避免 定义struct除非类型满足以下全部特征:

  • 逻辑上表达了一个单一值,类似基本数据类型(int, double)
  • 实例大小低于16字节
  • 不可改变
  • 不会被频繁装箱

个人总结了一些使用场景和注意的地方。

  • 对于初学者或者一般情况,请使用class不要考虑struct。当程序需要考虑性能而进行优化的阶段再考虑struct问题
  • 定义struct时,尽量作为私有类型或内部类型,不要公开
  • struct的属性不要定义公开的set方法,也就是不可改变
  • 使用struct管理非托管资源时,定义Free方法,使用时一定要在恰当时机调用Free。千万不要想着去实现IDisposable接口。如果觉得不安全,那就改用class吧!
  • 如果需要调用本地代码而迫不得已,才可以无视其它原则而选用struct

struct的性能

选用struct可以在一些特定条件下改善程序性能,但请注意,没有“银弹”能够在所有情况下解决所有问题。

struct一般用于一些结构简单,可以用单一值概念描述的类型。同时,类型的存活期应该不会太长。struct无需创建即可使用,也没有垃圾回收问题。struct压根就不在GC堆内存中分配,而是直接在栈内存中分配。在使用struct时都会复制到当前栈内存中,就像其它值类型一样。以上这些特性只能说和class在使用上会有差异,需要注意。但说不上是优点还是缺点,取决于用法和具体情况。另外,struct不存在并发竞争问题,多线程安全,这应该算是优点了。

一种已知情况可以用struct来优化程序,就是struct类型的数组(注意是数组不是List,至于基于哈希的集合不好说)。struct数组在物理上一定是一个连续的内存块。如果是引用类型,则物理上一般是分配指针来指向引用的实例,此时数组的内存块不能涵盖所有要访问的数据。而struct数组在这种情况下所有会用到的数据都在数组的物理内存之中包含,可以直接访问到,无需通过GC堆内存的对象引用来反复的间接查找。同时,如果实例数量非常多时,使用struct数组还能避免大量分散在GC堆中的对象实例,从而减轻GC压力。这里理想化的认为struct的定义中所有字段都是值类型的,不包含string等引用类型。

此时,对struct数组中的下标访问不会造成复制(List的下标访问则会),直接内存定位效率很高。

int id = structArray[i].Id;

注意,struct字段不可变会很有帮助,如果需要修改字段内容,通过ref方法。
定义:

public static void SetId(ref structType target, int value)
{
target.Id = value;
}

使用:

SetId(ref structArray[i], );

实际上很多情况下,struct反而会拖慢我们的程序。由于值类型在使用上的复制特性,定义一个庞大的struct在绝大多数情况下性能会比引用类型要糟糕。因为每次使用到struct时都会在栈中复制一份新实例,复制来复制去的,如果struct的定义的字段比较多占用很多字节的话,复制的成本就会很高。这也是为什么微软给出的准则中有一条:“当类型定义大于16字节时不要选用struct”。

struct是不可变的!

首先,从逻辑上,一个struct描述了一个单一值,struct的所有公开的属性、字段都应该是用于获取这个单一值的一些特征的,这从逻辑上就杜绝了可赋值的属性这样的定义。

其次,由于struct是值类型,分配在栈内存中或者是拥有struct类型的引用类型对象中,任何时候对struct的访问都会访问原始struct的副本,因此对struct属性的修改实际上是在修改原始struct的副本。除非你将修改后的struct实例重新赋值回去,否则原始struct是不会改变。这一特性同样适用于函数方法的参数是struct的情况。

当然,要直接改变原始struct也是有办法的,那就是使用ref类型的的方法参数来直接改变原始值。但这就需要定义一个专门的方法,通过struct的属性来访问时仍然会有上述问题。

用struct管理本地代码

用struct管理本地代码时,注意定义释放方法,而使用时要在恰当时机去明确调用释放方法。

struct没有明确的无参构造方法,也没有析构方法。这是因为struct本身就是一份栈内存,无需new新的实例,也无需去释放。

但如果struct内部使用了本地资源,这时本地资源的释放就成了问题。对于object的class类型,我们可以定义实现IDisposable接口,在使用时用using代码块来创建实例。但是对于struct来说,千万不要。因为在using的时候使用的是struct的副本,而内存中可能存在很多很多struct的副本。这种情况下,Dispose的逻辑应当非常可靠才能避免重复释放的问题。

实际上,用struct来管理本地资源的情况一定要将struct定义为私有或内部,作为一个公开类型的内部实现。这样可以保证所有使用的实例都能够被干净释放,避免内存泄漏。

C#语言struct结构体适用场景和注意事项的更多相关文章

  1. C语言 Struct 结构体在 Java 中的体现

    大一整个学期完成了 C 语言的学习,大二就进入了Java 的学习. 和C语言一样,我们都会尝试写一个小小的学生管理系统什么的,学习过 C 语言同学知道,在管理系统中 Struct 结构体是个很好用的东 ...

  2. 将c语言的结构体定义变成对应的golang语言的结构体定义,并将golang语言结构体变量的指针传递给c语言,cast C struct to Go struct

    https://groups.google.com/forum/#!topic/golang-nuts/JkvR4dQy9t4 https://golang.org/misc/cgo/gmp/gmp. ...

  3. go struct结构体

    struct结构体 用来自定义复杂数据结构 struct里面可以包含多个字段(属性),字段可以是任意类型 struct类型可以定义方法,注意和函数的区分 struct类型是值类型 struct类型可以 ...

  4. C语言、结构体 定义

    C语言允许用户自己建立由 不同类型数据组成的组合型数据结构 成为结构体. struct Student { int num; //学号 ]; //姓名为字符串 char sex; //性别为字符型 i ...

  5. C语言中结构体赋值问题的讨论

    今天帮师姐调一个程序的BUG,师姐的程序中有个结构体直接赋值的语句,在我印象中结构体好像是不能直接赋值的,正如数组不能直接赋值那样,我怀疑这个地方有问题,但最后证明并不是这个问题.那么就总结一下C语言 ...

  6. ios开发中的C语言学习—— 结构体简介

    在开发过程中,经常会需要处理一组不同类型的数据,比如学生的个人信息,由姓名.年龄.性别.身高等组成,因为这些数据是由不同数据类型组成的,因此不能用数组表示,对于不同数据类型的一组数据,可以采用结构体来 ...

  7. 01.C语言关于结构体的学习笔记

    我对于学习的C语言的结构体做一个小的学习总结,总结如下: 结构体:structure 结构体是一种用户自己建立的数据类型,由不同类型数据组成的组合型的数据结构.在其他高级语言中称为记录(record) ...

  8. C语言中结构体对齐问题

    C语言中结构体对齐问题 收藏 关于C语言中的结构体对齐问题 1,比如: struct{short a1;short a2;short a3;}A;struct{long a1;short a2;}B; ...

  9. C语言之结构体

    结构体类型 C语言中还有一种类型叫做结构体类型,它是可以保存不同类型数据并且可以把这些不同类型的数据当做一个整体来管理的类型 1).结构体的定义 语法: struct 结构体名{ 成员列表; };   ...

随机推荐

  1. 最小生成树(kruskal算法)

    首先明确三个概念: 1.最小生成树的特点? 答:假设某个连通网络由n个顶点组成,则其生成树必含n个顶点和n-1条边,而最小生成树的n-1条有个要求:总和最小. 2.并查集的运用? 答:在该问题中,首先 ...

  2. winform 以不规则图形背景显示窗体

    一:创建一个winform窗体,把BackgroundImage引入一个不规则的图片,设置属性BackgroundImageLayout为Stretch 二:主要代码 using System; us ...

  3. 微信小程序开发学习(二)

    一些官方API 总结了一些官方API,便于之后有用时针对性查找(发现官方给了好多好用的API)官方API文档 基础 wx.canIUse:判断小程序的API,回调,参数,组件等是否在当前版本可用,返回 ...

  4. 将mysql中的一张表中的一个字段数据根据条件导入另一张表中

    添加字段:alter table matInformation add facid varchar(99)  default ''; 导入数据:update matInformation m set ...

  5. mathJax基础语法-0基础开始,(这是网上抄来的如果有权限和版权问题联系本人处理,仅供学术参考)

  6. FreeRTOS如何结束和重新启动调度程序

    大多数主机或桌面系统(比如Linux,Mac或Windows)都有一个正常的用例,你可以在早上启动操作系统,然后在晚上关闭它,然后你就离开机器.嵌入式系统是不同的:他们没有参加,他们应该“永远”运行. ...

  7. 高可用Redis(十一):使用redis-trib.rb工具搭建集群

    环境说明: 两台虚拟机,IP地址分别为:192.168.81.100和192.168.81.101 虚拟机系统为:CentOS 7.5 Redis为yum安装,版本为3.2 系统环境:关闭firewa ...

  8. TypeError: can only concatenate str (not "int") to str解决方式

    使用format函数解决问题 for page in range(1,pagebox+1): url = "https://www.dd373.com/s/rbg22w-x9kjbs-wwf ...

  9. 怎么在PDF上进行文字修改

    文件相信大家不论是工作中还是在学习生活中都会有遇到,有时候我们会遇到PDF文件中的文字有时候会有错误的时候,这个时候就需要对修改PDF文件上的文字,那么具体要怎么做呢,PDF文件需要借助软件才可以编辑 ...

  10. Redis实战 - 5事务:multi、exec和watch

    介绍 redis的目标的是: 简洁,高效,由于事务本身就是一个很复杂的东西,所有我们不能把事务做的太复杂. DISCARD 取消事务,放弃执行事务块内的所有命令. EXEC 执行所有事务块内的命令. ...