百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Linq 下的扩展方法太少了,MoreLinq 来啦

itomcoil 2025-07-10 16:01 3 浏览

一:背景

1. 讲故事

前几天看同事在用 linq 给内存中的两个 model 做左连接,用过的朋友都知道,你一定少不了一个叫做 DefaultIfEmpty 函数,这玩意吧,本来很流畅的 from...in...join, 突然搞进来这么一个函数,真的是恶心他妈给恶心开门,恶心到家了,简化后的代码如下:


    class User
    {
        public int UserID { get; set; }
        public string Email { get; set; }
    }

    class Order
    {
        public int OrderID { get; set; }
        public string OrderTitle { get; set; }
        public int UserID { get; set; }
    }

    static void Main(string[] args)
    {
         var userList = new List<User>()
            {
                new User(){ UserID=1, Email="333@qq.com"},
                new User(){ UserID=2, Email="444@qq.com"},
            };

            var orderList = new List<Order>()
            {
                new Order(){ OrderID=1, OrderTitle="订单1", UserID=1},
                new Order(){ OrderID=2, OrderTitle="订单2", UserID=1}
            };

            var query = from u in userList
                        join o in orderList on u.UserID equals o.UserID into grp
                        from item in grp.DefaultIfEmpty()
                        select new { UserID = u.UserID, OrderTitle = item?.OrderTitle };       
    }    

之所以出现这个情况是因为不管是 查询关键词 还是 扩展方法,都并没有直接对 leftjoin 和 rightjoin 底层支持,只能通过变通的方式去实现,这就很尴尬了,比如说上面的这段代码,你很难在多天之后还能准确理解这里的 DefaultIfEmpty 是用来干嘛的,那现在问题的关键在于,有没有什么方法让底层支持或者有可信的第三方帮我封装好,还真巧,在 github 上 还真有一个 morelinq 项目:https://github.com/morelinq/MoreLINQ,看 md 是给 Enumerable 扩展了 60,70 个方法,如虎添翼呀,接下来一起来探索下吧。

二: MoreLinq 下的探索

1. 安装

安装非常简单,用 nuget 跑一下 Install-Package morelinq -Version 3.3.2 即可。

2. LeftJoin / RightJoin / FullJoin

在 morelinq 中就提供了 左外,右边,全连接,这就很了,提高开发效率,让我们更加专注业务,这里就使用 leftjoin 来改造刚才的代码,代码如下:


            var query2 = userList.LeftJoin(orderList, u => u.UserID, o => o.UserID,
                                                      u => new { UserID = u.UserID, OrderTitle = default(string) },
                                                      (u, o) => new { UserID = u.UserID, OrderTitle = o.OrderTitle });

怎么样,一句链式就搞定了,而且代码还非常好理解。

  • 第一个参数:join 集合
  • 第二个参数:source 集合的关联 id
  • 第三个参数:join 集合的关联 id
  • 第四个参数:source 存在,join不存在 应该返回的 model 架构
  • 第五个参数:source,join 都存在 应该返回的 model 架构

刚才也说到了有60,70个扩展方法,大家可以去趴一趴其他有趣的东西,这里我推荐几个给大家演示一下,毕竟还是非常实用的。

3. Shuffle

从字面意思上看就是洗牌,挺实用的一个方法,很多时候我希望在一个集合中随机抽取一条记录,比如说我有10套邮件模板,当用户下订单之后,我希望可以随机抽一套模板给用户,以防被 qq邮箱 放入垃圾箱中,原来我需要是用 new guid 来实现,如下代码:


            var list = new List<int>() { 1, 3, 5, 7, 9, 11, 12 };

            var query = list.OrderBy(m => Guid.NewGuid());

            Console.WriteLine(string.Join(",",query));

---- output -----

11,7,9,12,5,3,1

现在就简单多了,直接使用 Shuffle 搞定。


            var list = new List<int>() { 1, 3, 5, 7, 9, 11, 12 };

            var query = list.Shuffle();

            Console.WriteLine(string.Join(",", query));

----- output ------

5,1,9,12,7,3,11

4. Insert

