C# 协方差与IList
我想要一个协变集合,它的项可以通过索引检索。IEnumerable是我所知道的唯一一个协变的.net集合,但它没有这种索引支持 具体来说,我想这样做:C# 协方差与IList,c#,.net,.net-4.0,covariance,C#,.net,.net 4.0,Covariance,我想要一个协变集合,它的项可以通过索引检索。IEnumerable是我所知道的唯一一个协变的.net集合,但它没有这种索引支持 具体来说,我想这样做: List<Dog> dogs = new List<Dog>(); IEnumerable<Animal> animals = dogs; IList<Animal> animalList = dogs; // This line does not compile List dogs=new L
List<Dog> dogs = new List<Dog>();
IEnumerable<Animal> animals = dogs;
IList<Animal> animalList = dogs; // This line does not compile
List dogs=new List();
i可数动物=狗;
IList animalList=狗;//这一行不编译
现在,我知道为什么这是一个问题。列表实现具有Add方法的ICollection。通过向上转换到动物的IList
,它将允许后续代码添加“real”列表中不允许的任何类型的动物
那么,有人知道支持协变索引查找的集合吗?我不想创建我自己的。更新:从.NET4.5开始,有和都是协变的;后者基本上是IEnumerable
加Count
;前者添加了T这个[int索引]{get;}
。还应注意,IEnumerable
从.NET 4.0开始是协变的
List
和(via)都实现了这两个功能
只有当它只有一个get
索引器,即
public T this[int index] { get; }
但是所有的主要集合都有{get;set;}
,这让它很尴尬。我不知道有哪种方法可以满足要求,但您可以将其包装起来,即编写一个扩展方法:
var covariant = list.AsCovariant();
这是一个围绕IList
的包装器,它只公开IEnumerable
和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; }
}
公共静态类协方差
{
公共静态IIndexedEnumerable变量(此IList尾部)
{
返回新的协方差列表(尾部);
}
私有类协方差列表:IIndexedEnumerable
{
私有只读IList尾部;
公共协方差列表(IList尾部)
{
this.tail=tail;
}
public T this[int index]{get{return tail[index];}
公共IEnumerator GetEnumerator(){return tail.GetEnumerator();}
IEnumerator IEnumerable.GetEnumerator(){return tail.GetEnumerator();}
公共整数计数{get{return tail.Count;}}
}
}
公共接口IIndexedEnumerable:IEnumerable
{
T此[int索引]{get;}
整数计数{get;}
}
从技术上讲,这里有数组集合。它的方差有点小,但它会按你的要求去做
IList<Animal> animals;
List<Dog> dogs = new List<Dog>();
animals = dogs.ToArray();
IList动物;
列表狗=新列表();
动物=狗;
当然,如果您尝试将老虎
放在数组中的任何位置,在运行时都会非常惊人地爆炸。我编写了一个类来解决这个问题:
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; }
}
}
公共类协方差适配器:IList
来源:TBase
{
私人来源;
公共统计数据适配器(IList源)
{
this.source=源;
}
公共IEnumerator GetEnumerator()
{
foreach(源中的var项)
收益回报项目;
}
IEnumerator IEnumerable.GetEnumerator()
{
返回GetEnumerator();
}
公共作废添加(TBase项)
{
来源.添加((t派生)项);
}
公共空间清除()
{
source.Clear();
}
公共布尔包含(TBase项)
{
返回来源。包含((TDerived)项);
}
public void CopyTo(TBase[]数组,int arrayIndex)
{
foreach(源中的var项)
数组[arrayIndex++]=项;
}
公共布尔删除(TBase项)
{
返回来源。删除((t派生)项);
}
公共整数计数
{
获取{return source.Count;}
}
公共图书馆是只读的
{
获取{return source.IsReadOnly;}
}
公共内部索引(TBase项目)
{
返回source.IndexOf((t派生)项);
}
公共作废插入(内部索引,TBase项)
{
来源.插入(索引,(t派生)项);
}
公共无效删除(整数索引)
{
来源:RemoveAt(索引);
}
公共t基于此[int索引]
{
获取{返回源[索引];}
设置{source[index]=(TDerived)值;}
}
}
现在您可以编写如下代码:
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 });
List dogs=new List();
添加(新的Dog{Name=“Spot”,maximumbardecibals=110});
i可数动物=狗;
IList animalList=新的协变适应者(狗);
添加(新狗{Name=“Fluffy”,maximumbardecibals=120});
更改在两个列表中都可见,因为实际上只有一个列表。adapter类只是传递调用,根据需要转换项以实现所需的IList
接口
显然,如果您向animalList
添加除狗以外的任何内容,它将引发异常,但这满足了我的需要。从.NET Framework 4.5开始,存在一个协变的接口IReadOnlyList。它本质上与Mark Gravell回答中的IIndexedEnumerable界面相同
IReadOnlyList的实现方式如下:
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 });
//
///表示可由索引访问的元素的只读集合。
///
///只读列表中元素的类型。此类型参数是协变的。也就是说,您可以使用您指定的类型,也可以使用更派生的任何类型。有关协方差和逆变换的更多信息,请参见泛型中的协方差和逆变换。
公共接口IReadOnlyList:IReadOnlyCollection、IEnumerable、IEnumerable
{
///
///获取只读列表中指定索引处的元素。
///
///
///
///只读列表中指定索引处的元素。
///
///要获取的元素的从零开始的索引。
T此[int索引]{get;}
}
+1…在.NET中缺少只读集合接口(除了IEnumerable
)使得这几乎不可能,但我认为这是一个常见的问题