C# struct 结构体

参考:
https://blog.csdn.net/zeroflamy/article/details/52081462
https://www.w3cschool.cn/csharp/csharp-struct.html

1、C# 结构的特点

1. C#中struct结构体是一个特殊的存在,值类型、栈内拷贝(值类型栈内拷贝决定了struct许多使用特点类似int)。

2. C# 中的结构与传统的 C++ 中的结构不同。C# 中的结构有以下特点:
(1)结构可带有方法、字段、索引、属性、运算符方法和事件。
(2)结构可定义构造函数,不能定义析构函数。但不能定义默认构造函数。默认构造函数是自动定义的,且不可改变。
(3)与类不同,结构不能继承其他的结构或类。也不能作为其他结构或类的基础结构。
(4)结构可实现一个或多个接口。
(5)结构成员不能指定为 abstract、virtual 或 protected,这三个关键词都与继承有关,结构不能继承。
(6)当使用 new 创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 new 即可被实例化。
如果不使用 new,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。(栈内分配内存,也就不需要使用new在堆上分配内存)。

2、类和结构的区别

(1)类是引用类型,结构是值类型。
(2)结构不支持继承。
(3)结构不能定义默认的构造函数,系统自动定义且不可改变。
(4)不能定义析构函数,在栈内分配内存,不需要回收资源。

3、什么时候使用struct

Winform中涉及到本地代码的地方大量使用了struct,这很大程度上是为了代码移植的需要,不能作为我们写代码的规范参考。我们有时感觉结构比较简单的类改为struct可能会提高性能,但这种感觉在绝大多数情况下其实是错误的。那么我们自己在编写代码的时候究竟在什么情况下适合定义struct而不是class呢?

选用struct的原则

参考:Choosing Between Class and Struct,了解选择使用struct的一些准则。

1. 选择struct而非class:
    如果类型的实例很小而且通常存活期都很短,或者一般都嵌入到其它对象中使用。

2. 一般不使用struct,除非类型满足以下全部特征:
    (1)逻辑上表达了一个单一值,类似基本数据类型(int, double)
    (2)实例大小低于16字节
    (3)不可改变
    (4)不会被频繁装箱

3.总结:
    (1)一般情况,请使用class不要考虑struct。当程序需要优化性能再考虑使用struct。
    (2)定义struct时,尽量作为私有类型或内部类型,不要公开。    
    (3)struct的属性不要定义公开的set方法,也就是不可改变,只读,只能在构造时赋值。
    (4)使用struct管理非托管资源时,定义Free方法,使用时一定要在恰当时机调用Free。千万不要想着去实现IDisposable接口。如果觉得不安全,那就改用class吧!
    (5)如果需要调用本地代码而迫不得已,才可以无视其它原则而选用struct。

4、struct的性能

选用struct可以在一些特定条件下改善程序性能,但请注意,不是一定能改善的。

struct一般用于一些结构简单,可以用单一值概念描述的类型。同时,类型的存活期应该不会太长。

struct无需创建即可使用,也没有垃圾回收问题。其实struct直接在栈内分配内存,压根就不在堆内存中分配,所以不需要GC(垃圾回收)。在使用struct时都会复制到当前栈内存中,就像其它值类型一样。这些特性只能说和class在使用上会有差异,需要注意。但说不上是优点还是缺点,取决于具体用法和情况。

struct不存在并发竞争问题,多线程安全,这是个优点。(在结构都在自己当前的栈内,不共享,也就没有竞争)

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

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

int id = structArray[i].Id;

注意,struct字段不可变会很有帮助,如果需要修改字段内容,通过ref方法(值类型,需要ref声明引用)。例如:

// 定义
public static void SetId(ref structType target, int value) 
{
    target.Id = value; 
}
// 使用
SetId(ref structArray[i], 100);

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

5、struct是不可变的!

1. 首先,一个struct描述了一个单一值,类似于int, char这样的基本类型,要改变他应当是对整体重新赋值来改变,即要把struct看做一个整体。这从概念上就禁止了对属性的赋值,所以struct的所有公开的属性、字段都应该是只读的。

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

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

例如,如果struct定义了带有set方法的属性,那么在方法内作为局部变量确实是可以改变的。但是这其中会有陷阱的存在。比如将struct传入一个方法内,除非参数定义了ref否则方法内的任何修改不会影响原始的变量。另一种情况,某个类的一个成员是struct,如果通过类的属性访问修改struct的某个成员,是不会成功的,修改的实际上是栈内的副本,此时只能是将修改后的值重新赋值回去才能生效。

3.  综上:                                
(1)无论是从概念上还是从实践的角度,struct的成员都应当是只读的。要做到这一点,首先将所有Field的可访问性设置为不公开,公开的只有属性的get方法。
(2)如果你出于代码简(tou)洁(lan)的考虑设计了一个struct含有可改变的成员,必须将这个struct定义为私有,以保证可控。公开的struct必须声明成员只读,才能保证使用者在所有情况下获得预期的结果。
 

6、用struct管理本地代码的释放问题

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

struct不允许声明覆盖默认的无参构造方法,也没有析构方法。这是因为struct本身就是一份栈内存,无需new新的实例,也无需去释放。

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

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

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值