如何用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
核心疑问
- xUnit的
Assert.Equal()可以直接处理非可空与可空值类型的比较,但我不想每次用Shouldly时都手动把非可空值转成bool?,有没有更优雅的方式? - 显式转换
(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
相关产品推荐
相关产品推荐

