协变和IList
我想要一个Covariant集合,其项目可以通过索引检索。 IEnumerable是我知道的唯一一个.net集合,它是Covariant,但是它没有这个索引支持。
具体来说,我想这样做:
List<Dog> dogs = new List<Dog>(); IEnumerable<Animal> animals = dogs; IList<Animal> animalList = dogs; // This line does not compile
现在,我知道为什么这是一个问题。 列表实现具有Add方法的ICollection
。 通过向动物的IList
投射,它将允许随后的代码添加“真正的” List<Dog>
集合中不允许的任何types的动物。
那么是否有人知道一个支持索引查找的集合也是协变的? 我想不创造我自己的。
更新:从.NET 4.5开始, IReadOnlyList<out T>
和IReadOnlyCollection<out T>
都是协变的; 后者基本上是IEnumerable<out T>
加Count
; 前者添加T this[int index] {get;}
。 还应该注意IEnumerable<out T>
是从.NET 4.0开始的协变。
List<T>
和ReadOnlyCollection<T>
(通过List<T>.AsReadOnly()
)实现这两个。
它只能是协变的,如果它只有一个get
索引器,即
public T this[int index] { get; }
但是所有主要的集合都有{get;set;}
,这使得尴尬。 我不知道有足够的,但你可以包装它,即写一个扩展方法:
var covariant = list.AsCovariant();
这是一个只包含IEnumerable<T>
和get
索引器的IList<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 });
这两个列表中的更改都是可见的,因为仍然只有一个列表。 适配器类只是传递调用,根据需要投射项目以实现所需的IList<TBase>
接口。
很明显,如果你添加任何东西,但狗给animalList
,它会抛出一个exception,但这符合我的需求。
从.NET Framework 4.5开始,存在一个协变的接口IReadOnlyList。 它与Mark Gravell的答案中的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; } }
从技术上讲,这是数组收集。 它的差异有点不一样,但它是按照你的要求做的。
IList<Animal> animals; List<Dog> dogs = new List<Dog>(); animals = dogs.ToArray();
当然,如果你试图在arrays的任何地方放置一个Tiger
,那么你在运行时会相当壮观。