C#中struct和class的区别详解
本文详细分析了C#中struct和class的区别,对于C#初学者来说是有必要加以了解并掌握的。
简单来说,struct是值类型,创建一个struct类型的实例被分配在栈上。class是引用类型,创建一个class类型实例被分配在托管堆上。但struct和class的区别远不止这么简单。
概括来讲,struct和class的不同体现在:
● 类是引用类型,struct是值类型
● 在托管堆上创建类的实例,在栈上创建struct实例
● 类实例的赋值,赋的是引用地址,struct实例的赋值,赋的是值
● 类作为参数类型传递,传递的是引用地址,struct作为参数类型传递,传递的是值
● 类没有默认无参构造函数,struct有默认无参构造函数
● 类支持继承,struct不支持继承
● 类偏向于"面向对象",用于复杂、大型数据,struct偏向于"简单值",比如小于16字节,结构简单
● 类的成员很容易赋初值,很难给struct类型成员赋初值
● 类的实例只能通过new SomeClass()来创建,struct类型的实例既可以通过new SomeStruct()来创建,也可以通过SomeStruct myStruct;来创建
一、从赋值的角度体验struct和class的不同
引用类型赋值,是把地址赋值给了变量
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
class Program { static void Main(string[] args) { SizeClass sizeClass = new SizeClass(){Width = 10, Length = 10}; Console.WriteLine("赋值前:width={0},length={1}", sizeClass.Width, sizeClass.Length); var copyOfSizeClass = sizeClass; copyOfSizeClass.Length = 5; copyOfSizeClass.Width = 5; Console.WriteLine("赋值后:width={0},length={1}",sizeClass.Width, sizeClass.Length); Console.ReadKey(); } } public class SizeClass { public int Width { get; set; } public int Length { get; set; } } public struct SizeStruct { public int Width { get; set; } public int Length { get; set; } } |
运行结果如下图所示:

以上,当把sizeClass赋值给copyOfSize变量的时候,是把sizeClass所指向的地址赋值给了copyOfSize变量,2个变量同时指向同一个地址。所以,当改变copyOfSizeClass变量的值,也相当于改变了sizeClass的值。
struct类型赋值,是完全拷贝,在栈上多了一个完全一样的变量
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class Program { static void Main(string[] args) { SizeStruct sizeStruct = new SizeStruct(){Length = 10, Width = 10}; Console.WriteLine("赋值前:width={0},length={1}", sizeStruct.Width, sizeStruct.Length); var copyOfSizeStruct = sizeStruct; copyOfSizeStruct.Length = 5; copyOfSizeStruct.Width = 5; Console.WriteLine("赋值后:width={0},length={1}", sizeStruct.Width, sizeStruct.Length); Console.ReadKey(); } } |
程序运行结果如下图所示:

以上,当把sizeStruct赋值给copyOfSizeStruct变量的时候,是完全拷贝,改变copyOfSizeStruct的值不会影响到sizeStruct。
二、从参数传值角度体验struct和class的不同
引用类型参数传递的是地址
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class Program { static void Main(string[] args) { List<string> temp = new List<string>(){"my","god"}; temp.ForEach(t => Console.Write(t + " ")); Console.ReadKey(); } public static void ChangeReferenceType(List<string> list) { list = new List<string>(){"hello", "world"}; } } |
运行结果:my god
为什么不是hello world?
→栈上的temp指向托管堆上的一个集合实例
→当temp放到ChangeReferenceType(temp)方法中,本质是把temp指向的地址赋值给了变量list
→在ChangeReferenceType(List<string> list)方法内部,又把变量list的指向了另外一个集合实例地址
→但temp的指向地址一直没有改变
我们再来改变ChangeReferenceType(List<string> list)内部实现方式,其它不变。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Program { static void Main(string[] args) { List<string> temp = new List<string>(){"my","god"}; ChangeReferenceType(temp); temp.ForEach(t => Console.Write(t + " ")); Console.ReadKey(); } public static void ChangeReferenceType(List<string> list) { list.Clear(); list.Add("hello"); list.Add("world"); } } |
运行结果:hello world
为什么不是my god?
→栈上的temp指向托管堆上的一个集合实例
→当temp放到ChangeReferenceType(temp)方法中,本质是把temp指向的地址赋值给了变量list
→在ChangeReferenceType(List<string> list)方法内部,把temp和list共同指向的实例清空,又添加"hello"和"world"2个元素
→由于list和temp指向的实例是一样的,所以改变list指向的实例就等同于改变temp指向的实例
以上,很好地说明了:引用类型参数传递的是地址。
值类型struct参数传递的是值
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class Program { static void Main(string[] args) { Size s = new Size(){Length = 10, Width = 10}; ChangeStructType(s); Console.Write("Length={0},Width={1}", s.Length,s.Width); Console.ReadKey(); } public static void ChangeStructType(Size size) { size.Length = 0; size.Width = 0; } } public struct Size { public int Length { get; set; } public int Width { get; set; } } |
运行结果如下图所示:

