Should DisplayConduit-derived classes implement IDisposable?

Background

I’m working with a custom DisplayConduit class for visualizing curves in Rhino commands. The typical pattern is:

public class CurveDisplayer : DisplayConduit
{
    public List<Curve> Curves { get; set; } = [];
    public bool IsShowDirection { get; set; }
    // ... other properties
}

public class CyCurveReverse : Command
{
    private CurveDisplayer _displayer = new CurveDisplayer();
    
    protected override Result RunCommand(RhinoDoc doc, RunMode mode)
    {
        // Get curves
        var result = RhinoGet.GetMultipleObjects(...);
        
        _displayer.Curves = curves;
        _displayer.IsShowDirection = true;
        _displayer.Enabled = true;  // Enable display
        doc.Views.Redraw();
        
        // User interaction
        result = RhinoGet.GetString(...);
        
        // Process curves (could throw exceptions)
        foreach (var objRef in objRefs)
        {
            // ... operations that might fail
        }
        
        _displayer.Enabled = false;  // Disable display
        doc.Views.Redraw();
        
        return Result.Success;
    }
}

The Problem

If an exception occurs between Enabled = true and Enabled = false, the displayer remains active, causing:

  • Visual artifacts in the viewport

  • Memory leaks (curves remain referenced)

  • Unexpected behavior in subsequent operations

Question

Should custom DisplayConduit classes implement IDisposable to ensure proper cleanup?

Option A: Implement IDisposable

public class CurveDisplayer : DisplayConduit, IDisposable
{
    public void Dispose()
    {
        Enabled = false;
        Curves?.Clear();
        // Should we call RhinoDoc.ActiveDoc.Views.Redraw() here?
    }
}

// Usage with using statement
using (var displayer = new CurveDisplayer())
{
    displayer.Enabled = true;
    // ... operations
} // Automatically disabled even if exception occurs

Pros:

  • Standard .NET pattern for resource management

  • Compiler guarantees cleanup with using statement

  • Exception-safe by design

  • Clear semantic intent

Cons:

  • Must use local variable instead of field

  • Cannot reuse conduit instance across command calls

Option B: Manual try-finally

try
{
    _displayer.Enabled = true;
    // ... operations
}
finally
{
    _displayer.Enabled = false;
    doc.Views.Redraw();
}

Pros:

  • Can keep conduit as field

  • Can reuse same instance

  • No API changes needed

Cons:

  • Easy to forget in complex code

  • Less idiomatic .NET

  • Manual management required

Discussion Points

  1. Best practice: What’s the recommended pattern for managing DisplayConduit lifecycle in commands?

  2. Field vs Local:

    • If using IDisposable, the conduit must be a local variable (can’t use using with fields)

    • Is there a reason to keep conduits as command instance fields?

    • Are there performance implications of creating conduits per command invocation?

  3. Disposal responsibility:

    • Should DisplayConduit.Dispose() call Views.Redraw()?

    • Is there any cleanup needed beyond Enabled = false?

    • Should we dispose/clear internal collections like List<Curve>?

  4. Thread safety: Are there threading concerns when disposing conduits?

  5. Base class design: I notice that DisplayConduit itself doesn’t implement IDisposable. Is this intentional? Should it?

Current Observations

  • DisplayConduit base class does not implement IDisposable

  • Documentation doesn’t mention disposal patterns

  • Most examples don’t show exception handling for conduit cleanup

Request

Looking for guidance from the community and McNeel team on:

  • :white_check_mark: Recommended patterns for exception-safe DisplayConduit usage

  • :white_check_mark: Whether IDisposable is appropriate for this scenario

  • :white_check_mark: Any hidden considerations or gotchas I might be missing

  • :white_check_mark: Real-world patterns used in production plugins


I think both options are equally acceptable. Don’t overthink this, and choose one that you feel comfortable with.

1 Like

Did you generate the post with ai ?

yes ,my english is bad.

3 Likes

We have a Base Builder class that we inherit from for each of our “builders”. And so if there is an exception within a Builder command, then the Rhino Exception handler raised the event that we handle.

We then use that event to clean up resources such as display conduits, etc. And we also then log the exception for future analysis.

1 Like