当前位置: 首页 > news >正文

C# Enumerable类 之 数据连接

总目录


前言

在 C# 中,System.Linq.Enumerable 类是 LINQ(Language Integrated Query)的核心组成部分,它提供了一系列静态方法,用于操作实现了 IEnumerable 接口的集合。通过这些方法,我们可以轻松地对集合进行查询、转换、排序和聚合等操作。

本文属于 C# Enumerable类 使用详解 中的一个章节,着重介绍 C# Enumerable 类中数据连接这部分的内容。


一、概览

方法描述
Join数据内连接
GroupJoin 数据左外连接

二、Join :数据内连接

1. 什么是 Join

Join 是 LINQ 提供的一个扩展方法,用于执行类似于 SQL 中的内连接(INNER JOIN)操作。它允许你根据指定的键选择器函数将两个序列中的元素进行匹配,并返回一个包含匹配结果的新序列。每个匹配的结果是一个匿名类型或自定义类型的对象,通常包含来自两个序列的相关数据。

2. Join方法 基本信息

1) Join

Join 方法的基本签名如下:

public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(
    this IEnumerable<TOuter> outer,
    IEnumerable<TInner> inner,
    Func<TOuter, TKey> outerKeySelector,
    Func<TInner, TKey> innerKeySelector,
    Func<TOuter, TInner, TResult> resultSelector
)

public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(
    this IEnumerable<TOuter> outer,
    IEnumerable<TInner> inner,
    Func<TOuter, TKey> outerKeySelector,
    Func<TInner, TKey> innerKeySelector,
    Func<TOuter, TInner, TResult> resultSelector,
    IEqualityComparer<TKey> comparer
)
  • 参数
    • outer:第一个要连接的序列。
    • inner:第二个要连接的序列。
    • outerKeySelector:一个函数,用于从 outer 序列的每个元素中提取键值。
    • innerKeySelector:一个函数,用于从 inner 序列的每个元素中提取键值。
    • resultSelector:一个函数,用于定义如何将匹配的 outerinner 元素组合成结果。
    • comparer(可选):一个 IEqualityComparer<TKey> 实现,用于比较键值是否相等。
  • 返回值Join 方法返回一个 IEnumerable<TResult> 对象,其中 TResult 是你通过结果选择器函数定义的输出类型。

2)工作原理

Join 方法根据指定的键选择器函数将两个序列中的元素进行匹配,并返回一个包含匹配结果的新序列。每个匹配的结果是一个由结果选择器函数定义的对象,通常包含来自两个序列的相关数据。类似于SQL中的内连接(INNER JOIN),只有当两个序列中存在匹配键时,才会生成输出结果

3)使用场景

  • 适用于数据关联、数据预处理、多个集合的连接等场景。

3. 使用示例

示例 1:基本连接操作

假设我们有两个列表,一个包含学生信息,另一个包含课程信息,我们希望根据学生的ID和课程的StudentID进行连接。

class Student
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override string ToString()
    {
        return $"{Id}: {Name}";
    }
}

class Course
{
    public int StudentId { get; set; }
    public string CourseName { get; set; }

    public override string ToString()
    {
        return $"{StudentId}: {CourseName}";
    }
}

class Program
{
    static void Main()
    {
        List<Student> students = new List<Student>
        {
            new Student { Id = 1, Name = "Alice" },
            new Student { Id = 2, Name = "Bob" },
            new Student { Id = 3, Name = "Charlie" }
        };

        List<Course> courses = new List<Course>
        {
            new Course { StudentId = 1, CourseName = "Math" },
            new Course { StudentId = 2, CourseName = "Science" },
            new Course { StudentId = 1, CourseName = "History" }
        };

        // 使用 Join 进行连接操作
        var joinedData = students.Join(
            courses,
            student => student.Id,           // 外部序列的键选择器
            course => course.StudentId,      // 内部序列的键选择器
            (student, course) => new         // 结果选择器
            {
                StudentName = student.Name,
                CourseName = course.CourseName
            });

        // 输出结果
        Console.WriteLine("Joined data:");
        foreach (var item in joinedData)
        {
            Console.WriteLine($"Student: {item.StudentName}, Course: {item.CourseName}");
        }
    }
}

