web-dev-qa-db-ja.com

構造体に参照型のフィールドを含めることができます

構造体に参照型のフィールドを含めることはできますか?そして、もしできるならこれは悪い習慣ですか?

52
James Hay

はい、できます。それは良い考えですか?まあ、それは状況に依存します。個人的には、そもそも自分の構造体を作成することはめったにありません...私は、ユーザー定義の新しい構造体をある程度懐疑的に扱います。 alwaysが間違ったオプションであることを示唆しているのではなく、クラスよりも明確な引数が必要なだけです。

構造体が可変オブジェクトへの参照を持つことは悪い考えですが、それ以外の場合はlook独立しているが、そうではない2つの値を持つことができます。

MyValueType foo = ...;
MyValueType bar = foo; // Value type, hence copy...

foo.List.Add("x");
// Eek, bar's list has now changed too!

可変構造体は悪です。変更可能な型への参照を持つ不変の構造体は、さまざまな意味でこっそり悪です。

81
Jon Skeet

もちろん、そうすることは悪い習慣ではありません。

struct Example {
  public readonly string Field1;
}

Readonlyは必須ではありませんが、構造体を不変にすることをお勧めします。

19
JaredPar

はい、それは可能です。そして、はい、それは通常悪い習慣です。

.NETフレームワーク自体を見ると、事実上すべての構造体にプリミティブ値の型だけが含まれていることがわかります。

構造体を変更できないのは、参照型の振る舞いが原因です。この記事を読む: http://www.yoda.arachsys.com/csharp/parameters.html

オブジェクト(intやdoubleのようなプリミティブではないもの)を含む構造体があり、構造体のインスタンスをコピーする場合、内部のオブジェクトは単なる参照(ポインタ)であるため、「ディープ」コピーされません。 )実際のクラスを含むメモリ位置に。したがって、クラスインスタンスを含む変更可能な構造体をコピーすると、コピーは元のインスタンスと同じインスタンスを参照します(したがって、バーのリストは上記で変更されています)。

構造体を変更可能にする必要がある場合は、クラスインスタンスを読み取り専用にするか、これをお勧めしません。構造体のコピーを作成しないようにしてください。

4
Matthew

はい、できます。

場合によります。

多くの場合、構造体は不変である必要があるというスタンスを持っています。この場合、オブジェクトへの参照を保持することは、そうではない可能性があります。

しかし、それは状況に依存します。

これは反対票を得たので、それがより明確になるかどうかを確認するために少し書き直そうとしています。この質問は古いです。いいね!私は最近、これについて詳しく説明しているリンクもいくつか見つけました。

私が追加しようとしている点は、もしdo参照フィールドを宣言するなら、あなたは自分のブロックのoutsideを推論できる必要があるということです:誰かがいるとき使用あなたの構造体。追加した具体的なポイントは、実際には構造体の読み取り専用フィールドを宣言することだけでした。ただしその場合、構造体にあるフィールドによって結果が変わる可能性があります。そしてそれは推論するのが難しいです。

プログラマーがreadonlystructフィールドを含むclassを宣言したこのリンクに出くわしました。彼のクラスのfieldstructです--- LinkedList<T>.Enumerator-そして、フィールドがreadonlyであるために壊れました---独自のクラスメソッドは列挙子構造体のコピーを取得し、状態は動的ではなくコピーされます。

しかし、、先に進み、構造体フィールドからreadonlyを削除するだけでコードを修正した場合(---機能); それから、独自のクラスをstructにすることを決定した場合、今はもう一度yourの構造体はそれを次のように使用できません読み取り専用フィールドまたはそれ以外の場合は、同じ問題にかまれる。 (そして、読み取り専用の列挙子がないためにこれが不自然であると思われる場合、実際にはリセットがサポートされている場合はmightです!)

したがって、それが最も明確な例ではない場合、私が主張している点は、独自の実装について推論できることですが、構造体である場合は、次のことを行う必要がありますalso値をコピーするコンシューマについての理由彼らは得るでしょう。

私が見つけた例は以下にリンクされています。

