2013-03-25 33 views
8

Tôi gặp một số rắc rối hiểu tại sao đoạn sau đây không cho tôi một lỗichế Generic cho hành động không hoạt động như mong đợi

public void SomeMethod<T>(T arg) where T : MyInterface 
{ 
    MyInterface e = arg; 
} 

Nhưng thế này, mà tôi mong chờ để làm việc do sự chung loại chế

private readonly IList<Action<MyInterface>> myActionList = new List<Action<MyInterface>>(); 

public IDisposable Subscribe<T>(Action<T> callback) where T: MyInterface 
{ 
    myActionList.Add(callback); // doesn't compile 
    return null 
} 

Cung cấp cho lỗi này

cannot convert from 'System.Action<T>' to 'System.Action<MyInterface>' 

tôi đang sử dụng sp1 VS2012 và .NET 4.5.

Bất cứ ai có thể giải thích lý do tại sao ràng buộc không cho phép điều này biên dịch?

+1

Tại sao danh sách của bạn chỉ đọc, và tại sao "mới IList'? Đó có phải là tuyên bố thực sự? –

+1

Các lớp học và đại biểu không giống nhau. 'System.Action ' đại diện cho một hàm với một tham số duy nhất của kiểu 'MyInterface' trong khi' System.Action 'đại diện cho một phương thức có tham số kiểu' T: MyInterface'. Các chữ ký chức năng không tương thích, nó không liên quan đến 'T' là một dẫn xuất của' MyInterface', chữ ký sẽ chỉ tương thích nếu 'T' chính xác là' MyInterface'. –

+0

@PaoloTedesco xin lỗi, nó được xây dựng lại và đơn giản hóa từ một số mã khác. Sao chép/Dán lỗi –

Trả lời

3

Các lớp học và đại biểu không giống nhau. System.Action<MyInterface> đại diện cho một hàm có một tham số duy nhất của loại MyInterface trong khi System.Action<T> đại diện cho một phương thức có tham số loại T : MyInterface. Chữ ký chức năng không tương thích, nó không liên quan đến số T là một dẫn xuất của MyInterface, chữ ký sẽ chỉ tương thích nếu T chính xác là MyInterface.

0

Nếu T được giới hạn trong một giao diện nhất định dù sao, bạn chỉ có thể sử dụng giao diện trong stead của nó:

public void SomeMethod(MyInterface arg) 
{ 
    MyInterface e = arg; 
} 

private readonly IList<Action<MyInterface>> myActionList = new IList<Action<MyInterface>>(); 

public IDisposable Subscribe(Action<MyInterface> callback) 
{ 
    myActionList.Add(callback); // does compile 
    return null 
} 

Sẽ làm việc và biên dịch và thực tế giống như những gì bạn có bây giờ.

Generics rất hữu ích nếu bạn muốn thực hiện cùng một hoạt động REGARDLESS của loại, nếu bạn sau đó giới hạn loại đối với một số giao diện bạn đã đánh bại mục đích của generics và có lẽ nên chỉ sử dụng giao diện đó thay thế.

+0

Tôi đoán anh ấy muốn có thể vượt qua các kiểu con của giao diện cụ thể đó .. ví dụ. MyOtherInterface: MyInterface –

+0

@Roger có thể đúng, tôi đã không nhận được điều đó từ OP nhưng tôi thừa nhận đó là một cách tiếp cận mà tôi cũng không xem xét. – Bazzz

4

Đây là một vấn đề contravariance - một Action<MyInterface> sẽ có thể thực hiện bất kỳ MyInterface dụ như một cuộc tranh cãi, tuy nhiên bạn đang cố gắng để lưu trữ một Action<T> nơi T là một số subtype của MyInterface, mà không phải là an toàn.

Ví dụ, nếu bạn có:

public class SomeImpl : MyInterface { } 
public class SomeOtherImpl : MyInterface { } 
List<Action<MyInterface>> list; 

list.Add(new Action<SomeImpl>(i => { })); 
ActionMyInterface act = list[0]; 
act(new SomeOtherImpl()); 

Bạn chỉ có thể gán một Action<T> một số Action<U> nếu loại T là 'nhỏ' hơn so với loại U. Ví dụ:

Action<string> act = new Action<object>(o => { }); 

là an toàn vì đối số chuỗi luôn hợp lệ khi đối số đối tượng là.

+0

+1 để được giải thích bằng ví dụ. – nawfal

1

Các where T: MyInterface hạn chế có nghĩa là "bất kỳ thể hiện của bất kỳ lớp hoặc struct mà thực hiện MyInterface".

