for 文の中で Razor コンポーネントを使うと IndexOutOfRangeException が発生する件について
C# のコードだけだと、なかなかひっかからない問題なのですが Blazor の razor ファイル + 自分が作ったものではないコンポーネントの組み合わせで起きたときに問題の原因がわかるまで時間がかかったので忘れないようにメモしておきます。
起きた現象
例えば以下のような ChildContent
を持つ MyText
というコンポーネントがあるとします。
<p>
@ChildContent
</p>
@code {
[Parameter]
public RenderFragment? ChildContent { get; set; }
}
for
ループの中でループのインデックスの変数を使って配列にアクセスする処理を、このコンポーネントの ChildContent
で行うと IndexOutOfRangeException
が発生します。
@page "/"
<PageTitle>Home</PageTitle>
@for (int i = 0; i < _messages.Length; i++)
{
<MyText>@_messages[i]</MyText>
}
@code {
private readonly string[] _messages = ["a", "b", "c"];
}
理由は単純で MyText
の ChildContent
のレンダリングは for
文の中で即座に行われるのではなく、ラムダ式のように変数がキャプチャされてから後で行われるためです。そのためレンダリング時の i
の値は必ず 3
になってしまい、_messages[3]
が存在しないため IndexOutOfRangeException
が発生します。
ラムダ式だとわかりやすいですが ChildContent
を持つ Razor コンポーネントだと気づきにくいので注意が必要です。
解決策 1
解決策の 1 つ目は for
ループの中で i
をローカル変数としてキャプチャすることです。
@page "/"
<PageTitle>Home</PageTitle>
@for (int i = 0; i < _messages.Length; i++)
{
var index = i;
<MyText>@_messages[index]</MyText>
}
@code {
private readonly string[] _messages = ["a", "b", "c"];
}
これで index
は for
ループの中での i
の値をキャプチャするため、MyText
の ChildContent
に渡すときに正しい値が渡されるようになります。
解決策 2
foreach
ループを使うこともできます。というか純粋なループなら脳死で foreach
を使うのがいいです。
@page "/"
<PageTitle>Home</PageTitle>
@foreach (var message in _messages)
{
<MyText>@message</MyText>
}
@code {
private readonly string[] _messages = ["a", "b", "c"];
}
まとめ
ということで、わかってしまえば納得なのですがわかるまで時間がかかったので忘れないようにメモでした。
悩んだ+メモとして書いておくことで記憶の定着を狙う…!
Discussion