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

如何创建类似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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 15:03:27