65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (2投票s)

2015 年 12 月 14 日

CPOL

2分钟阅读

viewsIcon

9073

今天早上在工作中,我和一位同事发现在使用 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` 对象上!

我现在想知道我的其他软件中是否也存在类似这种定时炸弹,等待爆炸...

© . All rights reserved.