Linq的IEnumerable.Select是否返回对原始IEnumerable的引用? [英] Does Linq's IEnumerable.Select return a reference to the original IEnumerable?

查看:47
本文介绍了Linq的IEnumerable.Select是否返回对原始IEnumerable的引用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在代码中克隆一个List,因为我需要将该List输出到其他代码,但是稍后将清除原始引用.因此,我想到了使用 Select 扩展方法来创建对相同元素的 IEnumerable 的新引用,例如:

I was trying to clone an List in my code, because I needed to output that List to some other code, but the original reference was going to be cleared later on. So I had the idea of using the Select extension method to create a new reference to an IEnumerable of the same elements, for example:

List<int> ogList = new List<int> {1, 2, 3};
IEnumerable<int> enumerable = ogList.Select(s => s);

现在,在执行 ogList.Clear()之后,我很惊讶地发现我的新枚举也为空.

Now after doing ogList.Clear(), I was surprised to see that my new enumerable was also empty.

所以我开始在LINQPad中摆弄,发现即使我的 Select 完全返回了不同的对象,其行为也是相同的.

So I started fiddling around in LINQPad, and saw that even if my Select returned different objects entirely, the behaviour was the same.

List<int> ogList = new List<int> {1, 2, 3};
IEnumerable<int> enumerable = ogList.Select(s => 5); // Doesn't return the original int
enumerable.Count().Dump(); // Count is 3
ogList.Clear();
enumerable.Count().Dump(); // Count is 0!

请注意,在LINQPad中, Dump() Console.WriteLine()等效.

Note that in LINQPad, the Dump()s are equivalent to Console.WriteLine().

现在我可能首先需要克隆列表是由于设计不良,即使我不想重新考虑设计,我也可以轻松地对其进行正确克隆.但这让我考虑了 Select 扩展方法实际上做什么.

Now probably my need to clone the list in the first place was due to bad design, and even if I didn't want to rethink the design I could easily clone it properly. But this got me thinking about what the Select extension method actually does.

根据

此方法通过使用延迟执行来实现.立即返回值是一个对象,该对象存储执行操作所需的所有信息.在通过直接调用对象的GetEnumerator方法或在Visual C#中使用foreach或在Visual Basic中使用For Each枚举对象之前,不会执行此方法表示的查询.

This method is implemented by using deferred execution. The immediate return value is an object that stores all the information that is required to perform the action. The query represented by this method is not executed until the object is enumerated either by calling its GetEnumerator method directly or by using foreach in Visual C# or For Each in Visual Basic.

因此,然后我尝试在清除之前添加以下代码:

So then I tried adding this code before clearing:

foreach (int i in enumerable)
{
    i.Dump();
}

结果仍然相同.

最后,我尝试了最后一件事,以确定新枚举中的引用是否与旧引用相同.我没有清除原始列表,而是这样做了:

Finally, I tried one last thing to figure out if the reference in my new enumerable was the same as the old one. Instead of clearing the original List, I did:

ogList.Add(4);

