You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用LINQ遍历列表并按条件将元素分流至两个输出?

问题

使用LINQ时,我们可以通过Where方法遍历列表并筛选符合条件的元素,但如果需要对列表中符合条件的元素做一种处理,不符合条件的做另一种处理,并且希望避免两次调用Where导致遍历数组两次,该怎么实现?

我设想的理想API使用方式如下:

var myList = new [] { 1,2,3,4,5 };
var result = myList
  .MyAwesomeSpecialWhereMethod(i => i % 2 == 0) // 返回包含成功列表和失败列表的对象
  .SelectSuccess(i => $"{i} is even") // 将成功列表转换为字符串列表
  .SelectFailure(i => $"{i} is odd") // 将失败列表转换为字符串列表
  .Collect((success, failure) => success.Union(failure)) // 通过Lambda聚合两个列表
Console.WriteLine(string.Join(", ", result));

运行结果为:

2 is even, 4 is even, 1 is odd, 3 is odd, 5 is odd // 未做排序

这种场景很常见,我可以用循环实现:

var evens = new List<int>();
var odds = new List<int>();
foreach (var i in myList){
  if (i%2 == 0){
    evens.Add(i);
  } else {
    odds.Add(i);
  }
}
// 后续再分别处理两个列表并聚合

但我想知道有没有更具LINQ风格的实现方式。

解答

方法1:使用GroupBy一次性分组处理

这是最简洁的原生LINQ实现,仅遍历一次集合,通过条件分组后分别处理:

var myList = new[] { 1, 2, 3, 4, 5 };

var grouped = myList.GroupBy(i => i % 2 == 0);
var evenStrings = grouped.First(g => g.Key).Select(i => $"{i} is even");
var oddStrings = grouped.First(g => !g.Key).Select(i => $"{i} is odd");

var result = evenStrings.Concat(oddStrings);
Console.WriteLine(string.Join(", ", result));

如果担心集合全是偶数/奇数导致找不到对应键,可改用FirstOrDefault并做空值处理,避免抛出异常。

方法2:自定义扩展方法实现流畅API

如果想要贴合你设想的链式调用风格,可以封装一组扩展方法,核心是先一次性拆分出符合/不符合条件的两个集合,再提供后续转换和聚合能力:

首先定义存储拆分结果的中间类:

public class SplitResult<T>
{
    public IEnumerable<T> Success { get; }
    public IEnumerable<T> Failure { get; }

    public SplitResult(IEnumerable<T> success, IEnumerable<T> failure)
    {
        Success = success;
        Failure = failure;
    }
}

再实现扩展方法:

public static class SplitExtensions
{
    // 核心拆分方法,仅遍历一次集合
    public static SplitResult<T> Split<T>(this IEnumerable<T> source, Func<T, bool> predicate)
    {
        var success = new List<T>();
        var failure = new List<T>();
        foreach (var item in source)
        {
            if (predicate(item))
                success.Add(item);
            else
                failure.Add(item);
        }
        return new SplitResult<T>(success, failure);
    }

    // 转换成功集合
    public static SplitResult<TResult> SelectSuccess<T, TResult>(this SplitResult<T> splitResult, Func<T, TResult> selector)
    {
        return new SplitResult<TResult>(splitResult.Success.Select(selector), splitResult.Failure.Cast<TResult>());
    }

    // 转换失败集合
    public static SplitResult<TResult> SelectFailure<T, TResult>(this SplitResult<T> splitResult, Func<T, TResult> selector)
    {
        return new SplitResult<TResult>(splitResult.Success.Cast<TResult>(), splitResult.Failure.Select(selector));
    }

    // 聚合两个集合
    public static TCollect Collect<T, TCollect>(this SplitResult<T> splitResult, Func<IEnumerable<T>, IEnumerable<T>, TCollect> collector)
    {
        return collector(splitResult.Success, splitResult.Failure);
    }
}

使用时就和你设想的逻辑完全一致:

var myList = new[] { 1, 2, 3, 4, 5 };
var result = myList
    .Split(i => i % 2 == 0)
    .SelectSuccess(i => $"{i} is even")
    .SelectFailure(i => $"{i} is odd")
    .Collect((success, failure) => success.Concat(failure));

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

方法3:使用Aggregate一次性聚合拆分

也可以用Aggregate实现单次遍历,直接在聚合过程中完成拆分和元素转换:

var myList = new[] { 1, 2, 3, 4, 5 };

var result = myList.Aggregate(
    new { Evens = new List<string>(), Odds = new List<string>() },
    (acc, item) =>
    {
        if (item % 2 == 0)
            acc.Evens.Add($"{item} is even");
        else
            acc.Odds.Add($"{item} is odd");
        return acc;
    },
    acc => acc.Evens.Concat(acc.Odds)
);

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

这种方式无需额外定义类,通过匿名类型在单次遍历中完成所有逻辑,代码紧凑直接。

内容的提问来源于stack exchange,提问作者ewok

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 14:05:12