web-dev-qa-db-ja.com

定義サイトのrefstructとは

少し前にGitHubで「reflikestruct」という用語を聞いたことがあると思います。

最新のC#バージョン(7.3)を手にしたので、ようやく自分でテストすることができました。したがって、これは有効なコードのようです。

public ref struct MyStruct
{
    int x;
}

それについてのドキュメントがあるので、reflocalsとrefreturnsが何であるかを知っています。しかし、refstructに関するドキュメントが見つかりませんでした。


参照構造体は、自動プロパティまたはフィールドでは使用できません。オブジェクトにキャストすることもできません。これらは経験的な発見でした。

新しいc#が最近私に与えた「スパン」の背景から、ref構造体はスタックのみの構造体であると推測しました。それは決してヒープに進まない構造体です。しかし、100%確実ではありません。

これに関するドキュメントがあるはずですが、見つかりませんでした。

8
M.kazem Akhgary

いくつかの調査の後、私は C#7.2のrefのようなタイプの安全性のコンパイル時の強制 に関するこの記事に出くわしました。

このC#機能は、「内部ポインター」または「参照のようなタイプ」とも呼ばれます。提案は、コンパイラがSpan<T>などの特定のタイプがスタックにのみ表示されることを要求できるようにすることです。

このサイトには、主にガベージコレクションとスタック割り当てに関して、そうすることの利点も記載されています。


Refのような型を使用すると、次のようないくつかの制限もあります。

  • refのような型を配列要素の型にすることはできません
  • ref-like型はジェネリック型引数として使用できません
  • refのような変数はボックス化できません
  • ref-likeタイプは、ref-likeタイプではない通常のフィールドにすることはできません。
  • refのようなタイプはインターフェースを実装できません
  • 非同期メソッドでのrefのような型の使用の禁止などの間接的な制限。これは、実際にはrefのような型のフィールドを禁止した結果です。

これにより、パラメータ、ローカル変数、場合によっては戻り値に使用できるように制限されます。


@UnholySheepがコメントで指摘したように、 Microsoftの公式ドキュメント もあります。

12
Ian H.

C#7.2へのこの追加は、実際には feature であり、そのようにマークされた新しい機能を追加または有効にするという意味ではありません。値型自体、むしろ、開発者が特定の制限を宣言または公開できるようにします。これは他の場所でのその型の許容される使用を管理します。

[編集:github/dotnetサイトの span-safety を参照]

したがって、ref structの指定が構造体のエンドユーザーに与えるものを検討する代わりに、それが作成者にどのように役立つかを検討してください。外部使用に制限を追加すると、論理的にはref structが想定する関連保証が必要になるため、キーワードの効果は、ref structが特定の特定のものを必要とすることを実行できるようにするか "ライセンス" になります。保証します。

重要なのは、それが間接のメリットであるということです。これは、通常ref structによってライセンス供与されていると見なされる種類の操作は、基本的にキーワードの問題ではなく、実装および試行できる可能性があるためです。 、ref structマーキング(またはそうでない)に関係なく、どこでも賢明なコードで。

理論的な部分についてはこれだけです。実際には、付随するすべての制限を受け入れるという極端な点でさえ、追加の保証に本質的に依存する「wilyコード」ユースケースとは何ですか?基本的に、これはstruct管理された参照をそれ自体に公開するまたはそのフィールドの1つを実行する機能です。

通常、C#は、thisのインスタンスメソッドからリークするstruct参照に対して強い制限を適用します。

error CS8170: Struct members cannot return 'this' or other instance members by reference

コンパイラーは、thisが値型からリークする可能性が事実上ないことを確認する必要があります。これは、構造体インスタンスが呼び出しの目的で一時的にボックス化されている可能性があるためです(一部の用途では非常に可能性が高い)。インスタンスメソッド。この場合、GetPinnableReference(またはその内部)へのマネージポインターが取得される可能性のある永続的なstructインスタンスはありません。

近年のすべてのrefの機能強化により、C#は、thisのエスケープを検出して禁止するために、さらに長い時間を費やしています。たとえば、上記に加えて、次のようになります。

error CS8157: Cannot return 'x' by reference because it was initialized to a value that cannot be returned by reference

..および...などの関連エラー.

error CS8374: Cannot ref-assign 'foo' to 'p' because 'foo' has a narrower escape scope than 'p'.

コンパイラがCS8157をアサートする根本的な理由が複雑であるか、わかりにくい場合がありますが、コンパイラは保守的な「申し訳ないより安全である」アプローチに固執します。これにより、たとえば、追加の特別な知識がある場合に誤検知が発生することがあります。エスケープは最終的にスタック内に含まれます。

CS8157が本当に保証されていない場合(つまり、コンパイラが推測できなかった情報を考慮して)、これらは前に触れた「おそらく成功した」wilyコードの例を表しており、一般に簡単な回避策はありません。特にref struct経由ではありません。これは、複雑な誤検知が発生するのは、高レベルのref-パスコードシナリオでのみ発生することが多く、そもそもref structに適用される極端な制限を採用できないためです。

代わりに、ref structは非常に単純な値型に使用されます。 this参照が常に上位スタックフレームに固定されることを保証することで(したがって、重要なことに、GCヒープに溢れることはありません)、そのようなタイプは、マネージポインターを自分自身または彼らのインテリア。

ただし、ref structは、それが提供するリラクゼーションがどのように、なぜ、何に使用されるかについては不可知論者であると言ったことを思い出してください。私が特にほのめかしていたのは、残念ながら、ref structを使用してもCS8157が消えることはないということです(これはバグだと思います。 ここ および ここ を参照してください)。

独自のthisを返すことを適切に許可する必要があるref structコードは、コンパイラーによってそれを防ぐことができるため、内でコーディングするときに致命的なエラーを回避するには、かなり野蛮な手法を使用する必要があります。おそらく解放されたref structインスタンスメソッド。つまり、C#で記述された値型インスタンスメソッドは、致命的なエラーを正当にオーバーライドする必要がありますCS8170/CS8157 can 'this'ポインタを不透明にするそれをラウンドトリップすることによってIntPtr。これは読者の演習として残されていますが、これを行う1つの方法は、 System。Runtime。CompilerServices。Unsafe パッケージを使用することです。

6
Glenn Slayden

他の答えに少し追加するだけです。基本的に、彼らはマネージドポインタをメンバーとして保持できるようにref構造体を作成しました。これは、ガベージコレクションができないことを意味し、ヒープに到達すると、GCがクラッシュします。それを使ってできることとできないことに対する奇妙な制限は、すべてこれに関係しています(ここのMicrosoftのドキュメントで概説されています):

C#7.2の参照セマンティクスに関するMicrosoftドキュメント

これらはすべて完全に魅力的ですが、いったいなぜこの機能を提供したのかを実際には説明していません。本当の理由は、マネージメモリとアンマネージメモリの両方を処理するAPIが共通のインターフェースを持つことを許可することでした(つまり、無限のオーバーロードの必要性を排除します)。

これはこのブログで詳細に説明されています:

Adam Sitnik on Span <T>

4
Adam Brown