20

更新:まあ、今私は行ってそれをしました:私はそれが正しい振る舞いであると真剣に疑っているので、これについてマイクロソフトにバグレポートを提出しました。とはいえ、この質問に関して何を信じるべきかはまだ100%わかりません。ですから、「正しい」ものはある程度の解釈に開かれていることがわかります。

私の考えでは、Microsoftはこれがバグであることを受け入れるか、usingステートメント内の可変値型変数の変更が未定義の動作を構成すると応答します。

また、その価値については、少なくともここで何が起こっているのかについては推測できます。コンパイラがクロージャのクラスを生成し、ローカル変数をそのクラスのインスタンスフィールドに「リフト」しているのではないかと思います。usingブロック内にあるので、フィールドを作成していますreadonlyLukeHが他の質問へのコメントで指摘したように、これにより、フィールド自体の変更などのメソッド呼び出しが防止さMoveNextれます(代わりにコピーに影響します)。


注:読みやすさのためにこの質問を短くしましたが、まだ正確に短いわけではありません。元の(長い)質問全体については、編集履歴を参照してください。

ECMA-334の関連セクションであると私が信じていることを読み通しましたが、この質問に対する決定的な答えを見つけることができないようです。私は最初に質問を述べ、次に興味のある人のためにいくつかの追加のコメントへのリンクを提供します。

質問

を実装する可変値型がある場合は、IDisposable(1)usingステートメント内のローカル変数の値の状態を変更するメソッドを呼び出すことができ、コードは期待どおりに動作します。ただし、ステートメント内のクロージャusingで問題の変数をキャプチャすると、(2)値への変更はローカルスコープに表示されなくなります。

この動作は、変数がクロージャー内およびusingステートメント内でキャプチャされた場合にのみ明らかになります。using1つ( )または他の条件(閉鎖)のみが存在する場合は明らかではありません。

ステートメント内のクロージャー内で可変値型の変数をキャプチャすると、usingそのローカル動作が変わるのはなぜですか?

以下は、項目1と2を示すコード例です。どちらの例も、次のデモンストレーションMutable値タイプを利用します。

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

    public void Dispose() { }
}

1.usingブロック内の値型変数の変更

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

出力コードは以下を出力します:

0
1

using2.ブロック内のクロージャ内の値型変数をキャプチャする

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

さらなるコメント

Monoコンパイラは、私が期待する動作を提供することに注意してください(ローカル変数の値への変更は、using+クロージャの場合でも表示されます)。この振る舞いが正しいかどうかは私にはわかりません。

この問題に関する私の考えの詳細については、こちらを参照してください。

4

4 に答える 4

11

これは、クロージャータイプが生成および使用される方法と関係があります。cscがこれらのタイプを使用する方法には、微妙なバグがあるようです。たとえば、MoveNext()を呼び出すときにMonoのgmcsによって生成されるILは次のとおりです。

      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)

コピーしている列挙子の状態ではMoveNext()が呼び出されていないため、get_Current()は明らかに。を返しますdefault(int)

つまり、cscはバグがあるようです。MS.NETが正しくなかったのに、Monoがこれを正しく行ったのは興味深いことです。

...この特定の奇妙さについてのジョン・スキートのコメントを聞きたいです。


#monoでのbrajkovicとの議論で、彼は、C#言語仕様では、クロージャタイプを実装する方法や、クロージャにキャプチャされたローカルのアクセスを変換する方法について実際に詳しく説明していないと判断しました。仕様の実装例では、cscが使用する「コピー」メソッドを使用しているようです。したがって、どちらのコンパイラ出力も言語仕様に従って正しいと見なすことができますが、cscは、メソッド呼び出し後に少なくともローカルをクロージャオブジェクトにコピーして戻す必要があると主張します。

于 2011-01-10T00:11:11.323 に答える
7

これは既知のバグです。数年前に発見しました。修正は潜在的に壊れている可能性があり、問題はかなりあいまいです。これらはそれを修正することに対するポイントです。したがって、実際に修正するのに十分な優先順位が付けられたことはありません。

これは、数年前からブログの潜在的なトピックのキューに入っています。多分私はそれを書くべきです。

ちなみに、バグを説明するメカニズムに関するあなたの推測は完全に正確です。そこに素晴らしい超能力者のデバッグ。

ですから、はい、既知のバグですが、それでもレポートに感謝します!

于 2011-01-14T06:56:20.143 に答える
0

編集-これは正しくありません、私は質問を十分に注意深く読んでいませんでした。

構造体をクロージャに配置すると、割り当てが発生します。値型の割り当ては、型のコピーになります。つまり、何が起こっているのかというと、新しいを作成している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

于 2011-01-10T00:12:30.480 に答える
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();
    }
}

ラムダがないと、コードは期待するものに近くなります。

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
于 2011-01-10T00:15:46.543 に答える