今天在公司做一个需求的时候,写的是面条代码,一个方法直接从头写到尾,其中用到了GroupBy,且GroupBy的KeySelector是多个属性而不是单个属性。
但是公司最近推干净代码行,要让代码有可读性。且作为一个有追求的程序员,肯定是不能写面条代码的,要对代码进行拆分。
重构前GroupBy大概是这样子的:
var=数据组。GroupBy (m=比;新{m。PropertyA m.PropertyB})
个人对于短的Linq比较习惯于用方法而不是用关键字的那种写法。
一开始这样写是没问题的,但是重构的时候问题就来了:这个团体是什么类型?
重构以后这个组是要作为参数进入到别的方法中的,方法签名显然是不能用var做类型推导,必须指定确定的类型。
我们知道GroupBy出来的东西是个泛型的东西,签名是IEnumerable
但是这个关键就有问题了。
我没有指定关键的类型,这里应该是匿名类型,于是定义了一个类型承接钥匙,代码变成了:
类EntityKey { 公共int PropertyA{设置;} 公共字符串PropertyB{设置;} } …… var=数据组。GroupBy (m=比;新EntityKey {PropertyA=m。PropertyA PropertyB=m.PropertyB});
但是后来我发现这样有问题,GroupBy指定的关键失效了。也就是说,组织的分组数量与数据的长度一致,每一个组里面只有一个对象。
发现这个问题后,我仔细思考了一下,大致猜到了问题出在哪里。
GroupBy这种东西,判断两个对象是不是一个分组,必然用到了相等判断。
虽然我没有看匿名类型反编译生成后的IL代码,不知道之前用的是怎么做的关键相等判断,但是引用类型的肯定是直接用对象的HashCode做判断。
这样子肯定是不行的,要解决引用类型的相等判断问题。
根据猜测,我写了一个示例程序最小化的重现了这个问题:
类项目 { 静态void Main (string [] args) { var=new List<列表;Student> (); 列表。添加(新学生(1,“猫”,“University1”)); 列表。添加(新学生(2中,“狗”10 " University1 ")); 列表。添加(新学生(3,“猪”,10日,“University2”)); 列表。添加(新学生(4,“鱼”,12日" University1 ")); var=组列表。GroupBy (m=比;新{m。年龄,m.Class}); foreach (var集团组) { 控制台。WriteLine(“年龄:{0},类:{1}”,group.Key。年龄,group.Key.Class); foreach (var的学生组) { Console.WriteLine(学生); } } } 类学生 { 公共int Id{得到;设置;} 公共字符串名称{;设置;} 公共int年龄{得到;设置;} 公共字符串类{;设置;} 公开学生(int id字符串名称,int年龄,字符串@class) { Id=Id; Name=名称; 年龄=年龄; 类=@class; } 公共覆盖字符串ToString () { 返回$ " Id={Id},名称={名称},年龄={}时代,阶级={类}”; } } 类StudentKey { 公共int年龄{得到;设置;} 公共字符串类{;设置;} } }
<强>这时候输出结果是强>
年龄:10类:University1
引用>
Id=1,名称=猫类==10岁University1
Id=2,名称=狗类==10岁University1
年龄:10类:University2
Id=3、Name=猪类==10岁University2
年龄:12类:University1
Id=4, Name=鱼、类==12岁University1将新{m。年龄,m。类}替换为新StudentKey{年龄=m。年龄、阶级=m.Class},结果却变成了
年龄:10类:University1
引用>
Id=1,名称=猫类==10岁University1
年龄:10类:University1
Id=2,名称=狗类==10岁University1
年龄:10类:University2
Id=3、Name=猪类==10岁University2
年龄:12类:University1
Id=4, Name=鱼、类==12岁University1Id=1和Id=2变成了两组。
解决问题方式有几种。
<强>第一种强>
最简单,就是直接将StudentKey从类变成结构。
c# GroupBy的基本使用教程