У меня есть следующий класс, который может принимать обычно числовые типы или типы string:
public class MdSlider<TValue> where TValue: IConvertible, IEquatable<TValue>
{
TValue? Value { get; set; }
TValue? Min { get; set; }
TValue? Max { get; set; }
// ...
}
Однако я только что понял, что если пользователь создает, например, MdSlider<double>, все эти три свойства будут double, а не double?, и по умолчанию они имеют значения 0 вместо null. Моему приложению требуется нулевое значение, если пользователь его не устанавливает.
Если я добавлю ограничение struct, string больше не будет приниматься.
Я не могу объявить отдельный MdSlider для string, потому что это в Blazor, и имя компонента должно быть уникальным (MdSlider<TValue> и MdSlider считается одинаковым), и я не хочу, например, делать отдельный MdSliderString.
Можно ли объявить параметр универсального типа и принудительно установить Nullable на свойство, если оно не имеет значения NULL? Любое другое решение, которое я могу использовать для компонентов Blazor?
Вы, вероятно, правы. Я проверил официальный проект Microsoft, чтобы узнать, как они с этим справляются (у них также есть общие элементы управления Blazor). Таким образом, у них есть все свойства, допускающие значение NULL (Min, Max и т. д.), чтобы быть string вместо T, и только Value (который всегда присутствует) является T.
Ваш вопрос предполагает, что MdSlider — это компонент слайдера Blazor, а Value, Min и Max на самом деле являются параметрами для этого компонента. Если да, то откройте вопрос, и я попытаюсь продемонстрировать, как и почему это можно заставить работать в контексте компонента. Мне нужно больше места, чем комментарий!
Сделанный. См. ответ ниже, который может дать вам ответ, который вы ищете.





