2010-01-27 2 views
4

Я создаю ключ для словаря, который представляет собой структуру из двух строк. Когда я тестирую этот метод в консольном приложении, он работает, но я не уверен, что единственная причина, по которой он работает, заключается в том, что строки интернированы и поэтому имеют одинаковые ссылки.Зависит ли этот код от интернирования строк?

Foo foo1 = new Foo(); 
Foo foo2 = new Foo(); 
foo1.Key1 = "abc"; 
foo2.Key1 = "abc"; 
foo1.Key2 = "def"; 
foo2.Key2 = "def"; 

Dictionary<Foo, string> bar = new Dictionary<Foo, string>(); 
bar.Add(foo1, "found"); 

if(bar.ContainsKey(foo2)) 
    System.Console.WriteLine("This works."); 
else 
    System.Console.WriteLine("Does not work"); 

структура просто:

public struct Foo 
{ 
    public string Key1; 
    public string Key2; 
} 

Есть ли случаи, которые вызывали бы это потерпеть неудачу или я хорошо полагаться на это как уникальный ключ?

ответ

4

Согласно документации Microsoft, вы всегда должны переопределять GetHashCode(), если вы намерены использовать свои собственные структуры данных в качестве ключей в HashTables, иначе вы можете быть небезопасными.

«Объекты, используемые в качестве ключа в объекте Hashtable, также должны переопределять метод GetHashCode, поскольку эти объекты должны генерировать собственный хэш-код».

http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx

+0

Другая цитата из документации GetHashCode: реализация GetHashCode по умолчанию не гарантирует уникальность или согласованность; поэтому он не должен использоваться как уникальный идентификатор объекта для целей хэширования. –

+0

Да, верно для большинства типов. Для строк он говорит, что он правильно реализован, поэтому вы можете положиться на это. – rui

+0

Кажется, что строки работают как есть, а потому, что нет никакой явной гарантии для этого * всегда * в том случае, если я переопределяю GetHashCode и Equals. Спасибо всем за советы! –

1

Согласно MSDN,

Реализация по умолчанию метода GetHashCode не гарантирует уникальные возвращаемые значения для различных объектов. [...]

Метод GetHashCode может быть переопределен производным типом. Типы значений должны переопределять этот метод, чтобы обеспечить хеш-функцию, подходящую для этого типа, и обеспечить полезное распределение в хэш-таблице.

(курсив мой.)

Так что ваш код не гарантирует работу, и если он работает, это не гарантировано продолжать работать в следующей версии платформы .NET.

Для чего это стоит, текущая реализация Mono о ValueType.GetHashCode называет GetHashCode методы членов типа в поэтому код будет на самом деле работает корректно независимо от строки интернирование.

[простите за предыдущий ответ, это просто неправильно.]

3

http://msdn.microsoft.com/en-us/library/dd183755.aspx

Любой структура, что вы определяете уже имеет реализацию по умолчанию значения равенства, наследуемые из системы. . ::. ValueType переопределяет метод Object .. ::. Equals (Object). Эта реализация использует отражение для изучения всех открытых и непубличных полей и свойств в типе. Хотя эта реализация дает правильные результаты, она относительно медленная по сравнению с пользовательской реализацией, которую вы пишете специально для этого типа.

Так что, если свойства равны, они равны. Однако вы можете переопределить Equals.

+0

Но поскольку «GetHashCode» не дает такой гарантии, я не уверен, что мы можем заключить, что она будет работать. Фактически MSDN явно заявляет, что он * не * гарантированно работает. –

+1

мы можем, потому что, поскольку ruibm прокомментировал свой собственный ответ, мы имеем дело со строками здесь. однако вы правы, что он должен, вероятно, переопределить Equals и GetHashCode, потому что реализация по умолчанию использует отражение. – hackerhasid

+0

Ничего себе, не знал этого. Спасибо ^^ –

4

В соответствии с моим комментарием к rubim «s answer выше, вот ссылка на то, как я думаю, что сама структура должна быть реализована. Обратите внимание на неизменяемые поля, которые могут быть инициализированы только конструктором.

