CSharp中构造函数、析构函数和IDisposable使用细节探究

1. 先来一个简单的Demo

1.1. 定义一个类

public class ParentClass
{public ParentClass(){Console.WriteLine("ParentClass构造函数");}public void DoSomeThing(){Console.WriteLine("ParentClass做点什么DoSomeThing()");}~ParentClass(){Console.WriteLine("ParentClass析构函数");}
}

1.2. Main函数调用

internal class Program
{static void Main(string[] args){ReadInfo();Console.ReadKey();}private static void ReadInfo(){Console.WriteLine("ReadInfo开始调用............");ParentClass parentClass = new ParentClass();parentClass.DoSomeThing();Console.WriteLine("ReadInfo结束调用............");}
}

1.3. 查看一下输出

ReadInfo开始调用............
ParentClass构造函数
ParentClass做点什么DoSomeThing()
ReadInfo结束调用............

2. 看一下概念

2.1. 构造函数

在 C# 中,构造函数就是与类(或结构体)具有相同名称的成员函数,它在类中的地位比较特殊,

在 C# 中,构造函数是一种特殊类型的方法,用于初始化类的新实例。它的主要任务是为对象的成员变量赋初值、执行一些初始化操作或者为对象分配必要的资源。

构造函数的名称与类名相同,没有返回类型(甚至不能使用 void),并且不能被显式地调用。它在创建类的实例时由 .NET 运行时自动调用。

构造函数有两种主要类型:

  1. 默认构造函数:如果你没有显式定义构造函数,C# 编译器将自动为类生成一个默认的无参构造函数。默认构造函数没有任何参数,通常用于初始化对象的成员变量或执行其他初始化操作。例如:
class MyClass
{// 默认构造函数,无参构造函数public MyClass(){// 初始化操作}
}
  1. 自定义构造函数:你可以在类中定义自己的构造函数,可以有参数或没有参数,根据需求进行初始化。自定义构造函数可以根据提供的参数执行不同的初始化逻辑。例如:
class Person
{public string Name { get; set; }public int Age { get; set; }// 自定义构造函数,有参构造函数public Person(string name, int age){Name = name;Age = age;}
}

当你创建一个类的新实例时,会自动调用合适的构造函数来完成对象的初始化。例如:

Person person1 = new Person("Alice", 30); // 使用自定义构造函数
Person person2 = new Person(); // 使用默认构造函数

需要注意的是,如果你自定义了构造函数,则默认构造函数不再自动生成。如果你希望保留默认构造函数,同时定义自己的构造函数,需要显式地提供默认构造函数。例如:

