2012-03-13 51 views
27

最後の行が許可されないのはなぜですか?IEnumerable <struct>をIEnumerable <object>としてキャストできないのはなぜですか?

これは、doubleはオブジェクトから派生しない値型なので、共分散は機能しませんか?それは、この作品を作るための方法がないことを

を意味しています:

public interface IMyInterface<out T> 
{ 
    string Method(); 
} 

public class MyClass<U> : IMyInterface<U> 
{ 
    public string Method() 
    { 
     return "test"; 
    } 
} 

public class Test 
{ 
    public static object test2() 
    { 
     IMyInterface<double> a = new MyClass<double>(); 
     IMyInterface<object> b = a; // Invalid cast! 
     return b.Method(); 
    } 
} 

そして、私はそれを行うためにIMyInterface<T>.Cast<U>()私の非常に自身を記述する必要がありますか。

+0

[なぜ共分散と反差異が値型をサポートしないのか](http://stackoverflow.com/questions/12454794/why-covariance-and-contravariance-do-not-support-value-type) – nawfal

答えて

44

なぜ最後の行が許可されないのですか?

doubleは値型でオブジェクトは参照型なので、共分散は、両方の型が参照型である場合にのみ機能します。

doubleはオブジェクトから派生しない値型なので、共分散は機能しませんか?

いいえダブルはオブジェクトから派生します。すべての値の型はオブジェクトから派生します。

今あなたが求めている必要があります質問:

はなぜ共分散はIEnumerable<object>IEnumerable<double>を変換するために動作しませんか?

誰がボクシングを行うのですか?ダブルからオブジェクトへの変換はボックスダブルでなければなりません。 IEnumerator<object>.Currentへの呼び出しが、実際にはIEnumerator<double>.Currentの実装への呼び出しであるとします。呼び出し元は、オブジェクトが返されることを期待します。着呼者は二重を返す。 IEnumerator<double>.Currentによって返されたdoubleをボックス化されたdoubleに変換するボクシング命令を実行するコードはどこですか?

ここではですが、これがこの変換が違法な理由です。 Currentを呼び出すと、評価スタックに8バイトの倍精度値が設定され、コンシューマは評価スタックのボックス化された倍精度浮動小数点型への4バイト参照を期待します。そのため、消費者はひどくクラッシュし、スタックが整列しておらず、無効なメモリを参照しています。

あなたはにボックスがを実行するコードが必要な場合、それはは、いくつかの点でを書かなければならず、そしてあなたはそれを書くことを得る人です。

IEnumerable<object> objects2 = doubleenumerable.Cast<object>(); 

今、あなたが参照に、ダブル8バイトからダブルに変換ボクシングの命令が含まれているヘルパーメソッドを呼び出します。最も簡単な方法は、Cast<T>拡張メソッドを使用することです。

UPDATE:私が質問したことを覚えていることを注釈者が指摘しています。つまり、元の質問の解決策として難しい問題を解決する仕組みが存在することを前提に質問に答えました。 Cast<T>の実装は、ボックスにするかどうかを知るという問題をどのように解決することができますか?

このスケッチのように機能します。

public static IEnumerable<T> Cast<T>(this IEnumerable sequence) 
{ 
    if (sequence == null) throw ... 
    if (sequence is IEnumerable<T>) 
     return sequence as IEnumerable<T>; 
    return ReallyCast<T>(sequence); 
} 

private static IEnumerable<T> ReallyCast<T>(IEnumerable sequence) 
{ 
    foreach(object item in sequence) 
     yield return (T)item; 
} 

オブジェクトからTへのキャストがアンボクシング変換、または基準変換がランタイムに延期されたか否かを決定するための責任:パラメータタイプはないジェネリックであることに留意されたいです。ジッタは、Tが参照型であるか値型であるかを知る。 99%の時間はもちろんリファレンスタイプになります。

+6

オフ私はあなたがつぶやきました。 – Joe

+1

1+通常より速く転記します。私は、「消費者が誤整列されたスタックと無効なメモリへの参照」でひどく墜落して死にそうになるのが好きです。 –

+17

@JoeTuskan:私は、120文字に合っていて、一般的な関心事であると言うことがあるときは、そうします。長い待つことを期待してください。 –

4

何が許可されているのか、また許可されていないのか、そしてそのように振舞うのかを理解するには、何が起こっているのかを理解すると役に立ちます。すべての値型に対して、すべてのオブジェクトと同様に、対応するタイプのクラスオブジェクトが存在し、System.Objectから継承します。各クラスオブジェクトには、データとともにその型を識別する32ビットワード(x86)または64ビットロングワード(x64)が含まれています。ただし、値型の格納場所では、そのようなクラスオブジェクトや参照が保持されず、型データの単語も格納されません。その代わりに、各プリミティブ値タイプの位置は単に値を表すのに必要なビットを保持し、各struct-value-type格納場所はそのタイプのすべてのパブリックフィールドとプライベートフィールドの内容を保持します。

Doubleの変数をタイプObjectのいずれかにコピーすると、Doubleに関連付けられたクラスオブジェクトタイプの新しいインスタンスが作成され、元のすべてのバイトがその新しいクラスオブジェクトにコピーされます。 boxed-DoubleクラスタイプはDouble値タイプと同じ名前ですが、一般的に同じコンテキストでは使用できないため、あいまいさにはなりません。値型の格納場所は、格納された型情報のない生のビットまたはフィールドの組み合わせを保持します。そのような記憶場所を別の場所にコピーすると、すべてのバイトがコピーされ、結果としてすべてのパブリックフィールドとプライベートフィールドがコピーされます。対照的に、値型から派生した型のヒープオブジェクトはヒープオブジェクトであり、ヒープオブジェクトのように動作します。 C#は値型格納場所の内容をあたかもObjectの派生品であるかのように見なしますが、そのような格納場所の内容は実際には型システム外のバイトの集合に過ぎません。バイトは何を表すのかを知っているコードでしかアクセスできないため、そのような情報を格納場所自体に格納する必要はありません。構造体にGetTypeを呼び出すときのボクシングの必要性は、しばしばGetTypeで非シャドウ非仮想関数であると説明されていますが、本当の必要性は、値型格納場所の内容(場所自体)は型情報を持たない。

関連する問題