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