Vì vậy, những gì bạn đang cố gắng làm có thể được đơn giản hóa như thế này:

Action<IList> listAction = null; 
Action<IEnumerable> enumAction = listAction; 

nào không có nghĩa vụ phải làm việc, trong khi vẫn IList : IEnumerable.Thông tin chi tiết có thể được tìm thấy ở đây:

http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx http://msdn.microsoft.com/en-us/library/dd799517.aspx

Vì vậy, nếu bạn thực sự cần phải sử dụng giao diện chung và không chỉ - bạn có thể làm điều đó như thế này, mặc dù nó cho biết thêm các vấn đề phức tạp và hiệu suất nhỏ:

public static IDisposable Subscribe<T>(Action<T> callback) where T : MyInterface 
{ 
    myActionList.Add(t => callback((T)t)); // this compiles and work 
    return null; 
} 
1

Lớp học và đại biểu có hành vi hơi khác một chút. Chúng ta hãy xem một ví dụ đơn giản:

public void SomeMethod<T>(T arg) where T : MyInterface 
{ 
    MyInterface e = arg; 
} 

Trong phương pháp này, bạn có thể giả định rằng T sẽ có ít nhất MyInterface, vì vậy bạn có thể làm một cái gì đó giống như MyInterface e = arg; này vì args luôn có thể được đúc để MyInterface.

Bây giờ chúng ta hãy xem làm thế nào các đại biểu cư xử:

public class BaseClass { }; 
public class DerivedClass : BaseClass { }; 
private readonly IList<Action<BaseClass >> myActionList = new List<Action<BaseClass>>(); 

public void Subscribe<T>(Action<T> callback) where T: BaseClass 
{ 
    myActionList.Add(callback); // so you could add more 'derived' callback here Action<DerivedClass> 
    return null; 
} 

Bây giờ we'r thêm DerivedClass callback để myActionList và sau đó ở đâu đó bạn gọi các đại biểu:

foreach(var action in myActionList) { 
    action(new BaseClass); 
} 

Nhưng bạn không thể làm điều đó, bởi vì nếu bạn có hàm gọi lại DerivedClass, bạn phải chuyển nó thành DerivedClass làm tham số.

Câu hỏi này đề cập đến Covariance and contravariance. Bạn có thể đọc về phương sai từ bài viết this, cũng Eric Lippert có bài viết rất intersting về phương sai, this là bài viết đầu tiên, bạn có thể tìm thấy phần còn lại trong blog của mình.

P.S. Chỉnh sửa accorind để Lee bình luận.

+0

Các lớp không thể biến đổi được - chỉ các đại biểu và giao diện mới có thể có chú thích phương sai.Nó cũng gây hiểu nhầm khi nói rằng các đại biểu 'có' sự mâu thuẫn - các loại đại biểu 'Func' có thể gây tranh cãi trong các đối số và biến đổi của họ trong các kiểu trả về của họ. Contravariance không bị giới hạn đối với các loại delegate, xem giao diện 'IObserver ', ví dụ, đó là contravariant trong 'T'. – Lee

+0

Tôi đã chỉnh sửa bài đăng của mình, cảm ơn bạn. – Andrew

2

Tôi thấy hữu ích trong những tình huống này để xem xét những gì xảy ra nếu bạn cho phép hành vi. Vì vậy, hãy xem xét điều đó.

interface IAnimal { void Eat(); } 
class Tiger : IAnimal 
{ 
    public void Eat() { ... } 
    public void Pounce() { ... } 
} 
class Giraffe : IAnimal 
... 
public void Subscribe<T>(Action<T> callback) where T: IAnimal 
{ 
    Action<IAnimal> myAction = callback; // doesn't compile but pretend it does. 
    myAction(new Giraffe()); // Obviously legal; Giraffe implements IAnimal 
} 
... 
Subscribe<Tiger>((Tiger t)=>{ t.Pounce(); }); 

Vậy điều gì sẽ xảy ra? Chúng tôi tạo ra một đại biểu mà có một con hổ và pounces, vượt qua đó để Subscribe<Tiger>, chuyển đổi đó để Action<IAnimal>, và vượt qua một con hươu cao cổ, sau đó pounces.

Rõ ràng điều đó là bất hợp pháp. Nơi duy nhất hợp lý để làm cho nó bất hợp pháp là chuyển đổi từ Action<Tiger> thành Action<IAnimal>. Vì vậy, đó là nơi nó là bất hợp pháp.

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