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
usingstatement -
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
-
Best practice: What’s the recommended pattern for managing DisplayConduit lifecycle in commands?
-
Field vs Local:
-
If using IDisposable, the conduit must be a local variable (can’t use
usingwith fields) -
Is there a reason to keep conduits as command instance fields?
-
Are there performance implications of creating conduits per command invocation?
-
-
Disposal responsibility:
-
Should
DisplayConduit.Dispose()callViews.Redraw()? -
Is there any cleanup needed beyond
Enabled = false? -
Should we dispose/clear internal collections like
List<Curve>?
-
-
Thread safety: Are there threading concerns when disposing conduits?
-
Base class design: I notice that
DisplayConduititself doesn’t implementIDisposable. Is this intentional? Should it?
Current Observations
-
DisplayConduitbase class does not implementIDisposable -
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:
-
Recommended patterns for exception-safe DisplayConduit usage -
Whether IDisposable is appropriate for this scenario -
Any hidden considerations or gotchas I might be missing -
Real-world patterns used in production plugins
