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

基于规则的验证层实现疑问:泛型规则集合存储与Validator实现

实现基于规则的验证层:解决方案与思路修正

首先,你的核心思路是对的——构建一个可以聚合多种验证规则的Validator,但当前的实现有两个关键问题需要解决:

  1. 无法将不同泛型的IValidationRule<T>存入同一个集合(因为每个IValidationRule<string>、IValidationRule<int>都是不同的具体类型)
  2. 使用dynamic arg会丢失编译时类型安全,容易引入运行时错误

修正方案:引入非泛型统一接口

要解决泛型规则的统一存储问题,我们需要一个非泛型的基接口来作为所有验证规则条目的通用类型,然后通过泛型类封装具体的规则和待验证参数。

步骤1:定义非泛型的验证条目接口

这个接口提供统一的验证执行和错误获取方法,不需要关心具体的泛型类型:

public interface IValidationEntry
{
    string Error { get; }
    bool ExecuteValidation();
}

步骤2:实现泛型验证条目类

这个类负责绑定具体的IValidationRule<T>和待验证的参数T,并实现非泛型接口:

public class ValidationRuleEntry<T> : IValidationEntry
{
    private readonly IValidationRule<T> _rule;
    private readonly T _validationArg;

    public ValidationRuleEntry(IValidationRule<T> rule, T validationArg)
    {
        _rule = rule;
        _validationArg = validationArg;
    }

    public string Error => _rule.Error;

    public bool ExecuteValidation()
    {
        return _rule.Validate(_validationArg);
    }
}

步骤3:重构Validator类

现在Validator可以维护一个List<IValidationEntry>集合,同时提供类型安全的AddRule方法:

public interface IValidator
{
    // 重载1:传入已实例化的规则和参数(推荐,类型安全)
    void AddRule<T>(IValidationRule<T> rule, T arg);
    // 重载2:通过规则类型自动实例化(适合无状态规则)
    void AddRule<TRule, T>(T arg) where TRule : IValidationRule<T>, new();
    ValidationResult Validate();
}

public class Validator : IValidator
{
    private readonly List<IValidationEntry> _validationEntries = new();

    public void AddRule<T>(IValidationRule<T> rule, T arg)
    {
        _validationEntries.Add(new ValidationRuleEntry<T>(rule, arg));
    }

    public void AddRule<TRule, T>(T arg) where TRule : IValidationRule<T>, new()
    {
        var ruleInstance = new TRule();
        _validationEntries.Add(new ValidationRuleEntry<T>(ruleInstance, arg));
    }

    public ValidationResult Validate()
    {
        var errors = new List<string>();
        bool isValid = true;

        foreach (var entry in _validationEntries)
        {
            if (!entry.ExecuteValidation())
            {
                isValid = false;
                if (!string.IsNullOrWhiteSpace(entry.Error))
                {
                    errors.Add(entry.Error);
                }
            }
        }

        return new ValidationResult(isValid, errors);
    }
}

// 假设你的ValidationResult定义如下(可根据需求调整)
public class ValidationResult
{
    public bool IsValid { get; }
    public IReadOnlyList<string> Errors { get; }

    public ValidationResult(bool isValid, IEnumerable<string> errors)
    {
        IsValid = isValid;
        Errors = errors.ToList().AsReadOnly();
    }
}

步骤4:调用端代码调整

根据你的需求,调用端可以选择两种方式添加规则:

var validator = new Validator();

// 方式1:传入已实例化的规则(支持自定义规则初始化逻辑)
var testRule = new TestRule { Error = "字符串不是'test'" };
validator.AddRule(testRule, "OK");

// 方式2:通过规则类型自动实例化(适合无状态规则)
validator.AddRule<IntRangeRule, int>(45); // 假设你有IntRangeRule实现IValidationRule<int>

var result = validator.Validate();
if (!result.IsValid)
{
    foreach (var error in result.Errors)
    {
        Console.WriteLine(error);
    }
}

关于你原调用方式的兼容(可选)

如果你坚持要使用AddRule<TestRule>("OK")这种只指定规则类型的写法,可以通过反射实现,但会丢失编译时类型安全:

public void AddRule<TRule>(dynamic arg)
{
    var ruleType = typeof(TRule);
    // 找到TRule实现的IValidationRule<T>接口
    var genericRuleInterface = ruleType.GetInterfaces()
        .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IValidationRule<>));
    
    if (genericRuleInterface == null)
    {
        throw new InvalidOperationException($"{ruleType.Name} 未实现IValidationRule<T>接口");
    }

    var targetType = genericRuleInterface.GetGenericArguments()[0];
    if (arg.GetType() != targetType)
    {
        throw new ArgumentException($"参数类型与规则期望的{targetType.Name}不匹配");
    }

    var ruleInstance = Activator.CreateInstance<TRule>();
    var entryType = typeof(ValidationRuleEntry<>).MakeGenericType(targetType);
    var entry = Activator.CreateInstance(entryType, ruleInstance, arg);
    
    _validationEntries.Add((IValidationEntry)entry);
}

但这种方式不推荐,因为编译时无法检查参数类型是否匹配,容易引入运行时错误。

总结

你的核心思路是可行的,但需要通过非泛型接口统一存储来解决泛型规则的类型差异问题,同时尽量使用类型安全的泛型方法替代dynamic,避免不必要的运行时风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:37:54