2011-09-11 45 views
9

我有返回收集和協方差的問題,我想知道是否有人有更好的解決方案。如何處理在c#中返回集合時的協方差?

的情況是這樣的:

我有2個版本執行,我想保留的版本實現完全獨立的(儘管它們可能具有相同的邏輯)。在實現中,我想返回一個項目列表,因此在界面中,我會返回該項目的界面列表。但是,在界面的實際實現中,我想返回該項目的具體對象。在代碼中,它看起來像這樣。

interface IItem 
{ 
    // some properties here 
} 

interface IResult 
{ 
    IList<IItem> Items { get; } 
} 

然後,將會有2個命名空間具有這些接口的具體實現。例如,

命名空間版本1

class Item : IItem 

class Result : IResult 
{ 
    public List<Item> Items 
    { 
     get { // get the list from somewhere } 
    } 

    IList<IItem> IResult.Items 
    { 
     get 
     { 
      // due to covariance, i have to convert it 
      return this.Items.ToList<IItem>(); 
     } 
    } 
} 

會有下命名空間版本2同樣的事情另一種實現方式。

要創建這些對象,將會有一個工廠根據需要獲取版本並創建適當的具體類型。

如果來電者知道確切的版本並執行以下操作,代碼工作正常

Version1.Result result = new Version1.Result(); 
result.Items.Add(//something); 

不過,我想用戶能夠做這樣的事。

IResult result = // create from factory 
result.Items.Add(//something); 

但是,因爲它已經轉換成另一個列表,添加什麼都不會做,因爲項目不會被添加回原來的結果對象。

我能想到的幾個解決方案,如:

  1. 我可以同步兩個名單,但似乎是額外的工作要做
  2. 返回IEnumerable的,而不是IList中,並添加一個方法創建/刪除收藏
  3. 創建一個自定義集合了利用TConcrete和TInterface

我明白爲什麼會發生(由於類型安全和所有),但沒有workaroun的我認爲可以看起來很優雅。有人有更好的解決方案或建議嗎?

在此先感謝!

更新

思考這個更後,我想我可以做到以下幾點:

public interface ICustomCollection<TInterface> : ICollection<TInterface> 
{ 
} 

public class CustomCollection<TConcrete, TInterface> : ICustomCollection<TInterface> where TConcrete : class, TInterface 
{ 
    public void Add(TConcrete item) 
    { 
     // do add 
    } 

    void ICustomCollection<TInterface>.Add(TInterface item) 
    { 
     // validate that item is TConcrete and add to the collection. 
     // otherwise throw exception indicating that the add is not allowed due to incompatible type 
    } 

    // rest of the implementation 
} 

然後我可以有

interface IResult 
{ 
    ICustomCollection<IItem> Items { get; } 
} 

then for implementation, I will have 

class Result : IResult 
{ 
    public CustomCollection<Item, IItem> Items { get; } 

    ICustomCollection<TItem> IResult.Items 
    { 
     get { return this.Items; } 
    } 
} 

這樣,如果主叫方是訪問Result類,它將通過已經是TConcrete的CustomCollection.Add(TConcrete項)。如果調用者正在通過IResult接口訪問,它將通過customCollection.Add(TInterface項)進行驗證,並確保類型實際上是TConcrete。

我會試一試,看看這是否可行。

+1

我認爲使用選項#2將需要最少量的代碼。它還會減少界面的表面積,因爲實現不會負責完整的IList支持,而調用者可能不需要這些支持。 –

+0

您的解決方案看起來不錯,但您爲什麼直接使用'ICustomCollection '而不是'ICollection '? – svick

+0

我完全可以。唯一的原因是我有一些專門用於集合的方法,這些方法不適用於需要通過內部代碼訪問的常規ICollection ,我忘了提及這些方法。 – Khronos

回答

2

你所面對的問題是,因爲要揭露一個類型,說(除其他事項外)「你可以添加任何IItem給我」,但它實際上會做的是:「你可以只添加Item s到我。」 。我認爲最好的解決辦法是實際公開IList<Item>。無論如何,使用它的代碼必須知道具體的Item,如果它應該將它們添加到列表中。

但是,如果你真的想這樣做,我認爲最乾淨的解決方案將是3,如果我正確地理解你。這將是一個圍繞IList<TConcrete>的包裝,它將實施IList<TInterface>。如果您嘗試將其放入TConcrete中,則會引發異常。

0

+1 Brent的評論:返回不可修改的集合並提供對類的修改方法。

只是重申爲什麼你不能得到1解釋的方式工作:

如果您嘗試添加到單靠實現IItem,但類型不會(或源自)List<Item>元素項目你會不會能夠將這個新項目存儲在列表中。因爲結果接口的行爲將會非常不一致 - 一些實現IItem的元素可以很好地添加,sime會失敗,並且當您將實現更改爲version2時,行爲也會產生衝突。

簡單的修復方法是存儲IList<IItem> instendad List<Item>,但直接暴露集合需要仔細思考。

0

問題是Microsoft使用一個接口IList <T>來描述可以追加到的集合和不能追加的集合。雖然理論上可能的是一個類以可變的方式實現IList <Cat>,並且還以不可變的方式實現IList <Animal>(前接口的ReadOnly屬性將返回false,並且後者將返回true),但是沒有辦法一個類來指定它實現IList <Cat>單向,而IList <T>其中cat:T,另一種方式。我想微軟做了IList的<牛逼>列表<牛逼> T中>,IReadWriteByIndex <牛逼>,IAppendable < T中>和ICountable實施IReadableByIndex <出牛逼>,IWritableByIndex <,因爲這些會允許協方差和逆變,但他們沒有。自己實現這些接口並定義它們的包裝可能會有所幫助,具體取決於協方差有用的程度。