.NET 5.0 析構(gòu)函數(shù)依然有效?
【導讀】最近看到小伙伴在.NET Core中用到了析構(gòu)函數(shù),不禁打一疑問,大部分情況下,即使在.NET Framework中都不會怎么用到析構(gòu)函數(shù),我想在.NET Core中是否還依然有效呢?
隨著時間推移,迭代版本更新,有些當初我們腦海里認定的東西可能在當前并不再適用,這也就需要我們同步知識更新,如今我們所認為可能并不再是往昔我們所認為
.NET Core/.NET 5.0 析構(gòu)函數(shù)
下面首先來看在.NET Framework中一個很標準的資源釋放例子,這里我以4.7.2版本為例(其他版本一樣)。創(chuàng)建基于當前應用程序域的指定程序集的指定實例
public class CurrentDomainSandbox : IDisposable
{
private AppDomain _domain = AppDomain.CreateDomain(
"CurrentDomainSandbox",
null,
new AppDomainSetup
{
ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
});
~CurrentDomainSandbox()
{
Dispose(false);
}
public T CreateInstance<T>(params object[] args)
=> (T)CreateInstance(typeof(T), args);
private object CreateInstance(Type type, params object[] args)
{
HandleDisposed();
return _domain.CreateInstanceAndUnwrap(
type.Assembly.FullName,
type.FullName,
ignoreCase: false,
bindingAttr: 0,
binder: null,
args: args,
culture: null,
activationAttributes: null);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing && (_domain != null))
{
AppDomain.Unload(_domain);
_domain = null;
}
}
private void HandleDisposed()
{
if (_domain == null)
{
throw new ObjectDisposedException(null);
}
}
}
通過如上定義創(chuàng)建指定名稱的應用程序域沙箱盒子,這樣我們則可在此沙箱中創(chuàng)建對應程序集和實例,如此則可以其他域完全隔離且獨立,然后在控制臺進行如下調(diào)用
var sanBox = new CurrentDomainSandbox();
var instance = sanBox.CreateInstance<Program>();
還未完畢,直接運行將拋出如下異常

若用于遠程傳輸,我們直接將主類繼承自MarshalByRefObject就好,否則將此類通過Serializable特性標記,至于二者區(qū)別不詳細展開
通過上述比較標準的例子我們則可以創(chuàng)建和釋放未被使用的對應實例,我們看到用到了析構(gòu)函數(shù),但是我們發(fā)現(xiàn)最終調(diào)用Dispose方法,并未做任何處理,其實不然,問題出在對析構(gòu)函數(shù)概念的理解
析構(gòu)函數(shù):在應用程序終止之前,將調(diào)用尚未被垃圾回收的所有對象的析構(gòu)函數(shù)
析構(gòu)函數(shù)本質(zhì)是終結(jié)器,如果對象已被釋放,在合適時機將自動調(diào)用Finalize方法,除非我們手動通過GC來抑制調(diào)用終結(jié)器(GC.SuppressFinalize),但不建議手動調(diào)用Finalize方法
通過資源釋放標準例子,想必我們已經(jīng)知道了析構(gòu)函數(shù)的基本原理,接下來我們還是基于上述.NET Framework 4.7.2版本來演示析構(gòu)函數(shù)
public class ExampleDestructor
{
public ExampleDestructor()
{
Console.WriteLine("初始化對象");
}
public void InvokeExampleMethod()
{
}
~ExampleDestructor()
{
Console.WriteLine("終結(jié)對象");
}
}
既然析構(gòu)函數(shù)是在應用程序終止前進行調(diào)用,那么我們在調(diào)用上述示例中方法時,如下調(diào)用:
var exampleDestructor = new ExampleDestructor();
exampleDestructor.InvokeExampleMethod();

在.NET Framework中如我們所期望,在應用程序卸載時,此時會調(diào)用析構(gòu)函數(shù)并進行相關打印
接下來到.NET Core,此時將斷點放在析構(gòu)函數(shù)中,將不會再調(diào)用,打印如下:

好了,以上只是我個人猜測,接下來我們直接看官方文檔進行論證,官網(wǎng)對于析構(gòu)函數(shù)鏈接
析構(gòu)函數(shù)規(guī)范
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/destructors
在.NET Framework應用程序中會盡一切合理努力在程序退出時調(diào)用析構(gòu)函數(shù)進行清理(調(diào)用終結(jié)器方法),除非進行手動抑制,但在.NET Core并不能完全保證此行為
通過調(diào)用Collect來強制進行垃圾回收,但是在大多數(shù)情況下,應避免此調(diào)用,因為這可能會導致性能問題。
為何出現(xiàn)如此差異呢?更詳細分析請參看鏈接:
.NET Core析構(gòu)函數(shù)理解分析
https://github.com/dotnet/runtime/issues/16028
根據(jù)此鏈接表述,可以這樣理解:在.NET Core中不會在應用程序終止時運行終結(jié)器(針對可到達或不可到達的對象),根據(jù)建議,并不能保證所有可終結(jié)對象在關閉之前都將被終結(jié)。
由于上述鏈接原因存在,所以在ECMA的C#5.0規(guī)范削弱了這一要求,因此.Net Core并不會違反此版本規(guī)范
強烈推薦:8個GitHub 上可以賺錢的小程序!
微信終于可以免費提現(xiàn)了!
