将首先调用 Dispose,并在延迟执行的情况下导致失败

本文关键字:执行 情况下 失败 延迟 调用 Dispose | 更新日期: 2023-09-27 17:57:44

有两种方法,其中一种在using语句中使用 LINQ 返回数据。我想知道查询是否有可能因为查询执行被延迟并且它使用的变量已被释放而引发某种异常?

class Foo
{
    void Bar()
    {
       var bazResult = Baz();
       //... use bazResult here...
    }
    IEnumerable<int> Baz()
    {
        using (var d = new SomeDisposableSource())
        {
            return d.Select(e => e.Id);
        }
    }

}

顺便说一句,一定已经以某种形式问过了,但我找不到明显的候选人。所以不要踢我太狠:)

将首先调用 Dispose,并在延迟执行的情况下导致失败

我认为如果对象被释放,你会有一个例外。这个线程非常相似,并提供了几种处理问题的方法。 简单的方法是通过执行return d.Select(e => e.Id).ToList()强制执行,但这可能不适合您

是的,它可以抛出异常,但它取决于"SomeDisposableSource"的实现。在调用Dispose((之前,您要求源代码获取IEnumerable或数组,但实际上您是在Dispose之后枚举每个元素,因此它是否抛出和异常取决于该"yeld-return"代码的实际代码。(它是否使用任何已释放的对象?

您可以通过执行以下操作来解决此问题(具有更高的内存使用率(:

return d.Select(e => e.Id).ToArray();

这样,在执行 Dispose(( 之前完成所有枚举。

编辑:使用:

return d.Select(e => e.Id).ToList();

。可能会更好。

whether it's possible是一个

奇怪的问题。是的,这是可能的。您的SomeDisposableSource可能会检查它是否以GetEnumerator方法处理。

我认为 Gerardo 走在正确的轨道上,但我会以不同的方式编写它,这可能会导致内存占用更小:

return d.Select(e => e.Id).ToList();

编辑:哎呀!靛蓝三角洲遥遥领先于我

您将(更多(确定性using语句与(确定性较低的(LINQ 语句混合在一起。通过将资源d包装在该using语句中,您可以显式声明在方法结束时您希望释放它。

因此,如果要确保在方法退出之前释放d,则必须使用 ToArrayToList 或该类型的其他方法立即执行 LINQ。

稍微复杂一点(每个注释者(的路径是创建自定义IEnumerable<T>,该允许资源(d(与LINQ语句一起返回并在以后执行,即调用者现在负责释放IEnumerable<T>(通常只是通过使用foreach块(。

事实上,执行不会在您的代码中延迟,因为您使用了常规return。因此,Baz方法执行、返回和处置。稍后,当您枚举结果时,如果此枚举机制依赖于已释放的非托管资源(示例中很可能是这种情况(,这将失败。

解决方法很简单:不要使用return阻止延迟执行,而是使用yield return。这是延迟执行的准确关键字。

你的方法变成了这个

    IEnumerable<int> Baz()
    {
        using (var d = new SomeDisposableSource())
        {
            //return d.Select(e => e.Id); //Baaaad ! No proper deferred execution
            foreach (var i in d.Select(e => e.Id)) yield return i; //Proper deferred execution
        }
    }

然后一切都好了。在枚举完成之前,using不会调用 Dispose 方法。