WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

C# 参照型ListできちんとRemoveする

今回のエンジニアブログを担当する加賀です。

C#のListで、参照型リストの要素の削除がうまくいかなかったことがあり、今回はそれの解決法の1つを紹介したいと思います。
今回のコードはVS2010Pro SP1、C# 4.0で確認しています。

ListRemoveメソッドは、Tに指定した型のオブジェクトを引数に取り、最初に一致した要素を削除します。

bool Remove( T item )

サンプルとして以下のプログラムを使用して動きを見てみます。

using System;  
using System.Collections.Generic;  
  
//Listの要素クラス  
public class Data  
{  
    public int ID;  
    public string Name;  
    public int HP;  
    public Data(int ID, string Name, int HP)  
    {  
        this.ID   = ID;  
        this.Name = Name;  
        this.HP   = HP;  
    }  
    public Data(int ID)  
    {  
        this.ID   = ID;  
        this.Name = "";  
        this.HP   = 0;  
    }  
}  
  
public class Example  
{  
    static List<Data> DataList = new List<Data>();  
  
    //DataListの中身を表示  
    public static void ShowDataList()  
    {  
        Console.Out.WriteLine("ID    Name      HP");  
        foreach(Data data in DataList)  
        {  
            Console.Out.WriteLine(String.Format("{0,-4}  {1,-8}  {2}",data.ID,data.Name,data.HP));  
        }  
        Console.Out.WriteLine();  
    }  
  
    public static void Main()  
    {  
        DataList.Add(new Data(0, "Data1", 10));  
        DataList.Add(new Data(1, "Data2", 20));  
  
        Console.Out.WriteLine("List削除テスト");  
        ShowDataList();  
  
        //削除  
        Console.Out.WriteLine("ID 0を削除します\n");  
        DataList.Remove(new Data(0, "Data1", 10));  
  
        //削除後結果表示  
        ShowDataList();  
        Console.In.ReadLine();  
    }  
}  

実行結果
1
うまく削除されませんでした。

これは、デフォルトの比較はインスタンスが同じかどうかで比較をしているからです。
リストに登録した時のインスタンスと、Remove時に渡したインスタンスが別になるので、たとえ値がすべて同じだとしても一致してくれません。

◆解決法

今回の解決法は、DataクラスにIEquatableインタフェースを実装し、自分で比較関数を書くことです。
今回は、IDが一致すればオブジェクト一致ということにしてあります。この辺りは自分で好きな様に書くことができます。

ただし、IEquatableインタフェースを実装する際は、以下の2つも同じような結果を返すようにオーバーライドしておくべきでしょう。

bool Equals( Object obj )  
int GetHashCode()  

今回修正する部分はDataクラスのみなので、解決後のコードはDataクラスのみ記述しておきます。

public class Data : IEquatable<Data>  
{  
    public int ID;  
    public string Name;  
    public int HP;  
    public Data(int ID, string Name, int HP)  
    {  
        this.ID   = ID;  
        this.Name = Name;  
        this.HP   = HP;  
    }  
    public Data(int ID)  
    {  
        this.ID   = ID;  
        this.Name = "";  
        this.HP   = 0;  
    }  
  
    //IEquatable<Data>のEqualsメソッド  
    public bool Equals(Data obj)  
    {  
        return (this.ID == obj.ID);  
    }  
  
    //オーバーライドしておくべき  
    public override bool Equals(Object obj)  
    {  
        if (obj == null)  
            return base.Equals(obj);  
        if (obj is Data)  
            return Equals(obj as Data);  
        return false;  
    }  
    public override int GetHashCode()  
    {  
        return ID.GetHashCode();  
    }  
}  

実行結果
2
これできちんと削除されました。

今回のように、IDという1つの値で識別するような場合はDictionaryを使えば簡単に実装できますが、2つ以上の値で識別したい場合などでも使えると思います。