C#中Dictionary的GetHashCode和Equals实现

3
我来到这个网站搜索关于字典中对象比较的内容,我了解到在C#中重写GetHashCode和Equals是必须的。 这是一段我一直在尝试解决的代码,使用FOREACH迭代方法。但我的老板说要避免使用任何迭代方法(也许使用containskey或containsvalue方法)以避免性能问题。非常欢迎任何帮助。
  public class employee
    {
        public string empname { get; set; }
        public string location { get; set; }
        public double kinid { get; set; }
        public double managerKin { get; set; }
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }

    public class manager
    {
        public string managername { get; set; }
        public double kinid { get; set; }

        public override int GetHashCode() 
        { 
          return 17 * managername.GetHashCode() + kinid.GetHashCode();
        }
    }
    public class program
    {
        public static void Main()
        {
            employee emp = new employee();
            employee emp2 = new employee();
            manager mng = new manager();
            manager mng2 = new manager();

            emp.empname = "Deepak";
            emp.location = "Pune";
            emp.kinid = 36885;
            emp.managerKin = 007;


            emp2.empname = "Astha";
            emp2.location = "Pune";
            emp2.kinid = 30000;
            emp2.managerKin = 007;

            mng.kinid = 007;
            mng.managername = "Gaurav";
            mng2.kinid = 001;
            mng2.managername = "Surya";

            Dictionary<employee, manager> relations = new Dictionary<employee, manager>();
            relations.Add(emp, mng);
            relations.Add(emp2, mng2);

            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("The Manager details are :");
            foreach (var element in relations)
            Console.WriteLine(" \n KINID : {0} \n  Manager'sName :                    {1}",element.Value.kinid, element.Value.managername);
            Console.WriteLine("Enter the details of the manager..");
            Console.ForegroundColor = ConsoleColor.Gray;
            Console.Write("\nManager's Kin : ");
            double mkin = Convert.ToDouble(Console.ReadLine());

            Console.Write("Manager's Name : ");
            string mname = Console.ReadLine();

            manager mng1 = new manager();
            mng1.kinid = mkin;
            mng1.managername = mname;
            int hashvalue = 17 * mname.GetHashCode() + mkin.GetHashCode();



            #region BY USING FOREACH LOOP
            int i = 0;
            foreach (var element in relations)
            {
                if (element.Value.GetHashCode() == hashvalue)
                {
                    i += 1;
                    if (i == 1)
                    {
                        Console.WriteLine("The Following employees report to the Manager : {0}", mname);

                    }
                    Console.WriteLine(element.Key.empname + " " + element.Key.kinid + " " + element.Key.location + " " + element.Key.managerKin);

                }
            }
            if (i == 0)
            {
                Console.WriteLine("sorry the manager's details you entered \"{0}\" \"{1}\" does not exist in our database..", mng1.managername, mng1.kinid);

            }
            #endregion

            Console.ReadLine();
        }

    }

-1,你的样本非常长,包含了太多无关的细节。而且不清楚你的问题是什么——因为你已经说过字典适用于按键搜索,所以就这么做吧... - Alexei Levenkov
alexei levenkov:你可以在调试器上运行这个示例代码。 - LetsKickSomeAss in .net
@AlexeiLevenkov 这个问题是关于按值搜索的... - user166390
5个回答

3

使用ContainsKey或ContainsValue关键字在字典中搜索对象时,编译器会使用两个隐式函数:GetHashCode()和Equals()。因此,当我们有一个用于比较的对象时,我们需要重写这两个方法!

以下是代码示例:

#region USING DICTIONARY TO STORE CLASS OBJECTS (check employee existence and print manager's name)
public class employee
{
    public string empname { get; set; }
    public string location { get; set; }
    public double kinid { get; set; }
    public double managerKin { get; set; }

    //public override bool Equals(object obj) // ANY OF THE TWO EQUALS METHOD WORKS.
    //{
    //    employee otheremployee;
    //    otheremployee = (employee)obj;
    //    return (otheremployee.kinid == this.kinid && otheremployee.location == this.location && otheremployee.empname == this.empname && otheremployee.managerKin == this.managerKin);

