IEnumerable.ForEach 和 List.ForEach 之间的行为差异






4.50/5 (2投票s)
今天早上在工作中,我和一位同事发现在使用 C# ASP.NET MVC 项目中的一个字段的集合类型时,`IEnumerable.ForEach` 和 `List.ForEach` 之间存在一个令人担忧的差异。
今天早上在工作中,我和一位同事发现在使用 C# ASP.NET MVC 项目中的一个字段的集合类型时,`IEnumerable<T>.ForEach
` 和 `List<T>.ForEach
` 之间存在一个令人担忧的差异。 最初该字段的类型是 `IEnumerable<SomeType>
`,并且我们正在传递一个 action 调用它的 `.ForEach
`。 这使用的是 `IEnumerable<T>.ForEach
`,它定义在 `WebGrease.Css.Extensions
` 命名空间中,因为我们已经引用了 WebGrease。 当我们将字段类型切换到 `List<SomeType>
` 时,这行代码自动解析(可能是通过 Resharper)到 `System.Collections.Generic
` 命名空间中的 `.ForEach`。 我们开始在测试中收到一些空引用异常。
我们发现对于大多数代码路径来说,这并不是一个问题,但是当字段为 null 时,由于 WebGrease 版本是一个扩展方法(见下面的代码),该方法会检查 null,然后直接退出该方法,不执行任何操作。
// -------------------------------------------------------------------------------------------------------------------- // <copyright file="ListExtensions.cs" company="Microsoft"> // Copyright Microsoft Corporation, all rights reserved // </copyright> // <summary> // ListExtensions Class - Provides the extension on List // </summary> // -------------------------------------------------------------------------------------------------------------------- namespace WebGrease.Css.Extensions { using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; /// <summary>ListExtensions Class - Provides the extension on List</summary> public static class ListExtensions { /// <summary>For each extension method for IEnumerable</summary> /// <typeparam name="T">The type of items in collection</typeparam> /// <param name="list">The list of items</param> /// <param name="action">The action to perform on items</param> public static void ForEach<T>(this IEnumerable<T> list, Action<T> action) { if (list == null || action == null) { return; } foreach (var item in list) { action(item); } } } }
在 `System.Collections.Generic
` 中的 `List<T>
` 版本中,`ForEach` 是一个实例方法,因此无法在 null 实例上调用该方法,从而导致空引用异常。 该方法的反编译代码如下所示。
namespace System.Collections.Generic { /// <summary>Represents a strongly typed list of objects that can be accessed by index. Provides methods to search, sort, and manipulate lists.</summary> /// <filterpriority>1</filterpriority> [DebuggerDisplay("Count = {Count}")] [DebuggerTypeProxy(typeof(Mscorlib_CollectionDebugView<>))] [Serializable] public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IList, ICollection, IEnumerable { /// <summary>Performs the specified action on each element of the <see cref="T:System.Collections.Generic.List`1"></see>.</summary> /// <param name="action">The <see cref="T:System.Action`1"></see> delegate to perform on each element of the <see cref="T:System.Collections.Generic.List`1"></see>.</param> /// <exception cref="T:System.ArgumentNullException">action is null.</exception> public void ForEach(Action<T> action) { if (action == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match); } for (int i = 0; i < this._size; i++) { action(this._items[i]); } } } }
所以我的抱怨在于 WebGrease 程序集的作者。 如果他们也许在 `list` 参数上添加一个保护,以抛出异常,使其更接近 `List<T>
` 的实现,而不是仅仅“优雅地”忽略该问题并继续执行,我们本可以在开发的早期阶段就对 null 列表进行保护。 或者我的抱怨可能更多的是扩展方法可以被调用在 `null` 对象上!
我现在想知道我的其他软件中是否也存在类似这种定时炸弹,等待爆炸...