c# Linq常用的小技巧
前言
在C#語(yǔ)言發(fā)展的歷史長(zhǎng)河中,Linq是一個(gè)極其重要的里程碑!
Linq的語(yǔ)法吸取了SQL語(yǔ)法的特性,同時(shí)配合Lambda表達(dá)式又可以使代碼更加優(yōu)雅!
可以這么說(shuō),用好了Linq可以大大提高程序猿的工作效率,畢竟我們的日常工作本質(zhì)就是對(duì)數(shù)據(jù)的處理。經(jīng)歷了十多年的發(fā)展,現(xiàn)在微軟自帶的內(nèi)庫(kù)包含的Linq函數(shù)已經(jīng)非常多了,幾乎滿足我們?nèi)粘9ぷ鳌?/p>
下面根據(jù)一個(gè)對(duì)科室數(shù)據(jù)操作的例子,就個(gè)人覺(jué)得日常高頻使用的Linq小技巧貼出來(lái),權(quán)當(dāng)是做個(gè)筆記了。
初始化數(shù)據(jù)
定義模型
這里定義一個(gè)科室對(duì)象,模擬我們?nèi)粘9ぷ鞯目剖倚畔ⅰ?剖掖嬖趯蛹?jí)關(guān)系,還有一個(gè)員工數(shù)量的屬性。模型如下:
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; } }
初始化數(shù)據(jù)
public List<DepartmentDto> InitDepartmentData() { List<DepartmentDto> lst = new List<DepartmentDto>(); lst.AddRange(new DepartmentDto[] { new DepartmentDto() { Address ="一馬路XX號(hào)", Id=1, Name="一級(jí)一號(hào)科室", Remark="", TelPhone="0731-6111111", EmployeeNumber=3, }, new DepartmentDto() { Address ="二馬路XX號(hào)", Id=2, Name="一級(jí)二號(hào)科室", Remark="", TelPhone="0731-6111111", EmployeeNumber=4, }, new DepartmentDto() { Address ="三馬路XX號(hào)", Id=3, Name="一級(jí)三號(hào)科室", Remark="", TelPhone="0731-6222222", EmployeeNumber=6, }, new DepartmentDto() { Address ="一馬路XX號(hào)", ParentId=1, Id=4, Name="二級(jí)一號(hào)科室", Remark="", TelPhone="0731-6222222", EmployeeNumber=7, }, new DepartmentDto() { Address ="二馬路XX號(hào)", ParentId=2, Id=5, Name="二級(jí)二號(hào)科室", Remark="", TelPhone="0731-6222222", EmployeeNumber=5, }, }); return lst; }
獲取未存在父級(jí)科室的科室集合
List<DepartmentDto> lstDepartItems = InitDepartmentData(); //1、獲取未存在父級(jí)科室的科室集合 1、2、3 List<DepartmentDto> notExistsParentDepartmentIdLst = lstDepartItems .Where(p => !p.ParentId.HasValue) .ToList();
這里比較簡(jiǎn)單,Where內(nèi)校驗(yàn)下ParentId的值為空即可,不使用Linq則需要自己手寫一個(gè)循環(huán)搞定,如下:
List<DepartmentDto> notExistsParentDepartmentIdLst_1 = new List<DepartmentDto>(); foreach (DepartmentDto department in lstDepartItems) { if (!department.ParentId.HasValue) notExistsParentDepartmentIdLst_1.Add(department); }
這么看感覺(jué)便捷性不太明顯是吧~~ 沒(méi)事,萬(wàn)丈高樓平地起,咋們循行漸進(jìn)~
獲取存在子科室的科室集合
//2、獲取存在子科室的科室集合 1、2 List<DepartmentDto> existsParentDepartmentIdLst1 = lstDepartItems .Where(p => lstDepartItems.Select(k => k.ParentId).Contains(p.Id)) .ToList();
這里通過(guò)引用了外部的集合對(duì)象進(jìn)行關(guān)聯(lián),在Where內(nèi)對(duì)子科室的ParentId字段與當(dāng)前集合的科室Id校驗(yàn),從而得到已存在子科室的科室集合。如果不使用Linq則自己需要寫兩個(gè)循環(huán)才能搞定。
如下:
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帶來(lái)的便捷性是否足夠明顯了,代碼優(yōu)雅了太多了~
科室根據(jù)地址分組,獲取科室總數(shù)、科室平均數(shù)
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();
獲取兩個(gè)集合不相等的元素
引用類型的比較處理
這里需要留意我們的科室對(duì)象是class,class在C#里屬于引用類型。引用類型的比較和值類型的比較大不同相同!引用類型的比較是比較對(duì)象在內(nèi)存堆里指向的地址,并非對(duì)象包含的屬性值 但是我們預(yù)期的比較就是單純的對(duì)值進(jìn)行比較,所以這里需要通過(guò)實(shí)現(xiàn)IEqualityComparer<T>
接口來(lái)對(duì)兩個(gè)引用類型集合對(duì)象進(jìn)行比較。
創(chuàng)建IEqualityComparer<DepartmentDto>
對(duì)象
public class DepartmentEqualityComparer : IEqualityComparer<DepartmentDto> { public bool Equals([AllowNull] DepartmentDto x, [AllowNull] DepartmentDto y) { // 這里可以寫比較的關(guān)鍵代碼~ return x.Id == y.Id; } public int GetHashCode([DisallowNull] DepartmentDto obj) { return obj.ToString().GetHashCode(); } }
接下來(lái),定義一個(gè)新的集合,再手動(dòng)向這個(gè)集合追加元素。
List<DepartmentDto> lstDepartItemsCPs = InitDepartmentData(); lstDepartItemsCPs.Add(new DepartmentDto() { Address = "三馬路XX號(hào)", Id = 6, Name = "二級(jí)三號(hào)科室", Remark = "", TelPhone = "0731-6222222", EmployeeNumber = 7 });
集合比較代碼:
// 這里如果DepartmentDto為引用類型(class)則需要使用比較器DepartmentEqualityComparer才能返回我們的預(yù)期值(根據(jù)ID值判斷是否相等) List<DepartmentDto> diffList = lstDepartItemsCPs.Except(lstDepartItems, new DepartmentEqualityComparer()).ToList(); // 獲取相等元素 List<DepartmentDto> diffList1 = lstDepartItemsCPs.Intersect(lstDepartItems.Select(p => p), new DepartmentEqualityComparer()).ToList(); // 需要添加IEqualityComparer,因?yàn)榧蟽?nèi)的內(nèi)容為引用類型!所以兩個(gè)集合的“值”是不同的,引用類型的值在這里還包含了指向的內(nèi)存堆的引用地址 bool isEqual = lstDepartItems.SequenceEqual(InitDepartmentData(), new DepartmentEqualityComparer());
值類型的比較處理
可能你覺(jué)得需要去創(chuàng)建IEqualityComparer<DepartmentDto>
對(duì)象過(guò)于麻煩,那么想下是否一定需要將科室對(duì)象的類型設(shè)定為class,是否有更合適的類型可以替換? 答案是有的,微軟推薦如果對(duì)具體模型不需要多次執(zhí)行裝箱、拆箱操作最好將模型設(shè)置為結(jié)構(gòu)struct而非class。 現(xiàn)在我們回過(guò)頭將科室對(duì)象的類型更新為struct
。
然后發(fā)現(xiàn)上面集合比較的代碼可以簡(jiǎn)化成這樣:
// 這里如果DepartmentDto為值類型(struct)則不需要使用比較器DepartmentEqualityComparer即可返回我們的預(yù)期值(根據(jù)ID值判斷是否相等) List<DepartmentDto> diffList3 = lstDepartItemsCPs.Except(lstDepartItems).ToList(); // 獲取相等元素 List<DepartmentDto> diffList4 = lstDepartItemsCPs.Intersect(lstDepartItems.Select(p => p)).ToList(); // 如果把DepartmentDto的類型改為值類型則可以不需要IEqualityComparer進(jìn)行判斷的結(jié)果也會(huì)為true isEqual = lstDepartItems.SequenceEqual(InitDepartmentData());
OfType和Cast的不同之處
OfType允許對(duì)集合的項(xiàng)進(jìn)行隱性轉(zhuǎn)換(非強(qiáng)轉(zhuǎn)Convert)且在轉(zhuǎn)換前會(huì)進(jìn)行判斷,當(dāng)類型不允許轉(zhuǎn)換則continue到下一個(gè)集合項(xiàng)。而Cast則是子項(xiàng)不進(jìn)行判斷,直接隱性轉(zhuǎn)換,所以理論上效率更高,當(dāng)然相對(duì)的出錯(cuò)率也更高~
public void ConvertListTest() { try { object[] ss = { 1, "2", 3, "四", "五", "7" }; // 1、3 OfType的本質(zhì)是循環(huán)集合,對(duì)每個(gè)集合項(xiàng)進(jìn)行類型驗(yàn)證(不是強(qiáng)轉(zhuǎn),所以此處的結(jié)果是1、3 而不是1、2、3、7) var lst = ss.ToList().OfType<int>().ToList(); // 3 int max = ss.OfType<int>().Max(); // 這句代碼會(huì)提示“System.InvalidCastException:“Unable to cast object of type 'System.String' to type 'System.Int32'.”異常,原因:Cast的執(zhí)行效率會(huì)略高與OfType,因?yàn)樵趯?duì)集合項(xiàng)進(jìn)行類型轉(zhuǎn)換前不會(huì)對(duì)其進(jìn)行類型校驗(yàn),當(dāng)你確保集合的類型是安全的則可以用Cast,但是能用到Cast和OfType的時(shí)候基本上都是用OfType了.. int maxCast = ss.Cast<int>().Max(); } catch (Exception ex) { Console.WriteLine(ex); } }
上述代碼已上傳到github,地址: https://github.com/QQ897878763/LinqStudySample.git
以上就是c# Linq常用的小技巧的詳細(xì)內(nèi)容,更多關(guān)于c# Linq小技巧的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#歸并排序的實(shí)現(xiàn)方法(遞歸,非遞歸,自然歸并)
C#歸并排序的實(shí)現(xiàn)方法(遞歸,非遞歸,自然歸并),需要的朋友可以參考一下2013-04-04unity通過(guò)Mesh網(wǎng)格繪制圖形(三角形、正方體、圓柱)
這篇文章主要為大家詳細(xì)介紹了unity通過(guò)Mesh網(wǎng)格繪制圖形:三角形、正方體、圓柱,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11C#難點(diǎn)逐個(gè)擊破(4):main函數(shù)
貌似我是在寫C#的學(xué)習(xí)筆記哦,不過(guò)反正可以利用這個(gè)機(jī)會(huì)來(lái)好好溫習(xí)下基礎(chǔ)知識(shí),這其中很多知識(shí)點(diǎn)都屬于平時(shí)視而見的小知識(shí)2010-02-02C#中通過(guò)使用Connection類來(lái)實(shí)現(xiàn)打開/關(guān)閉數(shù)據(jù)庫(kù)的代碼實(shí)例
今天小編就為大家分享一篇關(guān)于C#中通過(guò)使用Connection類來(lái)實(shí)現(xiàn)打開/關(guān)閉數(shù)據(jù)庫(kù)的代碼實(shí)例,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-10-10C#連接SQL Server數(shù)據(jù)庫(kù)的實(shí)例講解
在本篇文章里小編給大家整理了關(guān)于C#連接SQL Server數(shù)據(jù)庫(kù)的實(shí)例內(nèi)容,有需要的朋友們參考學(xué)習(xí)下。2020-01-01C#?WPF后臺(tái)動(dòng)態(tài)添加控件實(shí)戰(zhàn)教程
最近嘗試用wpf在后臺(tái)動(dòng)態(tài)添加控件,所以下面這篇文章主要給大家介紹了關(guān)于C#?WPF后臺(tái)動(dòng)態(tài)添加控件的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05