Đây là trường hợp sử dụng khó để hỗ trợ vì cách trình biên dịch C# thực hiện độ phân giải quá tải và cách nó quyết định phương thức nào liên kết.
Vấn đề đầu tiên là constraints are not part of the signature của một phương pháp và sẽ không được xem xét giải quyết quá tải. Vấn đề thứ hai bạn phải vượt qua là trình biên dịch chọn kết hợp tốt nhất từ các chữ ký có sẵn - trong đó, khi giao dịch với generics, thường có nghĩa là SomeMethod<T>(T)
sẽ được xem là phù hợp hơn SomeMethod<T>(IEnumerable<T>)
... đặc biệt khi bạn có các thông số như T[]
hoặc List<T>
.
Nhưng về cơ bản hơn, bạn phải xem xét liệu hoạt động trên một giá trị duy nhất so với tập hợp các giá trị thực sự là cùng một hoạt động. Nếu chúng khác nhau về mặt logic, bạn có thể muốn sử dụng các tên khác nhau chỉ để làm rõ.Có lẽ có một số trường hợp sử dụng mà bạn có thể tranh luận rằng sự khác biệt ngữ nghĩa giữa các đối tượng đơn lẻ và bộ sưu tập đối tượng không có ý nghĩa ... nhưng trong trường hợp đó, tại sao lại thực hiện hai phương pháp khác nhau? Không rõ rằng quá tải phương thức là cách tốt nhất để thể hiện sự khác biệt. Hãy xem một ví dụ cho thấy sự nhầm lẫn:
Cache.GetOrAdd("abc",() => context.Customers.Frobble());
Trước tiên, lưu ý rằng trong ví dụ trên, chúng tôi chọn bỏ qua tham số trả về. Thứ hai, lưu ý rằng chúng tôi gọi một số phương thức Frobble()
trên bộ sưu tập Customers
. Bây giờ bạn có thể cho tôi biết tình trạng quá tải của GetOrAdd()
sẽ được gọi không? Rõ ràng là không biết loại Frobble()
trả về là không thể. Cá nhân tôi tin rằng mã mà ngữ nghĩa của bạn không thể dễ dàng suy ra từ cú pháp nên tránh khi có thể. Nếu chúng ta chọn tên tốt hơn, vấn đề này được giảm bớt:
Cache.Add("abc",() => context.Customers.Frobble());
Cache.AddRange("xyz",() => context.Customers.Frobble());
Cuối cùng, chỉ có ba tùy chọn để disambiguate các phương pháp trong ví dụ của bạn:
- Thay đổi tên của một trong những phương pháp .
- Truyền tới
IEnumerable<T>
bất cứ nơi nào bạn gọi là quá tải thứ hai.
- Thay đổi chữ ký của một trong các phương pháp theo cách mà trình biên dịch có thể phân biệt.
Tùy chọn 1 là hiển nhiên, vì vậy tôi sẽ không nói thêm về điều đó.
Tùy chọn 2 cũng là dễ hiểu:
var customers = Cache.GetOrAdd("All",
() => (IEnumerable<Customer>)context.Customers.ToArray());
Lựa chọn 3 là phức tạp hơn. Hãy xem xét cách chúng ta có thể đạt được nó.
On cách tiếp cận là bằng cách thay đổi chữ ký của người đại biểu Func<>
, ví dụ:
T GetOrAdd<T> (string cachekey, Func<object,T> fnGetItem)
T[] GetOrAdd<T> (string cachekey, Func<IEnumerable<T>> fnGetItem)
// now we can do:
var customer = Cache.GetOrAdd("First", _ => context.Customers.First());
var customers = Cache.GetOrAdd("All",() => context.Customers.ToArray());
Cá nhân, tôi tìm thấy tùy chọn này cực kỳ xấu xí, unintuitive, và khó hiểu. Giới thiệu một tham số không sử dụng là khủng khiếp ... nhưng, thật đáng buồn nó sẽ làm việc.
Một cách khác để thay đổi chữ ký (trong đó có phần ít khủng khiếp) là làm cho giá trị trả về một out
tham số:
void GetOrAdd<T> (string cachekey, Func<object,T> fnGetItem, out T);
void GetOrAdd<T> (string cachekey, Func<IEnumerable<T>> fnGetItem, out T[])
// now we can write:
Customer customer;
Cache.GetOrAdd("First", _ => context.Customers.First(), out customer);
Customer[] customers;
var customers = Cache.GetOrAdd("All",
() => context.Customers.ToArray(), out customers);
Nhưng điều này có thực sự tốt hơn? Nó ngăn cản chúng ta sử dụng các phương thức này làm tham số của các cuộc gọi phương thức khác. Nó cũng làm cho mã ít rõ ràng hơn và ít dễ hiểu hơn, IMO.
Một lựa chọn cuối cùng tôi sẽ trình bày là để thêm một tham số tổng quát để các phương pháp trong đó xác định các loại giá trị trả về:
T GetOrAdd<T> (string cachekey, Func<T> fnGetItem);
R[] GetOrAdd<T,R> (string cachekey, Func<IEnumerable<T>> fnGetItem);
// now we can do:
var customer = Cache.GetOrAdd("First", _ => context.Customers.First());
var customers = Cache.GetOrAdd<Customer,Customer>("All",() => context.Customers.ToArray());
Vì vậy, có thể sử dụng những gợi ý để giúp trình biên dịch để lựa chọn một tình trạng quá tải cho chúng tôi ... chắc chắn. Nhưng hãy nhìn vào tất cả các công việc phụ mà chúng ta phải làm khi nhà phát triển đến đó (chưa kể đến sự xấu xí và cơ hội được giới thiệu cho những sai lầm). Có thực sự đáng để nỗ lực không? Đặc biệt khi một kỹ thuật dễ dàng và đáng tin cậy (đặt tên cho các phương thức khác nhau) đã tồn tại để giúp chúng ta?
Tôi tự hỏi nếu viết ít được ưu tiên như một phương pháp mở rộng sẽ thực hiện công việc. Chưa được kiểm tra. –
Để làm phức tạp hơn, hãy lưu ý rằng có thể bạn sẽ không muốn loại trừ các mục 'chuỗi', ví dụ,' chuỗi' * thực hiện * thực hiện 'IEnumerable' và' IEnumerable '. –
herzmeister
Tôi vừa mới nhận thấy rằng trừ khi tôi thiếu một cái gì đó, không phải những khai báo nào sẽ biên dịch, như bạn đang nói 'void' * và * một kiểu trả về. Nếu không có 'void', bạn sẽ cố gắng quá tải trên kiểu trả về, đó là một không-không bỏ qua bất kỳ câu hỏi nào về generics. – AakashM