2014-11-04 12 views
22

Điều này phải là một cái gì đó thực sự đơn giản. Nhưng tôi sẽ hỏi nó, bởi vì tôi nghĩ rằng những người khác cũng sẽ đấu tranh với nó. Tại sao truy vấn LINQ đơn giản sau đây không được thực hiện luôn với biến-giá trị mới thay vì luôn sử dụng giá trị đầu tiên?Thực hiện hoãn lại của LINQ, nhưng làm cách nào?

static void Main(string[] args) 
{ 
    Console.WriteLine("Enter something:"); 
    string input = Console.ReadLine();  // for example ABC123 
    var digits = input.Where(Char.IsDigit); // 123 
    while (digits.Any()) 
    { 
     Console.WriteLine("Enter a string which doesn't contain digits"); 
     input = Console.ReadLine();   // for example ABC 
    } 
    Console.WriteLine("Bye"); 
    Console.ReadLine(); 
} 

Trong mẫu nhận xét, nó sẽ nhập vòng lặp do đầu vào ABC123 chứa chữ số. Nhưng nó sẽ không bao giờ rời khỏi nó ngay cả khi bạn nhập một cái gì đó như ABC từ digits vẫn là 123.

Vậy tại sao truy vấn LINQ không đánh giá giá trị input mới nhưng luôn là giá trị đầu tiên?

Tôi biết tôi có thể sửa chữa nó với dòng này thêm:

while (digits.Any()) 
{ 
    Console.WriteLine("Enter a string which doesn't contain digits"); 
    input = Console.ReadLine();   
    digits = input.Where(Char.IsDigit); // now it works as expected 
} 

hoặc - thanh lịch hơn - bằng cách sử dụng các truy vấn trực tiếp trong vòng lặp:

while (input.Any(Char.IsDigit)) 
{ 
    // ... 
} 
+5

Khi bạn chuyển một biến cho hàm dưới dạng tham số, biến đó được chuyển theo giá trị. –

+1

Một đoạn mã đơn giản như vậy, với những tác dụng phụ phức tạp như vậy. –

+2

Bò thánh, bạn có câu trả lời từ Raymond Chen ™ thực tế! –

Trả lời

40

Sự khác biệt là bạn đang thay đổi giá trị của biến số input, thay vì nội dung của đối tượng mà biến đó đề cập đến ... do đó digits vẫn đề cập đến bộ sưu tập gốc.

Hãy so sánh điều đó với mã này:

List<char> input = new List<char>(Console.ReadLine()); 
var digits = input.Where(Char.IsDigit); // 123 
while (digits.Any()) 
{ 
    Console.WriteLine("Enter a string which doesn't contain digits"); 
    input.Clear(); 
    input.AddRange(Console.ReadLine()); 
} 

thời gian này, chúng tôi đang sửa đổi các nội dung của bộ sưu tập mà input đề cập đến - và như digits là một cách hiệu quả một cái nhìn qua bộ sưu tập đó, chúng tôi có được để xem thay đổi.

+0

Tôi có chính xác không - điều này đã xảy ra trong ví dụ câu hỏi vì tính bất biến của chuỗi? – fex

+1

@fex: Sự bất biến của chuỗi không phải là lý do cho vấn đề này nhưng đó là lý do cho sự nhầm lẫn của tôi. Nếu chuỗi là một tập hợp như 'Danh sách 'tôi sẽ sửa đổi nó trực tiếp và không gán một danh sách mới cho biến đó. –

+1

@fex: Không trực tiếp. Nếu chuỗi có thể thay đổi thì việc thay đổi giá trị của 'đầu vào' sẽ * vẫn * không ảnh hưởng đến' chữ số' ... nhưng nội dung được tham chiếu bởi 'đầu vào' có thể đã được thay đổi. –

10

Bạn đang gán một giá trị mới đến input, nhưng trình tự digits vẫn bắt nguồn từ giá trị ban đầu là input. Nói cách khác, khi bạn thực hiện digits = input.Where(Char.IsDigit), , nó sẽ ghi lại giá trị hiện tại của biến số input, chứ không phải biến. Gán một giá trị mới cho input không có hiệu lực trên digits.

4

Các chữ số được liệt kê là một bản sao của chuỗi mà input chứa khi bạn tạo số đếm. Nó không giữ một tham chiếu đến biến input và việc thay đổi giá trị được lưu trữ trong input sẽ không gây ra hiện tượng hóa của số đếm để sử dụng giá trị mới.

Hãy nhớ rằng Where là một phương pháp mở rộng tĩnh và chấp nhận đối tượng mà bạn đang gọi nó làm tham số.

6

dòng này:

input.Where(Char.IsDigit) 

tương đương với:

Enumerable.Where(input, Char.IsDigit) 

Như vậy, giá trị của input đang được thông qua như là nguồn gốc của các truy vấn .Where, không phải là một tài liệu tham khảo -input.

Sửa chữa lần đầu tiên bạn đề xuất tác phẩm vì nó sử dụng giá trị mới được gán là input trên dòng trước đó.

+0

Có. Người ta có thể nói rằng 'đầu vào' là một tham số ByValue, không phải là một tham số ByRef (nó không nói' ref' hoặc 'out'). –

+0

@JeppeStigNielsen: Điều đó thực sự không đúng. Các chuỗi là các kiểu tham chiếu (đó là lý do tại sao bạn có thể có một chuỗi rỗng). Điều đó có nghĩa là biến 'đầu vào' thực sự chứa tham chiếu đến chuỗi. Nếu 'string' có thể thay đổi được, bạn có thể chuyển một chuỗi tới một hàm như một tham số bình thường, và việc sửa đổi chuỗi bên trong cũng sẽ làm thay đổi chuỗi gốc. Nhưng kể từ khi bạn không thể sửa đổi các chuỗi, sau đó trong một số kịch bản nó có tác dụng tương tự như đi qua tham số như ByValue. –

+0

@tomp Tôi biết 'chuỗi' là một loại tham chiếu. Đó không phải là điều tôi đang làm. Tôi đã lấy về việc tham số là một tham số ByRef (hoặc 'chuỗi ref' hoặc' chuỗi ra') hay không. Vì vậy, chúng tôi đồng ý. Thật không may là hai khái niệm riêng biệt "kiểu tham chiếu" (ví dụ 'lớp' (vv), không phải' struct') và "bởi tham số ref" (hoặc 'ref' hoặc' out') có tên tương tự nhau. Nó dẫn đến nhiều hiểu lầm. Tôi đã thực sự nhận thức được điều đó và cố gắng để làm cho từ ngữ của tôi chính xác, nhưng tôi vẫn bị hiểu lầm ... –

2

Tôi đang trả lời chỉ để thêm độ chính xác cho các câu trả lời hay khác, về việc thực thi hoãn lại .

Ngay cả khi truy vấn LINQ chưa được đánh giá (sử dụng .Any()), truy vấn nội bộ luôn đề cập đến nội dung ban đầu của biến. Thậm chí nếu các truy vấn LINQ được đánh giá sau những điều mới mẻ đã bị ảnh hưởng vào biến, nội dung ban đầu không thay đổi và thực hiện chậm sẽ sử dụng nội dung ban đầu truy vấn luôn được đề cập đến:

var input = "ABC123"; 
var digits = input.Where(Char.IsDigit); 
input = "NO DIGIT"; 
var result = digits.ToList(); // 3 items 
+1

Chỉ cần được cảnh báo rằng 'nội dung ban đầu' có thể trỏ đến một cấu trúc có thể thay đổi. ví dụ. 'input = new List {1}; var even = input.where (x => x% 2 == 0); input.Add (2); var result = even.ToList(); ' – NPSF3000

+2

@ NPSF3000: nếu nó có thể là một bộ sưu tập có thể thay đổi, tôi sẽ không hỏi câu hỏi này vì thiếu sự cố ;-) –

4

Đây gần như là một nhận xét, nhưng chứa mã có cấu trúc, vì vậy tôi gửi nó như là một câu trả lời.

Các sửa đổi chút ít mã sau đây bạn sẽ làm việc:

Console.WriteLine("Enter something:"); 
    string input = Console.ReadLine();  // for example ABC123 
    Func<bool> anyDigits =() => input.Any(Char.IsDigit); // will capture 'input' as a field 
    while (anyDigits()) 
    { 
    Console.WriteLine("Enter a string which doesn't contain digits"); 
    input = Console.ReadLine();   // for example ABC 
    } 
    Console.WriteLine("Bye"); 
    Console.ReadLine(); 

Đây input được chụp (đóng cửa) bởi các đại biểu của loại Func<bool>.

+0

Rên rỉ rên rỉ," Truy cập vào đóng cửa đã sửa đổi " cho lần sử dụng đầu tiên của 'đầu vào'. Đề xuất bạn thay đổi thành 'Func ' và gọi bằng 'while (anyDigits (input))' (có thể cải thiện khả năng đọc). – onedaywhen

+0

@onedaywhen Yep! Đây không phải là cách tốt nhất để viết mã. Nó đã được chỉ có nghĩa là một sự thay đổi "tối thiểu" của mã ban đầu (từ câu hỏi) mà thực sự làm việc. Tôi không khuyến khích hoặc khuyên mọi người nên viết mã như trên. Lý do tại sao ReSharper cảnh báo bạn rằng ngữ nghĩa đóng cửa có thể gây nhầm lẫn cho người đọc mã. Để có được chức năng mong muốn, chúng tôi không cần truy cập vào việc đóng cửa đã sửa đổi (và người hỏi biết cách tốt hơn để làm cho mọi thứ hoạt động trong câu hỏi). –

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