IEnumerable 在 Array 和 List 上的表现不同
IEnumerable performs differently on Array vs List
这个问题更像是一个 "is my understanding accurate",如果不是,请帮助我解决这个问题。我有这段代码可以解释我的问题:
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()方法乍一看returnIEnumerable 。但是,基础类型是 WhereSelectArrayIterator 和 List .
这是我的理智开始受到质疑的地方。根据我的理解,上面输出的差异是因为两种底层类型实现 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;
}
我从中收集到的是,当您对其进行枚举时,您实际上是在对克隆源进行枚举(如 WhereSelectArrayIterator class' Clone() 方法中所写)。
这将满足我现在的理解需要,但作为奖励,如果有人能帮我弄清楚为什么 this 't returned 我第一次枚举数据。据我所知, state 应该 = 0 第一关。除非,也许在幕后发生了从不同线程调用相同方法的魔法。
更新
在这一点上,我认为我的 'findings' 有点误导(该死的克隆方法让我误入了错误的兔子洞),这确实是由于延迟执行。我错误地认为,即使我推迟了执行,一旦它第一次被枚举,它就会将这些值存储在我的变量中。我早该知道的;毕竟我在 Select 中使用了 new 关键字。也就是说,它仍然让我大开眼界,一个特定的 class' GetEnumerator() 实现仍然可以 return 一个克隆,它会呈现一个非常相似的问题。正好我的问题不一样。
更新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
支持 classes:
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 的工作方式是使每个函数 return 成为另一个 IEnumerable,通常是延迟处理器。在枚举 finally returned Ienumerable 发生之前,不会发生实际执行。这允许创建高效的管道。
当你做的时候
var transformed = wtf.Select(s => new Example { MyString = s });
select代码还没有真正执行。只有当您最终枚举 transformed 时,select 才会完成。即这里
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 或 select 中以实际查看延迟管道执行
阅读 linq 的实现很有用。这里是 select https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,5c652c53e80df013,references
这个问题更像是一个 "is my understanding accurate",如果不是,请帮助我解决这个问题。我有这段代码可以解释我的问题:
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()方法乍一看returnIEnumerable
这是我的理智开始受到质疑的地方。根据我的理解,上面输出的差异是因为两种底层类型实现 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;
}
我从中收集到的是,当您对其进行枚举时,您实际上是在对克隆源进行枚举(如 WhereSelectArrayIterator class' Clone() 方法中所写)。
这将满足我现在的理解需要,但作为奖励,如果有人能帮我弄清楚为什么 this 't returned 我第一次枚举数据。据我所知, state 应该 = 0 第一关。除非,也许在幕后发生了从不同线程调用相同方法的魔法。
更新
在这一点上,我认为我的 'findings' 有点误导(该死的克隆方法让我误入了错误的兔子洞),这确实是由于延迟执行。我错误地认为,即使我推迟了执行,一旦它第一次被枚举,它就会将这些值存储在我的变量中。我早该知道的;毕竟我在 Select 中使用了 new 关键字。也就是说,它仍然让我大开眼界,一个特定的 class' GetEnumerator() 实现仍然可以 return 一个克隆,它会呈现一个非常相似的问题。正好我的问题不一样。
更新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
支持 classes:
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 的工作方式是使每个函数 return 成为另一个 IEnumerable,通常是延迟处理器。在枚举 finally returned Ienumerable 发生之前,不会发生实际执行。这允许创建高效的管道。
当你做的时候
var transformed = wtf.Select(s => new Example { MyString = s });
select代码还没有真正执行。只有当您最终枚举 transformed 时,select 才会完成。即这里
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 或 select 中以实际查看延迟管道执行
阅读 linq 的实现很有用。这里是 select https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,5c652c53e80df013,references