class MyClass
{public MyClass(){// 默认构造函数}public MyClass(int value){// 自定义构造函数}
}

构造函数在类的实例化过程中扮演着非常重要的角色,确保对象在创建时处于正确的状态。

2.2. 析构函数

在 C# 中,析构函数又称为 “Finalize” 方法。它是一种特殊类型的方法,用于在对象被垃圾回收之前执行一些清理工作。在 C# 中,程序员通常不需要手动编写析构函数,因为 .NET 的垃圾回收机制会自动处理内存管理和资源清理。

在需要手动管理非托管资源(例如文件、数据库连接、网络资源等)的情况下,可以使用析构函数来确保这些资源在对象被回收时得到释放。但是,现在推荐使用更现代的手段如实现 IDisposable 接口,使用 using 语句块,或使用 Dispose 方法来进行资源清理。这些方法提供了更明确和可控的资源管理方式。

如果你还是想了解如何编写析构函数,下面是一个示例:

using System;class MyClass
{// 析构函数~MyClass(){// 执行资源清理的代码// 例如关闭文件、释放非托管资源等Console.WriteLine("Destructor is called. Cleaning up resources.");}
}class Program
{static void Main(){// 创建一个对象MyClass obj = new MyClass();// 程序运行到此结束时,对象可能还未被回收,所以析构函数可能会在程序结束之后被调用。}
}

值得注意的是,析构函数的调用时间是由垃圾回收器决定的,所以无法确切控制它何时被调用。这也是为什么推荐使用 IDisposable 接口和 Dispose 方法,以便在资源不再需要时立即进行释放和清理。

3. 回到案例

回到我们一开始的案例中,我们在ParentClass类中编写了构造函数ParentClass()和析构函数~ParentClass(),但是在最后的输出中,我们看到只有构造函数被执行,但是析构函数并没有被执行。

~ParentClass()
{Console.WriteLine("ParentClass析构函数");
}

到此出,问题来了,不是说析构函数程序员通常不需要手动编写析构函数,因为 .NET 的垃圾回收机制会自动处理内存管理和资源清理。,但是为什么析构函数并没有被执行呢?这是以为只有在触发垃圾回收时,才会调用析构函数,基于此我们来改一下Main函数,使其主动调用一下GC。

3.1. 主动GC

  • 代码
internal class Program
{static void Main(string[] args){ReadInfo();Console.WriteLine("调用GC............");GC.Collect();Console.ReadKey();}private static void ReadInfo(){Console.WriteLine("ReadInfo开始调用............");ParentClass parentClass = new ParentClass();parentClass.DoSomeThing();Console.WriteLine("ReadInfo结束调用............");}
}
  • 代码输出
ReadInfo开始调用............
ParentClass构造函数
ParentClass做点什么DoSomeThing()
ReadInfo结束调用............
调用GC............
ParentClass析构函数

这个时候我们发现GC被调用,只是这个时候GC是主动调用的

3.3. 写一个GC,由CLR自动调用的案例

static void Main(string[] args)
{ReadInfo();//Console.WriteLine("调用GC............");//GC.Collect();List<String> list = new List<string>();for (int i = 0; i < 100000000; i++){list.Add($"内存调用{ i }");}//ReadInfo();Console.ReadKey();
}private static void ReadInfo()
{Console.WriteLine("ReadInfo开始调用............");ParentClass parentClass = new ParentClass();parentClass.DoSomeThing();//List<String> list = new List<string>();//for (int i = 0; i < 100000000; i++)//{//    list.Add($"内存调用{ i }");//}Console.WriteLine("ReadInfo结束调用............");
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FTdSsa1g-1690855974476)(GC发生调用.png)]

以下这个则不会触发调用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ckCtIIR0-1690855974478)(GC没有调用.png)]

static void Main(string[] args)
{ReadInfo();//Console.WriteLine("调用GC............");//GC.Collect();//ReadInfo();Console.ReadKey();
}private static void ReadInfo()
{Console.WriteLine("ReadInfo开始调用............");ParentClass parentClass = new ParentClass();parentClass.DoSomeThing();parentClass = null;List<String> list = new List<string>();for (int i = 0; i < 100000000; i++){list.Add($"内存调用{ i }");}Console.WriteLine("ReadInfo结束调用............");
}

3.3. 同一个变量,我们new两次,是否触发析构函数

internal class Program
{static void Main(string[] args){ReadInfo();Console.ReadKey();}private static void ReadInfo(){Console.WriteLine("ReadInfo开始调用............");ParentClass parentClass = new ParentClass();parentClass.DoSomeThing();parentClass = new ParentClass();parentClass.DoSomeThing();Console.WriteLine("ReadInfo结束调用............");}
}
ReadInfo开始调用............
ParentClass构造函数
ParentClass做点什么DoSomeThing()
ParentClass构造函数
ParentClass做点什么DoSomeThing()
ReadInfo结束调用............

4. 稍微提升一点难度

4.1. 如果现在有一个子类ChildClass继承了ParentClass

代码如下:

public class ParentClass
{public ParentClass(){Console.WriteLine("ParentClass构造函数");}public void DoSomeThing(){Console.WriteLine("ParentClass做点什么DoSomeThing()");}~ParentClass(){Console.WriteLine("ParentClass析构函数");}
}public class ChildClass: ParentClass
{public ChildClass(){Console.WriteLine("ChildClass构造函数");}~ChildClass(){Console.WriteLine("ChildClass析构函数");}
}
internal class Program
{static void Main(string[] args){ReadInfo();Console.ReadKey();}private static void ReadInfo(){Console.WriteLine("ReadInfo开始调用............");ChildClass childClass=new ChildClass();childClass.DoSomeThing();Console.WriteLine("ReadInfo结束调用............");}
}
  1. 子类和父类的构造函数的调用逻辑是?
  • A: 先调用父类,再调用子类
  • B: 只调用子类
  • C: 只调用父类
  • D: 先调用子类,再调用父类

