Я часто просматриваю вопросы в Интернете, и многие решения включают словари. Однако всякий раз, когда я пытаюсь реализовать их, я получаю этот ужасный запах в своем коде. Например, каждый раз, когда я хочу использовать значение:
int x;
if (dict.TryGetValue("key", out x)) {
DoSomethingWith(x);
}
Это 4 строки кода, чтобы по существу сделать следующее: DoSomethingWith(dict["key"])
Я слышал, что использование ключевого слова out является анти-паттерном, потому что оно заставляет функции изменять свои параметры.
Кроме того, я часто нуждаюсь в «обратном» словаре, где я переворачиваю ключи и значения.
Точно так же я часто хотел бы перебирать элементы в словаре и находить себя конвертирующим ключи или значения в списки и т.д., чтобы сделать это лучше.
Я чувствую, что почти всегда есть лучший, более элегантный способ использования словарей, но я в растерянности.
System.Collections.Generic
пространстве имен не являются потокобезопасными .Dictionary
, что они действительно хотели, это новый класс. Для этих 50% (только) это дизайнерский запах.Ответы:
Словари (C # или другие) - это просто контейнер, в котором вы ищите значение на основе ключа. Во многих языках это более правильно идентифицируется как Map с наиболее распространенной реализацией, являющейся HashMap.
Проблема в том, что происходит, когда ключ не существует. Некоторые языки ведут себя, возвращая
null
илиnil
или другое эквивалентное значение. По умолчанию значение по умолчанию вместо того, чтобы сообщать вам, что значение не существует.Хорошо это или плохо, но разработчики библиотеки C # придумали идиому, чтобы разобраться с поведением. Они пришли к выводу, что поведение по умолчанию для поиска несуществующего значения - это исключение. Если вы хотите избежать исключений, то вы можете использовать
Try
вариант. Это тот же подход, который они используют для разбора строк на целые числа или объекты даты / времени. По сути, воздействие таково:И это перенесено в словарь, индексатор которого делегирует
Get(index)
:Это просто способ, которым разработан язык.
Должны ли
out
переменные быть обескуражены?C # не первый язык, чтобы иметь их, и у них есть свое предназначение в определенных ситуациях. Если вы пытаетесь построить систему с высокой степенью параллелизма, вы не можете использовать
out
переменные на границах параллелизма.Во многих отношениях, если есть идиома, поддерживаемая поставщиками языка и основных библиотек, я пытаюсь использовать эти идиомы в своих API. Это заставляет API чувствовать себя более последовательным и удобным на этом языке. Поэтому метод, написанный на Ruby, не будет похож на метод, написанный на C #, C или Python. У каждого из них есть предпочтительный способ построения кода, и работа с ним помогает пользователям вашего API быстрее освоить его.
Являются ли Карты в целом анти-паттерном?
У них есть своя цель, но часто они могут быть неправильным решением для той цели, которую вы имеете. Особенно, если у вас есть двунаправленное отображение, которое вам нужно. Существует много контейнеров и способов организации данных. Есть много подходов, которые вы можете использовать, и иногда вам нужно немного подумать, прежде чем выбрать этот контейнер.
Если у вас очень короткий список значений двунаправленного отображения, то вам может понадобиться только список кортежей. Или список структур, где вы можете легко найти первое совпадение по обе стороны от сопоставления.
Подумайте о проблемной области и выберите наиболее подходящий инструмент для работы. Если его нет, создайте его.
источник
Option<T>
, то я думаю, что это усложнит использование метода в C # 2.0, потому что в нем нет сопоставления с образцом.Некоторые хорошие ответы здесь об общих принципах хэш-таблиц / словарей. Но я думал, что коснусь вашего примера кода,
Начиная с C # 7 (которому, я думаю, около двух лет) это можно упростить до:
И, конечно, его можно сократить до одной строки:
Если у вас есть значение по умолчанию, когда ключ не существует, он может стать:
Таким образом, вы можете создавать компактные формы, используя достаточно свежие языковые дополнения.
источник
"key"
нет,x
будет инициализирован доdefault(TValue)
"key".DoSomethingWithThis()
getOrElse("key", defaultValue)
объект Null - все еще мой любимый образец. Работайте таким образом, и вам все равно,TryGetValue
вернет ли значение true или false.TryGetValue
это не атомарно / потокобезопасно, так что вы могли бы так же легко выполнить одну проверку на существование и другую, чтобы захватить и воздействовать на значениеЭто не является ни запахом кода, ни анти-паттерном, так как использование функций в стиле TryGet с параметром out является идиоматическим C #. Тем не менее, в C # предусмотрено 3 варианта работы со словарем, поэтому вы должны быть уверены, что используете правильный вариант для вашей ситуации. Я думаю, что знаю, откуда появились слухи о наличии проблемы с использованием параметра out, поэтому я рассмотрю это в конце.
Какие функции использовать при работе со словарем C #:
Чтобы оправдать это, достаточно обратиться к документации для словаря TryGetValue в разделе «Замечания» :
Единственная причина, по которой существует TryGetValue, заключается в том, чтобы действовать как более удобный способ использования ContainsKey и Item [TKey], избегая при этом необходимости дважды искать в словаре - поэтому притворяться, что он не существует, и делать две вещи, которые он делает вручную, довольно неудобно. выбор.
На практике я редко когда-либо использовал необработанный словарь из-за этого простого принципа: выбирайте самый общий класс / контейнер, который дает вам необходимую функциональность . Словарь не был предназначен для поиска по значению, а не по ключу (например), поэтому, если это то, что вы хотите, может иметь больше смысла использовать альтернативную структуру. Я думаю, что я мог использовать Словарь один раз в последнем проекте разработки, который я делал, просто потому, что он редко был подходящим инструментом для работы, которую я пытался сделать. Словарь, безусловно, не швейцарский армейский нож из набора инструментов C #.
Что не так с нашими параметрами?
CA1021: избегать параметров
Я предполагаю, что именно здесь вы слышали, что параметры были чем-то вроде анти-паттерна. Как и со всеми правилами, вам следует прочитать поближе, чтобы понять «почему», и в этом случае даже есть явное упоминание о том, как шаблон Try не нарушает правило :
источник
В словарях C # отсутствуют по крайней мере два метода, которые, по моему мнению, значительно очищают код во многих ситуациях на других языках. Первый возвращает an
Option
, который позволяет вам написать код, подобный следующему в Scala:Вторая возвращает заданное пользователем значение по умолчанию, если ключ не найден:
Есть что-то, что можно сказать об использовании идиом, которые язык предоставляет, когда это уместно, например,
Try
шаблон, но это не значит, что вы должны использовать только то, что обеспечивает язык. Мы программисты. Можно создавать новые абстракции, чтобы облегчить понимание нашей конкретной ситуации, особенно если это устраняет много повторений. Если вам часто что-то нужно, например, обратный поиск или итерация значений, сделайте это. Создайте интерфейс, который вы хотели бы иметь.источник
Я согласен, что это не элегантно. Механизм, который мне нравится использовать в этом случае, где значение является структурным типом:
Теперь у нас есть новая версия
TryGetValue
этого возвратаint?
. Затем мы можем сделать аналогичный трюк для расширенияT?
:А теперь собери это:
и мы до единого, четкого утверждения.
Я бы сказал не так сильно и скажу, что избегать мутаций, когда это возможно, хорошая идея.
Затем внедрите или получите двунаправленный словарь. Их легко написать, или в Интернете доступно множество реализаций. Здесь есть множество реализаций, например:
/programming/268321/bidirectional-1-to-1-dictionary-in-c-sharp
Конечно, мы все делаем.
Спросите себя: «Предположим, у меня был класс, отличающийся от того,
Dictionary
который реализовал именно ту операцию, которую я хочу сделать; как бы выглядел этот класс?» Затем, как только вы ответите на этот вопрос, реализуйте этот класс . Вы программист. Решите свои проблемы, написав компьютерные программы!источник
Action<T>
что вы очень похожиinterface IAction<T> { void Invoke(T t); }
, но с очень мягкими правилами о том, что «реализует» этот интерфейс, и о том, какInvoke
можно вызывать. Если вы хотите узнать больше, узнайте о «делегатах» в C #, а затем узнайте о лямбда-выражениях.TryGetValue()
Конструкция необходима только если вы не знаете , присутствует ли в качестве ключа в словаре или нет «ключ», в противном случаеDoSomethingWith(dict["key"])
вполне допустимо.«Менее грязный» подход мог бы использовать
ContainsKey()
вместо этого проверку.источник
TryGetValue
она ни была, по крайней мере, трудно забыть разобраться с пустым делом. В идеале, я хотел бы, чтобы это просто вернуло опционально.ContainsKey
подходом для многопоточных приложений, который является уязвимостью времени проверки (TOCTOU). Что если какой-то другой поток удалит ключ между вызовамиContainsKey
иGetValue
?Optional<Optional<T>>
TryGetValue
наConcurrentDictionary
. Обычный словарь не будет синхронизированДругие ответы содержат замечательные моменты, поэтому я не буду их здесь повторять, но вместо этого я сосредоточусь на этой части, которая, как представляется, пока в значительной степени игнорируется:
На самом деле, перебирать словарь довольно просто, поскольку он реализует
IEnumerable
:Если вы предпочитаете Linq, это тоже отлично работает:
В общем, я не нахожу словари антипаттерном - это просто конкретный инструмент с особым использованием.
Кроме того, для получения более подробной информации, проверьте
SortedDictionary
(использует деревья RB для более предсказуемой производительности) иSortedList
(который также является словарь со странным названием, который жертвует скоростью вставки для скорости поиска, но светит, если вы смотрите с фиксированной, предварительно отсортировано множество). У меня был случай , когда заменяетеDictionary
сSortedDictionary
привела в порядок быстрее исполнения (но это может случиться наоборот тоже).источник
Если вы чувствуете, что использование словаря неудобно, это может быть неправильный выбор для вашей проблемы. Словари хороши, но, как заметил один комментатор, часто они используются как ярлык для того, что должно было быть классом. Или сам словарь может быть правильным в качестве основного метода хранения, но вокруг него должен быть класс-обертка, чтобы обеспечить требуемые методы обслуживания.
Много говорилось о Dict [key] против TryGet. Что я часто использую, так это перебирая словарь по KeyValuePair. По-видимому, это менее известная конструкция.
Основным преимуществом словаря является то, что он действительно быстр по сравнению с другими коллекциями, так как количество предметов растет. Если вам нужно проверить, существует ли ключ много, вы можете спросить себя, подходит ли вам использование словаря. Как клиент, вы, как правило, должны знать, что вы положили туда и, следовательно, что было бы безопасно запрашивать.
источник