>Blog>Memory leaks in Revit API applications

Memory leaks in Revit API applications

September 04, 2025
cover image

One of the advantages of C# is automatic garbage collection and memory cleaning. In most cases we do not need to think about memory because execution runtime will clean it up. However, there are some situations when we can create memory leaks and we should know how to avoid it.

Memory leaks caused by unmanaged code

They are also called ‘resource leaks’. It can happen when we have an object which uses unmanaged resources and this object is not properly disposed of. Make sure you use ‘using’ with such objects like Transaction, HttpClient, Stream, Bitmap.

You have to add using to all the IDisposables.

using var response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);

using var download = await response.Content.ReadAsStreamAsync();

These constructions compile as follows:

var download = await response.Content.ReadAsStreamAsync();
try
{
   //some code
}
finally
{
   download.Dispose();
}

"So, in all cases, the Dispose method will be called and the unmanaged resources will be cleaned up."

Pay attention to situations like this:

public class MyClass 
{
      private readonly Bitmap _pumpBitmap = LoadPumpBitmap();


//some code
}

Bitmap is IDisposable, but it won’t be disposed automatically when MyClass is garbage-collected. You should write a Dispose method for that case:

public class MyClass: IDisposable
{
   private readonly Bitmap _bitmap = LoadBitmap();


   public void Dispose()
   {
       _bitmap.Dispose();
   }
   
   //some code
}

Memory leaks caused by events

The main reason for this is incorrect event subscription management. If you subscribe to an event but do not unsubscribe, the object may remain in memory. The Garbage Collector will detect the existing reference to this object and will not reclaim it.

Bad practice:

private ImportMappingRule[] MyCollection { get; set; } = [];

private void OnNodeGroupRuleSelected(object? sender, PropertyChangedEventArgs args)
{
   
   //old collection objects still exist and still have subscriptions
   MyCollection = GetMyCollection();
  
   foreach (var rule in MyCollection)
   {
       rule.PropertyChanged += OnRevitFamilySelected;
   }
}

Good practise:

private ImportMappingRule[] MyCollection { get; set; } = [];

private void OnNodeGroupRuleSelected(object? sender, PropertyChangedEventArgs args)
{
   //delete subscription for old objects
   foreach (var rule in MyCollection)
   {
       rule.PropertyChanged -= OnRevitFamilySelected;
   }
  
   //get new collection, old objects will be destroyed by GC
   MyCollection = GetMyCollection();
  
   foreach (var rule in MyCollection)
   {
       rule.PropertyChanged += OnRevitFamilySelected;
   }
}

Events for DockablePane

Things can get worse if you’re using a DockablePane with a FrameworkElementCreator. Every time the active document changes, it creates a new Page and therefore a new PageViewModel. If your ViewModel has event subscriptions and you don’t unsubscribe, you’ll end up with multiple subscriptions, memory leaks, and performance issues. After the first document change, the handler will fire twice; the more changes, the more duplicate executions.

Bad practice:

public DockPaneViewModel()
{
   var application = Context.Application;
  
   //this unsubscription will do nothing, because new VM is not subscribed yet
   application.DocumentChanged -= TrackChanges;
   application.DocumentChanged += TrackChanges;
}

When the ViewModel is created, nothing is subscribed to its methods, so -= doesn’t do anything. You should create an Unsubscribe method and call it when the Page is unloaded. You can also create a Subscribe method if you only need subscriptions while the page is loaded:

//The ViewModel
public void Subscribe()
{
   // use handlers to avoid API context errors
   RevitShell.RaiseActionOnIdling(_ =>
   {
       var application = Context.Application;
       application.DocumentChanged += TrackChanges;
   });
}


public void Unsubscribe()
{
   RevitShell.RaiseActionOnIdling(_ =>
   {
       var application = Context.Application;
       application.DocumentChanged -= TrackChanges;
   });
}
//The Page
public DockablePanePage(DockablePaneViewModel viewModel)
{
   _viewModel = viewModel;
   DataContext = viewModel;

   Loaded += OnLoaded;
   Unloaded += OnUnloaded;

   InitializeComponent();
}

private void OnLoaded(object o, RoutedEventArgs routedEventArgs)
{
   _viewModel.Subscribe();
}
private void OnUnloaded(object sender, RoutedEventArgs args)
{
   _viewModel.Unsubscribe();
}

That’s it - after this, you will no longer encounter multiple subscriptions. Don’t forget to verify it in debug mode: the Subscribe action should hit the breakpoint only once.

Conclusion

As Nick Chapsas once said, you might never encounter memory leaks throughout your entire C# career. The language is designed very well, so even if you make mistakes with memory, they can easily go unnoticed. Still, it’s important to be aware of these issues, learn how to avoid them, and always strive to write better code. Keep coding!