有时候我需要在 IEnumerable 集合的指定位置插入一个集合,举个例子, A= { 1, 3, 5, 7, 9, 11, 12 }, B ={8}, 我希望将 8 插入到 7 和 9 之间,在现有的 Enumerable 下只有 Concat 和 Append 方法,无法做到指定插入,这个需求又可以被 moreqlinq 搞定啦。。。 代码如下:


            IEnumerable<int> list = new List<int>() { 1, 3, 5, 7, 9, 11, 12 };

            var query = list.Insert(new List<int>() { 8 }, 4);

            Console.WriteLine(string.Join(",", query));

可以看到往一个集合的指定位置插入一个集合就是这么轻松如意,太爽啦~~~

5. ForEach

不知道什么原因,至今在 IEnumerable 下都没有提供 ForEach 扩展方法,我这个升斗小民反正是体会不到这么设计的哲学在哪里,在原来你只能 ToList 立即执行,要么使用 foreach 进行延期遍历,现在就方便多了,简化的代码如下:


            IEnumerable<int> list = new List<int>() { 1, 3, 5, 7, 9, 11, 12 };

            list.ForEach(m =>
            {
                Console.Write(m+" ");
            });

6. ToDataTable

对在看的各位大佬估计都用不上,对我们公司几个 10 + 年的项目,那是万万不能少哈,如有能用得上的,可以简单看一下:

非常简单,不过我还是好奇一下它是怎么做到的,可以用 ilspy 去翻翻它的源码:


public static TTable ToDataTable<T, TTable>(this IEnumerable<T> source, TTable table, params Expression<Func<T, object>>[] expressions) where TTable : DataTable
{
	MemberInfo[] members = PrepareMemberInfos(expressions).ToArray();
	members = BuildOrBindSchema(table, members);
	Func<T, object[]> func = CreateShredder<T>(members);
	table.BeginLoadData();
	try
	{
		foreach (T item in source)
		{
			DataRow dataRow = table.NewRow();
			dataRow.ItemArray = func(item);
			table.Rows.Add(dataRow);
		}
		return table;
	}
	finally
	{
		table.EndLoadData();
	}
}

