Linq的distinct方法不会调用Equals方法。

8
我有以下类。
public class ModInfo : IEquatable<ModInfo>
{
    public int ID { get; set; }
    public string MD5 { get; set; }

    public bool Equals(ModInfo other)
    {
        return other.MD5.Equals(MD5);
    }

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

我使用像这样的方法将一些数据加载到该类的列表中:

public void ReloadEverything() {
    var beforeSort = new List<ModInfo>();
    // Bunch of loading from local sqlite database. 
    // not included since it's reload boring to look at
    var modinfo = beforeSort.OrderBy(m => m.ID).AsEnumerable().Distinct().ToList();
}

问题在于Distinct()方法似乎没有起到它的作用。仍然存在相互等同的对象。
根据这篇文章:https://msdn.microsoft.com/en-us/library/vstudio/bb348436%28v=vs.100%29.aspx,你应该这样做才能使distinct方法起作用,但是它似乎没有调用ModInfo对象上的Equals方法。是什么原因导致了这种情况?
示例值:
modinfo[0]: id=2069, MD5 =0AAEBF5D2937BDF78CB65807C0DC047C
modinfo[1]: id=2208, MD5 = 0AAEBF5D2937BDF78CB65807C0DC047C

我不在乎选择哪个值,因为它们很可能是一样的,因为md5值相同。


尝试在GetHashCode()中返回0而不是返回MD5.GetHashCode(); - bit
1
这段代码对我有效。你能否提供2个有问题的值并指出你想保留哪个值? - Amir Popovich
modinfo[0]: id=2069,MD5 = 0AAEBF5D2937BDF78CB65807C0DC047C modinfo[1]: id=2208,MD5 = 0AAEBF5D2937BDF78CB65807C0DC047C 如果 MD5 值相同,我不关心选择哪个对象,因为其余的对象也是相同的。 - Rasmus Hansen
对我来说仍然有效。它保留的是id = 2069的一个。 - Amir Popovich
@RasmusHansen - 请检查您的数据库字符串是否已修剪。我猜这就是问题所在。 - Amir Popovich
我知道它们相等,因为我可以执行modinfo.Where(m => m.MD5.Equals(paramMd5)),然后我会得到两个值。 - Rasmus Hansen
3个回答

10

你还需要覆盖 Object.Equals,而不仅仅是实现 IEquatable

如果你将以下内容添加到你的类中:

public override bool Equals(object other)
{
    ModInfo mod = other as ModInfo;
    if (mod != null)
        return Equals(mod);
    return false;
}

应该可以工作。

有关更多信息,请参阅此文章:正确实现 IEquatable

编辑:好的,这是一种稍微不同的实现方式,基于 GetHashCode 的最佳实践。

public class ModInfo : IEquatable<ModInfo>
{
    public int ID { get; set; }
    public string MD5 { get; set; }

    public bool Equals(ModInfo other)
    {
        if (other == null) return false;
        return (this.MD5.Equals(other.MD5));
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 13;
            hash = (hash * 7) + MD5.GetHashCode();
            return hash;
        }
    }

    public override bool Equals(object obj)
    {
        ModInfo other = obj as ModInfo;
        if (other != null)
        {
            return Equals(other);
        }
        else
        {
            return false;
        }
    }
}

您可以进行验证:

ModInfo mod1 = new ModInfo {ID = 1, MD5 = "0AAEBF5D2937BDF78CB65807C0DC047C"};
ModInfo mod2 = new ModInfo {ID = 2, MD5 = "0AAEBF5D2937BDF78CB65807C0DC047C"};

// You should get true here
bool areEqual = mod1.Equals(mod2);

List<ModInfo> mods = new List<ModInfo> {mod1, mod2};

// You should get 1 result here
mods = mods.Distinct().ToList();

C#中的GetHashCode是什么?为什么要使用具体的数字?


这只会比较引用,而不是对象的实际值,对吗? - Rasmus Hansen
不会,除非ModInfo.Equals(ModInfo other)的实现是这样的。 - Jurgen Camilleri
我提供了一个更完整的例子,你能告诉我它是否有效吗? - Fred Kleuver
我在本地运行了你的代码,并发现总共有496个ModInfo对象从你的数据库中出来。然后,我输出了它们所有的MD5属性值到一个Excel文件中并检查了Excel中是否有重复值,但没有找到。这是Excel的链接:http://s000.tinyupload.com/?file_id=06870272569563360623 ,所以如果MD5是你用来确定唯一性的标准,那么它们就从一开始就是独一无二的。你确定MD5是正确的检查值吗? - Fred Kleuver
在进行where检查时计算对象数量实际上显示出有些对象是在我从数据库读取信息后添加的,其中包括重复的MD5。所以现在我只需要停止这个过程,一切就会变得很好。 - Rasmus Hansen
显示剩余7条评论

0

那只会比较引用,而不是对象的实际值,对吧?此外,我不关心ID(以及对象上的其他一堆值),我只关心md5是否相等。 - Rasmus Hansen
不,它将调用public bool Equals(ModInfo other)方法,因为other被转换为ModInfo。另外,修改如下以处理“other”为空的情况: public bool Equals(ModInfo other) { if(other == null){return false;} return other.MD5.Equals(MD5); } - Radin Gospodinov

0
你还可以使用IEqualityComparer。
.Distinct(new ModInfoComparer())

链接到Microsoft docs

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