2015-09-17 5 views
12

C#5では、foreachステートメントのクロージャセマンティクス(匿名関数によって反復変数が "キャプチャ"または "クローズオーバー"されたとき)はfamously changed (link to thread on that topic)でした。ポインタ型のforeachオーバーレイのクロージャセマンティクス

質問:ポインタ型の配列に対してもこれを変更するつもりでしたか?

私たちは、このプロパティは、ポインタと互換性のないタイプobject宣言しているので、System.Collections.IEnumeratorCurrentプロパティを使用することはできません(foreach文の「拡大」は技術的な理由のために、書き直さなければならないことである尋ねる理由他のコレクションに比べてforeachと比較して、私たちは、宣言V v;があることに注意して

{ 
    T[,,…,] a = x; 
    V v; 
    for (int i0 = a.GetLowerBound(0); i0 <= a.GetUpperBound(0); i0++) 
    for (int i1 = a.GetLowerBound(1); i1 <= a.GetUpperBound(1); i1++) 
    … 
    for (int in = a.GetLowerBound(N); iN <= a.GetUpperBound(n); iN++) { 
    v = (V)a.GetValue(i0,i1,…,iN); 
    EMBEDDED-STATEMENT 
    } 
} 

foreach (V v in x) EMBEDDED-STATEMENT 

がに展開されます。バージョンではC#言語仕様の該当するセクション、"ポインタ配列"は、5.0、と述べていますforループの外側にあります。したがって、クロージャのセマンティクスは "古い" C#4のフレーバーであると思われます。 "ループ変数は再利用され、ループ変数はループに対して"外側 "です。

、私が何を言っているか、それを明確に、この完全なC#5のプログラムを検討する:

using System; 
using System.Collections.Generic; 

static class Program 
{ 
    unsafe static void Main() 
    { 
    char* zeroCharPointer = null; 
    char*[] arrayOfPointers = 
     { zeroCharPointer, zeroCharPointer + 1, zeroCharPointer + 2, zeroCharPointer + 100, }; 

    var list = new List<Action>(); 

    // foreach through pointer array, capture each foreach variable 'pointer' in a lambda 
    foreach (var pointer in arrayOfPointers) 
     list.Add(() => Console.WriteLine("Pointer address is {0:X2}.", (long)pointer)); 

    Console.WriteLine("List complete"); 
    // invoke those delegates 
    foreach (var act in list) 
     act(); 
    } 

    // Possible output: 
    // 
    // List complete 
    // Pointer address is 00. 
    // Pointer address is 02. 
    // Pointer address is 04. 
    // Pointer address is C8. 
    // 
    // Or: 
    // 
    // List complete 
    // Pointer address is C8. 
    // Pointer address is C8. 
    // Pointer address is C8. 
    // Pointer address is C8. 
} 

したがって、上記のプログラムの正しい出力は何ですか?

+0

備考:上記の拡張は、それは 'a.GetValue(I0、I1、...、IN)が' 'どこGetValue'は、システムの'によって定義された方法であると思わ書き込むには、別の明らかな問題があります.Array'。しかし、そのメソッドは戻り値 'object'を持っているので、ポインタ型には使用できません。したがって、C#仕様は、 "System.Array" _を介して配列要素にアクセスしようとすると、C#仕様自体を引用することはできませんでした。おそらく 'a [i0、i1、...、iN]'だったはずです。ここで、ブラケット '[...]'はサブセクション_ "配列要素アクセス" _によって定義されています。上記のコードサンプルでは、​​ 'arrayOfPointers.GetValue(0)'と言ってみてください。 –

答えて

11

私はMads Torgersen(C#言語PM)に連絡しましたが、仕様のこの部分を更新するのを忘れてしまったようです。 His exact answer(私は仕様が更新されなかった理由を尋ねました):

私は忘れてしまったので! :-)私は今、最新ドラフトでECMAに提出しています。ありがとう!

したがって、C#-5の動作はポインタ配列でも同じであると思われます。そのため、正しい出力である最初の出力が表示されます。

+0

たぶん彼はこの問題を 'GetValue'でも解決するでしょう(私の上記の質問のすぐ下の私のコメントを見てください)? –

4

V変数が内部スコープにも反映されるように、この部分(ポインタ配列について)で仕様が更新されていないとします。 C#5コンパイラでサンプルをコンパイルして出力を見ると、V変数がすべてのforループ内にある点を除いて、仕様(GetValueの代わりに配列にアクセスする)のようになります。そして、出力は00-02-04-C8になりますが、もちろん自分自身でそれを知っているでしょう:)

長い話は短いです。もちろん、その意図かどうかはわかりませんが、ポインタ配列を含むすべてのforeachループの内部スコープに変数を移動するために、仕様はそれを反映するように更新されていませんでした。

4

次のコードはコンパイルされています(C#5。コード0)与えられたILコードに(コメント)クラスはコンパイル時で生成され

.method private hidebysig static void Main() cil managed 
{ 
    .entrypoint 
    .maxstack 6 
    .locals init (
     [0] char* chPtr, 
     [1] char*[] chPtrArray, 
     [2] class [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action> list, 
     [3] char*[] chPtrArray2, 
     [4] int32 num, 
     [5] class ConsoleTests.Program/<>c__DisplayClass0_0 class_, 
     [6] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator`0<class [mscorlib]System.Action> enumerator, 
     [7] class [mscorlib]System.Action action) 
    L_0000: nop 
    L_0001: ldc.i4.0 //{{{{{ 
    L_0002: conv.u //chPtr = null; 
    L_0003: stloc.0 //}}}}} 
    L_0004: ldc.i4.4 //{{{{{ 
    L_0005: newarr char* //Creates a new char*[4]}}}}} 
    L_000a: dup //{{{{{ 
    L_000b: ldc.i4.0 // Sets the first element in the new 
    L_000c: ldloc.0 // char*[] to chPtr. 
    L_000d: stelem.i //}}}}} 
    L_000e: dup //{{{{{ 
    L_000f: ldc.i4.1 // 
    L_0010: ldloc.0 // Sets the second element of the 
    L_0011: ldc.i4.2 // char*[] to chPtr + 1 
    L_0012: add // (loads 2 instead of 1 because char is UTF-16) 
    L_0013: stelem.i //}}}}} 
    L_0014: dup //{{{{{ 
    L_0015: ldc.i4.2 // 
    L_0016: ldloc.0 // 
    L_0017: ldc.i4.2 // Sets the third element of the 
    L_0018: conv.i // char*[] to chPtr + 2 
    L_0019: ldc.i4.2 // (loads 4 instead of 2 because char is UTF-16) 
    L_001a: mul // 
    L_001b: add // 
    L_001c: stelem.i //}}}}} 
    L_001d: dup //{{{{{ 
    L_001e: ldc.i4.3 // 
    L_001f: ldloc.0 // 
    L_0020: ldc.i4.s 100 // Sets the third element of the 
    L_0022: conv.i // char*[] to chPtr + 100 
    L_0023: ldc.i4.2 // (loads 200 instead of 100 because char is UTF-16) 
    L_0024: mul // 
    L_0025: add // 
    L_0026: stelem.i // }}}}} 
    L_0027: stloc.1 // chPtrArray = the new array that we have just filled. 
    L_0028: newobj instance void [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action>::.ctor() //{{{{{ 
    L_002d: stloc.2 // list = new List<Action>() 
    L_002e: nop //}}}}} 
    L_002f: ldloc.1 //{{{{{ 
    L_0030: stloc.3 //chPtrArray2 = chPtrArray}}}}} 
    L_0031: ldc.i4.0 //for (int num = 0; num < 3; num++) 
    L_0032: stloc.s num // 
    L_0034: br.s L_0062 //<<<<< (for start) 
    L_0036: newobj instance void ConsoleTests.Program/<>c__DisplayClass0_0::.ctor() //{{{{{ 
    L_003b: stloc.s class_ //class_ = new temporary compile-time class 
    L_003d: ldloc.s class_ //}}}}} 
    L_003f: ldloc.3 //{{{{{ 
    L_0040: ldloc.s num // 
    L_0042: ldelem.i // 
    L_0043: stfld char* ConsoleTests.Program/<>c__DisplayClass0_0::pointer //class_.pointer = chPtrArray2[num]}}}}} 
    L_0048: ldloc.2 //{{{{{ 
    L_0049: ldloc.s class_ // 
    L_004b: ldftn instance void ConsoleTests.Program/<>c__DisplayClass0_0::<Main>b__0() // list.Add(class_.<Main>b__0); 
    L_0051: newobj instance void [mscorlib]System.Action::.ctor(object, native int) // (Adds the temporary compile-time class action, which has the correct pointer since 
    L_0056: callvirt instance void [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action>::Add(!0) //it is a specific class instace for this iteration, to the list)}}}}} 
    L_005b: nop 
    L_005c: ldloc.s num //practically the end of the for 
    L_005e: ldc.i4.1 // (actually increasing num and comparing) 
    L_005f: add // 
    L_0060: stloc.s num // 
    L_0062: ldloc.s num // 
    L_0064: ldloc.3 // 
    L_0065: ldlen // 
    L_0066: conv.i4 // 
    L_0067: blt.s L_0036 //>>>>> (for complete) 
    L_0069: ldstr "List complete" //Printing and stuff..... 
    L_006e: call void [mscorlib]System.Console::WriteLine(string) 
    L_0073: nop 
    L_0074: nop 
    L_0075: ldloc.2 
    L_0076: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator`0<!0> [mscorlib]System.Collections.Generic.List`1<class [mscorlib]System.Action>::GetEnumerator() 
    L_007b: stloc.s enumerator 
    L_007d: br.s L_0090 
    L_007f: ldloca.s enumerator 
    L_0081: call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator`0<class [mscorlib]System.Action>::get_Current() 
    L_0086: stloc.s action 
    L_0088: ldloc.s action 
    L_008a: callvirt instance void [mscorlib]System.Action::Invoke() 
    L_008f: nop 
    L_0090: ldloca.s enumerator 
    L_0092: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator`0<class [mscorlib]System.Action>::MoveNext() 
    L_0097: brtrue.s L_007f 
    L_0099: leave.s L_00aa 
    L_009b: ldloca.s enumerator 
    L_009d: constrained. [mscorlib]System.Collections.Generic.List`1/Enumerator`0<class [mscorlib]System.Action> 
    L_00a3: callvirt instance void [mscorlib]System.IDisposable::Dispose() 
    L_00a8: nop 
    L_00a9: endfinally 
    L_00aa: ret 
    .try L_007d to L_009b finally handler L_009b to L_00aa 
} 

あなたが見ることができるように、あなたのActionと含まれてい<>c__DisplayClass0_0と呼ばれますchar*の値。我々はforeachループのため、以下のようにコンパイルされていることがわかりますMSILコードで

[CompilerGenerated] private sealed class <>c__DisplayClass0_0 { // Fields public unsafe char* pointer; // Methods internal unsafe void <Main>b__0() { Console.WriteLine("Pointer address is {0:X2}.", (long) ((ulong) this.pointer)); } } 

:クラスはそのように見えます

shallowCloneOfArray = arrayOfPointers; 
for (int num = 0; num < arrayOfPointers.Length; num++) 
{ 
    <>c__DisplayClass0_0 temp = new <>c__DisplayClass0_0(); 
    temp.pointer = shallowCloneOfArray[num]; 
    list.Add(temp.<Main>b__0); //Adds the action to the list of actions 
} 

それはそれを意味することループの反復とデリゲートの作成時にポインタの値が実際ににコピーされるため、その時点のポインタの値は印刷されるものです(a.k.a:各アクションは<>c__DisplayClass0_0という独自のインスタンスからのもので、一時的に複製されたポインタを受け取ります)。

我々だけで見たように、foreach前から"reused variable"が参照ポインタがあなたは、彼らはあなたがattched仕様以来、間違っているよりも、言っているような仕様がある場合ことを意味して再利用されていないことを意味配列そのものであり、出力が00 00 00 00であることを示唆します。その結果:

List complete 
Pointer address is 00. 
Pointer address is 02. 
Pointer address is 04. 
Pointer address is C8. 
+0

この投稿には非常に多くの実装の詳細が含まれています(私はC#のバージョン5または6の実装から推測していますか?)しかし、それは本当に私が関心のある質問には触れません:C#言語仕様は出力を 'C8 C8 C8 C8'か、またはC#仕様では出力が '00 02 04 C8'か、どちらも出力されていないのでしょうか? –

+0

質問の仕様を参考に答えの終わりを変更してください。 –

関連する問題