2011-09-27 28 views
45

Gần đây tôi đã bắt đầu sử dụng Lazy trong suốt đơn đăng ký của mình và tôi đã tự hỏi liệu có bất kỳ khía cạnh tiêu cực rõ ràng nào mà tôi cần xem xét khi sử dụng Lazy<T> không?Nhược điểm của Lazy <T>?

Tôi đang cố gắng sử dụng Lazy<T> thường xuyên như tôi cho là phù hợp, chủ yếu để giúp giảm dung lượng bộ nhớ của các plugin đã tải, nhưng không hoạt động của chúng tôi.

+4

Tôi vừa mới bắt đầu sử dụng Lazy và thấy rằng nó thường mang tính chất thiết kế xấu; hoặc lười biếng trên một phần của lập trình viên. Ngoài ra, một trong những bất lợi là bạn phải thận trọng hơn với các biến scoped lên, và tạo ra đóng cửa thích hợp. – Gleno

+4

@Gleno Tại sao chính xác sự lười biếng của lập trình viên này? –

+4

@Gleno, Anton: Và quan trọng hơn, tại sao nó lại tệ? Tôi luôn dạy trong các lớp lập trình của tôi rằng sự lười biếng là một nhân tố quan trọng trong lập trình viên. –

Trả lời

17

tôi sẽ mở rộng một chút về nhận xét của tôi, mà đọc:

Tôi vừa mới bắt đầu sử dụng Lazy, và thấy rằng nó thường biểu hiện thiết kế xấu; hoặc lười biếng trên một phần của lập trình viên. Ngoài ra, một trong những bất lợi là bạn phải cảnh giác hơn với các biến số được tạo thành và tạo các bao đóng phù hợp.

Ví dụ, tôi đã sử dụng Lazy<T> để tạo ra các trang mà người dùng có thể nhìn thấy trong tôi (sessionless) MVC ứng dụng. Đó là một hướng dẫn hướng dẫn, vì vậy người dùng có thể muốn đi đến một bước ngẫu nhiên trước đó. Khi bắt tay được thực hiện, một mảng của các đối tượng Lazy<Page> bị đóng và nếu người dùng chỉ định là bước, thì trang chính xác đó được đánh giá. Tôi thấy nó mang lại hiệu năng tốt, nhưng có một số khía cạnh để nó mà tôi không thích, ví dụ nhiều foreach cấu trúc của tôi bây giờ trông như thế này:

