2013-02-08 40 views
1

我實現了一些依賴項(它們是MVP模式的一部分)。現在,當我嘗試執行投射時,VS會通知有關錯誤。C#3.0隱含類和具有泛型類型的接口的轉換錯誤

定義:

interface IView 
{ 
    void setPresenter(IPresenter<IView> presenter); 
} 

interface IViewA : IView 
{ 
} 

interface IPresenter<T> where T : IView 
{ 
    void setView(T view); 
} 

class PresenterA : IPresenter<IViewA> 
{ 
} 

隱式轉換:

IPresenter<IView> presenter = new PresenterA(); 

編譯錯誤: 無法隱式轉換類型 'PresenterA' 到 'IPresenter'。一個顯式轉換存在(是否缺少強制轉換?)

明確的轉換:

IPresenter<IView> presenter = (IPresenter<IView>)new PresenterA(); 

運行時錯誤:InvalidCastException的

我怎樣才能解決這個問題,以保持這一概念?泛型類型的概念(我的前一個沒有它)。我已經嘗試過其他帖子中提到的變異和逆變問題(進出),但也有錯誤(根據VS 2010)。

+0

你不能,約束是沒有意義的。 – millimoose 2013-02-08 18:58:28

+0

哪個約束? IPresenter中包含的通用類型? – Bronek 2013-02-08 19:02:57

+0

我去了它的細節,在[我的回答]例子(http://stackoverflow.com/a/14779661/41655)。 – millimoose 2013-02-08 19:10:41

回答

4

IViewAIView派生的事實並不表示IPresenter<IViewA>派生自IPresenter<IView>。實際上IPresenter<IViewA>IPresenter<IView>是兩個不同的類型,它們之間沒有繼承關係。他們唯一的共同祖先是object

讓我們來看一個例子。假定我們有一個Animal類別,類別來自Animal,而Dog類別來自Animal。現在讓我們聲明兩個列表

List<Animal> animals; 
List<Cat> cats = new List<Cat>(); 

而且,我們還假設下面的任務是可能的:

animals = cats; 
animals.Add(new Cat()); // OK 
animals.Add(new Dog()); // Ooops! 

這份名單是在現實中的貓名單,我們正在嘗試添加一隻狗!因此List<Animal>List<Cat>這兩種類型不允許分配兼容。

1

通過將PresenterA存儲在IPresenter<IView>中,表示「此對象的方法setView」接受任何IView「。

但是,PresenterA的方法setView只接受IViewA。如果你通過它IViewSomethingElse,你會期望發生?

它不起作用,所以編譯器不允許它。

0

你想要做的事情沒有意義。想象一下以下(包括一個變化的thingie這是有道理的):現在

interface IView {} 

interface IViewA : IView {} 
class ViewA : IViewA {} 

interface IViewB : IView {} 
class ViewB : IViewB {} 

interface IPresenter<in T> where T : IView 
{ 
    void setView(T view); 
} 

class PresenterA : IPresenter<IViewA> 
{ 
    public void setView(IViewA view) {} 
} 

class PresenterB : IPresenter<IViewB> 
{ 
    public void setView(IViewA view) {} 
} 

,如果轉換你正在試圖做的是有效的,你可以這樣做:

IPresenter<IView> presenter = new PresenterA(); 
presenter.setView(new ViewB()); 

,你可以看,這不是類型安全的。即你相信這些類型之間的關係不存在。

讓什麼變化你做的是相反的:

class Presenter : IPresenter<IView> 
{ 
    public void setView(IView view) {} 
} 

IPresenter<IViewA> presenter = new Presenter(); 

Presenter.setView()可以接受任何IView參數,因此可以接受IViewB。這也是編譯器提到顯式轉換的原因。這是爲了讓你做到以下幾點:

IPresenter<IViewA> presenterA = new Presenter(); 
IPresenter<IView> presenter = (IPresenter<IView>) presenterA; 

也就是說,要檢查,如果你在運行時分配給presenter值恰好是這是「通用不夠」,即使其編譯時類型ISN」噸。

2

這個問題實際上是關於泛型類型的協變和逆變。我們有

an IViewA is an IView

但這並不自動意味着

an IPresenter<IViewA> is an IPresenter<IView>

的結論成立,我們說IPresenter<T>T。在C#中一個通過把一個out關鍵字有問題的類型參數(這裏T)之前,爲使接口協:

interface IPresenter<out T> ... 

既然你沒有把out關鍵字,你做了什麼是不允許的。

但是,如果T的所有用途都「熄滅」,則僅在T中進行類型協變纔是安全的。例如,可以使用T作爲方法的返回類型,也可以將其作爲get的屬性類型 - 僅屬性。

您的接口在「in」位置使用T,即作爲值參數(即沒有0​​(或out)修飾符的參數)。因此,使你的界面協變是非法的。查看一些其他答案,瞭解如果此限制不存在會發生什麼情況的示例。

然後還有的逆變的概念,這對於IPresenter<>意味着

an IViewA is an IView

意味着

an IPresenter<IView> is an IPresenter<IViewA>

注意如何與逆變順序變化。當在「in」位置使用類型參數(例如值參數)時,逆變僅是安全的(並且只允許)。

基於唯一的成員,它是legat來聲明接口逆變:

interface IPresenter<in T> ... 

其中in意味着逆變,當然。但它會扭轉允許隱式轉換的「方向」。