Original answer:
Same as Thomas's answer, just a bit better according to me:
public static ICollection<T> Materialize<T>(this IEnumerable<T> source)
{
// Null check...
return source as ICollection<T> ?? source.ToList();
}
Please note that this tend to return the existing collection itself if its a valid collection type, or produces a new collection otherwise. While the two are subtly different, I don't think it could be an issue.
Edit:
Today this is a better solution:
public static IReadOnlyCollection<T> Materialize<T>(this IEnumerable<T> source)
{
// Null check...
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();
}
Mind you the above solution misses a certain covariant case where the collection type implements ICollection<T>
but not IReadOnlyCollection<T>
. For e.g. consider you have a collection like below:
class Collection<T> : ICollection<T>
{
}
// and then
IEnumerable<object> items = new Collection<Random>();
The above compiles since IEnumerable<T>
is covariant.
// later at some point if you do
IReadOnlyCollection<object> materialized = items.Materialize();
The above code creates a new List<Random>
(O(N)), even though we passed an already materialized collection. The reason is ICollection<T>
is not a covariant interface (it can't be), hence our cast from Collection<Random>
to ICollection<object>
fails, so the default:
case in the switch is executed.
I believe it is an extremely rare scenario for a collection type to implement ICollection<T>
but not IReadOnlyCollection<T>
. I would just ignore that case. Scanning BCL libraries I could find only very few and that too hardly heard of. If at all you need to cover that case as well, you could use some reflection. Something like:
public static IReadOnlyCollection<T> Materialize<T>(this IEnumerable<T> source)
{
// Null check...
if (source is IReadOnlyCollection<T> readOnlyCollection)
return readOnlyCollection;
if (source is ICollection<T> collection)
return new ReadOnlyCollectionAdapter<T>(collection);
// Use your type checking logic here.
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();
}