使用 InputSelect 时出现 Blazor 错误 "The call is ambiguous between the following methods or properties"

Blazor error "The call is ambiguous between the following methods or properties" when working with InputSelect

我正在开发服务器端 Blazor 应用程序。我的目标是用对象列表动态填充下拉列表,然后在选择该对象时对该对象执行某些操作。这是我得到的错误:

The call is ambiguous between the following methods or properties: 'RenderTreeBuilder.AddAttribute(int, string, string?)' and 'RenderTreeBuilder.AddAttribute(int, string, MulticastDelegate?)'

错误在文件“ListDropdownComponent.g.cs”中 我没有对此文件进行任何更改,因此我怀疑 Visual Studio 将我指向了错误的来源。

当我注释掉这段代码时,错误消失了,所以我认为这是错误所在:

<InputSelect @bind-Value="SelectedList" TValue="ListModel" @onchange="AddToList">
    <option value="@null">Add to list</option>
    @foreach (ListModel list in Lists)
    {
        <option value="@list">@list.ListName</option>
    }
    <option value="@(new ListModel{ ListID = -1, ListName="New List" }) ">New List</option>
</InputSelect>

SelectedList 正在使用 ListModel class:

public ListModel SelectedList;

更新:我切换到使用 class 的 int ID 属性,但我仍然遇到同样的错误。所以我不知道是什么原因。

更新 2:这就是问题所在:value="@null"。我会想办法解决这个问题,但是因为 int?可以为空我仍然想知道实际问题是什么。

首先,html 属性不能有空值,this is described in the docs here:

HTML attributes can't have null values. The closest equivalent to null in HTML is absence of the HTML value attribute from the element.

When selecting an <option> with no value attribute, the browser treats the value as the text content of that <option>'s element.

话虽如此,我不明白为什么 Blazor 不能为您处理这种情况。事实上,there is an issue posted about that here。所以,我想当前的答案是 Currently Blazor does not support this use case.

解决方法是使用空字符串 "" 并自行在 null 之间进行转换。

你的根本问题在这里:

<option value="@list">@list.ListName</option>

其中 list 是一个对象。如果您在浏览器开发工具中检查您生成的内容,它将看起来像这样:

<select>
   <option value="BlazorApp4.Pages.Index+ListModel">French</option>
   <option value="BlazorApp4.Pages.Index+ListModel">Spanish</option>
   <option value="BlazorApp4.Pages.Index+ListModel ">New List</option>
</select>

valueoption 中必须是字符串。

您需要重新考虑如何编码。如果没有更多的上下文,我会在黑暗中提出建议。

This is the problem: value="@null". I will find a way to work around this, but since int? is nullable I would still like to know what the actual issue is

你写的代码:

<option value="@null" selected>Add to list...</option>

