2010-02-11 29 views
6

Lấy đoạn mã sau, Resharper cho tôi biết rằng voicesSoFarvoicesNeededMaximum làm cho "quyền truy cập vào một đóng cửa đã sửa đổi". Tôi đọc về những điều này nhưng những gì tôi câu đố ở đây là Resharper cho thấy sửa lỗi này bằng cách giải nén các biến ngay trước truy vấn LINQ. Nhưng đó là nơi họ đã có!Mã này có thực sự gây ra sự cố "truy cập vào đóng cửa đã sửa đổi" không?

Trình chia sẻ lại ngừng khiếu nại nếu tôi chỉ thêm int voicesSoFar1 = voicesSoFar ngay sau int voicesSoFar = 0. Có một số logic lạ tôi không hiểu rằng làm cho đề nghị của Resharper chính xác? Hoặc là có một cách để an toàn "truy cập đóng cửa sửa đổi" trong trường hợp như thế này mà không gây ra lỗi?

// this takes voters while we have less than 300 voices  
int voicesSoFar = 0;  
int voicesNeededMaximum = 300;  
var eligibleVoters = 
    voters.TakeWhile((p => (voicesSoFar += p.Voices) < voicesNeededMaximum)); 
+3

Tôi tắt cảnh báo Resharper này. Bạn chắc chắn phải cẩn thận khi sửa đổi các biến bị bắt, nhưng đây là một trong những nơi mà một sự hiểu biết tốt về ngôn ngữ này sẽ vượt trội hơn sau một quy tắc rote. Có một lý do C# cho phép điều này - nó rất hữu ích. –

Trả lời

6

Bạn có một vấn đề rất khó chịu phát sinh từ việc biến đổi biến bên ngoài thành biểu thức lambda. Vấn đề là thế này: nếu bạn cố gắng lặp eligibleVoters hai lần (foreach(var voter in eligibleVoters) { Console.WriteLine(voter.Name); } và ngay lập tức sau (foreach(var voter in eligibleVoters) { Console.WriteLine(voter.Name); }), bạn sẽ không nhìn thấy đầu ra tương tự Đó chỉ là không đúng từ góc độ lập trình chức năng

Dưới đây là một phương pháp mở rộng mà sẽ.. tích lũy cho đến khi một số điều kiện về accumulator là đúng:

public static IEnumerable<T> TakeWhileAccumulator<T, TAccumulate>(
    this IEnumerable<T> elements, 
    TAccumulate seed, 
    Func<TAccumulate, T, TAccumulate> accumulator, 
    Func<TAccumulate, bool> predicate 
) { 
    TAccumulate accumulate = seed; 
    foreach(T element in elements) { 
     if(!predicate(accumulate)) { 
      yield break; 
     } 
     accumulate = accumulator(accumulate, element); 
     yield return element; 
    } 
} 

Cách sử dụng:

var eligibleVoters = voters.TakeWhileAccumulator(
         0, 
         (votes, p) => votes + p.Voices, 
         i => i < 300 
        ); 

Do đó, nói trên tích lũy tiếng nói trong khi chúng tôi đã tích lũy được ít hơn 300 phiếu bầu.

Sau đó, với:

foreach (var item in eligibleVoters) { Console.WriteLine(item.Name); } 
Console.WriteLine(); 
foreach (var item in eligibleVoters) { Console.WriteLine(item.Name); } 

Output là:

Alice 
Bob 
Catherine 

Alice 
Bob 
Catherine 
+0

Có cách nào tốt để xử lý việc này không? –

+0

Có, viết một phương thức mở rộng khác. Xem chỉnh sửa của tôi. – jason

0

Tôi nghi ngờ rằng việc sửa đổi giá trị 'voicesSoFar' trong TakeWhile đang gây ra sự cố.

3

Vâng, thông báo lỗi là chính xác trong càng nhiều rằng giá trị của voicesSoFar là không được bảo quản trong quá trình phẫu thuật. Trong các thuật ngữ "chức năng" thuần túy (và lambdas thực sự được thiết kế để hoạt động chức năng) nó sẽ gây nhầm lẫn.

Ví dụ, một thử nghiệm thú vị sẽ là:

gì xảy ra nếu tôi lặp truy vấn hai lần?

Ví dụ:

int count = voters.Count(); 
var first = voters.FirstOrDefault(); 

Tôi tin rằng bạn có thể nhìn thấy ... 10, null - khó hiểu. Sau đây có thể lặp lại:

public static IEnumerable<Foo> TakeVoices(
    this IEnumerable<Foo> voices, int needed) 
{ 
    int count = 0; 
    foreach (Foo voice in voices) 
    { 
     if (count >= needed) yield break; 
     yield return voice; 
     count += voice.Voices; 
    } 
} 
.... 
foreach(var voice in sample.TakeVoices(numberNeeded)) { 
    ... 
} 

Nếu bạn cần tất nhiên, bạn có thể viết phương pháp mở rộng có thể sử dụng lại lambda.

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