2012-02-08 34 views
59

Đoạn mã dưới đây sẽ cho bạn một lỗi biên dịch, như bạn mong muốn:Tại sao trình biên dịch C# cho phép một diễn viên rõ ràng giữa IEnumerable <T> và TAlmostAnything?

List<Banana> aBunchOfBananas = new List<Banana>(); 

Banana justOneBanana = (Banana)aBunchOfBananas; 

Tuy nhiên, khi sử dụng IEnumerable<Banana>, bạn chỉ nhận được một lỗi runtime.

IEnumerable<Banana> aBunchOfBananas = new List<Banana>(); 

Banana justOneBanana = (Banana)aBunchOfBananas; 

Tại sao trình biên dịch C# cho phép điều này?

+0

Một danh sách những thứ không phải là điều duy nhất. –

+2

Tuy nhiên, không được phép có loại giá trị. – ken2k

+1

Điều này cũng táo (ok áp dụng) để IList . Đó là những gì làm cho toàn bộ giao diện vs lớp bê tông nhấp cho tôi trong các câu trả lời dưới đây. – deepee1

Trả lời

48

tôi sẽ cho rằng đó là vì IEnumerable<T> là một giao diện, nơi một số thực hiện thể có một dàn diễn viên rõ ràng để Banana - bất kể có bao ngớ ngẩn đó sẽ là.

Mặt khác, trình biên dịch biết rằng List<T> không thể được truyền một cách rõ ràng đến Banana.

Lựa chọn tốt các ví dụ, nhân tiện!

Thêm ví dụ để làm rõ. Có lẽ chúng ta muốn có một số "đếm" mà nên luôn luôn chứa tại hầu hết các một đơn Banana:

public class SingleItemList<T>:Banana, IEnumerable<T> where T:Banana { 
    public static explicit operator T(SingleItemList<T> enumerable) { 
     return enumerable.SingleOrDefault(); 
    } 

    // Others omitted... 
} 

Sau đó, bạn thể thực sự làm điều này:

IEnumerable<Banana> aBunchOfBananas = new SingleItemList<Banana>(); 
Banana justOneBanana = (Banana)aBunchOfBananas; 

Vì nó cũng giống như viết phần sau, trình biên dịch hoàn toàn hài lòng với:

Banana justOneBanana = aBunchOfBananas.SingleOrDefault(); 
+4

Bạn không thể làm điều này - nó sẽ không tìm thấy nhà điều hành. Một ví dụ sẽ là 'lớp SingleBanana: Chuối, IEnumerable '. Đây là lý do tại sao điều này chỉ hoạt động cho các giao diện - bởi vì nếu cả hai loại là các lớp, điều này có thể được xác định là không thể tại thời gian biên dịch. – Random832

+0

@ Random832 Tốt bắt, nhưng trong thực tế chữ ký lớp học của bạn sẽ không làm việc hoặc là (bạn không thể có một chuyển đổi rõ ràng đến một lớp cơ sở). Tôi đã cập nhật câu trả lời của mình với một phiên bản hoạt động như dự định. – Yuck

+0

Er, quan điểm của tôi là bạn không thể sử dụng toán tử quá tải _at all_ cho điều này. Lý do nó hoạt động là vì một đối tượng lớp dẫn xuất là _inherently_ có thể chuyển đổi thành lớp cơ sở. – Random832

16

Có thể là do trình biên dịch biết rằng Banana không mở rộng List<T>, nhưng có khả năng một số đối tượng triển khai IEnumerable<T> cũng có thể mở rộng Banana và làm cho dàn diễn viên đó hợp lệ.

+1

Buồn để xem câu trả lời đúng, được đăng trong số những người đầu tiên, không nhận được nhiều upvotes. Có lẽ đoạn mã ngắn sẽ giúp ích. –

29

Khi bạn nói Y y = (Y)x; diễn viên này nói với trình biên dịch "tin tưởng tôi, bất cứ điều gì x là, lúc chạy nó có thể được đúc thành một Y, vì vậy, chỉ cần làm điều đó, được không?"

Nhưng khi bạn nói

List<Banana> aBunchOfBananas = new List<Banana>(); 
Banana justOneBanana = (Banana)aBunchOfBananas; 

trình biên dịch có thể nhìn vào các định nghĩa cho mỗi người trong các lớp bê tông (BananaList<Banana>) và thấy rằng không có static explicit operator Banana(List<Banana> bananas) định nghĩa (hãy nhớ, một diễn viên rõ ràng phải được xác định trong một trong hai loại được đúc hoặc loại được đúc, điều này là từ thông số kỹ thuật, phần 17.9.4). Nó biết tại thời gian biên dịch những gì bạn đang nói có thể không bao giờ là sự thật. Vì vậy, nó hét lên với bạn để ngăn chặn nói dối.

Nhưng khi bạn nói

IEnumerable<Banana> aBunchOfBananas = new List<Banana>(); 
Banana justOneBanana = (Banana)aBunchOfBananas; 

tốt, bây giờ là trình biên dịch không biết. Nó rất tốt có thể là trường hợp mà bất cứ điều gì aBunchOfBananas xảy ra được tại thời gian chạy, loại bê tông X có thể đã xác định static explicit operator Banana(X bananas). Vì vậy, trình biên dịch tin tưởng bạn, giống như bạn yêu cầu nó.

+0

Điều này đã được giảm giá? – jason

+0

Có thể vì bạn không đề cập đến việc bạn phân biệt giữa các lớp và số nguyên? Không biết điều đó, ví dụ thứ hai (X và Y) và thứ hai của bạn giống hệt nhau. –

+1

Có điều này bị downvoted ... nó sai. Không thể tìm thấy toán tử chuyển đổi khi chạy (trừ khi từ khóa 'dynamic' được tham gia). Có một downvote khác. –

0

Theo ngôn ngữ spec (6.2.4) "Quá trình chuyển đổi tài liệu tham khảo rõ ràng là: Từ bất kỳ đẳng cấp loại S để bất kỳ giao diện kiểu T, cung cấp S không được niêm phong và cung cấp S không thực hiện T .. . Quá trình chuyển đổi tài liệu tham khảo rõ ràng là những chuyển đổi giữa tài liệu tham khảo-loại mà đòi hỏi phải kiểm tra thời gian chạy để đảm bảo họ là chính xác ..."

Vì vậy, trình biên dịch không kiểm tra giao diện thực hiện trong quá trình biên dịch. Nó CLR trong thời gian chạy. Nó kiểm tra siêu dữ liệu cố gắng để tìm thấy sự hiện thực trong lớp học hoặc giữa các bậc cha mẹ của nó. Tôi không biết tại sao nó cư xử như thế này. Có lẽ phải mất rất nhiều thời gian. Vì vậy, mã này biên dịch một cách chính xác:

public interface IInterface 
{} 

public class Banana 
{ 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Banana banana = new Banana(); 

     IInterface b = (IInterface)banana; 
    } 
} 

Trong Mặt khác, nếu chúng ta cố gắng đúc chuối đến lớp, trình biên dịch kiểm tra siêu dữ liệu của nó và ném một lỗi:

FileStream fs = (FileStream)banana; 
Các vấn đề liên quan