[英]Why does IEnumerable<T>.ToList<T>() return List<T> instead of IList<T>?
[英]Should I always return IEnumerable<T> instead of IList<T>?
當我編寫我的 DAL 或其他返回一組項目的代碼時,我是否應該始終做出我的返回聲明:
public IEnumerable<FooBar> GetRecentItems()
要么
public IList<FooBar> GetRecentItems()
目前,在我的代碼中我一直在嘗試盡可能多地使用 IEnumerable 但我不確定這是否是最佳實踐? 這似乎是正確的,因為我返回了最通用的數據類型,同時仍然描述了它的作用,但也許這樣做是不正確的。
當您需要返回可由調用者修改的集合或只讀集合的ReadOnlyCollection 時,框架設計指南建議使用類Collection 。
這比簡單的IList
更IList
的原因是IList
不會通知調用者它是否為只讀。
如果您改為返回IEnumerable<T>
,則調用者執行某些操作可能會有些棘手。 此外,您將不再為調用者提供修改集合的靈活性,這是您可能想要也可能不想要的。
請記住,LINQ 包含一些技巧,並會根據執行的類型優化某些調用。 因此,例如,如果您執行Count
並且底層集合是 List,則它不會遍歷所有元素。
就我個人而言,對於 ORM,我可能會堅持使用Collection<T>
作為我的返回值。
這實際上取決於您使用該特定界面的原因。
例如, IList<T>
有幾個方法在IEnumerable<T>
中不存在:
IndexOf(T item)
Insert(int index, T item)
RemoveAt(int index)
和屬性:
T this[int index] { get; set; }
如果您以任何方式需要這些方法,那么一定要返回IList<T>
。
此外,如果使用IEnumerable<T>
結果的方法需要IList<T>
,它將使 CLR 免於考慮任何所需的轉換,從而優化已編譯的代碼。
通常,您應該要求最通用的並返回最具體的東西。 因此,如果您有一個接受參數的方法,並且您只需要 IEnumerable 中可用的內容,那么這應該是您的參數類型。 如果您的方法可以返回 IList 或 IEnumerable,則更喜歡返回 IList。 這確保它可供最廣泛的消費者使用。
在你需要的東西上松散,在你提供的東西上明確。
那要看...
返回最少派生類型( IEnumerable
)將為您留下最大的余地來更改底層實現。
返回一個更派生的類型 ( IList
) 為您的 API 用戶提供了對結果的更多操作。
我總是建議返回具有您的用戶將需要的所有操作的最少派生類型......所以基本上,您首先必須確定在您定義的 API 的上下文中對結果進行的哪些操作是有意義的。
需要考慮的一件事是,如果您使用延遲執行 LINQ 語句來生成IEnumerable<T>
,則在從方法返回之前調用.ToList()
意味着您的項目可能會迭代兩次 - 一次以創建 List ,當調用者循環時,過濾或轉換您的返回值一次。 在可行的情況下,我喜歡避免將 LINQ-to-Objects 的結果轉換為具體的列表或字典,除非我必須這樣做。 如果我的調用者需要一個 List,那么這是一個簡單的方法調用——我不需要為他們做出那個決定,這使得我的代碼在調用者只是執行 foreach 的情況下效率更高。
List<T>
為調用代碼提供了更多功能,例如修改返回的對象和通過索引訪問。 所以問題歸結為:在您的應用程序的特定用例中,您是否想要支持此類用途(大概是通過返回一個新構造的集合!),為了調用者的方便 - 或者您是否想要在所有情況下的簡單情況下的速度調用者需要循環遍歷集合,您可以安全地返回對真正底層集合的引用,而不必擔心這會導致它被錯誤地更改,等等?
只有您才能回答這個問題,並且只有通過充分了解您的調用者想要對返回值做什么,以及性能在這里有多重要(您將復制的集合有多大,這有多大可能成為瓶頸,等等)。
當您談論返回值而不是輸入參數時,這並不是那么簡單。 當它是一個輸入參數時,您確切地知道您需要做什么。 因此,如果您需要能夠遍歷集合,則使用 IEnumberable,而如果您需要添加或刪除,則使用 IList。
在返回值的情況下,它更難。 您的來電者期望什么? 如果您返回一個 IEnumerable,那么他將不會先驗地知道他可以從中制作一個 IList。 但是,如果你返回一個 IList,他就會知道他可以迭代它。 因此,您必須考慮您的調用者將如何處理數據。 您的調用者需要/期望的功能是在決定返回什么時應該支配的。
我認為你可以使用任何一個,但每個都有一個用途。 基本上
List
是IEnumerable
但你有計數功能,添加元素,刪除元素IEnumerable 對於元素計數效率不高
如果集合是只讀的,或者集合的修改由Parent
控制,那么只為Count
返回一個IList
不是一個好主意。
在 Linq 中, IEnumerable<T>
上有一個Count()
擴展方法,如果基礎類型是IList
,它在 CLR 內部將快捷方式到.Count
,因此性能差異可以忽略不計。
一般來說,我覺得(意見)最好在可能的情況下返回 IEnumerable,如果您需要添加這些方法,然后將這些方法添加到父類,否則消費者將在 Model 中管理違反原則的集合,例如manufacturer.Models.Add(model)
違反了德米特法則。 當然,這些只是指導方針,並不是硬性規定,但在你完全掌握適用性之前,盲目遵循總比不遵循要好。
public interface IManufacturer
{
IEnumerable<Model> Models {get;}
void AddModel(Model model);
}
(注意:如果使用 nNHibernate,您可能需要使用不同的訪問器映射到私有 IList。)
List
)作為返回值,並使用最通用的類型作為輸入參數,即使是在集合的情況下。IReadOnlyList
或IReadOnlyCollection
作為返回值類型來顯示。我認為你可以使用任何一個,但每個都有一個用途。 基本上List
是IEnumerable
但你有計數功能,添加元素,刪除元素
IEnumerable
在計算元素或獲取集合中的特定元素時效率不高。
List
是一個非常適合查找特定元素、易於添加或刪除元素的集合。
一般來說,我盡量使用List
,因為這給了我更大的靈活性。
使用List<FooBar> getRecentItems()
而不是IList<FooBar> GetRecentItems()
正如所有人所說,這取決於,如果您不希望在調用層添加/刪除功能,那么我將投票支持 IEnumerable,因為它僅提供我喜歡的迭代和基本功能。 返回 IList 我的投票總是反對它,但這主要是你喜歡什么,不喜歡什么。 在性能方面,我認為它們更相似。
如果您不計入外部代碼,返回 IEnumerable 總是更好,因為稍后您可以更改您的實現(不受外部代碼影響),例如,用於yield 迭代器邏輯並節省內存資源(順便說一句,非常好的語言功能)。
但是,如果您需要項目計數,請不要忘記 IEnumerable 和 IList - ICollection之間還有另一層。
我可能有點不對勁,看到到目前為止沒有其他人建議它,但你為什么不返回(I)Collection<T>
?
根據我的記憶, Collection<T>
是比List<T>
首選的返回類型,因為它抽象了實現。 他們都實現了IEnumerable
,但這對我來說聽起來有點太低級了。
我認為一般規則是使用更具體的類來返回,以避免做不必要的工作並給你的調用者更多的選擇。
就是說,我認為考慮您正在編寫的代碼比考慮下一個人將編寫的代碼(在合理范圍內)更重要。這是因為您可以對已經存在的代碼進行假設。
請記住,在界面中從 IEnumerable 向上移動到集合會起作用,從集合向下移動到 IEnumerable 將破壞現有代碼。
如果這些意見看起來都相互矛盾,那是因為這個決定是主觀的。
IEnumerable<T>
包含List<T>
內部內容的一小部分,其中包含與IEnumerable<T>
相同的內容,但更多! 如果您想要一組較小的功能,則只能使用IEnumerable<T>
。 如果您計划使用更大、更豐富的功能集,請使用List<T>
。
披薩的解釋
這里有一個更全面的解釋,說明在使用 C 語言(如 Microsoft C#)實例化對象時,為什么要使用IEnumerable<T>
與List<T>
之類的接口,反之亦然。
將IEnumerable<T>
和IList<T>
等接口視為比薩餅(意大利辣香腸、蘑菇、黑橄欖...)中的各種成分,將List<T>
等具體類視為比薩餅。 List<T>
實際上是一個Supreme Pizza ,它始終包含組合的所有接口成分(ICollection、IEnumerable、IList 等)。
你得到的比薩餅及其配料取決於你在 memory 中創建其 object 引用時如何“鍵入”列表。你必須聲明你正在烹飪的比薩餅類型,如下所示:
// Pepperoni Pizza: This gives you a single Interface's members,
// or a pizza with one topping because List<T> is limited to
// acting like an IEnumerable<T> type.
IEnumerable<string> pepperoniPizza = new List<string>();
// Supreme Pizza: This gives you access to ALL 8 Interface
// members combined or a pizza with ALL the ingredients
// because List type uses all Interfaces!!
IList<string> supremePizza = new List<string>();
請注意,您不能將接口實例化為它本身(或吃生意大利辣香腸)。 當您將List<T>
實例化為IEnumerable<T>
之類的一種接口類型時,您只能訪問其實現並獲得帶有一種澆頭的意大利辣香腸披薩。 您只能訪問IEnumerable<T>
成員,而看不到List<T>
中的所有其他接口成員。
當List<T>
實例化為IList<T>
時,它實現了所有 8 個接口,因此它可以訪問它已實現的所有接口的所有成員(或 Supreme Pizza 澆頭)!
這是List<T>
class,向您展示了為什么會這樣。 請注意 .NET 庫中的List<T>
已實現所有其他接口,包括 IList!! 但是IEnumerable<T>
只實現了這些列表接口成員的一小部分。
public class List<T> :
ICollection<T>,
IEnumerable<T>,
IEnumerable,
IList<T>,
IReadOnlyCollection<T>,
IReadOnlyList<T>,
ICollection,
IList
{
// List<T> types implement all these goodies and more!
public List();
public List(IEnumerable<T> collection);
public List(int capacity);
public T this[int index] { get; set; }
public int Count { get; }
public int Capacity { get; set; }
public void Add(T item);
public void AddRange(IEnumerable<T> collection);
public ReadOnlyCollection<T> AsReadOnly();
public bool Exists(Predicate<T> match);
public T Find(Predicate<T> match);
public void ForEach(Action<T> action);
public void RemoveAt(int index);
public void Sort(Comparison<T> comparison);
// ......and much more....
}
那么為什么不一直將List<T>
實例化為List<T>
呢?
將List<T>
實例化為List<T>
可以讓您訪問所有接口成員! 但你可能不需要一切。 選擇一種接口類型可以讓您的應用程序存儲更小的 object 和更少的成員,並使您的應用程序保持緊湊。 誰每次都需要Supreme Pizza ?
但是使用接口類型還有第二個原因:靈活性。 因為 .NET 中的其他類型(包括您自己的自定義類型)可能會使用相同的“流行”接口類型,這意味着您稍后可以將List<T>
類型替換為任何其他實現的類型,例如IEnumerable<T>
。 如果您的變量是接口類型,您現在可以切換出使用List<T>
以外的其他內容創建的 object。 依賴注入是這種使用接口而不是具體類型的靈活性的一個很好的例子,以及為什么你可能想要使用接口創建對象。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.