IList 和IReadOnlyList
如果我有一个需要参数的方法,
- 拥有
Count
属性 - 有一个整数索引器(get-only)
这个参数的类型应该是什么? 我会在.NET 4.5之前选择IList
,因为没有其他可索引的集合接口,并且数组实现它,这是一个很大的优点。
但.NET 4.5引入了新的IReadOnlyList
接口,我也希望我的方法能够支持它。 如何在不违反DRY等基本原则的情况下编写此方法以支持IList
和IReadOnlyList
?
编辑 :丹尼尔的回答给了我一些想法:
public void Foo(IList list) => Foo(list, list.Count, (c, i) => c[i]); public void Foo(IReadOnlyList list) => Foo(list, list.Count, (c, i) => c[i]); private void Foo( TList list, int count, Func indexer) where TList : IEnumerable { // Stuff }
编辑2:或者我可以接受一个IReadOnlyList
并提供这样的帮助:
public static class CollectionEx { public static IReadOnlyList AsReadOnly(this IList list) { if (list == null) throw new ArgumentNullException(nameof(list)); return list as IReadOnlyList ?? new ReadOnlyWrapper(list); } private sealed class ReadOnlyWrapper : IReadOnlyList { private readonly IList _list; public ReadOnlyWrapper(IList list) => _list = list; public int Count => _list.Count; public T this[int index] => _list[index]; public IEnumerator GetEnumerator() => _list.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } }
然后我可以把它称为Foo(list.AsReadOnly())
编辑3:数组实现IList
和IReadOnlyList
, List
类也是如此。 这使得很少找到实现IList
但不实现IReadOnlyList
。
你在这里运气不好。 IList
未实现IReadOnlyList
。 List
确实实现了两个接口,但我认为这不是你想要的。
但是,您可以使用LINQ:
-
Count()
扩展方法在内部检查实例是否实际上是一个集合,然后使用Count
属性。 -
ElementAt()
扩展方法在内部检查实例是否实际上是一个列表而不是使用索引器。
如果你更关心维持DRY的性能而不是性能,你可以使用dynamic
,如下所示:
public void Do(IList collection) { DoInternal(collection, collection.Count, i => collection[i]); } public void Do (IReadOnlyList collection) { DoInternal(collection, collection.Count, i => collection[i]); } private void DoInternal(dynamic collection, int count, Func indexer) { // Get the count. int count = collection.Count; }
但是,我不能真诚地说我推荐这个,因为陷阱太大了:
-
DoInternal
每次collection
的DoInternal
都将在运行时解决。 您丢失了类型安全性,编译时检查等。 - 性能下降(虽然不是严重的,但对于单一情况,但可以在聚合时) 将发生
你的帮助建议是最有用的,但我认为你应该翻转它; 鉴于在.NET 4.5中引入了IReadOnlyList
接口 ,许多API不支持它,但支持IList
接口 。
也就是说,你应该创建一个AsList
包装器,它接受一个IReadOnlyList
并在IList
实现中返回一个包装器。
但是,如果你想强调你正在使用IReadOnlyList
API(强调你没有改变数据这一事实),那么你现在拥有的AsReadOnlyList
扩展更合适,但我’ d对AsReadOnly
进行以下优化:
public static IReadOnlyList AsReadOnly (this IList collection) { if (collection == null) throw new ArgumentNullException("collection"); // Type-sniff, no need to create a wrapper when collection // is an IReadOnlyList *already*. IReadOnlyList list = collection as IReadOnlyList ; // If not null, return that. if (list != null) return list; // Wrap. return new ReadOnlyWrapper (collection); }
由于IList
和IReadOnlyList
不共享任何有用的“祖先”,并且如果您不希望您的方法接受任何其他类型的参数,您唯一能做的就是提供两个重载。
如果你决定重用代码是最重要的,那么你可以让这些重载将调用转发给接受IEnumerable
的private
方法,并以Daniel建议的方式使用LINQ,实际上让LINQ在运行时进行规范化。
然而,恕我直言,最好只复制/粘贴一次代码,并保留两个独立的重载,这些重载只有参数类型不同; 我不相信这种规模的微架构提供任何有形的东西,另一方面它需要非明显的机动并且速度较慢。
你需要的是.Net 4.5中提供的IReadOnlyCollection
,它本质上是一个IEnumerable
,它有Count
作为属性,但如果你还需要索引,那么你需要IReadOnlyList
,它也会给一个索引器。
我不了解你,但我认为这个界面已经很久没有了。