- 介绍
- 具体案例
- 将对象转为字典集合
- 将原始序列进行分组
- 按员工所属部门
- DefaultIfEmpty方法的作用
- 将分组后的序列重新排序
- 使用并行LINQ
- 总结
随着.net core
越来越流行,对.net core
基础知识的了解,实际应用等相关的知识也应该有所了解。所以就有了这篇文章,案例都是来自阅读的书籍,或者实际工作中感觉比较有用的应用。分享亦总结。
本文主要介绍 .net core
相关的LINQ案例。
【导语】
ToDictionary
扩展方法比较有趣,它可以将序列中的每个元素转换位Key-Value
对,然后组成以一个字典集合。
本实例用到以下重载版本。
Dictionary ToDictionary(this IEnumerable source, Func keySelector, Func elementSelector);
其中,keySelector
参数与 elementSelector
参数都是委托类型,分别用于返回作为字典中元素的 Key
和 Value
。
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:引入命名空间。
using System.Collections.Generic;
using System.Linq;
步骤3:声明一个类,它表示一件产品的基础信息。
public class Production
{
///
/// 产品编号
///
public int PID { get; set; }
///
/// 产品名称
///
public string Name { get; set; }
///
/// 产品尺寸
///
public float Size { get; set; }
///
/// 生产数量
///
public int Quantity { get; set; }
}
步骤4:在 Main
方法中声明一个 Production
数组,并进行实例化。
Production[] prds =
{
new Production
{
PID = 4007,
Name = "产品 1",
Size = 123.45f,
Quantity = 65
},
new Production
{
PID = 4008,
Name = "产品 2",
Size = 77.01f,
Quantity = 100
},
new Production
{
PID = 4012,
Name = "产品 3",
Size = 45.13f,
Quantity = 25
}
};
步骤5:调用 ToDictionary
方法将数组中的 Production
元素转为字典结构的数据。其中,PID
属性将作为字典的 Key
,Name
属性将作为字典的 Value
。
IDictionary dic = prds.ToDictionary(p => p.PID, p => p.Name);
步骤6:输出新生成的字典集合中的元素信息。
Console.WriteLine("转化得到的字典数据:");
foreach(var kp in dic)
{
Console.WriteLine("{0} - {1}", kp.Key, kp.Value);
}
步骤7:运行应用程序项目,结果如下。
【导语】
将原始序列中的元素进行分组,可以调用 GroupBy
扩展方法。此方法有多个重载的版本,本实例使用了以下重载的形式。
IEnumerable GroupBy(this IEnumerable source, Func keySelector, Func resultSelector);
其中有三个类型参数:TSource
是原始序列中的元素,TKey
是分组依据(例如按某个对象的 Age
属性进行分组,那么 TKey
就是 Age
属性的类型),TResult
是返回给调用方的以分组序列中的元素类型。
keySelector
委托用于产生分组依据,resultSelector
委托则用于产生分组结果。resultSelector
委托有两个输入参数:第一个参数是分组依据,即该分组的“标题”;第二个擦拭你和是隶属该分组下的元素所组成的子序列。
本实例中,Student
类表示学生的信息,代码将对学生列表中的对象按照它们各自所参与的课程分组,例如,参与学习 C++
语言的学生便构成了一个分组。
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:引入以下命名空间。
using System.Linq;
using System.Text;
步骤3:声明Student类,表示学生信息。
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Course { get; set; }
}
步骤4:在 Main
方法中实例化一个 Student
数组并填充一些示例元素。
Student[] stus =
{
new Student
{
ID = 201,
Name = "小王",
Course = "C"
},
new Student
{
ID = 202,
Name = "小曾",
Course = "C++"
},
new Student
{
ID = 203,
Name = "小吕",
Course = "C++"
},
new Student
{
ID = 204,
Name = "小孙",
Course = "C#"
},
new Student
{
ID = 205,
Name = "小郑",
Course = "C"
},
new Student
{
ID = 206,
Name = "小叶",
Course = "C"
},
new Student
{
ID = 207,
Name = "小苏",
Course = "C#"
},
new Student
{
ID = 208,
Name = "小梁",
Course = "Delphi"
}
};
步骤5:调用 GroupBy
方法,按照学生所参与的课程进行分组。
var result = stus.GroupBy(s => s.Course, (gKey, gItems) => (GroupKey: gKey, ItemCount: gItems.Count(), Items: gItems));
调用以上方法后,产生的结果类型是三元素序列,其中 GroupKey
字段表示分组标题,ItemCount
字段表示该分组下的学生数量,Items
字段表示属于该分组的学生列表。
步骤6:输出分组后的序列信息。
Console.WriteLine("学员参与课程汇总:");
StringBuilder strbuilder = new StringBuilder();
foreach(var g in result)
{
strbuilder.AppendFormat("课程:{0}\n", g.GroupKey);
strbuilder.AppendFormat(" 参与人数:{0}\n", g.ItemCount);
strbuilder.AppendLine(" 名单:");
foreach (Student s in g.Items)
{
strbuilder.AppendFormat(" {0} - {1}\n", s.ID, s.Name);
}
}
Console.WriteLine(strbuilder);
以上代码使用了 StringBuilder
类来组装字符串,再通过 WriteLine
方法进行输出。
步骤7:运行应用程序项目,结果如下。
【导语】
本实例假设 Employee
类表示某公司的员工信息,其中,Name
属性是员工名称,Department
属性是员工所属的部门。随后将员工信息序列按照部门进行分组。
在 LINIQ
查询中对序列进行分组需要用到 group···by
子句,group
关键字之后是要进行分组的对象,by
关键字之后是分组标题,即依据什么进行分组。
分组后会返回一个实现了 IGrouping
接口的对象实例,该接口也继承了 IEnumerable
接口成员,使得代码支持枚举此分组下的元素,同时又带有要给 Key
属性,即分组标题。
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:定义 Employee
类。
public class Employee
{
public string Name { get; set; }
public string Department { get; set; }
}
步骤3:初始化一个 Employee
数组。
Employee[] emps =
{
new Employee{ Name = "小黄", Department = "财务部" },
new Employee{ Name = "小卢", Department = "开发部" },
new Employee{ Name = "小邢", Department = "开发部" },
new Employee{ Name = "小陈", Department = "财务部" },
new Employee{ Name = "小卜", Department = "公关部" },
new Employee{ Name = "小罗", Department = "仓储部" },
new Employee{ Name = "小许", Department = "开发部" },
new Employee{ Name = "小田", Department = "仓储部" }
};
步骤4:通过 LINQ
查询,将员工信息序列按部门名称分组。
var q = from e in emps
group e by e.Department;
步骤5:输出分组后的序列信息。
foreach (var g in q)
{
Console.WriteLine("{0}:", g.Key);
foreach (var emp in g)
{
Console.WriteLine(" {0}", emp.Name);
}
Console.WriteLine();
}
步骤6:运行应用程序项目,结果如下。
【导语】
DefaultIfEmpty
方法的作用是:当某个序列中没恶意元素时,将返回该元素类型的默认值,例如下面的序列。
List l = new List();
此时,列表中每月元素,调用以下代码返回一个只有单个元素的序列,其中包含 int
类型的默认值,即 0。
var e = l.DefaultIfEmpty();
DefaultIfEmpty
方法一般用于联合查询中,当第二个序列中不存在与第一个序列匹配的元素时将返回元素的默认值,以保证第一规格序列中的元素能够全部查询出来,即“左外联”查询。
本实例将定义两个序列:一个是订单序列(Order
类表示),另一个是订单详细数据序列(OrderDetails
类表示),而 Order
类的 Details
属性会引用一个关联的 OrderDetails
实例。在两个序列联合查询是,一旦订单详细数据序列中不存在与 Details
属性匹配(Details
属性为 null
),就返回一个固定的 OrderDetails
实例作为默认值。
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:声明 OrderDetails
类,表示订单详细信息。
public class OrderDetails
{
public int Amount { get; set; }
public decimal Price { get; set; }
public string Code { get; set; }
}
步骤3:声明 Order
类,表示订单信息,其中 Details
属性应用 OrderDetails
实例。
public class Order
{
public int ID { get; set; }
public DateTime Date { get; set; }
public bool State { get; set; }
public OrderDetails Details { get; set; }
}
步骤4:实例化两个 OrderDetails
对象。
OrderDetails d1 = new OrderDetails
{
Amount = 10,
Price = 2.5M,
Code = "T-70770"
};
OrderDetails d2 = new OrderDetails
{
Amount = 12,
Price = 3.2M,
Code = "T-70778"
};
步骤5:实例化三个 Order
对象。第三个 Order
实例的 Details
属性每月引用任何对象。
Order o1 = new Order
{
ID = 1,
Date = new DateTime(2018, 3, 1),
State = true,
Details = d1
};
Order o2 = new Order
{
ID = 2,
Date = new DateTime(2018, 3, 13),
State = false,
Details = d2
};
Order o3 = new Order
{
ID = 3,
Date = new DateTime(2018, 3, 18),
State = true,
Details = null
};
步骤6:声明两个 List
集合,用于存放以上对象。
List orders = new List { o1, o2, o3 };
List details = new List { d1, d2 };
步骤7:对上述两个序列进行联合查询。
var q = from o in orders
join d in details on o.Details equals d into g
from x in g.DefaultIfEmpty(new OrderDetails { Amount = 0, Price = 0.00M,Code = "未知编码" })
select (OrderID: o.ID, Amout: x.Amount, Code: x.Code);
当每月匹配项时,产生一个固定的 OrderDetails
对象作为默认值,其中 Amount
属性为 0,Price
属性为 0.00
,Code
属性为"未知编码"。
步骤8:输出查询下结果到控制台。
foreach (var i in q)
{
Console.WriteLine("{0,-11}{1,-10}{2,-20}", i.OrderID, i.Amout, i.Code);
}
步骤9:运行应用程序项目,结果如下。
【导语】
group
子句可以搭配 into
关键字,暂时存放依据分好组的元素,例如:
group x by x.Type into gs
其中,gs
就是保存该分组序列的临时变量名。
在完成对数据序列的分组后,可以嵌套一个 LINQ
查询对组内元素进行重新排列,例如:
from a in someList
group a by a.Type int gk
let q2 = (from b in gk orderby b select b)
select new { Key = gk.Key, SubItems = q2 };
其中,临时变量 q2
所引用的就是一个嵌套 LINQ
查询。
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:初始一个 string
类型的数组。
string[] arrsrc =
{
"at", "act", "market", "fable", "also", "alt", "bee", "back", "book", "build", "face", "full", "fish", "food", "find", "meet", "make", "moo", "muklek"
};
步骤3:
var q = from s in arrsrc
group s by s[0].ToString().ToUpper() into g
orderby g.Key
let nq = (from w in g
orderby w
select w)
select (Key: g.Key, Items: nq);
步骤4:
foreach (var t in q)
{
Console.WriteLine(t.Key);
foreach (var sub in t.Items)
{
Console.WriteLine(" {0}", sub);
}
}
步骤5:运行应用程序项目,结果如下。
【导语】
开启 LINQ
查询的并行模式,只需要在原序列上调用 AsParallel
扩展方法,但是不应该滥用并行模式。如果查询的量很小,并且在查询的过程中每月过于复杂的处理,一般不建议使用并行模式。
满足以下条件的查询,可以考虑以并行模式执行:
- 序列中数量很大。
LINQ
查询中where
与select
子句上需要额外的处理工作(例如要转换类型)- 对产生的结果没有严格的顺序要求(尽管并行查询可以调用
AsOrdered
扩展方法来维持序列的顺序,但在一定程度上会降低新能,仅在必要时使用)。
本实例将定义一个 Rectangle
结构,它表示一个矩形的信息,其中包含宽度和高度两个字段。实例代码以并行方式生成要给庞大的 Rectangle
序列,仍然会分别用普通和并行两种模式进行 LINQ
查询,最后分别统计出两种模式下执行 LINQ
查询所消耗的时间(单位是 ms
)。
【操作流程】
步骤1:新建控制台应用程序项目。
步骤2:定义 Rectangle
结构。
public struct Rectangle
{
public double Width;
public double Height;
}
步骤3:初始化 Rectangle
序列,本例使用 ConcurrentQueue
集合来包装,该集合支持并行操作,并且是线程安全的。
ConcurrentQueue testList = new ConcurrentQueue();
步骤4:以并行方式向集合中添加元素。
Parallel.For(20, 300000000, n =>
{
testList.Enqueue(new Rectangle
{
Width = n,
Height = n
});
});
步骤5:分别以普通模式、并行模式执行 LINQ
查询,在select子句中计算矩形的面积。Stopwatch
组件的作用是计算执行代码所消耗的时间。
Stopwatch watch = new Stopwatch();
watch.Restart();
var q1 = from x in testList
select x.Width * x.Height;
watch.Stop();
Console.WriteLine("普通模式,耗时:{0} ms", watch.ElapsedMilliseconds);
watch.Restart();
var q2 = from x in testList.AsParallel()
select x.Width * x.Height;
watch.Stop();
Console.WriteLine("并行模式,耗时:{0} ms", watch.ElapsedMilliseconds);
步骤6:运行应用程序项目,结果如下。
出输出来看,在并行模式下执行 LINQ
查询确实提升了性能。
注意:Stopwatch
组件自身在运行期间也会占用一定的 CPU
资源,因此该组件所统计的耗时并非完全准确,仅供参考。
本文到这里就结束了,下一篇将介绍文件与I/O的知识案例。