2009-04-22 21 views
33

Như tôi đã hiểu, biến lặp lặp lại của C# là không thay đổi.Tại sao Biến Iteration trong câu lệnh C# foreach chỉ đọc?

Có nghĩa là tôi không thể sửa đổi iterator như thế này:

foreach (Position Location in Map) 
{ 
    //We want to fudge the position to hide the exact coordinates 
    Location = Location + Random();  //Compiler Error 

    Plot(Location); 
} 

tôi không thể thay đổi biến iterator trực tiếp và thay vào đó, tôi phải sử dụng một vòng lặp for

for (int i = 0; i < Map.Count; i++) 
{ 
    Position Location = Map[i]; 
    Location = Location + Random(); 

    Plot(Location);   
    i = Location; 
} 

Coming từ một nền C++, tôi thấy foreach như là một thay thế cho vòng lặp for. Nhưng với các hạn chế trên, tôi thường dự phòng sử dụng vòng lặp for.

Tôi tò mò, lý do đằng sau việc làm cho trình lặp không thay đổi là gì?


Edit:

Câu hỏi này là chi tiết của một câu hỏi tò mò và không phải là một câu hỏi mã hóa. Tôi đánh giá cao các câu trả lời mã hóa nhưng tôi không thể đánh dấu chúng là câu trả lời.

Ngoài ra, ví dụ ở trên đã được đơn giản hóa quá mức. Dưới đây là ví dụ về C++ về những gì tôi muốn thực hiện:

// The game's rules: 
// - The "Laser Of Death (tm)" moves around the game board from the 
//  start area (index 0) until the end area (index BoardSize) 
// - If the Laser hits a teleporter, destroy that teleporter on the 
//  board and move the Laser to the square where the teleporter 
//  points to 
// - If the Laser hits a player, deal 15 damage and stop the laser. 

for (int i = 0; i < BoardSize; i++) 
{ 
    if (GetItem(Board[i]) == Teleporter) 
    { 
     TeleportSquare = GetTeleportSquare(Board[i]); 
     SetItem(Board[i], FreeSpace); 
     i = TeleportSquare; 
    } 

    if (GetItem(Board[i]) == Player) 
    { 
     Player.Life -= 15; 
     break; 
    } 
} 

Tôi không thể thực hiện điều này trong lý do C# vì không lặp lại được. Tôi nghĩ (sửa tôi nếu tôi sai), điều này đặc trưng cho thiết kế của foreach bằng ngôn ngữ.

Tôi quan tâm đến lý do tại sao trình vòng lặp foreach là không thay đổi.

+1

Tìm tốt. Tôi ngạc nhiên là tôi chưa bao giờ để ý điều đó trước đây. –

+2

Bạn cũng không thực sự thay đổi phần tử mảng trong ví dụ thứ hai của mình. –

+0

@ Jakob, tôi không thực sự quan tâm đến việc thay đổi phần tử mảng. Tôi quan tâm nhiều hơn đến lý do tại sao trình vòng lặp là bất biến. Tôi đoán tôi cần chỉnh sửa câu hỏi để làm rõ câu hỏi đó. – MrValdez

Trả lời

24

Hãy bắt đầu với một ví dụ ngớ ngẩn nhưng minh họa:

Object o = 15; 
o = "apples"; 

Tại không có điểm nào chúng ta có ấn tượng rằng chúng tôi vừa bước sang số 15 thành một chuỗi các táo. Chúng tôi biết rằng o chỉ đơn giản là một con trỏ. Bây giờ, hãy làm điều này trong biểu mẫu trình lặp.

int[] nums = { 15, 16, 17 }; 

foreach (Object o in nums) { 
    o = "apples"; 
} 

Một lần nữa, điều này thực sự không thành công. Hoặc ít nhất là sẽ hoàn thành không có gì để biên dịch. Nó chắc chắn sẽ không chèn chuỗi của chúng tôi vào mảng int - đó là không được phép, và chúng tôi biết rằng o chỉ là một con trỏ anyway.

Hãy lấy ví dụ của bạn:

foreach (Position Location in Map) 
{ 
    //We want to fudge the position to hide the exact coordinates 
    Location = Location + Random();  //Compiler Error 

    Plot(Location); 
} 

