使用.NET 2.0删除List<T>中的重复项

4

当我尝试使用.NET 2.0从列表中删除重复项时,我没有得到预期的结果。

    List<Student> list1 = new List<Student>();

    Student s = new Student();
    s.Age = "35";
    s.Name = "Nazmul";
    list1.Add(s);

    s = new Student();
    s.Age = "35";
    s.Name = "Nazmul";
    list1.Add(s);

    s = new Student();
    s.Age = "35";
    s.Name = "Nazmul";
    list1.Add(s);

    s = new Student();
    s.Age = "35";
    s.Name = "Nazmul";
    list1.Add(s);

    IComparer<Student> compare = new MyOrderingClass();

    list1.Sort(compare);
    Int32 index = 0;

    while (index < list1.Count - 1)
    {

        if (list1[index].Equals(list1[index + 1]))
        {
            list1.RemoveAt(index);
        }
        else
        {
            index++;
        }
    }

    foreach (Student st in list1)
    {

        Response.Write("Name:" + st.Name);
        Response.Write("Age:" + st.Age + "<br/>");
    }

上面使用的类和方法如下所示:
public class Student
{
    private String name;
    private String age;
    public String Name
    {
        get { return name; }
        set { name = value; }
    }
    public String Age { 
        get { return age; } 
        set { age = value; } 
    }
}

public class MyOrderingClass : IComparer<Student>
{
    public int Compare(Student x, Student y)
    {
        int compareName = x.Name.CompareTo(y.Name);
        if (compareName == 0)
        {
            return x.Age.CompareTo(y.Age);
        }
        return compareName;
    }
}

我得到了所有列表项的输出。想知道我做错了什么。

2
首先,在迭代列表(IEnumerable)时,绝对不要更改它。 - Ganesh R.
如果 (list1[index].Equals(list1[index + 1])) 从不返回 true,那么您需要使用您的值重写 Student.Equals。 - Mate
1
默认情况下,等于仅支持引用相等性。这可能会有用。http://msdn.microsoft.com/en-US/library/bsc2ak47(v=vs.80).aspx - pmtamal
@pmtamal,非常感谢你的参考。这个方法是什么意思:"public override int GetHashCode()"? - Nazmul
@Mate,正在尝试您的代码。 - Nazmul
显示剩余4条评论
3个回答

2
你正在考虑的核心思想可以用一个操作——相等性——和一个数据结构——集合来描述。
首先是操作。
你的类MyOrderingClass接近于你在这里想要的。你可以使用IEqualityComparer<Student>,而不是IComparer<Student>,它提供了一种确定两个实例是否相等的方法。有两个方法需要确定这一点:当然,一个是Equals,另一个是GetHashCode,它是一种“快捷”方法,用于确定实例是否可能相等。这里要注意的重要事情是,GetHashCode的实现涉及到用于确定相等性的相同值。
public class MyOrderingClass : IEqualityComparer<Student>, IComparer<Student>
{   
    public int Compare(Student x, Student y)   
    {   
        if (ReferenceEquals(x, y))
        {
            return 0;
        }

        if (ReferenceEquals(x, null))
        {
            throw new ArgumentException("x");
        }

        if (ReferenceEquals(y, null))
        {
            throw new ArgumentException("y");
        }

        // Optional: use StringComparer.CurrentCultureIgnoreCase.CompareTo or maybe others 
        // from http://msdn.microsoft.com/en-us/library/system.stringcomparer.aspx
        int compareName = x.Name.CompareTo(y.Name);

        if (compareName == 0)   
        {   
            return x.Age.CompareTo(y.Age);   
        }   

        return compareName;   
    }

    public int GetHashCode(Student student)
    {
        if(student == null)
        {
            throw new ArgumentNullException("student");
        }

        unchecked
        {
            return (student.Name ?? String.Empty).GetHashCode() ^ student.Age;
        }
    }

    public bool Equals(Student x, Student y)
    {
        return Compare(x, y) == 0;
    }
}   

第二,数据结构。

使用Distinct扩展方法可以实现具有不同对象集合的想法,但查看ISet<T> - HashSet<T>的实现可能会有所帮助。原因是“不同元素”的概念实际上是一组概念的定义-属于一起的唯一元素。 HashSet<T>使用IEqualityComparer<T>的实例 - 默认情况下使用EqualityComparer<T>.Default进行引用相等性。当然,您可以提供自己的,例如新增的MyOrderingClass。事实证明,Distinct方法使用基于哈希的集合来跟踪它已经看到和生成的元素,并且为此可以采用IEqualityComparer<T>的实例,它将其传递给这个内部集合。

