List<T>.ForEach(Action<T> action)方法运行原理是什么?如何自行实现同款功能?
List.ForEach 底层实现与自定义实现方案
官方底层实现逻辑
.NET 官方List<T>.ForEach的核心实现代码如下(基于.NET 7 源码简化):
public void ForEach(Action<T> action) { // 入参非空校验 if (action == null) { throw new ArgumentNullException(nameof(action)); } // 记录集合初始版本号,每次修改集合(增删改元素)都会更新该值 int originalVersion = _version; // 遍历内部存储数组的有效元素 for (int i = 0; i < _size; i++) { // 遍历中途检测到集合被修改,终止遍历 if (originalVersion != _version) { break; } // 对当前元素执行传入的委托 action(_items[i]); } // 最终校验集合版本,被修改则抛出异常 if (originalVersion != _version) { throw new InvalidOperationException("集合已被修改,枚举操作无法继续执行。"); } }
其中三个内部字段的作用:
_items:List内部用来存储元素的数组_size:List当前实际存储的元素数量,和Count属性返回值一致_version:集合版本号,每次调用Add/Remove/Insert/Clear等修改集合的方法时都会自增,用来检测遍历过程中的集合修改行为。
完全对齐官方行为的自定义实现
如果需要自定义和官方功能完全一致的ForEach方法,分两种场景处理:
场景1:自定义集合类内置ForEach
直接在自己的集合类中维护_items、_size、_version三个字段,每次修改集合时更新_version,直接套用上面的官方实现代码即可,性能和官方实现完全一致,非常适合自定义类的使用场景。
场景2:给官方List写扩展方法
需要用反射获取List的内部字段,实现代码如下:
public static class ListExtensions { public static void CustomForEach<T>(this List<T> list, Action<T> action) { if (list == null) throw new ArgumentNullException(nameof(list)); if (action == null) throw new ArgumentNullException(nameof(action)); // 反射获取内部字段 var versionField = typeof(List<T>).GetField("_version", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); var itemsField = typeof(List<T>).GetField("_items", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); int originalVersion = (int)versionField.GetValue(list); T[] items = (T[])itemsField.GetValue(list); int count = list.Count; for (int i = 0; i < count; i++) { if ((int)versionField.GetValue(list) != originalVersion) throw new InvalidOperationException("集合已被修改,枚举操作无法继续执行。"); action(items[i]); } if ((int)versionField.GetValue(list) != originalVersion) throw new InvalidOperationException("集合已被修改,枚举操作无法继续执行。"); } }
如果不需要严格对齐官方的集合修改校验逻辑,只需要简化循环写法,可以用更通用的IEnumerable扩展方法,适配所有可枚举类型,性能损失更小:
public static class EnumerableExtensions { public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) { if (source == null) throw new ArgumentNullException(nameof(source)); if (action == null) throw new ArgumentNullException(nameof(action)); foreach (var item in source) { action(item); } } }
内容的提问来源于stack exchange,提问作者Exogenex
相关产品推荐
相关产品推荐