Were này để biên dịch, các Location trong ví dụ của bạn sao ra đề cập đến một giá trị trong Map, nhưng sau đó bạn thay đổi nó để chỉ một mới Position (ngầm được tạo ra bởi toán tử cộng thêm). Chức năng tương đương với điều này (mà DOES biên dịch):

foreach (Position Location in Map) 
{ 
    //We want to fudge the position to hide the exact coordinates 
    Position Location2 = Location + Random();  //No more Error 

    Plot(Location2); 
} 

Vì vậy, tại sao Microsoft cấm bạn gán lại con trỏ được sử dụng để lặp lại? Rõ ràng cho một điều - bạn không muốn mọi người gán cho nó nghĩ rằng họ đã thay đổi vị trí của bạn trong vòng lặp. Dễ thực hiện cho người khác: Biến có thể ẩn một số logic nội bộ cho biết trạng thái của vòng lặp đang được tiến hành.

Nhưng quan trọng hơn, không có lý do gì để bạn muốn gán cho nó. Nó đại diện cho phần tử hiện tại của chuỗi lặp. Chỉ định một giá trị cho nó sẽ phá vỡ "Nguyên tắc trách nhiệm duy nhất" hoặc Curly's Law nếu bạn theo dõi Horror Horror. Một biến chỉ có nghĩa là một điều duy nhất.

+3

Tyler, tôi có thể sai nhưng tôi không đồng ý với xác nhận của bạn rằng "Vị trí đăng bài" hoặc "Đối tượng O" là các trình lặp. Thay vào đó, chúng là các biến lặp có các trình vòng lặp hoạt động trên chúng. Nếu đó thực sự là trường hợp, sau đó tôi nghĩ rằng lý do của bạn có thể là một chút sai lệch. Tôi đồng ý rằng việc thay đổi giá trị của biến lặp có thể gây ra các vấn đề rõ ràng nhưng tôi muốn Microsoft đã để điều đó cho nhà phát triển quyết định. Tôi đã tìm thấy một số trường hợp mà nó sẽ có ích để thay đổi giá trị của một biến lặp foreach. –

+0

@AnthonyGatlin Tôi không đồng ý rằng họ nên để nhà phát triển quyết định trong trường hợp này. Có một chi phí rất thực sự để gây nhầm lẫn mã, và Microsoft có đủ người mã hóa trong C# rằng họ có một động lực rất thực tế để tạo ra một ngôn ngữ, càng nhiều càng tốt, ngăn chặn các nhà phát triển từ việc tạo ra lỗi. – tylerl

+0

"* Biến chỉ có nghĩa là một điều duy nhất *". Khái niệm tốt. Tuy nhiên, trong đầu của tôi, nó sẽ tốt hơn nếu nó đơn giản có nghĩa là "* phần tử hiện tại *" thay vì "* tham chiếu *" hoặc "* con trỏ *".Hoặc, trong ví dụ của op 'Map [i]', đó là chính xác những gì chúng ta mong đợi bằng trực giác. – cregox

12

Nếu biến có thể thay đổi, điều đó có thể tạo ra một hiển thị không chính xác. Ví dụ:

string[] names = { "Jon", "Holly", "Tom", "Robin", "William" }; 

foreach (string name in names) 
{ 
    name = name + " Skeet"; 
} 

Một số người có thể nghĩ rằng sẽ thay đổi nội dung mảng. Nó đạt đến một chút, nhưng nó có thể là một lý do. Tôi sẽ tra cứu nó trong đặc tả chú thích của tôi tối nay ...

+1

Đó sẽ là điểm Jon đang cố gắng minh họa cho tôi đoán. Chú ý "Nếu". –

+1

Brian, là nhận xét của bạn để trả lời cho một nhận xét khác hiện đã bị xóa? –

+3

Bạn đã bao giờ nhìn vào thông số kỹ thuật của mình chưa? Tôi cho rằng các nhà thiết kế đã tạo ra giá trị chỉ đọc cho rõ ràng như bạn đề xuất, nhưng thật thú vị khi biết liệu có những cân nhắc khác không! –

2

Tuyên bố foreach hoạt động với Enumerable. Điều khác biệt với một Enumerable là nó không biết về bộ sưu tập như một toàn thể, nó gần như mù.

