CodeGym /课程 /C# SELF /join (

join ( Join) 合并集合

C# SELF
第 33 级 , 课程 0
可用

1. 入门

有时候我们得处理两个不同的集合,它们通过某个共同点关联在一起。比如有个订单列表,还有一个单独的买家集合。怎么知道哪个订单属于哪个买家?这就得让两个集合通过一个共同的id关联起来——比如 userId

在关系型数据库里,这种事就是用 JOIN 操作搞定的,它会把不同表里key相同的行合并起来。LINQ里也有类似的操作符——名字也叫 Join

想象下有两张表:第一张是图书馆读者名单和他们的id,第二张是图书订单列表,每个订单里有个 id 表示读者是谁。用 join,我们就能把这两张表按 id 拼起来,最后得到「读者+他的订单」的配对。

顺便说一句,如果你觉得 join 只用在「数据库」里,那你可真是错过了不少东西!编程里合并集合其实很常见,尤其是和外部系统或者结构化文件打交道的时候(比如 JSONXML 或者会计发来的Excel表)。

2. Join 方法签名和原理

Join 方法长这样:


public static IEnumerable<TResult> Join<TOuter, TInner, TKey, TResult>(
    this IEnumerable<TOuter> outer,                 // 第一个集合(外部)
    IEnumerable<TInner> inner,                      // 第二个集合(内部)
    Func<TOuter, TKey> outerKeySelector,            // 怎么从外部集合元素拿key
    Func<TInner, TKey> innerKeySelector,            // 怎么从内部集合元素拿key
    Func<TOuter, TInner, TResult> resultSelector)   // 生成结果(新元素)的函数

乍一看有点吓人,其实很简单,咱们慢慢拆开说。
工作原理:
LINQ会对第一个集合(outer)里的每个元素,去第二个集合(inner)里找key相同的元素。key对上了,就调用 resultSelector,把结果塞进最终集合里。

3. 示例:合并买家和他们的订单

先来写俩class和集合。这思路就是我们课程里那个小商店项目的延续。


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

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

// 买家集合
var customers = new List<Customer>
{
    new Customer { Id = 1, Name = "瓦夏" },
    new Customer { Id = 2, Name = "彼佳" },
    new Customer { Id = 3, Name = "玛莎" },
};

// 订单集合
var orders = new List<Order>
{
    new Order { Id = 101, CustomerId = 2, Product = "书" },
    new Order { Id = 102, CustomerId = 1, Product = "钢笔" },
    new Order { Id = 103, CustomerId = 2, Product = "本子" },
    new Order { Id = 104, CustomerId = 3, Product = "橡皮" },
};

现在我们想拿到所有买家和他们的订单。比如输出:"彼佳 订购了 书","彼佳 订购了 本子" 之类的。

Join


// 按 customer.Id 和 order.CustomerId 合并买家和订单
var query = customers.Join(
    orders,
    customer => customer.Id,          // 怎么从买家拿key
    order => order.CustomerId,        // 怎么从订单拿key
    (customer, order) => new          // key对上的pair怎么处理(创建新对象)
    {
        CustomerName = customer.Name,
        Product = order.Product
    }
);

// 输出结果
foreach (var item in query)
{
    Console.WriteLine($"{item.CustomerName} 订购了 {item.Product}");
}

输出结果:

瓦夏 订购了 钢笔
彼佳 订购了 书
彼佳 订购了 本子
玛莎 订购了 橡皮

注意:如果一个买家有多个订单,他会在列表里出现多次——每个订单一行。就是应该这样!

4. 表格:JoinGroupBy + SelectMany 对比

操作 结果 使用场景
Join
扁平的pair列表 经典SQL JOIN。每个匹配pair一行。
GroupBy + SelectMany
带子集合的分组 想要「买家+他所有订单」这种一对多结构时用。

新手经常在需要「买家→订单列表」结构时用 Join。但 Join 是一行一行配对,不会分组。要分组得用 GroupJoin(下节课讲),或者 GroupBy

5. Query Syntax(SQL风格LINQ)里的 Join

LINQ支持两种写法:方法链(Method Syntax,比如 Join(...))和SQL风格(Query Syntax),看起来像普通SQL语句。

有些开发者——尤其是干过数据库的——觉得这种写法更直观:


var query2 =
    from customer in customers
    join order in orders
        on customer.Id equals order.CustomerId
    select new
    {
        CustomerName = customer.Name,
        Product = order.Product
    };

foreach (var item in query2)
{
    Console.WriteLine($"{item.CustomerName} 订购了 {item.Product}");
}

注意:
Query Syntax 里要用 equals 关键字——这里 == 不管用!
这点面试时经常考,尤其是新手 😉

6. 重要细节和坑

有时候不是所有集合元素都会出现在最终结果里。这是因为 Join 实现的是 「inner join」,只合并key能对上的元素。如果某个买家没订单,他就不会出现在结果里。同理,如果有个订单的 CustomerId 在买家里找不到,这个订单也不会进结果。

那如果想要所有买家都出现(哪怕没订单)怎么办?这种情况要用「左连接」,LINQ里得用 GroupJoinSelectMany 组合搞定。下节课会讲。普通 Join 必须两边都有,key对不上就不会出现在结果里。

7. 多key(composite key)合并

有时候合并集合得用多个字段。比如:按「商品编码+销售年份」合并商品和销售记录。

LINQ里可以用匿名对象做key来搞定:


var sales = ...; // 销售记录
var products = ...; // 商品

var query = products.Join(
    sales,
    prod => new { prod.Code, prod.Year },
    sale => new { sale.ProductCode, sale.Year },
    (prod, sale) => new { prod.Name, sale.Amount }
);

注意匿名对象里的属性类型和名字必须一样。不一样就算值一样也匹配不上。

8. 合并后的集合长啥样——示意图

下面这个图简单说明了join的原理:


flowchart LR
    subgraph Customers
        A1["瓦夏 (Id=1)"]
        A2["彼佳 (Id=2)"]
        A3["玛莎 (Id=3)"]
    end
    subgraph Orders
        B1["钢笔 (CustomerId=1)"]
        B2["书 (CustomerId=2)"]
        B3["本子 (CustomerId=2)"]
        B4["橡皮 (CustomerId=3)"]
    end

    A1-->|Id=1|B1
    A2-->|Id=2|B2
    A2-->|Id=2|B3
    A3-->|Id=3|B4
买家和订单按key合并的示意图

从图里能看出,每个买家会和他所有订单连起来——只要key能对上。

9. 常见错误和注意点

最常见的错误之一就是把参数顺序搞反,尤其是两边key类型一样的时候。编译器和LINQ都不会报错,但结果可能是空的或者很奇怪。

还有个常见坑——想用 Join 做「左连接」,让所有买家都在结果里(哪怕没订单)。但经典 Join 只会配对key能对上的pair,不会包含没匹配的元素。

2
任务
C# SELF, 第 33 级, 课程 0
已锁定
简单的顾客和订单列表合并
简单的顾客和订单列表合并
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION