C#中struct和class的区别

2023-05-16

本文详细分析了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的不同

引用类型赋值,是把地址赋值给了变量

 
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类型赋值,是完全拷贝,在栈上多了一个完全一样的变量

 
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的不同

引用类型参数传递的是地址

 
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)内部实现方式,其它不变。

 
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参数传递的是值

 
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类型的属性值呢?

 
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,只需把

r.TableSize.Length = 0;

改成如下:

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类的属性值发生了改变。

 
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类型包含隐式的默认无参构造函数

 
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。

类不包含隐式无参构造函数

 
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的不同

如果直接给字段赋初值。

public struct SizeStruct
   {
     public int _length = 10;
   }

运行,报错:结构中不能有实例字段初始值设定项

如果通过构造函数给字段赋初值。

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个字节
● 偏向于值,是简单数据,而不是偏向于"面向对象"
● 希望值不可变

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C#中struct和class的区别 的相关文章

  • 如何将所有GET请求查询参数放入Go中的结构体中?

    你好 我想将 get 查询参数转换为 Go 中的结构 例如我有这样的结构 type Filter struct Offset int64 json offset Limit int64 json limit SortBy string js
  • SwiftUI - 使用“ObservableObject”和@EnvironmentObject 有条件地显示视图

    我想在我的应用程序中有条件地显示不同的视图 如果某个布尔值为 true 则将显示一个视图 如果为 false 将显示不同的视图 该布尔值位于 ObservableObject 类中 并根据将要显示的视图之一进行更改 PracticeStat
  • 访问结构向量

    我有一个结构 struct OutputStore int myINT string mySTRING 如果我创建一个 OutputStore 类型的数组 如下所示 OutputStore OutputFileData new Output
  • 为什么非空槽不能与 int、tuple、bytes 子类一起使用?

    参考手册中明确记录了这一点 非空 slots 不适用于从 可变长度 内置类型 例如 int bytes 和 tuple 派生的类 情况确实如此 写道 class MyInt int slots spam 结果是 TypeError none
  • OOP 中的对象和结构有什么区别?

    对象与结构体有何区别 我们何时以及为何使用对象而不是结构体 数组与两者有何不同 何时以及为何使用数组而不是对象或结构 我想了解每个的用途 显然 您可以根据您的编程风格模糊这些区别 但通常结构是结构化的数据块 对象是可以执行某种任务的主权实体
  • 返回具有关联类型的特征

    struct A struct PropA struct B struct PropB trait AB type prop fn a self gt fn b self p Self prop gt impl AB for A type
  • 如何调用进行 API 调用的类的实例以及该类中发出请求的函数,并将其分配给变量?迅速

    这是以下问题的后续 为什么代码不在第二个 URLSession shared dataTask 之后 之内执行 即在初始 URLSession shared dataTask 的 do 块内 迅速 https stackoverflow c
  • 对结构体进行 typedef 对其自身有什么影响? [复制]

    这个问题在这里已经有答案了 我在 API 顶部看到过这样的代码 typedef struct SYSTEM SYSTEM 方式SYSTEM之前是未定义的 有谁知道这是做什么的 编译器认为什么SYSTEM这条线之后 感谢您的回答 我的问题是
  • 结构中未初始化字段没有 Clang 警告

    考虑以下结构 typedef struct foo int a int b foo 我的编译器不会对以下语句发出警告 foo m 300 为什么没有发出警告 我预计会收到警告 因为我没有为结构的最终字段提供任何值 这是我的编译器调用 cla
  • 在 Swift 中使用 NSCoding 归档可选结构数组?

    我已经在 Obj C 中完成了大量 NSCoding 归档 但我不确定它如何处理 Swift 中的结构 也不确定它如何处理具有可选值的数组 这是我的代码 public struct SquareCoords var x Int y Int
  • 将带有 **kwargs 错误的值线程化并传递给 TypeError

    我对 Python 还很陌生 并且正在通过这篇文章研究如何使用线程来处理某些代码 Python 使用线程或队列迭代调用函数的 for 循环 https stackoverflow com questions 12868956 python
  • Exit() 时是否调用基本对象析构函数?

    我意识到这个问题已经出现过几次 但我试图获得上述问题的明确答案 但我不断遇到相互矛盾的信息 我需要知道的是 当我使用 exit 时 基本类对象是否被破坏 我知道需要删除动态内存 但我的意思更像是 include
  • 如何在 Eclipse 中获得完全限定的类名?

    有没有一种快速方法可以在 Eclipse 中单击 Java 类并获取其完全限定名称 或将其复制到剪贴板 2016年6月29日编辑 正如 Jeff 所指出的 您只需要执行以下第二步 1 Double click on the class na
  • 不同 C++ 文件中的相同类名

    如果两个 C 文件具有相同名称的类的不同定义 那么当它们被编译和链接时 即使没有警告也会抛出一些东西 例如 a cc class Student public std string foo return A void foo a Stude
  • 不可变类与结构

    以下是类与 C 中的结构的唯一区别 如果我错了 请纠正我 类变量是引用 而结构变量是值 因此在赋值和参数传递中复制结构的整个值 类变量是存储在堆栈上的指针 指向堆上的内存 而结构变量作为值存储在堆上 假设我有一个不可变的结构 该结构的字段一
  • 如何修改另一个类的方法

    我有以下课程称为A 用方法getValue public class A public final int getValue return 3 方法getValue 总是返回3 然后我还有另一个类叫做B 我需要实现一些东西来访问该方法get
  • Java加减法与金钱

    我正在尝试对美元和美分进行加法和减法 但在超过 100 美分和低于 0 美分时遇到困难 我的代码可以很好地添加任何内容 直到我需要将 100 美分转换为 1 美元 我无法将我的话转化为代码 但我知道需要做什么才能将美分转换成美元 仅供参考
  • Vaadin:在表中显示列表

    我需要显示表中列表中包含的所有值 例如 class Person String name String age List
  • 从 using 语句中修改值类型是否是未定义的行为?

    这确实是一个分支这个问题 https stackoverflow com questions 4642665 why does capturing a mutable struct variable inside a closure wit
  • 为什么带有隐式转换运算符的自定义结构上的 Assert.AreEqual 失败?

    我创建了一个自定义结构来表示金额 它基本上是一个包装器decimal 它有一个隐式转换运算符将其转换回decimal 在我的单元测试中 我断言 Amount 等于原始十进制值 但测试失败 TestMethod public void Amo

