.net: Добавление элемента словаря - проверьте, существует ли оно или разрешено исключение?

Я добавляю элементы в StringDictionary, и возможно, что появится дубликат ключа. Это, конечно же, вызовет исключение.

Если вероятность дублирования очень низкая (т.е. это редко произойдет), мне лучше использовать блок Try Catch и оставить его необработанным, или я должен всегда делать проверку .ContainsKey перед добавлением каждой записи?

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

Мысли?

Edit

Я использовал рефлектор в общем словаре и нашел следующее для ContainsKey и TryGetValue, поскольку оба упомянуты ниже.

public bool TryGetValue(TKey key, out TValue value)
{
    int index = this.FindEntry(key);
    if (index >= 0)
    {
        value = this.entries[index].value;
        return true;
    }
    value = default(TValue);
    return false;
}

и

public bool ContainsKey(TKey key)
{
    return (this.FindEntry(key) >= 0);
}

Я что-то упускаю, или TryGetValue делает больше работы, чем ContainsKey?


Я ценю ответы, и для моей текущей цели я собираюсь сделать вызов ContainsKey, поскольку коллекция будет маленькой, а код более читабельным.

Ответ 1

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

// c# sample:
myDictionary[key] = value;

В качестве побочного примечания: я, возможно, по возможности использовал бы Dictionary<string, string> вместо StringDictionary. Если ничего другого не даст вам доступ к некоторым дополнительным методам Linq.

Ответ 2

Я бы выполнил проверку Содержит.

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

Ответ 3

Если возможно, замените StringDictionary на Dictionary<string, string> и используйте TryGetValue. Это позволяет избежать накладных расходов на обработку исключений и двойной поиск.

Ответ 4

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

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

Это имеет смысл, потому что выигрыш в производительности, полученный вами за счет try-catch (если вообще), тривиален, но "улов" может быть более наказуемым. Здесь тест:

public static void Benchmark(Action method, int iterations = 10000)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < iterations; i++)
        method();

    sw.Stop();
    MessageBox.Show(sw.Elapsed.TotalMilliseconds.ToString());
}

public static string GetRandomAlphaNumeric()
{
    return Path.GetRandomFileName().Replace(".", "").Substring(0, 8);
}

var dict = new Dictionary<string, int>();

Нет дубликатов:

Benchmark(() =>
{
    // approach 1
    var key = GetRandomAlphaNumeric();
    if (!dict.ContainsKey(key))
        dict.Add(item, 0);

    // approach 2
    try
    {
        dict.Add(GetRandomAlphaNumeric(), 0);
    }
    catch (ArgumentException)
    {

    }
}, 100000);  

50% дубликатов:

for (int i = 0; i < 50000; i++)
{
    dict.Add(GetRandomAlphaNumeric(), 0);  
}

var lst = new List<string>();
for (int i = 0; i < 50000; i++)
{
    lst.Add(GetRandomAlphaNumeric());
}
lst.AddRange(dict.Keys);
Benchmark(() =>
{
    foreach (var key in lst)
    {
        // approach 1
        if (!dict.ContainsKey(key))
            dict.Add(key, 0);

        // approach 2
        try
        {
            dict.Add(key, 0);
        }
        catch (ArgumentException)
        {

        }
    }
}, 1);  

100% дубликатов

var key = GetRandomAlphaNumeric();
dict.Add(key, 0);
Benchmark(() =>
{
    // approach 1
    if (!dict.ContainsKey(key))
        dict.Add(item, 0);

    // approach 2
    try
    {
        dict.Add(key, 0);
    }
    catch (ArgumentException)
    {

    }
}, 100000);

Результаты:

Нет дубликатов

подход 1: отладка → 630 мс - 680 мс; релиз → 620 мс - 640 мс

подход 2: debug → 640 мс - 690 мс; релиз → 640 мс - 670 мс

50% дубликатов

подход 1: отладка → 26 мс - 39 мс; релиз → 25 мс - 33 мс

подход 2: debug → 1340 мс; релиз → 1260 мс

100% дубликатов

подход 1: debug → 7 мс; релиз → 7 мс

подход 2: отладка → 2600 мс; релиз → 2400 мс

Вы можете видеть, что по мере увеличения дубликатов try-catch работает плохо. Даже в худшем случае, когда у вас нет дубликатов вообще, прирост производительности try-catch не является чем-то существенным.

Ответ 5

Если это не очень большой словарь или критический внутренний код кода, вы, вероятно, не увидите разницы.

Проверка .ContainsKey будет стоить вам небольшой производительности каждый раз, в то время как заброшенное исключение будет стоить вам немного больше производительности. Если вероятность дублирования действительно низкая, перейдите к разрешению Исключения.

Если вы действительно хотите иметь возможность управлять дублирующимися ключами, вы можете посмотреть MultiDictionary в PowerCollections

Ответ 7

Есть два способа взглянуть на это...

Производительность

Если вы смотрите на него с точки зрения производительности, вы должны учитывать:

  • Как дорого рассчитать хэш типа для ключа и
  • как дорого создавать и бросать исключения

Я не могу придумать никакого расчета хэша, который был бы дороже, чем выброс исключения. Помните, что когда исключаются исключения, они должны быть распределены между Interop и Win32 API, создаваться, сканировать всю трассировку стека и останавливать и обрабатывать любой захваченный блок. Выброс исключений в CLR по-прежнему рассматривается как HRESULT, обрабатываемый Win32 API под обложками. Из Блог Криса Брумме:

Конечно, мы не можем говорить об управляемых исключениях, не рассмотрев, прежде всего, Windows Structured Exception Handling (SEH). И нам также нужно посмотреть на модель исключений С++. Это потому, что и управляемые исключения, и исключения С++ реализованы поверх основного механизма SEH, и потому что управляемые исключения должны взаимодействовать как с SEH, так и с С++ исключения.

Заключение производительности: избегайте исключений


Лучшая практика

Принципы разработки .NET Framework - это хороший набор правил, которым нужно следовать (за редким исключением - каламбур). Обновление рекомендаций по дизайну: Бросок исключений. В руководящих принципах есть что-то, что называется шаблоном "Try/Doer", в этом случае рекомендуется избегать исключений:

Рассмотрим шаблон Tester-Doer для членов, который может генерировать исключения в общих сценариях, чтобы избежать проблем с производительностью, связанных с исключениями.

Исключения также никогда не должны использоваться в качестве механизма потока управления - снова написаны в Руководстве по дизайну CLR.

Заключение о лучшей практике: избегайте исключений

Ответ 8

Эта проблема намного проще для .NET Standard 2. 1+ и .Net Core 2. 0+. Просто используйте метод TryAdd. Он решает все проблемы, которые у вас есть наиболее изящным способом.

Ответ 9

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