自己动手重新实现LINQ to Objects: 8 - Concat

C#性能陷阱:GC、字符串、LINQ、异步与装箱的实战避坑指南 在.NET托管环境中,性能瓶颈往往不源于C#语言本身,而来自对运行时机制的误用。理解垃圾回收(GC)代际行为、字符串不可变性引发的内存风暴、LINQ to Objects延迟执行导致的时间复杂度幻觉、async/await在CPU密集场景的误用,以及结构体装箱带来的静默内存压力,是构建高吞吐服务的基础。这些概念共同构成.NET性能优化的核心原理链:从内存分配模式到线程调度逻辑,再到JIT编译与集合设计约束。典型应用场景包括电商订单处理、实时风控计算、IoT数据聚合等对延迟和吞吐敏感的系统。本文聚焦真实生产环 阅读详情

上文讲的Count和LongCount返回的是数值类型,本文我们讲的Concat返回的是一个序列。
 

Concat是什么?


Concat
只有一种签名形式,这让它使用起来很简单:


public
 static IEnumerable<TSource> Concat<TSource>( 

    this IEnumerable<TSource> first, 

    IEnumerable<TSource> second)


Concat的返回值依次包含了两个序列中的元素,也就是说把两个序列串联起来了。


我有时会觉得.NET没有提供Prepend/Append这样的方法是个遗憾,这两个方法应该可以和Concat做类似的事情,只不过它们把一个序列和一个单个的元素串联起来。如果要做一个填充着国家名和一个“None”值的下拉列表的话,这两个方法是很有用的。当然,向Concat中传入一个单元素的数组也可以达到同样的目的,但是我个人认为用特定的方法名做特定的事会让代码的可读性更高。
MoreLINQ中的Concat方法可以做这件事,不过Edulinq的目的只是要实现LINQ to Objects中已有的方法。

和往常一样,我们列出Concat的行为:


l 
参数校验需要立即执行:两个参数都不允许为null

l 返回值是延迟执行的:当Concat被调用时,两个参数不会立即被迭代

l 输入序列只有在需要的时候才会被迭代:如果你停止迭代输出序列时第一个输入序列还没有被耗尽的话,那么第二个序列根本就不会被迭代


这几点描述基本就涵盖了Concat的所有行为。


我们需要测试什么呢?


Concat的串联行为很容易被测试,只需要一个用例就够了。我们或许也可以测试输入空序列会如何,但是那种测试基本没有不通过的可能。

参数校验的测试方式和往常一样:调用方法时传入非法的参数,然后不去迭代方法的返回值。

最后,还有一个单元测试用来测试两个输入序列被迭代的时机。这个测试中用到了我们在测试Where时用过的ThrowingEnumerable:


[Test] 

public void FirstSequenceIsntAccessedBeforeFirstUse() 

{ 

    IEnumerable<int> first = new ThrowingEnumerable(); 

    IEnumerable<int> second = new int[] { 5 }; 

    // No exception yet... 

    var query = first.Concat(second); 

    // Still no exception... 

    using (var iterator = query.GetEnumerator()) 

    { 

        // Now it will go bang 

        Assert.Throws<InvalidOperationException>(() => iterator.MoveNext()); 

    } 

} 

[Test] 

public void SecondSequenceIsntAccessedBeforeFirstUse() 

{ 

    IEnumerable<int> first = new int[] { 5 }; 

    IEnumerable<int> second = new ThrowingEnumerable(); 

    // No exception yet... 

    var query = first.Concat(second); 

    // Still no exception... 

    using (var iterator = query.GetEnumerator()) 

    { 

        // First element is fine... 

        Assert.IsTrue(iterator.MoveNext()); 

        Assert.AreEqual(5, iterator.Current); 

        // Now it will go bang, as we move into the second sequence 

        Assert.Throws<InvalidOperationException>(() => iterator.MoveNext()); 

    } 

}


我们写测试来检查迭代器是否被Dispose掉了。但是我们可以预测到输入序列的迭代器应该会被合理的Dispose掉。实际上,第一个序列的迭代器会在第二个序列开始被迭代之前就被Dispose掉。


开始动手实现吧!


Concat的实现虽然比较简单,但是我写完之后还是觉得F#更值得拥有...实现分为参数校验和迭代器代码块两部分,每一部分都不复杂:


public
 static IEnumerable<TSource> Concat<TSource>( 

    this IEnumerable<TSource> first, 

    IEnumerable<TSource> second) 

