前言

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

    简介: 目前流行的版本控制软件中,SVN ( 集中式版本控制 ) 算是使用范围更广.且使用时间更早的一款了,现在 git ( 分布式版本控制 ) 更火爆一点. 一.安装svn [root@localh ...

  2. BPTT算法推导

    随时间反向传播 (BackPropagation Through Time,BPTT) 符号注解: \(K\):词汇表的大小 \(T\):句子的长度 \(H\):隐藏层单元数 \(E_t\):第t个时 ...

  3. 下拉tableView实现类似微信中带图的灰色背景

    UIView *topView = [[UIView alloc]initWithFrame:CGRectMake(, -, ScreenWidth, )]; UIImageView *iconIma ...

  4. rdlc报表大小设置

    参考:http://stackoverflow.com/questions/427730/how-to-limit-rdlc-report-for-one-page-in-a-pdf 主要设置为:报表 ...

  5. linux自动以root登录,并自动启动用户程序的设置方法

    系统自动以root登录,并自动启动用户程序的设置方法 第一步:删除root用户 vi /etc/passwd 该文件的第一行:root:X:0:0:root:/root:/bin/bash,只需要把第 ...

  6. Python 第五天 模块(2)

    模块,用一砣代码实现了某个功能的代码集合. 有两种存在的方式 1.写到一个文件夹里面 2.py文件 类似于函数式编程和面向过程编程,函数式编程则完成一个功能,其他代码用来调用即可,提供了代码的重用性和 ...

  7. 猿团YTFCloud生态系统,全面服务创业者

    9月15日,YTFCloud已正式开启了内测. 创业者翘首以待的YTFCloud,虽然让部分创业者感受到了它的神奇,但对于更多暂时无法尝试的创业者来说,它依然有一层神秘的面纱. 今天小编就来带你近距离 ...

  8. CAD规划成果入库GIS_SDE转换之分析

    问题: 1) 项目应用中要求将CAD规划成果合理的入库GIS SDE中,在建立一套比较规范的标准为前提下,如何“低技术.傻瓜式”实现规划数据更新管理.版本化是个迫切难题. 2) CAD作为数据源,不同 ...

  9. Linux ARP缓存配置和状态查看命令

    查看Linux ARP缓存老化时间 cat /proc/sys/net/ipv4/neigh/eth0/base_reachable_time同目录下还有一个文件gc_stale_time,官方解释如 ...

  10. CabArc to create or extract a cab file

    CabArc n D:\test.cab D:\output\*.* CabArc x D:\test.cab -r -p D:\output\*.*