2013-02-15 36 views
6

Chỉ cần tò mò, tại sao trình biên dịch xử lý một loại generic không bị ràng buộc nào khác với nó sẽ là typeof (đối tượng)?Các quy tắc của generics và các ràng buộc loại

class Bar { } 

class Foo 
{ 
    void foo(object thing) 
    { 
     ((Bar)thing).ToString(); 
    } 
} 

class Foo<T> 
{ 
    void foo(T thing) 
    { 
     ((Bar)thing).ToString(); 
    } 
} 

Ở trên, truyền "T" tới Bar dẫn đến lỗi trình biên dịch. Đúc "đối tượng điều" để Bar tuy nhiên là một cái gì đó trình biên dịch cho phép tôi làm, tại nguy cơ của riêng tôi tất nhiên.

Điều tôi không thấy là lý do. Trong .net đối tượng sau khi tất cả là một catch-tất cả và thời gian chạy loại có thể là một giá trị đóng hộp hoặc một đối tượng của bất kỳ loại. Vì vậy, tôi không thấy lý do hợp lý có cho trình biên dịch để phân biệt giữa hai trường hợp. Điều tốt nhất tôi có thể làm là một cái gì đó như "lập trình viên mong đợi trình biên dịch kiểm tra kiểu với các kiểu generic, nhưng không phải với đối tượng". :) Có phải đó là tất cả để có nó?

Btw, Tôi biết rằng tôi vẫn có thể được dàn diễn viên của tôi được thực hiện trong trường hợp Foo, bằng cách đơn giản viết

((Bar)(object)thing).ToString(); 

Tôi chỉ muốn hiểu tại sao các trình biên dịch thực hiện điều này ...

+3

Thời gian biên dịch có hợp pháp để bạn đưa 'int' vào' Bar' không? Khi bạn điền vào tham số kiểu đó với 'int', nên nó * sau đó * bắt đầu có lỗi trình biên dịch? Điều gì xảy ra nếu hội đồng không phải là của bạn, vì vậy bạn không thể nhìn thấy vấn đề? T không phải là đối tượng. Đó là một cái gì đó cực kỳ cụ thể. –

+1

Bạn có biết rằng bạn có thể nói 'class Foo trong đó T: Bar' to _ensure_ mà' T' luôn có thể được truyền sang 'Bar'? – Rawling

+1

Tôi chắc rằng Eric Lippert có một bài đăng blog về điều này ở đâu đó, nhưng tôi không thể tìm thấy nó ... –

Trả lời

4

Các ý nghĩa ở đây là object. Nếu ví dụ đầu tiên là , bất kỳ điều gì khác ngoài object nó sẽ hoạt động giống nhau. Về cơ bản, những gì bạn đang nói vào lúc này đây:

(Bar)thing 

là: "chuyển đổi một T đến một Bar"; đó là hư không gần pháp lý trong trường hợp chung. Bằng cách thêm object bạn thực hiện nó:

(Bar)(object)thing 

đó là "chuyển đổi một T một object ..." - mà luôn luôn là hợp pháp, vì object là gốc rễ của tất cả các loại quản lý; và lưu ý điều này có thể đưa ra một hộp - "... và sau đó truyền số object làm Bar" - một lần nữa; nó luôn luôn là hợp pháp tại thời gian biên dịch, và liên quan đến một loại kiểm tra ("unbox-bất kỳ") tại thời gian chạy.

Ví dụ: giả sử TDateTime ...

DateTime thing = ... 
Bar bar = (Bar)(object)thing; 

là hoàn toàn hợp lệ; chắc chắn nó sẽ thất bại trong thời gian chạy, nhưng: đây là kịch bản bạn cần phải ghi nhớ.

+0

Trong khi chính xác, tôi nghĩ OP đã yêu cầu quyết định thiết kế đằng sau sự lựa chọn để lộ lỗi tại thời gian * biên dịch cho trường hợp sau nhưng tại thời gian * chạy * đối với trường hợp cũ (điều giả định không chuyển đổi thành 'Bar'. –

+0

Cả hai (@Marc, @Ron) đều phát hiện ra.Tôi nghĩ đây là một lời giải thích tốt.Để đối tượng đúc với thứ gì đó luôn luôn là một downcast, một cái gì đó cụ thể hơn, nhưng trường hợp chung có thể là một trong hai, và do đó * được xây dựng * loại sẽ không biên dịch cho bất kỳ T - đó là một lý do chính đáng để đưa ra lỗi biên dịch! –

4

Nó đi xuống các ngữ nghĩa và mục đích của việc tạo ra generics. Nếu bạn có một loại T chung, trình biên dịch sẽ không cho phép bạn tùy ý truyền trực tiếp nó đến bất kỳ đối tượng nào khác. Điều này có ý nghĩa vì mục đích của T là ép buộc lập trình viên xác định loại T thực sự là gì. Nó sẽ không phải là "đối tượng", nó sẽ là một LOẠI đối tượng cụ thể. Tại thời gian biên dịch, trình biên dịch không thể biết những gì sẽ có trong T và do đó không thể cast nó.

Truyền từ đối tượng hoạt động vì đó là một đối tượng ẩn danh - như trái ngược với loại đối tượng KNOWN được xác định trong cách sử dụng của nó.

Điều này có thể được mở rộng với mệnh đề "where". Ví dụ., bạn có thể chỉ định rằng T phải thuộc loại IBar;

interface IBar { } 

class Bar : IBar { } 

class Foo<T> 
    where T : IBar 
{ 
    void foo(T thing) 
    { 
     ((IBar)thing).ToString(); 
    } 
} 

Thừa kế cũng hoạt động với mệnh đề where;

class Bar { } 

class Foo<T> 
    where T : Bar 
{ 
    void foo(T thing) 
    { 
     // now you don't need to cast at all as the compiler knows 
     // exactly what type T is (at a parent level at least) 
     thing.ToString(); 
    } 
} 
+0

Tôi rất vui khi đánh dấu câu trả lời này, mặc dù tôi thấy số tiền lý luận của bạn ít hơn đề xuất ban đầu của tôi; theo mong đợi của "người dùng" (người dùng của lớp chung của tôi, hoặc đúng hơn là một lớp được xây dựng mà T đã được định nghĩa) .Để nói rằng "T không chỉ là một đối tượng, nó là một cái gì đó cụ thể" là, IMHO, Bất kỳ đối tượng nào có thể, và thường là, một cái gì đó rất lớn ecific. Và có nhiều cách khác nhau để xem cùng một đối tượng. Tôi có thể muốn hỗ trợ bất kỳ loại nào, nhưng hành xử theo một cách cụ thể nếu loại là một trong những tôi biết, ví dụ - lớp chung chung hay không. –

+0

@ TheDag Thay đổi hành vi chung chung dựa trên loại T thường không phải là ý tưởng hay mặc dù có các trường hợp hợp lệ để xử lý các loại cụ thể, ví dụ: các loại được đóng hộp hoặc không được hộp. Trong ví dụ của bạn, bạn cố gắng đi trực tiếp từ T mà theo mặc định là gần như chắc chắn sẽ là một loại cụ thể cho một loại cụ thể 'Bar'. Điều này sẽ là cố gắng để cố gắng làm '(Foo) Bar' khi không có mối quan hệ hoặc giao diện chung. Tôi vẫn nghĩ cảnh báo trình biên dịch là một cảnh báo hợp lệ – DiskJunky

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