2012-01-18 32 views
19

Tôi có một tình huống mà tôi muốn hành vi của trình biên dịch được giải thích. Với một mã nhỏ:Từ khóa kín ảnh hưởng đến ý kiến ​​của trình biên dịch trên một diễn viên

interface IFoo<T> 
{ 
    T Get(); 
} 

class FooGetter : IFoo<int> 
{ 
    public int Get() 
    { 
     return 42; 
    } 
} 

Các biên dịch và chạy sau:

static class FooGetterGetter 
{ 
    public static IFoo<T> Get<T>() 
    { 
     return (IFoo<T>)new FooGetter(); 
    } 
} 

Nếu chúng ta thực hiện thay đổi chữ ký của lớp Foo và thêm sealed keyword:

sealed class FooGetter : IFoo<int> // etc 

Sau đó, tôi gặp lỗi trình biên dịch trên dòng sau:

return (IFoo<T>)new FooGetter(); 

Trong:

Không thể chuyển đổi loại 'MyNamespace.FooGetter' thành 'MyNamespace.IFoo <T>'

Ai đó có thể giải thích những gì đang xảy ra ở đây liên quan đến từ khóa sealed? Đây là C# 4 chống lại một dự án .NET 4 trong Visual Studio 2010.

Cập nhật: thú vị đủ Tôi vấp vào một phần của hành vi đó khi tôi đã tự hỏi tại sao đoạn mã sau sửa chữa nó khi sealed được áp dụng:

return (IFoo<T>)(IFoo<int>)new FooGetter(); 

cập nhật: chỉ cần làm rõ, tất cả chạy tốt khi loại T yêu cầu cũng giống như các loại T sử dụng bởi các loại bê tông. Nếu các loại khác nhau, các diễn viên thất bại trong thời gian chạy với một cái gì đó như:

Không thể cast đối tượng của loại 'MyNamespace.StringFoo' gõ 'MyNamespace.IFoo`1 [System.Int32]

Trong ví dụ trên, StringFoo : IFoo<string> và người gọi yêu cầu nhận int.

+2

Tôi không có câu trả lời, nhưng tôi sẽ tưởng tượng rằng nó có cái gì để làm với thực tế rằng 'IFoo ' là một kiểu generic mở trong khi 'dụng cụ FooGetter' 'IFoo ' là loại chung đóng. –

+1

Chỉ cần lưu ý: Tôi đã chắc chắn rằng mình đã có hành vi được xác định trước khi đăng câu hỏi - không muốn tự lừa dối mình :-) Tôi có thể thấy lý do tại sao nó có thể được cho phép, trình biên dịch không thể đảm bảo những gì đang diễn ra chỉ biết rằng nó có cơ hội thành công. Nhưng vì một lý do nào đó, nó loại bỏ cơ hội thành công tương tự khi từ khóa được niêm phong hiện diện do nó giả định rằng vì nó không thể được bắt nguồn, nó không thể khớp với T. –

+0

+1 Thú vị :) – leppie

Trả lời

8

FooGetter là triển khai rõ ràng IFoo<int> thay vì triển khai IFoo<T> một cách tổng quát. Kể từ khi nó được niêm phong, trình biên dịch biết không có cách nào để đúc nó vào một chung IFoo<T> nếu T là bất cứ điều gì khác hơn là một int. Nếu nó không được niêm phong, trình biên dịch sẽ cho phép nó biên dịch và ném một ngoại lệ vào thời gian chạy nếu T không phải là int.

Nếu bạn cố gắng sử dụng nó với bất cứ điều gì khác hơn là một int (ví dụ FooGetterGetter.Get<double>();) bạn sẽ có được một ngoại lệ:

Không thể cast đối tượng của loại 'MyNamespace.FooGetter' gõ 'MyNamespace.IFoo`1 [System.Double] '.

Điều tôi không chắc chắn là tại sao trình biên dịch thực hiện không phải phát sinh lỗi cho phiên bản không được niêm phong.Làm cách nào để phân loại phụ của bạn là FooGetter sao cho new FooGetter() cung cấp cho bạn bất kỳ thứ gì thực hiện IFoo<{something_other_than_int}>?

Cập nhật:

mỗi Dan BryantAndras Zoltan có những phương pháp để trả lại một lớp có nguồn gốc từ một nhà xây dựng (hoặc có thể chính xác hơn cho trình biên dịch để trở lại một kiểu khác nhau bằng cách phân tích các thuộc tính). Vì vậy, về mặt kỹ thuật, điều này là khả thi nếu lớp học không được niêm phong.

