[英]IEnumerable<T> vs. Array
我試圖了解這個想法, 將公共方法的只讀對象列表發布的最佳方法是什么? 從Eric Lippert的博客來看,陣列有點糟糕,因為有人可以輕松添加新條目。 因此,每次調用該方法時都必須傳遞一個新數組。 他建議,傳遞IEnumerable<T>
,因為這是每個定義只讀(沒有添加,刪除方法) ,我練習了很長一段時間。 但是在我們的新項目中,人們甚至開始創建這些IEnumerables
數組,因為他們不知道后面的DataSource,所以他們得到一個: 處理可能的多個枚舉IEnumerable的警告
我對技術方法感興趣,如何解決這個難題。 我到目前為止唯一的解決方案是使用IReadOnlyCollection
,但這比IEnumerable
更明確。
發布此類列表的最佳做法是什么,這些列表不應更改,但應聲明為內存列表?
通常 - 並且從那時起 - 這解決了使用不可變集合 。
例如,您的公共屬性應該是IImmutableList<T>
, IImmutableHashSet<T>
等類型。
任何IEnumerable<T>
都可以轉換為不可變集合:
someEnumerable.ToImmutableList();
someEnumerable.ToImmutableHashSet();
這樣,您可以使用可變集合處理私有屬性,並僅提供不可變集合的公共表面。
例如:
public class A
{
private List<string> StringListInternal { get; set; } = new List<string>();
public IImmutableList<string> StringList => StringListInternal.ToImmutableList();
}
還有一種使用接口的替代方法:
public interface IReadOnlyA
{
IImmutableList<string> StringList { get; }
}
public class A : IReadOnlyA
{
public List<string> StringList { get; set; } = new List<string>();
IImmutableList<string> IReadOnlyA.StringList => StringList.ToImmutableList();
}
檢查IReadOnlyA
是否已顯式實現 ,因此可變和不可變的StringList
屬性可以作為同一個類的一部分共存。
當你想要暴露一個不可變的A
,你將你的A
對象返回到IReadOnlyA
而上層將無法改變上面示例中的整個StringList
:
public IReadOnlyA DoStuff()
{
return new A();
}
IReadOnlyA a = DoStuff();
// OK! IReadOnly.StringList is IImmutableList<string>
IImmutableList<string> stringList = a.StringList;
它應該是一種可能的解決方案,以避免每次訪問不可變的列表時將源列表轉換為不可變列表。
如果項目類型覆蓋Object.Equals
和GetHashCode
,並且可選地實現IEquatable<T>
,則兩個公共不可變列表屬性訪問可能如下所示:
public class A : IReadOnlyA
{
private IImmutableList<string> _immutableStringList;
public List<string> StringList { get; set; } = new List<string>();
IImmutableList<string> IReadOnlyA.StringList
{
get
{
// An intersection will verify that the entire immutable list
// contains the exact same elements and count of mutable list
if(_immutableStringList.Intersect(StringList).Count == StringList.Count)
return _immutableStringList;
else
{
// the intersection demonstrated that mutable and
// immutable list have different counts, thus, a new
// immutable list must be created again
_immutableStringList = StringList.ToImmutableList();
return _immutableStringList;
}
}
}
}
我不認為不可改變是要走的路
int[] source = new int[10000000];//uses 40MB of memory
var imm1 = source.ToImmutableArray();//uses another 40MB
var imm2 = source.ToImmutableArray();//uses another 40MB
列表的行為方式相同。 如果我想每次都進行完整復制,我不必關心用戶對該數組的處理方式。 使其不可變不會保護集合中對象的內容,它們可以自由更改。 @HansPassant建議似乎是最好的
public class A
{
protected List<int> list = new List<int>(Enumerable.Range(1, 10000000));
public IReadOnlyList<int> GetList
{
get { return list; }
}
}
對於您不打算修改的集合, IEnumerable<T>
仍然可能是最安全的選項,而且它允許任何集合類型,而不僅僅是數組。 發出警告的原因是因為IEnumerable
可能表示使用延遲執行的查詢,這意味着可能會多次執行可能很昂貴的操作。
請注意,沒有一個接口可以區分內存中的集合與可能的延遲執行包裝器。 之前已經問過這個問題 。
造成這種情況的解決方法是不要多次枚舉源 。 如果代碼需要執行多次迭代(可能是合法的),那么在迭代之前將集合水化為List<T>
。
IEnumerable
是一個只讀接口,它隱藏了用戶的實現。 有人可以爭辯說,用戶可以將IEnumerable
為列表並添加新項目,但這意味着兩件事:
IEnumerable
描述了行為,而List
是該行為的實現。 當您使用IEnumerable
,您可以讓編譯器有機會將工作推遲到以后,可能會在此過程中進行優化。 如果使用ToList()
,則強制編譯器立即准備結果。
我使用IEnumerable
每當使用LINQ表達式時,因為只通過指定行為,我給LINQ提供了推遲評估並可能優化程序的機會。
要防止對List<T>
對象進行任何修改,只能通過ReadOnlyCollection<T>
包裝器公開它,該包裝器不公開修改集合的方法。 但是,如果對基礎List<T>
對象進行了更改,則只讀集合將反映MSDN中所述的那些更改。
請查看.NET 4.5中的新只讀集合 。
我編程的時間比我記憶中的時間長,並且從來沒有這樣的要求來保護列表不被修改。 我不是說這不是一個可能的要求,我只是說很少需要這樣的要求。 如果您的應用中有一個列表,那么很可能您必須修復您的設計。 如果您需要幫助,請告訴我們您如何使用該列表。
您在問題和評論中提供的示例並不是何時需要不可變或只讀列表的好例子。 我們一個一個地討論它們。
您提到將其作為API發布。 根據定義,您從API返回的任何內容都不再屬於您,您不應該關心它是如何使用的。 事實上,一旦它離開你的API,它現在就在API客戶端的域中,他們可以隨心所欲地做它。 除了一旦它在域中你無法保護它的事實,你不應該決定他們將如何使用它。 更重要的是,您不應該在API中接受任何輸入,即使它與您之前返回的列表相同,並且您認為您受到保護。 所有輸入必須適當驗證。
也許,你並不是指API,而是更像你在項目中共享的DLL庫。 無論是DLL還是項目中的類,都適用相同的原則。 當您返回某些內容時,由用戶決定如何使用它。 而且你不應該在沒有驗證的情況下接受同樣的事情(列表或其他)。 同樣,您不應該公開類的內部成員,無論是列表還是單個值。 畢竟,這就是使用屬性而不是將成員標記為公共的全部理念。 為單值成員創建屬性時,將返回該值的副本。 同樣,您應該為列表創建一個屬性並返回一個副本,而不是列表本身。
如果您確實需要一個可全局訪問的列表,並且您只想加載一次,將其公開給其他類,保護它不被修改,而且它太大而無法復制它。 根據AntonínLejsek的回答,您可以查看一些設計,例如將其包裝在Singleton中和/或將其設置為只讀。 實際上,由於C#中簡化的Singleton實現,通過將構造函數標記為私有,可以很容易地將其答案轉換為Singleton。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.