Table of Contents

Events in Discord.Net

Events in Discord.Net are consumed in a similar manner to the standard convention, with the exception that every event must be of the type Task and instead of using EventArgs, the event's parameters are passed directly into the handler.

This allows for events to be handled in an async context directly instead of relying on async void.

Usage

To receive data from an event, hook into it using C#'s delegate event pattern.

You may either opt to hook an event to an anonymous function (lambda) or a named function.

Safety

All events are designed to be thread-safe; events are executed synchronously off the gateway task in the same context as the gateway task.

As a side effect, this makes it possible to deadlock the gateway task and kill a connection. As a general rule of thumb, any task that takes longer than three seconds should not be awaited directly in the context of an event, but should be wrapped in a Task.Run or offloaded to another task.

This also means that you should not await a task that requests data from Discord's gateway in the same context of an event. Since the gateway will wait on all invoked event handlers to finish before processing any additional data from the gateway, this will create a deadlock that will be impossible to recover from.

Exceptions in commands will be swallowed by the gateway and logged out through the client's log method.

Common Patterns

As you may know, events in Discord.Net are only given a signature of Func<T1, ..., Task>. There is no room for predefined argument names, so you must either consult IntelliSense, or view the API documentation directly.

That being said, there are a variety of common patterns that allow you to infer what the parameters in an event mean.

Entity, Entity

An event handler with a signature of Func<Entity, Entity, Task> typically means that the first object will be a clone of the entity before a change was made, and the latter object will be an attached model of the entity after the change was made.

This pattern is typically only found on EntityUpdated events.

Cacheable

An event handler with a signature of Func<Cacheable, Entity, Task> means that the before state of the entity was not provided by the API, so it can either be pulled from the client's cache or downloaded from the API.

See the documentation for Cacheable for more information on this object.

Note

Many events relating to a Message entity (i.e., MessageUpdated and ReactionAdded) rely on the client's message cache, which is not enabled by default. Set the MessageCacheSize flag in DiscordSocketConfig to enable it.

Sample

using Discord;
using Discord.WebSocket;

public class Program
{
    private static DiscordSocketClient _client;	
    public static async Task MainAsync()
    {
        // When working with events that have Cacheable<IMessage, ulong> parameters,
        // you must enable the message cache in your config settings if you plan to
        // use the cached message entity. 
        var _config = new DiscordSocketConfig { MessageCacheSize = 100 };
        _client = new DiscordSocketClient(_config);

        await _client.LoginAsync(TokenType.Bot, Environment.GetEnvironmentVariable("DiscordToken"));
        await _client.StartAsync();

        _client.MessageUpdated += MessageUpdated;
        _client.Ready += () => 
        {
            Console.WriteLine("Bot is connected!");
            return Task.CompletedTask;
        };
        
        await Task.Delay(-1);
    }

    private static async Task MessageUpdated(Cacheable<IMessage, ulong> before, SocketMessage after, ISocketMessageChannel channel)
    {
        // If the message was not in the cache, downloading it will result in getting a copy of `after`.
        var message = await before.GetOrDownloadAsync();
        Console.WriteLine($"{message} -> {after}");
    }
}