揭晓答案,答案是A:先调用父类,再调用子类

ReadInfo开始调用............
ParentClass构造函数
ChildClass构造函数
ParentClass做点什么DoSomeThing()
ReadInfo结束调用............
  1. 同理可得,当触发GC.Collect();时,会先调用子类再调用父类。
static void Main(string[] args)
{ReadInfo();Console.WriteLine("调用GC............");GC.Collect();Console.ReadKey();
}
ReadInfo开始调用............
ParentClass构造函数
ChildClass构造函数
ParentClass做点什么DoSomeThing()
ReadInfo结束调用............
调用GC............
ChildClass析构函数
ParentClass析构函数
  1. 为了进一步验证,我们在ParentClass父类上再增加一层类。
  • 完整代码如下:
public class BaseClass
{public BaseClass(){Console.WriteLine("BaseClass构造函数");}public void DoSomeThing(){Console.WriteLine("BaseClass做点什么DoSomeThing()");}~BaseClass(){Console.WriteLine("BaseClass析构函数");}
}public class ParentClass: BaseClass
{public ParentClass(){Console.WriteLine("ParentClass构造函数");}public new void DoSomeThing(){Console.WriteLine("ParentClass做点什么DoSomeThing()");}~ParentClass(){Console.WriteLine("ParentClass析构函数");}
}public class ChildClass: ParentClass
{public ChildClass(){Console.WriteLine("ChildClass构造函数");}~ChildClass(){Console.WriteLine("ChildClass析构函数");}
}internal class Program
{static void Main(string[] args){ReadInfo();Console.WriteLine("调用GC............");GC.Collect();Console.ReadKey();}private static void ReadInfo(){Console.WriteLine("ReadInfo开始调用............");ChildClass childClass=new ChildClass();childClass.DoSomeThing();Console.WriteLine("ReadInfo结束调用............");}
}
  • 得到的输出如下:
ReadInfo开始调用............
BaseClass构造函数
ParentClass构造函数
ChildClass构造函数
ParentClass做点什么DoSomeThing()
ReadInfo结束调用............
调用GC............
ChildClass析构函数
ParentClass析构函数
BaseClass析构函数

4.2. 一般在使用时,不推荐使用析构函数,而使用IDispose

  • 完整代码如下:
internal class Program
{static void Main(string[] args){UsingReadInfo();Console.ReadKey();}private static void UsingReadInfo(){Console.WriteLine("UsingReadInfo开始调用............");using (ChildClass childClass = new ChildClass()){childClass.DoSomeThing();}Console.WriteLine("UsingReadInfo结束调用............");}private static void ReadInfo(){Console.WriteLine("ReadInfo开始调用............");ChildClass childClass=new ChildClass();childClass.DoSomeThing();Console.WriteLine("ReadInfo结束调用............");}
}public class BaseClass:IDisposable
{public BaseClass(){Console.WriteLine("BaseClass构造函数");}public void DoSomeThing(){Console.WriteLine("BaseClass做点什么DoSomeThing()");}~BaseClass(){Console.WriteLine("BaseClass析构函数");}public void Dispose(){Console.WriteLine("BaseClass调用Dispose");}
}public class ParentClass: BaseClass, IDisposable
{public ParentClass(){Console.WriteLine("ParentClass构造函数");}public new void DoSomeThing(){Console.WriteLine("ParentClass做点什么DoSomeThing()");}~ParentClass(){Console.WriteLine("ParentClass析构函数");}public void Dispose(){Console.WriteLine("ParentClass调用Dispose");}
}public class ChildClass: ParentClass, IDisposable
{public ChildClass(){Console.WriteLine("ChildClass构造函数");}~ChildClass(){Console.WriteLine("ChildClass析构函数");}public void Dispose(){Console.WriteLine("ChildClass调用Dispose");}
}
  • 输出结果
UsingReadInfo开始调用............
BaseClass构造函数
ParentClass构造函数
ChildClass构造函数
ParentClass做点什么DoSomeThing()
ChildClass调用Dispose
UsingReadInfo结束调用............
  • 如果将整个类中的Dispose改为虚函数则可以:
