Partager via


suppression de composants ASP.NET Core Razor

Note

Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 10 de cet article.

Avertissement

Cette version d'ASP.NET Core n'est plus prise en charge. Pour plus d’informations, consultez la stratégie de support .NET et .NET Core. Pour la version actuelle, consultez la version .NET 10 de cet article.

Cet article explique le processus d’élimination des composants ASP.NET Core Razor avec IDisposable et IAsyncDisposable.

Si un composant implémente IDisposable ou IAsyncDisposable, l’infrastructure appelle la suppression des ressources lorsque le composant est supprimé de l’interface utilisateur. Ne vous fiez pas au moment exact de l’exécution de ces méthodes. Par exemple, IAsyncDisposable vous pouvez être déclenché avant ou après une attente asynchrone Task dans OnInitalizedAsync ou OnParametersSetAsync est appelée ou terminée. En outre, le code de suppression d’objets ne doit pas supposer que les objets créés pendant l’initialisation ou d’autres méthodes de cycle de vie existent.

Les composants ne doivent pas avoir besoin d’implémenter IDisposable et IAsyncDisposable simultanément. Si les deux sont implémentés, le framework exécute uniquement la surcharge asynchrone.

Le code du développeur doit s’assurer que IAsyncDisposable les implémentations ne prennent pas beaucoup de temps.

Pour plus d’informations, consultez les remarques d’introduction de ASP.NET contexte de synchronisation coreBlazor.

Suppression des références d’objets d’interopérabilité JavaScript

Des exemples dans les articles d’interopérabilité JavaScript (JS) illustrent des modèles de suppression d’objets classiques :

JS Les références d’objet d’interopérabilité sont implémentées en tant que carte clé par un identificateur du côté de l’appel JS d’interopérabilité qui crée la référence. Lorsque l’élimination de l’objet est lancée à partir du .NET ou JS du côté, Blazor supprime l’entrée de la carte et que l’objet peut être récupéré à la mémoire tant qu’aucune autre référence forte à l’objet n’est présente.

Au minimum, supprimez toujours les objets créés côté .NET pour éviter la fuite de mémoire managée .NET.

Tâches de nettoyage DOM lors de l’élimination des composants

Pour plus d’informations, consultez Interopérabilité JavaScript d'ASP.NET Core Blazor (interop JS).

Pour obtenir des conseils sur JSDisconnectedException la déconnexion d’un circuit, consultez ASP.NET interopérabilité JavaScript principale Blazor (JS interopérabilité) . Pour obtenir des conseils généraux sur la gestion des erreurs d’interopérabilité JavaScript, consultez la section Interopérabilité JavaScriptdans Gérer les erreurs dans les applications ASP.NET CoreBlazor.

Synchrone IDisposable

Pour les tâches de suppression synchrone, utilisez IDisposable.Dispose.

Le composant suivant :

  • Implémente IDisposable avec la @implementsRazor directive.
  • objSupprime , qui est un type qui implémente IDisposable.
  • Une vérification null est effectuée, car obj elle est créée dans une méthode de cycle de vie (non affichée).
@implements IDisposable

...

@code {
    ...

    public void Dispose()
    {
        obj?.Dispose();
    }
}

Si un objet unique nécessite une suppression, une lambda peut être utilisée pour supprimer l’objet lorsqu’il Dispose est appelé. L’exemple suivant s’affiche dans l’article de rendu des composants ASP.NET Core Razor et illustre l’utilisation d’une expression lambda pour l’élimination d’un Timer.

TimerDisposal1.razor :

@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 1</PageTitle>

<h1>Timer Disposal Example 1</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

TimerDisposal1.razor :

@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 1</PageTitle>

<h1>Timer Disposal Example 1</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor :

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor :

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor :

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

CounterWithTimerDisposal1.razor :

@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer = new Timer(1000);

    protected override void OnInitialized()
    {
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer.Dispose();
}

Note

Dans l’exemple précédent, l’appel à est StateHasChanged encapsulé par un appel, ComponentBase.InvokeAsync car le rappel est appelé en dehors du contexte de Blazorsynchronisation. Pour plus d’informations, consultez ASP.NET rendu des composants principauxRazor.

Si l’objet est créé dans une méthode de cycle de vie, par OnInitialized{Async}exemple, vérifiez avant null d’appeler Dispose.

TimerDisposal2.razor :

@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 2</PageTitle>

<h1>Timer Disposal Example 2</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

TimerDisposal2.razor :

@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable

<PageTitle>Timer Disposal 2</PageTitle>

<h1>Timer Disposal Example 2</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor :

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor :

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer? timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor :

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

CounterWithTimerDisposal2.razor :

@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable

<h1>Counter with <code>Timer</code> disposal</h1>

<p>Current count: @currentCount</p>

@code {
    private int currentCount = 0;
    private Timer timer;

    protected override void OnInitialized()
    {
        timer = new Timer(1000);
        timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
        timer.Start();
    }

    private void OnTimerCallback()
    {
        _ = InvokeAsync(() =>
        {
            currentCount++;
            StateHasChanged();
        });
    }

    public void Dispose() => timer?.Dispose();
}

Pour plus d’informations, consultez :

Asynchrone IAsyncDisposable