{ 

    if (first == null) 

    { 

        throw new ArgumentNullException("first"); 

    } 

    if (second == null) 

    { 

        throw new ArgumentNullException("second"); 

    } 

    return ConcatImpl(first, second); 

} 

private static IEnumerable<TSource> ConcatImpl<TSource>( 

    IEnumerable<TSource> first, 

    IEnumerable<TSource> second) 

{ 

    foreach (TSource item in first) 

    { 

        yield return item; 

    } 

    foreach (TSource item in second) 

    { 

        yield return item; 

    } 

}


如果不能利用迭代器代码块的话,这个实现会变得很麻烦。虽然不会特别难,但是我们需要记住当前正在迭代的是哪个序列。

如果是在用F#的话,我们可以使用yield!表达式来把它实现的更简单,yield!表达式作用于一整个序列而不是单个的元素。必需得承认在这种场景下使用yield!并不会带来什么性能上的提升(如果是在递归的场景下就很可能会有性能提升),但是能够用一个语句来yield返回整个序列确实是一种更优雅的风格。(Spec#中也有一个类似的结构叫做嵌套迭代器,用yield foreach来表示。)我对F#和Spec#了解的都不够深入,所以就不做更深入的比较了。不过我们在以后实现Edulinq的过程中还会遇到好几次“yield返回一个序列中的每个元素”的模式。请记住,我们不能把yield返回的代码抽取到一个单独的方法中去,因为“yield”表达式需要C#编译器的特殊处理。


结论


虽然我用的实现方式还是蛮简单的,但是我还是吐槽一下:) 如果C#里面也有嵌套迭代器那多好啊,虽然说没有它也没有令我太苦恼。

Concat是一个很有用的操作符,不过它也不过是SelectMany的一个特例罢了。Concat只能把两个序列连接成一个序列,而SelectMany则可以把很多个序列连接成一个序列,而且SelectMany在有时还更有普遍性。下次我们会实现SelectMany,而且会展示一些基于SelectMany来实现其他操作符的例子。(等实现Aggregate的时候,我们会再次见到操作符只返回一个值的例子。)


附录:避免不必要的保持引用


有一条留言建议说要在遍历完第一个序列后把它设为null。这样,在遍历完第一个序列后,它就可以被垃圾回收了。如果采取这个建议,那么实现起来会是这样的:

private static IEnumerable<TSource> ConcatImpl<TSource>( 

    IEnumerable<TSource> first, 

    IEnumerable<TSource> second) 

{ 

    foreach (TSource item in first) 

    { 

        yield return item; 

    } 

    // Avoid hanging onto a reference we don't really need 

    first = null; 

    foreach (TSource item in second) 

    { 

        yield return item; 

    } 

}

在普通情况下,把一个不再使用的局部变量设为null这种做法是没用的。因为当CLR在执行优化过的代码,并且没有挂上调试器时,垃圾收集器只关心在方法内部可能还会被访问的变量。

但是在我们这个特例中,这么做还是有用的。因为第一个参数并不是一个简单的局部变量,在C#编译器生成的隐藏类型中,它是一个实例字段,而CLR无法判断实例字段是否会被再次使用。

或许我们可以在调用GetEnumerator之前清空掉我们对“first”这个参数的唯一引用。我们可以写一个这样的方法:

public static T ReturnAndSetToNull<T>(ref T value) where T : class 

{ 

    T tmp = value; 

    value = null; 

    return tmp; 

}

然后这样调用它:

foreach (TSource item in ReturnAndSetToNull(ref first))

我认为这样做绝对是有点过了,因为迭代器有可能还会持有对集合的引用。不过在遍历之后把“first”这个参数设为null在我看来是说得通的。

需要提醒你一下,我觉得.NET的LINQ to Objects的实现里面是不会这样做的。(以后我可能会用一个有finalizer的集合类来测试一下。)

LINQ核心操作符深度解析:集合运算、Zip对齐、转换固化与生成器 LINQ是C#中实现函数式查询的核心能力,其本质是基于IEnumerable<T>的延迟执行表达式管道。理解Concat/Union等集合运算符的关系代数语义、Zip的位置同步机制、ToArray/ToDictionary等转换方法的立即求值特性,以及Empty/Range等生成器的惰性构造原理,是掌握.NET数据处理范式的基石。这些操作符直面类型协变边界、泛型运行时行为、内存分配策略等底层工程问题,在EF Core查询优化、高并发数据对齐、缓存策略设计等真实场景中决定系统稳定性与性能表现。本文聚焦LINQ 阅读详情