public class BaseClass:IDisposable
{public virtual void Dispose(){Console.WriteLine("BaseClass调用Dispose");}
}public class ParentClass: BaseClass, IDisposable
{public override void Dispose(){base.Dispose();Console.WriteLine("ParentClass调用Dispose");}
}public class ChildClass: ParentClass, IDisposable
{public override void Dispose(){base.Dispose();Console.WriteLine("ChildClass调用Dispose");}
}
UsingReadInfo开始调用............
BaseClass构造函数
ParentClass构造函数
ChildClass构造函数
ParentClass做点什么DoSomeThing()
BaseClass调用Dispose
ParentClass调用Dispose
ChildClass调用Dispose
UsingReadInfo结束调用............

5. 结语

好久没有一气呵成的写C#代码了 好怀恋!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/26276.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电影售票后台管理系统快速搭建(优惠券制作+java开源)

为了快速搭建电影售票后台管理系统并实现优惠券制作功能&#xff0c;你可以按照以下步骤进行操作&#xff1a; 1. 确定系统的需求和功能&#xff0c;包括用户管理、影院管理、电影管理、订单管理以及优惠券制作等模块。 2. 选择一款适合的Java开源框架来搭建系统&#xff0c;…

【Megatron-DeepSpeed】张量并行工具代码mpu详解(三):张量并行层的实现及测试

相关博客 【Megatron-DeepSpeed】张量并行工具代码mpu详解(三)&#xff1a;张量并行层的实现及测试 【Megatron-DeepSpeed】张量并行工具代码mpu详解(一)&#xff1a;并行环境初始化 【Megatron-DeepSpeed】张量并行工具代码mpu详解(二)&#xff1a;Collective通信操作的封装ma…

SOME/IP学习笔记1

SOA概念 在SOA中,每个服务就好像我们每一个人在社会中扮演的角色,在对别人提供着服务的同时,同时也享受着别人提供出来的服务,人与人之间,既是彼此独立的,又是需要互相通讯的。服务提供者将功能具象为一组接口,这样使用者就能知道如何调用服务,完成某件事情,得到某个…

Windows下调试UEFI程序:Visual Studio调试

以edk2\MdeModulePkg\Application\HelloWorld这个项目作为调试目标。 1. 使用VS2017建立Makefile工程 VS2017, 新建 project&#xff0c;取名X64dbg_vs。 Visual C > Other > Makefile Project, 注意项目路径为HelloWord程序路径。 随便填写config中的字符串&#xff…

【并发编程】ShenyuAdmin里面数据同步用到的无锁环形队列LMAX Disruptor并发框架

并发&#xff0c;数据同步往往是业务开发中比较重要的部分。 shenyu网关数据同步设计方案图 shenyu官网给出的同步设计方案图如下&#xff1a; 基于事件异步并发框架com.lmax.disruptor 下载下示例代码&#xff0c;跑起来发现&#xff0c;在shenyuAdmin模块里面用到了com.lma…

【2.2】Java微服务:nacos的使用

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f49e;当前专栏&#xff1a; 深度学习 ✨特色专栏&#xff1a; 知识分享 &…

Leetcode.2034 股票价格波动

题目链接 Leetcode.2034 股票价格波动 rating : 1832 题目描述 给你一支股票价格的数据流。数据流中每一条记录包含一个 时间戳 和该时间点股票对应的 价格 。 不巧的是&#xff0c;由于股票市场内在的波动性&#xff0c;股票价格记录可能不是按时间顺序到来的。某些情况下&a…

HTR-01 桌式4英寸快速退火炉

HTR-01 桌式4英寸快速退火炉 HTR-01A快速退火炉系列采用红外辐射加热技术&#xff0c;可实现4寸晶圆片吋样品快速升温和降温&#xff0c;同时搭配超高精度温度控制系统&#xff0c;可达到极佳的温场均匀性&#xff0c;对材料的快速热处理(RTP)、快速退火(RTA)、快速热氮化(RTN)…

观测数据建模

观测数据 观测数据 / 样本空间覆盖均匀 psm 观测数据建模 因果模型得到什么样的值 base 0/1 gmv uplift delat 模型可以得到 confounder 的表征&#xff0c;而 NN 可以支持更个性化的结构。在这里给出两种思路&#xff1a; 思路1: 使用树模型生成的 confounder embedd…

大规模数据处理的技术的五个时代和四代引擎