既然我们已经研究了基本概念,让我们看看代码:

List<Student> list1 = new List<Student>();

// ... add instances with the same name and age

// option 1: use HashSet<T>
var set = new HashSet<Student>(list1, new MyOrderingClass());
Assert.Equal(1, set.Count);

// option 2: use Distinct
var distinct = list1.Distinct(new MyOrderingClass());
Assert.Equal(1, distinct.Count());

// option 3: when you don't have a proper set (e.g. .Net 2.0)
Dictionary<Student, Object> d = new Dictionary<Student, Object>(new MyOrderingClass());
list1.ForEach(delegate(Student e) { d[e] = null; });
Assert.Equal(1, d.Count);

".NET 2.0 中是否有“HashSet”和“Distinct”可用?" - Nazmul
不行。你需要使用其他实现,比如 Iesi 的 HashedSet(http://nuget.org/packages/Iesi.Collections),或者一个 Dictionary<K, V>,只使用键而不是值。其余部分相同。 - codekaizen
但说实话,我知道你们可能没有选择,但你们需要摆脱2.0! - codekaizen
我添加了“选项3”,以涵盖使用字典。 - codekaizen

2
在.NET 2.0中,您可以使用Dictionary<>类来获取不同的值。这可能是最有效的方法,利用现有功能。显然,在.NET 3.5及更高版本中,使用Distinct扩展方法。这里我添加了.NET 2.0实现。
public class Student : IEquatable<Student>
{
    private String name;
    private String age;
    public String Name
    {
        get { return name; }
        set { name = value; }
    }
    public String Age
    {
        get { return age; }
        set { age = value; }
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;

            hash = hash * 23 + ((Age == null) ? 0 : Age.GetHashCode());
            hash = hash * 23 + ((Name == null) ? 0 : Name.GetHashCode());

            return hash;
        }
    }

    public bool Equals(Student other)
    {
        if (other == null)
        {
            return false;
        }

        return Age == other.Age && Name == other.Name;
    }

    public override bool Equals(object obj)
    {
         if (obj == null) return false;
         if (obj.GetType() != typeof(Student)) return false;

         return Equals((Student)obj);
    }
}

Dictionary<Student, bool> dict = new Dictionary<Student, bool>();

foreach (Student student in list1)
{
    if (!dict.ContainsKey(student))
    {
        dict.Add(student, false);
    }
}

ICollection<Student> distinctList = dict.Keys;

非常感谢。GetHashCode() 的目的是什么? - Nazmul
@codekaizen,您是什么意思?实现完全相同。 - Kirill Polishchuk
@KirillPolishchuk - 试试这个: `Object s1 = new Student { Name = "abc", Age = "21" }; Object s2 = new Student { Name = "abc", Age = "21" };Assert.True(s1.Equals(s2));` - codekaizen
@KirillPolishchuk - 或许是这样,但有可能这段代码最终会被用于生产环境。因此,我正在尝试帮助那些最终使用它的复制粘贴者。 - codekaizen
为什么哈希值等于17,然后哈希值等于哈希值乘以23再加上((Age == null) ? 0 : Age.GetHashCode())?另外的数字,比如50、100等呢? - NoName
显示剩余6条评论

1
pmtamal提到:默认情况下,仅支持引用相等性。这可能会有用http://msdn.microsoft.com/en-US/library/bsc2ak47(v=vs.80).aspx
       public class Student
            {
                private String name;
                private String age;
                public String Name
                {
                    get { return name; }
                    set { name = value; }
                }
                public String Age
                {
                    get { return age; }
                    set { age = value; }
                }

                public override bool Equals(object obj)
                {
                    Student st = obj as Student;
                    if(st != null ){
                        return (this.name.Trim().ToLower().Equals(st.name.Trim().ToLower()) && this.age == st.age);
                    }
                    return base.Equals(obj);
                }

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


            }

你的等式没有考虑到学生的年龄。 - Ganesh R.
是的,我之前说过... "使用您的值"。我猜@Hoque会添加您需要说它们相等的条件。感谢您的观察,Ganesh R. :) - Mate
@Mate,是否有覆盖GetHashCode()的要求? - Nazmul
这段代码并没有什么实际作用...但是提到了codekaizen和GetHashCode,它是一种“快捷”方法,被一些数据结构用来判断实例是否可能相等。对于另一个对象结构也很有用。 - Mate

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接