foreach(var something in somethings){ 
    var somethingClosure = something; 
    list.Add(new Lazy<Page>(() => new Page(somethingClosure)); 
} 

Tức là bạn phải đối phó với vấn đề đóng cửa rất chủ động. Nếu không, tôi không nghĩ đó là một màn trình diễn tồi tệ để lưu trữ một lambda và đánh giá nó khi cần thiết. Mặt khác, điều này có thể là dấu hiệu cho thấy lập trình viên đang là Lazy<Programmer>, theo nghĩa là bạn không muốn suy nghĩ qua chương trình của mình ngay bây giờ, và thay vào đó hãy để logic thích hợp đánh giá khi cần, như ví dụ trong ví dụ của tôi trường hợp - thay vì xây dựng mảng đó, tôi chỉ có thể tìm ra những gì mà trang yêu cầu cụ thể đó sẽ là; nhưng tôi đã chọn lười biếng, và làm một cách tiếp cận tất cả.

EDIT

Nó xảy ra với tôi rằng Lazy<T> cũng có một vài peculiars khi làm việc với đồng thời. Ví dụ: có ThreadLocal<T> đối với một số tình huống và một số cấu hình cờ cho trường hợp đa luồng cụ thể của bạn. Bạn có thể đọc thêm trên msdn.

+3

Đó không phải là vấn đề của 'Lazy ' mỗi lần. Thay vào đó, đó là cách bạn sử dụng nó. –

+3

@Anton, vâng; và giả thuyết của tôi là Lazy <> là * đôi khi * có vấn đề trong việc cung cấp cho bạn tùy chọn không tìm kiếm giải pháp tốt hơn. Cho rằng tùy chọn bạn có thể giải quyết cho một cái gì đó mà chỉ hoạt động. – Gleno

+0

@ Fuji, hãy đặt nó theo cách này - điều tồi tệ nhất có thể xảy ra là bạn đột nhiên sẽ phải đánh giá tất cả các đối tượng Lười biếng của bạn vì thay đổi thông số kỹ thuật hoặc đối mặt với sự viết lại chính. bạn có thể sống với điều đó? – Gleno

4

Như với bất cứ điều gì, Lazy<T> có thể được sử dụng cho tốt hay xấu, do đó một bất lợi: khi được sử dụng không thích hợp, nó có thể gây nhầm lẫn và thất vọng. Tuy nhiên, mô hình khởi tạo lười biếng đã tồn tại trong nhiều năm, và bây giờ mà .NET BCL có một nhà phát triển thực hiện không cần phải tái tạo lại bánh xe nữa. Còn gì nữa, MEF loves Lazy.

2

Ý của bạn là chính xác với "trong suốt ứng dụng của tôi"?

Tôi nghĩ rằng nó chỉ nên được sử dụng khi bạn không chắc liệu giá trị có được sử dụng hay không, điều này chỉ có thể là trường hợp với các tham số tùy chọn mất nhiều thời gian để tính toán. Điều này có thể bao gồm các tính toán phức tạp, xử lý tệp, dịch vụ Web, truy cập cơ sở dữ liệu, v.v.

Mặt khác, tại sao sử dụng Lazy ở đây? Trong hầu hết trường hợp, bạn có thể chỉ cần gọi một phương thức thay vì lazy.Value và nó không tạo ra sự khác biệt nào. NHƯNG nó đơn giản và rõ ràng hơn cho lập trình viên những gì đang xảy ra trong tình huống này mà không cần Lazy.

Một nhược điểm rõ ràng có thể đã được thực hiện bộ nhớ đệm của giá trị, nhưng tôi không nghĩ đây là một lợi thế lớn như vậy.

7

Đây không phải là một khía cạnh tiêu cực, nhưng một hình ảnh xác thực cho những người lười biếng :).

Trình khởi chạy lười giống như trình khởi tạo tĩnh. Họ chạy được sau khi. Nếu một ngoại lệ được ném, ngoại lệ được lưu trong bộ nhớ cache và các lệnh gọi tiếp theo .Value sẽ ném cùng một ngoại lệ. Điều này là do thiết kế và được đề cập trong tài liệu ... http://msdn.microsoft.com/en-us/library/dd642329.aspx:

Trường hợp ngoại lệ được ném bởi valueFactory được lưu vào bộ nhớ cache.

Do đó, mã như dưới đây sẽ không bao giờ trả về một giá trị:

bool firstTime = true; 
Lazy<int> lazyInt = new Lazy<int>(() => 
{ 
    if (firstTime) 
    { 
     firstTime = false; 
     throw new Exception("Always throws exception the very first time."); 
    } 

    return 21; 
}); 

int? val = null; 
while (val == null) 
{ 
    try 
    { 
     val = lazyInt.Value; 
    } 
    catch 
    { 

    } 
} 
+0

Cảm ơn @Thilak. Hay đấy. Tôi đã không có đầu mối rằng ngoại lệ được lưu trữ như thế này. – eandersson

+1

@Fuji Đây là lý do tại sao nhóm MEF đã thêm ExportFactory và ExportLifetimeContext trong MEF 2. Hãy xem http://blogs.msdn.com/b/bclteam/archive/2011/11/17/exportfactory-amp-lt-t -amp-gt-in-mef-2-alok.aspx –

+0

Tôi nghĩ rằng tôi cần phải quay lại và sửa đổi một số mã MEF của tôi ngay bây giờ.;) – eandersson

7

Theo tôi, bạn nên luôn luôn có một lý do cho việc lựa chọn Lazy. Có một số lựa chọn thay thế tùy thuộc vào trường hợp sử dụng và chắc chắn có những trường hợp cấu trúc này phù hợp. Nhưng không sử dụng nó chỉ vì nó mát mẻ.