然后我打印出我的枚举(克隆"的)的内容,希望在其末尾看到"4".相反,我得到了:

Then I printed out the contents of my enumerable (the "cloned" one), expecting to see '4' appended to the end of it. Instead, I got:

5
5
5
5 // Huh?

现在,我别无选择,只能承认我不知道Select扩展方法在后台如何工作.发生了什么事?

Now I have no choice but to admit that I have no idea how the Select extension method works behind the scenes. What's going on?

推荐答案

List/List< T> 出于所有意图和目的,都是可调整大小的数组.他们拥有并保存值类型的数据(例如您的整数)或对内存中引用类型的数据的引用,并且他们始终知道它们有多少个项目.

List/List<T> are for all intents and purposes fancy resizable arrays. They own and hold the data for value types such as your ints or references to the data for reference types in memory and they always know how many items they have.

IEnumerable/IEnumerable< T> 是不同的野兽.他们提供不同的服务/合同. IEnumerable 是虚构的,它不存在.它可以在没有物理支持的情况下凭空创建数据.他们唯一的保证就是拥有一个名为 GetEnumerator()的公共方法,该方法返回 IEnumerator/IEnumerator< T> . IEnumerator 做出的承诺很简单:当您决定需要某个项目时,某些项目可能可用或不可用.这是通过 IEnumerator 接口具有的简单方法实现的: bool MoveNext()-在枚举完成时返回false,或者在实际上有新项目时返回true需要退还的.您可以通过 IEnumerator 接口具有的属性(通常称为 Current .)来读取数据.

IEnumerable/IEnumerable<T> are different beasts. They provide a different service/contract. An IEnumerable is fictional, it does not exist. It can create data out of thin air, with no physical backing. Their only promise is that they have a public method called GetEnumerator() that returns an IEnumerator/IEnumerator<T>. The promise that an IEnumerator makes is simple: some item could be available or not at a time when you decide you need it. This is achieved through a simple method that the IEnumerator interface has: bool MoveNext() - which returns false when the enumeration is completed or true if there was in fact a new item that needed to be returned. You can read the data through a property that the IEnumerator interface has, conveniently called Current.

回到您的观察/问题:就示例中的 IEnumerable 而言,除非您的代码告诉它获取某些数据,否则它甚至不会考虑数据.

To get back to your observations/question: as far as the IEnumerable in your example is concerned, it does not even think about the data unless your code tells it to fetch some data.

写作时:

List<int> ogList = new List<int> {1, 2, 3};
IEnumerable<int> enumerable = ogList.Select(s => s);

您的意思是:请在此处听 IEnumerable ,我可能会在将来某个时候向您询问一些物品.我会告诉您何时需要它们,暂时不动,不做任何事情.使用 Select(s => s)从概念上定义从int到int的恒等投影.

You are saying: Listen here IEnumerable, I might come to you asking for some items at some point in the future. I'll tell you when I will need them, for now sit still and do nothing. With Select(s => s) you are conceptually defining an identity projection of int to int.

您编写的选择的一个非常粗略的,简化的,非现实的实现是:

A very rough simplified, non-real-life implementation of the select you've written is:

IEnumerable<T> Select(this IEnumerable<int> source, Func<int,T> transformer) something like
{
    foreach (var i in source) //create an enumerator for source and starts enumeration
    {
        yield return transformer(i); //yield here == return an item and wait for orders
    }
}

(这解释了为什么您期望a为5时,您的变换为s => 5)

(this explains why you got a 5 when expecting a for, your transform was s => 5)

对于值类型(例如您所用的整数):如果要克隆列表,请使用通过 List实现的枚举结果来克隆整个列表或列表的一部分,以供将来进行枚举.这样,您可以创建一个列表,该列表是原始列表的克隆,并且与原始列表完全分离:

For value types, such as the ints in your case: If you want to clone the list, clone the whole list or part of it for future enumeration by using the result of an enumeration materialized through a List. This way you create a list that is a clone of the original list, entirely detached from its original list:

IEnumerable<int> cloneOfEnumerable = ogList.Select(s => s).ToList();

稍后当然ogList.Select(s => s)等同于ogList.我要把投影留在这里,就像问题所在一样.

Later edit: Of course ogList.Select(s => s) is equivalent to ogList. I'm leaving the projection here, as it was in the question.

您要在此处创建的是:可枚举结果的列表,该列表通过 IEnumerable< int> 接口进一步使用.考虑到我上面所说的关于 IList IEnumerable 的本质,我更喜欢写/读:

What you are creating here is: a list from the result of an enumerable, further consumed through the IEnumerable<int> interface. Considering what I've said above about the nature of IList vs IEnumerable, I would prefer to write/read:

IList<int> cloneOfEnumerable = ogList.ToList();

注意:注意参考类型. IList/List 不保证对象安全",它们可以在所有 IList 关心的情况下变异为null.关键字(如果需要):深度克隆.

CAUTION: Be careful with reference types. IList/List make no promise of keeping the objects "safe", they can mutate to null for all IList cares. Keyword if you ever need it: deep cloning.

注意:当心无限或不可倒退的IEnumerables

CAUTION: Beware of infinite or non-rewindable IEnumerables

这篇关于Linq的IEnumerable.Select是否返回对原始IEnumerable的引用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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