我想要一个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>
集合中不允许的任何类型的动物。
所有人都知道支持索引查找的集合也是协变的吗?我不想创建自己的。
答案 0 :(得分:53)
更新:从.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; }
}
答案 1 :(得分:7)
这是我为解决这个问题所写的课程:
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
添加除Dogs之外的任何东西,它会抛出异常,但这符合我的需要。
答案 2 :(得分:4)
从技术上讲,这是阵列集合。它的变化有点破碎,但它符合你的要求。
IList<Animal> animals;
List<Dog> dogs = new List<Dog>();
animals = dogs.ToArray();
当然,如果你试图将Tiger
放在数组中的任何地方,你会在运行时引起轰动。
答案 3 :(得分:4)
从.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; }
}