How to debounce function calls in .NET with on-board resources

How to debounce function calls in .NET with on-board resources for C# and VB.NET
How to debounce function calls in .NET with on-board resources for C# and VB.NET

The easiest way to debounce function calls in C# & VB.NET is by simply using the „Subject“ class from the „System.Reactive.Subjects“ namespace. Create an instance of this class, register a „callback“ and provide the values by calling the „OnNext“ method. This way you don’t need to install external or third-party libraries.

Without debouncing methods in C# or VB.NET – a visual example

Do you know these typical situations where your impatient application user is like bombing your controls with inputs – therefore triggering functions? I think every .NET developer will encounter this kind of situation at some point in time. Let’s see in today’s blogpost how we can actually handle these situations without external / third-party tools.

infoIn a hurry? Go to the quick examples section, if you just need some code. Look at the detailed explanation, if you need more in-depth information. However make sure, that you understand the stuff behind the scenes :)!

Maybe one of these situations look like the following image, keep an eye on the output window of Visual Studio as well. Please don’t get confused, as this is an image from my custom CRM software being translated to german. I added some english test projects for this image though:

Demonstrating input spam in .NET without debouncing function calls
Demonstrating input spam in .NET without debouncing function calls

As you can see, each time you’re typing a letter into the textbox, you will trigger the corresponding function. With the average typing speed of a user, being between 190 and 200 characters per minute (CPM), this is very very bad! A more experienced or like professional writer could even achieve up to twice the amount of words / characters.

Usually we would wish for something like the image below – being the result of this blogpost:

The end result with debounced calls
The end result with debounced calls

I mean, the example in the image is very typical and actually no „wrong use“ of the application’s user himself. This is exactly where we – being developers – should come into play. It’s our job to react to the users input speed and adjust „behind the scenes stuff“ – accordingly!

An on-board resource to debounce function calls

The on-board Subject class of the System.Reactive.Subjects namespace to debounce function calls in .NET
The on-board Subject class of the System.Reactive.Subjects namespace to debounce function calls in .NET

Being a developer, it’s pretty nice to just let your creativity flow and develop things from start, but not always, right? I mean, there are circumstances, where you will be thinking: „God, this is such a common task, isn’t there already some stuff for that!? I don’t want to reinvent the wheel!“. This is exactly where the „Subject“ class from the System.Reactive.Subjects„-namespace will help!

The easiest way to explain the usage of the subject in our case, is like you being a dad (or of course a mom…). Imagine, that your child is asking you about eating candy over and over again. No matter how often your child will be asking (of course depending on your parenting style), your answer will probably – still – be „No, not yet!“. I mean your mini-me shouldn’t probably be allowed to eat candy, everytime he / she asks. Only if your son behaved nice and didn’t ask for a specific amount of time.

Now swap yourself – being the dad in the previous example – with some sort of thing, some subject! That subject will now „filter“ the requests for you instead and only let „valid“ ones pass through. The requests are now coming from some sort of user input, instead of being your child requesting candy.

Take a look at this image representing the situation explained from above:

Debouncing multiple input to one single function call in .NET
Debouncing multiple input to one single function call in .NET

As you can see, there’s so much input happening (mouseclicks in this case), but the „debouncer“ will only allow 1 function call. In the next section, we will take a look at actually implementing the mentioned „subject“ class. Especially that „time passed after last input“ is pretty important.

Installing System.Reactive

I know, I know, you’re thinking: „But Robert, you said no external libraries“, but hear me out… I mean, this stuff is coming from microsoft itself residing inside of the „System.Reactive“ namespace, so – for me this isn’t „third-party“, etc. If you write the correct class name, it will even suggest you to install the package within 3 seconds – so you don’t even need like the NuGet paket manager, etc. (Of course you can use it though).

We will be using the easiest method to install the small package, just declare some variable or a field of the type and open the suggestions by hovering over the error and clicking „show potential fixes“. Or you can just use the hotkey „Ctrl+.“. We will be using / declaring the class in the section below, as well, so we will need it anyways!

Take a look at the following screenshot to install System.Reactive or go into the NuGet paket manager as usual and install it manually.

