LINQ 在使用联合和选择时未在我的 IEnumerator 上调用 Dispose 时,预期的行为或错误?



我的理解是,LINQ IEnumerable扩展应该在IEnumerable生成的IEnumerator上调用Dispose。在 SO 上看到这个答案。但是,我在实践中没有看到这一点。是其他 SO 答案不正确,还是我在 LINQ 中发现了错误?

以下是使用Mono的问题的最小重现。它还在Dotnet Core 2.1和2.2中重现。

using System;
using System.Threading;
using System.Linq;
using System.Collections.Generic;
using System.Collections;

namespace union
{
class MyEnumerable : IEnumerable<long>
{
public IEnumerator<long> GetEnumerator()
{
return new MyEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
class MyEnumerator : IEnumerator<long>
{
public long Current
{
get
{
return 0;
}
}
object IEnumerator.Current
{
get
{
return Current;
}
}
public bool MoveNext()
{
return false;
}
public void Reset()
{
return;
}
public void Dispose()
{
Console.WriteLine("I got disposed");
}
}
class Program
{
static void Main(string[] args)
{
var enum1 = new MyEnumerable();
var enum2 = new MyEnumerable();
enum1.Union(enum2).Select(x => x + 1).ToList();
Console.WriteLine("All done!");
}
}
}

如果调用了"处置",您将在控制台上看到两次I got disposed。相反,你没有得到任何I got disposed。需要UnionSelect才能重现此问题。

任何 C# 大师都知道我是否遇到了错误?

更新:

我相信这是一个错误,我已经提交了:https://github.com/dotnet/corefx/issues/40384。

我在.NET Fiddle中进行了测试,它允许您使用三个编译器之一,结果证实了您的发现:

  • .NET 4.7.2 确实会释放
  • 罗斯林 2.0 确实会处理
  • .NET Core 2.2 没有

似乎莫诺也没有。

但我想我缩小了正在发生的事情:当第一次调用MoveNext()返回false时,它不会调用Dispose()(即集合中没有项目(。

例如,如果您将MoveNext()代码替换为 this,您将看到以下内容,它只是第一次返回true,然后返回false(即模拟包含 1 个项目的集合(:

private bool happenedOnce = false;
public bool MoveNext()
{
if (happenedOnce) return false;
happenedOnce = true;
return true;
}

这是Mono和.NET Core 2.2中的示例。

我想可以说这不是一个错误,而是一个功能。如果馆藏中没有任何东西,有什么可处理的?(刮擦那个 - 文件等...

但话又说回来,这是不一致的。例如,这将给你完全相同的结果,但它释放,即使集合中没有任何内容:

enum1.Select(x => x + 1).Union(enum2.Select(x => x + 1)).ToList();

但所做的只是强制它使用Select()而不是Union()迭代集合。所以这听起来确实像是实现Union()中的一个错误。

我今天有时间(家人不在拜访朋友(,所以我花时间调试了.NET Core源代码。我相信问题就在这里:

if (enumerator.MoveNext())
{
SetEnumerator(enumerator);
StoreFirst();
return true;
}

SetEnumerator()将枚举器存储在实例变量中。您会看到只有当MoveNext()返回true时才调用它。后来,在Dispose()中,它只在迭代器上调用Dispose(),如果它不是null。但由于它从未被设置过,它确实null.

Select()的实现形成对比,后者在调用MoveNext()之前设置其_enumerator变量。

.NET Framework只依赖于foreach,正如你在这里看到的。奇怪的是,Mono也是如此,所以我不知道那里有什么。

相关内容

最新更新