C#8.0の使い捨てref構造体



C#8.0(Visual Studio 2019 Preview 2のバージョン)の今後の変更についてブログが何を言っているのか見てみましょう:







「スタックのみの構造はC#7.2で登場しました。 これらは非常に便利ですが、同時に、たとえばインターフェースを実装できないなどの制限に密接に関連しています。 IDisposableインターフェイスを使用せずに、内部でDisposeメソッドを使用して参照構造をクリアできるようになりました。







つまり、スタックのみのref構造体はインターフェイスを実装しません。そうしないと、パッケージ化の可能性が生じます。 したがって、IDisposableを実装できず、usingステートメントでこれらの構造を使用できません。







class Program { static void Main(string[] args) { using (var book = new Book()) { Console.WriteLine("Hello World!"); } } } ref struct Book : IDisposable { public void Dispose() { } }
      
      





このコードを実行しようとすると、コンパイルエラーが発生します。







 Error CS8343 'Book': ref structs cannot implement interfaces
      
      





ただし、パブリックDispose



メソッドを参照構造に追加すると、 using



ステートメントがそれを魔法のように受け入れ、すべてがコンパイルされます。







 class Program { static void Main(string[] args) { using (var book = new Book()) { // ... } } } ref struct Book { public void Dispose() { } }
      
      





さらに、ステートメント自体の変更のおかげで、より短い形式でuseを使用できるようになりました(いわゆるusing



宣言)。







 class Program { static void Main(string[] args) { using var book = new Book(); // ... } }
      
      





しかし...なぜですか?







これは長い話ですが、一般に、暗黙的(非決定的ファイナライズ)よりも明示的なクリーンアップ(決定的ファイナライズ)の方が望ましいです。 これは直感的です。 「いつか」(環境自体がファイナライザを開始する)暗黙的なクリーニングを待つのではなく、(Close、Disposeを呼び出す、またはステートメントを使用して)できるだけ早く明示的にリソースをクリアする方が適切です。







したがって、特定のリソースを所有するタイプを作成するときは、明示的にクリーニングできるようにすることをお勧めします。 C#では、これは明らかにIDisposable



インターフェイスとそのDispose



メソッドです。







ご注意 参照された構造の場合、明示的なクリーンアップのみが使用されることに注意してください。それらのファイナライザを定義することは不可能だからです。







通常の「管理されていないメモリプールのラッパー」の実例を見て​​みましょう。 パフォーマンスに夢中な人向けのリンク構造のおかげで、可能な限り小さなスペースを占有します(ヒープはまったく使用されません)。







 public unsafe ref struct UnmanagedArray<T> where T : unmanaged { private T* data; public UnmanagedArray(int length) { data = // get memory from some pool } public ref T this[int index] { get { return ref data[index]; } } public void Dispose() { // return memory to the pool } }
      
      





ラッパーにはアンマネージリソースが含まれているため、使用後にDisposeメソッドを使用してクリーンアップします。 したがって、例は次のようになります。







 static void Main(string[] args) { var array = new UnmanagedArray<int>(10); Console.WriteLine(array[0]); array.Dispose(); }
      
      





Disposeの呼び出しについて覚えておく必要があるため、これは不便です。 また、例外を適切に処理することはここでは適用できないため、これは苦痛な決定です。 したがって、Disposeを内部から呼び出すために、usingステートメントが導入されました。 ただし、既に述べたように、この状況に適用することは不可能でした。







しかし、C#8.0では、usingステートメントを最大限に活用できます。







 static void Main(string[] args) { using (var array = new UnmanagedArray<int>(10)) { Console.WriteLine(array[0]); } }
      
      





同時に、宣言によりコードはより簡潔になりました。







 static void Main(string[] args) { using var array = new UnmanagedArray<int>(10); Console.WriteLine(array[0]); }
      
      





以下の他の2つの例(簡潔にするためにコードの多くは省略されています)は、CoreFXリポジトリから取られています。







最初の例は、ValueUtf8Converter参照構造です。これは、配列プールからbyte []配列をラップします。







 internal ref struct ValueUtf8Converter { private byte[] _arrayToReturnToPool; ... public ValueUtf8Converter(Span<byte> initialBuffer) { _arrayToReturnToPool = null; } public Span<byte> ConvertAndTerminateString(ReadOnlySpan<char> value) { ... } public void Dispose() { byte[] toReturn = _arrayToReturnToPool; if (toReturn != null) { _arrayToReturnToPool = null; ArrayPool<byte>.Shared.Return(toReturn); } } }
      
      





2番目の例はRegexWriterです。これは、明示的にクリアする必要がある2つのValueListBuilder参照構造をラップします(配列プールから配列も管理するため)。







 internal ref struct RegexWriter { ... private ValueListBuilder<int> _emitted; private ValueListBuilder<int> _intStack; ... public void Dispose() { _emitted.Dispose(); _intStack.Dispose(); } }
      
      





おわりに



取り外し可能な参照構造は、C ++のように、REALデストラクタを持つ低スペース型と考えることができます。 対応するインスタンスがusingステートメントのスコープ(using宣言の場合はスコープ)を超えるとすぐに呼び出されます。







もちろん、通常の商用指向のプログラムを作成する際に突然普及することはありませんが、高性能で低レベルのコードを作成する場合は、それらについて知っておく必要があります。







また、会議に関する記事もあります。














All Articles