How to Resolve Error "CS0575: Only class types can contain destructors" in C#
The Compiler Error CS0575 is a structural restriction error. The message reads: "Only class types can contain destructors".
In C#, a Destructor (syntax: ~TypeName()) is technically a Finalizer. It is a mechanism used by the Garbage Collector (GC) to clean up unmanaged resources before an object on the Heap is permanently destroyed.
Structs are Value Types. They usually live on the Stack or are embedded inline within other objects. They are not tracked individually by the Garbage Collector in the same way Reference Types (Classes) are. Because the GC does not manage the lifecycle of value types via the Finalization Queue, structs cannot have destructors.
This guide explains how to handle resource cleanup in structs without using finalizers.
Understanding Value Types vs. Finalizers
- Classes (Reference Types): Allocated on the Managed Heap. The GC tracks them. When they are no longer used, the GC reclaims memory. If a Finalizer (
~Class) is present, the GC runs it to free unmanaged resources (like C++ pointers). - Structs (Value Types): Often allocated on the Stack. They are automatically cleaned up when the method returns or the containing scope ends. The GC does not "finalize" them. Therefore, declaring a finalizer for a struct is logically impossible in the .NET runtime model.
Scenario: Defining a Destructor in a Struct
This error occurs when a developer treats a struct exactly like a class, perhaps copying code that includes cleanup logic.
Example of error:
public struct Point
{
public int X;
public int Y;
// ⛔️ Error CS0575: Only class types can contain destructors.
// Structs cannot allow the Garbage Collector to run custom cleanup logic.
~Point()
{
System.Console.WriteLine("Point destroyed");
}
}
Solution 1: Remove the Destructor (Standard Fix)
If your struct contains only managed data (like int, double, string, List<T>), you do not need a destructor at all. The runtime automatically handles the memory.
Solution: simply delete the destructor code.
public struct Point
{
public int X;
public int Y;
// ✅ Correct: No destructor needed.
// When 'Point' goes out of scope, X and Y are popped off the stack instantly.
}
Solution 2: Implement IDisposable (Resource Management)
If your struct is holding onto unmanaged resources (like a native file handle, a window handle, or unmanaged memory pointers), you need a way to release them. Since you cannot use a destructor, you must implement the IDisposable interface.
This relies on the user of the struct correctly calling .Dispose() (or using a using statement).
Solution:
using System;
using System.Runtime.InteropServices;
public struct NativeWrapper : IDisposable
{
private IntPtr _handle;
public NativeWrapper(int size)
{
// Allocate unmanaged memory
_handle = Marshal.AllocHGlobal(size);
}
// ✅ Correct: Use Dispose() for cleanup instead of a destructor
public void Dispose()
{
if (_handle != IntPtr.Zero)
{
Marshal.FreeHGlobal(_handle);
_handle = IntPtr.Zero;
Console.WriteLine("Memory freed.");
}
}
}
public class Program
{
static void Main()
{
// Usage ensures cleanup happens
using (var native = new NativeWrapper(1024))
{
// Do work...
} // Dispose is called automatically here
}
}
Important Difference: With a class, a Finalizer is a "safety net" in case the user forgets to call Dispose(). With a struct, there is no safety net. If the user forgets to call Dispose(), the unmanaged resource will leak until the application terminates.
Conclusion
CS0575 enforces the architectural difference between Stack (Value) and Heap (Reference) memory management.
- Check the Type: Is it a
struct? - Remove the Tilde: Delete
~StructName(). - Clean up Manually: If you need cleanup logic, implement
IDisposableand use theusingstatement.