218

List<> 基本クラス/インターフェースから継承できますが、同じクラス/インターフェースを使用し て宣言できないのはなぜですか?

interface A
{ }

class B : A
{ }

class C : B
{ }

class Test
{
    static void Main(string[] args)
    {
        A a = new C(); // OK
        List<A> listOfA = new List<C>(); // compiler Error
    }
}

回避策はありますか?

4

12 に答える 12

274

これを機能させる方法は、リストを反復処理して要素をキャストすることです。これは、ConvertAll を使用して実行できます。

List<A> listOfA = new List<C>().ConvertAll(x => (A)x);

Linq を使用することもできます。

List<A> listOfA = new List<C>().Cast<A>().ToList();
于 2009-11-30T00:43:18.467 に答える
181

まず、A、B、C のようなわかりにくいクラス名を使用するのをやめます。動物、哺乳類、キリン、または食品、果物、オレンジなど、関係が明確なものを使用してください。

あなたの質問は、「キリンを動物型の変数に割り当てることができるのに、動物の型リストの変数にキリンのリストを割り当てることができないのはなぜですか?」ということです。

答えは次のとおりです。それでは何がうまくいかないのでしょうか?

さて、トラを動物のリストに追加できます。動物のリストを保持する変数にキリンのリストを入れることができるとします。次に、そのリストにトラを追加しようとします。何が起こるのですか?キリンのリストにトラを含めますか? クラッシュしたいですか?それとも、最初から割り当てを違法にすることで、コンパイラーにクラッシュから保護してもらいたいですか?

私たちは後者を選びます。

この種の変換は「共変」変換と呼ばれます。C# 4 では、変換が常に安全であることがわかっている場合、インターフェイスとデリゲートで共変変換を行うことができます。詳細については、共分散と反分散に関する私のブログ記事を参照してください。(今週の月曜日と木曜日の両方に、このトピックに関する新しいトピックがあります。)

于 2009-11-30T00:45:56.560 に答える
66

エリックの素晴らしい説明を引用すると

何が起こるのですか?キリンのリストにトラを含めますか? クラッシュしたいですか?それとも、最初から割り当てを違法にすることで、コンパイラーにクラッシュから保護してもらいたいですか? 私たちは後者を選びます。

しかし、コンパイル エラーの代わりにランタイム クラッシュを選択したい場合はどうでしょうか。通常は Cast<> または ConvertAll<> を使用しますが、2 つの問題があります。リストのコピーが作成されます。新しいリストに何かを追加または削除しても、元のリストには反映されません。次に、既存のオブジェクトで新しいリストを作成するため、パフォーマンスとメモリが大幅に低下します。

私は同じ問題を抱えていたので、まったく新しいリストを作成せずに汎用リストをキャストできるラッパー クラスを作成しました。

元の質問では、次を使用できます。

class Test
{
    static void Main(string[] args)
    {
        A a = new C(); // OK
        IList<A> listOfA = new List<C>().CastList<C,A>(); // now ok!
    }
}

そしてここにラッパークラス(+使いやすい拡張メソッドCastList)

public class CastedList<TTo, TFrom> : IList<TTo>
{
    public IList<TFrom> BaseList;

    public CastedList(IList<TFrom> baseList)
    {
        BaseList = baseList;
    }

    // IEnumerable
    IEnumerator IEnumerable.GetEnumerator() { return BaseList.GetEnumerator(); }

    // IEnumerable<>
    public IEnumerator<TTo> GetEnumerator() { return new CastedEnumerator<TTo, TFrom>(BaseList.GetEnumerator()); }

    // ICollection
    public int Count { get { return BaseList.Count; } }
    public bool IsReadOnly { get { return BaseList.IsReadOnly; } }
    public void Add(TTo item) { BaseList.Add((TFrom)(object)item); }
    public void Clear() { BaseList.Clear(); }
    public bool Contains(TTo item) { return BaseList.Contains((TFrom)(object)item); }
    public void CopyTo(TTo[] array, int arrayIndex) { BaseList.CopyTo((TFrom[])(object)array, arrayIndex); }
    public bool Remove(TTo item) { return BaseList.Remove((TFrom)(object)item); }

    // IList
    public TTo this[int index]
    {
        get { return (TTo)(object)BaseList[index]; }
        set { BaseList[index] = (TFrom)(object)value; }
    }

    public int IndexOf(TTo item) { return BaseList.IndexOf((TFrom)(object)item); }
    public void Insert(int index, TTo item) { BaseList.Insert(index, (TFrom)(object)item); }
    public void RemoveAt(int index) { BaseList.RemoveAt(index); }
}

public class CastedEnumerator<TTo, TFrom> : IEnumerator<TTo>
{
    public IEnumerator<TFrom> BaseEnumerator;

    public CastedEnumerator(IEnumerator<TFrom> baseEnumerator)
    {
        BaseEnumerator = baseEnumerator;
    }

    // IDisposable
    public void Dispose() { BaseEnumerator.Dispose(); }

    // IEnumerator
    object IEnumerator.Current { get { return BaseEnumerator.Current; } }
    public bool MoveNext() { return BaseEnumerator.MoveNext(); }
    public void Reset() { BaseEnumerator.Reset(); }

    // IEnumerator<>
    public TTo Current { get { return (TTo)(object)BaseEnumerator.Current; } }
}

public static class ListExtensions
{
    public static IList<TTo> CastList<TFrom, TTo>(this IList<TFrom> list)
    {
        return new CastedList<TTo, TFrom>(list);
    }
}
于 2015-06-05T09:01:35.447 に答える
34

代わりに使用IEnumerableすると動作します (少なくとも C# 4.0 では、以前のバージョンは試していません)。これは単なるキャストであり、もちろんリストになります。

それ以外の -

List<A> listOfA = new List<C>(); // compiler Error

質問の元のコードでは、使用 -

IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)

于 2015-02-25T14:32:30.550 に答える
31

うまくいかない理由については、共分散と反分散を理解することが役立つかもしれません。

これが機能しない理由を示すために、提供したコードを次のように変更します。

void DoesThisWork()
{
     List<C> DerivedList = new List<C>();
     List<A> BaseList = DerivedList;
     BaseList.Add(new B());

     C FirstItem = DerivedList.First();
}

これは機能するはずですか?リストの最初の項目はタイプ「B」ですが、DerivedList 項目のタイプは C です。

ここで、A を実装する型のリストを操作するジェネリック関数を本当に作成したいだけで、それがどの型であるかは気にしないとします。

void ThisWorks<T>(List<T> GenericList) where T:A
{

}

void Test()
{
     ThisWorks(new List<B>());
     ThisWorks(new List<C>());
}
于 2009-11-30T00:55:20.777 に答える
0

C# ではそのタイプの継承現時点での変換。

于 2009-11-30T00:32:01.023 に答える