Dela via


Implementera en avyttra-metod

Metoden Dispose implementeras främst för att frigöra ohanterade resurser. När du arbetar med instansmedlemmar som är IDisposable-implementationer är det vanligt att kedja Dispose-anrop. Det finns andra orsaker till att implementera Dispose, till exempel för att frigöra minne som allokerats, ta bort ett objekt som har lagts till i en samling eller signalera lanseringen av ett lås som har hämtats.

.NET-skräpinsamlaren allokerar eller frigör inte ohanterat minne. Mönstret för att avyttra ett objekt, som kallas dispose-mönstret, skapar ordning i ett objekts livscykel. Dispose-mönstret används för objekt som implementerar IDisposable-gränssnittet. Det här mönstret är vanligt när du interagerar med fil- och rörhandtag, registerhandtag, väntehandtag eller pekare till block med ohanterat minne, eftersom skräpinsamlaren inte kan frigöra ohanterade objekt.

För att säkerställa att resurserna alltid rensas på rätt sätt bör en Dispose-metod vara idempotent, så att den kan anropas flera gånger utan att utlösa ett undantag. Dessutom bör efterföljande anrop av Dispose inte göra någonting.

Kodexemplet som tillhandahålls för metoden GC.KeepAlive visar hur skräpinsamling kan orsaka att en finalizer körs medan en ohanterad referens till objektet eller dess medlemmar fortfarande är i användning. Det kan vara klokt att använda GC.KeepAlive för att göra objektet olämpligt för skräpinsamling från början av den aktuella rutinen till den punkt där den här metoden anropas.

Tips

När det gäller beroendeinjektion hanteras tjänstlivslängd implicit för din räkning när tjänster registreras i en IServiceCollection. IServiceProvider och motsvarande IHost orkestrerar resursrengöring. Mer specifikt tas implementeringar av IDisposable och IAsyncDisposable bort korrekt i slutet av den angivna livslängden.

Mer information finns i Beroendeinmatning i .NET.

Säkra handtag

Att skriva kod för ett objekts finaliserare är en komplex uppgift som kan orsaka problem om det inte görs korrekt. Därför rekommenderar vi att du skapar System.Runtime.InteropServices.SafeHandle objekt i stället för att implementera en finalizer.

En System.Runtime.InteropServices.SafeHandle är en abstrakt hanterad typ som omsluter en System.IntPtr som identifierar en ohanterad resurs. I Windows kan det identifiera ett handtag och på Unix en filbeskrivning. SafeHandle tillhandahåller all logik som krävs för att säkerställa att den här resursen släpps en gång och bara en gång, antingen när SafeHandle tas bort eller när alla referenser till SafeHandle har tagits bort och SafeHandle-instansen har slutförts.

System.Runtime.InteropServices.SafeHandle är en abstrakt basklass. Härledda klasser tillhandahåller specifika instanser för olika typer av handtag. Dessa härledda klasser verifierar vilka värden för System.IntPtr som anses vara ogiltiga och hur du faktiskt frigör handtaget. Till exempel härleds SafeFileHandle från SafeHandle för att omsluta IntPtrs som identifierar öppna filreferenser/deskriptorer och åsidosätter dess SafeHandle.ReleaseHandle() metod för att stänga den (via funktionen close på Unix eller CloseHandle i Windows). De flesta API:er i .NET-bibliotek som skapar en icke-hanterad resurs omsluter den i en SafeHandle och returnerar den som en SafeHandle till dig vid behov i stället för att lämna tillbaka råpekaren. I situationer där du interagerar med en ohanterad komponent och får en IntPtr för en ohanterad resurs kan du skapa en egen typ av SafeHandle för att inkapsla den. Som ett resultat behöver få icke-SafeHandle-typer implementera finalizers. De flesta implementeringar av engångsmönster omsluter bara andra hanterade resurser, varav vissa kan vara SafeHandle objekt.

Följande härledda klasser i Microsoft.Win32.SafeHandles-namnområdet ger säkra referenser.

