diff --git a/src/BootstrapBlazor/Components/AutoFill/AutoFill.razor.cs b/src/BootstrapBlazor/Components/AutoFill/AutoFill.razor.cs index 4e96805bb92..dc9f02e1ca1 100644 --- a/src/BootstrapBlazor/Components/AutoFill/AutoFill.razor.cs +++ b/src/BootstrapBlazor/Components/AutoFill/AutoFill.razor.cs @@ -241,9 +241,7 @@ public void TriggerChange(string v) _clientValue = v; } - private bool IsNullable() => !ValueType.IsValueType || NullableUnderlyingType != null; - - private bool GetClearable() => IsClearable && !IsDisabled && IsNullable(); + private bool GetClearable() => IsClearable && !IsDisabled && (!ValueType.IsValueType || IsNullable()); private async Task OnClickItem(TValue val) { diff --git a/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.cs b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.cs index a00945c8bc9..d56a38afd4d 100644 --- a/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.cs +++ b/src/BootstrapBlazor/Components/DateTimePicker/DateTimePicker.razor.cs @@ -338,7 +338,7 @@ protected override void OnInitialized() base.OnInitialized(); // 泛型设置为可为空 - AllowNull = NullableUnderlyingType != null; + AllowNull = IsNullable(); } /// diff --git a/src/BootstrapBlazor/Components/Display/DisplayBase.cs b/src/BootstrapBlazor/Components/Display/DisplayBase.cs index 68bdeb52928..b0c58f1249a 100644 --- a/src/BootstrapBlazor/Components/Display/DisplayBase.cs +++ b/src/BootstrapBlazor/Components/Display/DisplayBase.cs @@ -37,11 +37,24 @@ public abstract class DisplayBase : BootstrapModuleComponentBase protected static readonly Type? NullableUnderlyingType = Nullable.GetUnderlyingType(typeof(TValue)); /// - /// 获得/设置 泛型参数 TValue 可为空类型 Type 实例 - /// Gets or sets Generic Parameter TValue Nullable Type Instance + /// 判断泛型参数 是否为 可空值类型 + /// Determines whether the generic parameter is a value type. /// - [NotNull] - protected Type? ValueType { get; set; } + /// + /// 表示 否则返回 + /// when is ; otherwise . + /// + protected bool IsNullable() => NullableUnderlyingType != null; + + /// + /// 获得 泛型参数 解包后的实际 实例。当 时返回其底层类型,否则返回 自身 + /// Gets the unwrapped of : the underlying type when is , otherwise itself. + /// + /// + /// 每个封闭泛型类型只计算一次缓存为静态字段,避免热路径重复反射 + /// Cached once per closed generic type to avoid repeated reflection on hot paths. + /// + protected static readonly Type ValueType = NullableUnderlyingType ?? typeof(TValue); /// /// 获得/设置 输入组件的值,支持双向绑定 @@ -125,8 +138,6 @@ public override Task SetParametersAsync(ParameterView parameters) { parameters.SetParameterProperties(this); - ValueType ??= NullableUnderlyingType ?? typeof(TValue); - if (ValueExpression != null) { FieldIdentifier = Microsoft.AspNetCore.Components.Forms.FieldIdentifier.Create(ValueExpression); diff --git a/src/BootstrapBlazor/Components/InputNumber/BootstrapInputNumber.razor.cs b/src/BootstrapBlazor/Components/InputNumber/BootstrapInputNumber.razor.cs index 6a74774de51..0b73672aaec 100644 --- a/src/BootstrapBlazor/Components/InputNumber/BootstrapInputNumber.razor.cs +++ b/src/BootstrapBlazor/Components/InputNumber/BootstrapInputNumber.razor.cs @@ -280,7 +280,7 @@ protected override async Task OnBlur() CurrentValue = default!; } - if (NullableUnderlyingType != null && string.IsNullOrEmpty(CurrentValueAsString)) + if (IsNullable() && string.IsNullOrEmpty(CurrentValueAsString)) { // set component value empty await InvokeVoidAsync("clear", Id); diff --git a/src/BootstrapBlazor/Components/Radio/RadioList.razor.cs b/src/BootstrapBlazor/Components/Radio/RadioList.razor.cs index d5fc6ee9272..1209e267893 100644 --- a/src/BootstrapBlazor/Components/Radio/RadioList.razor.cs +++ b/src/BootstrapBlazor/Components/Radio/RadioList.razor.cs @@ -63,7 +63,7 @@ protected override void OnParametersSet() var t = ValueType; if (t.IsEnum && Items == null) { - Items = t.ToSelectList((NullableUnderlyingType != null && IsAutoAddNullItem) ? new SelectedItem("", NullItemText) : null); + Items = t.ToSelectList((IsNullable() && IsAutoAddNullItem) ? new SelectedItem("", NullItemText) : null); } base.OnParametersSet(); diff --git a/src/BootstrapBlazor/Components/Radio/RadioListGeneric.razor.cs b/src/BootstrapBlazor/Components/Radio/RadioListGeneric.razor.cs index a3982ba4062..61e359b8191 100644 --- a/src/BootstrapBlazor/Components/Radio/RadioListGeneric.razor.cs +++ b/src/BootstrapBlazor/Components/Radio/RadioListGeneric.razor.cs @@ -131,7 +131,7 @@ protected override void OnParametersSet() var t = ValueType; if (t.IsEnum && Items == null) { - Items = t.ToSelectList((NullableUnderlyingType != null && IsAutoAddNullItem) ? new SelectedItem(default!, NullItemText) : null); + Items = t.ToSelectList((IsNullable() && IsAutoAddNullItem) ? new SelectedItem(default!, NullItemText) : null); } Items ??= []; diff --git a/src/BootstrapBlazor/Components/Select/Select.razor.cs b/src/BootstrapBlazor/Components/Select/Select.razor.cs index a9707b4f88b..01cc586ae50 100644 --- a/src/BootstrapBlazor/Components/Select/Select.razor.cs +++ b/src/BootstrapBlazor/Components/Select/Select.razor.cs @@ -247,7 +247,7 @@ protected override async Task OnParametersSetAsync() if (!Items.Any() && ValueType.IsEnum()) { - var item = NullableUnderlyingType == null ? "" : PlaceHolder; + var item = IsNullable() ? PlaceHolder : ""; Items = ValueType.ToSelectList(string.IsNullOrEmpty(item) ? null : new SelectedItem("", item)); } diff --git a/src/BootstrapBlazor/Components/Select/SelectBase.cs b/src/BootstrapBlazor/Components/Select/SelectBase.cs index 29e51555f40..8c2aed8c6a1 100644 --- a/src/BootstrapBlazor/Components/Select/SelectBase.cs +++ b/src/BootstrapBlazor/Components/Select/SelectBase.cs @@ -220,13 +220,11 @@ protected override void OnParametersSet() /// A representing the asynchronous operation. public Task Hide() => InvokeVoidAsync("hide", Id); - private bool IsNullable() => !ValueType.IsValueType || NullableUnderlyingType != null; - /// /// 获得 是否显示清除按钮 /// Gets whether show the clear button /// - protected bool GetClearable() => IsClearable && !IsDisabled && IsNullable(); + protected bool GetClearable() => IsClearable && !IsDisabled && (!ValueType.IsValueType || IsNullable()); /// /// 清除搜索文本 diff --git a/src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.cs b/src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.cs index 9fbd06e166e..9e3bd44d209 100644 --- a/src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.cs +++ b/src/BootstrapBlazor/Components/SelectGeneric/SelectGeneric.razor.cs @@ -279,7 +279,7 @@ protected override void OnParametersSet() if (!Items.Any() && ValueType.IsEnum()) { - var item = NullableUnderlyingType == null ? "" : PlaceHolder; + var item = IsNullable() ? PlaceHolder : ""; Items = ValueType.ToSelectList(string.IsNullOrEmpty(item) ? null : new SelectedItem(default!, item)); } diff --git a/src/BootstrapBlazor/Components/Validate/ValidateBase.cs b/src/BootstrapBlazor/Components/Validate/ValidateBase.cs index 368ec610a2f..ba937e5f7dc 100644 --- a/src/BootstrapBlazor/Components/Validate/ValidateBase.cs +++ b/src/BootstrapBlazor/Components/Validate/ValidateBase.cs @@ -112,7 +112,7 @@ protected string CurrentValueAsString { _parsingValidationMessages?.Clear(); - if (NullableUnderlyingType != null && string.IsNullOrEmpty(value)) + if (IsNullable() && string.IsNullOrEmpty(value)) { // Assume if it's a nullable type, null/empty inputs should correspond to default(T) // Then all subclasses get nullable support almost automatically (they just have to @@ -498,7 +498,7 @@ private void ValidateType(ValidationContext context, List resu { // 增加数据基础类型验证 如泛型约定为 int 文本框值为 Empty // 可为空泛型约束时不检查 - if (NullableUnderlyingType == null && PreviousParsingAttemptFailed) + if (!IsNullable() && PreviousParsingAttemptFailed) { var memberNames = new string[] { context.MemberName! }; results.Add(new ValidationResult(PreviousErrorMessage, memberNames)); diff --git a/test/UnitTest/Components/SelectTest.cs b/test/UnitTest/Components/SelectTest.cs index d7fa414cb1d..4f2bec7f3f3 100644 --- a/test/UnitTest/Components/SelectTest.cs +++ b/test/UnitTest/Components/SelectTest.cs @@ -229,82 +229,6 @@ public async Task IsClearable_Ok() cut1.DoesNotContain("clear-icon"); } - [Fact] - public void IsNullable_Ok() - { - var cut = Context.Render>(pb => - { - pb.Add(a => a.Items, new List() - { - new("", "请选择"), - new("2", "Test2"), - new("3", "Test3") - }); - }); - Assert.True(IsNullable(cut.Instance)); - - var cut1 = Context.Render>(pb => - { - pb.Add(a => a.Items, new List() - { - new("", "请选择"), - new("2", "Test2"), - new("3", "Test3") - }); - }); - Assert.True(IsNullable(cut1.Instance)); - - var cut2 = Context.Render>(pb => - { - pb.Add(a => a.Items, new List() - { - new("", "请选择"), - new("2", "Test2"), - new("3", "Test3") - }); - }); - Assert.True(IsNullable(cut2.Instance)); - - var cut3 = Context.Render>(pb => - { - pb.Add(a => a.Items, new List() - { - new("", "请选择"), - new("2", "Test2"), - new("3", "Test3") - }); - }); - Assert.True(IsNullable(cut3.Instance)); - - var cut4 = Context.Render>(pb => - { - pb.Add(a => a.Items, new List() - { - new("", "请选择"), - new("2", "Test2"), - new("3", "Test3") - }); - }); - Assert.False(IsNullable(cut4.Instance)); - - var cut5 = Context.Render>(pb => - { - pb.Add(a => a.Items, new List() - { - new("", "请选择"), - new("2", "Test2"), - new("3", "Test3") - }); - }); - Assert.True(IsNullable(cut5.Instance)); - } - - private static bool IsNullable(object select) - { - var mi = select.GetType().BaseType!.BaseType!.GetMethod("IsNullable", BindingFlags.Instance | BindingFlags.NonPublic)!; - return (bool)mi.Invoke(select, null)!; - } - [Fact] public void SelectOption_Ok() {