暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

C# Linq使用的一些小技巧

码农说一说 2021-01-10
142


什么是Linq

System.Linq是微软在Framework3.5版本里发布的一个标准查询库,它内置了丰富的操作符,语法类似SQL脚本,对于开发者而言几乎零学习成本。具体它有多强大基本是个DotNet程序猿都懂,这里就不做过多的介绍了。


一些Linq的建议


延迟执行

在C#代码里,Linq表达式的创建和执行可以分开编写与执行,这种方式叫延迟执行。通常在EF数据库ORM里对于一些查询场景需要额外注意,避免不必要的开销。

如:

    var items =deptItems.Select(p=>new {
    Code =p.Id.ToString(),
    Name = P.Name,
        });   
    这里才会真正执行上面的Select表达式
    var b = items.ToList();
    复制

    优先选择FirstOrDefault()代替First()

    对于一个元素为0的集合,使用First()会直接抛出异常,使用FirstOrDefault()则会返回元素的默认值,测试代码如下:

      private static void Test()
      {
      try
            {
      List<int> items = new List<int>();
                var a = items?.FirstOrDefault();
      var b = items?.First();
      }
            catch (Exception ex)
      {
      System.Console.WriteLine(ex.Message);
      }
       }
      复制

      可以这么说,用好了Linq可以大大提高程序猿的工作效率,毕竟我们的日常工作本质就是对数据的处理。经历了十多年的发展,现在微软自带的内库包含的Linq函数已经非常多了,几乎满足我们日常工作。

      具有集合的request对象或者DTO最好在构造函数时候就new出来,避免后续代码引用到这些对象时候报空引用异常

      如下面的rquest对象

         public class DepartmentRequest
        {
        public DepartmentRequest()
        {
        默认就给Itmes赋予一个默认值(没有任何元素的空集合),避免后续代码中直接对该集合操作时候出现空引用异常
        Items = new List<DepartTagDto>();
                }       
        public List<DepartTagDto> Items { get; set; }
        }


        public class DepartTagDto
        {
                public int Id { get; set; }
        public string Title { get; set; }
        }
        复制


        接下来就是一些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="一级二号科室",
            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;
            }
            复制


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

                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则是子项不进行判断,直接隐性转换,所以理论上效率更高,当然相对的出错率也更高,这里使用OfType对集合内元素进行类型转化:

                               

                                  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




                                文章转载自码农说一说,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                评论