簡體   English   中英

可枚舉<t>作為返回類型</t>

[英]IEnumerable<T> as return type

使用IEnumerable<T>作為返回類型有問題嗎? FxCop 抱怨返回List<T> (它建議返回Collection<T> )。

好吧,我一直遵循一條規則“接受最少的東西,但返回最多的東西”。

從這個角度來看,返回IEnumerable<T>是一件壞事,但是當我想使用“惰性檢索”時該怎么辦呢? 另外, yield關鍵字是個好東西。

這實際上是一個兩部分問題。

1)返回IEnumerable <T>本身有什么問題

一點也不。 實際上,如果您使用的是C#迭代器,則這是預期的行為。 先發制人地將它轉換為List <T>或其他集合類並不是一個好主意。 這樣做是由調用者對使用模式進行假設。 我發現假設調用者的任何事情並不是一個好主意。 他們可能有充分的理由為什么他們想要一個IEnumerable <T>。 也許他們想將它轉換為完全不同的集合層次結構(在這種情況下,浪費了轉換為List)。

2)在任何情況下,最好還是返回IEnumerable <T>以外的東西嗎?

是。 雖然假設你的呼叫者並不是一個好主意,但根據你自己的行為做出決定是完全可以的。 想象一下這樣一個場景,你有一個多線程對象,它將請求排隊到一個不斷更新的對象中。 在這種情況下,返回原始IEnumerable <T>是不負責任的。 一旦修改了集合,枚舉就會失效並導致執行。 相反,您可以拍攝結構的快照並返回該值。 以List <T>形式說。 在這種情況下,我只是將對象作為直接結構(或接口)返回。

這當然是罕見的情況。

不, IEnumerable<T>是回到這里一件好事 ,因為你是有希望的是“(類型)值序列”。 LINQ等的理想選擇,完全可用。

調用者可以輕松地將此數據放入列表(或其他任何內容) - 尤其是使用LINQ( ToListToArray等)。

這種方法允許您懶惰地回退值,而不是必須緩沖所有數據。 絕對是一個好東西。 我前幾天寫了另一個有用的IEnumerable<T>技巧

IEnumerable很好,但它有一些缺點。 客戶端必須枚舉才能獲得結果。 它沒有辦法檢查Count等。列表是壞的,因為你暴露了太多的控制; 客戶端可以添加/刪除等等,這可能是一件壞事。 收藏似乎是最好的妥協,至少在FxCop看來。 我總是在我的上下文中使用似乎合適的東西(例如,如果我想返回一個只讀集合,我將集合公開為返回類型並返回List.AsReadOnly()或IEnumerable以通過yield等進行惰性求值)。 根據具體情況來看

關於你的原則:“盡可能接受,但要回報最大”。

管理大型程序復雜性的關鍵是一種稱為信息隱藏的技術。 如果你的方法通過構建List<T> ,那么通常不需要通過返回該類型來揭示這個事實。 如果您這樣做,那么您的呼叫者可以修改他們返回的列表。 這將刪除您使用yield return進行緩存或延遲迭代的能力。

因此,一個更好的原則是要遵循的功能是:“盡可能少地揭示你的工作方式”。

“盡可能接受,但最大限度地回報”是我所倡導的。 當一個方法返回一個對象時,我們必須通過返回一個基類型來返回實際類型並限制該對象的功能。 然而,這提出了一個問題,我們如何知道設計界面時“最大”(實際類型)是什么。 答案很簡單。 只有在接口設計人員設計開放接口的極端情況下,它才會在應用程序/組件之外實現,他們不知道實際的返回類型是什么。 聰明的設計師應該始終考慮方法應該做什么以及最佳/通用返回類型應該是什么。

例如,如果我正在設計一個接口來檢索對象的向量,並且我知道返回的對象的數量將是可變的,我將始終假設智能開發人員將始終使用List。 如果有人計划返回一個數組,我會質疑他的能力,除非他/她只是從他/她不擁有的另一層返回數據。 這可能就是為什么FxCop提倡ICollection(List和Array的共同基礎)。

如上所述,還有其他幾件事需要考慮

  • 如果返回的數據應該是可變的或不可變的

  • 如果返回的數據在多個呼叫者之間共享

關於LINQ懶惰評估,我確信95%+ C#用戶不理解這些內容。 它是如此非oo-ish。 OO促進方法調用的具體狀態更改。 LINQ延遲評估促進表達式評估模式的運行時狀態更改(不是非高級用戶總是遵循的)。

如果你真的只返回一個枚舉,那么返回IEnumerable <T>就可以了,並且你的調用者將會這樣做。

但正如其他人指出的那樣,它的缺點是調用者可能需要枚舉他是否需要任何其他信息(例如Count)。 如果返回值沒有實現ICollection <T>,則.NET 3.5擴展方法IEnumerable <T> .Count將在后台進行枚舉,這可能是不合需要的。

當結果是集合時,我經常返回IList <T>或ICollection <T> - 在內部,您的方法可以使用List <T>並按原樣返回,或者如果要保護則返回List <T> .AsReadOnly反對修改(例如,如果您在內部緩存列表)。 AFAIK FxCop對其中任何一個都非常滿意。

一個重要的方面是,當您返回List<T>您實際上返回了一個引用 這使得調用者可以操作您的列表。 這是一個常見問題 - 例如,將List<T>返回到GUI層的Business層。

只是因為你說你正在返回IEnumerable並不意味着你不能返回一個List。 這個想法是減少不必要的耦合。 調用者應該關心的只是獲取事物列表,而不是用於包含該列表的確切集合類型。 如果你有一些由數組支持的東西,那么得到類似Count的東西無論如何都會很快。

我無法接受所選擇的答案。 有一些方法可以處理所描述的場景,但使用List或其他任何你使用的不是其中之一。 返回IEnumerable的那一刻,你必須假設調用者可能會做一個foreach。 在這種情況下,具體類型是List還是意大利面並不重要。 事實上,只有索引是一個問題,特別是如果刪除項目。

任何返回的值都是快照。 它可能是IEnumerable的當前內容,在這種情況下,如果它被緩存,它應該是緩存副本的克隆; 如果它應該更加動態(比如sql查詢的結果)那么使用yield return; 然而,允許容器隨意變異,並提供像Count和indexer這樣的方法,這是多線程世界中的災難。 我甚至沒有讓調用者能夠在你的代碼應該控制的容器上調用Add或Delete。

還返回一個具體類型會將您鎖定到實現中。 今天在內部您可能正在使用列表。 明天,你可能會變成多線程,並且想要使用線程安全容器或數組或隊列,或者使用字典的Values集合或Linq查詢的輸出。 如果您將自己鎖定為具體的返回類型,則必須在返回之前更改一堆代碼或進行轉換。

我認為你自己的指導是很棒的 - 如果你能夠更加具體地了解你在沒有性能損失的情況下返回的內容(你不必例如在你的結果中建立一個List),那么這樣做。 但是如果你的函數合法地不知道它將找到什么類型,比如在某些情況下你將使用List和一些使用數組等,那么返回IEnumerable是你可以做的“最好的” 。 將其視為您可能想要返回的所有內容的“最常見的多重”。

IEnumerable很酷,因為您可以使用 yield 迭代器為消費者提供他們需要的數據,但構造中隱藏了成本。

讓我用一個例子來解釋它。 假設我正在使用這種方法:

IEnumerable GetFilesFromFolder(字符串路徑)

那么,我得到了什么? 要獲取我的文件夾中的所有文件,我必須迭代枚舉,這很好,畢竟枚舉就是這樣工作的,但是如果出於某種原因我必須枚舉它兩次怎么辦?

第二次我應該期待刷新的結果還是冪等的? 我不知道。 我必須檢查庫/方法的文檔。

調用消費者完成的枚舉的GetEnumerator方法,實際上可以在幕后執行 I/O 操作,或 http 調用,或者它可以簡單地迭代一個內部數組,我不能確定. 我必須檢查文檔,希望記錄這種行為。

這個細節重要嗎? 我認為是的。 至少從性能的角度來看。

即使迭代成本很慢且受 CPU 限制,這也不是零,並且在枚舉鏈的情況下它可能 go 更糟,這通常會使調試會話成為一場噩夢。

我不想讓我的庫的使用者產生疑慮,所以每當我知道我的 API 返回的元素很少時,我總是使用 arrays 作為返回類型,只有當要返回的數據很大時,我才使用 IEnumerable 或 IAsyncEnumerable。

無論如何,如果你想返回枚舉,請記錄你的 API 以告訴消費者結果是否是快照。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM