注
これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
警告
このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、 .NET および .NET Core サポート ポリシーを参照してください。 現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
重要
この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft は、ここで提供される情報に関して明示的または黙示的な保証を行いません。
現在のリリースについては、この記事の .NET 9 バージョンを参照してください。
この記事では、IDisposable と IAsyncDisposableを使用した ASP.NET Core Razor コンポーネントの破棄について説明します。
コンポーネントが IDisposable または IAsyncDisposableを実装する場合、フレームワークは、コンポーネントが UI から削除されたときにリソースの破棄を呼び出します。 これらのメソッドが実行される正確なタイミングに依存しないでください。 たとえば、IAsyncDisposable は、OnInitalizedAsync
または OnParametersSetAsync
での非同期 Task の待機中や、呼び出し後もしくは完了後にトリガーされることがあります。 また、オブジェクト破棄コードでは、初期化またはその他のライフサイクル メソッド中に作成されたオブジェクトが存在することを想定しないでください。
コンポーネントは、IDisposable と IAsyncDisposable を同時に実装する必要はありません。 両方が実装されている場合、フレームワークは非同期オーバーロードのみを実行します。
開発者コードでは、IAsyncDisposable の実装が完了するまでに長い時間がかからないようにする必要があります。
詳細については、ASP.NET Core Blazor 同期コンテキストの概要に関する解説を参照してください。
JavaScript 相互運用オブジェクト参照の破棄
JavaScript (JS) 相互運用に関する記事全体の例、一般的なオブジェクト破棄パターンを示しています。
ASP.NET Core Blazorの .NET メソッドから JavaScript 関数を呼び出すで説明されているように、.NET から JS を呼び出すときは、作成された IJSObjectReference/IJSInProcessObjectReference/JSObjectReference を .NET または JS から破棄して、JS メモリのリークを回避します。
ASP.NET Core Blazorの JavaScript 関数から .NET メソッドを呼び出す方法に関するページで説明されているように、JSから .NET を呼び出すときは、.NET から作成された DotNetObjectReference を破棄するか、JS から .NET メモリをリークしないようにします。
JS 相互運用オブジェクト参照は、参照を作成する JS 相互運用呼び出しの側で識別子によってキー付けされたマップとして実装されます。 オブジェクトの破棄が .NET または JS の側から開始されると、Blazor はマップからエントリを削除し、オブジェクトへの強力な参照が他に存在しない限り、オブジェクトをガベージ コレクションできます。
.NET マネージド メモリのリークを回避するため、少なくとも、.NET 側で作成されたオブジェクトを常に破棄します。
コンポーネントの破棄中の DOM クリーンアップ タスク
詳しくは、「ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)」をご覧ください。
回線が切断されたときの JSDisconnectedException に関するガイダンスについては、ASP.NET Core Blazor JavaScript の相互運用性 (JS 相互運用)を参照してください。 一般的な JavaScript 相互運用エラー処理のガイダンスについては、「ASP.NET Core Blazor アプリの でエラーを処理する」の「JavaScript 相互運用」セクションを参照してください。
同期的 IDisposable
同期破棄タスクの場合は、IDisposable.Disposeを使用します。
次のコンポーネント:
@implements
Razor ディレクティブを使用して IDisposable を実装します。- IDisposableを実装する型である
obj
を破棄します。 - ライフサイクル メソッドで
obj
が作成されるため、null チェックが実行されます (表示されません)。
@implements IDisposable
...
@code {
...
public void Dispose()
{
obj?.Dispose();
}
}
1 つのオブジェクトで破棄が必要な場合は、ラムダを使用して、Dispose が呼び出されたときにオブジェクトを破棄できます。 次に示す例は、ASP.NET Core Razor コンポーネントのレンダリングを扱った 記事に掲載されており、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();
}
注
前の例では、コールバックが Blazorの同期コンテキストの外部で呼び出されるため、StateHasChanged の呼び出しは ComponentBase.InvokeAsync の呼び出しによってラップされます。 詳しくは、「ASP.NET Core Razor コンポーネントのレンダリング」をご覧ください。
オブジェクトがライフサイクル メソッド (OnInitialized{Async}
など) で作成されている場合は、Dispose
を呼び出す前に null
を確認します。
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();
}
詳細については、以下を参照してください。
非同期的 IAsyncDisposable
非同期破棄タスクの場合は、IAsyncDisposable.DisposeAsyncを使用します。
次のコンポーネント:
@implements
Razor ディレクティブを使用して IAsyncDisposable を実装します。- IAsyncDisposable を実装するアンマネージド型である
obj
を破棄します。 - ライフサイクル メソッドで
obj
が作成されるため、null チェックが実行されます (表示されません)。
@implements IAsyncDisposable
...
@code {
...
public async ValueTask DisposeAsync()
{
if (obj is not null)
{
await obj.DisposeAsync();
}
}
}
詳細については、以下を参照してください。
破棄されたオブジェクトへの null
の割り当て
通常、Dispose/DisposeAsyncを呼び出した後、破棄されたオブジェクトに null
を割り当てる必要はありません。 null
を割り当てるまれなケースは次のとおりです。
- オブジェクトの型が適切に実装されておらず、Dispose/DisposeAsyncへの繰り返し呼び出しを許容しない場合は、破棄後に
null
を割り当てて、Dispose/DisposeAsyncへの呼び出しをさらに適切にスキップします。 - 有効期間の長いプロセスが破棄されたオブジェクトへの参照を保持し続ける場合、
null
を割り当てると、ガベージ コレクター は、そのオブジェクトへの参照を保持する有効期間の長いプロセスにもかかわらず、オブジェクトを解放できます。
これらは通常とは異なるシナリオです。 正しく実装され、正常に動作するオブジェクトの場合、破棄されたオブジェクトに null
を割り当てる意味はありません。 オブジェクトを null
割り当てる必要があるまれなケースでは、理由を文書化し、null
割り当てる必要のないソリューションを探することをお勧めします。
StateHasChanged
注
Dispose
および DisposeAsync
での StateHasChanged の呼び出しはサポートされていません。 StateHasChanged レンダラーの破棄の一環として呼び出される可能性があるため、その時点での UI 更新の要求はサポートされていません。
イベント ハンドラー
.NET イベントからイベント ハンドラーを常にサブスクライブ解除します。 次の Blazor フォーム 例は、Dispose
メソッドでイベント ハンドラーの登録を解除する方法を示しています。
プライベート フィールドとラムダのアプローチ
@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;
}
}
プライベート メソッドを使ったアプローチ
@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;
}
}
EditForm コンポーネントとフォームの詳細については、ASP.NET Core Blazor フォームの概要 及び フォーム ノード内のその他のフォーム記事を参照してください。
匿名関数、メソッド、および式
匿名関数 、メソッド、または式を使用する場合、IDisposable を実装してデリゲートのサブスクライブを解除する必要はありません。 ただし、デリゲートの登録解除に失敗すると、イベントを公開するオブジェクトがデリゲート を登録するコンポーネントの有効期間を上回る場合に問題になります。 この場合、登録されたデリゲートが元のオブジェクトを維持するため、メモリ リークが発生します。 そのため、イベント デリゲートが迅速に解放されることがわかっている場合にのみ、次の方法を使用します。 破棄が必要なオブジェクトの有効期間が不明な場合は、デリゲート メソッドをサブスクライブし、前の例に示すようにデリゲートを適切に破棄します。
匿名ラムダ メソッドのアプローチ (明示的な破棄は必要ありません):
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);
}
匿名のラムダ式のアプローチ (明示的な破棄は不要):
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);
}
上記のコードと匿名ラムダ式の完全な例は、ASP.NET Core Blazor フォームの検証 記事に示されています。
詳細については、「アンマネージ リソース のクリーンアップ」と、Dispose
および DisposeAsync
メソッドの実装に関するトピックを参照してください。
JS 相互運用中の破棄
Blazor の SignalR 回線が失われる潜在的ケースでは、JS の相互運用の呼び出しが妨げられて、ハンドルされない例外が発生する事態を防止するために、JSDisconnectedException をトラップします。
詳細については、次のリソースを参照してください。
ASP.NET Core