前言

重构代码的时候,会遇到长参数的方法,此时就需要使用“引入参数对象”来封装这些参数。大多数时候,这些参数都是简单类型,而且所有参数的值占用的空间也不是非常的大,此时使用对象真的好吗?对象的特性是堆上分配、地址引用,看似很好,但是分配一个对象需要的一些额外成员(类型对象指针、同步块索引)以及需要对基类型进行计算,这些开销值得吗?如果你感觉不值得,那结构体(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的一些事情的更多相关文章

  1. linux驱动---字符设备的注册register_chrdev说起

    首先我们在注册函数里面调用了register_chrdev(MEM_MAJOR,"mem",&memory_fops),向内核注册了一个字符设备. 第一个参数是主设备号,0 ...

  2. 每一个C#开发者必须知道的13件事情

    1.开发流程 程序的Bug与瑕疵往往出现于开发流程当中.只要对工具善加利用,就有助于在你发布程序之前便将问题发现,或避开这些问题. 标准化代码书写 标准化代码书写可以使代码更加易于维护,尤其是在代码由 ...

  3. struct 和 class 不同点

    在 C++ 里面 struct 和 class 没有本质的差别 仅仅是成员和继承方式的默认不同 struct 是 public class 是 private 我的个人建议是仅仅要须要实现成员函数的就 ...

  4. 为什么 把单一元素的数组放在一个struct的尾端问题

    问题摘自<深度探究c++对象模型>: struct mumble { /* stuff */ char pc[ 1 ];};[sizeof(mumble)是一个字节 .pc则代表的是指向这 ...

  5. C#开发人员应该知道的13件事情

    本文讲述了C#开发人员应该了解到的13件事情,希望对C#开发人员有所帮助. 1. 开发过程 开发过程是错误和缺陷开始的地方.使用工具可以帮助你在发布之后,解决掉一些问题. 编码标准 遵照编码标准可以编 ...

  6. C语言精要总结-内存地址对齐与struct大小判断篇

    在笔试时,经常会遇到结构体大小的问题,实际就是在考内存地址对齐.在实际开发中,如果一个结构体会在内存中高频地分配创建,那么掌握内存地址对齐规则,通过简单地自定义对齐方式,或者调整结构体成员的顺序,可以 ...

  7. SEO是件贼有意思的事情 golang入坑系列

    这两天迷上了SEO.真心看不起百度的竞价排名,但作为一个商业网站,赚钱是一件无可厚非的事情.只做活雷锋,没有大金主是做不长的.做完功课后,发现百度和google的SEO策略又不相同,几乎是无法通用.百 ...

  8. LOJ #6041. 事情的相似度

    Description 人的一生不仅要靠自我奋斗,还要考虑到历史的行程. 历史的行程可以抽象成一个 01 串,作为一个年纪比较大的人,你希望从历史的行程中获得一些姿势. 你发现在历史的不同时刻,不断的 ...

  9. Golang 入门 : 结构体(struct)

    Go 通过类型别名(alias types)和结构体的形式支持用户自定义类型,或者叫定制类型.试图表示一个现实世界中的实体. 结构体由一系列命名的元素组成,这些元素又被称为字段,每个字段都有一个名称和 ...

随机推荐

  1. 《VB语言程序设计(第3版)》总结

    我之前因学习昆仑通态的组态软件MCGS,用并学习过VB,还买了一本书<VB语言程序设计(第3版)>.现在在某公司实习,最近接触老的项目,又要用到VB.我就又把那本书大体看了一遍,并对其进行 ...

  2. My安卓知识5--百度地图api的使用,周边信息检索

    虽然查了很多资料,但是这个问题还是解决不了,不知道为什么检索城市内的相关信息能用,检索周边信息语句就是用不了.代码如下,第一段是检索保定市内的加油站,第二段是检索周边的加油站.centerToMyLo ...

  3. iOS Sonar 集成流程

    https://gold.xitu.io/entry/5781e6872e958a0054c93368 作者:advancer_chen,原文链接:http://my.oschina.net/Chen ...

  4. c# 导出数据到Excel模板

    最近在做一个发邮件的功能,客户要求需要导出一个Excel附件,并给了附件的格式, eg: Last Name 姓 First Name 名 Chinese Characters汉字书写(仅大陆人填写) ...

  5. Keep It Simple Stupid!

    Kelly Johnson提出了KISS原则.他是一个飞机工程师以及航空发明家,同时也是一个管理天才,他一生中主要设计了40多架飞机,获得的荣誉相当之多,总之,很牛. 这个原则是对Johnson带领的 ...

  6. 怎样防止重复发送 Ajax 请求?

    著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处.作者:长天之云链接:http://www.zhihu.com/question/19805411/answer/15465427来源 ...

  7. Extjs 表单验证后,几种错误信息展示方式

    今天要求对form表单验证,进行系统学习一下,故做了几个示例: Ext.onReady(function(){        var panel=Ext.create('Ext.form.Panel' ...

  8. python第一天基础1-1

    win下是没有多进程的 windows:1.下载安装包 https://www.python.org/downloads/2.安装 默认安装路径:C:\python273.配置环境变量 [右键计算机] ...

  9. 1260: [CQOI2007]涂色paint

    Description 假设你有一条长度为5的木版,初始时没有涂过任何颜色.你希望把它的5个单位长度分别涂上红.绿.蓝.绿.红色,用一个长度为5的字符串表示这个目标:RGBGR. 每次你可以把一段连续 ...

  10. 一行css代码调试中学到的javascript知识,很有意思

    现在到处都是JavaScript,每天都能知道点新东西.一旦你入了门,你总能从这里或是那里领悟到很多知识.今天我想分享Addy Osmani的一行代码 ,这行代码对于你调试你的CSS是很有用的.为了可 ...