相关推荐

Linq与Lambda底层原理:从委托到表达式树的双轨解析

Linq和Lambda是C#中支撑数据查询与函数式编程的核心机制,其本质并非语法糖,而是基于委托(Func/Action)与表达式树(Expression Tree)的双重实现范式。理解二者差异,是掌握IQueryable与IEnumerable双轨执行路径的关键——前者将Lambda编译为可分析、可翻译的表达式树,供EF Core生成SQL;后者则直接编译为可执行委托,在内存中运算。这种设计赋予了Linq‘声明式查询’的技术价值:开发者专注描述‘要什么’,而非‘怎么做’,从而实现数据库端计算下推、延迟执行

diedangxiang4092的博客 466

探索高效数据处理:LINQ to Objects (Java) 开源项目推荐

探索高效数据处理:LINQ to Objects (Java) 开源项目推荐 在现代软件开发中,高效处理数据集合是每个开发者不可或缺的技能。今天,我们将深入介绍一个强大的开源项目——LINQ to Objects (Java),它为Java开发者提供了一种全新的方式来处理集合数据,极大地简化了代码复杂性并提升了开发效率。 项目介绍 LINQ to Objects (Java) 是一个实现了LINQ...

gitblog_01165的博客 524

LINQ查询语法与方法语法深度解析:从SQL式声明到函数式编程实践

在.NET开发中,数据查询是核心操作之一。LINQ(Language Integrated Query)作为C#的语言集成查询技术,提供了两种等效但风格迥异的查询方式:查询语法(Query Syntax)与方法语法(Method Syntax)。查询语法采用类似SQL的声明式结构,使用from、where、select等关键字,直观描述数据筛选、连接与分组意图,尤其适合处理复杂的多表关联和分组聚合场景,其代码形态与SQL语句高度对应,降低了数据库开发者的学习门槛。方法语法则基于扩展方法和Lambda表达式,

weixin_33971977的博客 431

Linq入门详解(Linq to Objects)

Linq之旅:Linq入门详解(Linq to Objects)   示例代码下载:Linq之旅:Linq入门详解(Linq to Objects)          本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集成查询)。通过LINQ,我们可以使用相同API操作不同的数据源。接下来就让我们看看LINQ是什么

xuyongbeijing2008的专栏 1455

Unity性能优化实战:用Profiler精准定位CPU与GPU瓶颈

在游戏开发与实时应用领域,性能优化是保障流畅用户体验的核心工程实践。其基本原理在于通过系统性的数据采集与分析,识别并消除应用运行时的资源消耗热点。这项技术的核心价值在于将优化工作从依赖经验的“猜测”转变为数据驱动的“精准诊断”,从而显著提升开发效率与应用质量。典型的应用场景包括解决游戏卡顿、移动设备发热、渲染效率低下等问题。本文聚焦于Unity引擎中的性能分析利器——Profiler,深入讲解如何利用其CPU Usage、GPU Usage、Memory等核心模块,结合具体案例,系统化地诊断并区分CPU密集

weixin_30243533的博客 485

linq:LINQ to Java的对象

LINQ到对象(Java) 术语“对象的LINQ”是指将LINQ查询与任何IEnumerable<T> 。 您可以使用LINQ查询任何可枚举的集合,例如Primitive Array , Object Array , List , Collection或Iterable等。 该集合可以是用户定义的,也可以由JDK API返回。 从基本意义上讲,LINQ to Objects代表了一种新的集合方法。 用旧的方法,您必须编写复杂的foreach循环,以指定如何从集合中检索数据。 在LINQ方法中,您将编写描述性代码来描述要检索的内容。 此外,与传统的foreach循环相比,LINQ查询具有两个主要优点: 它们更简洁易读,尤其是在过滤多个条件时。 它们以最少的应用程序代码提供了强大的过滤,排序和分组功能。 与流API相比,LINQ查询还具有一些优势: 支持foreach循环,因此您可

Linq to Objects for Java 发布 1.0.1 版本

现在 java 支持 linq 啦.比原生 stream api 更好用,功能更强大.现已发布 version 1.0.1 地址: https://github.com/timandy/linq. API of IEnumerable where select selectMany take takeWhile skip skipWhile join groupJo...

weixin_33800593的博客 226

