C# List<T>与IEnumerable<T>性能问题

21

嗨,假设有这两种方法:

private List<IObjectProvider> GetProviderForType(Type type)
        {
            List<IObjectProvider> returnValue = new List<IObjectProvider>();

            foreach (KeyValuePair<Type, IObjectProvider> provider in _objectProviders)
            {
                if ((provider.Key.IsAssignableFrom(type) ||
                    type.IsAssignableFrom(provider.Key)) &&
                    provider.Value.SupportsType(type))
                {
                    returnValue.Add(provider.Value);
                }
            }
            return returnValue;
        }

private IEnumerable<IObjectProvider> GetProviderForType1(Type type)
        {
            foreach (KeyValuePair<Type, IObjectProvider> provider in _objectProviders)
                if ((provider.Key.IsAssignableFrom(type) ||
                    type.IsAssignableFrom(provider.Key)) &&
                    provider.Value.SupportsType(type))

                    yield return provider.Value;              
        }

哪种方法更快?当我看第一种方法时,我发现为List分配了内存,而在我看来这是不必要的。对我来说,IEnumerable 方法似乎更快。

例如,假设你调用

int a = GetProviderForType(myType).Count;
int b = GetProviderForType1(myType).Count();

现在,另一个问题是,这两种方法之间是否存在性能差异?

你认为呢?


16
所有关于“哪个更快?”的问题的答案都是相同的:两种方式都试一下,用秒表计时,这样你就会知道了。 - Eric Lippert
4个回答

38

在这种情况下,使用 IEnumerable<T> 形式将更加高效,因为您只需要知道计数。如果不需要存储数据、调整缓冲区大小等操作,那么就没有必要这样做。

如果您需要再次使用结果,则 List<T> 形式将更有效。

请注意,对于 List<T>Count() 扩展方法和 Count 属性都是高效的,因为 Count() 的实现会检查目标序列是否实现了 ICollection<T>,如果是,则使用 Count 属性。

另一个甚至更加高效的选择(尽管只是微小差别)是调用带有委托的 Count 重载:

private int GetProviderCount(Type type)
{
  return _objectProviders.Count(provider =>
      (provider.Key.IsAssignableFrom(type) 
       || type.IsAssignableFrom(provider.Key))
      && provider.Value.SupportsType(type));
}

这将避免由WhereSelect子句引起的额外间接层次。

(正如Marc所说,对于少量数据来说,性能差异可能会忽略不计。)


我认为在这种情况下你应该返回一个整数。 - bruno conde
2
你的所有答案都对我很有帮助,但我认为这一个回答提到了所有需要的内容,所以我接受了它。谢谢! - theSpyCry
抱歉Jon。假设非空的“Books”类型为“IEnumerable<Book>”或“List<Book>”,并且我只需要读取而不修改(添加,删除,插入等)“Books”。哪一个更有效率,“Books[0].Author”还是“Books.FirstOrDefault().Author”? - Second Person Shooter
@MoneyOrientedProgrammer:如果Books[0].Author是一个IEnumerable<Book>,那么它将无法编译。对于List<Book>,我会使用Books[0].Author,因为这可能更易读。使用FirstOrDefault()确实很奇怪,但是不处理返回null的情况则会抛出异常以获取作者的引用。 - Jon Skeet
是的。当然,初步假设是Books[0].Author适用于类型为List<Books>非空Books,以及Books.FirstOrDefault().Author适用于类型为IEnumerable<Books>非空Books。额外的假设是不执行调整大小操作。 - Second Person Shooter

6
这个问题的一个重要部分是“数据有多大”?有多少行...
对于小量数据,使用列表是可以的 - 分配足够大的列表需要极少的时间,并且不会多次调整大小(如果您事先知道它的大小,则根本不需要调整)。
然而,这并不能适用于大量数据; ���的供应商可能不支持成千上万的接口,所以我不会说必须采用这种模式 - 但它不会带来太大的伤害。
当然,您也可以使用LINQ:
return from provider in _objectProviders
       where provider.Key.IsAssignableFrom(type) ...
       select provider.Value;

这也是“延迟yield”方法的实现方式...


4

这类问题的确切答案可能因许多因素而异,并且随着CLR的发展可能会进一步变化。要确定答案,唯一的方法是进行测量 - 请记住,如果与操作相比差异很小,则应选择最可读性和可维护性较高的写法。

另外,您还可以尝试以下方法:

private IEnumerable<IObjectProvider> GetProviderForType1(Type type)
{
    return _objectProviders.Where(provider => 
                  provider.Key.IsAssignableFrom(type) ||
                  type.IsAssignableFrom(provider.Key)) &&
                  provider.Value.SupportsType(type))
                           .Select(p => p.Value);
}

通过返回 IEnumerable<T> 并使用 ToList 扩展方法,您还可以为自己提供很多灵活性,如果您想将结果“快照”到列表中。如果您需要多次检查它,则可以避免重复评估生成列表的代码。


2

IEnumerable和IList之间的主要区别:

IEnumerable: 实现了MoveNext、Reset、GetCurrent方法,并返回IEnumerator类型以遍历记录。

IList:暴露IEnumerable接口,同时也是一个非泛型对象集合,可以通过索引访问,因此IEnumerable+ICollection(数据操作)和添加、删除、插入(在特定索引处)是IList实现的有用方法。

在查看您的代码后,我认为IEnumerable更有效率,但如果您想对数据进行一些操作并且只想遍历数据,则使用IEnumerable更可取。


1
错误。IEnumerable只有一个方法。返回迭代器比构建列表更高效。 - SLaks
1
另一种可能性是返回一种类型,该类型以只读方式实现ICollection(非泛型!)以及IEnumerable<T>和可能的ICollection<T>。如果实现IEnumerable<T>的类也实现了ICollectionICollection<T>,则IEnumerable<T>Count扩展方法将使用其中一个接口的Count方法。否则,它将枚举所有项以获取计数。请注意,非泛型表单对于此目的略微更有用,因为... - supercat
1
一个 IEnumeration<Cat> 可以被用作 IEnumeration<Animal>,但是一个 ICollection<Cat> 不能被用作 ICollection<Animal>。如果一个期望 IEnumeration<Animal> 的例程被给予实现了 ICollection<Cat> 但没有非泛型 ICollection 的类实例,它将无法知道该集合应该被转换为 ICollection<Cat> 来获取计数。然而,如果该类实现了非泛型 ICollection,那么期望 IEnumerable<Animal> 的代码就不会有任何问题。 - supercat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接