Giả sử tôi đã tạo ra một lớp wrapper như sau:phương pháp tiếp cận cho chung chung, thời gian biên dịch phương pháp lười biếng tải an toàn
public class Foo : IFoo
{
private readonly IFoo innerFoo;
public Foo(IFoo innerFoo)
{
this.innerFoo = innerFoo;
}
public int? Bar { get; set; }
public int? Baz { get; set; }
}
Ý tưởng ở đây là innerFoo
có thể quấn các phương pháp truy cập dữ liệu hoặc một cái gì đó đắt tương tự và tôi chỉ muốn các phương thức GetBar
và GetBaz
được gọi một lần. Vì vậy, tôi muốn tạo một wrapper khác xung quanh nó, thao tác này sẽ lưu các giá trị thu được trong lần chạy đầu tiên.
Nó đủ đơn giản để làm được điều này, tất nhiên:
int IFoo.GetBar()
{
if ((Bar == null) && (innerFoo != null))
Bar = innerFoo.GetBar();
return Bar ?? 0;
}
int IFoo.GetBaz()
{
if ((Baz == null) && (innerFoo != null))
Baz = innerFoo.GetBaz();
return Baz ?? 0;
}
Nhưng nó được lặp đi lặp lại khá nếu tôi đang làm điều này với 10 thuộc tính khác nhau và 30 giấy gói khác nhau. Vì vậy, tôi đã tìm, hey, chúng ta hãy làm này generic:
T LazyLoad<T>(ref T prop, Func<IFoo, T> loader)
{
if ((prop == null) && (innerFoo != null))
prop = loader(innerFoo);
return prop;
}
nào gần được tôi, nơi tôi muốn, nhưng không hoàn toàn, bởi vì bạn không thể ref
một tính năng tự động tài sản (hoặc bất cứ tài sản nào cả). Nói cách khác, tôi không thể viết này:
int IFoo.GetBar()
{
return LazyLoad(ref Bar, f => f.GetBar()); // <--- Won't compile
}
Thay vào đó, tôi sẽ phải thay đổi Bar
để có một lĩnh vực ủng hộ rõ ràng và viết thu khí rõ ràng và setters. Đó là tốt, ngoại trừ thực tế là tôi kết thúc bằng văn bản mã dư thừa nhiều hơn tôi đã viết ở nơi đầu tiên.
Sau đó, tôi xem xét khả năng của việc sử dụng cây biểu thức:
T LazyLoad<T>(Expression<Func<T>> propExpr, Func<IFoo, T> loader)
{
var memberExpression = propExpr.Body as MemberExpression;
if (memberExpression != null)
{
// Use Reflection to inspect/set the property
}
}
này đóng tốt đẹp với refactoring - nó sẽ làm việc tuyệt vời nếu tôi làm điều này:
return LazyLoad(f => f.Bar, f => f.GetBar());
Nhưng nó không phải là thực sự an toàn, vì ai đó ít thông minh hơn (tức là bản thân tôi trong 3 ngày kể từ bây giờ khi tôi chắc chắn quên cách thực hiện điều này trong nội bộ) có thể quyết định viết thay thế này:
return LazyLoad(f => 3, f => f.GetBar());
Điều gì sẽ bị hỏng hoặc dẫn đến hành vi không mong muốn/không xác định, tùy thuộc vào cách phòng thủ tôi viết phương pháp LazyLoad
. Vì vậy, tôi không thực sự thích cách tiếp cận này, hoặc, bởi vì nó dẫn đến khả năng lỗi thời gian chạy mà có thể đã được ngăn chặn trong nỗ lực đầu tiên. Nó cũng dựa trên Reflection, mà cảm thấy một chút bẩn ở đây, mặc dù mã này được thừa nhận là không hiệu suất nhạy cảm.
Bây giờ tôi có thể cũng quyết định đi tất cả ra và sử dụng DynamicProxy để làm phương pháp đánh chặn và không phải viết bất kỳ mã , và trong thực tế tôi đã làm điều này trong một số ứng dụng. Nhưng mã này nằm trong một thư viện lõi mà nhiều hội đồng khác phụ thuộc vào, và có vẻ như khủng khiếp sai khi giới thiệu loại phức tạp này ở mức độ thấp như vậy. Tách việc thực hiện dựa trên chặn máy chủ từ giao diện IFoo
bằng cách đặt nó vào trong hội đồng của chính nó không thực sự hữu ích; thực tế là lớp này vẫn sẽ được sử dụng khắp nơi, phải sử dụng, vì vậy đây không phải là một trong những vấn đề có thể được giải quyết một cách trivially với một chút phép thuật DI.
Tùy chọn cuối cùng tôi đã nghĩ đến việc sẽ có một phương pháp như:
T LazyLoad<T>(Func<T> getter, Action<T> setter, Func<IFoo, T> loader) { ... }
Tùy chọn này là rất "meh" cũng - nó tránh Reflection nhưng vẫn là dễ bị lỗi, và nó không thực sự làm giảm sự lặp lại nhiều. Nó gần như là xấu như phải viết getters rõ ràng và setters cho mỗi tài sản.
Có lẽ tôi chỉ là cực kỳ khó tính, nhưng ứng dụng này vẫn đang trong giai đoạn đầu, và nó sẽ tăng lên đáng kể theo thời gian, và tôi thực sự muốn giữ cho mã sạch sẽ.
Tóm lại: Tôi đang gặp khó khăn, tìm kiếm các ý tưởng khác.
Câu hỏi:
Có cách nào để làm sạch mã lười biếng nạp ở phía trên, như vậy mà thực hiện sẽ:
- Đảm bảo thời gian biên dịch an toàn, giống như phiên bản
ref
; - Thực tế, giảm số lần lặp lại mã, như phiên bản
Expression
; và - Không nhận bất kỳ phụ thuộc bổ sung đáng kể nào?
Nói cách khác, có cách nào để làm điều này chỉ bằng các tính năng ngôn ngữ C# thông thường và có thể là một vài lớp trợ giúp nhỏ? Hay tôi chỉ cần phải chấp nhận rằng có một sự đánh đổi ở đây và tấn công một trong những yêu cầu trên từ danh sách?
Bạn có thực sự cần phải có thuộc tính có thể đọc/ghi công khai có thể hoặc không được điền và một GetX() công khai đặt thuộc tính và trả về giá trị? –
@ Jeffrey: Câu chuyện dài ngắn gọn, vâng, bởi vì một phần của lý do cho lớp này là các thuộc tính có thể được háo hức nạp thay thế. Ví dụ, nếu đối tượng kết thúc tốt đẹp một số cuộc gọi cơ sở dữ liệu, một thành phần cấp cao hơn có thể quyết định tải một số trong số này từ một bản ghi lớn để tránh lặp lại các cuộc gọi cơ sở dữ liệu. Tôi đang mở để tách các chức năng này - * nếu * kết quả cuối cùng vẫn đáp ứng được tất cả 3 tiêu chí. – Aaronaught