彼のクラスはstructではありませんが、m_Enumeratorフィールドが含まれています(そしてプログラマーはそれがisstructであることを知っているはずです)。

このクラスのメソッドはその値のコピーを取得し、機能しないことがわかりました。 ---実際にthisブロックを調べて、それを理解することができます。

あなたはcanフィールドを作成して修正しますnotreadonly ---すでに混乱を招いています。ただし、フィールドをinterfaceタイプとして宣言することで修正できます--- IEnumerator<int>

しかし、もしdoフィールドをstructとして宣言されたままにし、readonlyではないと宣言することで修正しますand次に、クラスを次のように定義することを選択しますstruct、次にnow誰かがyourstructのインスタンスを一部のクラスのreadonlyフィールドとして宣言した場合、again負けます!

例えば。:

public class Program
{
    private struct EnumeratorWrapper : IEnumerator<int>
    {
        // Fails always --- the local methods read the readonly struct and get a copy
        //private readonly LinkedList<int>.Enumerator m_Enumerator;

        // Fixes one: --- locally, methods no longer get a copy;
        // BUT if a consumer of THIS struct makes a readonly field, then again they will
        // always get a copy of THIS, AND this contains a copy of this struct field!
        private LinkedList<int>.Enumerator m_Enumerator;

        // Fixes both!!
        // Because this is not a value type, even a consumer of THIS struct making a
        // readonly copy, always reads the memory pointer and not a value
        //private IEnumerator<int> m_Enumerator;


        public EnumeratorWrapper(LinkedList<int> linkedList) 
            => m_Enumerator = linkedList.GetEnumerator();


        public int Current
            => m_Enumerator.Current;

        object System.Collections.IEnumerator.Current
            => Current;

        public bool MoveNext()
            => m_Enumerator.MoveNext();

        public void Reset()
            => ((System.Collections.IEnumerator) m_Enumerator).Reset();

        public void Dispose()
            => m_Enumerator.Dispose();
    }


    private readonly LinkedList<int> l = new LinkedList<int>();
    private readonly EnumeratorWrapper e;


    public Program()
    {
        for (int i = 0; i < 10; ++i) {
            l.AddLast(i);
        }
        e = new EnumeratorWrapper(l);
    }


    public static void Main()
    {
        Program p = new Program();

        // This works --- a local copy every time
        EnumeratorWrapper e = new EnumeratorWrapper(p.l);
        while (e.MoveNext()) {
            Console.WriteLine(e.Current);
        }

        // This fails if the struct cannot support living in a readonly field
        while (p.e.MoveNext()) {
            Console.WriteLine(p.e.Current);
        }
        Console.ReadKey();
    }
}

structフィールドを指定してinterfaceを宣言すると、そこに何があるのか​​はわかりませんが、実際に参照するだけで、得られるものについてさらに推論することができます。これは非常に興味深いです。しかし、言語がstructで非常に多くの自由を許可しているためだけです。非常に単純なものから始める必要があります。あなたが具体的に推論できるものだけを追加してください!

もう1つのポイントは、リファレンスには次のようにも書かれているということですすべきデフォルト値を妥当なものとして定義します。 not参照フィールドで可能です!誤ってデフォルトコンストラクタを呼び出した場合--- structで常に可能---になると、null参照が取得されます。

最後に一言。多くの人々は、可変構造体と大きな可変構造体を守っています。しかし、よく調べてみると、通常、これらのオブジェクトは動作について有限に推論できるような方法でスコープされており、構造体は、それらの不変式が発生する可能性のあるスコープにリークしません。 変更。

...あまりにも多くの人が「クラスと同じように... x、y、z、1、2、アルファ、ベータディスコ」として構造体を説明し始めます。読み取り専用の値として説明する必要があります。限目;あなたが何かを知っている今を除いて、あなたは何かを追加することについて推論を始めることができます!

私がアクロスに来た例はここにあります:

https://www.red-gate.com/simple-talk/blogs/why-enumerator-structs-are-a-really-bad-idea/

1
Steven Coco