2015-02-16 3 views
2

Я работаю над внедрением GetHashCode() на основе структуры HashCode в этом ответе here. Поскольку мой метод Equals рассмотрит коллекции, используя Enumerable.SequenceEqual(), мне нужно включить коллекции в мою реализацию GetHashCode().Обработка коллекций в реализации GetHashCode

В качестве отправной точки я использую встроенную реализацию GetHashCode() для внедрения результатов реализации структуры 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, но он вызывает бокс, и я обнаружил, что мне нужно беспокоиться об исключении таких типов, как строка, реализующая 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

Боковое примечание: ожидается, что 'GetHashCode' будет быстрым, отражение и итерация по всем элементам - не лучшие примеры« быстрого »кода. –

+0

Какая проблема вы пытаетесь решить? Вам не нужно включать коллекцию в хэш. Если XYZ даст вам хорошее хеш-распределение, то остановитесь там. – Paparazzi

+0

@AlexeiLevenkov Я поставил секундомер вокруг вызова GetHashCode с запросом LINQ и без него, и он запускает 5 мс с ним и без 3 мс, поэтому есть влияние производительности, которое необходимо учитывать. – brdmllr

ответ

1

Вы можете решить проблему сбора в несколькими способами:

  1. использовать не стандартный интерфейс, например, ICollection или IEnumerable.
  2. Добавить перегрузку для 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 типа, он смотрит на меня, как будто та же страница, которую ссылается имеет один: https://stackoverflow.com/a/2575444/3538012

Именование членов разные, но это в основном та же идея, что и тип struct HashCode, но с перегрузками для других сложных типов (как в моем предложении # 2 выше). Вы можете использовать это или просто применять методы там к вашей реализации struct HashCode, сохраняя соглашения об именах, используемые в нем.

+0

Я отметил это как ответ, потому что добавление перегрузки для метода Хэш обеспечило желаемое поведение. Однако я столкнулся с проблемой бесконечной рекурсии, которая привела к исключению StackOverflowException в моем переопределении Equals() из-за двунаправленной связи между двумя моими классами.Это заставило меня отказаться от коллекций и моих ссылочных типов в моей реализации IEquatable, и я в конечном итоге использовал решение, аналогичное тому, что @Blam упоминал в комментарии к моему вопросу. – brdmllr