2011-10-20 31 views
30

Tôi chỉ tò mò về vấn đề này: đoạn mã sau sẽ không biên dịch, bởi vì chúng ta không thể thay đổi một biến foreach lặp:Tại sao chúng ta không thể gán một biến lặp foreach, trong khi chúng ta hoàn toàn có thể sửa đổi nó với một accessor?

 foreach (var item in MyObjectList) 
     { 
      item = Value; 
     } 

Nhưng sau đây sẽ biên dịch và chạy:

 foreach (var item in MyObjectList) 
     { 
      item.Value = Value; 
     } 

Tại sao đầu tiên không hợp lệ, trong khi thứ hai có thể làm cùng bên dưới (tôi đang tìm kiếm cụm từ tiếng anh chính xác cho điều này, nhưng tôi không nhớ nó. Dưới ...? ^^)

+5

Tôi nghĩ bạn có nghĩa là "dưới mui xe" - nhưng nó không thực sự giống nhau chút nào. –

+0

@ GianT971 Bạn có thể mô tả cách bạn sẽ 'làm tương tự' trong setter của bạn không? 'this = value'? – jv42

+0

@JonSkeet Yess Dưới mui xe ^^. Ok vì vậy mục thực sự là một tham chiếu đến đối tượng, và không phải là đối tượng, tôi đã nhận nó ngay bây giờ – GianT971

Trả lời

27

foreach là một chỉ đọc iterator đó lặp động lớp mà thực hiện IEnumerable, mỗi chu kỳ trong foreach sẽ gọi IEnumerable để có được mục tiếp theo, mục bạn có là một tham chiếu chỉ đọc, bạn không thể gán lại nó, nhưng chỉ cần gọi item.Value đang truy cập nó và gán một số giá trị cho thuộc tính đọc/ghi mà vẫn là tham chiếu của mục đã đọc chỉ tham chiếu.

+2

"_foreach là một trình đọc lặp chỉ lặp lại các lớp động mà thực hiện IEnumerable_" Trong khi chính xác, điều này không đúng.Trình biên dịch chỉ yêu cầu loại mà bạn đang lặp lại để có các phương thức 'T GetEnumerator()', 'bool MoveNext()' và thuộc tính 'T Current' (giả sử bạn muốn tạo bảng liệt kê riêng của mình). Bạn không thực sự cần phải thực hiện 'IEnumerable'. –

24

Thứ hai không làm điều tương tự. Nó không thay đổi giá trị của biến số item - nó thay đổi thuộc tính của đối tượng mà giá trị đó đề cập đến. Hai giá trị này sẽ chỉ chỉ tương đương nếu item là loại giá trị có thể thay đổi - trong trường hợp này, bạn nên thay đổi loại giá trị đó, vì các loại giá trị có thể thay đổi là xấu. (Họ cư xử trong tất cả các loại cách mà các nhà phát triển không thận trọng có thể không mong đợi.)

Nó giống như này:

private readonly StringBuilder builder = new StringBuilder(); 

// Later... 
builder = null; // Not allowed - you can't change the *variable* 

// Allowed - changes the contents of the *object* to which the value 
// of builder refers. 
builder.Append("Foo"); 

Xem article on references and values để biết thêm thông tin của tôi.

2

Vì người đầu tiên không có ý nghĩa nhiều về cơ bản. Mục biến được điều khiển bởi trình vòng lặp (được đặt trên mỗi lần lặp). Bạn không nên cần phải thay đổi nó- chỉ cần sử dụng một biến khác:

foreach (var item in MyObjectList) 
{ 
    var someOtherItem = Value; 

    .... 
} 

Đối với thứ hai, có những trường hợp sử dụng hợp lệ there- bạn có thể muốn để lặp qua một điều tra xe ô tô và gọi .Drive() trên mỗi một hoặc đặt car.gasTank = full;

0

Điểm là bạn không thể sửa đổi bộ sưu tập chính nó trong khi lặp qua nó. Hoàn toàn hợp pháp và phổ biến để sửa đổi các đối tượng mà trình vòng lặp mang lại.

1

Vì hai số không giống nhau. Khi thực hiện thao tác đầu tiên, bạn có thể thay đổi giá trị trong bộ sưu tập. Nhưng cách thức foreach hoạt động, không có cách nào có thể được thực hiện. Nó chỉ có thể truy xuất các mục từ bộ sưu tập, không đặt chúng.

