如何创建类似FluentAssertions中AssertionScope的自定义作用域
实现类似FluentAssertions AssertionScope的作用域机制
核心思路是利用C#的using语句结合IDisposable接口——using块结束时会自动调用对象的Dispose方法,刚好用来执行你需要的收尾操作。下面是具体实现步骤和代码示例:
1. 定义作用域类(核心实现)
先写一个实现IDisposable的KeyScope类,用来跟踪作用域内的状态,并在释放时执行逻辑。这里模拟FluentAssertions的断言收集功能,你可以根据需求修改收尾逻辑:
using System; using System.Collections.Generic; using System.Linq; public class KeyScope : IDisposable { private readonly List<string> _failureMessages = new List<string>(); private bool _disposed; private readonly KeyScope _previousScope; public KeyScope() { // 保存上一个作用域,支持嵌套使用 _previousScope = CurrentScopeHolder.Current; CurrentScopeHolder.Current = this; } // 供断言逻辑调用,记录失败信息 public void RecordFailure(string message) { _failureMessages.Add(message); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { // 作用域释放时执行的核心逻辑 if (_failureMessages.Any()) { // 批量抛出所有断言错误,和AssertionScope行为一致 throw new InvalidOperationException( $"发现{_failureMessages.Count}个错误:\n{string.Join("\n", _failureMessages)}"); } // 恢复之前的作用域,保证嵌套块不冲突 CurrentScopeHolder.Current = _previousScope; } _disposed = true; } ~KeyScope() { Dispose(false); } } // 用AsyncLocal存储当前作用域,支持异步/多线程场景 public static class CurrentScopeHolder { private static readonly AsyncLocal<KeyScope> _currentScope = new AsyncLocal<KeyScope>(); public static KeyScope Current { get => _currentScope.Value; set => _currentScope.Value = value; } }
2. 模拟FluentAssertions的断言风格(可选)
如果想要和x.Should().Be(...)类似的语法,可以写扩展方法和辅助类:
public static class AssertionExtensions { // 给int类型扩展Should方法,其他类型可以同理添加 public static IntAssertion Should(this int value) { return new IntAssertion(value, CurrentScopeHolder.Current); } } public class IntAssertion { private readonly int _actualValue; private readonly KeyScope _scope; public IntAssertion(int actualValue, KeyScope scope) { _actualValue = actualValue; _scope = scope ?? throw new InvalidOperationException("必须在KeyScope作用域内使用断言"); } public void Be(int expectedValue) { if (_actualValue != expectedValue) { _scope.RecordFailure($"预期值: {expectedValue},实际值: {_actualValue}"); } } }
3. 使用示例
现在就可以像你给出的示例那样使用了:
using (new KeyScope()) { int num1 = 5; num1.Should().Be(3); // 记录第一个失败 int num2 = 10; num2.Should().Be(10); // 验证成功,无记录 int num3 = 7; num3.Should().Be(9); // 记录第二个失败 } // using块结束时,Dispose方法会自动抛出包含两个错误信息的异常
关键细节说明
- 异步/多线程支持:用
AsyncLocal存储当前作用域,避免不同线程或异步上下文之间的作用域冲突。 - 嵌套作用域:构造函数中保存并在Dispose时恢复之前的作用域,允许嵌套使用多个
using (new KeyScope())块,每个块只处理自己内部的断言。 - 自定义逻辑:你可以完全替换
Dispose方法里的逻辑,比如不抛出异常,而是把错误信息写入日志、收集到结果对象中返回等。
内容的提问来源于stack exchange,提问作者Iceeman
相关产品推荐
相关产品推荐

