[譯文]c# /.Net 技巧: ToDictionary() and ToList()


前言:

有兩個簡單好用的LINQ擴展方法 ToDictionary() 和ToList(),你可能知道或不知道,但是它的的確確可以簡化查詢轉化為集合的任務:

簡介: LINQ和延遲執行

據你所認識的LINQ,你可能會不知道這些查詢表達式在幕后做了些什么。 讓我們說說今天我們示例的目的,我們有一些POCO類(POCO代表傳統CLR對象,指的是一個類,它只有非常少的功能,這一概念源自Java POJO)。

1 // just a simple product POCO class.  
2  public class Product
3  {
4     public string Name { get; set; }
5     public int Id { get; set; }
6     public string Category { get; set; }
7 }

非常簡單的類,對嗎? 我不是說程序需要如此簡單,只是專注於LINQ本身,而且我們不一定要真正查詢。 所以,在我們的程序中我們可以構建一個簡單的例子,這些對象的集合的示例如下:

 1 var products = new List<Product>
 2      {
 3          new Product { Name = "CD Player", Id = 1, Category = "Electronics" },
 4          new Product { Name = "DVD Player", Id = 2, Category = "Electronics" },
 5          new Product { Name = "Blu-Ray Player", Id = 3, Category = "Electronics" },
 6          new Product { Name = "LCD TV", Id = 4, Category = "Electronics" },
 7          new Product { Name = "Wiper Fluid", Id = 5, Category = "Automotive" },
 8          new Product { Name = "LED TV", Id = 6, Category = "Electronics" },
 9          new Product { Name = "VHS Player", Id = 7, Category = "Electronics" },
10          new Product { Name = "Mud Flaps", Id = 8, Category = "Automotive" },
11          new Product { Name = "Plasma TV", Id = 9, Category = "Electronics" },
12          new Product { Name = "Washer", Id = 10, Category = "Appliances" },
13          new Product { Name = "Stove", Id = 11, Category = "Electronics" },
14          new Product { Name = "Dryer", Id = 12, Category = "Electronics" },
15          new Product { Name = "Cup Holder", Id = 13, Category = "Automotive" },
16      };

 

就是說,有這些產品的對象集合,你需要查詢它們。 例如,我們可以這樣得到一個所有產品實例的類別為“Electronics”的集合:

1 var electronicProducts = products.Where(p => p.Category == "Electronics");

許多擴展方法(包括Where() )的查詢結果是創建一個迭代器通過移動列表來執行查詢。 因此,此時的electronicProducts不是List<Product>,只是IEnumerable<Product>,它會在您使用這個列表時動態求值. 這就是LINQ中強大的延遲執行,在你需要結果前,都不會對表達式求值。 此時我們可以去查詢electronicProducts,這樣我們就可以得到結果列表!

讓我看一下下面的結果是什么:

1 // select all electronics, there are 7 of them
2 IEnumerable<Product> electronicProducts = products.Where(p => p.Category == "Electronics");
3   
4  // now clear the original list we queried
5  products.Clear();
6   
7  // now iterate over those electronics we selected first
8  Console.WriteLine(electronicProducts.Count());

你認為結果是7還是0? 答案是0,因為即使我們第2行上設置一個查詢所有電子產品,但是我們在第5行清除列表。 因此,當我們在第8行實際處理查詢列表(執行Count())是空的,沒有找到結果。

如果你感到困惑,認為它是這樣的: 創建一個使用LINQ查詢擴展方法(和LINQ表達式語法)很像定義一個存儲過程, 在你調用它之前都沒有“運行”。 我知道這不是100%准確的比喻,但是希望你要知道LINQ表達式在語句2是沒有執行的,我們處理了IEnumerable才執行。

ToList() LINQ擴展方法

如果你想立即得到(存儲)LINQ表達式的結果,你應該把它到導入到另一個集合,這樣就可以修改。 當然,你可以手動建立一個列表,然后以各種方式填充。

 1  IEnumerable<Product> electronicProducts = products.Where(p => p.Category == "Electronics");
 2   
 3 // You could create a list and then hand-iterate - BULKY!
 4  var results = new List<Product>();
 5   
 6  foreach (var product in electronicProducts)
 7  {
 8      results.Add(product);
 9  }
10   
11  // OR, you could take advantage of AddRange() - GOOD!
12  var results2 = new List<Product>();
13  results2.AddRange(electronicProducts);
14   
15  // OR, you could take advantage of List's constructor that takes an IEnumerable<T> - BETTER!
16  var results3 = new List<Product>(electronicProducts);

實際上,使用一個循環,通常是非常冗長的,或者你可能利用AddRange()或List<T>函數構造 IEnumerable<T>列表。

