struct を interface にキャストしたときの Alloc を回避してメソッドを呼ぶ方法
はじめに
C# で struct を interface にキャストするとそれだけで Boxing が起こり GC Alloc が発生します。class ではそんな事にはならないので、知らずに cast してしまっている人も多いのではないでしょうか?
せっかく Alloc を避けようと struct を使ったのに interface で扱うとかえって Alloc が発生してしまうのは残念ですよね。
今回はこの Alloc を避けて interface メソッドを呼ぶ事ができる小ワザをご紹介します。
struct を interface にキャストすると Alloc が・・
サンプル用に作った下記の構造体はよく使う IDisposable を実装しています。
public struct DisposableStruct : IDisposable
{
void IDisposable.Dispose(){}
}
ただこの構造体の Dispose メソッドは public ではなく IDisposable の型からしか呼べない定義になっています。
こんな interface メソッドの実装はそう多くはないと思いますが、たとえば Unity の PooledObject<T> の Dispose メソッドなんかもこの形だったりと、ちょいちょい見かけたりします。
この Dispose メソッドを外部から呼ぶには IDisposable へのキャストが必要です。・・が、キャストを行った時点で GC Alloc が発生してしまいます。
public class TestScript
{
[Test] public void CastTest()
{
var s = new DisposableStruct();
// s.Dispose(); // <- これは CS1061。 public ではないので呼べない
var disposable = s as IDisposable; // <- ここで Alloc が発生
disposable.Dispose();
}
Generic メソッドで呼び出せば Alloc は発生しない!
そこで下記のように、Generic メソッドの型制約に interface 型を指定したメソッドを用意します。こうするとキャストを行う事なく interface のメソッドを呼ぶ事が可能になり Alloc も発生しなくなります。
public class TestScript
{
public static void DisposeGeneric< T >( T instance )
where T : IDisposable
{
instance.Dispose();
}
[Test] public void GenericTest()
{
var s = new DisposableStruct();
DisposeGeneric( s );
}
}
Extension メソッドでより便利に!
この方法は拡張メソッドでも利用できます。下記のように Extension メソッドを用意しておけばどこからでも簡単に Zero Allocation で呼び出せるようになります!
public static class DisposableExtension
{
public static void DisposeExt< T >( this T self )
where T : IDisposable
{
self.Dispose();
}
}
public class TestScript
{
[Test] public void ExtensionTest()
{
var s = new DisposableStruct();
s.DisposeExt();
}
}
おわりに
今回は struct の interface メソッドをキャストの Alloc なしで呼び出す方法をご紹介しました。
IDisposable を例にしましたが他の interface メソッドでも同様に Alloc を回避する事が可能です。
構造体を使う場面では実行速度や Alloc 削減を意識している事も多いと思いますので今回の小ワザが参考になれば幸いです。
Discussion