Обработка коллекций в реализации GetHashCode
Я работаю над реализацией GetHashCode() на основе структуры HashCode в этом ответе здесь. Поскольку мой метод Equals будет рассматривать коллекции с использованием Enumerable.SequenceEqual(), мне нужно включить коллекции в мою реализацию GetHashCode().
В качестве отправной точки я использую встроенную реализацию GetHashCode() от Jon Skeet, чтобы проверить вывод реализации структуры HashCode. Это работает, как и ожидалось, используя следующий тест ниже -
private class MyObjectEmbeddedGetHashCode
{
public int x;
public string y;
public DateTimeOffset z;
public List<string> collection;
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + x.GetHashCode();
hash = hash * 31 + y.GetHashCode();
hash = hash * 31 + z.GetHashCode();
return hash;
}
}
}
private class MyObjectUsingHashCodeStruct
{
public int x;
public string y;
public DateTimeOffset z;
public List<string> collection;
public override int GetHashCode()
{
return HashCode.Start
.Hash(x)
.Hash(y)
.Hash(z);
}
}
[Test]
public void GetHashCode_CollectionExcluded()
{
DateTimeOffset now = DateTimeOffset.Now;
MyObjectEmbeddedGetHashCode a = new MyObjectEmbeddedGetHashCode()
{
x = 1,
y = "Fizz",
z = now,
collection = new List<string>()
{
"Foo",
"Bar",
"Baz"
}
};
MyObjectUsingHashCodeStruct b = new MyObjectUsingHashCodeStruct()
{
x = 1,
y = "Fizz",
z = now,
collection = new List<string>()
{
"Foo",
"Bar",
"Baz"
}
};
Console.WriteLine("MyObject::GetHashCode(): {0}", a.GetHashCode());
Console.WriteLine("MyObjectEx::GetHashCode(): {0}", b.GetHashCode());
Assert.AreEqual(a.GetHashCode(), b.GetHashCode());
}
Следующим шагом является рассмотрение коллекции в вычислении GetHashCode(). Это требует небольшого дополнения к реализации GetHashCode() в MyObjectEmbeddedGetHashCode.
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = hash * 31 + x.GetHashCode();
hash = hash * 31 + y.GetHashCode();
hash = hash * 31 + z.GetHashCode();
int collectionHash = 17;
foreach (var item in collection)
{
collectionHash = collectionHash * 31 + item.GetHashCode();
}
hash = hash * 31 + collectionHash;
return hash;
}
}
Однако это немного сложнее в структуре HashCode. В этом примере, когда коллекция типа List передается в метод Hash, T является List, поэтому попытка привести obj к ICollection или IEnumberable не работает. Я могу успешно привести к IEnumerable, но это вызывает бокс, и я обнаружил, что должен беспокоиться об исключении таких типов, как string, которые реализуют IEnumerable.
Есть ли способ надежно привести obj к ICollection или IEnumerable в этом сценарии?
public struct HashCode
{
private readonly int hashCode;
public HashCode(int hashCode)
{
this.hashCode = hashCode;
}
public static HashCode Start
{
get { return new HashCode(17); }
}
public static implicit operator int(HashCode hashCode)
{
return hashCode.GetHashCode();
}
public HashCode Hash<T>(T obj)
{
// I am able to detect if obj implements one of the lower level
// collection interfaces. However, I am not able to cast obj to
// one of them since T in this case is defined as List<string>,
// so using as to cast obj to ICollection<T> or IEnumberable<T>
// doesn't work.
var isGenericICollection = obj.GetType().GetInterfaces().Any(
x => x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(ICollection<>));
var c = EqualityComparer<T>.Default;
// This works but using IEnumerable causes boxing.
// var h = c.Equals(obj, default(T)) ? 0 : ( !(obj is string) && (obj is IEnumerable) ? GetCollectionHashCode(obj as IEnumerable) : obj.GetHashCode());
var h = c.Equals(obj, default(T)) ? 0 : obj.GetHashCode();
unchecked { h += this.hashCode * 31; }
return new HashCode(h);
}
public override int GetHashCode()
{
return this.hashCode;
}
}
1 ответ
Вы можете решить проблему с коллекцией несколькими способами:
- Используйте неуниверсальный интерфейс, например
ICollection
или жеIEnumerable
, - Добавьте перегрузку для
Hash()
метод, напримерHash<T>(IEnumerable<T> list) { ... }
Тем не менее, ИМХО было бы лучше просто оставить struct HashCode
в одиночку и поместите специфичный для коллекции код в ваш фактический GetHashCode()
метод. Например:
public override int GetHashCode()
{
HashCode hash = HashCode.Start
.Hash(x)
.Hash(y)
.Hash(z);
foreach (var item in collection)
{
hash = hash.Hash(item);
}
return hash;
}
Если вы хотите полнофункциональную версию struct HashCode
типа, мне кажется, что на той же странице, на которую вы ссылались, есть одна: /questions/18495027/kakoj-luchshij-algoritm-dlya-pereopredelennogo-systemobjectgethashcode/18495049#18495049
Имена членов разные, но это в основном та же идея, что и struct HashCode
тип, но с перегрузками для других сложных типов (как в моем предложении № 2 выше). Вы можете использовать это, или просто применить методы для реализации struct HashCode
, сохраняя соглашения об именах, используемые в нем.