Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/jpa/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
这种闭包组合行为是C#编译器错误吗?_C#_Lambda_Closures_.net 4.6 - Fatal编程技术网

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

这种闭包组合行为是C#编译器错误吗?,c#,lambda,closures,.net-4.6,C#,Lambda,Closures,.net 4.6,我在调查一些奇怪的对象生存期问题时,遇到了C#编译器的这种非常令人费解的行为: 考虑以下测试类: 类测试 { 委托流CreateStream(); CreateStream测试方法(IEnumerable数据) { string file=“dummy.txt”; var hashSet=新hashSet(); var count=data.count(s=>hashSet.Add); CreateStream CreateStream=()=>File.OpenRead(文件); 返回cre

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

考虑以下测试类:

类测试
{
委托流CreateStream();
CreateStream测试方法(IEnumerable数据)
{
string file=“dummy.txt”;
var hashSet=新hashSet();
var count=data.count(s=>hashSet.Add);
CreateStream CreateStream=()=>File.OpenRead(文件);
返回createStream;
}
}
编译器生成以下内容:

内部类测试
{
公开考试()
{
base..ctor();
}
private Test.CreateStream TestMethod(IEnumerable数据)
{
Test.c_uudisplayClass1_0 cDisplayClass10=新建Test.c_udisplayClass1_0();
cDisplayClass10.file=“dummy.txt”;
cDisplayClass10.hashSet=新的hashSet();
可枚举的计数(数据,新函数((对象)cDisplayClass10,u方法ptr(b_u0));
返回新的Test.CreateStream((对象)cDisplayClass10,_methodptr(b__1));
}
私有委托流CreateStream();
[编译生成]
专用密封类c\u\u显示器Class1\u 0
{
公共哈希集;
公共字符串文件;
公共c___显示类别1_0()
{
base..ctor();
}
内部布尔b__0(字符串s)
{
返回此.hashSet.Add;
}
内部流b__1()
{
return(Stream)File.OpenRead(this.File);
}
}
}
原始类包含两个lambda:
s=>hashSet.Add
()=>File.OpenRead(File)
。第一个关闭局部变量
哈希集
,第二个关闭局部变量
文件
。但是,编译器生成一个闭包实现类
c\uu DisplayClass1\u 0
,该类同时包含
hashSet
文件
。因此,返回的
CreateStream
委托包含并保持对
hashSet
对象的引用,一旦
TestMethod
返回,该对象本应可用于GC

在我遇到这个问题的实际场景中,一个非常重要(即大于100mb)的对象被错误地封闭起来

我的具体问题是:

  • 这是虫子吗?如果不是,为什么认为这种行为是可取的

  • 更新:

    C#5规范7.15.5.1规定:

    当外部变量被匿名函数引用时 据说外部变量已被匿名者捕获 功能。通常,局部变量的生存期限制为 与之关联的块或语句的执行 (§5.1.7). 但是,捕获的外部变量的生存期是 至少扩展到从中创建委托或表达式树 匿名函数可以进行垃圾收集

    这似乎对某种程度的解释是开放的,并没有明确禁止lambda捕获它没有引用的变量。但是,本文介绍了一个相关的场景,@eric lippert认为这是一个bug。IMHO,我认为编译器提供的组合闭包实现是一个很好的优化,但是优化不应该用于lambda,因为编译器可以合理地检测到lambda的生存期可能超过当前堆栈帧


  • 如何在不放弃使用lambdas的情况下编写代码?值得注意的是,我如何防御性地编写代码,以便将来的代码更改不会突然导致同一方法中的其他未更改的lambda开始包含它不应该包含的内容

  • 更新:

    我提供的代码示例是根据需要设计的。显然,将lambda创建重构为一个单独的方法可以解决这个问题。我的问题不是关于设计最佳实践的(正如@peter duniho所述)。相反,鉴于
    TestMethod
    的内容,我想知道是否有任何方法可以强制编译器将
    createStream
    lambda从组合闭包实现中排除



    作为记录,我的目标是.NET 4.6和VS 2015。

    我不知道C语言规范中有任何东西确切规定编译器如何实现匿名方法和变量捕获。这是一个实现细节

    规范所做的是为匿名方法及其捕获变量的行为设置一些规则。我没有C#6规范的副本,但这里有C#5规范的相关文本,在“7.15.5.1捕获的外部变量”下:

    …捕获的外部变量的生存期至少会延长到从匿名函数创建的委托或表达式树符合垃圾收集条件为止。[我的重点]

    规范中没有限制变量生存期的内容。如果匿名方法需要,编译器只需确保变量的寿命足够长,以保持有效

    所以

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

    不是虫子。编译器符合规范

    至于它是否被认为是“可取的”,这是一个很复杂的术语。什么是“可取的”取决于你的优先次序。这就是说,编译器作者的一个优先任务是简化编译器的任务(在这样做的过程中,使它运行得更快,并减少出现错误的机会)。在这方面,这一具体实施可能被认为是“可取的”

    另一方面,语言设计者和编译器作者都有一个共同的目标,即帮助程序员生成工作代码。由于实现细节可能会干扰这一点,因此这种实现细节可能被认为是“不可取的”。根据研究,归根结底,这是一个如何对这些优先事项进行排序的问题
    CreateStream TestMethod( IEnumerable<string> data )
    {
        string file = "dummy.txt";
        var hashSet = new HashSet<string>();
    
        var count = AddAndCountNewItems(data, hashSet);
    
        CreateStream createStream = GetCreateStreamCallback(file);
    
        return createStream;
    }
    
    int AddAndCountNewItems(IEnumerable<string> data, HashSet<string> hashSet)
    {
        return data.Count( s => hashSet.Add( s ) );
    }
    
    CreateStream GetCreateStreamCallback(string file)
    {
        return () => File.OpenRead( file );
    }