Implement IDisposable
ID |
csharp.implement_idisposable |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
CSharp |
Tags |
CWE:772, idisposable, reliability, resource-leak |
Description
Reports classes that own an IDisposable field but do not themselves implement
IDisposable. Without the IDisposable contract there is no documented place
for callers to release the contained resource and the leak surfaces only under
load.
Rationale
IDisposable is the .NET runtime’s deterministic-cleanup contract. When a class
holds a disposable handle as a field, the owning class is implicitly responsible
for releasing it; implementing IDisposable (and a matching Dispose(bool)
template) is how that responsibility is communicated and enforced. Skipping the
interface forces every caller to either guess or leak.
public class BadOwner // FLAW
{
private FileStream _stream;
public BadOwner(string path)
{
_stream = new FileStream(path, FileMode.Open);
}
}
public class GoodOwner : IDisposable // OK
{
private FileStream _stream;
public GoodOwner(string path)
{
_stream = new FileStream(path, FileMode.Open);
}
public void Dispose() => _stream.Dispose();
}
A class is not reported when it already implements IDisposable some other way, or
when implementing it is not even possible:
public interface IOwnsResource : IDisposable { }
public class ResourceOwner : IOwnsResource // OK - implements IDisposable
{ // transitively through IOwnsResource
private FileStream _stream;
public ResourceOwner(string path) { _stream = new FileStream(path, FileMode.Open); }
public void Dispose() => _stream.Dispose();
}
public class AuthHandler : DelegatingHandler // OK - DelegatingHandler (a known BCL type)
{ // already implements IDisposable
private readonly SemaphoreSlim semaphore = new(1);
}
public static class StaticOwner // OK - a static class cannot implement
{ // any interface at all
private static readonly HttpClient httpClient = new();
}