В этом ответе показано, как это работает с компонентами Blazor:
Вот моя демо Slider.razor
@typeparam TValue
<input type = "range" @bind:get = "Value" @bind:set = "SetValue" class = "form-range" min = "@Min" max = "@Max" id = "customRange2">
@code {
[Parameter] public TValue? Value { get; set; }
[Parameter] public EventCallback<TValue?> ValueChanged { get; set; }
[Parameter] public TValue? Max { get; set; }
[Parameter] public TValue? Min { get; set; }
private async Task SetValue(TValue? value)
{
await this.ValueChanged.InvokeAsync(value);
}
}
И демо Index использует как строку с нулевым значением, так и [не обнуляемое] двойное значение.
@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title = "How is Blazor working for you?" />
<div class = "mb-3">
<Slider Max = "_stringMax" Min = "_stringMin" @bind-Value = "_stringValue" />
</div>
<div class = "mb-3">
<Slider Max = "_max" Min = "_min" @bind-Value = "_value" />
</div>
<div class = "bg-dark text-white m-2 p-2">
<pre>String Value : @_stringValue</pre>
<pre>Value : @_value</pre>
</div>
@code {
string? _stringMax = "10";
string? _stringMin = "-10";
string? _stringValue;
double _max = 10;
double _min = -10;
double _value;
}
Причина, по которой это работает, заключается в том, что между Value в дочернем элементе и значением, указанным в определении компонента, нет жесткой связи. Значение, указанное в родительском компоненте, определяется в ParameterView, передаваемом дочернему компоненту в ParametersSetAsync.
То, как это применяется к дочернему компоненту, зависит от дочернего компонента: обычный способ использует Reflection и корректно обрабатывает преобразование типов.
public virtual Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
Вы можете сделать это вручную — см. https://learn.microsoft.com/en-us/aspnet/core/blazor/ Performance?view=aspnetcore-7.0#implement-setparametersasync-manually.
Поскольку я не совсем уверен, чего вы хотите, вот версия ползунка, демонстрирующая некоторые довольно продвинутые методы, которые вы можете использовать для создания любого реального компонента, который вам нужен.
SetParametersAsync выполняет всю работу по обработке входящего контента и преобразует все в строки для использования во входных данных.
SetValue выполняет преобразование строки, возвращенной из ввода, в TValue с помощью утилиты BindConverter.
BuildRenderTree использует построитель дерева рендеринга для построения контента с помощью логики для управления тем, что необходимо включить.
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using System.Globalization;
namespace SO77161463.Pages;
public class MySlider<TValue> : ComponentBase
{
[Parameter] public TValue? Value { get; set; }
[Parameter] public EventCallback<TValue?> ValueChanged { get; set; }
[Parameter] public TValue? Max { get; set; }
[Parameter] public TValue? Min { get; set; }
[Parameter] public string? CssClass { get; set; } = "form-range";
private string? _value;
private string? _max;
private string? _min;
public override Task SetParametersAsync(ParameterView parameters)
{
foreach (var parameter in parameters)
{
switch (parameter.Name)
{
case nameof(Value):
if (parameter.Value is null)
{
_value = null;
this.Value = default;
break;
}
_value = parameter.Value.ToString();
this.Value = (TValue)parameter.Value;
break;
case nameof(Max):
if (parameter.Value is null)
{
_max = null;
this.Max = default;
break;
}
_max = parameter.Value.ToString();
this.Max = (TValue)parameter.Value;
break;
case nameof(Min):
if (parameter.Value is null)
{
_min = null;
this.Min = default;
break;
}
this.Min = (TValue)parameter.Value;
_min = parameter.Value.ToString();
break;
case nameof(CssClass):
this.CssClass = parameter.Value?.ToString() ?? null;
break;
case nameof(ValueChanged):
if (parameter.Value is not null)
ValueChanged = (EventCallback<TValue?>)parameter.Value;
break;
default:
throw new ArgumentException($"Unknown parameter: {parameter.Name}");
}
}
return base.SetParametersAsync(ParameterView.Empty);
}
private async Task SetValue(ChangeEventArgs e)
{
if (BindConverter.TryConvertTo<TValue>(e.Value, CultureInfo.InvariantCulture, out TValue? result))
await ValueChanged.InvokeAsync(result);
else
await ValueChanged.InvokeAsync(default(TValue));
}
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenElement(1, "input");
builder.AddAttribute(2, "type", "range");
builder.AddAttribute(3, "value", _value);
builder.AddAttribute(4, "class", this.CssClass);
if (_max is not null)
builder.AddAttribute(5, "max", _max);
if (_min is not null)
builder.AddAttribute(6, "min", _min);
builder.AddAttribute(7, "onchange", EventCallback.Factory.Create<ChangeEventArgs>(this, SetValue));
builder.CloseElement();
}
}
Обратите внимание, что Parameters используются просто как служба обмена сообщениями для передачи значений в компонент.
Привет, извините, я пока не смогу протестировать его в ближайшие несколько дней, но не могли бы вы уточнить, что если пользователи не предоставляют значение Min или Max, этот атрибут не печатается (это важно)? Если TValue равно double, то Max по умолчанию будет равен 0 и не может быть нулевым и, следовательно, будет распечатан, когда пользователь его не предоставит?
Поясните, пожалуйста, что вы подразумеваете под словом «напечатать».
См. исправленный ответ и комментарий.
Привет, еще раз извините, что в ближайшие несколько дней у меня не будет доступа к IDE для тестирования. Под «печатью» я подразумевал, что полученный HTML-код не должен иметь этого атрибута, если он равен нулю. Например: <MdSlider Value = "10" /> должен печатать только <md-slider value = "10"></md-slider> и НЕ <md-slider value = "10" max = "0" />, если разработчик Blazor, который использует MdSlider, не дает Max значения (это проблема, с которой я сталкиваюсь, потому что это приводит к неисправности <md-slider>, <md-slider> эквивалентно input в вашем случае). Опять же, это просто объяснение, я не знаю, работает ли ваш ответ или нет.
Я предполагаю, что это Material Design. Второй компонент показывает, как добавлять или не добавлять атрибуты в зависимости от их значений. Вы можете сделать это в Razor, но здесь очень много дублирований разметки, поэтому в данном случае метод RenderTreeBuilder кажется более подходящим. В любом случае, надеюсь, есть какие-то подсказки о том, как закодировать настоящий.
Спасибо, сейчас я следую подходу команды MS в Fluent Blazor. Их Value (который в каком-то смысле является обязательным) относится к типу TValue?, но их необязательные Max, Min и т. д. относятся к string?. Таким образом, когда значение равно null (пользователь его не предоставляет), Blazor не распечатывает этот атрибут. Однако это имеет смысл, потому что вам на самом деле не нужно его привязывать, поэтому отсутствие TValue на самом деле не является проблемой.
Короче говоря - нет, без родового ограничения. Смотрите дубликат. В качестве обходного пути вы можете создать две версии — одну для структур и одну для ссылочных типов.