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
相关产品推荐
相关产品推荐

