C#懒惰执行+内存理解

时间:2014-01-02 20:05:57

标签: c# memory lazy-evaluation yield-return

请您在执行以下代码时向我解释内存中发生的情况:

案例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?

  1. 在下一次迭代中?
  2. 当foreach结束?
  3. 当功能结束时?
  4. 由于

    **编辑**

    案例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中,每个文本在未使用后都标记为垃圾回收。我是对的吗?

3 个答案:

答案 0 :(得分:5)

  • _urls将无限期保留,因为它位于一个字段中。
  • DownloadTexts()(由它返回的迭代器)保持活动状态直到循环结束。
  • 它生成的WebClienthtml保持活动一次。如果您想知道它的绝对精确寿命,您需要使用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,它擅长做它的工作。