Klass Resurser som den innehåller
SafeFileHandle
SafeMemoryMappedFileHandle
SafePipeHandle
Filer, minnesmappade filer och rör
SafeMemoryMappedViewHandle Minnesvyer
SafeNCryptKeyHandle
SafeNCryptProviderHandle
SafeNCryptSecretHandle
Kryptografikonstruktioner
SafeRegistryHandle Registernycklar
SafeWaitHandle Väntehandtag

Dispose() och Dispose(bool)

Gränssnittet IDisposable kräver implementering av en enda parameterlös metod Dispose. Dessutom bör alla icke-förseglade klasser ha en Dispose(bool) överlagringsmetod.

Metodsignaturer är:

  • public icke-virtuell (NotOverridable i Visual Basic) (IDisposable.Dispose implementering).
  • protected virtual (Overridable i Visual Basic) Dispose(bool).

Metoden Dispose()

Eftersom public, icke-virtuell (NotOverridable i Visual Basic), anropas parameterlös Dispose-metoden när den inte längre behövs (av en konsument av typen), är dess syfte att frigöra ohanterade resurser, utföra allmän rensning och att ange att finalizern, om den finns, inte behöver köras. Att frigöra det faktiska minnet som är associerat med ett hanterat objekt är alltid domänen för skräpinsamlare. På grund av detta har den en standardimplementering:

public void Dispose()
{
    // Dispose of unmanaged resources.
    Dispose(true);
    // Suppress finalization.
    GC.SuppressFinalize(this);
}
Public Sub Dispose() _
    Implements IDisposable.Dispose
    ' Dispose of unmanaged resources.
    Dispose(True)
    ' Suppress finalization.
    GC.SuppressFinalize(Me)
End Sub

Metoden Dispose utför all objektrensning, så skräpinsamlaren behöver inte längre anropa objektens Object.Finalize åsidosättning. Därför förhindrar anropet till SuppressFinalize-metoden sopinsamlaren från att köra finalizern. Om typen inte har någon finalizer har anropet till GC.SuppressFinalize ingen effekt. Den faktiska rensningen utförs av metodöverlagringen av Dispose(bool).

Överlagring av Dispose(bool)-metoden

I överlagringen är parametern disposing en Boolean som anger om metodanropet kommer från en Dispose -metod (dess värde är true) eller från en finalizer (dess värde är false).

protected virtual void Dispose(bool disposing)
{
    if (_disposed)
    {
        return;
    }

    if (disposing)
    {
        // Dispose managed state (managed objects).
        // ...
    }

    // Free unmanaged resources.
    // ...

    _disposed = true;
}
Protected Overridable Sub Dispose(disposing As Boolean)
     If disposed Then Exit Sub

     If disposing Then

         ' Free managed resources.
         ' ...

     End If

     ' Free unmanaged resources.
     ' ...

     disposed = True
End Sub

Viktig

Parametern disposing ska vara false när den anropas från en finalizer, och true när den anropas från metoden IDisposable.Dispose. Med andra ord är det true när deterministiskt kallas och false när nondeterministiskt anropas.

Metodens brödtext består av tre kodblock:

  • Ett block för villkorlig retur om objektet redan har tagits bort.

  • Ett villkorligt block som frigör hanterade resurser. Det här blocket körs om värdet för disposing är true. De hanterade resurser som frigörs kan vara:

    • Hanterade objekt som implementerar IDisposable. Det villkorliga blocket kan användas för att anropa deras Dispose implementering (kaskaderering). Om du har använt en härledd klass av System.Runtime.InteropServices.SafeHandle för att omsluta din icke-hanterade resurs, bör du här anropa implementeringen av SafeHandle.Dispose().
    • Hanterade objekt som förbrukar stora mängder minne eller förbrukar knappa resurser. Tilldela stora referenser till hanterade objekt till null för att göra dem svårare att nå. Detta frigör dem snabbare än om de hade återtagits på ett icke-deterministiskt sätt.
  • Ett block som frigör ej hanterade resurser. Det här blocket körs oavsett värdet för parametern disposing.

Om metodanropet kommer från en finalizer ska endast den kod som frigör ohanterade resurser köras. Implementeraren ansvarar för att se till att den falska sökvägen inte interagerar med hanterade objekt som kan ha tagits bort. Detta är viktigt eftersom ordningen i vilken skräpinsamlaren kasserar hanterade objekt under slutförande är icke-terministisk.

