3

私はジェネリッククラスを持っていますNamedValue<TValue>:

public class NamedValue<TValue>
{
    public string Name { get; set; }
    public TValue Value { get; set; }
}

を含む 2 番目のジェネリック クラスNamedValueSource<TValue>がありますList<NamedValue<TValue>>

public class NamedValueSource<TValue>
{
    public List<NamedValue<TValue>> NamedValues { get; set; }

    public NamedValueSource()
    {
        NamedValues = GetNamedValues().Cast<NamedValue<TValue>>().ToList();
    }

    private IEnumerable<NamedValue<bool>> GetNamedValues()
    {
        var yesNamedValue = new NamedValue<bool> { Name = "Yes", Value = true };
        var noNamedValue = new NamedValue<bool> { Name = "Yes", Value = false };
        yield return yesNamedValue;
        yield return noNamedValue;
    }
}

次のテスト コードは完全に機能します (アサーションはパスします)。

public class Tester
{
    public Tester()
    {
        var source = new NamedValueSource<bool>();
        Debug.Assert(source.NamedValues[0].Name == "Yes");
    }
}

さて、ここが興味深い部分です。内GetNamedValues()でキャストを実行しようとすると、コードはコンパイルされません。

public class NamedValueSourceFail<TValue>
{
    public List<NamedValue<TValue>> NamedValues { get; set; }

    public NamedValueSourceFail()
    {
        NamedValues = GetNamedValues().ToList();
    }

    private IEnumerable<NamedValue<TValue>> GetNamedValues()
    {
        var yesNamedValue = new NamedValue<bool> { Name = "Yes", Value = true };
        var noNamedValue = new NamedValue<bool> { Name = "Yes", Value = false };
        yield return (NamedValue<TValue>)yesNamedValue; // ERROR: cannot convert type
        yield return (NamedValue<TValue>)noNamedValue; // ERROR: cannot convert type
    }
}

エラーが発生しているNamedValueSource<TValue>間にコンパイルするのはなぜですか? NamedValueSourceFail<TValue>具体的には、Linq を使用してキャストを実行できるのに、古き良き括弧を使用できないのはなぜですか?

編集

受け入れられた回答のコメント スレッドから完全に明確でない場合は、objectまず に変換する必要があり、次に にキャストできNamedValue<TValue>ます。これはおそらく、LinqCastメソッドが舞台裏でどのように機能するかです。

4

2 に答える 2

12

更新: この質問は、2012 年 7 月 10 日の私のブログの主題でした。素晴らしい質問をありがとう!


複雑なプログラムを大幅に単純化しましょう。

public static class X
{
    public static V Cast<V>(object o) { return (V)o; }
}

class C<T> {}
class D<U>
{
    public C<U> value;
    public D()
    {
        this.value = X.Cast<C<U>>(new C<bool>());
    }
}

単純化された 2 番目のバージョン:

class C<T> {}
class D<U>
{
    public C<U> value;
    public D()
    {
        this.value = (C<U>)(new C<bool>());
    }
}

では、いくつか質問をしましょう。

コンパイル時に 2 番目のプログラムが失敗するのはなぜですか?

任意の からC<bool>への変換がないためです。コンパイラは、これが成功する可能性がある唯一の方法は常にbool であることを知っているため、このプログラムはほぼ間違いなく間違っています! コンパイラは、それが bool 以外のものになると想定しています。C<U>UUU

では、なぜ最初のプログラムはコンパイル時に成功するのでしょうか?

コンパイラは、"X.Cast" という名前のメソッドが、エラー検出のためにキャスト演算子のように扱われるべきであることを認識していません! コンパイラに関する限り、このCastメソッドは、オブジェクトを受け取り、Vfor に指定された型パラメータに関係なく を返すメソッドですV。D の ctor の本体をコンパイルするとき、コンパイラは、おそらく最初からこのプログラムにも含まれていない何らかのメソッドがキャストを実行しようとしていることをまったく知りませんU。ブール。

コンパイラには、最初のバージョンをエラーとして処理する根拠がありません。プログラムが間違っていることを確認するには、実行時まで待つ必要があります。

次に、プログラムの3 番目のバージョンを作成しましょう。

class C<T> {}
class D<U>
{
    public C<U> value;
    public D()
    {
        this.value = (C<U>)(object)(new C<bool>());
    }
}

これはコンパイル時に成功するので、聞いてみましょう:

これがコンパイル時に成功するのはなぜですか?

最初のものがコンパイル時に成功するのとまったく同じ理由で。キャストを挿入したとき、新しく構築された をオブジェクトとして扱いたいと効果的に述べたC<bool>ので、この式の残りの分析では、その式は object 型であると見なされ、より具体的な type ではないと見なされますC<bool>

C<U>では、この場合、オブジェクトをキャストすることが合法なのはなぜですか? それともV、最初のケースでは?

Vオブジェクトの型、オブジェクトの基本型、またはオブジェクトによって実装されたインターフェイスである可能性があるため、オブジェクトをキャストすることは合法ですV。コンパイラは、成功する可能性のある方法がたくさんあると判断して変換を許可します。

基本的に、変換できるものにキャストobjectすることは合法です。にキャストできるポインター型がないため、たとえば、ポインター型にキャストすることはできません。しかし、他のすべては公正なゲームです。objectobjectobject

object最初にキャストすることにより、コンパイラの範囲から情報を削除しています。あなたは「これは常にC<bool>エラー検出の目的であることがわかっているという事実を無視してください。

于 2012-04-06T20:49:08.200 に答える
8

NamedValue<bool>2 番目の例では、変換しようとしていますNamedValue<TValue>-- これは機能しません。これは、変換が任意の型引数に対して有効である必要があるためです。またはまたはに変換NamedValue<bool>することはできません。NamedValue<int>NamedValue<string>NamedValue<AnythingElseOtherThanBool>

1 つの解決策は、メソッドとNamedValueSource<TValue>同様に抽象化してから、テストで使用するクラス クラスを作成することです。GetNamedValues()BooleanNamedValueSource : NamedValueSource<bool>

linq の場合、キャストはコンパイラによって行われません。キャストは、既にコンパイルされているメソッドで発生します。IEnumerable<bool>コンパイラが知っているのは、 を受け取って返すメソッドを呼び出しているということだけですIEnumerable<TValue>。その変換の詳細は、コンパイラにはまったく見えません。

于 2012-04-06T20:27:32.507 に答える