    //}
    public override bool Equals(object obj)   //When Running this entire code, put a break-point on both the Equals() and GetHashCode() methods, and see the execution flow.
    {
        employee otheremployee;
        otheremployee = (employee)obj;
        return (obj.GetHashCode() == otheremployee.GetHashCode());

    }
    public override int GetHashCode()    //When Running this entire code, put a break-point on both the Equals() and GetHashCode() methods, and see the execution flow.
    {
        //int temp = base.GetHashCode(); // DONT USE THIS
        //return base.GetHashCode();
        int temp = empname.GetHashCode() + location.GetHashCode() + kinid.GetHashCode() + managerKin.GetHashCode();
        return temp;
    }
}

public class manager
{
    public string managername { get; set; }
    public double kinid { get; set; }


   
    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
    public override bool Equals(object obj)
    {
        return base.Equals(obj);
    }
}
public class program
{
    public static void Main()
    {
        employee emp = new employee();
        employee emp2 = new employee();
        manager mng = new manager();
        manager mng2 = new manager();

        emp.empname = "Deepak";
        emp.location = "Pune";
        emp.kinid = 36885;
        emp.managerKin = 007;


        emp2.empname = "Astha";
        emp2.location = "Pune";
        emp2.kinid = 30000;
        emp2.managerKin = 001;

        mng.kinid = 007;
        mng.managername = "Gaurav";
        mng2.kinid = 001;
        mng2.managername = "Surya";

        Dictionary<employee, manager> relations = new Dictionary<employee, manager>();
        relations.Add(emp, mng); // put a BreakPoint here and see the execution flow
        relations.Add(emp2, mng2);// put a BreakPoint here and see the execution flow

        Console.ForegroundColor = ConsoleColor.Yellow;
        Console.WriteLine("The Employee details are :");
        foreach (var element in relations)
            Console.WriteLine(" \n Employee Name : {0} \n Location : {1} \n Employee KinId : {2} \n Manager's KinId : {3} ",
                element.Key.empname, element.Key.location, element.Key.kinid, element.Key.managerKin);

        Console.WriteLine("Enter the details of the Employee..");
        Console.ForegroundColor = ConsoleColor.Gray;
        Console.Write("\nEmployee Name : "); string ename = Console.ReadLine();
        Console.Write("Location : "); string elocn = Console.ReadLine();
        Console.Write("Employee KinId : "); double ekinid = Convert.ToDouble(Console.ReadLine());
        Console.Write("Manager's ID : "); double emngr = Convert.ToDouble(Console.ReadLine());
        employee emp1 = new employee();
        emp1.empname = ename;
        emp1.location = elocn;
        emp1.kinid = ekinid;
        emp1.managerKin = emngr;


        int i = 0; // This variable acts as a indicator to find whether the Employee Key exists or not.
        if (relations.ContainsKey(emp1)) //Put a break point here and see the execution flow.
        {
            Console.WriteLine("the Employee : {0} exists..", emp1.empname);
            Console.WriteLine("the Employee reports to the following manager : {0} \n and the Manager's KinId is {1}.", (relations[emp1]).managername, relations[emp1].kinid);
            i = 1;
            Console.ReadLine();
        }

        if (i == 0)
        {
            Console.WriteLine("the details of the employee named {0} does not exist !!", emp1.empname);
            Console.ReadLine();
        }

#endregion

1
两个员工具有不同的empname和位置,但可能会拥有相同的GetHashCode。这被称为冲突。当你说它们相等时,完全是错误的。虽然这种情况不经常发生,程序似乎可以正常工作,但实际上存在一个潜在的错误。 - Antonín Lejsek
将GetHashCode()返回的多个哈希值进行组合最好使用异或运算以更好地混淆结果。此外,在执行异或后,每个结果左移或右移可以产生更好的混淆效果。 int temp = empname.GetHashCode() + location.GetHashCode() + kinid.GetHashCode() + managerKin.GetHashCode();替换为: int temp = empname.GetHashCode() ^ location.GetHashCode() ^ kinid.GetHashCode() ^ managerKin.GetHashCode(); - user5747799

1

要在字典中搜索元素,您可以使用ContainsKey、ContainsValue方法或者只需编写LINQ查询

var dict = (from pair in relations
where pair.Value.Equals(mng1)
select pair).ToDictionary<employee,manager>();

这可能会导致异常。很可能有多个员工拥有相同的经理,而ToDictionary不支持重复键。使用GroupBy然后ToDictionary可以解决问题。哦,嗯,我错过了...我以为你正在创建一个反向映射。把它放回字典看起来有点傻。 - user166390
如果有一个Dictionary<manager, employee>,你是正确的,但这里是相反的情况。 - Maria Topchian

1

Dictionary.ContainsKey(employee) 在这里不管用,因为员工是“未知”的值,而 Contains 也无法帮助,因为它需要一个 KeyValuePair<employee,manager>,但...再一次...没有已知的员工。 ContainsValue(manager) 不会有所帮助,因为它不返回任何键 并且 因为它不是一个键,它是一个 O(n) 操作,而不是像 ContainsKey 这样的 O(1) 操作!

当前 结构下,唯一的方法是使用某种形式的循环,尽管我会像这样写:

// Key is Employee, Value is Manager
// This is O(n)
var theEmployees = relations
  .Where(rel => rel.Value.Equals(theManager))
  .Select(rel => rel.Key);

manager获得有效的Equals实现之后,这才起作用。请注意,哈希码根本没有使用。(因为不同的对象可能共享相同的哈希码,仅比较哈希码不能替代Equals,或==,或CompareTo!-- 这取决于哪个是适当的。)

如果有许多这样的查询,则可以“倒置”初始结构。

// Build a reverse lookup-up
var employeesForManager = relations
  .GroupBy(rel => rel.Value)            // group on Manager
  .ToDictionary(g => g.Key, g => g);    // Key is the group's Manager

// This is O(1), but only valid AFTER employeesForManager is [re-]generated
var theEmployees = employeesForManager[theManager]

只有在manager具有有效的EqualsGetHashCode实现时,此方法才能正常工作。(因为manager对象被用作新字典的键,所以需要GetHashCode。)

至于哪个更好——这取决于情况。例如,创建反向查找仅使用一次是愚蠢的。除非出现性能问题,否则没有性能问题:编写干净的代码并进行分析。

祝编码愉快。


事实上,我已经纠正了这个问题。而且我正确地使用了containskey方法。你所需要做的就是在你的键类中重写gethashcode方法和equals方法。我很快会发布更正后的代码。祝编码愉快。 - LetsKickSomeAss in .net
@LetsKickSomeAssin.net 好耶 :) - user166390

1
为了能够比较两个实例的相等性,你应该重写Equals方法,并且最好实现IEquatable接口。当你重写Equals时,你也应该重写GetHashcode(这在将实例放入字典中计算桶时使用)。
你不应该自己使用GetHashCode来比较对象的两个实例是否相等;相反,你应该使用Equals(或者使用EqualityComparer,它也会使用Equals方法)。
如果你已经很好地实现了GetHashCode和Equals,那么你就能够通过执行以下操作来确定字典是否包含特定实例:
var myDictionary<int, Manager> = new Dictionary<int,Manager>();

myDictionary.ContainsKey (someKey)

或者

var mySet = new HashSet<Manager>();
mySet.Contains(someManagerObject);

这是一个新想法,我也会尝试一下。 - LetsKickSomeAss in .net

0
我相信你最后的回应中有个bug。
这一行
return (obj.GetHashCode() == otheremployee.GetHashCode());
可能应该是
return (this.GetHashCode() == otheremployee.GetHashCode());
这样你就可以比较这个对象和另一个对象的哈希码了。按照你响应中的写法,你似乎在将另一个对象与其本身进行比较。

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