更新: :嗯,现在我去了:我 用Microsoft提交了错误报告 关于这一点,我严重怀疑这是正确的行为。也就是说,我仍然不是100%确定要相信什么 这个问题;因此,我可以看到“正确”的内容是开放的 一些 解释水平。

我的感觉是Microsoft会接受这是一个错误,或者回答说对一个可变值类型变量的修改 using 声明构成不确定的行为。

另外,就价值而言,我至少有一个 猜测 至于这里发生了什么。我怀疑编译器正在生成一个用于关闭的类,将局部变量“抬高”到该类的实例字段;而且由于它在 using 堵塞, 它正在做这个领域 readonly. 。正如卢克指出的那样 对另一个问题的评论, ,这将防止诸如 MoveNext 通过修改字段本身(它们会影响副本)。


注意:我已经缩短了这个问题以获得可读性,尽管它仍然不足。有关原始问题的完整问题,请参见编辑历史记录。

我已经阅读了我认为是ECMA-334的相关部分,似乎无法找到这个问题的确定答案。我将首先提出问题,然后为有兴趣的人提供一些其他评论的链接。

问题

如果我有一个可变的价值类型 IDisposable, ,我可以(1)调用一种方法,该方法修改了局部变量值的状态 using 声明和代码的行为如我所期望的。一旦我捕获了关闭中所讨论的变量 using 但是,(2)对该值的修改在本地范围中不再可见。

这种行为仅在变量捕获在闭合内部的情况下才是明显的 using 陈述;只有一个using)或另一个条件(封闭)。

为什么在闭合内部捕获可变值类型的变量 using 声明改变其本地行为?

以下是说明项目1和2的代码示例。两个示例将使用以下演示 Mutable 值类型:

struct Mutable : IDisposable
{
    int _value;
    public int Increment()
    {
        return _value++;
    }

    public void Dispose() { }
}

1.在A内突变值类型变量 using 堵塞

using (var x = new Mutable())
{
    Console.WriteLine(x.Increment());
    Console.WriteLine(x.Increment());
}

输出代码输出:

0
1

2.捕获一个闭合内部的值类型变量 using 堵塞

using (var x = new Mutable())
{
    // x is captured inside a closure.
    Func<int> closure = () => x.Increment();

    // Now the Increment method does not appear to affect the value
    // of local variable x.
    Console.WriteLine(x.Increment());
    Console.WriteLine(x.Increment());
}

以上代码输出:

0
0

进一步的评论

已经注意到,单声道编译器提供了我期望的行为(在本地变量的价值的变化中仍然可见 using +关闭案例)。这种行为是否正确,我不清楚。

有关我对这个问题的更多想法,请参阅 这里.

有帮助吗?

解决方案

这是一个已知的错误;几年前,我们发现了它。解决方案可能会破裂,问题非常晦涩。这些是不适合修复它的要点。因此,它从来没有得到足够高的优先级来实际修复它。

这已经在我的潜在博客主题队列中已经有几年了。也许我应该把它写成。

顺便说一下,您对解释该错误的机制的猜想是完全准确的;在那里不错的心理调试。

所以,是的,已知的错误,但感谢您的报告!

其他提示

这与生成和使用闭合类型的方式有关。 CSC使用这些类型的方式似乎有一个微妙的错误。例如,这是单声道GMC生成的IL,当调用moveNext():

      IL_0051:  ldloc.3
      IL_0052:  ldflda valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Foo/'<Main>c__AnonStorey0'::enumerator
      IL_0057:  call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()

请注意,它正在加载字段的地址,该地址允许方法调用可以修改存储在闭合对象上的值类型的实例。这是我认为正确的行为,这导致列表中的内容列举了很好。

这是CSC生成的:

      IL_0068:  ldloc.3
      IL_0069:  ldfld valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Tinker.Form1/'<>c__DisplayClass3'::enumerator
      IL_006e:  stloc.s 5
      IL_0070:  ldloca.s 5
      IL_0072:  call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()

因此,在这种情况下,它将获取值类型实例的副本,并在副本上调用该方法。为什么这无处可去,这不足为奇。 get_current()调用类似:

      IL_0052:  ldloc.3
      IL_0053:  ldfld valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Tinker.Form1/'<>c__DisplayClass3'::enumerator
      IL_0058:  stloc.s 5
      IL_005a:  ldloca.s 5
      IL_005c:  call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
      IL_0061:  call void class [mscorlib]System.Console::WriteLine(int32)

由于枚举的状态已复制,因此没有compeNext()呼叫get_current()显然返回 default(int).

简而言之:CSC似乎是越野车。有趣的是,Mono在Net女士没有的情况下做到了这一点!

...我很想听听乔恩·斯基特(Jon Skeet)对这种特殊奇怪的评论。


在与#Nono的Brajkovic的讨论中,他确定C#语言规范实际上并未详细介绍 如何 应实现封闭类型,也应如何在关闭中捕获的当地人的访问。规格中的实现示例实现似乎使用了CSC使用的“复制”方法。因此,根据语言规范,可以将任一编译器输出视为正确,尽管我认为CSC至少应在方法调用后将本地返回重新返回到闭合对象。

编辑 - 这是不正确的,我没有仔细阅读这个问题。

将结构置于封闭中会导致分配。价值类型的作业导致类型的副本。所以发生的事情是您正在创建一个新的 Enumerator<int>, , 和 Current 在该枚举者上将返回0。

using System;
using System.Collections.Generic;

class Program
{
    static void Main(string[] args)
    {
        List<int> l = new List<int>();
        Console.WriteLine(l.GetEnumerator().Current);
    }
}

结果:0

问题是枚举者存储在另一类中,因此每个动作都与枚举者的副本一起使用。

[CompilerGenerated]
private sealed class <>c__DisplayClass3
{
    // Fields
    public List<int>.Enumerator enumerator;

    // Methods
    public int <Main>b__1()
    {
        return this.enumerator.Current;
    }
}

public static void Main(string[] args)
{
    List<int> <>g__initLocal0 = new List<int>();
    <>g__initLocal0.Add(1);
    <>g__initLocal0.Add(2);
    <>g__initLocal0.Add(3);
    List<int> list = <>g__initLocal0;
    Func<int> CS$<>9__CachedAnonymousMethodDelegate2 = null;
    <>c__DisplayClass3 CS$<>8__locals4 = new <>c__DisplayClass3();
    CS$<>8__locals4.enumerator = list.GetEnumerator();
    try
    {
        if (CS$<>9__CachedAnonymousMethodDelegate2 == null)
        {
            CS$<>9__CachedAnonymousMethodDelegate2 = new Func<int>(CS$<>8__locals4.<Main>b__1);
        }
        while (CS$<>8__locals4.enumerator.MoveNext())
        {
            Console.WriteLine(CS$<>8__locals4.enumerator.Current);
        }
    }
    finally
    {
        CS$<>8__locals4.enumerator.Dispose();
    }
}

没有lambda,代码将更接近您的期望。

public static void Main(string[] args)
{
    List<int> <>g__initLocal0 = new List<int>();
    <>g__initLocal0.Add(1);
    <>g__initLocal0.Add(2);
    <>g__initLocal0.Add(3);
    List<int> list = <>g__initLocal0;
    using (List<int>.Enumerator enumerator = list.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            Console.WriteLine(enumerator.Current);
        }
    }
}

具体IL

L_0058: ldfld valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Machete.Runtime.Environment/<>c__DisplayClass3::enumerator
L_005d: stloc.s CS$0$0001
L_005f: ldloca.s CS$0$0001
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top