You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Discord.NET RemoveReactionAsync操作旧消息抛空异常问题

问题原因

空异常的核心原因是直接访问了Cacheable类型的Value属性:

  • Cacheable<TEntity, TId>是Discord.NET设计的缓存包装类型,只有对应实体已经存在于客户端本地缓存时,HasValue属性才为true,此时访问Value才能拿到有效实例
  • 机器人启动前发送的历史消息、未加载到缓存的用户/频道实体,本地没有缓存,直接访问Value会返回null触发空引用
  • 原有判断用户是否为Bot的写法也存在同样隐患:如果触发反应的用户不在本地缓存,sr.User.GetValueOrDefault()会返回null,直接访问IsBot同样会抛错

修复方案

不需要拉取完整的消息实体就能删除反应,Discord.NET的IMessageChannel提供了直接传入消息ID、表情、用户ID的RemoveReactionAsync重载,性能更好也不会受消息缓存影响。所有Cacheable类型统一用GetOrDownloadAsync()方法获取实例,该方法会自动优先读本地缓存,缓存不存在时自动请求API拉取实体,不会返回空。

修正后的可运行代码如下:

private async Task ReactionAdded(Cacheable<IUserMessage, ulong> message, Cacheable<IMessageChannel, ulong> channel, SocketReaction sr)
{
    try
    {
        // 跳过机器人添加的反应,缓存未命中时自动拉取用户实体
        var user = await sr.User.GetOrDownloadAsync();
        if (user.IsBot) return;

        // 若需要只移除指定EMOJI,在这里加判断即可,例如:
        // if (sr.Emote.Name != "目标表情名") return;
        // 自定义服务器表情可通过sr.Emote.Id匹配

        // 获取频道实例,缓存未命中时自动拉取,无需下载完整消息体即可直接删除反应
        var msgChannel = await channel.GetOrDownloadAsync();
        await msgChannel.RemoveReactionAsync(sr.MessageId, sr.Emote, sr.UserId);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"移除反应失败: {ex.Message}");
    }
}

额外说明

  • 如果业务逻辑确实需要拿到完整消息实体(比如判断消息发送者、读取消息内容),不要直接访问message.Value,改用await message.GetOrDownloadAsync()获取即可,该方法对机器人启动前的历史消息同样有效
  • GetOrDownloadAsync()只有在缓存未命中时才会发起API请求,不会额外增加太多开销,也能自动适配Discord的速率限制规则
  • 消息被删除、机器人没有频道操作权限等场景触发的异常会被catch块捕获,不会导致程序崩溃

内容的提问来源于stack exchange,提问作者hyspo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 19:09:24