我的理解是,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
。需要Union
和Select
才能重现此问题。
任何 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也是如此,所以我不知道那里有什么。