请您在执行以下代码时向我解释内存中发生的情况:
案例1:
public static void Execute()
{
foreach(var text in DownloadTexts())
{
Console.WriteLine(text);
}
}
public static IEnumerable<string> DownloadTexts()
{
foreach(var url in _urls)
{
using (var webClient = new WebClient())
{
yield return webClient.DownloadText(url);
}
}
}
让我们假设在第一次迭代后我得到html1。
何时从内存中清除html1?
由于
**编辑**
案例2:
public static void Execute()
{
var values = DownloadTexts();
foreach(var text in values)
{
Console.WriteLine(text);
}
}
public static IEnumerable<string> DownloadTexts()
{
foreach(var url in _urls)
{
using (var webClient = new WebClient())
{
yield return webClient.DownloadText(url);
}
}
}
根据我的理解,案例1对记忆更好,然后案例2对吗?
如果案例2仍然保留对我们已经下载的文本的引用,则在案例1中,每个文本在未使用后都标记为垃圾回收。我是对的吗?
答案 0 :(得分:5)
_urls
将无限期保留,因为它位于一个字段中。DownloadTexts()
(由它返回的迭代器)保持活动状态直到循环结束。WebClient
和html
保持活动一次。如果您想知道它的绝对精确寿命,您需要使用Reflector并在心理上模拟参考传播的位置。你会发现循环中使用的IEnumerator
引用它,直到下一次迭代开始。所有不活动的对象都可以进行GC操作。只要GC认为这是一个好主意,就会发生这种情况。
关于您的编辑:案例相同。如果不将枚举数放入变量中,编译器将为您执行此操作。它必须保持引用直到循环结束。有多少引用并不重要。至少有一个。
实际上,循环只需要使枚举器保持活动状态。您添加的其他变量也将使可枚举值保持活动状态。另一方面,您没有使用变量,因此GC不会使其保持活动状态。
您可以轻松测试:
//allocate 1TB of memory:
var items =
Enumerable.Range(0, 1024 * 1024 * 1024)
.Select(x => new string('x', 1024));
foreach (var _ in items) { } //constant memory usage
答案 1 :(得分:0)
当垃圾收集器运行时,它将从内存中清除,并确定它已不再使用。
当foreach
导致调用IEnumerator.MoveNext()
方法时,该值将不再使用。所以,实际上,#1。
答案 2 :(得分:0)
当垃圾收集器感觉这样做时,它将从内存中清除。
但是起点是代码不再引用对象的实例。 因此,在这种情况下的答案是:在创建对象结束的块之后的某个时间。
相信GC,它擅长做它的工作。