ASP.NET Core Razor 组件泛型类型支持

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅此文的 .NET 8 版本

本文介绍 Razor 组件中的泛型类型支持。

如果你不熟悉泛型类型,请在阅读本文之前参阅泛型类和方法(C# 指南)以获取有关使用泛型的一般指导。

本文中的示例代码仅适用于 Blazor 示例应用中的最新 .NET 版本。

泛型类型参数支持

@typeparam 指令声明生成的组件类的泛型类型参数

@typeparam TItem

支持具有 where 类型约束的 C# 语法:

@typeparam TEntity where TEntity : IEntity

在下例中,ListItems1 组件的泛型类型为 TExample,表示 ExampleList 集合的类型。

ListItems1.razor

@typeparam TExample

<h2>List Items 1</h2>

@if (ExampleList is not null)
{
    <ul style="color:@Color">
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>

    <p>
        Type of <code>TExample</code>: @typeof(TExample)
    </p>
}

@code {
    [Parameter]
    public string? Color { get; set; }

    [Parameter]
    public IEnumerable<TExample>? ExampleList { get; set; }
}

以下 组件会呈现两个 ListItems1 组件:

  • 字符串或整数数据将分配给每个组件的 ExampleList 参数。
  • 会为每个组件的类型参数 (TExample) 设置与分配的数据类型匹配的类型 stringint

Generics1.razor

@page "/generics-1"

<PageTitle>Generics 1</PageTitle>

<h1>Generic Type Example 1</h1>

<ListItems1 Color="blue"
            ExampleList="@(new List<string> { "Item 1", "Item 2" })"
            TExample="string" />

<ListItems1 Color="red"
            ExampleList="@(new List<int> { 1, 2 })"
            TExample="int" />

有关详细信息,请参阅 ASP.NET Core 的 Razor 语法参考。 有关使用模板化组件进行泛型类型化的示例,请参阅 ASP.NET Core Blazor 模板化组件

级联的泛型类型支持

上级组件可以使用 [CascadingTypeParameter] 特性将类型参数按名称级联到下级。 此特性允许泛型类型推理自动使用指定的类型参数以及具有相同名称的类型参数的下级。

通过将 @attribute [CascadingTypeParameter(...)] 添加到组件中,符合以下条件的下级会自动使用指定的泛型类型参数:

  • 在同一个 .razor 文档中嵌套为组件的子内容。
  • 同时还使用完全相同的名称声明了 @typeparam
  • 没有为类型参数显式提供或隐式推断另一个值。 如果提供或推断了其他值,则其优先于级联泛型类型。

接收级联类型参数时,组件从具有 [CascadingTypeParameter] 属性和一个匹配名称的最接近的上级中获取参数值。 级联泛型类型参数在特定子树内被重写。

匹配只按名称进行。 因此,建议使用通用名称(例如 TTItem)来避免使用级联泛型类型参数。 如果开发人员选择级联一个类型参数,则会隐式承诺其名称是唯一的,不会与来自无关组件的其他级联类型参数冲突。

可以通过以下任一使用上级(父)组件的方法将泛型类型级联到子组件,如以下两个子部分中所示:

  • 显式设置级联的泛型类型。
  • 推断级联的泛型类型。

以下子部分使用以下 ListDisplay1 组件提供了上述方法的示例。 该组件接收并呈现泛型类型为 TExample 的列表数据。 若要使 ListDisplay1 的每个实例突出显示,可使用一个附加组件参数来控制列表的颜色。

ListDisplay1.razor

@typeparam TExample

@if (ExampleList is not null)
{
    <ul style="color:@Color">
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>
}

@code {
    [Parameter]
    public string? Color { get; set; }

    [Parameter]
    public IEnumerable<TExample>? ExampleList { get; set; }
}

基于上级组件的显式泛型类型

本部分中的演示为 TExample 显式级联了一个类型。

注意

本部分使用级联的泛型类型支持部分中的前一个 ListDisplay1 组件。

以下 ListItems2 组件接收数据,并将名为 TExample 的泛型类型参数级联到其下级组件。 在即将到来的父组件中,ListItems2 组件用于显示前面 ListDisplay1 组件的列表数据。

ListItems2.razor

@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample

<h2>List Items 2</h2>

@ChildContent

<p>
    Type of <code>TExample</code>: @typeof(TExample)
</p>

@code {
    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

下面的父组件将设置指定 ListItems2 类型 (TExample) 的两个 ListItems2 组件的子内容 (RenderFragment),这些类型会级联到子组件。 ListDisplay1 组件呈现为示例中所示的列表项数据。 字符串数据与第一个 ListItems2 组件一起使用,整数数据与第二个 ListItems2 组件一起使用。

Generics2.razor

@page "/generics-2"

<PageTitle>Generics 2</PageTitle>

<h1>Generic Type Example 2</h1>

<ListItems2 TExample="string">
    <ListDisplay1 Color="blue" 
                  ExampleList="@(new List<string> { "Item 1", "Item 2" })" />
    <ListDisplay1 Color="red" 
                  ExampleList="@(new List<string> { "Item 3", "Item 4" })" />
</ListItems2>

<ListItems2 TExample="int">
    <ListDisplay1 Color="blue" 
                  ExampleList="@(new List<int> { 1, 2 })" />
    <ListDisplay1 Color="red" 
                  ExampleList="@(new List<int> { 3, 4 })" />
</ListItems2>

显式指定类型还允许使用级联值和参数向子组件提供数据,如下面的演示所示。

ListDisplay2.razor

@typeparam TExample

@if (ExampleList is not null)
{
    <ul style="color:@Color">
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>
}

@code {
    [Parameter]
    public string? Color { get; set; }

    [CascadingParameter]
    protected IEnumerable<TExample>? ExampleList { get; set; }
}

ListItems3.razor

@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample

<h2>List Items 3</h2>

@ChildContent

@if (ExampleList is not null)
{
    <ul style="color:green">
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>

    <p>
        Type of <code>TExample</code>: @typeof(TExample)
    </p>
}

@code {
    [CascadingParameter]
    protected IEnumerable<TExample>? ExampleList { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

在下面的示例中对数据进行级联时,必须向 组件提供类型。

Generics3.razor

@page "/generics-3"

<PageTitle>Generics 3</PageTitle>

<h1>Generic Type Example 3</h1>

<CascadingValue Value="stringData">
    <ListItems3 TExample="string">
        <ListDisplay2 Color="blue" />
        <ListDisplay2 Color="red" />
    </ListItems3>
</CascadingValue>

<CascadingValue Value="integerData">
    <ListItems3 TExample="int">
        <ListDisplay2 Color="blue" />
        <ListDisplay2 Color="red" />
    </ListItems3>
</CascadingValue>

@code {
    private List<string> stringData = new() { "Item 1", "Item 2" };
    private List<int> integerData = new() { 1, 2 };
}

当级联多个泛型类型时,必须传递集中所有泛型类型的值。 在下面的示例中,TItemTValueTEditGridColumn 泛型类型,但放置 GridColumn 的父组件并不指定 TItem 类型:

<GridColumn TValue="string" TEdit="TextEdit" />

前面的示例会生成一个编译时错误,即 GridColumn 组件缺少 TItem 类型参数。 有效的代码将指定所有类型:

<GridColumn TValue="string" TEdit="TextEdit" TItem="User" />

基于上级组件推断泛型类型

本部分中的演示为 TExample 级联了一个推断的类型。

注意

本部分使用级联的泛型类型支持部分中的 ListDisplay 组件。

ListItems4.razor

@attribute [CascadingTypeParameter(nameof(TExample))]
@typeparam TExample

<h2>List Items 4</h2>

@ChildContent

@if (ExampleList is not null)
{
    <ul style="color:green">
        @foreach (var item in ExampleList)
        {
            <li>@item</li>
        }
    </ul>

    <p>
        Type of <code>TExample</code>: @typeof(TExample)
    </p>
}

@code {
    [Parameter]
    public IEnumerable<TExample>? ExampleList { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }
}

具有所推断的级联类型的以下 组件提供了不同的显示数据。

Generics4.razor

@page "/generics-4"

<PageTitle>Generics 4</PageTitle>

<h1>Generic Type Example 4</h1>

<ListItems4 ExampleList="@(new List<string> { "Item 5", "Item 6" })">
    <ListDisplay1 Color="blue" 
                  ExampleList="@(new List<string> { "Item 1", "Item 2" })" />
    <ListDisplay1 Color="red" 
                  ExampleList="@(new List<string> { "Item 3", "Item 4" })" />
</ListItems4>

<ListItems4 ExampleList="@(new List<int> { 5, 6 })">
    <ListDisplay1 Color="blue" 
                  ExampleList="@(new List<int> { 1, 2 })" />
    <ListDisplay1 Color="red" 
                  ExampleList="@(new List<int> { 3, 4 })" />
</ListItems4>

具有所推断的级联类型的以下 组件提供了相同的显示数据。 下面的示例将数据直接分配给组件。

Generics5.razor

@page "/generics-5"

<PageTitle>Generics 5</PageTitle>

<h1>Generic Type Example 5</h1>

<ListItems4 ExampleList="stringData">
    <ListDisplay1 Color="blue" ExampleList="stringData" />
    <ListDisplay1 Color="red" ExampleList="stringData" />
</ListItems4>

<ListItems4 ExampleList="integerData">
    <ListDisplay1 Color="blue" ExampleList="integerData" />
    <ListDisplay1 Color="red" ExampleList="integerData" />
</ListItems4>

@code {
    private List<string> stringData = new() { "Item 1", "Item 2" };
    private List<int> integerData = new() { 1, 2 };
}