群晖NAS实战:从零部署Container Manager与Docker代理配置全攻略

本文详细介绍了在群晖NAS上从零部署Container Manager与配置Docker代理的完整流程。针对国内用户常见的镜像拉取问题,提供了图形化界面与SSH命令行两种解决方案,并分享了MySQL容器部署的实战案例。文章特别强调了网络配置技巧和日常维护方法,帮助用户高效管理NAS上的容器应用。

weixin_29254673的博客 1302

linq for java_java 的 linq,不要再写令人讨厌的 for 了!

packagecom.ly.linq;importjava.util.ArrayList;importjava.util.HashMap;importjava.util.Iterator;public classEnm {public static ArrayList notNull(Iterablesource) {return where(source, new Pre() {@Overri...

weixin_32028323的博客 369

Linux运维人,该醒醒了,2019年要变天了

外界都说运维只是处于被动低效率手工救火的状态,企业对其重视程度不高,提起运维很多人能联想到的字眼就有“苦逼”、“辛苦”、“加班”、“背锅”。在大数据、人工智能、容器技术的强力加持下,运维这个传统的技术工种已经从“机械劳动”这样的刻板印象中蜕变出来,成为了任何一家技术公司所必须依赖和大力投入的核心技术能力。 现在,运维人员的发展前景也开始让很多程序员眼羡,作为Linux运维人员的你:这一年,你的L...

qq_43083284的博客 1万+

【Benchmark 性能测试全指南:从核心原理到 CINEBENCH 实战解析】

本文深度拆解基准测试(Benchmark)的底层逻辑,详解数据集、工作负载、度量指标三大核心要素,分步演示测试流程与统计分析要点。结合 CINEBENCH R23 实战案例,揭秘 CPU 性能评估方法论,助你掌握系统性能量化与优化的核心技能。

rong_ha_ha的博客 6327

自己动手重新实现LINQ to Objects: 9 - SelectMany

本文翻译自JonSkeet的系列博文“Edulinq”。 本篇原文地址: http://msmvps.com/blogs/jon_skeet/archive/2010/12/27/reimplementing-linq-to-objects-part-9-selectmany.aspx 我们接下来要实现的这个操作符是LINQ中最重要的操作符。大多数(或者是全部?)其他的返回...

weixin_30245867的博客 90

Arnold Denoise流程

参考:https://support.solidangle.com/display/ARP/Removing+Noise+Workflow Arnold生来不是一个以单帧渲染速度见长的渲染器,但这并不能作为一个简单场景渲染时间长达几个小时的借口。 个人认为高品质生产级渲染结果中,Unbiased renderer在渲染时最耗计算时间的其实就是降噪,以至于降噪专门成了一门灯光师必备的技术

机器猫的CG空间 5084

LINQ to Objects(用于集合、文件、字符串对象的LINQ)

LINQ to  Object是LINQ的基础,而LINQ to SQL、LINQ to  XML是中间LINQ提供程序,它们主要把数据源转换成LINQ to Objects兼容的类型,以便与LINQ to Objects进行操作。LINQ to Objcets就是直接 对IEnumerable或IEnumberable集合使用LINQ查询。LINQ标准查询运算符概述LINQ标准查询运算符是依靠一组扩展方法来实现的。而这些扩展方法分别是System.Linq.Enumerable和System.Linq.Q

游戏全栈,技术合作 3452

Android 13 Settings蓝牙列表卡顿问题排查及优化过程

此时我们的卡顿优化基本完成了,我们应该要考虑内存是否会泄漏,如果大家不知道哪些需要回收可以用leakcanary和android profiler进行内存检测,看代码有没有内存泄漏。

龚礼鹏的博客 2546

低功耗设计-ir drop的signoff corner怎么选择?

2.tt的(tt85 是比较接近芯片真实工作情况的);我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧?有要求就用要求的就好,没要求前两种看公司需求。3.signoff文档要求的;有几个方向,看公司需求吧。

m0_61544122的博客 1591

LINQ to Objects---延时执行的Enumerable类方法

  LINQ标准查询运算法是依靠一组扩展方法来实现的。而这些扩展方法分别在System.Linq.Enumerable和System.Linq.Queryable这连个静态类中定义。   Enumerable的扩展方法采用线性流程,每个运算法会被线性执行。这种执行方法如果操作类似关系型数据库数据源,效率会非常低下,所以Queryable重新定义这些扩展方法,把LINQ表达式拆解为表达式树,提供程...

weixin_30790841的博客 113

[QMT量化交易小白入门]-三十七、QMT中如何使用类似于通达信中的VBA函数(量化python代码解析)

这段代码主要围绕着在QMT环境中实现类似通达信VBA函数的功能展开,核心功能是基于RSI指标来判断股票的买卖时机,并根据持仓情况进行相应的交易操作。class G():passg = G()这里定义了一个空类G,并创建了该类的实例g。这个实例g将作为全局变量来存储一些在整个程序运行过程中需要共享的数据,例如除复权类型、股票代码、目标数量等。1、使用VBA开发策略时,参数固化的问题会带来一些限制,比如无法修改MA函数的周期参数,只能使用固定周期。

木头大左的博客 1692

C#五大隐性性能杀手:字符串拼接、LINQ、事件订阅、异步等待与集合初始化

在.NET开发中,性能瓶颈往往不源于框架缺陷,而来自对基础类型机制与运行时行为的误用。字符串不可变性导致高频拼接引发GC风暴;LINQ延迟执行与客户端评估造成O(n log n)级无谓计算;事件强引用引发内存泄漏;ConfigureAwait(false)缺失导致线程上下文绑架;集合未预设容量触发多次扩容复制。这些现象共同构成C#生产环境‘慢而不报错’的核心成因。本文聚焦字符串拼接优化、LINQ链式调用陷阱、事件生命周期管理、异步上下文控制及集合初始化策略五大高频场景,结合.NET 6+实践给出可落地的诊断

480

基于深度学习的工业零件缺陷检测系统

工业零件缺陷检测系统摘要 本文介绍了一个基于深度学习的工业零件缺陷检测系统解决方案。该系统采用Faster R-CNN目标检测算法,结合PyTorch框架和OpenCV图像处理库,实现了对工业零件表面缺陷(如裂纹、划痕、凹坑等)的自动检测与定位。系统包含完整的开发流程:从数据采集与标注、数据增强预处理、模型构建与训练,到最终的评估与部署。相比传统机器视觉方法,该深度学习方案具有更强的特征表示能力和环境适应性,能够有效提升工业质检的准确率和效率。文章详细阐述了系统架构设计、实现细节和优化策略,并提供了完整的代

huanghm88的专栏 286

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Unity游戏开发GC优化实战:5个技巧解决C#内存管理痛点

在实时交互应用开发中,内存管理是保障性能流畅的核心技术。其原理在于通过高效分配与回收内存资源,避免因垃圾回收(GC)导致的程序暂停。对于追求高帧率的Unity游戏开发而言,GC引发的卡顿会直接破坏用户体验。因此,掌握托管堆对象生命周期管控、减少不必要的堆分配,具有极高的技术价值。这尤其适用于移动端游戏、VR/AR应用等对性能敏感的场景。本文聚焦Unity开发中的GC优化,深入剖析了对象池化、值类型应用等实战技巧,旨在帮助开发者从根源上治理性能问题。通过结合增量式GC配置与Profiler深度监控,形成系统性

weixin_30267785的博客 325

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Unity子资源编辑器开发指南:SubAssetEditor核心原理与实现

在Unity引擎的资源管理体系中,主资源文件(如Prefab、Material)内部常包含多个子资源(Sub-Asset),例如材质球内嵌的纹理。这些子资源通常无法在Project视图中直接访问和独立操作,给美术资源管理和技术工作流带来不便。其原理在于Unity的序列化系统将相关引用数据打包存储于同一资产文件内。为解决此问题,基于编辑器扩展(Editor Extension)技术,通过AssetDatabase API加载和识别子资源,结合EditorWindow创建可视化操作界面,能够显著提升资源操作效率

weixin_33724570的博客 448

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

unity常见面试题

接口是一种完全抽象的类型,它只定义了成员的签名,但没有提供成员的实现。接口定义了对象应该具有的方法、属性和事件。抽象类是一种包含了抽象成员(方法、属性、索引器、事件)的类,它可以包含抽象成员和非抽象成员的实现。抽象类可以包含方法的实现,也可以包含字段和构造函数等。

yasin的博客 1万+
上一篇: 自己动手重新实现LINQ to Objects: 7 - Count和LongCount
下一篇: 自己动手重新实现LINQ to Objects: 9 - SelectMany
崔鹏飞
博客等级 码龄19年 441粉丝 128原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值