.net core精彩实例分享 -- LINQ

介绍

随着.net core越来越流行,对.net core 基础知识的了解,实际应用等相关的知识也应该有所了解。所以就有了这篇文章,案例都是来自阅读的书籍,或者实际工作中感觉比较有用的应用。分享亦总结。

本文主要介绍 .net core 相关的LINQ案例。

具体案例

将对象转为字典集合

【导语】

ToDictionary 扩展方法比较有趣,它可以将序列中的每个元素转换位Key-Value对,然后组成以一个字典集合。

本实例用到以下重载版本。

Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector);

其中,keySelector 参数与 elementSelector 参数都是委托类型,分别用于返回作为字典中元素的 KeyValue

【操作流程】

步骤1:新建控制台应用程序项目。

步骤2:引入命名空间。

using System.Collections.Generic;
using System.Linq;

步骤3:声明一个类,它表示一件产品的基础信息。

public class Production
{
    /// <summary>
    /// 产品编号
    /// </summary>
    public int PID { get; set; }
    /// <summary>
    /// 产品名称
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 产品尺寸
    /// </summary>
    public float Size { get; set; }
    /// <summary>
    /// 生产数量
    /// </summary>
    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 属性将作为字典的 KeyName 属性将作为字典的 Value

IDictionary<int, string> 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<TResult> GroupBy<TSource, TKey, TResult>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, IEnumerable<TSource>, TResult> 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方法的作用

【导语】

DefaultIfEmpty 方法的作用是:当某个序列中没恶意元素时,将返回该元素类型的默认值,例如下面的序列。

List<int > l = new List<int>();

此时,列表中每月元素,调用以下代码返回一个只有单个元素的序列,其中包含 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<Order> orders = new List<Order> { o1, o2, o3 };
List<OrderDetails> details = new List<OrderDetails> { 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 属性为 0Price 属性为 0.00Code 属性为"未知编码"。

步骤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

【导语】

开启 LINQ 查询的并行模式,只需要在原序列上调用 AsParallel 扩展方法,但是不应该滥用并行模式。如果查询的量很小,并且在查询的过程中每月过于复杂的处理,一般不建议使用并行模式。

满足以下条件的查询,可以考虑以并行模式执行:

  • 序列中数量很大。
  • LINQ 查询中 whereselect 子句上需要额外的处理工作(例如要转换类型)
  • 对产生的结果没有严格的顺序要求(尽管并行查询可以调用 AsOrdered 扩展方法来维持序列的顺序,但在一定程度上会降低新能,仅在必要时使用)。

本实例将定义一个 Rectangle 结构,它表示一个矩形的信息,其中包含宽度和高度两个字段。实例代码以并行方式生成要给庞大的 Rectangle 序列,仍然会分别用普通和并行两种模式进行 LINQ 查询,最后分别统计出两种模式下执行 LINQ 查询所消耗的时间(单位是 ms)。

【操作流程】

步骤1:新建控制台应用程序项目。

步骤2:定义 Rectangle 结构。

public struct Rectangle
{
    public double Width;
    public double Height;
}

步骤3:初始化 Rectangle 序列,本例使用 ConcurrentQueue<T> 集合来包装,该集合支持并行操作,并且是线程安全的。

ConcurrentQueue<Rectangle> testList = new ConcurrentQueue<Rectangle>();

步骤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的知识案例。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值