【C#】Listの内容的等価性と削除操作に関する対応方法

C#におけるList<T>の等価性と削除操作は、Equalsメソッドやオブジェクトのインスタンスがどのように扱われるかによって結果が異なる場合があります。本資料では、内容的に等価な要素を持つ異なるListのインスタンスから削除がうまくいかない場合の問題とその対応方法について説明します。

問題の背景

List<T>Removeメソッドは、リスト内から指定されたオブジェクトを削除するために使用されます。このメソッドは、指定されたオブジェクトとリスト内の各要素を比較し、最初に一致した要素を削除します。しかし、異なるインスタンスのListで同じ内容を持つオブジェクトがあっても、デフォルトのEqualsメソッドではそれらが等価とみなされないため、削除が期待通りに行われない場合があります。

サンプルコードとその解説

基本サンプル

まず、LibraryALibraryBという2つの異なるクラスで同じ内容を持つList<Book>が定義されています。しかし、これらのリストの要素を削除しようとすると失敗します。

public class Book
{
    public string Title { get; set; }
}

public class LibraryA
{
    public List<Book> Books { get; set; }

    public LibraryA()
    {
        Books = new List<Book>
        {
            new Book { Title = "C#プログラミング" },
            new Book { Title = "オブジェクト指向を学ぶ" },
            new Book { Title = "高度なC#" }
        };
    }
}

public class LibraryB
{
    public List<Book> Books { get; set; }

    public LibraryB()
    {
        Books = new List<Book>
        {
            new Book { Title = "C#プログラミング" },
            new Book { Title = "オブジェクト指向を学ぶ" },
            new Book { Title = "高度なC#" }
        };
    }
}

public class Program
{
    public static void Main()
    {
        var libraryA = new LibraryA();
        var libraryB = new LibraryB();

        var bookToRemove = libraryB.Books[0]; // "C#プログラミング"を指す

        bool removed = libraryA.Books.Remove(bookToRemove);

        Console.WriteLine("LibraryAからLibraryBの要素を削除できましたか?: " + removed); // False
    }
}

対応方法1: Equalsメソッドのオーバーライド

この問題を解決するための1つの方法は、BookクラスにおいてEqualsメソッドとGetHashCodeメソッドをオーバーライドすることです。これにより、異なるインスタンスでも内容が等価であれば削除操作が成功するようになります。

public class Book
{
    public string Title { get; set; }

    public override bool Equals(object obj)
    {
        if (obj is Book otherBook)
        {
            return this.Title == otherBook.Title;
        }
        return false;
    }

    public override int GetHashCode()
    {
        return Title.GetHashCode();
    }
}

これにより、次のように削除操作が期待通りに動作します。

bool removed = libraryA.Books.Remove(bookToRemove);
Console.WriteLine("LibraryAからLibraryBの要素を削除できましたか?: " + removed); // True

対応方法2: コンストラクタによる依存性の注入

もう一つの方法として、LibraryALibraryBに同じリストインスタンスをコンストラクタを通じて注入する方法があります。これにより、両方のクラスが同じBookインスタンスを参照するようになり、Equalsメソッドのオーバーライドを必要とせずに削除操作が期待通りに動作するようになります。

public class LibraryA
{
    public List<Book> Books { get; private set; }

    public LibraryA(List<Book> sharedBooks)
    {
        Books = sharedBooks;
    }
}

public class LibraryB
{
    public List<Book> Books { get; private set; }

    public LibraryB(List<Book> sharedBooks)
    {
        Books = sharedBooks;
    }
}

public class Program
{
    public static void Main()
    {
        var sharedBooks = new List<Book>
        {
            new Book { Title = "C#プログラミング" },
            new Book { Title = "オブジェクト指向を学ぶ" },
            new Book { Title = "高度なC#" }
        };

        var libraryA = new LibraryA(sharedBooks);
        var libraryB = new LibraryB(sharedBooks);

        var bookToRemove = libraryB.Books[0];

        bool removed = libraryA.Books.Remove(bookToRemove);

        Console.WriteLine("LibraryAからLibraryBの要素を削除できましたか?: " + removed); // True
    }
}

まとめ

  • 問題の背景: 内容が等しいオブジェクトでも、異なるインスタンスであればList<T>Removeメソッドが正しく動作しないことがある。
  • 対応方法1EqualsおよびGetHashCodeメソッドをオーバーライドすることで、異なるインスタンスの内容を等価とみなす。
  • 対応方法2: コンストラクタによる依存性の注入を利用し、同じインスタンスを共有することで削除操作が正しく行えるようにする。

C#

Posted by hidepon