gpt4 book ai didi

c# - 这种闭包组合行为是 C# 编译器错误吗?

转载 作者:可可西里 更新时间:2023-11-01 03:08:36 27 4
gpt4 key购买 nike

我在调查一些奇怪的对象生命周期问题时,发现了 C# 编译器的这种非常令人费解的行为:

考虑以下测试类:

class Test
{
delegate Stream CreateStream();

CreateStream TestMethod( IEnumerable<string> data )
{
string file = "dummy.txt";
var hashSet = new HashSet<string>();

var count = data.Count( s => hashSet.Add( s ) );

CreateStream createStream = () => File.OpenRead( file );

return createStream;
}
}

编译器生成以下内容:

internal class Test
{
public Test()
{
base..ctor();
}

private Test.CreateStream TestMethod(IEnumerable<string> data)
{
Test.<>c__DisplayClass1_0 cDisplayClass10 = new Test.<>c__DisplayClass1_0();
cDisplayClass10.file = "dummy.txt";
cDisplayClass10.hashSet = new HashSet<string>();
Enumerable.Count<string>(data, new Func<string, bool>((object) cDisplayClass10, __methodptr(<TestMethod>b__0)));
return new Test.CreateStream((object) cDisplayClass10, __methodptr(<TestMethod>b__1));
}

private delegate Stream CreateStream();

[CompilerGenerated]
private sealed class <>c__DisplayClass1_0
{
public HashSet<string> hashSet;
public string file;

public <>c__DisplayClass1_0()
{
base..ctor();
}

internal bool <TestMethod>b__0(string s)
{
return this.hashSet.Add(s);
}

internal Stream <TestMethod>b__1()
{
return (Stream) File.OpenRead(this.file);
}
}
}

原始类包含两个 lambda:s => hashSet.Add( s )() => File.OpenRead( file ) .第一个关闭局部变量 hashSet ,第二个关闭局部变量 file .但是,编译器会生成一个闭包实现类 <>c__DisplayClass1_0包含 hashSetfile .因此,返回的 CreateStream委托(delegate)包含并保持对 hashSet 的引用应该可用于 GC 一次的对象 TestMethod返回。

在我遇到此问题的实际场景中,一个非常大的(即 >100mb)对象被错误地包含在内。

我的具体问题是:

  1. 这是一个错误吗?如果不是,为什么这种行为被认为是可取的?

更新:

C# 5 规范 7.15.5.1 说:

When an outer variable is referenced by an anonymous function, the outer variable is said to have been captured by the anonymous function. Ordinarily, the lifetime of a local variable is limited to execution of the block or statement with which it is associated (§5.1.7). However, the lifetime of a captured outer variable is extended at least until the delegate or expression tree created from the anonymous function becomes eligible for garbage collection.

这在某种程度上似乎是开放的解释,并且没有明确禁止 lambda 捕获它不引用的变量。然而,this question涵盖了一个相关的场景,@eric-lippert 认为这是一个错误。恕我直言,我认为编译器提供的组合闭包实现是一个很好的优化,但该优化不应该用于编译器可以合理检测到的 lambda 可能具有超出当前堆栈帧的生命周期。


  1. 如何在不完全放弃使用 lambda 的情况下针对此进行编码?尤其是我如何防御性地编写代码,以便将来的代码更改不会突然导致同一方法中其他一些未更改的 lambda 开始包含它不应该包含的内容?

更新:

我提供的代码示例是出于必要而设计的。显然,将 lambda 创建重构为一个单独的方法可以解决这个问题。我的问题不是关于设计最佳实践(@peter-duniho 也涵盖了)。相反,鉴于 TestMethod 的内容就目前而言,我想知道是否有任何方法可以强制编译器排除 createStream来自组合闭包实现的 lambda。


郑重声明,我的目标是 .NET 4.6 和 VS 2015。

最佳答案

Is this a bug?

没有。编译器符合此处的规范。

Why is this behaviour considered desirable?

这是不可取的。这是非常不幸的,正如你在这里发现的,正如我在 2007 年所描述的那样:

http://blogs.msdn.com/b/ericlippert/archive/2007/06/06/fyi-c-and-vb-closures-are-per-scope.aspx

自 C# 3.0 以来,C# 编译器团队已考虑在每个版本中修复此问题,而且它的优先级一直不够高。考虑在 Roslyn github 站点上输入一个问题(如果还没有;很可能有)。

我个人希望看到这个问题得到解决;就目前而言,这是一个很大的“问题”。

How do I code against this without abandoning the use of lambdas all together?

变量就是被捕获的东西。完成后,您可以将 hashset 变量设置为 null。然后唯一消耗的内存是变量的内存,四个字节,而不是它所指的东西的内存,它将被收集。

关于c# - 这种闭包组合行为是 C# 编译器错误吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33894603/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com