Этот код зависит от интернирования строк для работы? - PullRequest
4 голосов
/ 27 января 2010

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

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;
}

Существуют ли случаи, которые могут привести к сбою, или я могу положиться на это как на уникальный ключ?

Ответы [ 5 ]

4 голосов
/ 27 января 2010

Согласно моему комментарию к rubim 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");
    }
4 голосов
/ 27 января 2010

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

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

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

3 голосов
/ 27 января 2010

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

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

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

1 голос
/ 27 января 2010

По данным MSDN,

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

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

(выделено мое.)

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

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

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

0 голосов
/ 27 января 2010

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

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

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

public int GetHashCode()
{
   return 0;
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...