2010-10-08 27 views
6

Tôi có phương thức quá tải - lần triển khai đầu tiên luôn trả về một đối tượng duy nhất, việc triển khai thứ hai luôn trả về một điều tra.Tôi chặn một loại cụ thể bằng cách sử dụng các hạn chế chung

Tôi muốn làm cho các phương pháp chung quá tải, và hạn chế các trình biên dịch từ cố gắng để liên kết với các phương pháp không liệt kê khi loại chung là đếm được ...

class Cache 
{ 
    T GetOrAdd<T> (string cachekey, Func<T> fnGetItem) 
     where T : {is not IEnumerable} 
    { 
    } 

    T[] GetOrAdd<T> (string cachekey, Func<IEnumerable<T>> fnGetItem) 
    { 
    } 
} 

Để có được sử dụng cùng với ...

{ 
    // The compile should choose the 1st overload 
    var customer = Cache.GetOrAdd("FirstCustomer",() => context.Customers.First()); 

    // The compile should choose the 2nd overload 
    var customers = Cache.GetOrAdd("AllCustomers",() => context.Customers.ToArray()); 
} 

Đây có phải là mã không hợp lệ mà tôi đang xâm phạm ở đây hoặc có thể phân biệt các phương pháp trên để trình biên dịch luôn nhận được mã gọi không?

Phiếu bầu cho bất kỳ ai có thể sản xuất bất kỳ câu trả lời nào khác ngoài "đổi tên một trong các phương pháp".

+0

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. –

+3

Để 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

+0

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

Trả lời

2

Chỉ sử dụng một phương pháp và để nó phát hiện trường hợp IEnumerable<T> động hơn là cố gắng không thể thông qua các ràng buộc chung. Nó sẽ là "mã mùi" để có để đối phó với hai phương pháp bộ nhớ cache khác nhau tùy thuộc vào nếu đối tượng để lưu trữ/lấy là một cái gì đó enumerable hay không. Ngoài ra, chỉ vì nó thực hiện IEnumerable<T> không có nghĩa là nó nhất thiết phải là một bộ sưu tập.

+0

Tôi nghĩ bạn đã đóng góp khá nhiều. Sau khi phản ánh trên các câu trả lời được đăng, tôi tin rằng những gì tôi đã cố gắng là xấu mã nhỏ ... nếu mã gọi nên được oblovious để hoạt động khác nhau của phương pháp/s, sau đó sẽ không có sự khác biệt trong chữ ký phương pháp ... đó là ... nên có một phương pháp mà, như bạn đã chỉ ra, nó kiểm tra nội bộ. Cảm ơn bạn! – Mark

+0

@Mark - Tôi hy vọng điều đó sẽ hữu ích. Chỉ cần cố gắng ngăn bạn giải quyết vấn đề sai :). Câu trả lời được chấp nhận? –

1

ràng buộc không hỗ trợ loại trừ, điều này có vẻ gây khó chịu lúc đầu, nhưng nhất quán và có ý nghĩa (xem xét, ví dụ: giao diện không ra lệnh triển khai không thể làm).

Điều đó đang được nói, bạn có thể chơi xung quanh với những hạn chế của quá tải IEnumerable của bạn ... có thể thay đổi phương pháp của bạn để có hai kiểu gõ chung <X, T> với một ràng buộc như "where X : IEnumerable<T>"?

ETA mẫu mã sau:

void T[] GetOrAdd<X,T> (string cachekey, Func<X> fnGetItem) 
      where X : IEnumerable<T> 
    { 
    } 
8

Đổi tên một trong những phương pháp này. Bạn sẽ nhận thấy rằng List<T> có một phương thức Add và AddRange; theo mô hình đó. Làm một cái gì đó cho một mục và làm một cái gì đó để một chuỗi các mục là nhiệm vụ hợp lý khác nhau, do đó, làm cho các phương pháp có tên khác nhau.

+1

Dường như OP đang thiết kế giao diện bộ nhớ đệm. Có viết một bản thân mình, tôi tin rằng bộ nhớ cache không nên quan tâm liệu đối tượng cố gắng được lưu trữ là một bộ sưu tập hay không; đó chỉ là một chi tiết thực hiện của quá trình tuần tự hóa. Chỉ có một phương pháp là đủ mọi nhu cầu ở đây, theo ý kiến ​​của tôi. –

+1

Ok, tôi thừa nhận điều đó. Tôi LOL. – MojoFilter

+0

@MojoFilter: Bạn LOL muốn làm gì? –

5

Đâ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:

  1. Thay đổi tên của một trong những phương pháp .
  2. Truyền tới IEnumerable<T> bất cứ nơi nào bạn gọi là quá tải thứ hai.
  3. 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?

+0

Một câu trả lời tuyệt vời, được giải thích tốt ... cảm ơn bạn! – Mark

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