为什么Length和Width不是0呢?
→在栈上变量size
→当通过ChangeStructType(size),把s变量赋值给ChangeStructType(Size size)中的size变量,其本质是在栈上又创建了一个变量size,size的值和s是完全一样的
→在ChangeStructType(Size size)内部改变size的值,与变量s毫无关系
三、从struct类型的struct类型属性和struct引用类型属性体验struct和class的不同
假设有一个struct,它有struct类型的属性
以下, struct类型Room有struct类型的属性TableSize和TvSize,我们如何通过Room实例来修改其struct类型的属性值呢?
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
class Program { static void Main(string[] args) { Room r = new Room() { TableSize = new Size(){Length = 100, Width = 80}, TvSize = new Size(){Length = 10, Width = 8} }; r.TableSize.Length = 0; Console.WriteLine("table目前的尺寸是:length={0},width={1}", r.TableSize.Length, r.TableSize.Width); Console.ReadKey(); } } public struct Size { public int Length { get; set; } public int Width { get; set; } } public struct Room { public Size TableSize { get; set; } public Size TvSize { get; set; } } |
以上,r.TableSize.Length = 0;此处会报错:不能修改r.TableSize的值,因为不是变量。的确,r.TableSize只是Size的一份拷贝,而且也没有赋值给其它变量,所以r.TableSize是临时的,会被自动回收,对其赋值也是没有意义的。
如果要修改r.TableSize,只需把
|
1
|
r.TableSize.Length = 0; |
改成如下:
|
1
|
r.TableSize = new Size(){Length = 0, Width = 0}; |
运行结果如下图所示:

可见,改变struct类型的struct类型属性的某个属性是行不通的,因为像以上r.TableSize只是一份拷贝,是临时的,会被自动回收的。要改变struct类型的struct类型属性,就需要像上面一样,给r.TableSize赋上一个完整的Size实例。
假设有一个struct,它有引用类型的属性呢?
以下,struct类型的Room有引用类型属性,TableSize和TvSize,如何通过Room实例来修改其引用类型的属性值呢?并且,我们在类Size中定义了一个事件,当给Size的属性赋值时就触发事件,提示size类的属性值发生了改变。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
class Program { static void Main(string[] args) { var oneSize = new Size() {Length = 10, Width = 10}; var twoSize = oneSize; oneSize.Changed += (s, e) => Console.Write("Size发生了改变~~"); oneSize.Length = 0; Console.ReadKey(); } } public class Size { private int _length; private int _width; public event System.EventHandler Changed; public int Length { get { return _length; } set { _length = value; OnChanged(); } } public int Width { get { return _width; } set { _width = value; OnChanged(); } } private void OnChanged() { if (Changed != null) { Changed(this, new EventArgs()); } } } public struct Room { public Size TableSize { get; set; } public Size TvSize { get; set; } } |
运行,显示:Size发生了改变~~
对oneSize.Length的修改,实际上修改的是oneSize.Length指向托管堆上的实例。
四、从构造函数体验struct和class的不同
struct类型包含隐式的默认无参构造函数
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Program { static void Main(string[] args) { var size = new SizeStruct(); Console.WriteLine("length={0},width={1}", size.Length, size.Width); Console.ReadKey(); } } public struct SizeStruct { public int Length { get; set; } public int Width { get; set; } } |
运行结果如下图所示:

