17

参照型よりも値型を使用するかどうかの決定は、パフォーマンスではなくセマンティクスに基づくべきであることを理解しています。値型に参照型メンバーを合法的に含めることができる理由がわかりません。これにはいくつかの理由があります。

1 つには、コンストラクターを必要とする構造体を構築するべきではありません。

public struct MyStruct
{
    public Person p;
    // public Person p = new Person(); // error: cannot have instance field initializers in structs

    MyStruct(Person p)
    {
        p = new Person();
    }
}

第二に、値型のセマンティクスのため:

MyStruct someVariable;
someVariable.p.Age = 2; // NullReferenceException

Personコンパイラは、宣言で初期化することを許可しません。これをコンストラクターに移動するか、呼び出し元に依存するか、NullReferenceException. これらの状況はどれも理想的ではありません。

.NET Framework には、値型内の参照型の例がありますか? いつこれを行う必要がありますか?

4

3 に答える 3

20

値型のインスタンスに参照型のインスタンスが含まれることはありません。参照型のオブジェクトはマネージド ヒープのどこかにあり、値型のオブジェクトにはそのオブジェクトへの参照が含まれている場合があります。このような参照のサイズは固定されています。これを行うのは完全に一般的です — たとえば、構造体内で文字列を使用するたびに。

しかし、はい、パラメーターのないコンストラクターを定義できないため、参照型フィールドの初期化を保証することはstructできません (また、C# 以外の言語で定義した場合、呼び出されることを保証することもできません)。

structあなたは、「コンストラクターを要求するために a をビルドしない」べきだと言います。私はそうではないと言います。値型はほとんど常に不変であるべきなので、コンストラクターを使用する必要があります (プライベート コンストラクターへのファクトリを介して可能性が高い)。そうでなければ、面白いコンテンツはありません。

コンストラクターを使用します。コンストラクタは問題ありません。

Personのインスタンスを initializeに渡したくない場合はp、プロパティを介して遅延初期化を使用できます。(明らかに public フィールドpはデモンストレーション用だったからですよね?)

public struct MyStruct
{
    public MyStruct(Person p)
    {
        this.p = p;
    }

    private Person p;

    public Person Person
    {
        get
        {
            if (p == null)
            {
                p = new Person(…); // see comment below about struct immutability
            }
            return p;
        }
    }

    // ^ in most other cases, this would be a typical use case for Lazy<T>;
    //   but due to structs' default constructor, we *always* need the null check.
}
于 2012-05-02T13:55:17.757 に答える
3

クラス型フィールドを保持する構造体には、主に 2 つの有用なシナリオがあります。

  1. 構造体は、不変オブジェクトへの変更可能な参照を保持します (`String` が最も一般的です)。不変オブジェクトへの参照は、null 許容値型と通常の値型の間のクロスとして動作します。前者の「Value」および「HasValue」プロパティはありませんが、可能な (およびデフォルトの) 値として null があります。プロパティを介してフィールドにアクセスする場合、フィールドが null の場合、そのプロパティは null 以外のデフォルト値を返すことがありますが、フィールド自体を変更してはならないことに注意してください。
  2. 構造体は、可変オブジェクトへの「不変」参照を保持し、オブジェクトまたはその内容をラップする役割を果たします。List.Enumerator は、おそらくこのパターンを使用する最も一般的な構造体です。構造体フィールドを不変のふりをさせるのは危険な構造 (*) のようなものですが、状況によってはかなりうまくいく可能性があります。このパターンが適用されるほとんどの場合、構造体の動作は基本的にクラスの動作と同じになりますが、パフォーマンスが向上します (**)。

(*) ステートメントstructVar = new structType(whatever);は の新しいインスタンスを作成し、structTypeそれをコンストラクターに渡し、そのstructVar新しいインスタンスからすべてのパブリックおよびプライベート フィールドを にコピーして変更しstructVarます。それが完了すると、新しいインスタンスは破棄されます。したがって、すべての構造体フィールドは、そうではないふりをしている場合でも、変更可能です。それらが不変であるふりをすることは、structVar = new structType(whatever);実際に実装されている方法が決して問題を引き起こさないことがわかっていない限り、危険な場合があります。

(**) 状況によっては、構造体のパフォーマンスが向上します。クラスは他のクラスのパフォーマンスが向上します。一般に、いわゆる「不変」構造体は、パフォーマンスが向上することが期待され、セマンティクスがクラスのセマンティクスと異なるコーナーケースが問題を引き起こすとは予想されない状況で、クラスよりも選択されます。

一部の人々は、構造体はクラスに似ているがより効率的であるというふりをするのが好きで、クラスではないという事実を利用する方法で構造体を使用することを嫌います。そのような人々はおそらく、上記のシナリオ (2) を使用する傾向があるだけでしょう。Stringシナリオ #1 は、変更可能な構造体、特に本質的に値として動作する型の場合に非常に役立ちます。

于 2012-05-02T16:55:29.010 に答える
0

マークの答えに付け加えたかったのですが、コメントが多すぎました。

C#の仕様を見ると、構造体コンストラクターについて次のようになっています。

Structコンストラクターはnew演算子で呼び出されますが、これはメモリが割り当てられていることを意味するものではありません。オブジェクトを動的に割り当ててそのオブジェクトへの参照を返す代わりに、構造体コンストラクターは単に構造体値自体(通常はスタック上の一時的な場所)を返し、この値は必要に応じてコピーされます。

(仕様のコピーは以下にあります
C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC#\Specifications\1033

したがって、構造体コンストラクターは本質的にクラスコンストラクターとは異なります。

これに加えて、構造体は値によってコピーされることが期待されているため、次のようになります。

構造体を使用すると、変数はそれぞれ独自のデータコピーを持ち、一方の操作が他方に影響を与えることはできません。

構造体で参照型を見たときはいつでも、それは文字列でした。文字列は不変であるため、これは機能します。あなたのPersonオブジェクトは不変ではなく、構造体の予想される動作から逸脱しているため、非常に奇妙で深刻なバグを引き起こす可能性があると思います。

そうは言っても、構造体のコンストラクターで見られるエラーはp、パラメーターと同じ名前のパブリックフィールドがあり、構造体をとしてp参照していないか、キーワードが欠落している可能性があります。pthis.pstruct

于 2012-05-02T14:10:04.900 に答える