List <DerivedClass>をList <BaseClass>に変換します


179

基本クラス/インターフェースから継承できますが、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
    }
}

回避策はありますか?


回答:


230

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

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

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

List<A> listOfA = new List<C>().Cast<A>().ToList();

2
別のオプション:List <A> listOfA = listOfC.ConvertAll(x =>(A)x);
ahaliav fox 14

6
どちらが速いですか?ConvertAllまたはCast
Martin Braun 14

24
これにより、リストのコピーが作成されます。新しいリストで何かを追加または削除した場合、これは元のリストには反映されません。次に、既存のオブジェクトを使用して新しいリストを作成するため、パフォーマンスとメモリが大幅に低下します。これらの問題のない解決策については、以下の私の回答を参照してください。
Bigjim

modiXへの回答:ConvertAllはList <T>のメソッドであるため、汎用リストでのみ機能します。IEnumerableまたはIEnumerable <T>では機能しません。ConvertAllは、単に変換するだけでなく、ConvertAll(インチ=>インチ* 25.4)などのカスタム変換も実行できます。Cast <A>はLINQ拡張メソッドであるため、すべてのIEnumerable <T>で機能し(一般的なIEnumerableでも機能します)、ほとんどのLINQと同様に、遅延実行を使用します。つまり、変換するアイテムの数だけ取得しました。ここではそれについての詳細を読む:codeblog.jonskeet.uk/2011/01/13/...
エドワード

172

まず、A、B、Cなどの理解できないクラス名の使用をやめます。動物、哺乳類、キリン、または食品、果物、オレンジなど、関係が明確なものを使用します。

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

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

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

後者を選択します。

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


3
非ジェネリックIListを実装するIList <T>またはICollection <T>について安全ではないものはありますが、非ジェネリックIlist / ICollectionを実装すると、IsReadOnlyに対してTrueを返し、それを変更するメソッドまたはプロパティに対してNotSupportedExceptionをスローしますか?
スーパーキャット'28

33
この回答にはかなり許容できる推論が含まれていますが、実際には「真実」ではありません。簡単な答えは、C#はこれをサポートしていないということです。キリンやトラを含む動物のリストを作成するという考えは完全に有効です。唯一の問題は、より高いレベルのクラスにアクセスするときに発生します。実際には、これは親クラスをパラメーターとして関数に渡し、それを別の兄弟クラスにキャストしようとすることと同じです。キャストの実装には技術的な問題があるかもしれませんが、上記の説明は、それが悪い考えになる理由を提供していません。
Paul Coldrey、2016年

2
@EricLippertなぜのIEnumerable代わりにを使用してこの変換を実行できるのListですか?つまりList<Animal> listAnimals = listGiraffes as List<Animal>;、不可能ですが、IEnumerable<Animal> eAnimals = listGiraffes as IEnumerable<Animal>機能します。
LINQ

3
@jbueno:私の回答の最後の段落を読んでください。そこでの変換は安全であることが知られています。どうして?一連のキリンを一連の動物にしてから、虎を一連の動物に入れることは不可能だからです。IEnumerable<T>IEnumerator<T>共分散に対して安全であるとマークされており、コンパイラはそれを確認しました。
エリックリッペルト2017年

60

エリックのすばらしい説明を引用する

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

しかし、コンパイルエラーの代わりにランタイムクラッシュを選択する場合はどうでしょうか。通常は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);
    }
}

1
MVCビューモデルとして使用しただけで、汎用のかみそり部分ビューが得られました。素晴らしいアイデア!私はこれを読んでとても幸せです。
Stefan Cebulak 2016年

5
私はクラス宣言で「where TTo:TFrom」のみを追加するので、コンパイラは誤った使用に対して警告できます。無関係な型のCastedListを作成することはとにかく無意味であり、「CastedList <TBase、TDerived>」を作成しても意味がありません。通常のTBaseオブジェクトを追加することはできず、元のリストから取得したTDerivedはすでに次のように使用できます。 TBase。
Wolfzoon、2016年

2
@PaulColdrey Alas、6年の頭のスタートは非難することです。
Wolfzoon、2016年

@Wolfzoon:標準の.Cast <T>()にもこの制限はありません。動作を.Castと同じにしたかったので、AnimalsのリストをTigerのリストにキャストでき、たとえばキリンが含まれている場合は例外があります。Castの場合と同じように
Bigjim 2017

こんにちは@Bigjim、ラッパークラスを使用して既存のキリンの配列を動物の配列(基本クラス)にキャストする方法を理解できません。すべてのヒントはappriciatedされます:)
デニスヴィテス

28

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

の代わりに -

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

質問の元のコードでは、次を使用します-

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


その場合にIEnumerableを正確に使用するにはどうすればよいですか?
Vladius

1
List<A> listOfA = new List<C>(); // compiler Error質問の元のコードの代わりに、IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
PhistucK

IEnumerable <BaseClass>をパラメーターとして取るメソッドにより、継承されたクラスをリストとして渡すことができます。したがって、実行することはほとんどありませんが、パラメータのタイプを変更します。
beauXjames

27

それが機能しない理由に関しては、共分散と反分散を理解することが役立つ場合があります。

なぜこれが機能しないのかを示すために、ここにあなたが提供したコードへの変更があります:

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>());
}

