什么可以是someIEnumerable.Select扩展方法的代码?

时间:2014-07-03 08:10:48

标签: c# linq yield

我是LINQ的新手,想写一些扩展方法。在这之前我想测试我是否会正确地做到这一点。我只想比较CustomSelect扩展方法与内置Select扩展方法的效果。

static void Main(string[] args)
{
    List<int> list = new List<int>();
    for (int i = 0; i < 10000000; i++)
        list.Add(i);

    DateTime now1 = DateTime.Now;
    List<int> process1 = list.Select(i => i).ToList();
    Console.WriteLine(DateTime.Now - now1);

    DateTime now2 = DateTime.Now;
    List<int> process2 = list.CustomSelect(i => i).ToList();
    Console.WriteLine(DateTime.Now - now2);
}

public static IEnumerable<TResult> CustomSelect<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
    foreach (TSource item in source)
    {
        yield return selector(item);
    }
}

内置方法的Timespan:0.18秒
自定义方法的Timespan:0.35秒

更改流程的顺序会产生相同的结果 如果我收集列表中的元素而return而不是yield return,则时间跨度与内置的几乎相同。但据我所知,我们应尽可能yield return

那么内置方法的代码是什么?我的方法应该是什么?

提前致谢

2 个答案:

答案 0 :(得分:4)

我可以看到的关键区别是内置方法检查List<T>和特殊情况,利用自定义List<T>.Enumerator实现,而不是IEnumerable<T> / IEnumerator<T> 。你可以自己做那个特例:

public static IEnumerable<TResult> CustomSelect<TSource, TResult>(
    this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
    if (source is List<TSource>)
        return CustomSelectList((List<TSource>)source, selector);
    return CustomSelectDefault(source, selector);
}
private static IEnumerable<TResult> CustomSelectList<TSource, TResult>(
    List<TSource> source, Func<TSource, TResult> selector)
{
    foreach (TSource item in source)
    {
        yield return selector(item);
    }
}
private static IEnumerable<TResult> CustomSelectDefault<TSource, TResult>(
    IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
    foreach (TSource item in source)
    {
        yield return selector(item);
    }
}

你可以通过手工滚动整个迭代器(这是WhereSelectListIterator<TSource, TResult>所做的)来进一步采取这个步骤,但上面的内容可能足够接近。

内置实现还包括特殊情况数组,并处理各种形式的组合查询。

答案 1 :(得分:1)

您的性能测试存在很多问题,这使得它不确定 - 您应该研究.NET中基准测试代码的最佳实践。使用Stopwatch代替DateTime.Now,同时使用多次重复相同的内容,而不是每次重复一次,确保您不会受到GC的阻碍(.ToList()会对你的测量结果进行一些调整。)

yield return不应该被使用,因为它更快,想法是它易于编写,而且它是懒惰的。如果我在Take(10)变种上yield return,我只会获得10个元素。另一方面,return变体将生成整个列表,返回它,然后将其减少为10个元素。

实际上,您几乎采用了最简单的情况,根本没有理由使用Select(除了清晰度)。 Enumerables用于处理更疯狂的东西,并使用LINQ方法,以易于理解和简洁的方式完成,暴露出功能程序员熟悉的界面。这通常意味着您可以通过以不太常规的方式重写整个事物来获得更高的性能 - 关键是您应该只在必要时才这样做 - 如果这不是应用程序的性能瓶颈(并且它很少会),更清晰,更容易扩展的代码是更好的选择。