Pour les tâches d’élimination asynchrones, utilisez IAsyncDisposable.DisposeAsync.

Le composant suivant :

  • Implémente IAsyncDisposable avec la @implementsRazor directive.
  • objSupprime , qui est un type non managé qui implémente IAsyncDisposable.
  • Une vérification null est effectuée, car obj elle est créée dans une méthode de cycle de vie (non affichée).
@implements IAsyncDisposable

...

@code {
    ...

    public async ValueTask DisposeAsync()
    {
        if (obj is not null)
        {
            await obj.DisposeAsync();
        }
    }
}

Pour plus d’informations, consultez :

Affectation d’objets null supprimés

En règle générale, il n’est pas nécessaire d’affecter null des objets supprimés après l’appelDispose/DisposeAsync. Les cas rares pour l’affectation null incluent les éléments suivants :

  • Si le type de l’objet est mal implémenté et ne tolère pas les appels répétés, Dispose/DisposeAsyncaffectez null après suppression pour ignorer correctement les appels à .Dispose/DisposeAsync
  • Si un processus de longue durée continue de contenir une référence à un objet supprimé, l’affectation null permet au garbage collector de libérer l’objet malgré le processus de longue durée contenant une référence à celui-ci.

Il s’agit de scénarios inhabituels. Pour les objets qui sont implémentés correctement et se comportent normalement, il n’existe aucun point d’affectation null aux objets supprimés. Dans les rares cas où un objet doit être affecté null, nous vous recommandons de documenter la raison et de rechercher une solution qui empêche l’affectation de l’objet null.

StateHasChanged

Note

L’appel StateHasChanged n’est pas pris en DisposeDisposeAsync charge. StateHasChanged peut être appelé dans le cadre de la destruction du renderer, de sorte que la demande de mises à jour de l’interface utilisateur à ce stade n’est pas prise en charge.

Gestionnaires d’événements

Désinscrivez toujours les gestionnaires d’événements à partir d’événements .NET. Les exemples de formulaires suivants Blazor montrent comment désabonner un gestionnaire d’événements dans la Dispose méthode.

Champ privé et approche lambda :

@implements IDisposable

<EditForm ... EditContext="editContext" ...>
    ...
    <button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>

@code {
    ...

    private EventHandler<FieldChangedEventArgs>? fieldChanged;

    protected override void OnInitialized()
    {
        editContext = new(model);

        fieldChanged = (_, __) =>
        {
            ...
        };

        editContext.OnFieldChanged += fieldChanged;
    }

    public void Dispose()
    {
        editContext.OnFieldChanged -= fieldChanged;
    }
}

Approche de méthode privée :

@implements IDisposable

<EditForm ... EditContext="editContext" ...>
    ...
    <button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>

@code {
    ...

    protected override void OnInitialized()
    {
        editContext = new(model);
        editContext.OnFieldChanged += HandleFieldChanged;
    }

    private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
    {
        ...
    }

    public void Dispose()
    {
        editContext.OnFieldChanged -= HandleFieldChanged;
    }
}

Pour plus d’informations sur le EditForm composant et les formulaires, consultez ASP.NET Vue d’ensemble des formulaires principaux Blazor et les autres articles sur les formulaires dans le nœud Formulaires.

Fonctions anonymes, méthodes et expressions

Lorsque des fonctions, méthodes ou expressions anonymes sont utilisées, il n’est pas nécessaire d’implémenter IDisposable et de désabonner des délégués. Toutefois, l’échec de l’annulation d’un délégué est un problème lorsque l’objet exposant l’événement dépasse la durée de vie du composant qui inscrit le délégué. Lorsque cela se produit, une fuite de mémoire se produit, car le délégué inscrit conserve l’objet d’origine actif. Par conséquent, utilisez uniquement les approches suivantes lorsque vous savez que le délégué d’événement se supprime rapidement. En cas de doute sur la durée de vie des objets qui nécessitent une suppression, abonnez-vous à une méthode de délégué et supprimez correctement le délégué comme le montrent les exemples précédents.

Approche de méthode lambda anonyme (suppression explicite non requise) :

private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{
    formInvalid = !editContext.Validate();
    StateHasChanged();
}

protected override void OnInitialized()
{
    editContext = new(starship);
    editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e);
}

Approche d’expression lambda anonyme (suppression explicite non requise) :

private ValidationMessageStore? messageStore;

[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }

protected override void OnInitialized()
{
    ...

    messageStore = new(CurrentEditContext);

    CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear();
    CurrentEditContext.OnFieldChanged += (s, e) => 
        messageStore.Clear(e.FieldIdentifier);
}

L’exemple complet du code précédent avec des expressions lambda anonymes apparaît dans l’article de validation des formulaires core Blazor ASP.NET .

Pour plus d’informations, consultez Nettoyage des ressources non managées et des rubriques qui suivent sur l’implémentation des méthodes et Dispose des DisposeAsync méthodes.

Élimination pendant l’interopérabilité JS

Interceptez JSDisconnectedException dans les cas potentiels où la perte du BlazorSignalR circuit empêche JS les appels d’interopérabilité et génère une exception non gérée.

Pour plus d’informations, consultez les ressources suivantes :