2010-02-09 39 views
15

Tôi tự hỏi liệu có cách nào để có thể sử dụng các phương thức giao diện chung bằng cách nào đó trong C#? Tôi đã tìm thấy những câu hỏi tương tự, nhưng không có gì chính xác như thế này. Bây giờ tôi nghi ngờ rằng câu trả lời là "Không, bạn không thể" nhưng tôi muốn có nó xác nhận.Chuyên môn về giao diện chung của C#

Những gì tôi có là một cái gì đó như sau.

public interface IStorage 
{ 
    void Store<T>(T data); 
} 

public class Storage : IStorage 
{ 
    public void Store<T>(T data) 
    { 
     Console.WriteLine("Generic"); 
    } 

    public void Store(int data) 
    { 
     Console.WriteLine("Specific"); 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     IStorage i = new Storage(); 
     i.Store("somestring"); // Prints Generic 
     i.Store(1); // Prints Generic 
     Storage s = (Storage)i; 
     s.Store("somestring"); // Prints Generic 
     s.Store(1); // Prints Specific 
    } 
} 

Có cách nào để sử dụng phiên bản Cửa hàng chuyên dụng khi được gọi thông qua giao diện không? Và nếu không, không ai biết lý do chính xác tại sao C# xử lý các đối số chung theo cách này?

Chỉnh sửa: Sự cố có thể được khắc phục nếu không phải vì vậy C# không thể giải quyết đối số mẫu trong nhiều bước.

void Foo<T>(T t) 
{ 
    SubFoo(t); 
} 

void SubFoo<T>(T t) 
{ 
    Console.WriteLine("Generic"); 
} 

void SubFoo(int t) 
{ 
    Console.WriteLine("Specific"); 
} 

Cuộc gọi tới Foo (1) ở đây cũng sẽ in "Chung", trình biên dịch có thể giải quyết vấn đề này không? Hoặc JIT có ngăn chặn điều này không?

+0

Bản sao có thể có của [Cách thực hiện chuyên môn mẫu trong C#] (http://stackoverflow.com/questions/600978/how-to-do-template-specialization-in-c-sharp) –

Trả lời

18

Độ phân giải quá tải được thực hiện tại thời gian biên dịch, không phải tại thời gian chạy dựa trên loại giá trị được truyền thực tế.

IStorage i = new Storage(); 
i.Store("somestring"); // Prints Generic 
i.Store(1); // Prints Generic 

này sẽ luôn luôn gọi phương thức "chung chung", bởi vì chỉ có một tình trạng quá tải của Store trong IStorage và trình biên dịch không biết rằng i thực sự chứa một đối tượng Storage. Làm thế nào để trình biên dịch biết về tình trạng quá tải khác trong Storage?

Storage s = (Storage)i; 
s.Store("somestring"); // Prints Generic 
s.Store(1); // Prints Specific 

Ở đây, trình biên dịch biết rằng s chứa một đối tượng Storage (hoặc một trong bắt nguồn từ Storage), bởi vì s được khai báo như vậy. Vì vậy, nó thấy hai tình trạng quá tải. Nó chọn mức quá tải cụ thể cho các giá trị int, bởi vì các quy tắc về độ phân giải quá tải cho biết muốn quá tải cụ thể về quá tải chung.


Về mặt kỹ thuật, có thể xác định typeof(T) trong phương pháp chung vào thời gian chạy và chuyển tiếp cuộc gọi phương thức đến phương thức cụ thể. Nhưng nếu bạn nghĩ về nó, điều này không có ý nghĩa gì nhiều. Một phương thức chung có nghĩa là việc triển khai thực hiện giống nhau cho các đối số của các kiểu khác nhau, không liên quan. Nếu bạn muốn triển khai khác nhau cho các loại khác nhau, bạn không nên sử dụng Generics cho việc này.


void Foo<T>(T t) 
{ 
    SubFoo(t); 
} 

void SubFoo<T>(T t); 
void SubFoo(int t); 

Generics làm việc khá một chút khác biệt so với C++ mẫu. Trình biên dịch C# biên dịch Foo chỉ một lần - thành một phương thức chung. Hãy nhớ rằng: generic có nghĩa là thực hiện giống nhau cho các loại khác nhau. Trình biên dịch C# không biết tại thời gian biên dịch nếu T là int hoặc string hoặc bất kỳ loại nào khác. Vì vậy, chỉ có thể thực hiện Foo mà làm việc cho bất kỳ T là gọi SubFoo <T>. Nếu một trong những quá tải SubFoo sẽ được gọi tùy thuộc vào T, việc thực hiện Foo sẽ không giống nhau cho tất cả T nữa.

+0

+1 giải thích tuyệt vời. –

+0

Điểm chung của T là gì? Tại sao không chỉ nhập tham số dữ liệu làm đối tượng và kiểm tra nếu (dữ liệu là int)? –

+0

Điều đó làm cho rất nhiều ý nghĩa khi suy nghĩ về nó từ góc độ trình biên dịch. Mặc dù tôi có một bộ nhớ mơ hồ C++ có thể thực hiện điều này bằng cách nào đó, mặc dù tôi không thể làm cho nó hoạt động ngay bây giờ. Tôi sẽ chỉnh sửa câu hỏi của tôi một chút để giải thích thêm một vấn đề ngăn cản một cách giải quyết vấn đề này, tôi hy vọng bạn có thể giải thích điều đó, mặc dù tôi sẽ chấp nhận câu trả lời này :) – Runeborg