Nhưng một khi bạn có mặt hàng, đó là một đối tượng giống như bất kỳ mục đích nào khác và bạn có thể sửa đổi nó theo bất kỳ cách nào (được phép) mà bạn có thể.

10

Nếu bạn nhìn vào các đặc điểm kỹ thuật ngôn ngữ mà bạn có thể thấy tại sao điều này không hoạt động:

Các thông số kỹ thuật nói rằng một foreach được mở rộng để đoạn mã sau:

E e = ((C)(x)).GetEnumerator(); 
    try { 
     V v; 
     while (e.MoveNext()) { 
     v = (V)(T)e.Current; 
        embedded-statement 
     } 
    } 
    finally { 
     … // Dispose e 
    } 

Như bạn có thể thấy hiện tại phần tử được sử dụng để gọi MoveNext(). Vì vậy, nếu bạn thay đổi phần tử hiện tại, mã sẽ bị 'mất' và không thể lặp qua bộ sưu tập. Vì vậy, việc thay đổi phần tử thành một thứ khác không có ý nghĩa gì nếu bạn thấy mã trình biên dịch thực sự tạo ra.

+2

Khi tôi hiểu 'v' sẽ giữ tham chiếu phần tử hiện tại và' e' giữ tham chiếu vòng lặp. Vì vậy, trình biên dịch chỉ có thể gán bất cứ thứ gì chúng ta muốn o 'v' và' e' sẽ vẫn giữ trình vòng lặp. Cách này cho phép các biến phần tử có thể được gán các tham chiếu mới khi chúng được chuyển đổi thành 'v'variable, chứ không phải' e'. – mFeinstein

10

Bạn không thể sửa đổi bộ sưu tập khi đang được liệt kê. Ví dụ thứ hai chỉ cập nhật một thuộc tính của đối tượng, hoàn toàn khác.

Sử dụng một vòng lặp for nếu bạn cần phải thêm/xóa/sửa đổi các yếu tố trong một bộ sưu tập:

for (int i = 0; i < MyObjectList.Count; i++) 
{ 
    MyObjectList[i] = new MyObject(); 
} 
2

sẽ có thể làm cho item có thể thay đổi. Chúng ta có thể thay đổi cách mã được sản xuất để:

foreach (var item in MyObjectList) 
{ 
    item = Value; 
} 

Trở thành tương đương với:

using(var enumerator = MyObjectList.GetEnumerator()) 
{ 
    while(enumerator.MoveNext()) 
    { 
    var item = enumerator.Current; 
    item = Value; 
    } 
} 

Và sau đó nó sẽ biên dịch. Tuy nhiên nó sẽ không ảnh hưởng đến bộ sưu tập.

Và có chà xát. Mã:

foreach (var item in MyObjectList) 
{ 
    item = Value; 
} 

Có hai cách hợp lý để con người suy nghĩ về điều đó. Một là item chỉ là một địa chủ sở hữu và thay đổi nó không khác nhau để thay đổi item trong:

for(int item = 0; item < 100; item++) 
    item *= 2; //perfectly valid 

khác là rằng việc thay đổi mục thực sự sẽ thay đổi bộ sưu tập.

Trong trường hợp trước đây, chúng tôi chỉ có thể gán mục cho một biến khác, và sau đó chơi với biến đó, vì vậy không mất mát. Trong trường hợp sau, cả hai trường hợp này đều bị cấm (hoặc ít nhất, bạn không thể mong đợi để thay đổi bộ sưu tập trong khi lặp lại nó, mặc dù nó không được thực thi bởi tất cả điều tra viên) và trong nhiều trường hợp không thể cung cấp (tùy thuộc vào bản chất của số đếm). Ngay cả khi chúng tôi coi trường hợp trước là thực hiện "chính xác", thực tế là nó có thể được giải thích hợp lý bởi con người theo hai cách khác nhau là một lý do đủ tốt để tránh cho phép nó, đặc biệt là xem xét chúng ta có thể dễ dàng làm việc xung quanh trong mọi trường hợp.

1

Sử dụng vòng lặp thay vì vòng lặp foreach và giá trị gán. nó sẽ hoạt động

foreach (var a in FixValue) 
{ 
    for (int i = 0; i < str.Count(); i++) 
    { 
     if (a.Value.Contains(str[i])) 
      str[i] = a.Key; 
    } 
} 
Các vấn đề liên quan