+
Skip to content

dev.DevelopingOperators

Thomas Mann edited this page May 2, 2025 · 1 revision

Developing Advanced C# Operators

Preface

Developing basic C# operators — e.g., for math operations or list processing — is straightforward and a lot of fun. .NET handles many of the complicated aspects like automatic hot-reloading, memory management, and cleanup.

However, if you move on to more advanced topics like generating resources on the fly or connecting to 3rd-party libraries, there are a few important things to consider. This chapter collects various lessons we’ve learned during code reviews and real-world development.


How Disposing Works

Some resources cannot be cleaned up by the .NET garbage collector alone. Examples include:

  • Active network connections you opened
  • Listeners created via 3rd-party libraries like OSCRug or MIDI
  • DirectX resources like textures or buffers

In all of these cases, you need to correctly dispose of the resource when the operator instance’s lifetime ends. Operator instances are disposed in several situations:

  • When closing the Editor or the Player, all instantiated operator instances are disposed. If an instance still holds onto unmanaged resources and does not properly implement Dispose, it can prevent a clean application shutdown, resulting in zombie processes that must be killed via Task Manager. This is hard to debug and confusing for users.

  • When rebuilding assemblies (e.g., after editing a project or the operator library), all instances are also disposed.


Implementing the Correct Dispose Pattern

If you're unfamiliar with C#, you might naively try to implement the IDisposable interface like this:

// ❌ DO NOT DO THIS
public void Dispose()
{
    ReleaseCpuData();
}

While this compiles and may seem to work, it has multiple problems:

  • It's not thread-safe.
  • The Dispose method might be called multiple times from different places, which could lead to incorrect or unsafe behavior.

The correct implementation looks like this:

protected override void Dispose(bool isDisposing)
{
    if (!isDisposing)
        return;

    ReleaseCpuData();
}

You do not need to implement the parameterless Dispose() method — it is already provided by the base class Instance.


Avoid Destructors

You may occasionally come across code like this:

[Guid("9412d0f4-dab8-4145-9719-10395e154fa7")]
public sealed class NdiOutput : Instance<NdiOutput>, IStatusProvider
{
    public NdiOutput()
    {
        TextureOutput.UpdateAction = Update;
    }

    // ❌ DO NOT DO THIS
    ~NdiOutput()
    {
        Dispose();
    }
}

Please avoid implementing a destructor. The base class Instance already implements it and will correctly call Dispose(bool isDisposing) when needed.

Clone this wiki locally
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载