转换为构建页面的更多相关代码。你的 @null 被转换成一个 *.g.cs 文件,最终看起来像这样:

                __builder2.AddAttribute(13, "ValueChanged", global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck<Microsoft.AspNetCore.Components.EventCallback<int?>>(Microsoft.AspNetCore.Components.EventCallback.Factory.Create<int?>(this, global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.CreateInferredEventCallback(this, __value => _selectedList = __value, _selectedList))));
                __builder2.AddAttribute(14, "ValueExpression", global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck<System.Linq.Expressions.Expression<System.Func<int?>>>(() => _selectedList));
                __builder2.AddAttribute(15, "ChildContent", (Microsoft.AspNetCore.Components.RenderFragment)((__builder3) => {
                    __builder3.OpenElement(16, "option");
                    __builder3.AddAttribute(17, "value", 
#nullable restore
#line 15 "C:\repos\path\to\your\project\Pages\Whatever.razor"
                            null

#line default
#line hidden
#nullable disable
                    );

页面标记中的 null 已最终放置在生成代码中的 #line 15 下方。您可以看到您的页面标记已转换为其他代码,而其他代码构建了页面 html。问题来了,因为您在使用 null 的地方导致生成的代码调用:

__builder3.AddAttribute(17, "value", null);

但是有两个 AddAttribute 重载匹配这个:

RenderTreeBuilder.AddAttribute(int, string, string?)
RenderTreeBuilder.AddAttribute(int, string, MulticastDelegate?)

编译器不知道调用哪一个。和写这段代码完全一样:

static void Main(){
  Hello(null);        //error: the call is ambiguous between Hello(string) and Hello(int?)
}

static void Hello(string x) { }
static void Hello(int? x) { }

如果你想克服这种情况,你可以将 null 转换为特定类型,这样编译器就可以知道你想调用哪个重载:

Hello((string)null);

因此,可以将您的代码修改为:

<option value="@(int?)null" selected>Add to list...</option>

这将使错误消失,因为编译器会知道要调用哪个重载..

..但它引入了另一个问题,从整体上看代码,我们 通常不会像这样编写 Blazor

Blazor 专注于数据绑定;您将页面元素绑定到变量,然后使用这些变量做出其他决定。您不会采取“当组合发生变化时触发一个事件,并且在该事件处理程序中您获得组合的选定索引并获得...”的心态 - 这完全是一种 Windows 形式看待世界的方式。在 Blazor 中,它更像是“你将组合绑定到 X 变量,然后当你决定要在数据库中保存什么时(比如当用户按下保存时),你会查看 X 的值”

因此,您的代码可能更像:

...

        <InputSelect @bind-Value="_selectedListId" TValue="int" >
            <option value="-1">Choose a list...</option>
            @foreach (var list in _existingLists)
            {
                <option value="@list.ListID">@list.ListName</option>
            }
            <option value="-2">New List</option>
        </InputSelect>

        @if(_selectedList == -2){
            called: 
            <InputText @bind-Value="_newListName" ></InputText>
        }
...

@code{
    class ListModel{ public string ListName { get; set; } public int ListID { get; set; } }

    int _selectedListId = -1;
    string _newListName;
    List<ListModel> _existingLists = new(){ 
      new ListModel { ListID = 100, ListName = "Red Team" }, 
      new ListModel { ListID = 101, ListName = "Blue Team" } 
    };
}

您将 Select 绑定到 _selectedListId 私有 int 字段。您为选项列表中的所有内容提供了一个编号 value=。也许您正在以“edit-an-existing-X”模式打开,并且您的初始化例程在某个数据库中查找并获取所显示内容的选定列表编号,并将 _selectedListId 设置为 100 等。或者也许这是“add-new-X”页面,我们默认选择的列表 ID 为 -1。

组合与其所有选项一起呈现;在上面的代码中,_selectedlistId 为 -1,因此组合显示在“选择列表...”处。用户打开组合并选择“红队”。由于他们这样做,_selectedListId 设置为 100 - 你 不需要 立即对此采取行动 - 他们选择了红队,_selectedListId 现在是 100,并且当需要将它保存到数据库或其他任何东西时,它仍然是 100..

以类似的方式,如果他们选择“添加到新列表..”,则将 _selectedListId 设置为 -2,并且当出现 re-render 时,这现在意味着 [=标记中的 31=] 为真,因此会呈现更多内容 - 出现一个文本框供用户输入新列表名称。该文本框的值为 @bind'd to _newListName,所以当需要保存时,您可以读取 -2 的 _selectedListId,您可以调用 int CreateNameListNamed(string name) 方法,该方法创建新列表和 returns id 102 等,然后您可以将数据保存为 102列表 ID..

我想说的是,通常我们会寻找方法来利用这种“绑定输入以便它们更改状态,根据状态做出渲染或逻辑决策”过程,而不是 WinFormsy“将处理程序绑定到控件 X 上的事件,查询控件 X 以进行选择,结果更改控件 Y 的属性”方法

如果就用户做出选择时您想执行的操作而言,这还不够,您可以不使用 @bind-Value(它通过 created [=89 生成双向绑定) =] Value/ValueExpression/ValueChanged 属性上的行为)手动指定这些行为。

我相信很多博客会告诉你关于 @bind-ValueValue + ValueChanged + ValueExpression 的区别,但实际上,大多数情况下你可以不用 @bind-Value,你甚至可以进行设置属性 触发您的其他操作(如果有意义):

<InputSelect @bind-Value="SelectedListId" TValue="int" >

int _selectedListId;
int SelectedListId {
  get => _selectedListId;
  set { _undoHistoryList.Add(_selectedListId); _selectedListId = value; }
}

看看属性的设置怎么也更新了撤消历史?这意味着我们继续使用 bind-Value,因为它干净简单,并且在 set

的上下文中从代码中触发这些额外位

您也可以拆分并提供您自己的:

<InputSelect Value="_selectedListId" ValueExpression="() => selectedListId" ValueChanged="x => { _undoHistory.Add(_selectedListId); _selectedListId = x; }" TValue="int" >

..但在你可以

的地方努力“用@bind-做”