「これでうまくいくだろうか?」-はい、いいえ。'C'型としてFirstItemにアクセスしたときにコードが実際に無効な変換を行っているため、コードを記述できず、コンパイル時にコードが失敗する理由はわかりません。サポートされているC#を爆破する多くの類似した方法があります。IFあなたが実際に正当な理由のために、この機能を実現したい(そして多くがあります)、その後bigjimのは、以下の答え素晴らしいです。
Paul Coldrey、2016年

16

キャストできるのは読み取り専用リストのみです。例えば:

IEnumerable<A> enumOfA = new List<C>();//This works
IReadOnlyCollection<A> ro_colOfA = new List<C>();//This works
IReadOnlyList<A> ro_listOfA = new List<C>();//This works

また、要素の保存をサポートするリストに対してはできません。理由は次のとおりです。

List<string> listString=new List<string>();
List<object> listObject=(List<object>)listString;//Assume that this is possible
listObject.Add(new object());

今何?listObjectとlistStringは実際には同じリストであることを思い出してください。そのため、listStringにはオブジェクト要素があります。これは不可能であり、不可能です。


1
これは私にとって最も理解しやすい答えでした。特に、IReadOnlyListと呼ばれるものがあることについて言及しているため、これは要素が追加されないことを保証するため機能します。
bendtherules 2017年

IReadOnlyDictionary <TKey、TValue>に対して同様のことを行うことができないのは残念です。何故ですか?
Alastair Maw 2017年

これは素晴らしい提案です。便宜上、どこでもList <>を使用していますが、ほとんどの場合、読み取り専用にしたいです。これにより、このキャストを許可し、readonlyを指定する場所を改善することで、コードを2つの方法で改善できるので、すばらしい提案です。
リック・ラブ

1

個人的には、クラスの拡張機能を備えたライブラリを作成したい

public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
  where TFrom : class 
  where TTo : class
{
  return fromlist.ConvertAll(x => x as TTo);
}

0

C#はそのタイプを許可しないため 継承現時点での変換。


6
まず、これは相続ではなく、交換可能性の問題です。第2に、ジェネリック型の共分散はクラス型では機能せず、インターフェイス型とデリゲート型でのみ機能します。
Eric Lippert、

5
まあ、私はあなたとほとんど議論することはできません。
正午シルク

0

これはBigJimの素晴らしい答えの拡張版です。

私の場合NodeBaseChildren辞書を持つクラスがあり、子からO(1)ルックアップを一般的に行う方法が必要でした。私はのゲッターでプライベートディクショナリフィールドを返そうとしていたChildrenので、明らかに、高価なコピー/反復を避けたかったのです。したがって、Bigjimのコードを使用しDictionary<whatever specific type>て、ジェネリックにキャストしましたDictionary<NodeBase>

// Abstract parent class
public abstract class NodeBase
{
    public abstract IDictionary<string, NodeBase> Children { get; }
    ...
}

// Implementing child class
public class RealNode : NodeBase
{
    private Dictionary<string, RealNode> containedNodes;

    public override IDictionary<string, NodeBase> Children
    {
        // Using a modification of Bigjim's code to cast the Dictionary:
        return new IDictionary<string, NodeBase>().CastDictionary<string, RealNode, NodeBase>();
    }
    ...
}

これはうまくいきました。しかし、結局、無関係な制限に遭遇しFindChild()、代わりに検索を行う基本クラスで抽象メソッドを作成することになりました。結局、これによりキャストされた辞書の必要性が最初からなくなりました。(IEnumerable目的に合わせてシンプルに置き換えることができました。)

したがって、あなたが尋ねることがある質問(特に、パフォーマンスが.Cast<>orの使用を禁止する問題である場合.ConvertAll<>)は次のとおりです。

「本当にコレクション全体をキャストする必要がありますか、それとも抽象メソッドを使用して、タスクを実行するために必要な特別な知識を保持して、コレクションに直接アクセスしないようにできますか?」

時には、最も簡単な解決策が最善です。


0

System.Runtime.CompilerServices.UnsafeNuGetパッケージを使用して、同じものへの参照を作成することもできますList

using System.Runtime.CompilerServices;
...
class Tool { }
class Hammer : Tool { }
...
var hammers = new List<Hammer>();
...
var tools = Unsafe.As<List<Tool>>(hammers);

上記のサンプルHammerでは、tools変数を使用してリスト内の既存のインスタンスにアクセスできます。Toolリストにインスタンスを追加すると、と同じ変数を参照するArrayTypeMismatchExceptionため、例外がスローさtoolshammersます。


0

このスレッド全体を読んだので、矛盾しているように見えるものを指摘したいだけです。

コンパイラーは、リストを使用した割り当てを防止します。

List<Tiger> myTigersList = new List<Tiger>() { new Tiger(), new Tiger(), new Tiger() };
List<Animal> myAnimalsList = myTigersList;    // Compiler error

しかし、コンパイラは配列で完全にうまくいきます:

Tiger[] myTigersArray = new Tiger[3] { new Tiger(), new Tiger(), new Tiger() };
Animal[] myAnimalsArray = myTigersArray;    // No problem

割り当てが安全である知られているかどうかについての議論はここで崩れます。配列で行った割り当ては安全ではありません。それを証明するために、私がこれをフォローアップした場合:

myAnimalsArray[1] = new Giraffe();

私が得るランタイム例外「ArrayTypeMismatchExceptionを」。これをどのように説明しますか?コンパイラが本当に私が愚かなことをするのを防ぎたいなら、それは私が配列の割り当てをすることを防ぐべきでした。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.