public struct Foo 
{ 
    private readonly string key1; 

    private readonly string key2; 

    public string Key1 
    { 
     get 
     { 
      return this.key1; 
     } 
    } 

    public string Key2 
    { 
     get 
     { 
      return this.key2; 
     } 
    } 

    public Foo(string key1, string key2) 
    { 
     this.key1 = key1; 
     this.key2 = key2; 
    } 

    public static bool operator ==(Foo foo1, Foo foo2) 
    { 
     return foo1.Equals(foo2); 
    } 

    public static bool operator !=(Foo foo1, Foo foo2) 
    { 
     return !(foo1 == foo2); 
    } 

    public override bool Equals(object obj) 
    { 
     if (!(obj is Foo)) 
     { 
      return false; 
     } 

     Foo foo = (Foo)obj; 
     bool key1Equal = ((this.key1 == null) && (foo.Key1 == null)) 
      || ((this.key1 != null) && this.key1.Equals(foo.Key1)); 
     bool key2Equal = ((this.key2 == null) && (foo.Key2 == null)) 
      || ((this.key2 != null) && this.key2.Equals(foo.Key2)); 

     return key1Equal && key2Equal; 
    } 

    public override int GetHashCode() 
    { 
     unchecked 
     { 
      int hash = 17; 

      hash = (23 * hash) 
       + (this.key1 == null ? 0 : this.key1.GetHashCode()); 
      return (31 * hash) 
       + (this.key2 == null ? 0 : this.key2.GetHashCode()); 
     } 
    } 

    public override string ToString() 
    { 
     return (this.key1 == null ? string.Empty : this.key1.ToString() + ",") 
      + (this.key2 == null ? string.Empty : this.key2.ToString()); 
    } 
} 

Затем, как использовать их было бы так:

Foo foo1 = new Foo("abc", "def"); 
    Foo foo2 = new Foo("abc", "def"); 

    Dictionary<Foo, string> bar = new Dictionary<Foo, string>(); 
    bar.Add(foo1, "found"); 

    if (bar.ContainsKey(foo2)) 
    { 
     Console.WriteLine("This works."); 
    } 
    else 
    { 
     Console.WriteLine("Does not work"); 
    } 
+2

Это правильно и очень полно. –

+1

@Steven - Увы, это было не так полно, как должно было быть. Важным дополнением были проверки недействительности в переопределенном методе «Equals». –

+1

Моя ошибка в том, что я не поймал это и не признал вас за это. –

0

Ваш код должен работать без каких-либо проблем в .NET, и я бы осмелился сказать, что это так и останется, даже если Изменения в версии .NET. Дело в том, что словарь использует две вещи, чтобы получить доступ к значению с помощью определенного ключа: Equals() и GetHashCode() следующим образом

  • получить все ключи, которые соответствуют запрошенные значение ключа с помощью GetHashCode() (значение GetHashCode должен быть равным для равных объектов и должен отличаться для разных объектов)
  • отфильтровать список, созданный на предыдущем шаге, с помощью Equals().

Стандарт Equals() для типов значений использует отражение для доступа к полям и сравнивает их (как упоминалось ранее). Принимая это во внимание, значение по умолчанию GetHashCode() должно соответствовать тому, как работает реализация по умолчанию Equals(), если a == b, a.GetHashCode() == b.GetHashCode() - в любом случае не было бы никакого смысла в предоставлении по умолчанию, если он даже не соответствует минимальному минимуму. Что MSDN говорит, что GetHashCode() не предоставляет уникальные значения, и это вполне понятно. Имейте в виду, что такая простая реализация:

public int GetHashCode() 
{ 
    return 0; 
} 

все еще правильна (словари, Hashtables и т.д. будет работать), даже если значения не являются уникальными. Производительность - это другое дело, использование такой реализации приведет к полному сканированию каждой коллекции и тестированию каждого элемента для равенства.

Смежные вопросы