基本クラス/インターフェースから継承できますが、同じクラス/インターフェースを使用して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
}
}
回避方法はありますか?
これを機能させる方法は、リストを反復処理して要素をキャストすることです。これはConvertAllを使用して実行できます。
List<A> listOfA = new List<C>().ConvertAll(x => (A)x);
Linqを使用することもできます。
List<A> listOfA = new List<C>().Cast<A>().ToList();
まず、A、B、Cなどのわかりにくいクラス名の使用をやめます。動物、哺乳類、キリン、または食べ物、果物、オレンジなど、関係が明確なものを使用します。
あなたの質問は、「キリンを動物のタイプの変数に割り当てることができないのはなぜですか?キリンを動物のタイプの変数に割り当てることができるので、なぜですか?」です。
答えは次のとおりです。その後、何がうまくいかないのでしょうか?
さて、あなたは動物のリストにタイガーを追加することができます。動物のリストを保持する変数にキリンのリストを入れることができるとします。次に、そのリストにトラを追加しようとします。何が起こるのですか?キリンのリストにトラを入れたいですか?クラッシュしますか?または、そもそも割り当てを違法にすることで、コンパイラーをクラッシュから保護しますか?
後者を選択します。
この種の変換は、「共変」変換と呼ばれます。 C#4では、インターフェイスとデリゲートで共変変換を行うことができます変換が常に安全であることがわかっている場合。詳細については、共分散と反分散に関するブログ記事を参照してください。 (今週の月曜日と木曜日の両方で、このトピックに関する新しいトピックがあります。)
エリックの素晴らしい説明を引用する
何が起こるのですか?キリンのリストにトラを入れたいですか?クラッシュしますか?または、そもそも割り当てを違法にすることで、コンパイラーをクラッシュから保護しますか?後者を選択します。
しかし、コンパイルエラーではなくランタイムクラッシュを選択する場合はどうでしょうか。通常は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);
}
}
なぜ機能しないのかについては、 共分散と反分散 を理解しておくと役立ちます。
このが機能しない理由を示すために、提供したコードの変更を以下に示します。
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>());
}
代わりにIEnumerable
を使用すると、動作します(少なくともC#4.0では、以前のバージョンを試したことはありません)。これは単なるキャストであり、もちろんリストのままです。
の代わりに -
List<A> listOfA = new List<C>(); // compiler Error
質問の元のコードでは、次を使用します-
IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
読み取り専用リストにのみキャストできます。例えば:
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にはオブジェクト要素が含まれることを忘れないでください。これは不可能であり、不可能です。
個人的には、クラスの拡張機能を備えたライブラリを作成したい
public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
where TFrom : class
where TTo : class
{
return fromlist.ConvertAll(x => x as TTo);
}
System.Runtime.CompilerServices.Unsafe
NuGetパッケージを使用して、同じ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
がtools
と同じ変数を参照するため、hammers
例外がスローされます。
これは、BigJimの素晴らしい answer の拡張です。
私の場合、NodeBase
辞書を持つChildren
クラスがあり、一般的に子供からO(1)ルックアップを行う方法が必要でした。 Children
のgetterでプライベート辞書フィールドを返そうとしていたので、明らかに高価なコピー/反復を避けたいと思いました。したがって、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<>
または.ConvertAll<>
の使用を禁止する問題である場合)は次のとおりです。
「本当にコレクション全体をキャストする必要がありますか、それともタスクを実行するために必要な特別な知識を保持する抽象メソッドを使用して、コレクションに直接アクセスするのを避けることができますか?」
場合によっては、最も簡単な解決策が最善であることがあります。
C#はそのタイプを許可しないため 継承 変換 現時点 。