需要知道关于struct的一些事情
前言
重构代码的时候,会遇到长参数的方法,此时就需要使用“引入参数对象”来封装这些参数。大多数时候,这些参数都是简单类型,而且所有参数的值占用的空间也不是非常的大,此时使用对象真的好吗?对象的特性是堆上分配、地址引用,看似很好,但是分配一个对象需要的一些额外成员(类型对象指针、同步块索引)以及需要对基类型进行计算,这些开销值得吗?如果你感觉不值得,那结构体(struct)就是你需要找的答案了。
定义描述
1、 结构体(Struct),值类型,继承自System.ValueType,在线程栈上分配内存,每一个实例都有自己的内存地址,不同实例互不影响。
2、 由于在栈上分配内存,所以实例不受垃圾回收器的控制,缓解了托管堆中的压力,减少了应用程序在其生存期内需要进行的垃圾回收次数,从而也提高了程序的性能。
3、 不能继承也不能是基类型,是sealed类型的,但是可以实现接口。
有些人会感觉不能继承基类有点“Low”,从面向对象的观点来说是有点。但是从结构体的特性上看,其实一点都不“Low”。如果能继承基类,按照继承规则,当获取实例的时候,就需要对所有的基类进行计算,这是有开销的。作为一个值类型,是为了给你提供轻便使用的,这些开销,绝对不接受。
4、结构体是没有null这种初始状态的,所以不要用null来对结构体的实例来进行判断。如果你想通过null来判断结构体是否已经实例化,可以使用可空类型Nullable。
不可变
1、建议把struct定义为和基元类型一样是不可变的,因为不可变可以减少一些不必要的问题。所谓的不可变就是不要让struct的实例的成员在struct外部被修改,内部定义的方法是可以修改的。如果要修改一个struct的实例,就重新构造一个新的实例(可参考DateTime, 看看它的实现)。
如下:
public class Struct_1_4
{
public static void Run()
{
BroomCloset bc = new BroomCloset();
changeBroom(bc);// 修改为10
Console.WriteLine(bc.Broom);// 输出0
} private static void changeBroom(BroomCloset bc)
{
bc.Broom = ;
} struct BroomCloset
{
private readonly int _mop;
public BroomCloset(int mop)
{
_mop = mop;
Broom = ;
} public int Mop { get { return _mop; } }
public int Broom { get; set; }
}
}
通过方法修改了结构体的实例,但是最后并没有达到我们的预期,究其原因还是因为struct是按照值传递的。其实大家也知道只要把方法的参数改成添加ref关键字就可以了,这样就变成按照引用传递了。所谓的传引用只不过是把实例的地址作为参数传递过去进行计算罢了。.method private hidebysig static void changeBroom(valuetype StructAndClass.Struct_1_4/BroomCloset& bc) cil managed
声明和初始化
1、必须用new 初始化,如果仅仅是声明的话,可以不用new。如果不用new进行初始化,是不能调用属性或者方法的。其实公共字段也一样,如果不对字段进行初始化(赋值操作)也是不能使用的。
字段未赋值就使用,会提示字段不存在;实例未初始化就使用,提示实例不存在。
下面的实例代码展示了上面的描述,使用中一定要注意:
struct BroomCloset
{
public int Dustpan;
public int Broom { get; set; }
} public static void Run()
{
BroomCloset bc1; Console.WriteLine(bc1.Dustpan);// 报错:使用了可能未赋值的变量"Dustpan" bc1.Dustpan = ;
Console.WriteLine(bc1.Dustpan);// 输出12 bc1.Broom = ;// 报错:使用了未赋值的局部变量"bc1" bc1 = new BroomCloset();
bc1.Broom = ;
Console.WriteLine(bc1.Broom);// 输出12
}
struct 和class都可以使用new关键字进行创建实例,但是他们的内部执行方式却是不一致的。从IL中可以看出struct的new是调用initobj StructAndClass.Struct_1_4/BroomCloset
对struct内的成员进行初始化。
如果还是不好理解,可以定义一个带有构造函数的struct,你会发现,如果你在构造函数内不对所有成员变量进行初始化,就会报错。
struct Room
{
public int Window;
public Room(int window,int door)
{
Window = window;
Door = door;
}
public int Door { get; set; }
}
使用前考虑
虽然结构体不受垃圾回收器控制的这一特性,让它具有高性能的特质,但是如果整个系统中都用结构体来代替类,也是非常不合适的,以下情况需要考虑进去。
1、 由于值类型是按照值方式传递的,所以在传入方法参数或者返回方法返回值的时候,会造成字段复制,这一点是会造成性能损失的。
2、 值类型变量之间的赋值操作,由于是按值传递,所以会执行一次逐字段的复制操作。
3、 由于System.ValueType也继承自System.Object,所以如果方法的参数是object类型的,结构体是可以作为参数传递并使用的,此时就会出现装箱和拆箱的操作。
public static void Run()
{
BroomCloset bc2 = new BroomCloset();
showObj(bc2); // 装箱
}
private static void showObj(object obj)
{
Console.WriteLine("showObj");
if (obj is BroomCloset)
{
Console.WriteLine(((BroomCloset)obj).Dustpan); // 拆箱
}
}
使用时机
1、类型具有基元类型的行为,主要是不可变。
2、类型不需要从其他任何类型继承,也不会派生出其他任何类型。
3、类型的实例较小(Jeffery 建议16字节或者更小),这个需要自己把握一下。但是如果需要传递大数据(比如要传递一个数据库中的多条数据列表等等),还是不要使用结构体。
延伸一点
1、在使用方法的时候,如果是值类型,最好使用对应的值类型的重载方法,以减少装箱和拆箱操作带来的性能损失。
2、枚举的继承链是这样的,System.Enum -> System.ValueType -> System.Object, 所以按照辈分来说类是结构体的叔辈,结构体是枚举的叔辈。
3、使用dynamic可以简化语法,但是使用dynamic产生的额外开销也是不容忽视的。使用dynamic的时候,在运行时需要把Microsoft.CSharp.dll、System.dll、System.Core.dll加载到AppDomain中,加载这些程序集会产生额外的开销。如果程序中只是一、二个地方需要使用dynamic,使用传统的方法性能或许会更好。
需要知道关于struct的一些事情的更多相关文章
- linux驱动---字符设备的注册register_chrdev说起
首先我们在注册函数里面调用了register_chrdev(MEM_MAJOR,"mem",&memory_fops),向内核注册了一个字符设备. 第一个参数是主设备号,0 ...
- 每一个C#开发者必须知道的13件事情
1.开发流程 程序的Bug与瑕疵往往出现于开发流程当中.只要对工具善加利用,就有助于在你发布程序之前便将问题发现,或避开这些问题. 标准化代码书写 标准化代码书写可以使代码更加易于维护,尤其是在代码由 ...
- struct 和 class 不同点
在 C++ 里面 struct 和 class 没有本质的差别 仅仅是成员和继承方式的默认不同 struct 是 public class 是 private 我的个人建议是仅仅要须要实现成员函数的就 ...
- 为什么 把单一元素的数组放在一个struct的尾端问题
问题摘自<深度探究c++对象模型>: struct mumble { /* stuff */ char pc[ 1 ];};[sizeof(mumble)是一个字节 .pc则代表的是指向这 ...
- C#开发人员应该知道的13件事情
本文讲述了C#开发人员应该了解到的13件事情,希望对C#开发人员有所帮助. 1. 开发过程 开发过程是错误和缺陷开始的地方.使用工具可以帮助你在发布之后,解决掉一些问题. 编码标准 遵照编码标准可以编 ...
- C语言精要总结-内存地址对齐与struct大小判断篇
在笔试时,经常会遇到结构体大小的问题,实际就是在考内存地址对齐.在实际开发中,如果一个结构体会在内存中高频地分配创建,那么掌握内存地址对齐规则,通过简单地自定义对齐方式,或者调整结构体成员的顺序,可以 ...
- SEO是件贼有意思的事情 golang入坑系列
这两天迷上了SEO.真心看不起百度的竞价排名,但作为一个商业网站,赚钱是一件无可厚非的事情.只做活雷锋,没有大金主是做不长的.做完功课后,发现百度和google的SEO策略又不相同,几乎是无法通用.百 ...
- LOJ #6041. 事情的相似度
Description 人的一生不仅要靠自我奋斗,还要考虑到历史的行程. 历史的行程可以抽象成一个 01 串,作为一个年纪比较大的人,你希望从历史的行程中获得一些姿势. 你发现在历史的不同时刻,不断的 ...
- Golang 入门 : 结构体(struct)
Go 通过类型别名(alias types)和结构体的形式支持用户自定义类型,或者叫定制类型.试图表示一个现实世界中的实体. 结构体由一系列命名的元素组成,这些元素又被称为字段,每个字段都有一个名称和 ...
随机推荐
- Xcode Custom Shortcut
edit file "/Applications/Xcode.app/Contents/Frameworks/IDEKit.framework/Resources" add < ...
- sql 通过表名获取所有列名
因为要做数据迁移,也就是业务数据库的数据要迁移到历史数据库,这两个数据库理论上表结构是一样的,但因为时间原因,可能业务库升级了表结构,但历史库没有升级,且加字段的顺序不一样,导致 insert int ...
- C#实现MS-Office文档转Pdf(Word、Execel、PowerPoint、Visio、Project)
using System; using Microsoft.Office.Core; namespace Office { class Util { private Util() { } /// &l ...
- SikuliLibrary 库关键字注释
在 https://github.com/rainmanwy/robotframework-SikuliLibrary 看到rainmanwy 整理的SikuliLibrary库,非常适合工作需要, ...
- Centos7 密码重置
1.在grub启动页面,按e编辑启动选项 2.找到Linux 16的那一行,将ro改为rw init=/sysroot/bin/sh 3.按下 Control+x ,使用修改后的设置启动,出现命令行 ...
- SharePoint中的ASHX
<%@ Assembly Name="namespace, Version=1.0.0.0, Culture=neutral, PublicKeyToken=key" %&g ...
- EF for Firebird
今天用了Firebird,记录下怎么用,不然下次给忘记了 1.官网下载包 1.DDEXProvider-3.0.1.0.msi 2.FirebirdSql.Data.FirebirdClient-4. ...
- 基于Socket客户端局域网或广域网内共享同一短信猫收发短信的开发解决方案
可使同一网络(局域网或广域网)内众多客户端,共享一个短信猫设备短信服务器进行短信收发,短信服务器具备对客户端的管理功能. 下面是某市建设银行采用本短信二次开发平台时实施的系统方案图: 在该方案中,考虑 ...
- hibernate+mysql 自动生成数据库问题
Hibernate Entity类 表名注解大写时,在windows下mysql自动生成的表都为小写(不区分大小写),在linux下mysql自动生成区分大小写.导致数据库问题. 原因(window下 ...
- Backbone.js
Backbone.js是一套JavaScript框架與RESTful JSON的應用程式介面.也是一套大致上符合MVC架構的編程範型.Backbone.js以輕量為特色,只需依賴一套Javascrip ...