输出结果:

Joined data:
Student: Alice, Course: Math
Student: Alice, Course: History
Student: Bob, Course: Science

在这个例子中,我们使用 Join 方法根据学生的 Id 和课程的 StudentId 进行连接,并打印了结果。

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

class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override string ToString()
    {
        return $"{Id}: {Name}";
    }
}

class Order
{
    public int CustomerId { get; set; }
    public string Product { get; set; }

    public override string ToString()
    {
        return $"{CustomerId}: {Product}";
    }
}

class Program
{
    static void Main()
    {
        List<Customer> customers = new List<Customer>
        {
            new Customer { Id = 1, Name = "Alice" },
            new Customer { Id = 2, Name = "Bob" },
            new Customer { Id = 3, Name = "Charlie" }
        };

        List<Order> orders = new List<Order>
        {
            new Order { CustomerId = 1, Product = "Apple" },
            new Order { CustomerId = 2, Product = "Banana" },
            new Order { CustomerId = 1, Product = "Cherry" }
        };

        // 使用 Join 关联客户和订单
        var associatedData = customers.Join(
            orders,
            customer => customer.Id,
            order => order.CustomerId,
            (customer, order) => new
            {
                CustomerName = customer.Name,
                Product = order.Product
            });

        // 输出结果
        Console.WriteLine("Associated data:");
        foreach (var item in associatedData)
        {
            Console.WriteLine($"Customer: {item.CustomerName}, Product: {item.Product}");
        }
    }
}

输出结果:

Associated data:
Customer: Alice, Product: Apple
Customer: Alice, Product: Cherry
Customer: Bob, Product: Banana

示例 2:自定义比较器

有时你需要自定义比较逻辑来判断两个键是否相等。这时可以传递一个实现了 IEqualityComparer<TKey> 接口的对象给 Join 方法。

class Student
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override string ToString()
    {
        return $"{Id}: {Name}";
    }
}

class Course
{
    public int StudentId { get; set; }
    public string CourseName { get; set; }

    public override string ToString()
    {
        return $"{StudentId}: {CourseName}";
    }
}

class CustomComparer : IEqualityComparer<int>
{
    public bool Equals(int x, int y)
    {
        // 自定义比较逻辑:允许Id相差不超过1 视为相同
        return Math.Abs(x - y) <= 1;
    }

    public int GetHashCode(int obj)
    {
        return 0;
    }
}

class Program
{
    static void Main()
    {
        List<Student> students = new List<Student>
        {
            new Student { Id = 1, Name = "Alice" },
            new Student { Id = 4, Name = "Bob" },
            new Student { Id = 7, Name = "Charlie" }
        };

        List<Course> courses = new List<Course>
        {
            new Course { StudentId = 2, CourseName = "Math" },
            new Course { StudentId = 4, CourseName = "Science" },
            new Course { StudentId = 9, CourseName = "History" }  // 注意这里的StudentId为4
        };

        // 使用 Join 进行连接操作并使用自定义比较器
        var joinedData = students.Join(
            courses,
            student => student.Id,
            course => course.StudentId,
            (student, course) => new
            {
                StudentName = student.Name,
                CourseName = course.CourseName
            },
            new CustomComparer());

        // 输出结果
        Console.WriteLine("Joined data with custom comparer:");
        foreach (var item in joinedData)
        {
            Console.WriteLine($"Student: {item.StudentName}, Course: {item.CourseName}");
        }
    }
}

输出结果:

Joined data with custom comparer:
Student: Alice, Course: Math
Student: Bob, Course: Science

在这个例子中,我们展示了如何使用自定义比较器来进行连接操作,并确保比较是基于自定义逻辑进行的。

示例 3:惰性求值示例

Join 方法采用惰性求值(Lazy Evaluation),这意味着它不会立即执行连接操作,而是等到实际遍历时才会计算结果。这使得 Join 可以处理无限序列或延迟执行复杂的查询。

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

class Program
{
    static void Main()
    {
        IEnumerable<int> infiniteNumbers = Enumerable.Range(0, int.MaxValue).Select(i => i * 2);
        List<int> finiteNumbers = new List<int> { 4, 6, 8 };

        // 使用 Join 进行连接操作
        var joinedSequence = infiniteNumbers.Join(
            finiteNumbers,
            number => number,
            target => target,
            (number, target) => new { Number = number, Target = target });

        // 限制输出数量
        Console.WriteLine("Joined elements (First 3 elements):");
        foreach (var item in joinedSequence.Take(3))
        {
            Console.WriteLine($"Number: {item.Number}, Target: {item.Target}");
        }
    }
}

输出结果:

Joined elements (First 3 elements):
Number: 4, Target: 4
Number: 6, Target: 6
Number: 8, Target: 8

在这个例子中,我们展示了如何使用 Join 方法计算一个无限序列和一个有限序列之间的连接,并通过 Take 方法限制输出的数量,从而避免无限循环。

示例 4: 多个集合的连接

当需要计算多个集合的连接时,可以多次使用 Join 方法,或者使用更复杂的数据结构和查询逻辑来简化代码。

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

class Program
{
    static void Main()
    {
        List<int> list1 = new List<int> { 1, 2, 3, 4, 5 };
        List<int> list2 = new List<int> { 3, 4, 6 };
        List<int> list3 = new List<int> { 4, 7, 8 };

        // 使用 Join 计算多个集合的连接
        var joinedList = list1.Join(
            list2,
            number => number,
            target => target,
            (number, target) => new { Number = number, Target = target })
            .Join(
                list3,
                firstJoin => firstJoin.Target,
                thirdList => thirdList,
                (firstJoin, thirdList) => new { FirstJoin = firstJoin, ThirdList = thirdList });

        // 输出结果
        Console.WriteLine("Joined data among list1, list2, and list3:");
        foreach (var item in joinedList)
        {
            Console.WriteLine($"Number: {item.FirstJoin.Number}, Target: {item.FirstJoin.Target}, ThirdList: {item.ThirdList}");
        }
    }
}

输出结果:

Joined data among list1, list2, and list3:
Number: 4, Target: 4, ThirdList: 4

二、GroupJoin :数据类型转换

1. 什么是 GroupJoin

GroupJoin 是 LINQ 提供的一个扩展方法,用于执行类似于 SQL 中的左外连接(LEFT OUTER JOIN)操作。它允许你根据指定的键选择器函数将两个序列中的元素进行匹配,并返回一个包含每个外部元素及其相关内部元素集合的结果。每个匹配的结果是一个匿名类型或自定义类型的对象,通常包含来自两个序列的相关数据。

2. GroupJoin 方法 基本信息

1) GroupJoin

GroupJoin 方法的基本签名如下:

public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(
    this IEnumerable<TOuter> outer,
    IEnumerable<TInner> inner,
    Func<TOuter, TKey> outerKeySelector,
    Func<TInner, TKey> innerKeySelector,
    Func<TOuter, IEnumerable<TInner>, TResult> resultSelector
)

public static IEnumerable<TResult> GroupJoin<TOuter, TInner, TKey, TResult>(
    this IEnumerable<TOuter> outer,
    IEnumerable<TInner> inner,
    Func<TOuter, TKey> outerKeySelector,
    Func<TInner, TKey> innerKeySelector,
    Func<TOuter, IEnumerable<TInner>, TResult> resultSelector,
    IEqualityComparer<TKey> comparer
)
  • 参数
    • outer:第一个要连接的序列。
    • inner:第二个要连接的序列。
    • outerKeySelector:一个函数,用于从 outer 序列的每个元素中提取键值。
    • innerKeySelector:一个函数,用于从 inner 序列的每个元素中提取键值。
    • resultSelector:一个函数,用于定义如何将外部元素和其相关的内部元素集合组合成结果。
    • comparer(可选):一个 IEqualityComparer<TKey> 实现,用于比较键值是否相等。
  • 返回值GroupJoin 方法返回一个 IEnumerable<TOuter> 对象,其中每个 TOuter 元素都包含一个与其相关的内部元素集合(IEnumerable<TInner>)。

2)工作原理

GroupJoin 方法根据指定的键选择器函数将两个序列中的元素进行匹配,并返回一个包含匹配结果的新序列。与 Join 不同的是,GroupJoin 对于第一个序列中的每个元素,返回第二个序列中所有具有相同键的元素组成的组,类似于SQL中的左外连接(LEFT OUTER JOIN),但返回的结果是分组形式,允许访问每个组中的所有相关项。

两种方法的核心功能和主要区别:

  • Join 主要用于找到两个序列之间的匹配对,并生成一个新的结果序列。
  • GroupJoin 则更进一步,它不仅找到匹配对,还保留了每个匹配对的分组信息,适用于需要处理一对多关系的场景。

3. 使用示例

示例 1:基本分组连接操作

假设我们有两个列表,一个包含学生信息,另一个包含课程信息,我们希望根据学生的ID和课程的StudentID进行分组连接。

class Student
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override string ToString()
    {
        return $"{Id}: {Name}";
    }
}

class Course
{
    public int StudentId { get; set; }
    public string CourseName { get; set; }

    public override string ToString()
    {
        return $"{StudentId}: {CourseName}";
    }
}

class Program
{
    static void Main()
    {
        List<Student> students = new List<Student>
        {
            new Student { Id = 1, Name = "Alice" },
            new Student { Id = 2, Name = "Bob" },
            new Student { Id = 3, Name = "Charlie" }
        };

        List<Course> courses = new List<Course>
        {
            new Course { StudentId = 1, CourseName = "Math" },
            new Course { StudentId = 2, CourseName = "Science" },
            new Course { StudentId = 1, CourseName = "History" }
        };

        // 使用 GroupJoin 进行分组连接操作
        var groupedData = students.GroupJoin(
            courses,
            student => student.Id,           // 外部序列的键选择器
            course => course.StudentId,      // 内部序列的键选择器
            (student, studentCourses) => new // 结果选择器
            {
                StudentName = student.Name,
                Courses = studentCourses.Select(c => c.CourseName).ToList()
            });

        // 输出结果
        Console.WriteLine("Grouped data:");
        foreach (var item in groupedData)
        {
            Console.Write($"Student: {item.StudentName}, Courses: ");
            if (item.Courses.Any())
            {
                Console.WriteLine(string.Join(", ", item.Courses));
            }
            else
            {
                Console.WriteLine("None");
            }
        }
    }
}

输出结果:

Grouped data:
Student: Alice, Courses: Math, History
Student: Bob, Courses: Science
Student: Charlie, Courses: None

在这个例子中,我们使用 GroupJoin 方法根据学生的 Id 和课程的 StudentId 进行分组连接,并打印了结果。

示例 2:自定义比较器

有时你需要自定义比较逻辑来判断两个键是否相等。这时可以传递一个实现了 IEqualityComparer<TKey> 接口的对象给 GroupJoin 方法。

可参考 Join 方法中案例。

示例 3:惰性求值

GroupJoin 方法采用惰性求值(Lazy Evaluation),这意味着它不会立即执行连接操作,而是等到实际遍历时才会计算结果。这使得 GroupJoin 可以处理无限序列或延迟执行复杂的查询。

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

class Program
{
    static void Main()
    {
        IEnumerable<int> infiniteNumbers = Enumerable.Range(0, int.MaxValue).Select(i => i * 2);
        List<int> finiteNumbers = new List<int> { 4, 6, 8 };

        // 使用 GroupJoin 进行分组连接操作
        var groupedSequence = infiniteNumbers.GroupJoin(
            finiteNumbers,
            number => number,
            target => target,
            (number, targets) => new { Number = number, Targets = targets.ToList() });

        // 限制输出数量
        Console.WriteLine("Grouped elements (First 3 elements):");
        foreach (var item in groupedSequence.Take(3))
        {
            Console.Write($"Number: {item.Number}, Targets: ");
            if (item.Targets.Any())
            {
                Console.WriteLine(string.Join(", ", item.Targets));
            }
            else
            {
                Console.WriteLine("None");
            }
        }
    }
}

输出结果:

Grouped elements (First 3 elements):
Number: 4, Targets: 4
Number: 6, Targets: 6
Number: 8, Targets: 8

在这个例子中,我们展示了如何使用 GroupJoin 方法计算一个无限序列和一个有限序列之间的分组连接,并通过 Take 方法限制输出的数量,从而避免无限循环。

示例 4:多个集合的分组连接

当需要计算多个集合的分组连接时,可以多次使用 GroupJoin 方法,或者使用更复杂的数据结构和查询逻辑来简化代码。

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

class Program
{
    static void Main()
    {
        List<int> list1 = new List<int> { 1, 2, 3, 4, 5 };
        List<int> list2 = new List<int> { 3, 4, 6 };
        List<int> list3 = new List<int> { 4, 7, 8 };

        // 使用 GroupJoin 计算多个集合的分组连接
        var groupedList = list1.GroupJoin(
            list2,
            number => number,
            target => target,
            (number, targets) => new { Number = number, Targets = targets.ToList() })
            .GroupJoin(
                list3,
                firstGroup => firstGroup.Number,
                thirdList => thirdList,
                (firstGroup, thirdTargets) => new 
                { 
                    FirstGroup = firstGroup, 
                    ThirdTargets = thirdTargets.ToList() 
                });

        // 输出结果
        Console.WriteLine("Grouped data among list1, list2, and list3:");
        foreach (var item in groupedList)
        {
            Console.Write($"Number: {item.FirstGroup.Number}, Targets: ");
            if (item.FirstGroup.Targets.Any())
            {
                Console.Write(string.Join(", ", item.FirstGroup.Targets));
            }
            else
            {
                Console.Write("None");
            }
            Console.Write(", ThirdTargets: ");
            if (item.ThirdTargets.Any())
            {
                Console.WriteLine(string.Join(", ", item.ThirdTargets));
            }
            else
            {
                Console.WriteLine("None");
            }
        }
    }
}

输出结果:

Grouped data among list1, list2, and list3:
Number: 1, Targets: None, ThirdTargets: None
Number: 2, Targets: None, ThirdTargets: None
Number: 3, Targets: 3, ThirdTargets: None
Number: 4, Targets: 4, ThirdTargets: 4
Number: 5, Targets: None, ThirdTargets: None

结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
微软官方文档 Enumerable

相关文章:

  • js,html,css,vuejs手搓级联单选
  • 网络空间安全(34)安全防御体系
  • 【蓝桥杯】每天一题,理解逻辑(4/90)【Leetcode 二进制求和】
  • system()执行和shell脚本的优劣
  • Spark SQL 编程初级实践
  • Spring相关面试题
  • 若依前后端分离项目部署(使用docker)
  • ArcGIS Pro 制作风台路径图:从数据到可视化
  • 中电金信25/3/18面前笔试(需求分析岗+数据开发岗)
  • Direct2D 极速教程(3) —— 画动态淳平
  • STM32F030通过DMA方式读取ADC及芯片内部温度程序
  • 基于srpingboot高校智慧校园教学管理服务平台的设计与实现(源码+文档+部署讲解)
  • 折叠树展示树状层级数据
  • WEB安全--SQL注入--DNSlog外带
  • c# 正则表达式基础知识
  • Hard Disk Sentinel:您的硬盘健康“全科医生”,守护数据安全的智能管家
  • STT-MRAM CIM 赋能边缘 AI:高性能噪声鲁棒贝叶斯神经网络宏架构详解
  • 进行交通流预测,使用KAN+Transformer模型
  • 好的服务设计怎么做?15个原则.服务的归一化设计原则是什么?
  • VMware Tools 安装详细教程(Ubuntu 虚拟机)
  • “拼好假”的年轻人,今年有哪些旅游新玩法?
  • 网络主播直播泄机密,别让这些“小事”成威胁国家安全的“突破口”
  • 时隔14个月北京怀柔区重启供地,北京建工以3.59亿元摘得
  • 开局良好,我国第一季度广告业务收入保持较快增速
  • 异域拾异|大脚怪的形状:一项神秘社会学研究
  • 印巴战火LIVE丨印巴互相发动无人机袭击,巴官员称两国已在国安层面接触