为什么这个 if 语句不能正常工作

Why doesn't this if statement work properly

此代码旨在在邮箱再水化后执行清理(来自 Symantec Enterprise Vault)。我们在重新水化之前对邮箱中所有项目的 MessageIdConversationId 进行快照索引。

补水后这段代码

    if (string.Equals(item.ItemClass, "IPM.Note.EnterpriseVault.Shortcut", StringComparison.InvariantCulture) || ((existingIds.Any(x => x.ConversationId == item.ConversationId.ToString()) == false || (item.ItemClass == "IPM.Appointment" && existingIds.Any(x => x.MessageId == item.Id.ToString()) == false) && item.DateTimeReceived < snapshotDate)))
    {
        item.Delete(DeleteMode.HardDelete);
    }

应该删除

  1. 任何 ItemClass 为 "IPM.Note.EnterpriseVault.Shortcut"
  2. 的项目
  3. 具有 ItemClass 的 "IPM.Appointment" 的任何项目,其中 Id 不在 MessageIdexistingIds 列表中,除非已收到在`snapshotDate
  4. 之后
  5. 任何其他 ConversationId 不在 existingIds 列表中的项目,除非它们是在 snapshotDate 之后收到的。

在 运行 这段代码之后,一位用户报告丢失了一些在 snapshotDate 之后收到的电子邮件,所以看来我的 if 声明有误! :( 有人可以告诉我我出了什么问题(或者我可以分解它以更好地理解它的方法)以及这段代码实际上做了什么所以我可以让用户知道丢失了什么。我知道 lofical OR 是出了名的难写,我想我在某处的括号里弄错了,但我就是看不到它。

我发现查看此类问题的最简单方法是使用大量换行符和缩进。我在每个 ( 之后添加一个中断并增加缩进(除了琐碎的 ()),将匹配的 ) 放在它们的匹配对下方,并将运算符放在它们要加入的项目之间的单独行上:

if (
    string.Equals(
        item.ItemClass,
        "IPM.Note.EnterpriseVault.Shortcut", StringComparison.InvariantCulture
    )
    ||
    (
        (
            existingIds.Any(
                x => x.ConversationId == item.ConversationId.ToString()
            ) == false
            ||
            (
                item.ItemClass == "IPM.Appointment"
                &&
                existingIds.Any(
                    x => x.MessageId == item.Id.ToString()
                ) == false
            )
            &&
            item.DateTimeReceived < snapshotDate
        )
    )
)
{
    item.Delete(DeleteMode.HardDelete);
}

我可以立即发现两件事 - 一对括号只包含另一对括号,并且 &&|| 同时出现 "level" 所以我们是依赖运算符优先级。

我猜你想要 && 在内括号之外,这样它既适用于约会检查,也适用于现有的 ID。例如。改为:

if (
    string.Equals(
        item.ItemClass,
        "IPM.Note.EnterpriseVault.Shortcut", StringComparison.InvariantCulture
    )
    ||
    (
        (
            existingIds.Any(
                x => x.ConversationId == item.ConversationId.ToString()
            ) == false
            ||
            (
                item.ItemClass == "IPM.Appointment"
                &&
                existingIds.Any(
                    x => x.MessageId == item.Id.ToString()
                ) == false
            )
        )
        &&
        item.DateTimeReceived < snapshotDate
    )
)
{
    item.Delete(DeleteMode.HardDelete);
}

(一旦您确认所有内容都符合您的需要,您可以折叠回更少的行)

如果检查到本地函数,我会建议您拆分,这样调试起来会容易得多。

 if (IsShourtcut(item) || NotExistingAppItment(item) || ExistingConversation(item) && IsReceivedBeforSnapshot(item)) 
    {
            // to delete 
    }

bool IsShourtcut(Item item) => string.Equals(item.ItemClass, "IPM.Note.EnterpriseVault.Shortcut", StringComparison.InvariantCulture);
bool NotExistingAppItment(Item item) => item.ItemClass == "IPM.Appointment" && existingIds.All(x => x.MessageId != item.Id.ToString());
bool ExistingConversation(Item item) => existingIds.All(x => x.ConversationId != item.ConversationId.ToString();
bool IsReceivedBeforSnapshot(Item item) => item.DateTimeReceived < snapshotDate;

其他两个答案都非常有帮助,我也相应地投了赞成票。然而,我认为我会 post 我的最终解决方案受到 and 的启发,特别依赖 OxQ 推荐的局部函数的使用。我现在已经编写了单元测试(我应该早点完成)并且这些都使用我的新方法通过了,而其中五个之前失败了。我相信这段代码更符合所陈述的问题,并且非常易于阅读和理解。我不得不使用标量值而不是整个 Item 对象,因此我可以对它进行单元测试,因为 Exchange Web 服务托管 API 不使用接口。

switch (itemClass)
{
    case "IPM.Note.EnterpriseVault.Shortcut":
        return true;
    case "IPM.Appointment":
        return NotExistingMessage() && IsReceivedBeforeSnapshot();
    default:
        return NotExistingConversation() && IsReceivedBeforeSnapshot();
}

bool NotExistingMessage() => existingIds.All(x => x.MessageId != messageId);
bool NotExistingConversation() => existingIds.All(x => x.ConversationId != conversationId);
bool IsReceivedBeforeSnapshot() => dateTimeReceived < uploadDate;