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 个参数,并不代表验证已经成为性能瓶颈。还要结合组件实例数量、参数更新频率和验证规则的实际成本判断。
可以按以下顺序处理:
- 保留自动属性,在
OnParametersSet中直接检查简单约束。 - 对耗时较高的验证规则和派生数据计算,按值或依赖组增加缓存。
- 在真实使用场景中测量参数接收、验证和渲染各自的耗时,再决定是否接管
SetParametersAsync。
不要仅为减少检查数量,就把所有参数合并成一个可变配置对象。这样会减少参数个数,也会改变 Blazor 的变化检测行为,收益需要重新测量。Microsoft Learn:参数数量与渲染性能
题目中的两个非空检查直接留在 OnParametersSet 中即可,已有的格式化缓存可以继续使用。后续出现耗时较高的验证时,再为相应规则增加变化判断,无需因此抑制 BL0007。
备注:内容仅供参考。
