原始答案:
和Thomas的答案一样,只是我认为更好一些:
public static ICollection<T> Materialize<T>(this IEnumerable<T> source)
{
return source as ICollection<T> ?? source.ToList();
}
请注意,如果它是一个有效的集合类型,它往往会返回现有的集合本身,否则会生成一个新的集合。虽然这两者略有不同,但我认为这不会成为问题。
编辑:
今天这是一个更好的解决方案:
public static IReadOnlyCollection<T> Materialize<T>(this IEnumerable<T> source)
{
switch (source)
{
case IReadOnlyCollection<T> readOnlyCollection:
return readOnlyCollection;
case ICollection<T> collection:
return new ReadOnlyCollectionAdapter<T>(collection);
default:
return source.ToList();
}
}
public class ReadOnlyCollectionAdapter<T> : IReadOnlyCollection<T>
{
readonly ICollection<T> m_source;
public ReadOnlyCollectionAdapter(ICollection<T> source) => m_source = source;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int Count => m_source.Count;
public IEnumerator<T> GetEnumerator() => m_source.GetEnumerator();
}
请注意,上述解决方案忽略了一种特定的协变情况,即集合类型实现了
ICollection<T>
但未实现
IReadOnlyCollection<T>
。例如,请考虑以下集合:
class Collection<T> : ICollection<T>
{
}
IEnumerable<object> items = new Collection<Random>();
由于IEnumerable<T>
是协变的,因此上述代码可以编译通过。
// later at some point if you do
IReadOnlyCollection<object> materialized = items.Materialize()
上述代码创建了一个new List<Random>
(O(N)),即使我们传递了一个已经实例化的集合。原因是ICollection<T>
不是协变接口(它不能是),因此我们从Collection<Random>
到ICollection<object>
的转换失败,因此在switch语句中执行default:
情况。
我认为,实现ICollection<T>
但不实现IReadOnlyCollection<T>
的集合类型是极为罕见的情况。我会忽略这种情况。扫描BCL库,我只能找到很少的几个,而且也很少听说过。如果您确实需要涵盖这种情况,可以使用一些反射。例如:
public static IReadOnlyCollection<T> Materialize<T>(this IEnumerable<T> source)
{
if (source is IReadOnlyCollection<T> readOnlyCollection)
return readOnlyCollection;
if (source is ICollection<T> collection)
return new ReadOnlyCollectionAdapter<T>(collection);
if (source.GetType() (is some kind of typeof(ICollection<>))
return new EnumerableAdapter<T>(source);
return source.ToList();
}
public class EnumerableAdapter<T> : IReadOnlyCollection<T>
{
readonly IEnumerable<T> m_source;
public EnumerableAdapter(IEnumerable<T> source) => m_source = source;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int Count => ((dynamic)m_source).Count;
public IEnumerator<T> GetEnumerator() => m_source.GetEnumerator();
}