IDisposableAnalyzers

repository·master·Indexed 19 days ago

https://github.com/dotnetanalyzers/idisposableanalyzers

A collection of Roslyn analyzers for C# designed to detect improper usage and implementation of the IDisposable pattern. It includes 26 rules (IDISP001 through IDISP026) to identify resource leaks, such as ignored disposable objects, missing Dispose calls in classes, and improper return types. The analyzers can be installed via NuGet or Paket and configured using Visual Studio .ruleset files, #pragma directives, or the [SuppressMessage] attribute.

Tokens
16.3K
Snippets
55
Records
96
Agent score
63%

What's inside IDisposableAnalyzers

  1. What is the SyntaxTreeCacheAnalyzer?

    master

    The SyntaxTreeCacheAnalyzer is a specialized analyzer that controls whether semantic models should be cached for syntax trees.

    Key Trade-offs:

    • Enabled (Default): Significantly speeds up analysis performance but increases Visual Studio's memory usage during compilation.
    • Disabled: Reduces memory consumption, which is useful for extremely large solutions where memory pressure is a concern.
    TopicValue
    IdSyntaxTreeCacheAnalyzer
    SeverityHidden
    EnabledTrue
    CategoryCaching
    CodeSemanticModelCacheAnalyzer
  2. Understand IDISP015: Member should not return created and cached instance

    master

    The IDISP015 rule flags methods that return both a newly created IDisposable instance and a previously cached/stored IDisposable instance.

    This pattern is problematic because it creates ambiguity for the caller: they cannot know whether they are responsible for disposing the returned object or if it is a shared instance that should remain alive. Mixing these two behaviors leads to potential memory leaks or ObjectDisposedException errors.

    // Example of a violation
    public IDisposable Bar()
    {
        if (condition)
        {
            // Returning a cached instance
            return this.disposable;
        }
    
        // Returning a newly created instance
        return File.OpenRead(string.Empty);
    }
  3. Understand the IDISP007: Don't dispose injected rule

    master
    The IDISP007 rule prevents you from disposing of IDisposable objects that you do not own. Disposing of an object that was injected (e.g., via dependency injection or passed as a parameter) can lead to bugs because the owner of that object may still need to use it or may attempt to dispose of it themselves, leading to ObjectDisposedException errors.
  4. Understand the IDISP001: Dispose created rule

    master

    The IDISP001 rule warns when you create an instance of a type that implements IDisposable without ensuring it is properly disposed. This rule is designed to encourage the use of using statements or declarations, as they are safer and more readable than manual Dispose() calls within try/finally blocks. The rule triggers even if an explicit Dispose() call is present, as the analyzer prefers the using pattern.

    // Violating code: The file remains open because the reader is never disposed.
    var reader = new StreamReader(fileName);
    return reader.ReadLine();
  5. Understand IDISP026: Class with no virtual DisposeAsyncCore method should be sealed

    master

    IDISP026 is a correctness rule that identifies classes implementing IAsyncDisposable that lack a virtual DisposeAsyncCore method. To prevent incorrect disposal patterns in inheritance hierarchies, such classes should be marked as sealed.

    Refer to the official Microsoft documentation for implementing IAsyncDisposable for more context on why this pattern is important.

  6. Understand and fix IDISP016: Don't use disposed instance

    master

    The IDISP016 rule detects code where a disposed instance is accessed after its Dispose() method has been called. Accessing a disposed object typically results in an ObjectDisposedException at runtime, making this a correctness issue.

    Example of a violation:

    var stream = File.OpenRead(string.Empty);
    stream.Dispose();
    var b = stream.ReadByte(); // Violation: stream is already disposed

    How to fix: Ensure that you only call Dispose() after the last usage of the instance.

  7. Understand IDISP008: Don't assign member with injected and created disposables

    master

    The IDISP008 rule prevents confusing ownership situations where a class member (field or property) can be assigned both an injected disposable (provided via constructor/parameter) and a locally created disposable. This ambiguity makes it unclear whether the class is responsible for disposing of that member.

    Violating Patterns

    1. Mixed assignment in constructor: Assigning a field with either a constructor parameter or a new instance makes it impossible to determine if the class owns the lifecycle.

    public class Foo : IDisposable
    {
        private readonly Mutex dependency;
    
        public Foo(Mutex dependency = null)
        {
            // Violation: Is 'dependency' owned by the caller or this class?
            this.dependency = dependency ?? new Mutex();
        }
    }

    2. Publicly settable properties with initializers: If a property has a public set accessor and an initializer, it is unclear if the property holds the initial instance or an instance provided from the outside.

    public class Foo
    {
        // Violation: Ownership of the stream is ambiguous
        public Stream Stream { get; set; } = File.OpenRead(string.Empty);
    }
  8. Understand IDISP006: Implement IDisposable

    master

    The IDISP006 analyzer rule detects when a class member (field or property) is assigned an instance of an IDisposable object created within that same type. When this occurs, the type is responsible for the lifecycle of that object and must implement IDisposable to properly dispose of the member.

    Rule Details:

    • ID: IDISP006
    • Severity: Warning
    • Category: IDisposableAnalyzers.Correctness
  9. Avoid using reference types in finalizer context (IDISP023)

    master

    The IDISP023 rule prevents the use of reference types within a finalizer context.

    Why this is important

    Accessing reference types during finalization is hazardous, especially during AppDomain shutdown. The CLR does not guarantee the order of finalization or garbage collection, meaning any reference type access could result in an access violation and process crash.

    Safe activities in a finalizer are limited to:

    • Accessing value types.
    • Calling P/Invoke methods (native code) to release resources.

    Note: Even accessing SafeHandle is considered unsafe because SafeHandle types have their own finalizers and should not be relied upon by their owners during finalization.

    How to fix violations

    Ensure that any access to reference types is wrapped within a check for the disposing parameter (typically found in the Dispose(bool disposing) pattern), which ensures the code only runs when called via explicit disposal rather than the finalizer.

    // Invalid: Accessing a reference type (logger) outside the 'if (disposing)' block
    protected virtual void Dispose(bool disposing)
    {
       if (disposing)
       {
       }
    
       this.logger.Log("In Dispose(bool)"); // violation!
    }
    
    // Valid: Accessing the reference type only when 'disposing' is true
    protected virtual void Dispose(bool disposing)
    {
       if (disposing)
       {
           this.logger.Log("In Dispose(bool)");
       }
    }
  10. Fix IDISP019: Call SuppressFinalize

    master

    The IDISP019 rule flags classes that have a virtual Dispose method but fail to call GC.SuppressFinalize(this) within that method.

    Motivation: When a class provides a virtual Dispose method, it is often part of a pattern where the base class or derived classes might implement a finalizer. Calling GC.SuppressFinalize(this) ensures that the garbage collector does not call the finalizer for an object that has already been explicitly disposed, which improves performance and prevents unnecessary finalization logic from running.

  11. Suppress IDISP012 violations via [SuppressMessage] attribute

    master

    You can suppress IDISP012 violations using the [SuppressMessage] attribute. This is useful for documenting why a specific instance of the rule is being ignored.

    Required parameters:

    • Category: IDisposableAnalyzers.Correctness
    • TargetName: IDISP012:Property should not return created disposable
    [System.Diagnostics.CodeAnalysis.SuppressMessage("IDisposableAnalyzers.Correctness", 
        "IDISP012:Property should not return created disposable", 
        Justification = "Reason...")]