随机推荐

  • 第9章 项目成本管理

    文章目录 9 1 1 成本与成本管理概念项目成本管理的过程 9 1 2 相关术语成本的类型 xff08 6种 xff09 应急储备与管理储备 9 2 3 项目成本管理计划制订的 输出9 3 1 项目成本估算的主要相关因素项目估算还需要考虑但
  • 第10章 项目质量管理

    文章目录 10 1 2 质量管理及其 发展史10 1 3 项目质量管理 xff08 包括 xff1a 规划质量管理 实施质量保证 质量控制 xff09 10 2 3 规划质量管理 的工具与技术1 成本效益分析法2 质量成本法 xff08 一
  • 第11章 项目人力资源管理

    文章目录 项目人力资源管理 过程11 2 1 编制项目人力资源计划的工具与技术 xff08 1 xff09 层次结构图 xff08 工作 组织 资源 分解结构 xff09 xff08 2 xff09 矩阵图 xff08 责任分配矩阵 xff
  • 第12章 项目沟通管理和干系人管理

    文章目录 沟通渠道计算 xff08 M 61 n n 1 2 xff09 12 1 2 沟通的方式沟通管理计划的编制过程12 2 2 制订沟通管理计划的工具4 沟通方法 xff08 交互式 推式 拉式 xff09 12 3 2 管理沟通的工
  • 第13章 项目合同管理

    文章目录 13 2 1 按信息系统 范围 划分的合同分类1 总承包合同2 单项工程承包合同3 分包合同 13 2 2 按项目 付款方式 划分的合同分类1 总价合同2 成本补偿合同 xff08 卖方有利 xff09 3 工料合同 13 3 1
  • FS-Cli常用命令简介

    目录 退出操作 日志与显示操作 全局变量 fsctl xff1a 发送控制信息 通话相关命令简介 show xff1a 显示信息 fs cli是FreeSWITCH的一个客户端连接程序 xff0c 可以方便地查看运行情况 xff0c 并对其
  • 第14章 项目采购管理

    文章目录 采购管理包括如下几个过程14 2 编制采购计划编制采购计划的输出1 xff09 采购管理计划2 xff09 采购工作说明书3 xff09 采购文件 14 2 3 工作说明书 xff08 SOW xff09 14 3 实施采购14
  • 第15章 信息(文档)和配置管理

    文章目录 软件文档的分类 xff08 1 xff09 开发文档 xff1a 描述开发过程 本身 xff08 2 xff09 产品文档 xff1a 描述开发过程的 产物 xff08 3 xff09 管理文档 xff1a 记录项目管理的信息 文
  • 第16章 变更管理

    文章目录 16 1 项目变更的基本概念16 1 1 项目变更的含义16 1 2 项目变更的分类16 1 3 项目变更产生的原因 16 2 变更管理的基本原则16 3 变更管理角色职责与工作程序16 3 1 角色职责16 3 2 工作程序 1
  • 第17章 信息系统安全管理

    文章目录 信息安全属性及目标 xff08 1 xff09 保密性 xff08 Confidentiality xff09 xff08 2 xff09 完整性 xff08 Integrity xff09 xff08 3 xff09 可用性 x
  • 第18章 项目风险管理

    文章目录 18 1 2 风险的分类按照性质划分 xff08 纯粹 投机 xff09 按照产生原因 xff08 自然 社会 政治 经济 技术 xff09 18 1 3 风险的性质 xff08 客观 偶然 相对 社会 不确定 xff09 项目风
  • 第19章 项目收尾管理

    文章目录 19 1 项目验收 xff08 1 xff09 验收测试 xff08 2 xff09 系统试运行 xff08 3 xff09 系统文档验收 xff08 4 xff09 项目终验 19 2 项目总结 xff08 属于项目收尾的 管理
  • 第20章 知识产权管理、第21章 法律法规和标准规范

    文章目录 20 1 2 知识产权的特性 58420 2 1 著作权及邻接权 58520 2 2 专利权 58920 2 3 商标权 59221 3 诉讼时效 59921 6 3 标准分级与标准类型 60321 7 2 信息系统集成项目管理常
  • 系统集成项目管理工程师 下午 真题 及考点(2022年四套卷)

    文章目录 2022年下半年试题一 xff1a 第10章 项目质量管理 xff0c 流程图 核查表 帕累托图 xff0c 7种质量工具 xff0c 一致性成本和非一致性成本 xff0c 质量保证和质量控制试题二 xff1a 第8章 项目进度管
  • 系统集成项目管理工程师 下午 真题 及考点(2021年上下半年)

    文章目录 2021年下半年试题一 xff1a 第18章 项目风险管理 xff0c 风险应对策略 xff0c 风险的性质 xff08 客观 偶然 相对 社会 不确定 xff09 试题二 xff1a 第9章 项目成本管理 xff0c 执行绩效
  • 系统集成项目管理工程师 下午 真题 及考点(2020年下半年)

    文章目录 2020年下半年试题一 xff1a 第10章 项目质量管理 xff0c 规划质量管理过程的输入试题二 xff1a 第9章 项目成本管理 xff0c 典型 xff1a EAC 61 AC 43 ETC 61 AC 43 xff08
  • FreeSWITCH之lua脚本事件订阅

    目录 相关接口简要说明 Even EventConsumer pop获取事件 bind订阅 代码示例 事件创建 事件订阅 FreeSWITCH中通过订阅事件 xff0c 我们能获取到各种实时信息 xff0c 进而可以对通话进行精确的控制 在
  • 超级基础A*寻路教程

    download code resource 学习了一下A 算法 xff0c 但是天生对算法无奈 xff0c 还好一不小心找到下面这篇文章 如果你苦于无法理解网上各大牛人的巅峰讨论以及他们火星文般的源代码 xff0c 那么这篇文章实在是太适
  • Cannot find module ‘body-parser‘

    node modules下模块缺失 解决方案 xff1a npm install span class token operator span save body span class token operator span parser
  • C#中struct和class的区别

    本文详细分析了C 中struct和class的区别 xff0c 对于C 初学者来说是有必要加以了解并掌握的 简单来说 xff0c struct是值类型 xff0c 创建一个struct类型的实例被分配在栈上 class是引用类型 xff0c