Điều này có nghĩa là nó không biết điều gì sẽ xảy ra tiếp theo, hoặc thực sự nếu có bất kỳ điều gì cho đến chu kỳ lặp lại tiếp theo. Đó là lý do tại sao bạn thấy .ToList() rất nhiều để mọi người có thể lấy một số của Enumerable.

Vì một lý do nào đó mà tôi không chắc chắn, điều này có nghĩa là bạn cần đảm bảo bộ sưu tập của mình không thay đổi khi bạn cố gắng di chuyển điều tra viên.

+2

Nhưng thay đổi * biến * sẽ không thay đổi điều tra. Nó chỉ là một bản sao cục bộ của giá trị hiện tại. –

+1

Chắc chắn phụ thuộc vào loại tham chiếu hoặc loại giá trị của nó? Nhưng vâng, tôi hiểu bạn đang nhìn vào giá trị, và có lẽ không điều chỉnh bản thân điều tra ... hmm – Ian

+0

Chắc chắn, đây không phải là lý do "khó" để không cho phép sửa đổi biến lặp. Trình biên dịch đã được mã hóa cứng để tìm kiếm 'IEnumerable'/'IEnumerable ' để quyết định có hay không cho phép vòng lặp 'foreach' ở vị trí đầu tiên. * Nếu * các nhà thiết kế ngôn ngữ đã chọn để cho phép sửa đổi biến lặp, trình biên dịch có thể đã được thực hiện để chấp nhận các sửa đổi đó trong trường hợp vòng lặp 'foreach' được sử dụng để lặp qua một' IList'/'IList '. –

2

Khi bạn sửa đổi bộ sưu tập, sửa đổi có thể có tác dụng phụ không thể đoán trước. Điều tra viên không có cách nào để biết cách đối phó đúng với những tác dụng phụ này, vì vậy họ đã làm cho các bộ sưu tập không thay đổi được.

cũng Xem câu hỏi này: What is the best way to modify a list in a foreach

+0

Bạn có thể đặt tên ít nhất một ví dụ cụ thể về tác dụng phụ như vậy mà điều tra viên sẽ phải xử lý (điều đó không thể xảy ra khi biến lặp không thay đổi)? Nếu không, câu trả lời này nghe có vẻ rất mơ hồ. –

1

Điều kiện để làm việc với các đối tượng IEnumerable là bộ sưu tập cơ bản không được thay đổi trong khi bạn đang truy cập nó với Enumerable. Bạn có thể giả sử rằng đối tượng Enumerable là một ảnh chụp nhanh của bộ sưu tập gốc.Vì vậy, nếu bạn cố gắng thay đổi bộ sưu tập trong khi liệt kê nó sẽ ném một ngoại lệ. Tuy nhiên, các đối tượng được tìm nạp trong Liệt kê không phải là bất biến.

Vì biến được sử dụng trong vòng lặp foreach là cục bộ đối với khối vòng lặp, biến này không có sẵn bên ngoài khối.

10

Tôi nghĩ giới hạn nhân tạo của nó, không thực sự cần phải làm điều đó. Để chứng minh quan điểm, hãy đưa mã này vào xem xét và giải pháp có thể cho vấn đề của bạn. Vấn đề là để asign, nhưng thay đổi nội bộ của các đối tượng không trình bày một vấn đề:

using System; 
using System.Collections.Generic; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 

      List<MyObject> colection =new List<MyObject>{ new MyObject{ id=1, Name="obj1" }, 
                new MyObject{ id=2, Name="obj2"} }; 

      foreach (MyObject b in colection) 
      { 
      // b += 3;  //Doesn't work 
       b.Add(3); //Works 
      } 
     } 

     class MyObject 
     { 
      public static MyObject operator +(MyObject b1, int b2) 
      { 
       return new MyObject { id = b1.id + b2, Name = b1.Name }; 
      } 

      public void Add(int b2) 
      { 
       this.id += b2; 
      } 
      public string Name; 
      public int id; 
     } 
    } 
} 

Tôi không biết về hiện tượng này, bởi vì tôi luôn luôn sửa đổi các đối tượng theo cách tôi mô tả.

+2

+1 cho công việc phong nha. – ashes999

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