L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Implementare un metodo Dispose
Articolo
16/03/2025
Il metodo Dispose viene implementato principalmente per rilasciare risorse non gestite. Quando si lavora con membri dell'istanza che sono implementazioni di IDisposable, è comune effettuare chiamate Dispose in cascata. Esistono altri motivi per implementare Dispose, ad esempio per liberare memoria allocata, rimuovere un elemento aggiunto a una raccolta o segnalare il rilascio di un blocco acquisito.
L'Garbage Collector .NET non alloca o rilascia memoria non gestita. Il modello per l'eliminazione di un oggetto, definito modello di eliminazione , impone l'ordine per la durata di un oggetto. Il modello dispose viene usato per gli oggetti che implementano l'interfaccia IDisposable. Questo modello è comune quando si interagisce con handle di file e pipe, handle del Registro di sistema, handle di attesa o puntatori a blocchi di memoria non gestita, perché il Garbage Collector non è in grado di recuperare oggetti non gestiti.
Per garantire che le risorse vengano sempre pulite in modo appropriato, un metodo Dispose deve essere idempotente, in modo che sia chiamabile più volte senza generare un'eccezione. Inoltre, le chiamate successive di Dispose non dovrebbero fare nulla.
L'esempio di codice fornito per il metodo GC.KeepAlive mostra come la garbage collection possa causare l'esecuzione di un finalizzatore mentre un riferimento non gestito all'oggetto o ai suoi membri è ancora in uso. Può essere utile utilizzare GC.KeepAlive per rendere l'oggetto non idoneo per l'operazione di Garbage Collection dall'inizio della routine corrente al punto in cui viene chiamato questo metodo.
Suggerimento
Per quanto riguarda il dependency injection, quando si registrano servizi in un IServiceCollection, il ciclo di vita del servizio viene gestito in modo implicito per voi. I IServiceProvider e i rispettivi IHost coordinano la pulizia delle risorse. In particolare, le implementazioni di IDisposable e IAsyncDisposable vengono eliminate correttamente alla fine della durata specificata.
Se la classe è proprietaria di un'istanza di un altro tipo che implementa IDisposable, anche la classe contenitore deve implementare IDisposable. In genere, una classe che crea un'istanza di un'implementazione di IDisposable e la archivia come membro dell'istanza (o proprietà) è anche responsabile della pulizia. Ciò consente di garantire che ai tipi eliminabili a cui si fa riferimento sia data la possibilità di eseguire la pulizia in modo deterministico tramite il metodo Dispose. Nell'esempio seguente la classe è sealed (o NotInheritable in 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
Suggerimento
Se la classe ha un campo o una proprietà IDisposable ma non proprio, la classe non deve implementare IDisposable. In genere una classe che crea e archivia il IDisposable oggetto figlio diventa anche il proprietario, ma in alcuni casi la proprietà può essere trasferita a un altro tipo di IDisposable.
In alcuni casi può essere necessario eseguire il controllo nullin un finalizzatore (che include il metodo Dispose(false) richiamato da un finalizzatore). Uno dei motivi principali è l'incertezza se un'istanza sia stata completamente inizializzata (ad esempio, un'eccezione potrebbe essere generata in un metodo costruttore).
Dispose() e Dispose(bool)
L'interfaccia IDisposable richiede l'implementazione di un singolo metodo senza parametri, Dispose. Inoltre, qualsiasi classe non sigillata deve avere un metodo di overload Dispose(bool).
Le firme dei metodi sono:
public non virtuale (NotOverridable in Visual Basic) (implementazioneIDisposable.Dispose).
protected virtual (Overridable in Visual Basic) Dispose(bool).
Metodo Dispose()
Poiché il public, non virtuale (NotOverridable in Visual Basic), il metodo Dispose senza parametri viene chiamato quando non è più necessario (da un consumer del tipo), lo scopo è liberare risorse non gestite, eseguire la pulizia generale e per indicare che il finalizzatore, se presente, non deve essere eseguito. Liberare la memoria effettiva associata a un oggetto gestito è sempre il dominio del Garbage Collector . Per questo motivo, ha un'implementazione standard:
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
Il metodo Dispose esegue tutte le operazioni di pulizia degli oggetti, pertanto Garbage Collector non deve più richiamare l'override Object.Finalize degli oggetti. Pertanto, la chiamata al metodo SuppressFinalize impedisce al Garbage Collector di eseguire il finalizzatore. Se il tipo non dispone di finalizzatore, la chiamata a GC.SuppressFinalize non ha alcun effetto. La pulizia effettiva viene eseguita dal sovraccarico del metodo Dispose(bool).
La sovraccarica del metodo Dispose(bool)
Nell'overload, il parametro disposing è un Boolean che indica se la chiamata al metodo proviene da un metodo Dispose (il relativo valore è true) o da un finalizzatore (il relativo valore è false).
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
Importante
Il parametro disposing deve essere false quando viene chiamato da un finalizzatore e true quando viene chiamato dal metodo IDisposable.Dispose. In altre parole, è true quando viene chiamato in modo deterministico e false quando viene invocato in modo non deterministico.
Il corpo del metodo è costituito da tre blocchi di codice:
Blocco per la restituzione condizionale se l'oggetto è già eliminato.
Blocco condizionale che libera le risorse gestite. Questo blocco viene eseguito se il valore di disposing è true. Le risorse gestite gratuite possono includere:
Oggetti gestiti che implementano IDisposable. Il blocco condizionale può essere usato per chiamare l'implementazione Dispose (eliminazione a catena). Se è stata usata una classe derivata di System.Runtime.InteropServices.SafeHandle per eseguire il wrapping della risorsa non gestita, è necessario chiamare l'implementazione SafeHandle.Dispose() qui.
Oggetti gestiti che utilizzano grandi quantità di memoria o consumano risorse scarse. Assegnare riferimenti a grandi oggetti gestiti a null per renderli più probabilmente non raggiungibili. Queste versioni vengono rilasciate più velocemente rispetto a quelle recuperate in modo non deterministico.
Blocco che libera le risorse non gestite. Questo blocco viene eseguito indipendentemente dal valore del parametro disposing.
Se la chiamata al metodo proviene da un finalizzatore, deve essere eseguito solo il codice che libera le risorse non gestite. L'implementatore è responsabile di garantire che il percorso false non interagisca con gli oggetti gestiti che potrebbero essere stati eliminati. Questo è importante perché l'ordine in cui il Garbage Collector elimina gli oggetti gestiti durante la finalizzazione non è deterministico.
Implementare il modello dispose
Tutte le classi non sigillate (o le classi Visual Basic non modificate come NotInheritable) dovrebbero essere considerate una potenziale classe base, perché potrebbero essere ereditate. Se si implementa il modello dispose per qualsiasi classe base potenziale, è necessario aggiungere i metodi seguenti alla classe :
Implementazione Dispose che chiama il metodo Dispose(bool).
Metodo Dispose(bool) che esegue la pulizia effettiva.
Se la classe gestisce risorse non gestite, fornire un override al metodo Object.Finalize oppure incapsulare la risorsa non gestita in un SafeHandle.
Importante
Un finalizzatore (un override Object.Finalize) è necessario solo se si fa riferimento direttamente a risorse non gestite. Si tratta di uno scenario altamente avanzato che in genere può essere evitato:
Se la classe fa riferimento solo a oggetti gestiti, è comunque possibile che la classe implementi il modello dispose. Non è necessario implementare un finalizzatore.
Se è necessario gestire le risorse non gestite, consigliamo vivamente di racchiudere l'handle non gestito di IntPtr in un SafeHandle. Il SafeHandle fornisce un finalizzatore in modo che non sia necessario scriverne uno manualmente. Per ulteriori informazioni, vedere il paragrafo "Maniglie sicure".
Classe base con risorse gestite
Di seguito è riportato un esempio generale di implementazione del modello dispose per una classe base proprietaria solo di risorse gestite.
using System;
using System.IO;
public class DisposableBase : IDisposable
{
// Detect redundant Dispose() calls.
private bool _isDisposed;
// Instantiate a disposable object owned by this class.
private Stream? _managedResource = new MemoryStream();
// 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 (!_isDisposed)
{
_isDisposed = true;
if (disposing)
{
// Dispose managed state.
_managedResource?.Dispose();
_managedResource = null;
}
}
}
}
Imports System.IO
Public Class DisposableBase
Implements IDisposable
' Detect redundant Dispose() calls.
Private _isDisposed As Boolean
' Instantiate a disposable object owned by this class.
Private _managedResource As Stream = New MemoryStream()
' 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(disposing As Boolean)
If Not _isDisposed Then
_isDisposed = True
If disposing Then
' Dispose managed state.
_managedResource?.Dispose()
_managedResource = Nothing
End If
End If
End Sub
End Class
Nota
Nell'esempio precedente viene utilizzato un oggetto fittizioMemoryStream per illustrare il modello. In alternativa, è possibile usare qualsiasi IDisposable.
Classe base con risorse non gestite
Ecco un esempio per implementare il modello dispose per una classe base che esegue l'override di Object.Finalize per pulire le risorse non gestite di cui è proprietario. L'esempio illustra anche un modo per implementare Dispose(bool) in modo thread-safe. La sincronizzazione potrebbe essere fondamentale quando si gestiscono risorse non gestite in un'applicazione multithread. Come accennato in precedenza, si tratta di uno scenario avanzato.
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
public class DisposableBaseWithFinalizer : IDisposable
{
// Detect redundant Dispose() calls in a thread-safe manner.
// _isDisposed == 0 means Dispose(bool) has not been called yet.
// _isDisposed == 1 means Dispose(bool) has been already called.
private int _isDisposed;
// Instantiate a disposable object owned by this class.
private Stream? _managedResource = new MemoryStream();
// A pointer to 10 bytes allocated on the unmanaged heap.
private IntPtr _unmanagedResource = Marshal.AllocHGlobal(10);
~DisposableBaseWithFinalizer() => 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)
{
// In case _isDisposed is 0, atomically set it to 1.
// Enter the branch only if the original value is 0.
if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
if (disposing)
{
_managedResource?.Dispose();
_managedResource = null;
}
Marshal.FreeHGlobal(_unmanagedResource);
}
}
}
Imports System
Imports System.IO
Imports System.Runtime.InteropServices
Imports System.Threading
Public Class DisposableBaseWithFinalizer
Implements IDisposable
' Detect redundant Dispose() calls in a thread-safe manner.
' _isDisposed == 0 means Dispose(bool) has not been called yet.
' _isDisposed == 1 means Dispose(bool) has been already called.
Private _isDisposed As Integer
' Instantiate a disposable object owned by this class.
Private _managedResource As Stream = New MemoryStream()
' A pointer to 10 bytes allocated on the unmanaged heap.
Private _unmanagedResource As IntPtr = Marshal.AllocHGlobal(10)
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(disposing As Boolean)
' In case _isDisposed is 0, atomically set it to 1.
' Enter the branch only if the original value is 0.
If Interlocked.CompareExchange(_isDisposed, 1, 0) = 0 Then
If disposing Then
_managedResource?.Dispose()
_managedResource = Nothing
End If
Marshal.FreeHGlobal(_unmanagedResource)
End If
End Sub
End Class
Nota
L'esempio precedente usa AllocHGlobal per allocare 10 byte nell'heap non gestito nel costruttore e liberare il buffer in Dispose(bool) chiamando FreeHGlobal. Si tratta di un'allocazione fittizia a scopo illustrativo.
In C# si implementa una finalizzazione fornendo un finalizzatore , non eseguendo l'override di Object.Finalize. In Visual Basic si crea un finalizzatore con Protected Overrides Sub Finalize().
Implementare il modello dispose per una classe derivata
Una classe derivata da una classe che implementa l'interfaccia IDisposable non deve implementare IDisposable, perché l'implementazione della classe base di IDisposable.Dispose viene ereditata dalle classi derivate. Per pulire invece una classe derivata, fornire quanto segue:
Metodo protected override void Dispose(bool) che esegue l'override del metodo della classe base ed esegue la pulizia effettiva della classe derivata. Questo metodo deve anche chiamare il metodo base.Dispose(bool) (MyBase.Dispose(bool) in Visual Basic) passando lo stato di smaltimento come argomento (parametro bool disposing).
Una classe derivata da SafeHandle che esegue il wrapping della risorsa non gestita (scelta consigliata) o un override del metodo Object.Finalize. La classe SafeHandle fornisce un finalizzatore che consente di liberare la necessità di crearne uno. Se si specifica un finalizzatore, deve chiamare l'overload Dispose(bool) con false come argomento.
Ecco un esempio del pattern generale per implementare il pattern di dispose per una classe derivata che usa un handle sicuro:
using System.IO;
public class DisposableDerived : DisposableBase
{
// To detect redundant calls
private bool _isDisposed;
// Instantiate a disposable object owned by this class.
private Stream? _managedResource = new MemoryStream();
// Protected implementation of Dispose pattern.
protected override void Dispose(bool disposing)
{
if (!_isDisposed)
{
_isDisposed = true;
if (disposing)
{
_managedResource?.Dispose();
_managedResource = null;
}
}
// Call base class implementation.
base.Dispose(disposing);
}
}
Imports System.IO
Public Class DisposableDerived
Inherits DisposableBase
' To detect redundant calls
Private _isDisposed As Boolean
' Instantiate a disposable object owned by this class.
Private _managedResource As Stream = New MemoryStream()
' Protected implementation of Dispose pattern.
Protected Overrides Sub Dispose(disposing As Boolean)
If Not _isDisposed Then
_isDisposed = True
If disposing Then
_managedResource?.Dispose()
_managedResource = Nothing
End If
End If
' Call base class implementation.
MyBase.Dispose(disposing)
End Sub
End Class
Nota
Nell'esempio precedente viene utilizzato un oggetto SafeFileHandle per illustrare il modello; è possibile utilizzare qualsiasi oggetto derivato da SafeHandle. Si noti che l'esempio non instanzia correttamente l'oggetto SafeFileHandle.
Ecco il modello generale per implementare il modello dispose per una classe derivata che esegue l'override di Object.Finalize:
using System.Threading;
public class DisposableDerivedWithFinalizer : DisposableBaseWithFinalizer
{
// Detect redundant Dispose() calls in a thread-safe manner.
// _isDisposed == 0 means Dispose(bool) has not been called yet.
// _isDisposed == 1 means Dispose(bool) has been already called.
private int _isDisposed;
~DisposableDerivedWithFinalizer() => Dispose(false);
// Protected implementation of Dispose pattern.
protected override void Dispose(bool disposing)
{
// In case _isDisposed is 0, atomically set it to 1.
// Enter the branch only if the original value is 0.
if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
{
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.
}
// Call the base class implementation.
base.Dispose(disposing);
}
}
Imports System.Threading
Public Class DisposableDerivedWithFinalizer
Inherits DisposableBaseWithFinalizer
' Detect redundant Dispose() calls in a thread-safe manner.
' _isDisposed == 0 means Dispose(bool) has not been called yet.
' _isDisposed == 1 means Dispose(bool) has been already called.
Private _isDisposed As Integer
Protected Overrides Sub Finalize()
Dispose(False)
End Sub
' Protected implementation of Dispose pattern.
Protected Overrides Sub Dispose(disposing As Boolean)
' In case _isDisposed is 0, atomically set it to 1.
' Enter the branch only if the original value is 0.
If Interlocked.CompareExchange(_isDisposed, 1, 0) = 0 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.
End If
' Call the base class implementation.
MyBase.Dispose(disposing)
End Sub
End Class
Maniglie sicure
La scrittura di codice per il finalizzatore di un oggetto è un'attività complessa che può causare problemi se non eseguita correttamente. È pertanto consigliabile costruire System.Runtime.InteropServices.SafeHandle oggetti anziché implementare un finalizzatore.
Un System.Runtime.InteropServices.SafeHandle è un tipo gestito astratto che incapsula un System.IntPtr che identifica una risorsa non gestita. In Windows potrebbe identificare un handle e in Unix un descrittore di file. Il SafeHandle fornisce tutta la logica necessaria per garantire che questa risorsa venga rilasciata una sola volta, quando il SafeHandle viene eliminato o quando tutti i riferimenti all'SafeHandle sono stati eliminati e l'istanza di SafeHandle viene finalizzata.
Il System.Runtime.InteropServices.SafeHandle è una classe base astratta. Le classi derivate forniscono istanze specifiche per diversi tipi di gestione. Queste classi derivate convalidano quali valori per il System.IntPtr sono considerati non validi e come liberare correttamente l'handle. Ad esempio, SafeFileHandle proviene da SafeHandle per avvolgere IntPtrs che identificano handle e descrittori di file aperti e sovrascrive il suo metodo SafeHandle.ReleaseHandle() per chiuderlo (tramite la funzione close in Unix o la funzione CloseHandle in Windows). La maggior parte delle API nelle librerie .NET crea una risorsa non gestita, la avvolge in un SafeHandle e la restituisce all'utente come SafeHandle secondo necessità, invece di restituire semplicemente il puntatore non elaborato. Nelle situazioni in cui si interagisce con un componente non gestito e si ottiene un IntPtr per una risorsa non gestita, è possibile creare un tipo di SafeHandle personalizzato per eseguirne il wrapping. Di conseguenza, pochi tipi non-SafeHandle devono implementare i finalizzatori. La maggior parte delle implementazioni di modelli eliminabili comporta solo il wrapping di altre risorse gestite, alcune delle quali potrebbero essere SafeHandle oggetti.
Implementare il pattern dispose utilizzando un handle sicuro personalizzato
Il codice seguente illustra come gestire le risorse non gestite implementando un SafeHandle.
using System;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
// Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle.
class LocalAllocHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private LocalAllocHandle() : base(ownsHandle: true) { }
// No need to implement a finalizer - SafeHandle's finalizer will call ReleaseHandle for you.
protected override bool ReleaseHandle()
{
Marshal.FreeHGlobal(handle);
return true;
}
// Allocate bytes with Marshal.AllocHGlobal() and wrap the result into a SafeHandle.
public static LocalAllocHandle Allocate(int numberOfBytes)
{
IntPtr nativeHandle = Marshal.AllocHGlobal(numberOfBytes);
LocalAllocHandle safeHandle = new LocalAllocHandle();
safeHandle.SetHandle(nativeHandle);
return safeHandle;
}
}
public class DisposableBaseWithSafeHandle : IDisposable
{
// Detect redundant Dispose() calls.
private bool _isDisposed;
// Managed disposable objects owned by this class
private LocalAllocHandle? _safeHandle = LocalAllocHandle.Allocate(10);
private Stream? _otherUnmanagedResource = new MemoryStream();
// 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 (!_isDisposed)
{
_isDisposed = true;
if (disposing)
{
// Dispose managed state.
_otherUnmanagedResource?.Dispose();
_safeHandle?.Dispose();
_otherUnmanagedResource = null;
_safeHandle = null;
}
}
}
}
Imports System
Imports System.IO
Imports System.Runtime.InteropServices
Imports Microsoft.Win32.SafeHandles
' Wraps the IntPtr allocated by Marshal.AllocHGlobal() into a SafeHandle.
Public Class LocalAllocHandle
Inherits SafeHandleZeroOrMinusOneIsInvalid
Private Sub New()
MyBase.New(True)
End Sub
' No need to implement a finalizer - SafeHandle's finalizer will call ReleaseHandle for you.
Protected Overrides Function ReleaseHandle() As Boolean
Marshal.FreeHGlobal(handle)
Return True
End Function
' Allocate bytes with Marshal.AllocHGlobal() and wrap the result into a SafeHandle.
Public Shared Function Allocate(numberOfBytes As Integer) As LocalAllocHandle
Dim nativeHandle As IntPtr = Marshal.AllocHGlobal(numberOfBytes)
Dim safeHandle As New LocalAllocHandle()
safeHandle.SetHandle(nativeHandle)
Return safeHandle
End Function
End Class
Public Class DisposableBaseWithSafeHandle
Implements IDisposable
' Detect redundant Dispose() calls.
Private _isDisposed As Boolean
' Managed disposable objects owned by this class
Private _safeHandle As LocalAllocHandle = LocalAllocHandle.Allocate(10)
Private _otherUnmanagedResource As Stream = New MemoryStream()
' 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(disposing As Boolean)
If Not _isDisposed Then
_isDisposed = True
If disposing Then
' Dispose managed state.
_otherUnmanagedResource?.Dispose()
_safeHandle?.Dispose()
_otherUnmanagedResource = Nothing
_safeHandle = Nothing
End If
End If
End Sub
End Class
Non è necessario implementare un finalizzatore, perché SafeHandle si occuperà della finalizzazione.
Non è necessario eseguire la sincronizzazione per garantire la sicurezza dei thread. Anche se esiste una race condition nell'implementazione Dispose di DisposableBaseWithSafeHandle, SafeHandle garantisce che SafeHandle.ReleaseHandle venga chiamato una sola volta.
L'origine di questo contenuto è disponibile in GitHub, in cui è anche possibile creare ed esaminare i problemi e le richieste pull. Per ulteriori informazioni, vedere la guida per i collaboratori.
Feedback su
.NET
.NET
è un progetto di open source. Selezionare un collegamento per fornire feedback:
Informazioni su come usare gli oggetti che implementano l'interfaccia IDisposable in .NET. I tipi che usano risorse non gestite implementano IDisposable per consentire il recupero delle risorse.
Informazioni sul criterio Dispose, il quale standardizza l'utilizzo e l'implementazione di finalizzatori e l'interfaccia "IDisposable" per il rilascio di risorse non gestite.
Informazioni su come implementare classi usando tecniche avanzate come classi statiche, classi parziali e inizializzatori di oggetti che possono migliorare la leggibilità, la gestibilità e l'organizzazione del codice.