服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - c# Linq常用的小技巧

c# Linq常用的小技巧

2022-09-22 15:13长沙大鹏 C#

这篇文章主要介绍了c# Linq常用的小技巧,文中讲解非常详细,示例代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下

前言

在C#语言发展的历史长河中,Linq是一个极其重要的里程碑!

Linq的语法吸取了SQL语法的特性,同时配合Lambda表达式又可以使代码更加优雅!
可以这么说,用好了Linq可以大大提高程序猿的工作效率,毕竟我们的日常工作本质就是对数据的处理。经历了十多年的发展,现在微软自带的内库包含的Linq函数已经非常多了,几乎满足我们日常工作。

下面根据一个对科室数据操作的例子,就个人觉得日常高频使用的Linq小技巧贴出来,权当是做个笔记了。

初始化数据

定义模型

这里定义一个科室对象,模拟我们日常工作的科室信息。科室存在层级关系,还有一个员工数量的属性。模型如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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; }
}

初始化数据

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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="一级二号科室",
    Remark="",
    TelPhone="0731-6111111",
    EmployeeNumber=4,
  },
  new DepartmentDto() {
    Address ="三马路XX号",
    Id=3,
    Name="一级三号科室",
    Remark="",
    TelPhone="0731-6222222",
    EmployeeNumber=6,
  },
  new DepartmentDto() {
    Address ="一马路XX号",
    ParentId=1,
    Id=4,
    Name="二级一号科室",
    Remark="",
    TelPhone="0731-6222222",
    EmployeeNumber=7,
  },
  new DepartmentDto() {
    Address ="二马路XX号",
    ParentId=2,
    Id=5,
    Name="二级二号科室",
    Remark="",
    TelPhone="0731-6222222",
    EmployeeNumber=5,
  },
 });
 return lst;
}

获取未存在父级科室的科室集合

?
1
2
3
4
5
List<DepartmentDto> lstDepartItems = InitDepartmentData();
  //1、获取未存在父级科室的科室集合 1、2、3  
  List<DepartmentDto> notExistsParentDepartmentIdLst = lstDepartItems
   .Where(p => !p.ParentId.HasValue)
   .ToList();

这里比较简单,Where内校验下ParentId的值为空即可,不使用Linq则需要自己手写一个循环搞定,如下:

?
1
2
3
4
5
6
List<DepartmentDto> notExistsParentDepartmentIdLst_1 = new List<DepartmentDto>();
foreach (DepartmentDto department in lstDepartItems)
{
  if (!department.ParentId.HasValue)
   notExistsParentDepartmentIdLst_1.Add(department);
}

这么看感觉便捷性不太明显是吧~~ 没事,万丈高楼平地起,咋们循行渐进~

获取存在子科室的科室集合

?
1
2
3
4
//2、获取存在子科室的科室集合 1、2
  List<DepartmentDto> existsParentDepartmentIdLst1 = lstDepartItems
   .Where(p => lstDepartItems.Select(k => k.ParentId).Contains(p.Id))
   .ToList();

这里通过引用了外部的集合对象进行关联,在Where内对子科室的ParentId字段与当前集合的科室Id校验,从而得到已存在子科室的科室集合。如果不使用Linq则自己需要写两个循环才能搞定。
如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
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带来的便捷性是否足够明显了,代码优雅了太多了~

科室根据地址分组,获取科室总数、科室平均数

?
1
2
3
4
5
6
7
8
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>对象

?
1
2
3
4
5
6
7
8
9
10
11
12
13
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();
 }
}

接下来,定义一个新的集合,再手动向这个集合追加元素。

?
1
2
3
4
5
6
7
8
9
10
List<DepartmentDto> lstDepartItemsCPs = InitDepartmentData();
  lstDepartItemsCPs.Add(new DepartmentDto()
  {
   Address = "三马路XX号",
   Id = 6,
   Name = "二级三号科室",
   Remark = "",
   TelPhone = "0731-6222222",
   EmployeeNumber = 7
  });

集合比较代码:

?
1
2
3
4
5
6
// 这里如果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

然后发现上面集合比较的代码可以简化成这样:

?
1
2
3
4
5
6
// 这里如果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则是子项不进行判断,直接隐性转换,所以理论上效率更高,当然相对的出错率也更高~

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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小技巧的资料请关注服务器之家其它相关文章!

延伸 · 阅读

精彩推荐
  • C#C#在图片增加文字的实现代码

    C#在图片增加文字的实现代码

    最近做项目需要动态给图片增加文字(书本的封面图片),修改字体大小、字体、颜色、控制位置等,下面通过实例代码给大家分享C#在图片增加文字的实...

    空空隆隆4462022-01-10
  • C#UGUI实现ScrollView无限滚动效果

    UGUI实现ScrollView无限滚动效果

    这篇文章主要为大家详细介绍了UGUI实现ScrollView无限滚动效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    Tong19932223802022-03-11
  • C#C#模拟http 发送post或get请求的简单实例

    C#模拟http 发送post或get请求的简单实例

    下面小编就为大家带来一篇C#模拟http 发送post或get请求的简单实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    C#教程网3702021-11-24
  • C#C#字符串自增自减算法详解

    C#字符串自增自减算法详解

    这篇文章主要为大家详细介绍了C#字符串自增自减的算法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    云梦鸿6612022-01-19
  • C#c#打开py文件的方法

    c#打开py文件的方法

    在本篇内容里小编给大家分享的是关于c#打开py文件的方法和步骤,需要的朋友们可以跟着学习下。...

    C#教程网3672022-03-07
  • C#关于C#连接FTP时路径问题的解决方法

    关于C#连接FTP时路径问题的解决方法

    最近在工作中遇到一个需求,需要利用C#连接FTP,在连接过程中遇到一个问题,所以下面这篇文章主要给大家介绍了关于C#连接FTP时路径问题的解决方法,需...

    菜鸟葫芦娃11952022-01-19
  • C#C#正则过滤HTML标签并保留指定标签的方法

    C#正则过滤HTML标签并保留指定标签的方法

    这篇文章主要介绍了C#正则过滤HTML标签并保留指定标签的方法,涉及C#针对页面HTML元素正则匹配与替换相关操作技巧,需要的朋友可以参考下...

    蓝色水6772022-01-11
  • C#使用C# CefSharp Python采集某网站简历并且自动发送邀请短信的方法

    使用C# CefSharp Python采集某网站简历并且自动发送邀请短信的方法

    这篇文章主要给大家介绍了关于如何使用C# CefSharp Python采集某网站简历并且自动发送邀请短信的相关资料,文中通过示例代码介绍的非常详细,对大家的学...

    ROTA4892022-07-11