C# PR

【C# LINQ】重複を削除して結合するUnionとUnionByの違い

記事内に商品プロモーションを含む場合があります

こんにちは!

C#のLINQにおける「Union」と「UnionBy」。複数のコレクションを効率的に結合するためのこれらのメソッドについて、詳しく知りたくありませんか?

Unionって何だろう?UnionByとの違いは?
重複をどうやって判定しているの?
自分が作ったクラスでも使える?

C#でのコレクション処理において、このような疑問をお持ちではないでしょうか。

この記事では、C# LINQの「Union」と「UnionBy」について、基本概念から具体的な使い方、そして実践的な例までを詳しく解説します。

この記事は次のような方におすすめです。

この記事はこんな人におすすめ!
  • C#でコレクションの結合処理をしたい方
  • 重複を除外した配列結合の方法を知りたい方
  • LINQの基本から応用までを学びたい方
  • 効率的なコレクション処理を実装したい開発者
  • Unionの内部動作や仕組みに興味がある方

この記事を読めば、UnionとUnionByの違いが理解でき、実際のプロジェクトですぐに活用できるスキルが身につきます。C#での効率的なコレクション処理を取り入れて、より洗練されたコードを書けるようになりますよ!

「C#でコレクション結合をマスターしたい方」「LINQをもっと効果的に活用したい方」は、ぜひ参考にしてください。

それでは、順を追って詳しく見ていきましょう!

LINQ Unionとは何か

LINQ Unionは、2つのシーケンス(配列やリストなど)を結合し、重複を除外した新しいシーケンスを生成するためのメソッドです。SQLのUNION演算に似た機能を持っています。

Unionの基本的な特徴は以下の通りです。

  • 2つのコレクションを結合する
  • 重複する要素は1つだけ残して除外する
  • デフォルトでは型に応じた等値比較を使用する
  • カスタムの等値比較器を指定することも可能

LINQ Unionは古くからある機能で、.NET Frameworkの頃から使用可能でした。一方、UnionByは.NET 6から導入された比較的新しい機能です。

Unionの基本的な使い方

Unionを使うためには、まず「System.Linq」名前空間をインポートします。

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

class Program
{
  static void Main()
  {
    // 整数配列の例
    int[] numbers1 = { 1, 2, 3, 4, 5 };
    int[] numbers2 = { 4, 5, 6, 7, 8 };

    // Unionメソッドを使用して配列を結合
    var unionResult = numbers1.Union(numbers2);

    Console.WriteLine("Union結果:");
    foreach (var num in unionResult)
    {
      Console.Write($"{num} ");
    }
    // 出力: 1 2 3 4 5 6 7 8
  }
}

この例では、2つの整数配列をUnionメソッドで結合し、重複する4と5は1回だけ表示されています。基本的な型(int, string, decimal, bool など)では、値の等価性に基づいて重複を判定します。

UnionとConcatの違い

LINQには配列を結合する別のメソッド「Concat」もあります。UnionとConcatの違いを理解することは重要です。

Union Concat
重複要素を除外する 重複要素もそのまま含める
一意の要素のみを返す すべての要素を返す
要素の比較処理が必要 単純に追加するだけ
処理負荷がやや高い 処理負荷が低い

例えば、[1, 2, 3]と[3, 4, 5]という2つの配列があるとします。それぞれ以下のような結果になります。

  • Unionの結果:[1, 2, 3, 4, 5]
  • Concatの結果:[1, 2, 3, 3, 4, 5]
【C#】LINQのConcatで配列・リスト結合を簡単に!
【C#】LINQのConcatで配列・リスト結合を簡単に!C#のConcatメソッドをマスターしよう!複数コレクションの効率的な結合方法、パフォーマンスの注意点、類似メソッドとの違いまで詳しく解説。...

メソッド構文とクエリ構文

LINQでは、メソッド構文とクエリ構文の2種類の書き方があります。Unionを使う場合、それぞれ次のように記述します。

// サンプルデータ
int[] numbers1 = { 1, 2, 3 };
int[] numbers2 = { 3, 4, 5 };
string[] names1 = { "田中", "佐藤", "山田" };
string[] names2 = { "山田", "鈴木", "高橋" };

// 1. メソッド構文の例
var unionNumbers = numbers1.Union(numbers2);
// 結果: 1, 2, 3, 4, 5

// 2. クエリ構文では直接Unionをサポートしていないため、
// クエリ構文で前半を書き、メソッド構文でUnionを適用する混合構文を使用
var unionNames = (from name in names1
               where name.Length > 1
               select name)
              .Union(names2);
// 結果: "田中", "佐藤", "山田", "鈴木", "高橋"

