3

私はこの質問を見ていましたが、何かを列挙するかなり奇妙な方法は別として、列挙子が構造体であるため、操作に問題がありました。値型であるため、構造体を返すか渡すにはコピーが使用されることを理解しています。

public MyStruct GetThingButActuallyJustCopyOfIt() 
{ 
    return this.myStructField; 
}

また

public void PretendToDoSomething(MyStruct thingy) 
{ 
    thingy.desc = "this doesn't work as expected"; 
}

私の質問は、MyStruct が IMyInterface (IEnumerable など) を実装している場合、これらのタイプのメソッドは期待どおりに機能しますか?

public struct MyStruct : IMyInterface { ... }

//will caller be able to modify the property of the returned IMyInterface?
public IMyInterface ActuallyStruct() { return (IMyInterface)this.myStruct; }

//will the interface you pass in get its value changed?
public void SetInterfaceProp(IMyInterface thingy)
{
    thingy.desc = "the implementing type is a struct";
}
4

3 に答える 3

5

はい、そのコードは機能しますが、説明が必要です。なぜなら、機能しないコードの世界があり、これを知らない限り、それにつまずく可能性が高いからです。

忘れる前に:可変構造体は悪です。では、それはさておき、先に進みましょう。

簡単な例を見てみましょう。LINQPadを使用してこのコードを検証できます。

void Main()
{
    var s = new MyStruct();
    Test(s);
    Debug.WriteLine(s.Description);
}

public void Test(IMyInterface i)
{
    i.Description = "Test";
}

public interface IMyInterface
{
    string Description { get; set; }
}

public struct MyStruct : IMyInterface
{
    public string Description { get; set; }
}

これを実行すると、何が印刷されますか?

ヌル

では、なぜですか?

さて、問題は次の行です。

Test(s);

これは実際には、その構造体をボックス化し、ボックス化されたコピーをメソッドに渡します。そのボックス化されたコピーは正常に変更されていますが、s何も割り当てられていない元の変数は変更されていないため、まだnull.

では、コードの最初の部分を 1 行だけ変更すると、次のようになります。

IMyInterface s = new MyStruct();

これによって結果は変わりますか?

はい、ここでその構造体をボックス化し、ボックス化されたコピーを常に使用するためです。このコンテキストでは、オブジェクトのように動作ます。ボックス化されたコピーを変更し、ボックス化されたコピーの内容を書き出します。

したがって、その構造体をボックス化またはボックス化解除するたびに問題が発生し、別々の生活を送るコピーが得られます。

結論: 可変構造体は悪です。

ここでの使用について 2 つの回答がrefありましたが、これは間違ったツリーを吠えています。を使用するということは、追加する前にref問題を解決したことを意味します。ref

これが例です。

上記のメソッドをパラメーターTestを取るように変更すると、次のようになります。ref

public void Test(ref IMyInterface i)

これで何か変わるでしょうか?

いいえ、このコードは無効になっているためです:

var s = new MyStruct();
Test(ref s);

あなたはこれを得るでしょう:

「UserQuery.Test(ref UserQuery.IMyInterface)」に最適なオーバーロードされたメソッドの一致には、無効な引数がいくつかあります

引数 1: 'ref UserQuery.MyStruct' から 'ref UserQuery.IMyInterface' に変換できません

したがって、コードを次のように変更します。

IMyInterface s = new MyStruct();
Test(ref s);

しかし、ここで私の例に戻ってきました。追加しただけです。refこれは、変更が反映されるために必要ではありません。

したがって、使用refは直交し、さまざまな問題を解決しますが、これは解決しません。

OK、 に関するその他のコメントref

はい、もちろん、構造体を使用して渡すrefと、実際に変更がプログラム全体に流れます。

それはこの質問の内容ではありません。質問はいくつかのコードを投稿し、それが機能するかどうかを尋ねました。この特定のコードのバリアントでは、機能します。でもつまずくのはとても簡単です。そして、質問が構造体とインターフェースに関するものであったことに特に注意してください。インターフェースを省略し、 を使用して構造体を渡すと、ref何が得られるでしょうか? 別の質問です。

追加refしても、この質問も答えも変わりません。

于 2013-07-17T13:59:28.093 に答える
1

CLR 内では、すべての値型の定義で、実際には構造体型とヒープ オブジェクト型の 2 種類が定義されています。構造型からボックス化されたオブジェクト型への拡大変換が存在し、構造型からの縮小変換が存在しObjectます。構造体型は値セマンティクスで動作し、ヒープ オブジェクト型は可変参照セマンティクスで動作します。すべての非自明な構造体型 (つまり、デフォルト以外の状態を持つもの) に関連付けられたヒープ オブジェクト型は常に変更可能であり、構造体定義の何ものもそれらを別の状態にすることはできないことに注意してください。

値型は、インターフェイス型に制約、キャスト、または強制され、参照型にキャストまたは強制されることに注意してください。検討:

void DoSomethingWithDisposable<T,U>(ref T p1, 
     List<int>.Enumerator p2) where T:IDisposable
{
  IDisposable v1a = p1; // Coerced
  Object v1b = p1; // Coerced
  IDisposable v2a = (IDisposable)p2; // Cast
  Object v2b = (Object)p2; // Cast
  p1.Dispose(); // Constrained call
}
void blah( List<string>.Enumerator p1, List<int>.Enumerator p2) // These are value types
{
  DoSomethingWithDisposable(p1,p2); // Constrains p1 to IDisposable
}

ジェネリック型をインターフェイス型に制約しても、値型としての動作には影響しません。ただし、値型をインターフェイスまたは参照型にキャストまたは強制すると、ヒープ オブジェクト型の新しいインスタンスが作成され、それへの参照が返されます。その参照は、参照型のセマンティクスで動作します。

一般的な制約を伴う値型の動作は、非常に役立つ場合があり、そのような有用性は変更インターフェイスを使用する場合にも当てはまりますが、残念ながら、値型が値型のままである必要があること、およびコンパイラが値型のままであることをコンパイラに伝える方法はありません。それ自体が何か他のものに変換されていることに気付いた場合は警告する必要があります。次の 3 つの方法を検討してください。

bool AdvanceIntEnumerator1(IEnumerator<int> it)
  { return it.MoveNext(); }

bool AdvanceIntEnumerator2(ref T it) where T:IEnumerator<int>
  { return it.MoveNext(); }

bool AdvanceIntEnumeratorTwice<T>(ref T it) where T:IEnumerator<int>
  { return it.MoveNext() && AdvanceIntEnumerator1(it); }

コードの最初の部分に type の変数を渡すとList<int>.Enumerator、システムはその状態を新しいヒープ オブジェクトにコピーし、そのオブジェクトを呼び出しMoveNextて破棄します。IEnumerator<int>代わりに、 typeのヒープ オブジェクトへの参照を保持するtype の変数を渡すと、そのインスタンスList<int>.Enumeratorが呼び出さMoveNextれ、呼び出し元のコードは保持します。

コードの 2 番目の部分に type の変数を渡すとList<int>.Enumerator、システムはその変数を呼び出しMoveNext、その状態を変更します。type の変数を渡すと、システムはその変数によって参照されるオブジェクトIEnumerable<T>を呼び出します。MoveNext変数は変更されません (同じインスタンスを指すままです) が、それが指すインスタンスは変更されます。

コードの 3 番目の部分に渡すと、型の変数がList<int>.EnumeratorそのMoveNext変数で呼び出され、その状態が変更されます。それが を返す場合true、システムは既に変更された変数を新しいヒープ オブジェクトにコピーし、それを呼び出しますMoveNext。その後、オブジェクトは破棄されるため、変数は 1 回だけ進められますが、戻り値は 2 回目MoveNextで成功したかどうかを示します。IEnumerator<T>ただし、への参照を保持する型の変数をコードの 3 番目の部分に渡すList<T>.Enumeratorと、そのインスタンスが 2 回進められます。

于 2013-07-17T15:44:10.227 に答える
-1

いいえ、インターフェイスはコントラクトrefです。適切に機能させるには、キーワードを使用する必要があります。

public void SetInterfaceProp(ref IMyInterface thingy)
{
    thingy.desc = "the implementing type is a struct";
}

ここで重要なのは、そのインターフェイス ラップ内にとどまる実際の型です。

より明確にするために:

SetInterfacePropのようにメソッドが定義されたコードであっても

public void SetInterfaceProp(IMyInterface thingy)
{
    thingy.desc = "the implementing type is a struct";
}

動作します:

IMyInterface inter= default(MyStruct); 
SetInterfaceProp(inter);

これはしません:

MyStruct inter = default(MyStruct); 
SetInterfaceProp(inter);

メソッドの呼び出し元が常に を使用するIMyInterfaceことを保証することはできないため、期待される動作を保証するために、この場合、refキーワードを定義して、両方のケースでメソッドが期待どおりに実行されることを保証できます。

于 2013-07-17T13:51:07.660 に答える