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 运行时自动调用。
构造函数有两种主要类型:
- 默认构造函数:如果你没有显式定义构造函数,C# 编译器将自动为类生成一个默认的无参构造函数。默认构造函数没有任何参数,通常用于初始化对象的成员变量或执行其他初始化操作。例如:
class MyClass
{// 默认构造函数,无参构造函数public MyClass(){// 初始化操作}
}
- 自定义构造函数:你可以在类中定义自己的构造函数,可以有参数或没有参数,根据需求进行初始化。自定义构造函数可以根据提供的参数执行不同的初始化逻辑。例如:
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结束调用............");}
}
- 子类和父类的构造函数的调用逻辑是?
- A: 先调用父类,再调用子类
- B: 只调用子类
- C: 只调用父类
- D: 先调用子类,再调用父类
揭晓答案,答案是A:先调用父类,再调用子类
ReadInfo开始调用............
ParentClass构造函数
ChildClass构造函数
ParentClass做点什么DoSomeThing()
ReadInfo结束调用............
- 同理可得,当触发
GC.Collect();
时,会先调用子类再调用父类。
static void Main(string[] args)
{ReadInfo();Console.WriteLine("调用GC............");GC.Collect();Console.ReadKey();
}
ReadInfo开始调用............
ParentClass构造函数
ChildClass构造函数
ParentClass做点什么DoSomeThing()
ReadInfo结束调用............
调用GC............
ChildClass析构函数
ParentClass析构函数
- 为了进一步验证,我们在
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#代码了 好怀恋!!!