C# LINQ 核心:Select 与SelectMany 妙用

660 阅读4分钟

前言

在 C# 的 LINQ 查询中,Select 和 SelectMany 是处理集合或可查询对象的常用方法,它们虽有相似之处,但也存在显著区别。

Select 方法用于将集合中的每个元素映射(投影)到一个新的形式,生成的序列长度与原集合相同。例如,若有一个整数集合,通过 Select 可将每个整数映射为对应的字符串形式,结果序列的元素数量与原集合一致。

而 SelectMany 方法则针对集合中的每个元素返回一个可枚举序列,随后将这些子序列"压平"(Flatten),合并为一个新的序列。

以一个包含多个子列表的列表为例,使用 SelectMany 可将所有子列表中的元素整合到一个单一的序列中,便于进一步处理和操作。

Select 示例

以下例子演示如何使用 Select 将字符串列表中的每个元素转换成大写形式:

public class Program
{
    public static void Main()
    {
        var fruits = new List<string> { "apple", "banana", "cherry" };

        // 使用 Select 把每个字符串都转换为大写
        var upperFruits = 
        fruits.Select(fruit => fruit.ToUpper());

        foreach (var item in upperFruits)
        {
            Console.WriteLine(item);
        }
    }
}

fruits.Select(...)将原始字符串映射为新的大写字符串。

结果数量和 fruits中的元素数量相同,只有值发生了改变。

SelectMany 示例

下面示例演示如何使用 SelectMany将一个"列表的列表"展平成单个列表。

假设我们有三位学生,每位学生拥有多门课程:

public class Program
{
    public static void Main()
    {
        var studentsCourses = new List<List<string>>
        {
            new List<string> { "Math", "Physics" },
            new List<string> { "History", "Literature", "Philosophy" },
            new List<string> { "ComputerScience", "Statistics" }
        };

        // 使用 SelectMany 将多层列表进行扁平化
        var flattenedCourses = studentsCourses.SelectMany(courses => courses);

        foreach (var course in flattenedCourses)
        {
            Console.WriteLine(course);
        }
    }
}

在这个示例中:

SelectMany对每个子列表执行投影,然后将所有子列表合并成一个单一序列。

与Select的区别在于,如果使用Select,最终结果会是一个列表(外层)里装着多个列表(内层);

而SelectMany则直接展开为单层列表。

SelectMany 实例

将某一个月的项目自动补齐日期

namespace AppSelectLinq
{
    public class ProjectData
    {
        public DateTime Date { get; set; }
        public string Project { get; set; }
        public int Qty { get; set; }
    }

    public class Program
    {
        public static void Main()
        {
            // 假设的日期范围  
            var monthRange = Tuple.Create(
            new DateTime(2024, 12, 1), new DateTime(2024, 12, 31));

            // 假设的项目数据列表  
            var lst = new List<ProjectData>
            {
                new ProjectData 
                { Date = new DateTime(2024, 12, 1), Project = "Project A", Qty = 10 },
                new ProjectData 
                { Date = new DateTime(2024, 12, 1), Project = "Project B", Qty = 5 },
                new ProjectData 
                { Date = new DateTime(2024, 12, 2), Project = "Project A", Qty = 8 },
                new ProjectData 
                { Date = new DateTime(2024, 12, 3), Project = "Project C", Qty = 12 },  
            };

            DateTime start2 = monthRange.Item1.Date;
            DateTime end2 = monthRange.Item2.Date;

            var allDates = Enumerable.Range(0, (end2 - start2).Days + 1)
                .Select
                (offset => start2.AddDays(offset))
                .SelectMany
                (date => lst.Select(x => x.Project).Distinct()
                    .Select(proj => new
                    {
                        Date = date,
                        Project = proj,
                        Qty = lst.FirstOrDefault
                        (x => x.Date == date && x.Project == proj)?.Qty ?? 0
                    }))
                .ToList();

            // 输出结果  
            foreach (var item in allDates)
            {
                Console.WriteLine($"Date: {item.Date.ToShortDateString()}, Project: {item.Project}, Qty: {item.Qty}");
            }
        }
    }
}

定义了一个日期范围monthRange。

创建了一个示例项目数据列表 lst,其中包含不同日期和项目的数量。

使用 Enumerable.Range生成从start2 到end2 的所有日期。

对于每个日期,使用 SelectMany结合 Distinct方法获取所有项目,并为每个项目创建一个匿名对象,包含日期、项目名称和数量(如果没有找到,则数量为 0)

总结

1、Select

一对一映射:对原序列中的每个元素进行转换,生成一个新元素,新元素与原元素一一对应。

长度不变:返回的结果序列长度与原序列相同,元素数量保持一致。

2、SelectMany

一对多映射:原序列中的每个元素可映射为一个子序列,子序列中可包含多个元素。

扁平化处理:将所有子序列合并、压平,最终返回一个单层序列,便于统一处理。

3、重要性

这两个方法在 LINQ 中极为常用,合理运用它们,能让你在处理复杂数据结构时,代码更加简洁、可读性更高,有效提升开发效率和代码质量。

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:技术老小子

出处:mp.weixin.qq.com/s/18885o92RrJigsjvTqDPhg

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!