// 3. より複雑な例(複数の条件とUnionの組み合わせ)
var result = (from n in numbers1
           where n % 2 == 1  // 奇数のみ選択
           select n)
          .Union(
              from m in numbers2
              where m > 3    // 3より大きい数のみ選択
              select m
          );
// 結果: 1, 3, 4, 5

メソッド構文は簡潔でチェーンしやすい特徴があり、Unionのような集合演算ではこちらが一般的です。クエリ構文は複雑なフィルタリングやプロジェクションでSQL風の読みやすさがありますが、Unionのような操作は直接サポートされていないため、上記のように混合構文を使用する必要があります。

UnionByとは

UnionByは.NET 6から導入された比較的新しいメソッドで、Unionとほぼ同じ機能を持ちますが、比較に使用するプロパティをラムダ式で指定できるという大きな違いがあります。

これにより、カスタムクラスを扱う際に、IEqualityComparerを実装するコードを書かなくても、特定のプロパティに基づいて重複を除外できます。

UnionとUnionByの違い

次の表で主な違いを比較してみましょう。

機能 Union UnionBy
.NETバージョン すべてのバージョン .NET 6以降
重複判定 オブジェクト全体または等値比較器 指定したプロパティ
使いやすさ カスタムクラスでは等値比較器が必要 ラムダ式でプロパティを指定するだけ
コード量 カスタムクラスでは多め シンプル

UnionByの基本的な使い方

それでは、UnionByの基本的な使い方を見ていきましょう。

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

// 商品クラス
class Product
{
  public int Id { get; set; }
  public string Name { get; set; }
  public decimal Price { get; set; }

  public override string ToString()
  {
    return $"ID: {Id}, 名前: {Name}, 価格: {Price}円";
  }
}

class Program
{
  static void Main()
  {
    // 2つの商品リストを作成
    var products1 = new List<Product>
    {
      new Product { Id = 1, Name = "ノートPC", Price = 80000 },
      new Product { Id = 2, Name = "マウス", Price = 3000 },
      new Product { Id = 3, Name = "キーボード", Price = 5000 }
    };

    var products2 = new List<Product>
    {
      new Product { Id = 1, Name = "ノートPC", Price = 80000 }, // ID:1は重複
      new Product { Id = 4, Name = "モニター", Price = 25000 },
      new Product { Id = 5, Name = "スピーカー", Price = 4000 }
    };

    // UnionByでIDに基づいて結合
    var result = products1.UnionBy(products2, p => p.Id);

    Console.WriteLine("UnionBy結果 (IDで比較):");
    foreach (var product in result)
    {
      Console.WriteLine(product);
    }
    // ID:1の商品は1回だけ表示される
  }
}

この例では、商品リストproducts1products2を「Id」プロパティに基づいて結合しています。両方のリストに存在するID:1の商品は1つのみ結果に含まれます。

カスタムクラスでUnionを使用する場合

UnionByを使用せずに、カスタムクラスで従来のUnionを使用する場合は、IEqualityComparer<T>インターフェースを実装する必要があります。

// 商品のIDに基づく等値比較器
class ProductComparer : IEqualityComparer<Product>
{
  public bool Equals(Product x, Product y)
  {
    if (x == null || y == null)
      return false;

    return x.Id == y.Id;
  }

  public int GetHashCode(Product obj)
  {
    return obj.Id.GetHashCode();
  }
}

// Unionの使用(比較器を指定)
var result = products1.Union(products2, new ProductComparer());

上記のコードでは、ProductComparerクラスを作成して、Idプロパティに基づく比較を実装しています。これをUnionメソッドの第2引数に渡すことで、IdプロパティでUNION操作を行うことができます。

UnionとUnionByの内部動作

UnionとUnionByの内部動作を理解することで、より効率的に使用できます。

両メソッドとも、内部的にはHashSet<T>を利用して重複チェックを行っています。動作の流れは以下の通りです。

  • 結果を格納するための空のHashSet<T>を作成
  • 最初のシーケンスの各要素をHashSet<T>に追加(重複は自動的に排除)
  • 2番目のシーケンスの各要素を同じHashSet<T>に追加(すでに存在する要素は無視)
  • HashSet<T>の内容を結果として返す

UnionByの場合は、要素自体ではなく、指定されたキーセレクター関数によって生成されたキーに基づいて、HashSet<TKey>を使用して重複チェックを行います。

UnionByの複数プロパティによる比較

場合によっては、複数のプロパティを組み合わせて比較したいことがあります。例えば、顧客データを統合する際に「氏名とメールアドレスが両方一致した場合のみ同一人物とみなす」といったケースです。

そのような場合は、匿名型を使用して複合キーを作成することができます。以下に具体例を示します。

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

