All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.plugins.csharp.S6802.html Maven / Gradle / Ivy

There is a newer version: 10.2.0.105762
Show newest version

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

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