+4

+1 nhưng để trả lời câu hỏi cuối cùng của bạn - tốt - đó là giao diện - vì vậy bạn có thể thêm một phiên bản giao diện khác vào? Trình biên dịch không nhìn thấy 'mới' nó chỉ thấy một biểu thức của kiểu' FooGetter' mà, khi không 'niêm phong' có thể là bất kỳ kiểu nào có nguồn gốc từ nó. –

+0

@DStanley Tôi thấy quan điểm của bạn. Lời giải thích của bạn sau đó cũng áp dụng cho việc truyền hai lần để có được từ khóa kín. Trong tình huống cast-two, bạn biết rằng bạn có thể cast thành 'IFoo ' nếu kiểu bê tông sử dụng 'int', bạn có thể cast từ' IFoo 'sang' IFoo 'vì cùng một lý do trình biên dịch cho phép bạn làm 'FooGetter' đến' IFoo '(không được niêm phong) - vì một lý do nào đó nó cho phép bất kỳ lỗi nào xảy ra khi chạy. –

+3

Để xác định điểm của Andras, bạn có thể có 'SecondFoo: FooGetter, IFoo '. Tò mò là lý do tại sao nó bỏ qua 'mới' trong trường hợp đó, đảm bảo không có các kiểu dẫn xuất ... trừ khi nó giả định rằng một kiểu cơ sở có thể có thể thực hiện giao diện chính xác và không làm phiền các lỗi tìm kiếm - để lại thời gian chạy. –

4

Khi một lớp trong niêm phong bất kỳ lớp được thừa kế có thể thực hiện IFoo<T>:

class MyClass : FooGetter, IFoo<double> { } 

Khi FooGetter được đánh dấu là niêm phong, trình biên dịch biết rằng nó không thể là có thể cho bất kỳ triển khai thêm IFoo<T> khác hơn IFoo<int> có thể tồn tại cho FooGetter.

Đây là hành vi tốt, nó cho phép bạn nắm bắt các vấn đề với mã của bạn tại thời gian biên dịch thay vì khi chạy.

Lý do mà (IFoo<T>)(IFoo<int>)new FooGetter(); hoạt động là vì bạn hiện đang đại diện cho lớp niêm phong của mình là IFoo<int> có thể được thực hiện bởi bất kỳ thứ gì. Nó cũng là một công việc tốt đẹp xung quanh như bạn không phải là vô tình, nhưng cố ý ghi đè kiểm tra trình biên dịch.

1

Chỉ cần thêm vào các câu trả lời hiện có: Điều này thực sự không liên quan gì đến các generics được sử dụng.

Hãy xem xét ví dụ này đơn giản hơn:

interface ISomething 
{ 
} 

class OtherThing 
{ 
} 

Sau đó nói (bên trong một phương pháp):

OtherThing ot = XXX; 
ISomething st = (ISomething)ot; 

công trình tốt. Trình biên dịch không biết nếu một OtherThing có thể là một ISomething, do đó, nó tin rằng chúng tôi khi chúng ta nói nó sẽ thành công. Tuy nhiên, nếu chúng tôi thay đổi OtherThing thành loại kín (cụ thể là sealed class OtherThing { } hoặc struct OtherThing { }), thì không được phép truyền nữa. Trình biên dịch biết rằng nó không thể hoạt động tốt (trừ khi otnull, nhưng các quy tắc của C# vẫn không cho phép các diễn viên từ một loại niêm phong đến một giao diện không được thực hiện bởi loại niêm phong đó).

Về việc cập nhật câu hỏi: Viết (IFoo<T>)(IFoo<int>)new FooGetter() không khác nhiều so với viết (IFoo<T>)(object)new FooGetter(). Bạn có thể "cho phép" bất kỳ diễn viên nào (với Generics hoặc không có) bằng cách đi qua một số loại trung gian đó là chắc chắn/có thể là một tổ tiên cho cả hai loại bạn muốn chuyển đổi giữa. Nó rất giống nhiều để mô hình này:

void MyMethod<T>(T t) // no "where" constraints on T 
{ 
    if (typeof(T) = typeof(GreatType)) 
    { 
    var tConverted = (GreatType)(object)t; 
    // ... use tConverted here 
    } 
    // ... other stuff 
} 
Các vấn đề liên quan