structをusingステートメントで使用するとboxingされるのかを検証しました。
結論
兎にも角にも調査した結論を。
// これはboxingは発生せず、GC.Allocは0で済む using (var val = new MyStruct()) {}
この記事の説明
CSharpにはusingステートメントという構文が備わっており、これによりIDisposableオブジェクトのDispose呼び出しを保証することができます。 このusingステートメントはあくまでシンタックスシュガーであり、コンパイル時にはtry-finallyに展開されます。
using ステートメント - C# リファレンス | Microsoft Docs
このusingステートメント内部でIDisposableを実装したstructを使用すると、boxingが発生するのではないか?という疑問が浮かんだので検証しました。
検証環境
- Unity2020.3.18f1
- .NET Standard2.0
- IL2CPP
検証コード
- IDisposableを実装したstruct
- IDisposableを実装したstructをIDisposableとして渡す
- IDisposableを実装したclass
の3パターンで検証しました。下二つのケースはそれぞれboxingとclassのインスタンス化が走るのでGC.Allocが実行されるのは大方予想がつきます。 今回のポイントは1つ目の「IDisposableを実装したstruct」をusingステートメント内で使用するとboxingが走るのかです。
public class StructExperiment : MonoBehaviour { private const int TRY_COUNT = 100_000; private class Data : IDisposable { public void Dispose() { } } private struct Value : IDisposable { public void Dispose() { } } public void StructDispose() { for (var i = 0; i < TRY_COUNT; i++) { using (var val = new Value()) { } } } public void IDisposableDispose() { for (var i = 0; i < TRY_COUNT; i++) { Dispose(new Value()); } } public void ClassDispose() { for (var i = 0; i < TRY_COUNT; i++) { using (var data = new Data()) {} } } private void Dispose(IDisposable disposable) { disposable.Dispose(); } }
検証結果
検証項目 | 結果 |
---|---|
IDisposableを実装したstruct | GC.Allocなし |
IDisposableを実装したstructをIDisposableとして渡す | GC.Alloc発生 |
IDisposableを実装したclass | GC.Alloc発生 |
考察
IDisposableを実装したstructをusingステートメント内部で使うコードから生成されるILをILViewerで確認してみると下記の通りになっていました。
IL_0000: ldloca.s 0 IL_0002: initobj StructExperiment/Value .try { IL_0008: leave.s IL_0018 } // end .try finally { // sequence point: hidden IL_000a: ldloca.s 0 IL_000c: constrained. StructExperiment/Value IL_0012: callvirt instance void [System.Private.CoreLib]System.IDisposable::Dispose() IL_0017: endfinally } // end handler
ここでのポイントはcallvirt命令の実行直前にconstrainedを呼び出していることにあるかと思います。
The constrained opcode allows IL compilers to make a call to a virtual function in a uniform way independent of whether ptr is a value type or a reference type.
公式ドキュメントから引用してきましたが、どうやらconstrained命令を実行することで通常のクラスのインスタンスへのポインタの代わりに構造体へのポインタを受け取ってcallvirtを実行することができるようです。 この工夫がなされているおかげで、構造体をIDisposableにboxingする必要なくDisposeメソッドを呼び出すことが実現できているということでした。