Использование поля объекта в качестве общего ключа словаря

Если я хочу использовать объекты в качестве ключей для Dictionary, какие методы мне нужно переопределить, чтобы сравнить их определенным образом?

Скажем, у меня есть класс, который имеет свойства:

class Foo {
    public string Name { get; set; }
    public int FooID { get; set; }

    // elided
} 

И я хочу создать:

Dictionary<Foo, List<Stuff>>

Я хочу, чтобы объекты Foo с тем же FooID считались одной и той же группой. Какие методы мне нужно переопределить в классе Foo?

Подводя итог: я хочу классифицировать объекты Stuff в списки, сгруппированные по объектам Foo. Stuff объекты будут иметь FooID, чтобы связать их с их категорией.

Ответ 1

По умолчанию два важных метода: GetHashCode() и Equals(). Важно, что если две вещи равны (Equals() возвращает true), они имеют одинаковый хеш-код. Например, вы можете "вернуть FooID"; как GetHashCode(), если вы хотите, чтобы это соответствовало. Вы также можете реализовать IEquatable<Foo>, но это необязательно:

class Foo : IEquatable<Foo> {
    public string Name { get; set;}
    public int FooID {get; set;}

    public override int GetHashCode() {
        return FooID;
    }
    public override bool Equals(object obj) {
        return Equals(obj as Foo);
    }
    public bool Equals(Foo obj) {
        return obj != null && obj.FooID == this.FooID;
    }
}

Наконец, еще одна альтернатива - предоставить IEqualityComparer<T> сделать то же самое.

Ответ 2

Как вы хотите, чтобы FooID был идентификатором для группы, вы должны использовать это как ключ в словаре вместо объекта Foo:

Dictionary<int, List<Stuff>>

Если вы будете использовать объект Foo как ключ, вы просто реализуете метод GetHashCode и Equals, чтобы рассматривать только свойство FooID. Свойство Name было бы просто мертвым весом до Dictionary, поэтому вы просто использовали бы Foo в качестве обертки для int.

Поэтому лучше использовать значение FooID напрямую, а затем вам не нужно ничего реализовывать, поскольку Dictionary уже поддерживает использование int в качестве ключа.

Edit:
Если вы все равно хотите использовать класс Foo как ключ, IEqualityComparer<Foo> легко реализовать:

public class FooEqualityComparer : IEqualityComparer<Foo> {
   public int GetHashCode(Foo foo) { return foo.FooID.GetHashCode(); }
   public bool Equals(Foo foo1, Foo foo2) { return foo1.FooID == foo2.FooID; }
}

Использование:

Dictionary<Foo, List<Stuff>> dict = new Dictionary<Foo, List<Stuff>>(new FooEqualityComparer());

Ответ 3

Для Foo вам необходимо переопределить object.GetHashCode() и object.Equals()

Словарь вызовет GetHashCode() для вычисления хэш-ведра для каждого значения и Equals для сравнения того, являются ли два Foo одинаковыми.

Не забудьте рассчитать хорошие хэш-коды (избегайте многих одинаковых объектов Foo с одинаковым хэш-кодом), но убедитесь, что два равных Foos имеют одинаковый хеш-код. Вы можете начать с Equals-Method, а затем (в GetHashCode()) xor хэш-код каждого члена, который вы сравниваете в Equals.

public class Foo { 
     public string A;
     public string B;

     override bool Equals(object other) {
          var otherFoo = other as Foo;
          if (otherFoo == null)
             return false;
          return A==otherFoo.A && B ==otherFoo.B;
     }

     override int GetHashCode() {
          return 17 * A.GetHashCode() + B.GetHashCode();
     }
}

Ответ 4

Что насчет класса Hashtable!

Hashtable oMyDic = new Hashtable();
Object oAnyKeyObject = null;
Object oAnyValueObject = null;
oMyDic.Add(oAnyKeyObject, oAnyValueObject);
foreach (DictionaryEntry de in oMyDic)
{
   // Do your job
}

В приведенном выше виде вы можете использовать любой объект (объект класса) в качестве общего ключа словаря:)

Ответ 5

У меня была та же проблема. Теперь я могу использовать любой объект, который я пытался использовать в качестве ключа из-за переопределения Equals и GetHashCode.

Вот класс, который я построил с помощью методов для использования внутри переопределений Equals (объект obj) и GetHashCode(). Я решил использовать generics и алгоритм хэширования, который должен быть способен охватить большинство объектов. Пожалуйста, дайте мне знать, если вы видите что-то здесь, что не работает для некоторых типов объектов, и у вас есть способ его улучшить.

public class Equality<T>
{
    public int GetHashCode(T classInstance)
    {
        List<FieldInfo> fields = GetFields();

        unchecked
        {
            int hash = 17;

            foreach (FieldInfo field in fields)
            {
                hash = hash * 397 + field.GetValue(classInstance).GetHashCode();
            }
            return hash;
        }
    }

    public bool Equals(T classInstance, object obj)
    {
        if (ReferenceEquals(null, obj))
        {
            return false;
        }
        if (ReferenceEquals(this, obj))
        {
            return true;
        }
        if (classInstance.GetType() != obj.GetType())
        {
            return false;
        }

        return Equals(classInstance, (T)obj);
    }

    private bool Equals(T classInstance, T otherInstance)
    {
        List<FieldInfo> fields = GetFields();

        foreach (var field in fields)
        {
            if (!field.GetValue(classInstance).Equals(field.GetValue(otherInstance)))
            {
                return false;
            }
        }

        return true;
    }

    private List<FieldInfo> GetFields()
    {
        Type myType = typeof(T);

        List<FieldInfo> fields = myType.GetTypeInfo().DeclaredFields.ToList();
        return fields;
    }
}

Вот как он используется в классе:

public override bool Equals(object obj)
    {
        return new Equality<ClassName>().Equals(this, obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return new Equality<ClassName>().GetHashCode(this);
        }
    }