2011-01-26 4 views
15

Chuỗi là các loại tham chiếu, nhưng chúng không thay đổi. Điều này cho phép chúng được interned bởi trình biên dịch; ở khắp mọi nơi cùng một chuỗi chữ xuất hiện, cùng một đối tượng có thể được tham chiếu.Tại sao các biểu thức lambda không "interned"?

Đại biểu cũng là loại tham chiếu không thay đổi. (Thêm một phương thức cho một đại biểu multicast bằng cách sử dụng toán tử += cấu thành gán; đó không phải là khả năng biến đổi.) Và, giống như, chuỗi, có một cách "theo nghĩa đen" để đại diện cho một đại biểu trong mã, sử dụng một biểu thức lambda, ví dụ:

Func<int> func =() => 5; 

Phía bên phải của tuyên bố đó là biểu thức có loại Func<int>; nhưng không nơi nào tôi gọi một cách rõ ràng các nhà xây dựng Func<int> (cũng không phải là một chuyển đổi tiềm ẩn xảy ra). Vì vậy, tôi xem chủ đề này thực chất là chữ số. Tôi có nhầm lẫn về định nghĩa của tôi về "chữ" ở đây không?

Bất kể, đây là câu hỏi của tôi. Nếu tôi có hai biến cho, nói rằng, các loại Func<int>, và tôi gán biểu thức lambda giống hệt nhau cho cả hai:

Func<int> x =() => 5; 
Func<int> y =() => 5; 

... gì đang ngăn cản các trình biên dịch từ điều trị này như là cùng một đối tượng Func<int>?

Tôi hỏi vì mục 6.5.1 của C# 4.0 language specification nêu rõ:

Chuyển đổi của ngữ nghĩa giống hệt chức năng ẩn danh với cùng (có thể rỗng) bộ ngoài trường biến bị bắt với cùng đại biểu các loại được phép (nhưng không được yêu cầu ) để trả lại cùng một cá nhân đại diện . Thuật ngữ ngữ nghĩa giống hệt nhau được sử dụng ở đây để ngụ ý rằng thực hiện các hàm ẩn danh sẽ, trong mọi trường hợp, tạo ra cùng một hiệu ứng cho cùng một đối số.

Điều này làm tôi ngạc nhiên khi đọc; nếu hành vi này rõ ràng là cho phép, tôi đã mong đợi nó sẽ được triển khai. Nhưng có vẻ như không. Điều này trong thực tế đã nhận được rất nhiều nhà phát triển gặp rắc rối, đặc biệt. khi các biểu thức lambda đã được sử dụng để gắn các trình xử lý sự kiện thành công mà không có khả năng loại bỏ chúng. Ví dụ:

class EventSender 
{ 
    public event EventHandler Event; 
    public void Send() 
    { 
     EventHandler handler = this.Event; 
     if (handler != null) { handler(this, EventArgs.Empty); } 
    } 
} 

class Program 
{ 
    static string _message = "Hello, world!"; 

    static void Main() 
    { 
     var sender = new EventSender(); 
     sender.Event += (obj, args) => Console.WriteLine(_message); 
     sender.Send(); 

     // Unless I'm mistaken, this lambda expression is semantically identical 
     // to the one above. However, the handler is not removed, indicating 
     // that a different delegate instance is constructed. 
     sender.Event -= (obj, args) => Console.WriteLine(_message); 

     // This prints "Hello, world!" again. 
     sender.Send(); 
    } 
} 

Có bất kỳ lý do tại sao điều này vi-một thể hiện ủy nhiệm cho anonymous ngữ nghĩa giống hệt phương pháp-không được thực hiện?

+4

Ngay cả khi nó hoạt động, tôi vẫn nghĩ rằng một ý tưởng tồi là tách một trình xử lý sự kiện bằng cách sao chép mã trong lambda. –

+2

Tôi nghi ngờ rằng đây sẽ là một trong số đó "bởi vì ngân sách không có sẵn để thiết kế, triển khai, kiểm tra, ghi chép, vận chuyển và duy trì" những điều thú vị. Có lẽ Eric Lippert có thể kêu vang với một số thông tin nội bộ. – LukeH

+0

Câu hỏi hay. Tôi sẽ thừa nhận rằng một lambda như trong mã có thể trông giống hệt nhau, nhưng vì sự khác biệt tham chiếu như đóng cửa bên ngoài nó sẽ không biên dịch cho cùng một MSIL ở cả hai nơi. Trình biên dịch phải biên dịch nó thành MSIL để phát hiện sự khác biệt, so với việc có thể phát hiện một chuỗi chữ "đang chạy" từ mã nguồn. Vì đây là một bước bổ sung cần thiết cho bất kỳ lambda nào, và chỉ cung cấp một khoản tiết kiệm kích thước nhỏ và ít nếu đạt được hiệu suất, họ có thể chỉ bỏ qua nó. – KeithS