Kaskad avslutningsanrop

Om klassen äger ett fält eller en egenskap och dess typ implementerar IDisposablebör den innehållande klassen också implementera IDisposable. En klass som instansierar en IDisposable implementering och lagrar den som instansmedlem ansvarar också för rensningen. Detta säkerställer att de refererade disponibla typerna ges möjlighet att deterministiskt utföra rensning via metoden Dispose. I följande exempel är klassen sealed (eller NotInheritable i Visual Basic).

using System;

public sealed class Foo : IDisposable
{
    private readonly IDisposable _bar;

    public Foo()
    {
        _bar = new Bar();
    }

    public void Dispose() => _bar.Dispose();
}
Public NotInheritable Class Foo
    Implements IDisposable

    Private ReadOnly _bar As IDisposable

    Public Sub New()
        _bar = New Bar()
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        _bar.Dispose()
    End Sub
End Class

Dricks

  • Om klassen har ett IDisposable fält eller egenskap men inte egna det, vilket innebär att klassen inte skapar objektet, behöver klassen inte implementera IDisposable.
  • Det finns fall då du kanske vill utföra null-kontroll i en finalisator (som inkluderar Dispose(false)-metoden som anropas av en finalisator). En av de främsta orsakerna är om du är osäker på om instansen har initierats helt (till exempel kan ett undantag utlösas i en konstruktor).

Implementera mönstret för bortskaffning

Alla icke-förseglade klasser (eller Visual Basic-klasser som inte har ändrats som NotInheritable) bör betraktas som en potentiell basklass eftersom de kan ärvas. Om du implementerar mönstret för bortskaffning för eventuella basklasser måste du ange följande:

  • En Dispose implementering som anropar metoden Dispose(bool).
  • En Dispose(bool) metod som utför den faktiska rensningen.
  • Antingen en klass som härleds från SafeHandle som omsluter din ohanterade resurs (rekommenderas), eller ett åsidosättande av metoden Object.Finalize. Klassen SafeHandle innehåller en finalator, så du behöver inte skriva en själv.

Viktig

Det är möjligt för en basklass att endast referera till hanterade objekt och implementera mönstret för bortskaffning. I dessa fall är en finalator onödig. En finalizer krävs endast om du direkt refererar till ohanterade resurser.

Här är ett generellt exempel på hur du implementerar dispose-mönstret för en basklass som använder ett SafeHandle.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

public class BaseClassWithSafeHandle : IDisposable
{
    // To detect redundant calls
    private bool _disposedValue;

    // Instantiate a SafeHandle instance.
    private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle?.Dispose();
                _safeHandle = null;
            }

            _disposedValue = true;
        }
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Public Class BaseClassWithSafeHandle
    Implements IDisposable

    ' To detect redundant calls
    Private _disposedValue As Boolean

    ' Instantiate a SafeHandle instance.
    Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    ' Public implementation of Dispose pattern callable by consumers.
    Public Sub Dispose() _
               Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                _safeHandle?.Dispose()
                _safeHandle = Nothing
            End If

            _disposedValue = True
        End If
    End Sub
End Class

Anteckning

I föregående exempel används ett SafeFileHandle-objekt för att illustrera mönstret; vilket som helst objekt som härleds från SafeHandle kan användas istället. Observera att exemplet inte instansierar sitt SafeFileHandle objekt korrekt.

Här är det allmänna mönstret för att implementera dispose-mönstret för en basklass som överskrider Object.Finalize.

using System;

public class BaseClassWithFinalizer : IDisposable
{
    // To detect redundant calls
    private bool _disposedValue;

    ~BaseClassWithFinalizer() => Dispose(false);

    // Public implementation of Dispose pattern callable by consumers.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    // Protected implementation of Dispose pattern.
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects)
            }

            // TODO: free unmanaged resources (unmanaged objects) and override finalizer
            // TODO: set large fields to null
            _disposedValue = true;
        }
    }
}
Public Class BaseClassWithFinalizer
    Implements IDisposable

    ' To detect redundant calls
    Private _disposedValue As Boolean

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub

    ' Public implementation of Dispose pattern callable by consumers.
    Public Sub Dispose() _
               Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                ' TODO: dispose managed state (managed objects)
            End If

            ' TODO free unmanaged resources (unmanaged objects) And override finalizer
            ' TODO: set large fields to null
            _disposedValue = True
        End If
    End Sub
