如何使用IEqualityComparer和LinQ Distinct从集合中删除重复项

38
我无法从集合中删除重复项,我已经为Employee类实现了IEqualityComparer,但是我仍然无法获得输出。
static void Main(string[] args)
    {
        List<Employe> Employeecollection = new List<Employe>();

        Employeecollection.Add(new Employe("abc","def"));
        Employeecollection.Add(new Employe("lmn","def"));
        Employeecollection.Add(new Employe("abc", "def"));

        IEnumerable<Employe> coll = Employeecollection.Distinct(new Employe());

        foreach (Employe item in coll)
        {
            Console.WriteLine(item.fName + "   " + item.lName );
        }

    }

以下是Employee类的实现,我在这里实现了IEqualityComparer。

class Employe : IEqualityComparer<Employe>
{
    public string fName { get; set; }
    public string lName { get; set; }

    public Employe()
    {

    }

    public Employe(string firstName, string LastName)
    {
        this.fName = firstName;
        this.lName = LastName;
    }

    #region IEqualityComparer<pcf> Members

    public bool Equals(Employe x, Employe y)
    {
        if (x.fName == y.fName && x.lName == y.lName)
        {
            return true;
        }

        return false;
    }

    public int GetHashCode(Employe obj)
    {
        return obj.GetHashCode();
    }

    #endregion
}
6个回答

105

忘记IEqualityComparer,直接使用Linq:

EmployeeCollection.GroupBy(x => new{x.fName, x.lName}).Select(g => g.First());

你能否解释一下上面的代码?我知道GroupBy是什么,但Select(g => g.First())是什么意思? - Gun
6
GroupBy操作将返回一个IEnumerable [IGrouping]的集合(也是IEnumerable)。在您的示例中,外部IEnumerable将有两个项目:一个IGrouping有两个条目对应"abc","def",另一个IGrouping有一个条目对应"lmn","def"。First()操作符将从内部IGrouping IEnumerable中取出第一个项目。 - avanek
对于具有多个属性的逻辑,您必须按所有属性进行分组,这将比异或比较运行得更慢。 - Nick
@NikolaMitev:使用XOR计算哈希值时,可能会在枚举等值域小的值上发生冲突。虽然冲突不会影响哈希表操作的正确性,但会使其变得更加昂贵。C#编译器会自动为匿名对象编写更好的GetHashCode实现,这样做相对于冲突较少。我认为,虽然使用XOR可能计算成本较低,但更好的实现方式也很轻量级,将有超越仅计算哈希码的好处。 - spender
@NikolaMitev:只有在哈希集合中发生冲突时,属性的实际比较才会发生。GroupBy 仍然以与 Distinct 相同的方式使用哈希。我认为你上面的评论是误导性的。 - spender
@spender,那么你的代码只会返回具有唯一的名字和姓氏组合的员工吗?(即:1个“ABC DEF”和1个“LMN DEF”?) - Eliezer

5

您需要在Employee类中重写GetHashCode方法,但您没有这样做。下面是一个良好的哈希方法示例(由ReSharper生成):

public override int GetHashCode()
{
    return ((this.fName != null ? this.fName.GetHashCode() : 0) * 397) ^ (this.lName != null ? this.lName.GetHashCode() : 0);
}

现在调用 Distinct 后,foreach循环会打印:

abc   def
lmn   def

在您的情况下,您正在调用对象的类GetHashCode,它对内部字段一无所知。
一个简单的提示,MoreLINQ包含DistinctBy扩展方法,允许您执行以下操作:
IEnumerable<Employe> coll = 
 Employeecollection.DistinctBy(employee => new {employee.fName, employee.lName});

匿名对象在GetHashCodeEquals方法的实现方面都是正确的。


5
这是一个不错的教程,请点击链接
    public int GetHashCode(Employe obj)
    {
        return obj.fname.GetHashCode() ^ obj.lname.GetHashCode();
    }

4
如果你要实现GetHashCode方法,就必须同时实现相应的相等性成员。GetHashCode方法也需要被覆盖重写。另外需要记住的是,“在数据结构中存在冗余时,异或运算符可能会导致或加剧分布问题。” 参考链接:http://blogs.msdn.com/b/ericlippert/archive/2011/02/28/guidelines-and-rules-for-gethashcode.aspx - spender

3
哈希码实现不正确:
public override int GetHashCode()
{
    return 13 * fName.GetHashCode() + 7 * lName.GetHashCode();
}

1
当对象的字段为空时,在GetHashCode中收到NullReferenceException是否可以接受? - Ilya Ivanov
这绝对不是 - 我只是想澄清“当相等时哈希码应该相同”的事实,而不是专注于空值检查。但是,你是对的。 - aquaraga

0

看起来你是在比较引用而不是内容,因此比较函数无法工作。

将其更改为使用.Equals()而不是==,它应该可以工作。 以下是示例:

#region IEqualityComparer<pcf> Members

public bool Equals(Employe x, Employe y)
{
    if (x.fName.Equals(y.fName) && x.lName.Equals(y.lName))
    {
        return true;
    }

    return false;
}

public int GetHashCode(Employe obj)
{
    return obj.GetHashCode();
}

#endregion

2
在C#中,“==”运算符对字符串的处理是没有问题的。问题出在GetHashCode()方法的实现上。 - Fredrik

-1
public int GetHashCode(Employe obj)
{
    return obj.GetHashCode();
}

对于这个方法,返回你要比较相等的属性的哈希码,而不是对象本身。比较对象的哈希码将始终为false,因此您的列表永远不会被过滤重复项。


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