Ví dụ: tôi không nhận được điểm trong ví dụ lựa chọn trang ở một trong các câu trả lời khác. Sử dụng danh sách Lazy để chọn một phần tử đơn lẻ có thể được thực hiện tốt với danh sách hoặc từ điển của các đại biểu trực tiếp mà không cần sử dụng Lazy hoặc với một câu lệnh chuyển đổi đơn giản.

lựa chọn thay thế Vì vậy, rõ ràng nhất là

  • instantiation trực tiếp cho các cấu trúc dữ liệu rẻ tiền hoặc cấu trúc được nào cần
  • đại biểu cho điều đó là cần thiết không để vài lần trong một số thuật toán
  • một số cấu trúc bộ nhớ đệm cho các mục cần giải phóng bộ nhớ khi không được sử dụng trong một khoảng thời gian
  • một số loại cấu trúc "tương lai" như Tác vụ đã có thể bắt đầu khởi chạy không đồng bộ trước khi sử dụng thực tế e xác suất khá cao là công trình sẽ được yêu cầu sau

Ngược lại với đó, Lazy thường là phù hợp khi

  • cấu trúc dữ liệu tính toán dữ dội
  • là cần thiết không đến nhiều lần trong một số thuật toán trong đó trường hợp 0 ​​có xác suất quan trọng
  • và dữ liệu cục bộ cho một số phương thức hoặc lớp và có thể được thu gom rác khi không sử dụng nữa hoặc dữ liệu phải được lưu trong bộ nhớ cho toàn bộ thời gian chạy của chương trình
5

Tôi đã sử dụng Lazy<T> chủ yếu do khả năng đồng thời trong khi tải tài nguyên từ cơ sở dữ liệu. Vì vậy, tôi đã loại bỏ các đối tượng khóa và các mẫu khóa có thể tranh cãi. Trong trường hợp của tôi ConcurrentDictionary + Lazy như một giá trị làm cho tôi ngày, nhờ vào @Reed Copsey và ông blog post

này trông giống như sau. Thay vì gọi:

MyValue value = dictionary.GetOrAdd(
          key, 
          () => new MyValue(key)); 

Chúng tôi sẽ thay vì sử dụng một ConcurrentDictionary>, và viết:

MyValue value = dictionary.GetOrAdd(
          key, 
          () => new Lazy<MyValue>(
           () => new MyValue(key))) 
          .Value; 

Không nhược điểm của Lazy<T> nhận thấy cho đến nay.

+0

Điều đó khá ngọt ngào. Tôi đã không may là một chút quá tích cực ngày hôm nay và hết phiếu, vì vậy tôi không thể bỏ phiếu cho câu trả lời của bạn. ; * ( – eandersson

2

Lười biếng được sử dụng để bảo tồn tài nguyên trong khi không thực sự cần thiết. Mô hình này khá tốt nhưng việc triển khai có thể vô ích.

Tài nguyên lớn hơn, hữu ích là mẫu này.

Sự không thích hợp để sử dụng Lớp học lười biếng là sự minh bạch trong sử dụng. Thật vậy, bạn phải duy trì ở khắp mọi nơi một bổ sung indirection (.Value). Khi bạn chỉ cần một thể hiện của loại thực, nó buộc phải tải ngay cả khi bạn không cần phải sử dụng nó trực tiếp.

Lười biếng là để phát triển lười biếng đạt được năng suất nhưng mức tăng này có thể bị mất do sử dụng cao.

Nếu bạn có một thực thi minh bạch thực sự (sử dụng mẫu proxy cho exemple), nó thoát khỏi sự không thích hợp và nó có thể rất hữu ích trong nhiều trường hợp.

Đồng thời phải được xem xét ở một khía cạnh khác và không được triển khai theo mặc định trong loại của bạn. Nó phải được bao gồm chỉ trong mã khách hàng hoặc loại người trợ giúp cho khái niệm này.

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