Можно добавить элемент в словарь .NET, пропуская внутренний вызов containsKey().
я знаю оDictionary<K,V>.TryAdd()
метод, добавленный в .NET Core 2.0, который повышает производительность, проверяя только один раз, содержит ли словарь ключ, прежде чем добавлять элемент, в отличие от:
if(!dico.ContainsKey(key)) { dico.Add(key,val); } // .Add() calls ContainsKey() a second time
Однако из соображений производительности я бы хотел ленивую сборкуval
только если!dico.ContainsKey(key)
:
if(!dico.ContainsKey(key)) { dico.Add(key, new Value()); }
В этой ситуацииTryAdd()
снижает производительность, поскольку значение не создается лениво.
dico.TryAdd(key,new Value());
Есть ли способ, чтобы оба имели одинContainsKey()
вызвать И лениво построить значение? Что-то вродеAddWithNoContainsKeyCheck()
:
if(!dico.ContainsKey(key)) { dico.AddWithNoContainsKeyCheck(key, new Value()); }
2 ответа
Да, это возможно, используя расширенный API., доступный в .NET 6 и более поздних версиях:
/// <summary>
/// Uses the specified function to add a key/value pair to the dictionary,
/// if the key does not already exist.
/// </summary>
public static bool TryAdd<TKey, TValue>(
this Dictionary<TKey, TValue> dictionary,
TKey key,
Func<TKey, TValue> valueFactory,
out TValue value) where TKey : notnull
{
ArgumentNullException.ThrowIfNull(dictionary);
ArgumentNullException.ThrowIfNull(valueFactory);
ref TValue valueRef = ref CollectionsMarshal
.GetValueRefOrAddDefault(dictionary, key, out bool exists);
if (!exists)
{
try { valueRef = valueFactory(key); }
catch { dictionary.Remove(key); throw; }
value = valueRef;
return true;
}
value = valueRef; // It was `value = default` in the original answer
return false;
}
The CollectionsMarshal.GetValueRefOrAddDefault
возвращает ссылку на значение, хранящееся в словаре. В случае, еслиvalueFactory
не удается, важно удалить вновь добавленный ключ. В противном случае ключ сdefault(TValue)
будет случайно добавлен в словарь.
Примечание. Метод расширенияTryAdd
в этом ответе предлагает ту же функциональность, но более удобную и свободнуюGetOrAdd
. bool
Возвращаемое значение (передача информации о том, было ли значение создано или уже существовало) на практике требуется редко.
Вы можете использоватьCollectionsMarshal.GetValueRefOrAddDefault()
чтобы сделать это, вот так:
public static class Program
{
public static void Main()
{
var d = new Dictionary<int, Test>();
ref Test? element = ref CollectionsMarshal.GetValueRefOrAddDefault(d, key:0, out bool exists);
if (!exists)
{
element = new Test(42);
}
Console.WriteLine(d[0].Value); // Prints "42"
}
}
public sealed class Test
{
public Test(int value)
{
Console.WriteLine($"Creating Test with value = {value}");
Value = value;
}
public int Value { get; }
}
Это добавитnull
элемент, который вы должны перезаписать фактическим значением после того, как определите, что элемент ранее не существовал.
Обратите внимание, что это немного хитроумно, поскольку мы определили словарь как НЕ содержащий каких-либо нулевых значений, но здесь мы временно вставляем нулевое значение (хотя оно сразу же перезаписывается ненулевым значением). Требуется некоторая осторожность и внимание, особенно еслиTest
конструктор может бросить.