class Contact
{
  public int Id { get; set; }
  public string Name { get; set; }
  public string Email { get; set; }
  public string Phone { get; set; }
  public string Source { get; set; }

  public override string ToString()
  {
    return $"ID: {Id}, 名前: {Name}, メール: {Email}, 電話: {Phone}, 情報元: {Source}";
  }
}

class Program
{
  static void Main()
  {
    // CRMシステムからの連絡先リスト
    var crmContacts = new List<Contact>
    {
      new Contact { Id = 1, Name = "山田太郎", Email = "yamada@example.com", Phone = "090-1234-5678", Source = "CRM" },
      new Contact { Id = 2, Name = "佐藤花子", Email = "sato@example.com", Phone = "080-8765-4321", Source = "CRM" },
      new Contact { Id = 3, Name = "田中次郎", Email = "tanaka@example.com", Phone = "070-5555-1234", Source = "CRM" },
      new Contact { Id = 4, Name = "鈴木一郎", Email = "wrong_suzuki@example.com", Phone = "060-9999-8888", Source = "CRM" } // メールアドレス間違い
    };

    // マーケティングシステムからの連絡先リスト
    var marketingContacts = new List<Contact>
    {
      new Contact { Id = 101, Name = "山田太郎", Email = "yamada@example.com", Phone = "090-1234-5678", Source = "Marketing" }, // 名前とメールが一致
      new Contact { Id = 102, Name = "佐藤花子", Email = "sato_new@example.com", Phone = "080-8765-4321", Source = "Marketing" }, // メールが異なる
      new Contact { Id = 103, Name = "鈴木一郎", Email = "suzuki@example.com", Phone = "060-9999-8888", Source = "Marketing" }, // 正しいメールアドレス
      new Contact { Id = 104, Name = "高橋慎一", Email = "takahashi@example.com", Phone = "050-1111-2222", Source = "Marketing" } // 新規連絡先
    };

    Console.WriteLine("===== 名前のみで比較した場合 =====");
    // 名前だけで比較する場合
    var contactsByName = crmContacts.UnionBy(marketingContacts, c => c.Name);
    foreach (var contact in contactsByName)
    {
      Console.WriteLine(contact);
    }
    Console.WriteLine($"件数: {contactsByName.Count()}"); // 重複を除いたユニークな名前の数

    Console.WriteLine("\n===== メールアドレスのみで比較した場合 =====");
    // メールアドレスだけで比較する場合
    var contactsByEmail = crmContacts.UnionBy(marketingContacts, c => c.Email);
    foreach (var contact in contactsByEmail)
    {
      Console.WriteLine(contact);
    }
    Console.WriteLine($"件数: {contactsByEmail.Count()}"); // 重複を除いたユニークなメールアドレスの数

    Console.WriteLine("\n===== 名前とメールアドレスの両方で比較した場合 =====");
    // 名前とメールアドレスの両方が一致する場合のみ重複とみなす
    var contactsByNameAndEmail = crmContacts.UnionBy(marketingContacts, c => new { c.Name, c.Email });
    foreach (var contact in contactsByNameAndEmail)
    {
      Console.WriteLine(contact);
    }
    Console.WriteLine($"件数: {contactsByNameAndEmail.Count()}"); // 名前とメールの両方が一致する場合のみ重複排除
  }
}

この例の出力結果は以下のようになります。

===== 名前のみで比較した場合 =====
ID: 1, 名前: 山田太郎, メール: yamada@example.com, 電話: 090-1234-5678, 情報元: CRM
ID: 2, 名前: 佐藤花子, メール: sato@example.com, 電話: 080-8765-4321, 情報元: CRM
ID: 3, 名前: 田中次郎, メール: tanaka@example.com, 電話: 070-5555-1234, 情報元: CRM
ID: 4, 名前: 鈴木一郎, メール: wrong_suzuki@example.com, 電話: 060-9999-8888, 情報元: CRM
ID: 104, 名前: 高橋慎一, メール: takahashi@example.com, 電話: 050-1111-2222, 情報元: Marketing
件数: 5

===== メールアドレスのみで比較した場合 =====
ID: 1, 名前: 山田太郎, メール: yamada@example.com, 電話: 090-1234-5678, 情報元: CRM
ID: 2, 名前: 佐藤花子, メール: sato@example.com, 電話: 080-8765-4321, 情報元: CRM
ID: 3, 名前: 田中次郎, メール: tanaka@example.com, 電話: 070-5555-1234, 情報元: CRM
ID: 4, 名前: 鈴木一郎, メール: wrong_suzuki@example.com, 電話: 060-9999-8888, 情報元: CRM
ID: 102, 名前: 佐藤花子, メール: sato_new@example.com, 電話: 080-8765-4321, 情報元: Marketing
ID: 103, 名前: 鈴木一郎, メール: suzuki@example.com, 電話: 060-9999-8888, 情報元: Marketing
ID: 104, 名前: 高橋慎一, メール: takahashi@example.com, 電話: 050-1111-2222, 情報元: Marketing
件数: 7

