インデックスでアイテムを取得できる共分散コレクションが欲しいのですが。 IEnumerableは、共変であると私が知っている唯一の.netコレクションですが、このインデックスはサポートされていません。
具体的には、これを実行したいと思います。
List<Dog> dogs = new List<Dog>();
IEnumerable<Animal> animals = dogs;
IList<Animal> animalList = dogs; // This line does not compile
今、私はこれがなぜ問題であるかを知っています。 Listは、Addメソッドを持つICollection
を実装します。動物のIList
にキャストすることで、後続のコードで「実際の」List<Dog>
コレクションで許可されていないあらゆる種類の動物を追加できるようになります。
では、共変でもあるインデックスルックアップをサポートするコレクションを知っている人はいますか?自分で作りたくない。
更新:.NET 4.5以降、 _IReadOnlyList<out T>
_ と _IReadOnlyCollection<out T>
_ があり、どちらも共変です。後者は基本的に_IEnumerable<out T>
_とCount
です。前者は_T this[int index] {get;}
_を追加します。 _IEnumerable<out T>
_は.NET4.0以降は共変であることに注意してください。
_List<T>
_と _ReadOnlyCollection<T>
_ (via List<T>.AsReadOnly()
)の両方がこれらの両方を実装します。
get
インデクサーしかない場合にのみ、共変することができます。
_public T this[int index] { get; }
_
しかし、すべてのメインコレクションには_{get;set;}
_があり、それが厄介です。私はそこで十分なものは何も知りませんが、それをwrapすることができます、つまり拡張メソッドを書くことができます:
_var covariant = list.AsCovariant();
_
これは、_IList<T>
_とget
インデクサーのみを公開する_IEnumerable<T>
_のラッパーです...?ほんの数分の作業である必要があります...
_public static class Covariance
{
public static IIndexedEnumerable<T> AsCovariant<T>(this IList<T> tail)
{
return new CovariantList<T>(tail);
}
private class CovariantList<T> : IIndexedEnumerable<T>
{
private readonly IList<T> tail;
public CovariantList(IList<T> tail)
{
this.tail = tail;
}
public T this[int index] { get { return tail[index]; } }
public IEnumerator<T> GetEnumerator() { return tail.GetEnumerator();}
IEnumerator IEnumerable.GetEnumerator() { return tail.GetEnumerator(); }
public int Count { get { return tail.Count; } }
}
}
public interface IIndexedEnumerable<out T> : IEnumerable<T>
{
T this[int index] { get; }
int Count { get; }
}
_
このシナリオに対処するために私が書いたクラスは次のとおりです。
public class CovariantIListAdapter<TBase, TDerived> : IList<TBase>
where TDerived : TBase
{
private IList<TDerived> source;
public CovariantIListAdapter(IList<TDerived> source)
{
this.source = source;
}
public IEnumerator<TBase> GetEnumerator()
{
foreach (var item in source)
yield return item;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(TBase item)
{
source.Add((TDerived) item);
}
public void Clear()
{
source.Clear();
}
public bool Contains(TBase item)
{
return source.Contains((TDerived) item);
}
public void CopyTo(TBase[] array, int arrayIndex)
{
foreach (var item in source)
array[arrayIndex++] = item;
}
public bool Remove(TBase item)
{
return source.Remove((TDerived) item);
}
public int Count
{
get { return source.Count; }
}
public bool IsReadOnly
{
get { return source.IsReadOnly; }
}
public int IndexOf(TBase item)
{
return source.IndexOf((TDerived) item);
}
public void Insert(int index, TBase item)
{
source.Insert(index, (TDerived) item);
}
public void RemoveAt(int index)
{
source.RemoveAt(index);
}
public TBase this[int index]
{
get { return source[index]; }
set { source[index] = (TDerived) value; }
}
}
これで、次のようなコードを記述できます。
List<Dog> dogs = new List<Dog>();
dogs.Add(new Dog { Name = "Spot", MaximumBarkDecibals = 110 });
IEnumerable<Animal> animals = dogs;
IList<Animal> animalList = new CovariantIListAdapter<Animal, Dog>(dogs);
animalList.Add(new Dog { Name = "Fluffy", MaximumBarkDecibals = 120 });
実際にはまだ1つのリストしかないため、変更は両方のリストに表示されます。アダプタクラスは呼び出しを渡すだけで、必要に応じてアイテムをキャストして、目的のIList<TBase>
インターフェイスを実現します。
明らかに、犬以外のものをanimalList
に追加すると、例外がスローされますが、これは私のニーズを満たしていました。
技術的には、配列コレクションがあります。分散が壊れているようですが、要求どおりに機能します。
IList<Animal> animals;
List<Dog> dogs = new List<Dog>();
animals = dogs.ToArray();
もちろん、配列のどこかにTiger
を入れようとすると、実行時にかなり見事に爆発します。
.NET Framework 4.5の時点で、共変であるインターフェイスIReadOnlyListが存在します。これは、MarkGravellの回答のIIndexedEnumerableインターフェイスと本質的に同じです。
IReadOnlyListは次のように実装されます。
/// <summary>
/// Represents a read-only collection of elements that can be accessed by index.
/// </summary>
/// <typeparam name="T">The type of elements in the read-only list. This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics.</typeparam>
public interface IReadOnlyList<out T> : IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable
{
/// <summary>
/// Gets the element at the specified index in the read-only list.
/// </summary>
///
/// <returns>
/// The element at the specified index in the read-only list.
/// </returns>
/// <param name="index">The zero-based index of the element to get. </param>
T this[int index] { get; }
}