IEnumerable在数组和列表上的性能不同 [英] IEnumerable performs differently on Array vs List

查看:28
本文介绍了IEnumerable在数组和列表上的性能不同的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题更像是一个"我的理解是否准确"的问题,如果不是,请帮我理清思路。我有一小段代码来解释我的问题:

class Example
{
    public string MyString { get; set; }
}

var wtf = new[] { "string1", "string2"};
IEnumerable<Example> transformed = wtf.Select(s => new Example { MyString = s });
IEnumerable<Example> transformedList = wtf.Select(s => new Example { MyString = s }).ToList();

foreach (var i in transformed)
    i.MyString = "somethingDifferent";

foreach (var i in transformedList)
    i.MyString = "somethingDifferent";

foreach(var i in transformed)
    Console.WriteLine(i.MyString);

foreach (var i in transformedList)
    Console.WriteLine(i.MyString);

输出:

string1
string2
somethingDifferent
somethingDifferent

这两个Select()方法乍一看都返回<;Example>。但是,基础类型是Where SelectArrayIterator<;String,Example>List<;Example>

这就是我的理智开始受到质疑的地方。根据我的理解,上述输出的差异是因为两个基础类型实现GetEnumerator()方法的方式不同。

使用this handy website,我能够(我认为)找到导致差异的代码位。

class WhereSelectArrayIterator<TSource, TResult> : Iterator<TResult>
{ }

查看第169行会将我指向Iterator<;TResult>,因为它就是在那里显示GetEnumerator()被调用的。

从第90行开始,我看到:

public IEnumerator<TSource> GetEnumerator() {
    if (threadId == Thread.CurrentThread.ManagedThreadId && state == 0) {
        state = 1;
        return this;
    }
    Iterator<TSource> duplicate = Clone();
    duplicate.state = 1;
    return duplicate;
}

我从中了解到的是,当您枚举它时,您实际上是在枚举克隆的源(正如在Where SelectArrayIterator类的Clone()方法中所写的那样)。

这将满足我现在理解的需要,但作为额外的好处,如果有人能帮助我找出为什么在我第一次枚举数据时没有返回。据我所知,状态在第一次传递时应为0。除非,也许在从不同线程调用同一方法的幕后发生了神奇的事情。

更新

在这一点上,我认为我的‘发现’有点误导(该死的克隆方法把我带进了错误的兔子洞),这确实是由于延迟执行。我错误地认为,即使我推迟了执行,但一旦它第一次被枚举,它就会将这些值存储在我的变量中。我应该更清楚;毕竟我在Select中使用了new关键字。尽管如此,它仍然让我明白了一个想法,即某个特定的类‘GetEnumerator()实现仍然可能返回一个克隆,这将呈现非常类似的问题。碰巧我的问题不一样。

更新2

这是我认为问题所在的一个示例。感谢大家提供的信息。

IEnumerable<Example> friendly = new FriendlyExamples();
IEnumerable<Example> notFriendly = new MeanExamples();

foreach (var example in friendly)
    example.MyString = "somethingDifferent";
foreach (var example in notFriendly)
    example.MyString = "somethingDifferent";

foreach (var example in friendly)
    Console.WriteLine(example.MyString);
foreach (var example in notFriendly)
    Console.WriteLine(example.MyString);

// somethingDifferent
// somethingDifferent
// string1
// string2

支持类:

class Example
{
    public string MyString { get; set; }
    public Example(Example example)
    {
        MyString = example.MyString;
    }
    public Example(string s)
    {
        MyString = s;
    }
}
class FriendlyExamples : IEnumerable<Example>
{
    Example[] wtf = new[] { new Example("string1"), new Example("string2") };

    public IEnumerator<Example> GetEnumerator()
    {
        return wtf.Cast<Example>().GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return wtf.GetEnumerator();
    }
}
class MeanExamples : IEnumerable<Example>
{
    Example[] wtf = new[] { new Example("string1"), new Example("string2") };

    public IEnumerator<Example> GetEnumerator()
    {
        return wtf.Select(e => new Example(e)).Cast<Example>().GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return wtf.Select(e => new Example(e)).GetEnumerator();
    }
}

推荐答案

LINQ的工作方式是让每个函数返回另一个通常是延迟处理器的IEumable。直到对最终返回的可枚举进行枚举,才会发生实际执行。这允许创建高效的管道。

当您这样做时

var transformed = wtf.Select(s => new Example { MyString = s });

选择代码尚未实际执行。只有当您最终枚举已转换时,才会完成选择。IE此处

foreach (var i in transformed)
    i.MyString = "somethingDifferent";

请注意,如果您这样做

foreach (var i in transformed)
    i.MyString = "somethingDifferent";

管道将再次执行。在这里,这不是什么大问题,但如果涉及IO,它可能会很大。

此行

 var transformedList = wtf.Select(s => new Example { MyString = s }).ToList();

相同
var transformedList = transformed.ToList();

真正让人大开眼界的是将调试语句或断点放在WHERE中,或者选择以实际查看延迟的流水线执行

阅读LinQ的实现非常有用。下面是SELECThttps://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,5c652c53e80df013,references

这篇关于IEnumerable在数组和列表上的性能不同的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