石器时代 随着互联网的蓬勃发展&#xff0c;数据量在呈指数型增长。早在2003年之前&#xff0c;各大厂商都已经严重面临大规模数据的处理难题&#xff0c;比如Google就已经面对大于600亿的搜索量。如何从海量的原始数据中挖掘出有效的信息&#xff0c;如何保证数据挖掘程序的容…

react组件编写最佳实践

react组件编写最佳实践 作为 React.js 开发的新手&#xff0c;理解基本原则并坚持实用性对于更好地快速开发至关重要。无论我们是在处理简单的任务&#xff08;例如构建初学者待办事项应用程序&#xff09;还是更复杂的项目&#xff08;例如建立电子商务平台&#xff09;&…

【Java】-【tomcat部署项目】

Tomcat 的目录结构 bin&#xff1a;Tomcat 的启动、关闭脚本。 conf&#xff1a;Tomcat 配置文件。 lib&#xff1a;Tomcat 需要的类库&#xff08;jar 包&#xff09;。 logs&#xff1a;日志目录。 temp&#xff1a;Tomcat 运行时产生的临时文件。 webapps&#xff1a;存放 …

相机取图后将数据转换成对应的图像处理库图像文件

相机取图后将数据转换成对应的图像处理库图像文件 前言提要一、大华相机1.to HImage 总结 前言 不同相机取完图像后的图像数据包里包含的数据并不一定是相同的&#xff0c;而且相机取图后的图像数据第三方的视觉库&#xff08;Halcon&#xff0c;visionpro等&#xff09;也并不…

React diff 根据相对位置的 diff 算法

文章目录 diff 算法没有 key 时的diff通过 key 的 diff查找需要移动的节点移动节点添加新元素移除不存在的元素缺点 diff 算法 没有 key 时的diff 根据新旧列表的长度进行 diff 公共长度相同的部分直接patch新列表长度>旧列表长度则添加&#xff0c;否则删除 function pa…

Vue3_02 创建Vue3.0工程

1.使用 vue-cli 创建 ## 查看 vue/cli 版本&#xff0c;确保 vue/cli 版本在4.5.0以上 vue -V 或 vue --version## 安装或升级你的 vue/cli npm install -g vue/cli## 创建 vue create vue_test## 启动 cd vue-test npm run serve 2.使用 vite 创建 什么是vite?——新一代…

31.SpringMVC

SpringMVC MVC MVC是一种软件架构的思想&#xff0c;将软件按照模型、视图、控制器来划分 M&#xff1a;Model&#xff0c;模型层&#xff0c;指工程中的JavaBean&#xff0c;作用是处理数据 JavaBean分为两类&#xff1a; 一类称为实体类Bean&#xff1a;专门存储业务数据的…

RxJava 倒计时,轮询器

笔记 倒计时 /*** 短信倒计时** param s*/private Subscription subscription30;public void startCountdownFinishRx30(int s) {clearFinishSubscription30();subscription30 Observable.interval(0, 1, TimeUnit.SECONDS).take(s 1).map(new Func1<Long, Long>() {O…

Java设计模式---工厂方法(Factory Method)模式

说明 工厂方法&#xff08;Factory Method&#xff09;模式是一种工厂模式&#xff0c;也叫多态性工厂&#xff08;Poloymorphic Factory&#xff09;模式。 工厂方法模式涉及如下角色&#xff1a; 抽象工厂角色&#xff1a;给出具体工厂角色必须实现的接口。具体工厂角色&a…

图像 检测 - DETR: End-to-End Object Detection with Transformers (arXiv 2020)

图像 检测 - DETR: End-to-End Object Detection with Transformers - 端到端目标检测的Transformers&#xff08;arXiv 2020&#xff09; 摘要1. 引言2. 相关工作2.1 集预测2.2 Transformers和并行解码2.3 目标检测 3. DETR模型References 声明&#xff1a;此翻译仅为个人学习…

深度学习,计算机视觉任务

目录 计算机视觉任务 1.K近邻算法 2.得分函数 3.损失函数的作用 4.向前传播整体流程 5.反向传播计算方法 计算机视觉任务 机器学习的流程&#xff1a; 数据获取 特征工程 建立模型 评估与应用 计算机视觉&#xff1a; 图像表示&#xff1a;计算机眼中的图像&#…