org.sonar.plugins.csharp.S6802.html Maven / Gradle / Ivy
Why is this an issue?
In Blazor, using lambda expressions
as event handlers when the UI elements
are rendered in a loop can lead to negative user experiences and performance issues. This is particularly noticeable when rendering a large number of
elements.
The reason behind this is that Blazor rebuilds all lambda expressions within the loop every time the UI elements are rendered.
How to fix it
Ensure to not use a delegate in elements rendered in loops, you can try:
- using a collection of objects containing the delegate as an Action,
- or extracting the elements into a dedicated component and using an EventCallback to call the delegate
Code examples
Noncompliant code example
@for (var i = 1; i < 100; i++)
{
var buttonNumber = i;
<button @onclick="@(e => DoAction(e, buttonNumber))"> @* Noncompliant *@
Button #@buttonNumber
</button>
}
@code {
private void DoAction(MouseEventArgs e, int button)
{
// Do something here
}
}
Compliant solution
@foreach (var button in Buttons)
{
<button @key="button.Id" @onclick="button.Action"> @* Compliant *@
Button #@button.Id
</button>
}
@code {
private List<Button> Buttons { get; set; } = new();
protected override void OnInitialized()
{
for (var i = 0; i < 100; i++)
{
var button = new Button();
button.Action = (e) => DoAction(e, button);
Buttons.Add(button);
}
}
private void DoAction(MouseEventArgs e, Button button)
{
// Do something here
}
private class Button
{
public string? Id { get; } = Guid.NewGuid().ToString();
public Action<MouseEventArgs> Action { get; set; } = e => { };
}
}
Noncompliant code example
@* Component.razor *@
@for (var i = 1; i < 100; i++)
{
var buttonNumber = i;
<button @onclick="@(e => DoAction(e, buttonNumber))"> @* Noncompliant *@
Button #@buttonNumber
</button>
}
@code {
private void DoAction(MouseEventArgs e, int button)
{
// Do something here
}
}
Compliant solution
@* MyButton.razor *@
<button @onclick="OnClickCallback">
@ChildContent
</button>
@code {
[Parameter]
public int Id { get; set; }
[Parameter]
public EventCallback<int> OnClick { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
private void OnClickCallback()
{
OnClick.InvokeAsync(Id);
}
}
@* Component.razor *@
@for (var i = 1; i < 100; i++)
{
var buttonNumber = i;
<MyButton Id="buttonNumber" OnClick="DoAction">
Button #@buttonNumber
</MyButton>
}
@code {
private void DoAction(int button)
{
// Do something here
}
}
Resources
Documentation
- Microsoft Learn - ASP.NET
Core Blazor performance best practices
- Microsoft Learn - ASP.NET Core
Blazor event handling - Lambda expressions
- Microsoft Learn - Event handling -
EventCallback Struct
Benchmarks
The results were generated with the help of BenchmarkDotNet and Benchmark.Blazor:
Method
NbButtonRendered
Mean
StdDev
Ratio
UseDelegate
10
6.603 us
0.0483 us
1.00
UseAction
10
1.994 us
0.0592 us
0.29
UseDelegate
100
50.666 us
0.5449 us
1.00
UseAction
100
2.016 us
0.0346 us
0.04
UseDelegate
1000
512.513 us
9.7561 us
1.000
UseAction
1000
2.005 us
0.0243 us
0.004
Hardware configuration:
BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 10 (10.0.19045.3448/22H2/2022Update)
12th Gen Intel Core i7-12800H, 1 CPU, 20 logical and 14 physical cores
.NET SDK 8.0.100-rc.1.23463.5
[Host] : .NET 7.0.11 (7.0.1123.42427), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.11 (7.0.1123.42427), X64 RyuJIT AVX2
© 2015 - 2024 Weber Informatics LLC | Privacy Policy