Installing the System.Reactive package to use the Subject class
Installing the System.Reactive package to use the Subject class

Using the Subject class for debouncing

After we’ve discussed the basic problem and some theoretical approaches, we will now take a look at an actual usage example. When talking about debouncing, we should first take a look at some „well-known values“ like: „What is a commonly used delay for debouncing?“. Searching further, you will find some values being between like 150 and 400 milliseconds.

As always, I would encourage you to adjust these times to your specific usecase, as these can differ from project to project and so on. In the end, we will be using 400 milliseconds for our example, as this was used by a big „library“ consuming inputs, I’ve commonly used in the past. Sadly, I don’t remember the name, lol.

infoKeep in mind, that your used technology (WPF or Winforms) isn’t pretty much important. You will be able to use the common „pattern“ of the Subject class for both of those. Your used language (C# or VB.NET) isn’t relevant, as well. You might need to adjust the code for like using MVVM, etc. – but in general, it’s the same.

Declaring and instantiating the subject class

With an emphasis on the above info-box, we will now declare and instantiate an example instance of the subject. At this point, you should know the data being used for the „Subject“, as it needs a generic type parameter. If you’re trying to debounce an input string from like a textbox, your generic type would therefore be a string.

Take a look at this example, preparing to debounce a string input from a textbox, being in a Windows Forms (Winforms) app – again, you can easily adjust to fit your normal or MVVM WPF app as well:

// import the namespace for the Subject
using System.Reactive.Subjects;

// a typical Winforms "Form"
public class Form1 : Form
{

  // declare the Subject
  Subject<string> _searchSubject;

  // things get typically instantiated
  // inside of the constructor
  public Form1()
  {
    _searchSubject = new Subject<string>();
    _searchSubject
      .Throttle(TimeSpan.FromMilliseconds(400));
  }

}
' import the namespace for the Subject
Imports System.Reactive.Subjects

' a typical Winforms "Form"
Public Class Form1

  ' declare the Subject
  Private _searchSubject As Subject(Of String)

  ' things get typically instantiated
  ' inside of the constructor
  Public Sub New()
    _searchSubject = New Subject(Of String)()
    _searchSubject _
      .Throttle(TimeSpan.FromMilliseconds(400))
  End Sub

End Class

After we have declared and instantiated a subject with the correct generic type being „string“ (for our usecase), we can proceed. So far, there’s like nothing happening! This is, because we are missing like two steps when using the subject class. Take a look at the next section, soon!

In the last part of this section, we will configure the „_searchSubject“ instance, to debounce the input by 400ms. Just call the „Throttle“ method with an appropriate parameter as in the example. We’re using the „TimeSpan.FromMilliseconds“ helper here!

Feeding the user input into our subject

Now that we have declared and created an actual instance of the subject class, we can start feeding the actual input of the user to the subject. Remember, this would be the child asking the dad in the above example. As the raw input will usually be coming from some sort of input controls like textboxes, we will use one of those for our typical example.

So the first step would be, to do something like this: „Everytime the text of the textbox changes, I will feed that information to our subject“. Basically, we need to therefore react to the user typing some input into the textbox and that’s pretty basic, right?

Take a look at this – you of course have to make sure, that you actually have a textbox on your form like „TextBox1“:

public class Form1 : Form
{

  public Form1()
  {
    // the other code from above!
    TextBox1.TextChanged += TextBox1_TextChanged;
  }

  private void TextBox1_TextChanged(object? sender, EventArgs e)
  {
    // each time the user types a letter, we will get here
    // now feed the information to our subject from
    // the code from above
    _searchSubject.OnNext(TextBox1.Text);
  }

}
Public Class Form1

  Public Sub New()
    ' the other code from above
    ' to stay the same with the C# example
    ' you could of course use the Handles keyword...
    AddHandler TextBox1.TextChanged, AddressOf TextBox1.TextChanged
  End Sub

  Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs)
    ' each time the user types a letter, we will get here
    ' now feed the information to our subject from
    ' the code from above
    _searchSubject.OnNext(TextBox1.Text)
  End Sub

End Class

Registering a „callback“

