2010-08-20 122 views
16

我知道這是舊的,但我仍然不是很瞭解這些問題。誰能告訴我爲什麼以下不起作用(引發runtime例外)?泛型和鑄造 - 不能將繼承類映射到基類

public abstract class EntityBase { } 
public class MyEntity : EntityBase { } 

public abstract class RepositoryBase<T> where T : EntityBase { } 
public class MyEntityRepository : RepositoryBase<MyEntity> { } 

而現在的鑄造生產線:

MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever 
RepositoryBase<EntityBase> baseRepo = (RepositoryBase<EntityBase>)myEntityRepo; 

所以,任何人都可以解釋這是怎麼無效?而且,我沒有心情解釋 - 是否有一行代碼可以用來實際執行此演職員表演?

+1

謝謝大家的答案。爲了簡短 - 我現在用一個基本接口(RepositoryBase :IRepository)解決了這個問題。原來,我只需要執行我得到的實例上的函數,並讓類本身處理其他事情。 – Jefim 2010-08-20 07:44:48

+0

查看[C#協方差和逆變常見問題](http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx) – 2010-08-20 07:12:49

回答

24

RepositoryBase<EntityBase>不是基類MyEntityRepository。您正在尋找通用方差,它存在於C#中的範圍有限,但在此不適用。

假設你RepositoryBase<T>類有這樣的方法:

void Add(T entity) { ... } 

現在考慮:

MyEntityRepository myEntityRepo = GetMyEntityRepo(); // whatever 
RepositoryBase<EntityBase> baseRepo = (RepositoryBase<EntityBase>)myEntityRepo; 
baseRepo.Add(new OtherEntity(...)); 

現在你已經添加了不同種類的實體爲MyEntityRepository ......這能不對。

基本上,一般方差在某些情況下只是安全的。特別是通用的協方差(這就是你在這裏描述的)只有當你只有API的值「出」時纔是安全的;當您只將數據「放入」API中時(例如,可以將任意兩個形狀比較的一般比較可以被認爲是正方形的比較),通用反變化(其相反地工作)是安全的。

在C#4中,這可用於通用接口和泛型委託,而不是類 - 並且僅用於引用類型。請參閱MSDN獲取更多信息,請閱讀<插件>閱讀C# in Depth, 2nd edition,第13章< /插件>或Eric Lippert的blog series關於該主題。另外,2010年7月,我在NDC上對此進行了一個小時的討論 - 視頻可用here;只是搜索「差異」。

+0

感謝Jon,對於解釋和鏈接到非常有趣的材料。我一定會考慮閱讀這本書並觀看視頻。 – Jefim 2010-08-20 07:42:44

6

這需要協變或逆變,其支持僅限於.Net,不能用於抽象類。儘管你可以在接口上使用差異,所以解決你的問題的一個可能的方法是創建一個IRepository來代替抽象類。

public interface IRepository<out T> where T : EntityBase { //or "in" depending on the items. 
    } 
    public abstract class RepositoryBase<T> : IRepository<T> where T : EntityBase { 
    } 
    public class MyEntityRepository : RepositoryBase<MyEntity> { 
    } 

    ... 

    IRepository<EntityBase> baseRepo = (IRepository<EntityBase>)myEntityRepo; 
11

每當有人問這個問題,我試圖把他們的榜樣,並使用比較知名的類,顯然是非法它翻譯的東西(這是Jon Skeet has done in his answer;但我把它更進一步,執行此翻譯)。

讓我們更換MyEntityRepositoryMyStringList,像這樣:

class MyStringList : List<string> { } 

現在,你似乎想MyEntityRepository將強制轉換爲RepositoryBase<EntityBase>,推理是,這應該是可能的,因爲從MyEntity派生EntityBase

但是string派生自object,不是嗎?所以通過這種邏輯,我們應該能夠將MyStringList投射到List<object>

讓我們看看會發生什麼,如果我們允許...

var strings = new MyStringList(); 
strings.Add("Hello"); 
strings.Add("Goodbye"); 

var objects = (List<object>)strings; 
objects.Add(new Random()); 

foreach (string s in strings) 
{ 
    Console.WriteLine("Length of string: {0}", s.Length); 
} 

嗯,哦。突然我們列舉了一個List<string>,我們遇到了一個Random對象。這不好。

希望這會讓問題更容易理解。

+4

我已經開始在示例中進一步了 - 我曾經使用過字符串和對象,但在Eric Lippert的建議中,我已經開始使用真實世界的對象......水果/蘋果/香蕉在「你可以」給一串香蕉添加一個蘋果「。 – 2010-08-20 07:27:06