协方差和列表
本文关键字:列表 方差 | 更新日期: 2023-09-27 17:54:01
我想要一个协变集合,其项目可以通过索引检索。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>
从。net 4.0开始是协变的。
List<T>
和ReadOnlyCollection<T>
(通过List<T>.AsReadOnly()
)实现了这两个。
只有当它只有一个get
索引器时,它才能协变,即
public T this[int index] { get; }
但是所有的主集合都有{get;set;}
,这使得这很尴尬。我不知道这里有什么足够的,但是你可以包装,即写一个扩展方法:
var covariant = list.AsCovariant();
这是一个包装围绕IList<T>
,只暴露IEnumerable<T>
和get
索引器…?应该只需要几分钟的工作…
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
中添加狗之外的任何东西,它将抛出异常,但这满足了我的需求。
技术上讲,这里有数组集合。它在变化中有点破碎,但它会按你的要求做。
IList<Animal> animals;
List<Dog> dogs = new List<Dog>();
animals = dogs.ToArray();
当然,如果你试图在数组的任何地方放一个Tiger
,你会在运行时爆炸,相当引人注目。
在。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; }
}