为什么我们没有给SizeStruct定义无参构造函数,而没有报错?
--因为,struct类型有一个隐式的无参构造函数,并且给所有的成员赋上默认值,int类型属性成员的默认值是0。
类不包含隐式无参构造函数
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class Program { static void Main(string[] args) { var size = new SizeClass(); Console.WriteLine("length={0},width={1}", size.Length, size.Width); Console.ReadKey(); } } public class SizeClass { public int Length { get; set; } public int Width { get; set; } public SizeClass(int length, int width) { Length = length; Width = Width; } } |
运行,报错:SizeClass不包含0个参数的构造函数
五、从给类型成员赋初值体验struct和class的不同
如果直接给字段赋初值。
|
1
2
3
4
|
public struct SizeStruct { public int _length = 10; } |
运行,报错:结构中不能有实例字段初始值设定项
如果通过构造函数给字段赋初值。
|
1
2
3
4
5
6
7
8
9
|
public struct SizeStruct { public int _length; public SizeStruct() { _length = 10; } } |
运行,报错:结构中不能包含显式无参数构造函数
可见,给struct类型成员赋初值是不太容易的,而给class成员赋初值,no problem。
何时使用struct,何时使用class?
在多数情况下,推荐使用class类,因为无论是类的赋值、作为参数类型传递,还是返回类的实例,实际拷贝的是托管堆上引用地址,也就大概4个字节,这非常有助于性能的提升。
而作为struct类型,无论是赋值,作为参数类型传递,还是返回struct类型实例,是完全拷贝,会占用栈上的空间。根据Microsoft's Value Type Recommendations,在如下情况下,推荐使用struct:
● 小于16个字节
● 偏向于值,是简单数据,而不是偏向于"面向对象"
● 希望值不可变
C#中struct和class的区别详解的更多相关文章
- C#中struct和class的区别详解 (转载)
本文详细分析了C#中struct和class的区别,对于C#初学者来说是有必要加以了解并掌握的. 简单来说,struct是值类型,创建一个struct类型的实例被分配在栈上.class是引用类型,创建 ...
- C#中struct与class的区别详解
转自:http://blog.csdn.net/justlovepro/archive/2007/11/02/1863734.aspx 有这么几点不同: 1.struct 是值类型,class是对象类 ...
- 基于python中staticmethod和classmethod的区别(详解)
例子 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 class A(object): def foo(self,x): print "executing foo ...
- DOS批处理中%cd%与%~dp0的区别详解
转载:https://www.jb51.net/article/105325.htm DOS批处理中%cd%与%~dp0的区别详解 Windows下批处理中%cd%和%~dp0都能用来表示当前 ...
- Java中的==和equals的区别详解
1.基础知识 (1)String x = "hello"; (2)String x = new String ("hello"); 第1种方式的工作机制是,首先 ...
- 关于C中struct和union长度的详解
这几天看<代码大全>中的第十三章---不常见的数据类型,里面讲解到了C语言中的struct以及对指针的解释,联想到以前看过相关的关于C语言中stuct长度的文章,只是现在有些淡忘了,因此今 ...
- Lua中ipairs和pairs的区别详解
迭代器for遍历table时,ipairs和pairs的区别: 区别一:ipairs遇到nil会停止,pairs会输出nil值然后继续下去 区别二: , b = , x = , y = , " ...
- ThinkPHP3.2.3中M()和D()的区别详解
在实例化的过程中,经常使用D方法和M方法, 区别在于:M方法实例化模型无需用户为每个数据表定义模型类,如果D方法没有找到定义的模型类,则会自动调用M方法. 通俗一点说:1.M实例化参数是数据库的表名, ...
- Python中__init__和__new__的区别详解
__init__ 方法是什么? 使用Python写过面向对象的代码的同学,可能对 __init__ 方法已经非常熟悉了,__init__ 方法通常用在初始化一个类实例的时候.例如: # -*- cod ...
随机推荐
- noip第31课资料
- Email发展历史
据我所知:1987年9月20日,有“中国互联网第一人”之称的钱天白从北京经意大利向前联邦德国卡尔斯鲁厄大学发出了中国第一封电子邮件,内容是“ 穿越长城,走向世界”.这是中国人在网络上的第一步,他开创了 ...
- EmpireCMS_V7.5的一次审计
i春秋作家:Qclover 原文来自:EmpireCMS_V7.5的一次审计 EmpireCMS_V7.5的一次审计 1概述 最近在做审计和WAF规则的编写,在CNVD和CNNVD等漏洞平台寻找 ...
- 刺透内网的HTTP代理
从偶然出发 在做测试的时候发现了这样一个漏洞,原请求报文如下: GET / HTTP/1.1 Host: attack_website [... HEADER ...] ... 当时最初目的是想测SS ...
- Javascript高级编程学习笔记(73)—— 表单(1)表单基础
表单 JS最初的一个用途就是帮助服务器分担处理表单的责任 时至今日,虽然web应用以及JS都有了长足的发展,但是表单依然是现在web应用中比较重要的部分. 因为默认的表单控件很丑,所以有时候我们会使用 ...
- Redis学习笔记之延时队列
目录 一.业务场景 二.Redis延时队列 一.业务场景 所谓延时队列就是延时的消息队列,下面说一下一些业务场景比较好理解 1.1 实践场景 订单支付失败,每隔一段时间提醒用户 用户并发量的情况,可以 ...
- 基于 jq 实现拖拽上传 APK 文件,js解析 APK 信息
技术栈 jquery 文件上传:jquery.fileupload,github 文档 apk 文件解析:app-info-parser,github 文档 参考:前端解析ipa.apk安装包信息 - ...
- Static了解和复习继承。
Static了解 static加在局部变量的前面改变其存储类型使之成为静态局部变量,会延长它的生存周期,但是不会改变其作用域. 静态成员是属于类的,而不是属于某个对象的.即便没有任何一个实例,类的静态 ...
- LVS DR模式配置
关于LVS的相关介绍,以下两篇文章均做了具体的介绍: http://www.linuxvirtualserver.org/zh/lvs1.html http://www.cnblogs.com/liw ...
- Spring面试底层原理的那些问题,你是不是真的懂Spring?
1.什么是 Spring 框架?Spring 框架有哪些主要模块?Spring 框架是一个为 Java 应用程序的开发提供了综合.广泛的基础性支持的 Java 平台.Spring帮助开发者解决了开发中 ...