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

如何用Shouldly的ShouldBe比较非可空与可空类型的值?

问题:Shouldly如何比较非可空bool与可空bool?

场景与错误信息

现有代码中:

  • o[0].AccountEnabled 类型为非可空bool (bool)
  • graphUsers[0].AccountEnabled 类型为可空bool (bool?)

如果不做显式类型转换,直接调用ShouldBe会触发编译错误:

'bool' does not contain a definition for 'ShouldBe' and the best extension method overload 'ShouldBeTestExtensions.ShouldBe<bool?>(bool?, bool?, string?)' requires a receiver of type 'bool?'CS1929

核心疑问

  1. xUnit的Assert.Equal()可以直接处理非可空与可空值类型的比较,但我不想每次用Shouldly时都手动把非可空值转成bool?,有没有更优雅的方式?
  2. 显式转换(bool?)o[0].AccountEnabled为什么能生效?看ShouldBe的泛型定义,若T为bool?,参数应该是bool??,但这个类型并不合法。

ShouldBe方法定义

public static void ShouldBe<T>(
    [NotNullIfNotNull(nameof(expected))] this T? actual,
    [NotNullIfNotNull(nameof(actual))] T? expected,
    string? customMessage = null)
{
    if (ShouldlyConfiguration.CompareAsObjectTypes.Contains(typeof(T).FullName!) || typeof(T) == typeof(string))
        actual.AssertAwesomely(v => Is.Equal(v, expected, new ObjectEqualityComparer<T>()), actual, expected, customMessage);
    else
        actual.AssertAwesomely(v => Is.Equal(v, expected), actual, expected, customMessage);
}

解答

1. 强制转换生效的原因

你误解了泛型参数的推断逻辑:当你调用((bool?)o[0].AccountEnabled).ShouldBe(graphUsers[0].AccountEnabled)时,编译器推断的泛型参数T是bool,而非bool?。此时T?就是bool?,完全符合方法的参数类型要求——不存在所谓的bool??,因为C#不允许值类型的可空嵌套,编译器会自动忽略多余的?。

简单说:T是基础值类型bool,T?对应可空类型bool?,所以方法参数类型完全匹配,因此强制转换后可以正常调用。

2. 无需手动强制转换的解决方案

方法一:自定义扩展方法

写一个专门处理非可空值类型与对应可空类型比较的ShouldBe重载,内部帮你完成转换:

public static void ShouldBe<T>(this T actual, T? expected, string? customMessage = null) where T : struct
{
    // 将非可空值包装为可空类型,调用原ShouldBe方法
    ((T?)actual).ShouldBe(expected, customMessage);
}

添加这个扩展后,就可以直接写:

owners.ShouldSatisfyAllConditions(
    o => o.Count.ShouldBe(graphUsers.Count),
    o => o[0].Id.ShouldBe(Guid.Parse(graphUsers[0].Id!)),
    o => o[0].DisplayName.ShouldBe(graphUsers[0].DisplayName),
    o => o[0].GivenName.ShouldBe(graphUsers[0].GivenName),
    o => o[0].Surname.ShouldBe(graphUsers[0].Surname),
    o => o[0].UserPrincipalName.ShouldBe(graphUsers[0].UserPrincipalName),
    o => o[0].Email.ShouldBe(graphUsers[0].Mail),
    o => o[0].AccountEnabled.ShouldBe(graphUsers[0].AccountEnabled) // 无需强制转换
);

方法二:转换预期值(不推荐)

如果能确定graphUsers[0].AccountEnabled不为null,可以把预期值转成非可空bool:

o => o[0].AccountEnabled.ShouldBe(graphUsers[0].AccountEnabled.Value)

但这种方式有风险,若预期值为null会直接抛出InvalidOperationException,仅适用于明确非空的场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 14:29:58