Right now, we have already created the subject instance and configured it, to have some sort of throttling process going on. We’ve also created a handler function – listening to the TextBox TextChanged event – to feed information to our subject. To make the final step, we just need to tell the subject: „Hey, this is our function you should be calling, when everything’s alright!“.

So let’s now tell our subject to call the specific method we want to be called – after it took out all the noise:

public class Form1 : Form
{

  public Form1()
  {
    // the other code from above!
    // notice the additional "Subscribe" call
    _searchSubject
      .Throttle(TimeSpan.FromMilliseconds(400))
      .Subscribe(DebouncedSearchTextChanged);
  }

  private void DebouncedSearchTextChanged(string searchText)
  {
    // do you API stuff without the noise!
  }

}
Public Class Form1

  Public Sub New()
    ' the other code from above!
    ' notice the additional "Subscribe" call
    _searchSubject _
      .Throttle(TimeSpan.FromMilliseconds(400)) _
      .Subscribe(DebouncedSearchTextChanged)
  End Sub

  Private Sub DebouncedSearchTextChanged(searchText As String)
    ' do you API stuff without the noise!
  End Sub

End Class

How it looks now – being debounced!

Our calls now look like the following. I’m just typing a lot of stuff and only if I stop typing with an additional delay of like 400ms – our target function will be called, great!

Demonstrating input spam in .NET with debouncing function calls
Demonstrating input spam in .NET with debouncing function calls

Quick example

A quick example of debouncing method calls using the subject class
A quick example of debouncing method calls using the subject class

In this section, you will find pretty quick examples of the subject class usage. If you need more explanation / details, take a look at the previous section.

Keep in mind, that this example can be applied to WPF and Windows Forms, but as WPF possibly implies using MVVM (or not), I thought, the Winforms example would be more typical and easier.

// import the namespace for the Subject
using System.Reactive.Subjects;

public class Form1 : Form
{

  // declare the Subject
  Subject<string> _searchSubject;

  public Form1()
  {
    // create an instance
    _searchSubject = new Subject<string>();
    // configure
    _searchSubject
      .Throttle(TimeSpan.FromMilliseconds(400))
      .Subscribe(DebouncedSearchTextChanged);
    TextBox1.TextChanged += TextBox1_TextChanged;
  }

  private void TextBox1_TextChanged(object? sender, EventArgs e)
  {
    // each time the user types a letter, we will get here
    // now feed the information to our subject
    _searchSubject.OnNext(TextBox1.Text);
  }

  private void DebouncedSearchTextChanged(string searchText)
  {
    // do you API stuff without the noise!
    System.Diagnostics.Debug.WriteLine($"Debounced: {searchText}");
  }

}
' import the namespace for the Subject
Imports System.Reactive.Subjects

Public Class Form1
    Inherits Form

    ' declare the Subject
    Private _searchSubject As Subject(Of String)

    Public Sub New()
        ' create an instance
        _searchSubject = New Subject(Of String)()
        ' configure
        _searchSubject _
            .Throttle(TimeSpan.FromMilliseconds(400)) _
            .Subscribe(AddressOf DebouncedSearchTextChanged)
    End Sub

    Private Sub TextBox1_TextChanged(ByVal sender As Object?, ByVal e As EventArgs) Handles TextBox1.TextChanged
        ' each time the user types a letter, we will get here
        ' now feed the information to our subject
        _searchSubject.OnNext(TextBox1.Text)
    End Sub

    Private Sub DebouncedSearchTextChanged(ByVal searchText As String)
        ' do you API stuff without the noise!
        System.Diagnostics.Debug.WriteLine($"Debounced: {searchText}")
    End Sub

End Class

Wrapping up – debouncing method calls

In today’s blogpost, I’ve explained a common problem when working with user input. There are many situations where users will just bomb the UI by typing characters or like selecting values from a selection control. This will trigger many unwanted function calls, eventually resulting in too many API calls or performance problems.

We then used the „Subject“-class residing in the „System.Reactive.Subjects“-namespace to implement some sort of throttling / debouncing. Even if many inputs will be provided by the user, our „Subject“ instance will take care of only triggering the target function, if a specific amount of time has passed (since the last provided input).

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert