73318


例えば以下のようなコードがあるとします。

[code]

    public class Employee
        {
            public string Name { get; set; }
            public int JobId { get; set; }
            public string JobName { get; set; }
        }
    
        public class Job
        {
            public int JobId { get; set; }
    
            public string JobName { get; set; }
        }
    
        public void DoTest()
        {
            var employees = new List<Employee>();
            employees.Add(new Employee() { Name = "John", JobId = 1 });
            employees.Add(new Employee() { Name = "Danny", JobId = 2 });
            employees.Add(new Employee() { Name = "Ashley", JobId = 2 });
            employees.Add(new Employee() { Name = "Taro", JobId = 3 });
    
            var jobs = new List<Job>();
            jobs.Add(new Job() { JobId = 1, JobName = "Database Engineer" });
            jobs.Add(new Job() { JobId = 2, JobName = "Designer" });
            jobs.Add(new Job() { JobId = 3, JobName = "Manager" });
            jobs.Add(new Job() { JobId = 4, JobName = "Network Engineer" });
    
            var query = from em in employees
                        join j in jobs on em.JobId equals j.JobId
                        select new Employee() { Name = em.Name, JobId = em.JobId, JobName = j.JobName };
    
        }
    
[/code]

最後のjoin句後にselect句でnewをし、Emplyeeクラスのインスタンスを生成しています。

私は **emplyeesに含まれる各インスタンスのJobNameのみを更新したい** のですが、その場合、どのように書くのが良いでしょうか。  
このコードではselect句後にnewしているので、レコードが多い場合、インスタンス生成にメモリを奪われる、または  
EmployeeインスタンスのJobNameだけを更新したいのに、newするときに各パラメータを指定し直しているため、パラメータが多い場合にコード量が多くなることを懸念しています。


CC BY-SA 4.0
2021-01-15T09:33:36.670
73317
2022-02-13T14:11:08.083
2021-01-15T10:11:02.257
12388
12388
question
c#
C# Linq Joinによる結合元のデータ更新方法


C# 9.0（.NET 5.0）だとレコードが追加されます。レコードには[`with`式](https://docs.microsoft.com/ja-
jp/dotnet/csharp/language-reference/operators/with-
expression)というものがあり、指定したプロパティだけ書き換わった新しいインスタンスを作成できます。これを使うと次のように書けます。

[code]

    public record Employee {
        public string Name { get; init; }
        public int JobId { get; init; }
        public string JobName { get; init; }
    }
    
    public record Job {
        public int JobId { get; init; }
        public string JobName { get; init; }
    }
    
    public void DoTest() {
        var employees = new List<Employee> {
            new Employee { Name = "John", JobId = 1 },
            new Employee { Name = "Danny", JobId = 2 },
            new Employee { Name = "Ashley", JobId = 2 },
            new Employee { Name = "Taro", JobId = 3 },
        };
        var jobs = new List<Job> {
            new Job { JobId = 1, JobName = "Database Engineer" },
            new Job { JobId = 2, JobName = "Designer" },
            new Job { JobId = 3, JobName = "Manager" },
            new Job { JobId = 3, JobName = "Network Engineer" },
        };
    
        var query = from em in employees
                    join j in jobs on em.JobId equals j.JobId
                    select em with { JobName = j.JobName };
    }
    
[/code]

* * *

> レコードが多い場合、インスタンス生成にメモリを奪われる

懸念はごもっともです。ただし、Employeeインスタンスと同数のName文字列が必要ですので、Employeeだけを考慮していても仕方がありません。  
もしEmployeeインスタンスが膨大でパフォーマンスに支配的な影響を与える場合は、値型への変更なども視野に入ってきます。値型であればGC管理不要で、配列や`List<T>`が連続した大きな領域を確保するだけとなります。

* * *

.NET 6 / C# 10がリリースされました。C#
10では、`with`式が拡張され、レコード以外に構造体や匿名型にも適用できるようになりました。また、レコードも拡張されレコード構造体も定義できるようになりました。


CC BY-SA 4.0
2021-01-15T09:54:12.727
73318
2022-02-13T14:11:08.083
2022-02-13T14:11:08.083
4236
4236
73317
answer


単純にプロパティを書き換えるだけならば、queryの代わりにForEachと[First](https://docs.microsoft.com/ja-
jp/dotnet/api/system.linq.enumerable.first?view=net-5.0)を組み合わせて、employeesとJobIdが最初に合致するjobsのJobNameを取得して代入することができます。

`employees.ForEach(e => e.JobName = jobs.First(j => j.JobId ==
e.JobId).JobName);`

なおTaroさんのJobIdは3なので該当Jobが2つありますが、先に合致したManagerが割り振られます。

**サンプルコード**

[code]

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace ConsoleApp1
    {
        class Program
        {
            static void Main(string[] args)
            {
                new Program().DoTest();
                Console.ReadLine();
            }
    
            public void DoTest()
            {
                var employees = new List<Employee>();
                employees.Add(new Employee() { Name = "John", JobId = 1 });
                employees.Add(new Employee() { Name = "Danny", JobId = 2 });
                employees.Add(new Employee() { Name = "Ashley", JobId = 2 });
                employees.Add(new Employee() { Name = "Taro", JobId = 3 });
    
                var jobs = new List<Job>();
                jobs.Add(new Job() { JobId = 1, JobName = "Database Engineer" });
                jobs.Add(new Job() { JobId = 2, JobName = "Designer" });
                jobs.Add(new Job() { JobId = 3, JobName = "Manager" });
                jobs.Add(new Job() { JobId = 3, JobName = "Network Engineer" });
    
                //書き換え
                employees.ForEach(e => e.JobName = jobs.First(j => j.JobId == e.JobId).JobName);
    
                //確認
                employees.ForEach(e => Console.WriteLine(string.Join(", ", e.Name, e.JobId, e.JobName)));
            }
        }
        public class Employee
        {
            public string Name { get; set; }
            public int JobId { get; set; }
            public string JobName { get; set; }
        }
    
        public class Job
        {
            public int JobId { get; set; }
    
            public string JobName { get; set; }
        }
    }
    
[/code]

**出力結果**

[code]

    John, 1, Database Engineer
    Danny, 2, Designer
    Ashley, 2, Designer
    Taro, 3, Manager
    
[/code]


CC BY-SA 4.0
2021-01-15T09:59:36.400
73319
2021-01-15T09:59:36.400
9820
73317
answer