0

Bạn có thể làm một cái gì đó như thế này:

public interface IStorage<T> 
{ 
    void Store(object data); 
    void Store<T>(T data); 
} 

public class Storage : IStorage<int> 
{ 
    public void Store(object data) 
    { 
     Console.WriteLine("Generic"); 
    } 

    public void Store(int data) 
    { 
     Console.WriteLine("Specific"); 
    } 
} 

Bạn đã gõ i như IStorage và giao diện mà không định nghĩa phương pháp cửa hàng quá tải.

1

Nếu bạn muốn tận dụng lợi thế của độ phân giải quá tải thời gian biên dịch bạn cũng có thể mở rộng giao diện với một phương pháp mà phải mất một int:

public interface IStorage 
{ 
    void Store<T>(T data); 
} 

public interface IIntStorage: IStorage 
{ 
    void Store(int data); 
} 

public class Storage : IIntStorage 
{ 
    public void Store<T>(T data) 
    { 
     Console.WriteLine("Generic"); 
    } 

    public void Store(int data) 
    { 
     Console.WriteLine("Specific"); 
    } 
} 

Bây giờ nếu bạn gọi Store(1) thông qua giao diện IIntStorage nó sẽ sử dụng phương pháp chuyên ngành (tương tự như cách bạn gọi trực tiếp phương thức của Storage), nhưng nếu bạn gọi nó qua IStorage, nó vẫn sẽ sử dụng phiên bản chung.

+0

Tôi muốn có một giao diện chung và không phải là một lớp chuyên biệt cho từng loại :) – Runeborg

-1

Vì C# generics là các mẫu thời gian chạy trong một số trường hợp, bạn nên sử dụng chuyên môn thời gian chạy. Ví dụ, trong các phương thức tĩnh chung, kế thừa và các giao diện không thể sử dụng được. Nếu bạn muốn chuyên phương pháp tĩnh chung - trong phương pháp khuyến nông đặc biệt - bạn phải phát hiện các loại trong mã với cấu trúc như sau:

if (typeof (T) == typeof (bool))

Đối với chuyên môn của các loại tham chiếu (như chuỗi chẳng hạn) và dữ liệu tham số T, bạn sẽ đặt trước:

string s = data as string; nếu (s! = Null)

Trong ví dụ này, một vấn đề xuất phát từ chuyển đổi giữa T và bool trong mã chuyên biệt: Bạn biết rằng T là bool nhưng ngôn ngữ không cho phép chuyển đổi giữa các loại đó. Các giải pháp đến từ các loại đối tượng: một đối tượng có thể được đúc thành bất kỳ loại (chuyển đổi được kiểm tra tại thời gian chạy và không phải lúc biên dịch thời gian trong trường hợp này). vì vậy nếu bạn có

Dữ liệu T;

bạn có thể viết:

bool b = (bool) (đối tượng) dữ liệu; dữ liệu = (T) (đối tượng) b;

Điều này không hoàn hảo: Nếu loại bình đẳng là khá nhanh, trong một số trường hợp, bạn phải kiểm tra xem T có phải là kiểu bắt nguồn của một loại được chỉ định (lâu hơn một chút) không. Và khi T là một kiểu giá trị như bool, cast to object và sau đó quay trở lại gõ kiểu giá trị trung bình là boxing/unboxing và kiểm tra kiểu thời gian chạy cho các kiểu tham chiếu. Trình tối ưu hóa thời gian chạy có thể loại bỏ các bước không cần thiết này nhưng tôi không thể nói nếu chúng làm như vậy.

Tùy thuộc vào cách sử dụng phương pháp tĩnh của bạn, hãy nhớ rằng bạn có thể áp dụng nơi T: ... hạn chế đối với các loại được tham số. Và giá trị mặc định (T) đó trả về false cho boolean, 0 cho các kiểu cơ sở số và null cho các kiểu tham chiếu.

Chuyên môn thời gian chạy ngụ ý các bước kiểm tra bổ sung và kiểm tra kiểu đánh đấm/bỏ hộp/thời gian chạy, vì vậy nó không phải là thuốc chữa bách bệnh mà cho phép các phương pháp chuyên dùng chung trong một thời gian chấp nhận được trong nhiều trường hợp: hoặc khi ẩn hoặc nhóm các loại quản lý phức tạp thì quan trọng hơn là biểu diễn.

+0

Trước khi truyền từ 'T' hoặc kiểm tra kiểu của' T', bạn nên kiểm tra loại 'T'. Nếu 'T' là một kiểu giá trị không nullable, một vị trí lưu trữ kiểu' T' sẽ giữ một thể hiện kiểu 'T'; gọi 'GetType()' trên thứ ở vị trí lưu trữ sẽ yêu cầu boxing nó, nhưng việc kiểm tra kiểu 'T' thì không. Lưu ý rằng ngay cả khi 'T' không phải là một kiểu giá trị, có thể là một vị trí lưu trữ kiểu' T' sẽ giữ một tham chiếu đến một thể hiện kiểu giá trị được đóng hộp. – supercat

+0

Toàn bộ điểm của câu hỏi là để tránh truyền. – Runeborg

2

Tại sao chuyên môn dựa trên mã chung có ý nghĩa rất nhiều trong thế giới thực và đặc biệt, trong các phương pháp mở rộng?

Tôi sẽ lấy một ví dụ về các bộ sưu tập vì evrybody đã thu thập được nhiều hoặc ít hơn các bộ sưu tập .NET.

Tôi sẽ lấy ví dụ đơn giản về phương pháp mở rộng .Last(this IEnumerable<<T>> coll). Trong .NET Framework, phương pháp này sử dụng chuyên môn kiểu mã hóa.

Đầu tiên, liên quan đến lợi ích của chuyên môn kiểu, ví dụ này khá rõ ràng.Một số bộ sưu tập có thể đếm được cần quét toàn bộ bộ sưu tập và trả về phần tử cuối cùng, mảng dựa trên chỉ cần trả về phần tử được phân bổ cuối cùng của mảng, nhiều danh sách liên kết có con trỏ đến phần tử cuối cùng ... có thể làm cho phương pháp .Last() hiệu quả hơn nhiều.

Thứ hai vì phương pháp này là tĩnh, có nhiều triển khai cho từng loại bộ sưu tập hoặc giao diện sẽ không giải quyết được vấn đề lựa chọn phương pháp đúng. Trong thực tế, lựa chọn phương pháp đúng được thực hiện tại thời gian biên dịch dựa trên loại đối tượng coll rõ ràng. Nếu bạn tưởng tượng, bạn muốn áp dụng các phương pháp mở rộng liên tiếp trên List<<T>>, phương thức đầu tiên có thể không cần nhiều cho mỗi lần triển khai chuyên biệt về loại bộ sưu tập và sử dụng một phương thức duy nhất dựa trên IEnumerable<<T>>. Vì vậy, ngay cả khi chúng tôi có một .Last(this List<<T>> coll), phương pháp mở rộng không chuyên biệt đầu tiên sẽ trả lại một IEnumerable<<T>> và chuyên ngành .Last(this List<<T>> coll) sẽ không được sử dụng cho List<<T>>. Vì vậy, nếu mã của bạn sử dụng các hội đồng bên ngoài (ngay cả .NET Framework), nếu bạn phải cung cấp một giải pháp trong hai tuần cho một vấn đề kiến ​​trúc phức tạp ... bạn rời khỏi miền hoàn hảo để bước vào thế giới thực . Và loại chuyên môn chung trở thành một tùy chọn không bỏ qua.

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