前言

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

    若未声明,则都是js的方法 1.indexOf indexOf(str):默认返回字符串中第一次出现索引位置 的下标,没有则返回-1 indexOf(str,position):返回从position ...

  2. lvs+keepalived+nginx实现高性能负载均衡集群

    一.为什么要使用负载均衡技术? 1.系统高可用性 2.  系统可扩展性 3.  负载均衡能力 LVS+keepalived能很好的实现以上的要求,LVS提供负载均衡,keepalived提供健康检查, ...

  3. Linux系统下的Nginx安装

    nginx可以使用各平台的默认包来安装,本文是介绍使用源码编译安装,包括具体的编译参数信息. 正式开始前,编译环境gcc g++ 开发库之类的需要提前装好,这里默认你已经装好. ububtu平台编译环 ...

  4. VBA操作单元格

    行或列的Group化 ws.Rows("row1:row2").group row1:Group化的开始行                row2:Group化的结束行 ws.Co ...

  5. ncurses-devel

    :Install ncurses(ncurses-devel) and try again. 做一个简单的铺垫,ncurses是字符终端下屏幕控制的基本库.可能很多新开发的程序已经不再使用.假如要编译 ...

  6. 证书过期-->app审核提示90034证书错误

    1.证书过期问题,去钥匙串中删除过期证书,然后新下载一个证书,重新添加,注:一定要把所有过期证书全部删除,如果不显示则点击钥匙串-->显示过期证书 然后下载新证书:https://develop ...

  7. 移动端HTML5<video>视频播放优化实践[转]

    http://blog.csdn.net/u010918416/article/details/52705732 http://www.xuanfengge.com/html5-video-play. ...

  8. python第一天基础1-2

    python入门 1 第一个python代码: 在linux上创建第一个.py脚本 #!/usr/bin/env python #-*- coding:utf-8 -*- print "He ...

  9. linux环境下部署tomcat

    服务器环境:Red Hat Enterprise Linux Server release 6.5 安装部署包:apache-tomcat-8.0.30.tar.gz.jdk-8u66-linux-x ...

  10. Microsoft.Office.Interop.Word.Document.Open returns null on Windows Server 2008 R2

    系统终于通过UAT,可以上线了.一遍测下来还行,可是为什么word转PDF就是不行呢?查了一下log,原来在wordApp.Documents.Open来打开生产的word文件的时候,返回一直是空.之 ...