.NET Core/.NET 5.0 析構函數依然有效?

前言

最近看到小夥伴在.NET Core中用到了析構函數,不由打一疑問,大部分狀況下,即便在.NET Framework中都不會怎麼用到析構函數,我想在.NET Core中是否還依然有效呢?隨着時間推移,迭代版本更新,有些當初咱們腦海裏認定的東西可能在當前並再也不適用,這也就須要咱們同步知識更新,現在咱們所認爲可能並再也不是往昔咱們所認爲git

.NET Core/.NET 5.0 析構函數

下面首先來看在.NET Framework中一個很標準的資源釋放例子,這裏我以4.7.2版本爲例(其餘版本同樣)。建立基於當前應用程序域的指定程序集的指定實例github

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);
      }
    }
}

經過如上定義建立指定名稱的應用程序域沙箱盒子,這樣咱們則可在此沙箱中建立對應程序集和實例,如此則能夠其餘域徹底隔離且獨立,而後在控制檯進行以下調用dom

  var sanBox = new CurrentDomainSandbox();
  var instance = sanBox.CreateInstance<Program>();

還未完畢,直接運行將拋出以下異常ide

 若用於遠程傳輸,咱們直接將主類繼承自MarshalByRefObject就好,不然將此類經過Serializable特性標記,至於兩者區別不詳細展開。經過上述比較標準的例子咱們則能夠建立和釋放未被使用的對應實例,咱們看到用到了析構函數,可是咱們發現最終調用Dispose方法,並未作任何處理,其實否則,問題出在對析構函數概念的理解函數

 

析構函數:在應用程序終止以前,將調用還沒有被垃圾回收的全部對象的析構函數。析構函數本質是終結器,若是對象已被釋放,在合適時機將自動調用Finalize方法,除非咱們手動經過GC來抑制調用終結器(GC.SuppressFinalize),但不建議手動調用Finalize方法性能

 

經過資源釋放標準例子,想必咱們已經知道了析構函數的基本原理,接下來咱們仍是基於上述.NET Framework 4.7.2版原本演示析構函數ui

public class ExampleDestructor
{
    public ExampleDestructor()
    {
      Console.WriteLine("初始化對象");
    }

    public void InvokeExampleMethod()
    {

    }

    ~ExampleDestructor()
    {
      Console.WriteLine("終結對象");
    }
}

既然析構函數是在應用程序終止前進行調用,那麼咱們在調用上述示例中方法時,以下調用:this

var exampleDestructor = new ExampleDestructor();

exampleDestructor.InvokeExampleMethod();

在.NET Framework中如咱們所指望,在應用程序卸載時,此時會調用析構函數並進行相關打印。接下來到.NET Core,此時將斷點放在析構函數中,將不會再調用,打印以下:url

好了,以上只是我我的猜想,接下來咱們直接看官方文檔進行論證,官網對於析構函數連接spa

析構函數規範

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/destructors

在.NET Framework應用程序中會盡一切合理努力在程序退出時調用析構函數進行清理(調用終結器方法),除非進行手動抑制,但在.NET Core並不能徹底保證此行爲。經過調用Collect來強制進行垃圾回收,可是在大多數狀況下,應避免此調用,由於這可能會致使性能問題。爲什麼出現如此差別呢?更詳細分析請參看連接:

.NET Core析構函數理解分析

https://github.com/dotnet/runtime/issues/16028

根據此連接表述,能夠這樣理解:在.NET Core中不會在應用程序終止時運行終結器(針對可到達或不可到達的對象),根據建議,並不能保證全部可終結對象在關閉以前都將被終結。因爲上述連接緣由存在,因此在ECMA的C#5.0規範削弱了這一要求,所以.Net Core並不會違反此版本規範

總結

💡 在應用程序關閉前,.NET Framework會盡一切合理努力調用析構函數即終結器進行資源清理,但在.NET Core中並不能保證此行爲,因此在ECMA 語言規範中削弱了這一要求

💡 基於上述,在.NET Core中使用析構函數並無實質性意義

相關文章
相關標籤/搜索