System collections generic dictionary

This question is in regards to a .Net Framework 4.5 MVC Web Application.

I’ve got a block of code that we’ve inherited and been using for years that generically converts a DataTable to a List , and one of the private methods gets a list of the properties for a generic class, for example:

ORIGINAL CODE

Nothing incredibly exciting going on there, it’s just making sure the typeDictionary doesn’t already contain the key (type) and adds it to the dictionary (key=type, value=properties), so we can access them later.

We use this code generically for any kind of "model" object, but for this particular example, this is the one that’s given me trouble on 2 different occasions.

MODEL OBJECT

Again, nothing really significant going on in that particular model class, and nothing any different than we use in any other class.

Like I said, we use this code generically in several projects, and have never once had issues with it, but on 2 separate occasions in the past day we’ve had it throw the following exception:

An item with the same key has already been added.

In case it is helpful, you can see the full Extensions.cs class (static) that the extension methods live in here:

My questions are:

Given the fact that the code is already doing a !typeDictionary.ContainsKey(typeof(T)) check, how is it possible that it could ever pass that test, yet fail on the typeDictionary.Add(type, type.GetProperties().ToList()); call?

Why would it be so sporadic? It seemingly works 99% of the time, using the same code, the same class (GetApprovalsByUserId, shown above), and never has failed otherwise in any other project or any other model class.

We have not been able to reproduce this issue using the exact same code, model, data, or otherwise the exact same setup in any environment, so not sure how to safe-guard this code anymore than it already is.

One thought I have is to change the code to this:

PROPOSED CODE CHANGE

But since I can’t reproduce the error in the first place, I’m not entire sure if this is bullet proof either. My thinking would be that it’s just something weird with ContainsKey, particularly with using a typeof(T) as the "key", which allows it to pass the test in odd cases, when it really shouldn’t, but the Add fails because it knows the key is already there. So if I try/catch it, if the ContainsKey incorrectly tells me it’s not there, when it in fact is, the Add will still fail, but I’ll catch it, and move on, and then I can TryParse to get the value out, and all should be well.

Appreciate any thoughts, ideas, or specifically how to reproduce the problem with the Original Code shown above, and recommended improvements to safe guard it.

Represents a collection of keys and values.

Type Parameters

The type of the keys in the dictionary.

The type of the values in the dictionary.

Examples

The following code example creates an empty Dictionary of strings with string keys and uses the Add method to add some elements. The example demonstrates that the Add method throws an ArgumentException when attempting to add a duplicate key.