private static IEnumerable<MemberInfo> PrepareMemberInfos<T>(ICollection<Expression<Func<T, object>>> expressions)
{
	if (expressions == null || expressions.Count == 0)
	{
		return typeof(T).GetMembers(BindingFlags.Instance | BindingFlags.Public).Where(delegate(MemberInfo m)
		{
			if (m.MemberType != MemberTypes.Field)
			{
				PropertyInfo propertyInfo = m as PropertyInfo;
				if ((object)propertyInfo != null && propertyInfo.CanRead)
				{
					return propertyInfo.GetIndexParameters().Length == 0;
				}
				return false;
			}
			return true;
		});
	}
	try
	{
		return expressions.Select(GetAccessedMember);
	}
	catch (ArgumentException innerException)
	{
		throw new ArgumentException("One of the supplied expressions is not allowed.", "expressions", innerException);
	}
	MemberInfo GetAccessedMember(LambdaExpression lambda)
	{
		Expression expression = lambda.Body;
		if (expression.NodeType == ExpressionType.Convert || expression.NodeType == ExpressionType.ConvertChecked)
		{
			expression = ((UnaryExpression)expression).Operand;
		}
		MemberExpression memberExpression = expression as MemberExpression;
		if (memberExpression == null || memberExpression.Expression.NodeType != ExpressionType.Parameter)
		{
			throw new ArgumentException(#34;Illegal expression: {lambda}", "lambda");
		}
		return memberExpression.Member;
	}
}

从上面源码大概可以看到,将 List 转成 DataTable 支持两种方式,要么反射,要么 Expression 解析树,默认用的是反射,性能要稍微低一点。

三: 总结

当然这里还有很多的扩展方法,篇幅原因恕不能一一介绍,有兴趣的朋友可以pull下来试一试,总的来说这种工具类太棒了,让我们可以更好的专注于业务。

相关推荐

Java 如何从一个 List 中随机获得元素

概述从一个List中随机获得一个元素是有关List的一个基本操作,但是这个操作又没有非常明显的实现。本页面主要向你展示如何有效的从List中获得一个随机的元素和可以使用的一些方法。选择一个...

想月薪过万吗?计算机安卓开发之&quot;集合&quot;

集合的总结:/***Collection*List(存取有序,有索引,可以重复)*ArrayList*底层是数组实现的,线程不安全,查找和修改快,增和删比较慢*LinkedList*底层是...

China Narrows AI Talent Gap With U.S. as Research Enters Engineering Phase: Report

ImagegeneratedbyAITMTPOST--ChinaisclosinginontheU.S.intheAIindustry-academia-research...

大促系统优化之应用启动速度优化实践

作者:京东零售宋维飞一、前言本文记录了在大促前针对SpringBoot应用启动速度过慢而采取的优化方案,主要介绍了如何定位启动速度慢的阻塞点,以及如何解决这些问题。希望可以帮助大家了解如何定位该类问...

MyEMS开源能源管理系统核心代码解读004

本期解读:计量表能耗数据规范化算法:myems/myems-normalization/meter.py代码见底部这段代码是一个用于计算和存储能源计量数据(如电表读数)的小时值的Python脚本。它主...

Java接口与抽象类:核心区别、使用场景与最佳实践

Java接口与抽象类:核心区别、使用场景与最佳实践一、核心特性对比1.语法定义接口:interface关键字定义,支持extends多继承接口javapublicinterfaceDrawabl...

超好看 vue2.x 音频播放器组件Vue-APlayer

上篇文章给大家分享了视频播放器组件vue-aliplayer,这次给大家推荐一款音频插件VueAplayer。vue-aplayer一个好看又好用的轻量级vue.js音乐播放器组件。清爽漂亮的U...

Linq 下的扩展方法太少了,MoreLinq 来啦

一:背景1.讲故事前几天看同事在用linq给内存中的两个model做左连接,用过的朋友都知道,你一定少不了一个叫做DefaultIfEmpty函数,这玩意吧,本来很流畅的from......

MapReduce过程详解及其性能优化(详细)

从JVM的角度看Map和ReduceMap阶段包括:第一读数据:从HDFS读取数据1、问题:读取数据产生多少个Mapper??Mapper数据过大的话,会产生大量的小文件,由于Mapper是基于虚拟...

手把手教你使用scrapy框架来爬取北京新发地价格行情(实战篇)

来源:Python爬虫与数据挖掘作者:霖hero前言关于Scrapy理论的知识,可以参考我的上一篇文章,这里不再赘述,直接上干货。实战演练爬取分析首先我们进入北京新发地价格行情网页并打开开发者工具,如...

屏蔽疯狂蜘蛛,防止CPU占用100%(mumu模拟器和雷电模拟器哪个更占用cpu)

站点总是某个时间段莫名的cpu100%,资源占用也不高,这就有必要怀疑爬虫问题。1.使用"robots.txt"规范在网站根目录新建空白文件,命名为"robots.txt&#...

Web黑客近年神作Gospider:一款基于Go语言开发的Web爬虫,要收藏

小白看黑客技术文章,一定要点首小歌放松心情哈,我最爱盆栽!开始装逼!Gospider是一款运行速度非常快的Web爬虫程序,对于爱好白帽黑客的小白来说,可谓是佳作!Gospider采用厉害的Go语言开发...

用宝塔面板免费防火墙屏蔽织梦扫描网站

今天教大家在免费的基础上屏蔽织梦扫描,首先您要安装宝塔面板,然后再安装免费的防火墙插件,我用的是Nginx免费防火墙,然后打开这个插件。设置GET-URL过滤设置一条简单的宝塔面板的正则规则就可以屏蔽...

蜘蛛人再捞4千万美元 连续三周蝉联北美票房冠军

7月15日讯老马追踪票房数据的北美院线联盟今天表示,“蜘蛛人:离家日”(Spider-Man:FarFromHome)击退两部新片的挑战,连续第2周勇夺北美票房冠军,海捞4530万美元。法新...

夏天到了,需要提防扁虱,真是又小又恐怖的动物

夏天马上要到了,你知道吗,扁虱是这个夏天最危险的动物之一,很少有动物能比它还凶猛。Whenitcomestosummer'slittledangers,fewarenastiert...