如何可靠判断IEnumerable<T>是否已被物化?
判断IEnumerable是否已物化的通用方案
问题由来
之前我用这段代码确保返回的序列已物化、不会被重复求值:
return source as IList<T> ?? source.ToArray();
但在.NET 8及以上版本中,Linq的Where、Cast等方法返回的内部Iterator<T>子类,虽然实现了IList<T>接口,却依然是延迟执行的——并没有真正物化。而Iterator<T>是内部类,没法直接类型检查,用反射又会严重拖慢性能。
现在需要一种通用、简单的方法,不用枚举所有已知集合类型,就能判断IEnumerable<T>是否确实已物化,要覆盖List<T>、T[]、ObservableCollection<T>、ConcurrentBag<T>、Dictionary<K,V>的键/值/键值对集合这类典型类型。
核心思路
真正物化的内存集合,必然要支持快速获取元素数量(O(1)时间复杂度),这类集合都会实现ICollection<T>(或非泛型ICollection)接口。而Linq的延迟迭代器即使实现了IList<T>,也不会实现ICollection<T>——因为它们的Count属性需要遍历整个序列才能计算出来,不符合物化集合的特征。
实现代码
判断是否物化的扩展方法
public static bool IsMaterialized<T>(this IEnumerable<T> source) { ArgumentNullException.ThrowIfNull(source); // 数组、List是最典型的物化集合,直接判定 if (source is T[] || source is List<T>) return true; // 实现ICollection<T>/ICollection的集合,Count可直接获取,说明已物化 return source is ICollection<T> || source is ICollection; }
改进后的确保物化方法
替换原来的逻辑,避免误判Linq迭代器:
public static IEnumerable<T> EnsureMaterialized<T>(this IEnumerable<T> source) { ArgumentNullException.ThrowIfNull(source); if (source is T[] array) return array; if (source is List<T> list) return list; if (source is ICollection<T> || source is ICollection) return source; // 未物化的序列,转为数组实现物化 return source.ToArray(); }
验证说明
在.NET 8中测试:
Enumerable.Range(1, 10).Where(x => x > 5)返回的WhereEnumerableIterator<int>,虽然实现了IList<int>,但未实现ICollection<int>,IsMaterialized会返回false,符合预期。new List<int> {1,2,3}、new int[] {1,2,3}、new ObservableCollection<int> {1,2,3}这类集合,都会被正确判定为已物化。new Dictionary<int, string>().KeyValuePairs也实现了ICollection<KeyValuePair<int, string>>,会被正确识别。
内容的提问来源于stack exchange,提问作者Christoph Meißer
相关产品推荐
相关产品推荐