The example uses the Item[TKey] property (the indexer in C#) to retrieve values, demonstrating that a KeyNotFoundException is thrown when a requested key is not present, and showing that the value associated with a key can be replaced.

The example shows how to use the TryGetValue method as a more efficient way to retrieve values if a program often must try key values that are not in the dictionary, and it shows how to use the ContainsKey method to test whether a key exists before calling the Add method.

The example shows how to enumerate the keys and values in the dictionary and how to enumerate the keys and values alone using the Keys property and the Values property.

Finally, the example demonstrates the Remove method.

Remarks

The Dictionary generic class provides a mapping from a set of keys to a set of values. Each addition to the dictionary consists of a value and its associated key. Retrieving a value by using its key is very fast, close to O(1), because the Dictionary class is implemented as a hash table.

The speed of retrieval depends on the quality of the hashing algorithm of the type specified for TKey .

As long as an object is used as a key in the Dictionary , it must not change in any way that affects its hash value. Every key in a Dictionary must be unique according to the dictionary’s equality comparer. A key cannot be null , but a value can be, if its type TValue is a reference type.

Читайте также:  Южный мост обеспечивает работу

Dictionary requires an equality implementation to determine whether keys are equal. You can specify an implementation of the IEqualityComparer generic interface by using a constructor that accepts a comparer parameter; if you do not specify an implementation, the default generic equality comparer EqualityComparer .Default is used. If type TKey implements the System.IEquatable generic interface, the default equality comparer uses that implementation.

For example, you can use the case-insensitive string comparers provided by the StringComparer class to create dictionaries with case-insensitive string keys.

The capacity of a Dictionary is the number of elements the Dictionary can hold. As elements are added to a Dictionary , the capacity is automatically increased as required by reallocating the internal array.

.NET Framework only: For very large Dictionary objects, you can increase the maximum capacity to 2 billion elements on a 64-bit system by setting the enabled attribute of the configuration element to true in the run-time environment.

For purposes of enumeration, each item in the dictionary is treated as a KeyValuePair structure representing a value and its key. The order in which the items are returned is undefined.

The foreach statement of the C# language ( for each in C++, For Each in Visual Basic) returns an object of the type of the elements in the collection. Since the Dictionary is a collection of keys and values, the element type is not the type of the key or the type of the value. Instead, the element type is a KeyValuePair of the key type and the value type. For example:

The foreach statement is a wrapper around the enumerator, which allows only reading from the collection, not writing to it.

Because keys can be inherited and their behavior changed, their absolute uniqueness cannot be guaranteed by comparisons using the Equals method.

Constructors

Initializes a new instance of the Dictionary class that is empty, has the default initial capacity, and uses the default equality comparer for the key type.

Initializes a new instance of the Dictionary class that contains elements copied from the specified IDictionary and uses the default equality comparer for the key type.

Initializes a new instance of the Dictionary class that contains elements copied from the specified IDictionary and uses the specified IEqualityComparer .

Initializes a new instance of the Dictionary class that is empty, has the default initial capacity, and uses the specified IEqualityComparer .

Initializes a new instance of the Dictionary class that is empty, has the specified initial capacity, and uses the default equality comparer for the key type.

Initializes a new instance of the Dictionary class that is empty, has the specified initial capacity, and uses the specified IEqualityComparer .

Initializes a new instance of the Dictionary class with serialized data.

Properties

Gets the IEqualityComparer that is used to determine equality of keys for the dictionary.

Gets the number of key/value pairs contained in the Dictionary .

Gets or sets the value associated with the specified key.

Gets a collection containing the keys in the Dictionary .

Gets a collection containing the values in the Dictionary .

Methods

Adds the specified key and value to the dictionary.

Removes all keys and values from the Dictionary .

Determines whether the Dictionary contains the specified key.

Determines whether the Dictionary contains a specific value.

Ensures that the dictionary can hold up to a specified number of entries without any further expansion of its backing storage.

Determines whether the specified object is equal to the current object.

(Inherited from Object)

GetEnumerator()

Returns an enumerator that iterates through the Dictionary .

Serves as the default hash function.

(Inherited from Object)

GetObjectData(SerializationInfo, StreamingContext)

Implements the ISerializable interface and returns the data needed to serialize the Dictionary instance.

Gets the Type of the current instance.

(Inherited from Object)

MemberwiseClone()

Creates a shallow copy of the current Object.

(Inherited from Object)

OnDeserialization(Object)

Implements the ISerializable interface and raises the deserialization event when the deserialization is complete.

Removes the value with the specified key from the Dictionary .

Returns a string that represents the current object.

(Inherited from Object)

TrimExcess()

Sets the capacity of this dictionary to what it would be if it had been originally initialized with all its entries.

Sets the capacity of this dictionary to hold up a specified number of entries without any further expansion of its backing storage.

Attempts to add the specified key and value to the dictionary.

Gets the value associated with the specified key.

Explicit Interface Implementations

Copies the elements of the ICollection to an array, starting at the specified array index.

Читайте также:  Как восстановить модем билайн

Gets a value that indicates whether access to the ICollection is synchronized (thread safe).

Gets an object that can be used to synchronize access to the ICollection.

Adds the specified value to the ICollection with the specified key.

Determines whether the ICollection contains a specific key and value.

Copies the elements of the ICollection to an array of type KeyValuePair , starting at the specified array index.

Gets a value that indicates whether the dictionary is read-only.

Removes a key and value from the dictionary.

Adds the specified key and value to the dictionary.

Determines whether the IDictionary contains an element with the specified key.

Gets a value that indicates whether the IDictionary has a fixed size.

Gets a value that indicates whether the IDictionary is read-only.

Gets or sets the value with the specified key.

Gets an ICollection containing the keys of the IDictionary.

Removes the element with the specified key from the IDictionary.

Gets an ICollection containing the values in the IDictionary.

Gets an ICollection containing the keys of the IDictionary .

Gets an ICollection containing the values in the IDictionary .

Returns an enumerator that iterates through the collection.

Returns an enumerator that iterates through the collection.

Gets a collection containing the keys of the IReadOnlyDictionary .

Gets a collection containing the values of the IReadOnlyDictionary .

Extension Methods

Tries to get the value associated with the specified key in the dictionary .

Tries to get the value associated with the specified key in the dictionary .

Tries to remove the value with the specified key from the dictionary .

Tries to add the specified key and value to the dictionary .

Returns a DataTable that contains copies of the DataRow objects, given an input IEnumerable object where the generic parameter T is DataRow.

Copies DataRow objects to the specified DataTable, given an input IEnumerable object where the generic parameter T is DataRow.

Copies DataRow objects to the specified DataTable, given an input IEnumerable object where the generic parameter T is DataRow.

Casts the elements of an IEnumerable to the specified type.

Filters the elements of an IEnumerable based on a specified type.

Enables parallelization of a query.

Returns a collection of elements that contains the ancestors of every node in the source collection.

Returns a filtered collection of elements that contains the ancestors of every node in the source collection. Only elements that have a matching XName are included in the collection.

Returns a collection of the descendant nodes of every document and element in the source collection.

Returns a collection of elements that contains the descendant elements of every element and document in the source collection.

Returns a filtered collection of elements that contains the descendant elements of every element and document in the source collection. Only elements that have a matching XName are included in the collection.

Returns a collection of the child elements of every element and document in the source collection.

Returns a filtered collection of the child elements of every element and document in the source collection. Only elements that have a matching XName are included in the collection.

Returns a collection of nodes that contains all nodes in the source collection, sorted in document order.

Returns a collection of the child nodes of every document and element in the source collection.

Removes every node in the source collection from its parent node.

Applies to

Thread Safety

A Dictionary can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. In the rare case where an enumeration contends with write accesses, the collection must be locked during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

For thread-safe alternatives, see the ConcurrentDictionary class or ImmutableDictionary class.

Public static ( Shared in Visual Basic) members of this type are thread safe.

представляет собой сложную структуру данных, позволяющую обеспечить доступ к элементам по ключу. Главное свойство словарей — быстрый поиск на основе ключей. Можно также свободно добавлять и удалять элементы, подобно тому, как это делается в List , но без накладных расходов производительности, связанных с необходимостью смещения последующих элементов в памяти.

На следующем рисунке представлена упрощенная модель словаря. Здесь ключами словаря служат идентификаторы сотрудников, такие как В4711. Ключ трансформируется в хеш. В хеше создается число для ассоциации индекса со значением. После этого индекс содержит ссылку на значение. Изображенная модель является упрощенной, поскольку существует возможность того, что единственное вхождение индекса может быть ассоциировано с несколькими значениями, и индекс может храниться в виде дерева.

В .NET Framework предлагается несколько классов словарей. Главный класс, который можно использовать — это Dictionary .

Читайте также:  Как открыть скаченную карту в майнкрафт

Тип ключа

Тип, используемый в качестве ключа словаря, должен переопределять метод GetHashCode() класса Object. Всякий раз, когда класс словаря должен найти местоположение элемента, он вызывает метод GetHashCode().

Целое число, возвращаемое этим методом, используется словарем для вычисления индекса, куда помещен элемент. Мы не станем углубляться в подробности работы этого алгоритма. Единственное, что следует знать — это то, что он использует простые числа, так что емкость словаря всегда выражается простым числом.

Реализация метода GetHashCode() должна удовлетворять перечисленным ниже требованиям:

Один и тот же объект должен всегда возвращать одно и то же значение.

Разные объекты могут возвращать одно и то же значение.

Он должен выполняться насколько возможно быстро, не требуя значительных вычислительных затрат.

Он не должен генерировать исключений.

Он должен использовать как минимум одно поле экземпляра.

Значения хеш-кода должны распределяться равномерно по всему диапазону чисел, которые может хранить int.

Хеш-код не должен изменяться на протяжении времени существования объекта.

Чем вызвана необходимость равномерного распределения значений хеш-кода по диапазону целых чисел? Если два ключа возвращают хеш-значения, дающие один и тот же индекс, класс словаря вынужден искать ближайшее доступное свободное место для сохранения второго элемента, к тому же ему придется выполнять некоторый поиск, чтобы впоследствии извлечь требуемое значение. Понятно, что это наносит ущерб производительности, и если множество ключей дают одни и те же индексы, куда их следует поместить, вероятность конфликтов значительно возрастает. Однако благодаря способу, которым работает часть алгоритма, принадлежащая Microsoft, риск снижается до минимума, когда вычисляемое значение хеш-кода равномерно распределено между int.MinValue и int.MaxValue.

Помимо реализации GetHashCode() тип ключа также должен реализовывать метод IEquatable .Equals() либо переопределять метод Equals() класса Object. Поскольку разные объекты ключа могут возвращать один и тот же хеш-код, метод Equals() используется при сравнении ключей словаря. Словарь проверяет два ключа А и В на эквивалентность, вызывая A.Equals(В). Это означает, что потребуется обеспечить истинность следующего утверждения:

Если истинно А.Equals(В) , значит, А.GetHashCode() и В.GetHashCode() всегда должны возвращать один и тот же хеш-код.

Класс Dictionary

В классе Dictionary реализуются интерфейсы IDictionary, IDictionary , ICollection, ICollection >, IEnumerable, IEnumerable >, ISerializable и IDeserializationCallback. В двух последних интерфейсах поддерживается сериализация списка. Словари имеют динамический характер, расширяясь по мере необходимости.

В классе Dictionary предоставляется немало конструкторов. Ниже перечислены наиболее часто используемые из них:

В первом конструкторе создается пустой словарь с выбираемой по умолчанию первоначальной емкостью. Во втором конструкторе создается словарь с указанным количеством элементов dictionary. А в третьем конструкторе с помощью параметра capacity указывается емкость коллекции, создаваемой в виде словаря. Если размер словаря заранее известен, то, указав емкость создаваемой коллекции, можно исключить изменение размера словаря во время выполнения, что, как правило, требует дополнительных затрат вычислительных ресурсов.

В классе Dictionary определяется также ряд методов:

Add()

Добавляет в словарь пару "ключ-значение", определяемую параметрами key и value. Если ключ key уже находится в словаре, то его значение не изменяется, и генерируется исключение ArgumentException

ContainsKey()

Возвращает логическое значение true, если вызывающий словарь содержит объект key в качестве ключа; а иначе — логическое значение false

ContainsValue()

Возвращает логическое значение true, если вызывающий словарь содержит значение value; в противном случае — логическое значение false

Remove()

Удаляет ключ key из словаря. При удачном исходе операции возвращается логическое значение true, а если ключ key отсутствует в словаре — логическое значение false

Кроме того, в классе Dictionary определяются собственные свойства, помимо тех, что уже объявлены в интерфейсах, которые в нем реализуются. Эти свойства приведены ниже:

Comparer

Получает метод сравнения для вызывающего словаря

Keys

Получает коллекцию ключей

Values

Получает коллекцию значений

Следует иметь в виду, что ключи и значения, содержащиеся в коллекции, доступны отдельными списками с помощью свойств Keys и Values. В коллекциях типа Dictionary .KeyCollection и Dictionary .ValueCollection реализуются как обобщенные, так и необобщенные формы интерфейсов ICollection и IEnumerable.

И наконец, в классе Dictionary реализуется приведенный ниже индексатор, определенный в интерфейсе IDictionary

Этот индексатор служит для получения и установки значения элемента коллекции, а также для добавления в коллекцию нового элемента. Но в качестве индекса в данном случае служит ключ элемента, а не сам индекс. При перечислении коллекции типа Dictionary из нее возвращаются пары "ключ-значение" в форме структуры KeyValuePair . Напомним, что в этой структуре определяются два поля.

В этих полях содержится ключ или значение соответствующего элемента коллекции. Как правило, структура KeyValuePair не используется непосредственно, поскольку средства класса Dictionary позволяют работать с ключами и значениями по отдельности. Но при перечислении коллекции типа Dictionary , например, в цикле foreach перечисляемыми объектами являются пары типа KeyValuePair.

Давайте рассмотрим пример использования словарей:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock detector