PAYATHON 2026

Blazor 参数使用 auto-properties 时,有比 setter 更好的验证方式吗?

技术老齐

可以保留 auto-properties,在 OnParametersSet 中比较参数的新旧值,只验证发生变化的参数。这种做法适合耗时较高的验证或格式化操作。对于 Length == 0、数值范围判断这类简单检查,直接验证通常更省事,是否需要缓存应根据实际测量结果决定。

把验证移到 setter,并不能保证只有参数变化时才验证。Blazor 可能再次给属性赋相同的值,setter 仍会执行。

为什么 setter 不能解决重复验证

Blazor 成组处理父组件传来的参数。一次参数传递可能包含多个属性,其中一些值与上次相同,因此不能假设父组件只传递发生变化的那个参数。

OnParametersSet 会在初始化和后续参数更新过程中执行,即使参数值没有实际变化,也可能被调用。它适合检查本轮参数赋值完成后的组件状态,哪些工作需要重做则要自行判断。Microsoft Learn:组件生命周期

两种写法的区别是:

  • setter 中的验证在属性被赋值时执行,包括重复赋入相同值的情况。
  • OnParametersSet 中的验证在本轮参数赋值完成后执行,可以统一检查多个参数之间的约束。

例如,检查 StartDate <= EndDate 时,OnParametersSet 能读取本轮赋值完成后的两个值。如果在某个 setter 中检查,另一个参数可能尚未更新。

官方的 BL0007 说明建议使用 auto-properties,将读取或转换参数的逻辑放到 OnParametersSet 或 OnParametersSetAsync 中,并明确不建议抑制这条警告。单纯抛异常不一定引发渲染循环,但也不能据此认定 setter 能减少验证次数。

如何只验证发生变化的参数

可以记录上一次成功验证的值,让两个参数分别判断是否需要重新验证,同时保留自动属性:

using System;
using Microsoft.AspNetCore.Components;

public class ValidatableControlShell : ComponentBase
{
    [Parameter]
    public string? Label { get; set; }

    [Parameter]
    public string? Guidance { get; set; }

    private bool _hasValidated;
    private string? _validatedLabel;
    private string? _validatedGuidance;

    protected override void OnParametersSet()
    {
        bool labelChanged =
            !_hasValidated ||
            !string.Equals(
                Label, _validatedLabel, StringComparison.Ordinal);

        bool guidanceChanged =
            !_hasValidated ||
            !string.Equals(
                Guidance, _validatedGuidance, StringComparison.Ordinal);

        if (labelChanged)
        {
            ValidateOptionalText(Label, nameof(Label));
        }

        if (guidanceChanged)
        {
            ValidateOptionalText(Guidance, nameof(Guidance));
        }

        // 所有验证通过后,再记录本轮成功验证的值。
        _validatedLabel = Label;
        _validatedGuidance = Guidance;
        _hasValidated = true;
    }

    private static void ValidateOptionalText(
        string? value,
        string parameterName)
    {
        if (value?.Length == 0)
        {
            throw new ArgumentException(
                "If specified, the value must not be empty.",
                parameterName);
        }
    }
}
  • _hasValidated 保证首次调用时执行验证,即使参数值与缓存字段的默认值相同。
  • 缓存只在所有验证通过后更新,保存的是已成功验证的值。
  • 示例允许 null 和 " ",拒绝 ""。如果空白字符串也不合法,需要调整验证规则。
  • 示例使用标准的 ArgumentException,也可以换回已有的自定义异常类型。

原代码中的 formatGuidance 也可以采用同样的处理方式:先验证 Guidance,在 guidanceChanged 为 true 时执行格式化,成功后再更新缓存。这样既能避免处理无效输入,也能跳过重复格式化。

这种写法仍需比较新旧值,但能跳过未变化参数的验证逻辑。如果验证只是读取字符串长度,比较字符串内容和管理缓存反而可能增加工作量。因此,它更适合复杂解析、集合扫描等耗时较高的操作。

参数之间有依赖时,要一起判断

只有验证结果完全取决于已比较的输入,才能安全地通过缓存跳过验证。

例如,EndDate 的合法性还取决于 StartDate,其中任何一个发生变化,都应该重新检查:

if (!_hasValidated ||
    StartDate != _validatedStartDate ||
    EndDate != _validatedEndDate)
{
    if (StartDate > EndDate)
    {
        throw new ArgumentException(
            "StartDate must not be later than EndDate.");
    }
}

可变对象需要额外处理。父组件可能保留同一个对象引用,却修改了内部属性或集合内容。此时,只比较引用无法判断之前的验证结果是否仍然有效。可以比较相关字段、保存必要的快照,或使用可靠的版本号,不能直接照搬字符串示例中的缓存方式。

SetParametersAsync 能否作为替代方案

可以重写 SetParametersAsync(ParameterView parameters),在赋值前读取传入参数,验证独立约束。这适合需要控制参数接收过程的组件。

ParameterView 包含本轮传入的参数,并不表示这些参数都发生了变化。即使用 TryGetValue 读取某个参数,也可能拿到与上次相同的值,仍需自行比较。默认实现还会保留本轮未提供的属性值。Microsoft Learn:SetParametersAsync

重写该方法时,通常应继续调用:

return base.SetParametersAsync(parameters);

基类方法还负责后续生命周期和渲染流程,不能为了避免验证而直接省略。只有完整接管参数赋值时,才应考虑其他调用方式。

对于当前的验证需求,使用 OnParametersSet 并按需增加缓存,通常更容易维护。官方也将手动实现参数赋值列为需要先测量、谨慎采用的性能优化方式。Microsoft Learn:手动实现 SetParametersAsync

参数较多、使用频繁时怎么处理

有 20~30 个参数,并不代表验证已经成为性能瓶颈。还要结合组件实例数量、参数更新频率和验证规则的实际成本判断。

可以按以下顺序处理:

  1. 保留自动属性,在 OnParametersSet 中直接检查简单约束。
  2. 对耗时较高的验证规则和派生数据计算,按值或依赖组增加缓存。
  3. 在真实使用场景中测量参数接收、验证和渲染各自的耗时,再决定是否接管 SetParametersAsync。

不要仅为减少检查数量,就把所有参数合并成一个可变配置对象。这样会减少参数个数,也会改变 Blazor 的变化检测行为,收益需要重新测量。Microsoft Learn:参数数量与渲染性能

题目中的两个非空检查直接留在 OnParametersSet 中即可,已有的格式化缓存可以继续使用。后续出现耗时较高的验证时,再为相应规则增加变化判断,无需因此抑制 BL0007。

备注:内容仅供参考。