===== 名前とメールアドレスの両方で比較した場合 =====
ID: 1, 名前: 山田太郎, メール: yamada@example.com, 電話: 090-1234-5678, 情報元: CRM
ID: 2, 名前: 佐藤花子, メール: sato@example.com, 電話: 080-8765-4321, 情報元: CRM
ID: 3, 名前: 田中次郎, メール: tanaka@example.com, 電話: 070-5555-1234, 情報元: CRM
ID: 4, 名前: 鈴木一郎, メール: wrong_suzuki@example.com, 電話: 060-9999-8888, 情報元: CRM
ID: 102, 名前: 佐藤花子, メール: sato_new@example.com, 電話: 080-8765-4321, 情報元: Marketing
ID: 103, 名前: 鈴木一郎, メール: suzuki@example.com, 電話: 060-9999-8888, 情報元: Marketing
ID: 104, 名前: 高橋慎一, メール: takahashi@example.com, 電話: 050-1111-2222, 情報元: Marketing
件数: 7

このように匿名型(new { c.Name, c.Email })を使用することで、複数のプロパティを組み合わせた比較ができます。この例では。

  • 名前のみで比較した場合 → 5件(「山田太郎」「佐藤花子」「田中次郎」「鈴木一郎」「高橋慎一」)
  • メールアドレスのみで比較した場合 → 7件(すべてのメールアドレスが異なる)
  • 名前とメールの両方で比較した場合 → 7件(「山田太郎」のみが両方とも一致)

複数プロパティを使った比較は、データクレンジングや統合の際に、どの程度厳密に重複を判定するかという判断に役立ちます。

UnionとUnionByの使い分け

UnionとUnionByはどのように使い分ければよいのでしょうか。

  • 基本型(int、stringなど)の場合は Union だけで十分
  • カスタムクラスでプロパティに基づいて比較する場合は UnionBy が便利
  • .NET 6未満の環境では UnionIEqualityComparer<T> の組み合わせを使用
  • パフォーマンスが重要な場合、UnionBy のほうが一般的に簡潔で効率的

キャリア形成/給与還元
ひとつひとつ真摯に向き合う企業
ONE_WEDGE社員募集

株式会社 ONE WEDGEでは、新たな仲間を募集しています!

私たちと一緒に、革新的で充実したキャリアを築きませんか?
当社は、従業員が仕事と私生活のバランスを大切にできるよう、充実した福利厚生を整えています。

  • 完全週休2日制(土日休み)で、祝日や夏季休暇、年末年始休暇もしっかり保証!
  • 様々な休暇制度(有給、慶弔、産前・産後、育児、バースデー休暇、有給6日取得で特別休暇付与)を完備!
  • 従業員の成長と健康を支援するための表彰制度、資格取得支援、健康促進手当など!
  • 生活を支えるテレワーク手当、記事寄稿手当、結婚祝金・出産祝金など、様々な手当を提供!
  • 自己啓発としての書籍購入制度や、メンバー間のコミュニケーションを深める交流費補助!
  • 成果に応じた決算賞与や、リファラル採用手当、AI手当など、頑張りをしっかり評価!
  • ワークライフバランスを重視し、副業もOK!

株式会社 ONE WEDGEでは、一人ひとりの従業員が自己実現できる環境を大切にしています。
共に成長し、刺激を与え合える仲間をお待ちしております。
あなたの能力と熱意を、ぜひ当社で発揮してください。
ご応募お待ちしております!

ホームページ、採用情報は下記ボタンからご確認ください!

応募、ご質問など、LINEでお気軽にご相談ください♪

まとめ

C# LINQのUnionとUnionByは、複数のコレクションを結合し、重複を除外するための強力なツールです。

  • Unionは基本型のコレクション結合に最適で、カスタムクラスでは等値比較器が必要
  • UnionBy(.NET 6以降)は特定のプロパティに基づく比較を簡単に実装できる
  • 両メソッドともConcatと異なり、重複要素を除外する
  • 内部的にはHashSet<T>を使用して効率的に重複を排除している

適切なメソッドを選択することで、コードの可読性と保守性を向上させることができます。特にカスタムクラスを扱う場合、UnionByを使用することで、従来のUnionと比較して大幅にコード量を削減できます。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です