Trả lời

11

Bạn nhầm là gọi nó là IMO theo nghĩa đen. Nó chỉ là một biểu thức có thể chuyển đổi thành kiểu đại biểu.

Bây giờ là phần "interning" - một số biểu thức lambda được lưu trong bộ nhớ cache, đôi khi một cá thể đơn lẻ có thể được tạo và sử dụng lại. Một số không được xử lý theo cách đó: nó thường phụ thuộc vào việc biểu thức lambda nắm bắt bất kỳ biến không tĩnh (cho dù đó là thông qua "này" hoặc địa phương để phương pháp).

Dưới đây là một ví dụ về bộ nhớ đệm này:

using System; 

class Program 
{ 
    static void Main() 
    { 
     Action first = GetFirstAction(); 
     first -= GetFirstAction(); 
     Console.WriteLine(first == null); // Prints True 

     Action second = GetSecondAction(); 
     second -= GetSecondAction(); 
     Console.WriteLine(second == null); // Prints False 
    } 

    static Action GetFirstAction() 
    { 
     return() => Console.WriteLine("First"); 
    } 

    static Action GetSecondAction() 
    { 
     int i = 0; 
     return() => Console.WriteLine("Second " + i); 
    } 
} 

Trong trường hợp này chúng ta có thể thấy rằng các hành động đầu tiên được lưu trữ (hoặc ít nhất, hai bằng đại biểu được sản xuất, và trong thực tế Reflector cho thấy rằng nó thực sự được lưu trong một trường tĩnh). Hành động thứ hai đã tạo hai trường hợp không bằng nhau là Action cho hai cuộc gọi đến GetSecondAction, đó là lý do tại sao "giây" không phải là vô giá trị ở cuối.

Các lambda nội bộ xuất hiện ở các vị trí khác nhau trong mã nhưng với cùng một mã nguồn là một vấn đề khác. Tôi nghi ngờ nó sẽ là khá phức tạp để làm điều này đúng (sau khi tất cả, cùng một mã nguồn có thể có nghĩa là những thứ khác nhau ở những nơi khác nhau) và tôi chắc chắn sẽ không muốn dựa trên nó diễn ra. Nếu nó không đáng để dựa vào, và có rất nhiều việc phải làm cho nhóm biên dịch, tôi không nghĩ đó là cách tốt nhất mà họ có thể dành thời gian của họ.

+0

"cùng một mã nguồn" không phải là "giống hệt về mặt ngữ nghĩa" như được định nghĩa trong spec. '(x, y) => x + y' có thể là" giống hệt về mặt ngữ nghĩa "với' (a, b) => a + b' trong một số trường hợp nhất định (không có vars cục bộ, không có kiểu này, cùng loại, v.v.). Tôi hiểu rằng sự tương đương ngữ nghĩa này có thể đầy những trường hợp góc và bị phát hiện địa ngục, và do đó dẫn đến việc thực hiện theo cách tiếp cận đơn giản. –

+0

() => 5 tại một điểm trong mã nguồn không tương đương với() => 5 tại một số điểm khác. Các đại biểu được cung cấp bởi mỗi được yêu cầu để so sánh bất bình đẳng, mà sẽ không xảy ra nếu họ đã được interned. – supercat

+1

@supercat: Không, tôi không nghĩ như vậy - giả sử các loại đại biểu là như nhau, tôi tin rằng phần của spec được gọi ra trong câu hỏi là nói rằng họ * sẽ không * được yêu cầu để so sánh bất bình đẳng. –

1

Không thể thực tập lambdas vì chúng sử dụng đối tượng để chứa các biến cục bộ đã chụp. Và trường hợp này là khác nhau mỗi khi bạn xây dựng các đại biểu.

+3

Không phải tất cả lambdas đều nắm bắt các biến cục bộ. Ví dụ của Dan Tao là một trong số đó. –

+1

Không phải tất cả, nhưng đủ của họ rằng tối ưu hóa có lẽ là không có giá trị nó. Và mã không bao giờ nên dựa vào chúng được tập trung cho đúng đắn kể từ IMO nó sẽ chỉ là một tối ưu hóa và không phải theo hợp đồng. Đặc biệt, nếu việc tối ưu hóa được thực hiện, nó sẽ làm cho mã OPs hoạt động trong một số triển khai, nhưng không phải ở những người khác. Và nó đột nhiên ngừng hoạt động khi các biến bị bắt. Vì vậy, nó thậm chí có thể lừa nhiều nhà phát triển hơn hành vi hiện tại. – CodesInChaos