End Class

Tips

I C# implementerar du en slutförande genom att tillhandahålla en finaliserare, inte genom att åsidosätta Object.Finalize. I Visual Basic skapar du en finalator med Protected Overrides Sub Finalize().

Implementera dispose-mönstret för en härledd klass

En klass som härleds från en klass som implementerar IDisposable-gränssnittet bör inte implementera IDisposableeftersom basklassimplementeringen av IDisposable.Dispose ärvs av dess härledda klasser. För att rensa en härledd klass anger du i stället följande:

  • En protected override void Dispose(bool) metod som åsidosätter basklassmetoden och utför den faktiska rensningen av den härledda klassen. Den här metoden måste också anropa base.Dispose(bool)-metoden (MyBase.Dispose(bool) i Visual Basic) och skicka den avyttringsstatusen (bool disposing parameter) som ett argument.
  • Antingen en klass som härleds från SafeHandle som omsluter din ohanterade resurs (rekommenderas), eller att åsidosätta metoden Object.Finalize. Klassen SafeHandle innehåller en finalator som gör att du inte behöver koda en. Om du anger en finalizer måste den anropa med Dispose(bool)-överlagringen och false-argumentet.

Här är ett exempel på det allmänna mönstret för att implementera dispose-mönstret för en härledd klass som använder ett säkert handtag.

using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;

public class DerivedClassWithSafeHandle : BaseClassWithSafeHandle
{
    // To detect redundant calls
    private bool _disposedValue;

    // Instantiate a SafeHandle instance.
    private SafeHandle? _safeHandle = new SafeFileHandle(IntPtr.Zero, true);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                _safeHandle?.Dispose();
                _safeHandle = null;
            }

            _disposedValue = true;
        }

        // Call base class implementation.
        base.Dispose(disposing);
    }
}
Imports Microsoft.Win32.SafeHandles
Imports System.Runtime.InteropServices

Public Class DerivedClassWithSafeHandle
    Inherits BaseClassWithSafeHandle

    ' To detect redundant calls
    Private _disposedValue As Boolean

    ' Instantiate a SafeHandle instance.
    Private _safeHandle As SafeHandle = New SafeFileHandle(IntPtr.Zero, True)

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                _safeHandle?.Dispose()
                _safeHandle = Nothing
            End If

            _disposedValue = True
        End If

        ' Call base class implementation.
        MyBase.Dispose(disposing)
    End Sub
End Class

Anteckning

I föregående exempel används ett SafeFileHandle objekt för att illustrera mönstret. objekt som härleds från SafeHandle kan användas i stället. Observera att exemplet inte instansierar sitt SafeFileHandle objekt korrekt.

Här är det allmänna mönstret för att implementera mönstret för bortskaffning för en härledd klass som åsidosätter Object.Finalize:

public class DerivedClassWithFinalizer : BaseClassWithFinalizer
{
    // To detect redundant calls
    private bool _disposedValue;

    ~DerivedClassWithFinalizer() => Dispose(false);

    // Protected implementation of Dispose pattern.
    protected override void Dispose(bool disposing)
    {
        if (!_disposedValue)
        {
            if (disposing)
            {
                // TODO: dispose managed state (managed objects).
            }

            // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
            // TODO: set large fields to null.
            _disposedValue = true;
        }

        // Call the base class implementation.
        base.Dispose(disposing);
    }
}
Public Class DerivedClassWithFinalizer
    Inherits BaseClassWithFinalizer

    ' To detect redundant calls
    Private _disposedValue As Boolean

    Protected Overrides Sub Finalize()
        Dispose(False)
    End Sub

    ' Protected implementation of Dispose pattern.
    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If Not _disposedValue Then

            If disposing Then
                ' TODO: dispose managed state (managed objects).
            End If

            ' TODO free unmanaged resources (unmanaged objects) And override a finalizer below.
            ' TODO: set large fields to null.
            _disposedValue = True
        End If

        ' Call the base class implementation.
        MyBase.Dispose(disposing)
    End Sub
End Class

Se även