2015-09-10 144 views
6

我有兩個接口:C#泛型,接口和繼承

public interface IAmA 
{ 
} 

public interface IAmB<T> where T : IAmA 
{ 
} 

和兩個類實現這樣這些接口:

public class ClassA : IAmA 
{ 
} 

public class ClassB : IAmB<ClassA> 
{ 
} 

當試圖如圖所示,使用這些類:

public class Foo 
{ 
    public void Bar() 
    { 
     var list = new List<IAmB<IAmA>>(); 
     list.Add(new ClassB()); 
    } 
} 

我得到這個編譯器錯誤:

cannot convert from 'ClassB' to 'IAmB<IAmA>'

我知道我可以編譯開心使用make:

public class ClassB : IAmB<IAmA> 
{ 
} 

但我需要能夠成爲類型參數在ClassBIAmB<>IAmA的實現。

+1

「通過IAmA的實現」是什麼意思? ClassB不*從*繼承任何東西,它仍然需要實現IAmB的每一個成員。爲什麼你*有*繼承IAmB 而不是IAmB ?你試圖解決的實際問題是什麼? –

+0

我更新了問題。 – Alex

+0

更新沒有解釋任何事情 - 從一開始就很清楚你想使用具體的類 - 問題是*爲什麼*? –

回答

11

簡單的回答是,你可以做什麼你問通過聲明的IAmB<T>作爲協變類型參數,只有在類型用作返回類型

public interface IAmB<out T> where T : IAmA 
{ 
    T SomeMethod(string someparam); 
} 

out T意味着您可以使用比約束中指定的更具體的類型。

您將無法將T用作參數。下面將無法編譯:

public interface IAmB<out T> where T : IAmA 
{ 
    void SomeMethod(T someparam); 
} 

從文檔

You can use a covariant type parameter as the return value of a method that belongs to an interface, or as the return type of a delegate. You cannot use a covariant type parameter as a generic type constraint for interface methods.

這不是一個編譯器的怪癖。 假設你可以聲明一個協變方法參數,你的列表最終將包含一些對象,不能處理IAmB<IAmA>參數 - 它們會期望ClassA或更具體的輸入。你的代碼會編譯但在運行時失敗。

哪一個會引發這個問題 - 你爲什麼要用IAmB<ClassA>

您應該在使用此之前考慮一下,因爲可能有其他,更適合解決您的實際問題的方法。使用通用接口來實現一個具體的類型是非常不尋常的,但是試圖像使用另一個接口一樣使用它。

您可以查看MSDN文檔對Covariance and Contravariance部分以及埃裏克利珀的一個喬恩斯基特的答案this SO question: Difference between Covariance and Contravariance

3

快速回答:使通用型協(見msdn)在你的界面

public interface IAmB<out T> where T : IAmA 
{ 
} 

這將解決編譯器問題。

但這不會回答Panagiotis Kanavos問的why

2

訣竅是使類型約束TIAmB<T>,與out關鍵字:

public interface IAmB<out T> where T : IAmA 
{ 
} 

這允許您使用比原先指定的類型更具體的類型,在這種情況下,您可以將IAmB<ClassA>分配給類型變量IAmB<IAmA>

欲瞭解更多信息,請參閱the documentation

0

我只是告訴爲什麼這個錯誤報告。

如果您IAmB有一個方法

public interface IAmB<T> where T : IAmA 
{ 
    void foo(T p); 
} 

public class ClassB : IAmB<ClassA> 
{ 
    void foo(ClassA p) 
    { 
     p.someIntField++; 
    } 
} 

,我們有另一個類

public class ClassC : IAmB<ClassA2> 
{ 
    void foo(ClassA2 p) 
    { 
     p.someOtherIntField++; 
    } 
} 

我們假設List<IAmB<IAmA>>.Add(T p)實現這樣

IAmA mParam = xxxx; 
void Add(IAmB<IAmA>> p){ 
    p.foo(mParam); 
} 

想所有編譯OK。你傳遞一個ClassB的實例List.Add,它成爲

void Add(IAmB<IAmA>> p){ 
    //p is ClassB now 
    p.foo(mParam);//COMPILER CAN NOT MAKE SURE mParam fit ClassB.foo 
} 
+0

這不是一個實際的答案。 –

0

它可以通過逆變和協方差來解決。

public interface IAmA 
{ 
} 

**public interface IAmB<out T> where T : IAmA 
{ 
}** 


public class ClassA : IAmA 
{ 
} 

public class ClassB : IAmB<ClassA> 
{ 
} 


public class Foo 
{ 
    public void Bar() 
    { 
     var list = new List<IAmB<IAmA>>(); 
     **list.Add(new ClassB());** 
    } 
} 

現在你不會收到編譯器錯誤。編譯器很高興。

+0

不完全 - 協方差只允許T用作返回類型。 –