c# Linq常用的小技巧
前言
在C#語言發展的歷史長河中,Linq是一個極其重要的里程碑!
Linq的語法吸取了SQL語法的特性,同時配合Lambda表示式又可以使程式碼更加優雅!
可以這麼說,用好了Linq可以大大提高程式猿的工作效率,畢竟我們的日常工作本質就是對資料的處理。經歷了十多年的發展,現在微軟自帶的內庫包含的Linq函式已經非常多了,幾乎滿足我們日常工作。
下面根據一個對科室資料操作的例子,就個人覺得日常高頻使用的Linq小技巧貼出來,權當是做個筆記了。
初始化資料
定義模型
這裡定義一個科室物件,模擬我們日常工作的科室資訊。科室存在層級關係,還有一個員工數量的屬性。模型如下:
public class DepartmentDto { public int Id { get; set; } public int? ParentId { get; set; } public string Name { get; set; } public string TelPhone { get; set; } public string Address { get; set; } public string Remark { get; set; } public int EmployeeNumber { get; set; } }
初始化資料
public List<DepartmentDto> InitDepartmentData() { List<DepartmentDto> lst = new List<DepartmentDto>(); lst.AddRange(new DepartmentDto[] { new DepartmentDto() { Address ="一馬路XX號",Id=1,Name="一級一號科室",Remark="",TelPhone="0731-6111111",EmployeeNumber=3,},new DepartmentDto() { Address ="二馬路XX號",Id=2,Name="一級二號科室",EmployeeNumber=4,new DepartmentDto() { Address ="三馬路XX號",Id=3,Name="一級三號科室",TelPhone="0731-6222222",EmployeeNumber=6,new DepartmentDto() { Address ="一馬路XX號",ParentId=1,Id=4,Name="二級一號科室",EmployeeNumber=7,ParentId=2,Id=5,Name="二級二號科室",EmployeeNumber=5,}); return lst; }
獲取未存在父級科室的科室集合
List<DepartmentDto> lstDepartItems = InitDepartmentData(); //1、獲取未存在父級科室的科室集合 1、2、3 List<DepartmentDto> notExistsParentDepartmentIdLst = lstDepartItems .Where(p => !p.ParentId.HasValue) .ToList();
這裡比較簡單,Where內校驗下ParentId的值為空即可,不使用Linq則需要自己手寫一個迴圈搞定,如下:
List<DepartmentDto> notExistsParentDepartmentIdLst_1 = new List<DepartmentDto>(); foreach (DepartmentDto department in lstDepartItems) { if (!department.ParentId.HasValue) notExistsParentDepartmentIdLst_1.Add(department); }
這麼看感覺便捷性不太明顯是吧~~ 沒事,萬丈高樓平地起,咋們循行漸進~
獲取存在子科室的科室集合
//2、獲取存在子科室的科室集合 1、2 List<DepartmentDto> existsParentDepartmentIdLst1 = lstDepartItems .Where(p => lstDepartItems.Select(k => k.ParentId).Contains(p.Id)) .ToList();
這裡通過引用了外部的集合物件進行關聯,在Where內對子科室的ParentId欄位與當前集合的科室Id校驗,從而得到已存在子科室的科室集合。如果不使用Linq則自己需要寫兩個迴圈才能搞定。
如下:
List<DepartmentDto> existsParentDepartmentIdLst1_1 = new List<DepartmentDto>(); foreach (DepartmentDto parentDepart in lstDepartItems) { foreach (DepartmentDto childDepart in lstDepartItems) { if (parentDepart.Id == childDepart.ParentId) { existsParentDepartmentIdLst1_1.Add(parentDepart); continue; } } }
這麼看,Linq帶來的便捷性是否足夠明顯了,程式碼優雅了太多了~
科室根據地址分組,獲取科室總數、科室平均數
var groupDto = lstDepartItems .GroupBy(p => p.Address) .Select(p => new { Address = p.Key,SumEmployeeCount = p.Sum(p => p.EmployeeNumber),AvgEmployeeCount = p.Average(p => p.EmployeeNumber),}).ToList();
獲取兩個集合不相等的元素
引用型別的比較處理
這裡需要留意我們的科室物件是class,class在C#裡屬於引用型別。引用型別的比較和值型別的比較大不同相同!引用型別的比較是比較物件在記憶體堆裡指向的地址,並非物件包含的屬性值 但是我們預期的比較就是單純的對值進行比較,所以這裡需要通過實現IEqualityComparer<T>
介面來對兩個引用型別集合物件進行比較。
建立IEqualityComparer<DepartmentDto>
物件
public class DepartmentEqualityComparer : IEqualityComparer<DepartmentDto> { public bool Equals([AllowNull] DepartmentDto x,[AllowNull] DepartmentDto y) { // 這裡可以寫比較的關鍵程式碼~ return x.Id == y.Id; } public int GetHashCode([DisallowNull] DepartmentDto obj) { return obj.ToString().GetHashCode(); } }
接下來,定義一個新的集合,再手動向這個集合追加元素。
List<DepartmentDto> lstDepartItemsCPs = InitDepartmentData(); lstDepartItemsCPs.Add(new DepartmentDto() { Address = "三馬路XX號",Id = 6,Name = "二級三號科室",Remark = "",TelPhone = "0731-6222222",EmployeeNumber = 7 });
集合比較程式碼:
// 這裡如果DepartmentDto為引用型別(class)則需要使用比較器DepartmentEqualityComparer才能返回我們的預期值(根據ID值判斷是否相等) List<DepartmentDto> diffList = lstDepartItemsCPs.Except(lstDepartItems,new DepartmentEqualityComparer()).ToList(); // 獲取相等元素 List<DepartmentDto> diffList1 = lstDepartItemsCPs.Intersect(lstDepartItems.Select(p => p),new DepartmentEqualityComparer()).ToList(); // 需要新增IEqualityComparer,因為集合內的內容為引用型別!所以兩個集合的“值”是不同的,引用型別的值在這裡還包含了指向的記憶體堆的引用地址 bool isEqual = lstDepartItems.SequenceEqual(InitDepartmentData(),new DepartmentEqualityComparer());
值型別的比較處理
可能你覺得需要去建立IEqualityComparer<DepartmentDto>
物件過於麻煩,那麼想下是否一定需要將科室物件的型別設定為class,是否有更合適的型別可以替換? 答案是有的,微軟推薦如果對具體模型不需要多次執行裝箱、拆箱操作最好將模型設定為結構struct而非class。 現在我們回過頭將科室物件的型別更新為struct
。
然後發現上面集合比較的程式碼可以簡化成這樣:
// 這裡如果DepartmentDto為值型別(struct)則不需要使用比較器DepartmentEqualityComparer即可返回我們的預期值(根據ID值判斷是否相等) List<DepartmentDto> diffList3 = lstDepartItemsCPs.Except(lstDepartItems).ToList(); // 獲取相等元素 List<DepartmentDto> diffList4 = lstDepartItemsCPs.Intersect(lstDepartItems.Select(p => p)).ToList(); // 如果把DepartmentDto的型別改為值型別則可以不需要IEqualityComparer進行判斷的結果也會為true isEqual = lstDepartItems.SequenceEqual(InitDepartmentData());
OfType和Cast的不同之處
OfType允許對集合的項進行隱性轉換(非強轉Convert)且在轉換前會進行判斷,當型別不允許轉換則continue到下一個集合項。而Cast則是子項不進行判斷,直接隱性轉換,所以理論上效率更高,當然相對的出錯率也更高~
public void ConvertListTest() { try { object[] ss = { 1,"2",3,"四","五","7" }; // 1、3 OfType的本質是迴圈集合,對每個集合項進行型別驗證(不是強轉,所以此處的結果是1、3 而不是1、2、3、7) var lst = ss.ToList().OfType<int>().ToList(); // 3 int max = ss.OfType<int>().Max(); // 這句程式碼會提示“System.InvalidCastException:“Unable to cast object of type 'System.String' to type 'System.Int32'.”異常,原因:Cast的執行效率會略高與OfType,因為在對集合項進行型別轉換前不會對其進行型別校驗,當你確保集合的型別是安全的則可以用Cast,但是能用到Cast和OfType的時候基本上都是用OfType了.. int maxCast = ss.Cast<int>().Max(); } catch (Exception ex) { Console.WriteLine(ex); } }
上述程式碼已上傳到github,地址: https://github.com/QQ897878763/LinqStudySample.git
以上就是c# Linq常用的小技巧的詳細內容,更多關於c# Linq小技巧的資料請關注我們其它相關文章!