但是你可以用另一種方式,。 LINQ擴展方法之上包含了ToList(),你可以將任何IEnumerable<T>來填充一個 List<T>。如果你想用一步執行查詢和填充,這很方便:

var electronicProducts = products.Where(p =>  p.Category == "Electronics").ToList();

現在,  List<T>代替 electronicProducts 作為IEnumerable<T> 動態執行的原始集合,這將是另一個新的集合,修改不會影響原來的集合。

當然,這有優點也有缺點。 通常,如果你只是要遍歷的結果和過程,你不需要(也不想)將它存儲在一個單獨的列表,這只會浪費內存,后來還需要垃圾收集。 然而,如果你想保存子集,並將它分配給另一個類,ToList()是非常方便的,你不需要擔心改變原來的集合。

ToDictionary()LINQ擴展方法

ToList() 使用IEnumerable<T>並將其轉換為 List<T>,那么 ToDictionary()也是類似的。大多數情況ToDictionary()是一個非常方便的方法,將查詢的結果(或任何 IEnumerable<T>)轉換成一個Dictionary<TKey,TValue>。 關鍵是您需要定義T如何分別轉換TKey和TValue。

如果說我們有超級大的產品列表,希望把它放在一個Dictionary<int, product>,這樣我們可以根據ID得到最快的查找時間。 你可能會這樣做:

1  var results = new Dictionary<int, Product>();
2  foreach (var product in products)
3  {
4      results.Add(product.Id, product);
5  }

和它看起來像一個很好的代碼,但是我們可以輕松地使用LINQ而無需手寫一大堆邏輯:

1 var results = products.ToDictionary(product =>  product.Id);

它構造一個Dictionary<int, Product> ,Key是產品的Id屬性,Value是產品本身。 這是最簡單的形式ToDictionary(),你只需要指定一個key選擇器。 如果你想要不同的東西作為你的value? 例如如果你不在乎整個Product,,你只是希望能夠轉換ID到Name? 我們可以這樣做:

1 var results = products.ToDictionary(product =>  product.Id, product =>  product.Name);

 

這將創建一個 Key為Id,Value為Name 的Dictionary<int, string>,。由此來看這個擴展方法有很多的方式來處理IEnumerable<T> 集合或查詢結果來生成一個dictionary。

 注:還有一個Lookup<TKey, TValue>類和ToLookup()擴展方法,可以以類似的方式做到這一點。 他們不是完全相同的解決方案(Dictionary和Lookup接口不同,他們的沒有找到索引時行為也是不同的)。

因此,在我們的Product 示例中,假設我們想創建一個Dictionary<string, List<Product>> ,Key是分類,Value是所有產品的列表。 在以前你可能自實現自己的循環:

 

 1 // create your dictionary to hold results
 2  var results = new Dictionary<string, List<Product>>();
 3   
 4 // iterate through products
 5  foreach (var product in products)
 6 {
 7     List<Product> subList;
 8  
 9     // if the category is not in there, create new list and add to dictionary
10      if (!results.TryGetValue(product.Category, out subList))
11     {
12          subList = new List<Product>();
13          results.Add(product.Category, subList);
14     }
15   
16      // add the product to the new (or existing) sub-list
17      subList.Add(product);
18  }

但代碼應該更簡單! 任何新人看着這段代碼可能需要去詳細分析才能完全理解它,這給維護帶來了困難

幸運的是,對我們來說,我們可以利用LINQ擴展方法GroupBy()提前助力ToDictionary()和ToList():

 // one line of code!

 var results = products.GroupBy(product =>  product.Category)

.ToDictionary(group =>  group.Key, group =>  group.ToList());

GroupBy()是用Key和IEnumerable創建一個IGrouping的LINQ表達式查詢語句。 所以一旦我們使用GroupBy() ,所有我們要做的就是把這些groups轉換成dictionary,所以我們的key選擇器 (group => group.Key) 分組字段(Category),使它的成為dictionary的key和Value擇器((group =>  group.ToList()) 項目,並將它轉換成一個List<Product>作為我們dictionary的Value!

這樣更容易讀和寫,單元測試的代碼也更少了! 我知道很多人會說lamda表達式更難以閱讀,但他們是c#語言的一部分,高級開發人員也必須理解。我認為你會發現當你越來越多的使用他們后,代碼能被更好的理解和比以前更具可讀性。

 

譯者按:我用Transmate翻譯的同時,也盡量保留原文的描述,避免信息的丟失,如果這篇文章對你有一點兒用,請不要吝嗇點推薦,讓更多人看到,謝謝!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM