2015-09-17 10 views
12

Trong C# 5, ngữ nghĩa đóng của câu lệnh foreach (khi biến lặp được "bắt" hoặc "đóng cửa" bởi các hàm ẩn danh) là famously changed (link to thread on that topic).Ngữ nghĩa đóng cửa để tìm kiếm trên các mảng kiểu con trỏ

Câu hỏi: Có ý định thay đổi điều này cho các mảng kiểu con trỏ không?

Lý do tại sao tôi hỏi là "bành trướng" của một tuyên bố foreach phải được viết lại, vì lý do kỹ thuật (chúng tôi không thể sử dụng Current tài sản của System.Collections.IEnumerator từ khách sạn này đã tuyên bố loại object mà không tương thích với một con trỏ loại) so với foreach so với các bộ sưu tập khác. Các phần có liên quan trong C# Language Specification, "Pointer mảng", trong phiên bản 5,0, nói rằng:

foreach (V v in x) EMBEDDED-STATEMENT 

được mở rộng để:

{ 
    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 
    } 
} 

Chúng tôi lưu ý rằng việc kê khai V v; là bên ngoài tất cả các vòng for. Vì vậy, nó sẽ xuất hiện rằng ngữ nghĩa đóng cửa vẫn là "cũ" hương vị C# 4, "biến vòng lặp được tái sử dụng, biến vòng lặp là" bên ngoài "đối với vòng lặp".

Để làm cho nó rõ ràng những gì tôi đang nói về, hãy xem xét hoàn thành chương trình này 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. 
} 

Vì vậy, đầu ra chính xác của chương trình trên là gì?

+0

Chú ý: Việc mở rộng trên có một vấn đề rõ ràng ở chỗ nó viết 'a.GetValue (i0, i1, ..., iN) 'nơi' GetValue' dường như được phương pháp này được xác định bởi' hệ thống .Array'. Nhưng phương thức đó có giá trị trả về 'đối tượng', vì vậy nó không thể được sử dụng cho các kiểu con trỏ. Vì vậy, thông số C# không thành công trong việc tránh _ "bất kỳ nỗ lực nào để truy cập các phần tử mảng thông qua' System.Array' "_, để trích dẫn chính C# spec. Có lẽ đó phải là 'a [i0, i1,…, iN]' trong đó khung '[…]' được định nghĩa bởi phần con _ "Truy cập phần tử mảng" _. Hãy thử nói 'arrayOfPointers.GetValue (0)' cho mình, trong mẫu mã trên. –

Trả lời

11

Tôi đã liên lạc với Mads Torgersen, C# Language PM, và có vẻ như họ chỉ đơn giản là quên cập nhật phần này của đặc điểm kỹ thuật. His exact answer là (Tôi đã hỏi lý do thông số không được cập nhật):

vì tôi đã quên! :-) Tôi hiện đã có bản nháp mới nhất và gửi cho ECMA. Cảm ơn!

Vì vậy, dường như hành vi của C# -5 giống hệt với mảng con trỏ, và đó là lý do tại sao bạn thấy đầu ra đầu tiên, đó là kết quả chính xác.

+0

Có lẽ anh ấy sẽ khắc phục vấn đề với 'GetValue' (xem bình luận của tôi ngay bên dưới câu hỏi của tôi ở trên)? –

4

Tôi cho rằng đặc điểm kỹ thuật đó không được cập nhật trong phần này (về mảng con trỏ) để phản ánh biến V đó cũng đi vào phạm vi bên trong. Nếu biên dịch ví dụ của bạn với trình biên dịch C# 5 và nhìn vào đầu ra - nó sẽ trông giống như trong đặc tả (với truy cập mảng thay vì GetValue khi bạn trỏ chính xác vào bình luận của bạn), ngoại trừ biến V sẽ ở bên trong tất cả các vòng lặp. Và đầu ra sẽ là 00-02-04-C8, nhưng tất nhiên bạn biết tất cả những điều đó cho mình :)

Câu chuyện ngắn - tất nhiên tôi không thể biết đó có ý định hay không, nhưng tôi đoán là nó được dự định để di chuyển biến sang phạm vi bên trong cho tất cả các vòng lặp foreach, bao gồm các mảng con trỏ và đặc điểm kỹ thuật không được cập nhật để phản ánh điều đó.

4

Mã sau đây là biên dịch (C# 5.0) đến trao đang IL(Cảm nhận trong mã):

.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 
} 

Như bạn thấy, một lớp được tạo ra trong thời gian biên dịch, gọi <>c__DisplayClass0_0 chứa Action của bạn và một giá trị char*. Lớp trông như thế:

[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)); 
    } 
} 

Trong mã MSIL chúng ta có thể thấy rằng foreach được biên dịch như sau vòng lặp for:

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 
} 

gì nó có nghĩa là nó mà sự giá trị của con trỏ thực sự được sao chép khi vòng lặp được lặp lại và các đại biểu được tạo, vì vậy giá trị của con trỏ tại thời điểm đó là bản in sẽ được in (a.k.a: mỗi hành động là từ trường hợp riêng của nó là <>c__DisplayClass0_0 và sẽ nhận được con trỏ được nhân bản tạm thời của nó).

Như chúng ta vừa thấy, "reused variable" là trước bản thân, có nghĩa là các con trỏ được tham chiếu không được sử dụng lại, đề nghị đầu ra phải là 00 00 00 00. Và kết quả:

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

Bài đăng này chứa rất nhiều chi tiết triển khai (tôi giả sử từ một số triển khai C# phiên bản 5 hoặc 6?), Nhưng nó không thực sự giải quyết câu hỏi tôi quan tâm: Liệu đặc tả ngôn ngữ C# có yêu cầu đầu ra là 'C8 C8 C8 không C8', hay đặc tả C# yêu cầu đầu ra là '00 02 04 C8', hay không? –

+0

Thay đổi kết thúc câu trả lời với tham chiếu đến đặc điểm kỹ thuật được yêu cầu. –

Các vấn đề liên quan