14

私は最近、この Stackoverflow の質問に出くわしました:いつ構造体を使用するのですか?

その中で、それは少し深遠なことを言った答えを持っていました:

さらに、構造体が (Enumerator のように) インターフェイスを実装し、その実装された型にキャストされると、構造体は参照型になり、ヒープに移動されることに注意してください。Dictionary クラスの内部である Enumerator は、依然として値型です。ただし、メソッドが GetEnumerator() を呼び出すとすぐに、参照型の IEnumerator が返されます。

これはどういう意味ですか?

私が何かを持っていたら

struct Foo : IFoo 
{
  public int Foobar;
}

class Bar
{
  public IFoo Biz{get; set;} //assume this is Foo
}

...

var b=new Bar();
var f=b.Biz;
f.Foobar=123; //What would happen here
b.Biz.Foobar=567; //would this overwrite the above, or would it have no effect?
b.Biz=new Foo(); //and here!?

参照型のように扱われる値型構造の詳細なセマンティクスは正確には何ですか?

4

4 に答える 4

15

構造体型のすべての宣言は、実際にはランタイム内で値型とヒープ オブジェクト型の 2 つの型を宣言します。外部コードの観点から見ると、ヒープ オブジェクト タイプは、対応する値タイプのフィールドとメソッドを持つクラスのように動作します。内部コードの観点からは、ヒープ型はthis、対応する値型のフィールドを持っているかのように動作します。

Object値型を参照型 ( 、ValueType、 、または任意のインターフェイス型)にキャストしようとするとEnum、対応するヒープ オブジェクト型の新しいインスタンスが生成され、その新しいインスタンスへの参照が返されます。値の型を参照型の格納場所に格納しようとしたり、参照型のパラメーターとして渡そうとしたりすると、同じことが起こります。値がヒープ オブジェクトに変換されると、外部コードの観点からはヒープ オブジェクトとして動作します。

値型が最初にヒープ オブジェクトに変換されずにインターフェイスの値型の実装が使用される唯一の状況は、インターフェイス型を制約として持つジェネリック型パラメーターとして渡される場合です。その特定の状況では、最初にヒープ オブジェクトに変換する必要なく、値型のインスタンスでインターフェイス メンバーを使用できます。

于 2013-03-04T20:15:35.667 に答える
2

ボックス化とボックス化解除について読んでください(インターネットを検索してください)。たとえば、MSDN: Boxing and Unboxing (C# Programming Guide)

SO スレッドも参照してください。C# でボックス化とボックス化解除が必要な理由 、およびそのスレッドにリンクされているスレッド。

注: 次のように、値型の基底クラスに「変換」する場合はそれほど重要ではありません。

object obj = new Foo(); // boxing

または実装されたインターフェースに「変換」します。

IFoo iFoo = new Foo(); // boxing

a が持つ唯一の基本クラスstructは、System.ValueTypeおよびobject( を含むdynamic) です。enum型の基本クラスは、、、System.EnumおよびSystem.ValueTypeですobject

構造体は、任意の数のインターフェースを実装できます (ただし、基本クラスからインターフェースを継承しません)。列挙型はIComparable(非ジェネリック バージョン)、IFormattableを実装します。IConvertibleこれは、基底クラスSystem.Enumがこれら 3 つを実装するためです。

于 2013-05-14T10:26:17.710 に答える
1

少し遅れるかもしれませんが、2013-03-04 の実験に関する投稿に返信しています :)

これを覚えておいてください: struct 値をインターフェース型の変数に代入する (またはそれをインターフェース型として返す) たびに、それはボックス化されます。新しいオブジェクト (ボックス) がヒープ上に作成され、そこに構造体の値がコピーされると考えてください。そのボックスは、他のオブジェクトと同様に、参照が作成されるまで保持されます。

動作 1 では、タイプ IFoo の Biz auto プロパティがあるため、ここに値を設定すると、ボックス化され、プロパティはボックスへの参照を保持します。プロパティの値を取得するたびに、ボックスが返されます。このようにして、ほとんどの場合、Foo がクラスであるかのように機能し、期待どおりの結果が得られます。値を設定すると、それが返されます。

動作 2 では、構造体 (フィールド tmp) を格納し、Biz プロパティはその値を IFoo として返します。つまり、get_Biz が呼び出されるたびに、新しいボックスが作成されて返されます。

Main メソッドに目を通します。b.Biz が表示されるたびに、それは別のオブジェクト (ボックス) です。これにより、実際の動作が説明されます。

例: インライン

    b.Biz.Foobar=567;

b.Biz がヒープにボックスを返し、その中の Foobar を 576 に設定すると、それへの参照を保持しないため、すぐにプログラムから失われます。

次の行に b.Biz.Foobar と記述しますが、この b.Biz の呼び出しにより、Foobar がデフォルトの 0 値を持つまったく新しいボックスが再び作成され、それが出力されます。

次の行では、以前の変数 f も、新しいボックスを作成する b.Biz 呼び出しによって入力されましたが、その (f) の参照を保持し、その Foobar を 123 に設定したため、残りのボックスにはまだそれが残っています。メソッドの。

于 2017-09-27T12:47:17.223 に答える
0

そこで、この動作を自分でテストすることにしました。「結果」を出しますが、なぜこのようなことが起こるのか説明できません。これがどのように機能するかについてより多くの知識を持っている人が来て、より完全な答えを教えてくれることを願っています

完全なテスト プログラム:

using System;

namespace Test
{
    interface IFoo
    {
        int Foobar{get;set;}
    }
    struct Foo : IFoo 
    {
        public int Foobar{ get; set; }
    }

    class Bar
    {
        Foo tmp;
        //public IFoo Biz{get;set;}; //behavior #1
        public IFoo Biz{ get { return tmp; } set { tmp = (Foo) value; } } //behavior #2

        public Bar()
        {
            Biz=new Foo(){Foobar=0};
        }
    }


    class MainClass
    {
        public static void Main (string[] args)
        {
            var b=new Bar();
            var f=b.Biz;
            f.Foobar=123; 
            Console.WriteLine(f.Foobar); //123 in both
            b.Biz.Foobar=567; /
            Console.WriteLine(b.Biz.Foobar); //567 in behavior 1, 0 in 2
            Console.WriteLine(f.Foobar); //567 in behavior 1, 123 in 2
            b.Biz=new Foo();
            b.Biz.Foobar=5;
            Console.WriteLine(b.Biz.Foobar); //5 in behavior 1, 0 in 2
            Console.WriteLine(f.Foobar); //567 in behavior 1, 123 in 2
        }
    }
}

ご覧のとおり、手動でボックス化/ボックス化解除すると、まったく異なる動作が得られます。私はどちらの振る舞いも完全には理解していません。

于 2013-03-04T19:27:21.087 に答える