+0

Ngoài ra, phần tôi trích dẫn từ spec chỉ ra rằng nếu hai biểu thức lambda trong cùng một phương thức nắm bắt các biến * cùng * cục bộ, thì chúng giống nhau về mặt ngữ nghĩa và có thể được hỗ trợ bởi cùng một cá thể ủy nhiệm. Tôi không nói về * giữa các cuộc gọi phương thức *, nhớ bạn - nhưng trong một cuộc gọi phương thức duy nhất, hai biểu thức có thể sử dụng cùng một đối tượng. –

1

Nói chung, không có sự khác biệt giữa các biến chuỗi tham chiếu đến cùng một cá thể String, so với hai biến tham chiếu đến các chuỗi khác nhau có chứa cùng một chuỗi ký tự. Điều này cũng không đúng đối với các đại biểu.

Giả sử tôi có hai đại biểu, được gán cho hai biểu thức lambda khác nhau. Sau đó tôi đăng ký cả hai đại biểu để xử lý sự kiện và hủy đăng ký. Kết quả là gì? Nó có thể hữu ích nếu có một cách trong vb hoặc C# để chỉ định rằng một phương thức nặc danh hoặc lambda không tham chiếu đến Me/điều này nên được coi là phương pháp tĩnh, tạo ra một đại biểu duy nhất có thể được tái sử dụng trong suốt cuộc đời của ứng dụng. Không có cú pháp để chỉ ra rằng, tuy nhiên, và để trình biên dịch quyết định có các biểu thức lambda khác nhau trả về cùng một cá thể sẽ là một thay đổi có khả năng phá vỡ.

EDIT Tôi đoán đặc tả cho phép nó, mặc dù nó có thể là một thay đổi có khả năng phá vỡ nếu bất kỳ mã nào dựa vào các cá thể khác biệt.

+1

'public static readonly Func TheDeepThoughtLambda =() => 42;': P –

+0

Mã dựa vào các cá thể riêng biệt phụ thuộc vào các chi tiết triển khai hiện tại và bỏ qua đặc tả. Nếu nó đã được thực hiện ngay bây giờ, mà không có bất kỳ thay đổi nào đối với thông số kỹ thuật, bạn có thể gọi đó là một thay đổi đột phá, bởi vì mã không tuân thủ đã phá vỡ? –

+0

@Martinho: (Lưu ý chỉnh sửa của tôi ở trên) – supercat

1

Điều này được cho phép vì nhóm C# không thể kiểm soát điều này. Họ phụ thuộc rất nhiều vào các chi tiết thực hiện của các đại biểu (CLR + BCL) và trình tối ưu hóa trình biên dịch JIT. Hiện đã có khá một sự gia tăng của CLR và jitter triển khai ngay bây giờ và có rất ít lý do để giả định đó sẽ kết thúc. Đặc tả CLI rất nhẹ về các quy tắc về các đại biểu, không đủ mạnh để đảm bảo tất cả các nhóm khác nhau này sẽ kết thúc với việc triển khai đảm bảo rằng bình đẳng đối tượng đại biểu là nhất quán. Không ít nhất là vì điều đó sẽ cản trở sự đổi mới trong tương lai. Có rất nhiều thứ để tối ưu hóa ở đây.

6

Có bất kỳ lý do nào tại sao hành vi này — một cá thể ủy nhiệm cho các phương thức vô danh giống hệt hệt nhau không được triển khai?

Có.Bởi vì dành thời gian vào một tối ưu hóa khôn lanh mà hầu như không ai mất thời gian để thiết kế, triển khai, thử nghiệm và duy trì các tính năng mà làm mang lại lợi ích cho mọi người.

+0

Điều này về cơ bản xác nhận những gì đã được đề xuất bởi Jon, LukeH, và có thể những người khác; điều khiến tôi nghi ngờ điều này đơn giản là khả năng tính năng này được đề cập trong thông số kỹ thuật. Nhưng như Jon (và những người khác) đã chỉ ra, điều này có thể đơn giản là tránh đóng cửa một cách không cần thiết. Hans cũng đưa ra một lời giải thích khá thuyết phục. Dù sao, cảm ơn như luôn luôn cho chiming với những gì tôi đoán chỉ có thể được gọi là câu trả lời có thẩm quyền! –