Dictionary
)创建一个List
,然后在填充数据时总是编写相同的代码模式。
例如:
var dict = new Dictionary<string, List<string>>();
string key = "foo";
string aValueForKey = "bar";
也就是说,我想将
"bar"
插入到与键 "foo"
对应的列表中,其中键 "foo"
可能不会映射到任何内容。
这就是我使用不断重复模式的地方:
List<string> keyValues;
if (!dict.TryGetValue(key, out keyValues))
dict.Add(key, keyValues = new List<string>());
keyValues.Add(aValueForKey);
有更优雅的方法吗?
本问题没有答案的相关问题:
我们对此的看法略有不同,但效果相似:
public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key)
where TValue : new()
{
if (!dict.TryGetValue(key, out TValue val))
{
val = new TValue();
dict.Add(key, val);
}
return val;
}
打电话:
var dictionary = new Dictionary<string, List<int>>();
List<int> numbers = dictionary.GetOrCreate("key");
它使用公共无参数构造函数的通用约束:
where TValue : new()
。
为了帮助发现,除非扩展方法非常特定于某个狭窄的问题,否则我们倾向于将扩展方法放置在它们所扩展的类型的命名空间中,在这种情况下:
namespace System.Collections.Generic
大多数时候,使用该类型的人在顶部定义了
using
语句,因此 IntelliSense 还会找到代码中定义的扩展方法。
Dictionary<>.TryAdd()
。
var dict = new Dictionary<string, string>();
dict.TryAdd("foo", "bar"); // returns bool whether it added or not feel free to ignore.
var myValue = dict["foo"];
与许多编程问题一样,当您发现自己做了很多事情时,请将其重构为方法:
public static void MyAdd<TKey, TCollection, TValue>(
this Dictionary<TKey, TCollection> dictionary, TKey key, TValue value)
where TCollection : ICollection<TValue>, new()
{
TCollection collection;
if (!dictionary.TryGetValue(key, out collection))
{
collection = new TCollection();
dictionary.Add(key, collection);
}
collection.Add(value);
}
这是构造函数需要参数时的解决方案。
public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TValue> createNew)
{
if (!dict.TryGetValue(key, out var val))
{
val = createNew();
dict.Add(key, val);
}
return val;
}
使用简单:
MyDict.GetOrCreate(si.Id, createNew: () => new ObjectKnowingItsId(si.Id))
对于更多的读者,这里有一些我认为合适的每种风格的扩展。如果您需要检查是否添加了值,您也可以使用
out
参数执行某些操作,但我认为您可以使用 containskey
或已有的内容。
您可以使用
GetOrAddNew
检索项目,或创建并将其添加到字典中。您可以使用 GetOrAdd
的各种重载来添加新值。这可能是 default
所以例如NULL
或 0
但您也可以提供 lambda 来为您构造一个对象,并使用您想要的任何类型的构造函数参数。
var x = new Dictionary<string, int>();
var val = x.GetOrAdd("MyKey", (dict, key) => dict.Count + 2);
var val2 = x.GetOrAdd("MyKey", () => Convert.ToInt32("2"));
var val3 = x.GetOrAdd("MyKey", 1);
/// <summary>
/// Extensions for dealing with <see cref="Dictionary{TKey,TValue}"/>
/// </summary>
public static class DictionaryExtensions
{
public static TValue GetOrAddNew<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default)
where TValue : new()
=> dict.GetOrAdd(key, (values, innerKey) => EqualityComparer<TValue>.Default.Equals(default(TValue), defaultValue) ? new TValue() : defaultValue);
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue defaultValue = default)
=> dict.GetOrAdd(key, (values, innerKey) => defaultValue);
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TValue> valueProvider)
=> dict.GetOrAdd(key, (values, innerKey) => valueProvider());
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TKey, TValue> valueProvider)
=> dict.GetOrAdd(key, (values, innerKey) => valueProvider(key));
public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<IDictionary<TKey, TValue>, TKey, TValue> valueProvider)
{
if (dict == null) throw new ArgumentNullException(nameof(dict));
if (key == null) throw new ArgumentNullException(nameof(key));
if (valueProvider == null) throw new ArgumentNullException(nameof(valueProvider));
if (dict.TryGetValue(key, out var foundValue))
return foundValue;
dict[key] = valueProvider(dict, key);
return dict[key];
}
}
那这个呢?
var keyValues = dictionary[key] = dictionary.ContainsKey(key) ? dictionary[key] : new List<string>();
keyValues.Add(aValueForKey);
我缺少
Dictionary
的 GetOrAdd,它确实存在于 ConcurrentDictionary
。
ConcurrentDictionary<int,Guid> Conversion = new ConcurrentDictionary<int, Guid>();
List<int> before = new List<int> { 1, 2, 1, 3 };
List<Guid> after = before.Select(x => Conversion.GetOrAdd(x, Guid.NewGuid())).ToList();
此代码将为每个数字生成 Guid。最终将
before
中的两个 1 转换为相同的 Guid。
您的情况:
ConcurrentDictionary<int, List<String>> dict;
keyValues = dict.GetOrAdd(key, new List<String>());
keyValues.Add(aValueForKey);
using System.Runtime.InteropServices;
public static TValue GetOrCreate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, Func<TValue> valueProvider)
where TKey: notnull
{
ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(dictionary, key, out bool exists);
if (!exists) value = valueProvider.Invoke();
return value!;
}
public static TValue GetOrCreate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue value)
where TKey: notnull
=> GetOrCreate(dictionary, key, () => value);
public static TValue GetOrCreate<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
where TKey: notnull
where TValue : new()
=> GetOrCreate(dictionary, key, new TValue());
避免重复的哈希查找。
您可以在此处找到更多信息。
有一个与问题字面相符的特殊需求,但可能不是其意图。 对于这种情况,获取值的成本很高(反射),并且只想生成一次值,以填充字典进行缓存。 在@adam-houdsworth 答案的基础上,值参数被修改为委托。
public static TValue GetOrCreate<TKey, TValue>(
this IDictionary<TKey, TValue> self,
TKey key,
Func<TValue> getValue)
{
if (self == null)
{
throw new ArgumentNullException(nameof(self));
}
else if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
else if (!self.ContainsKey(key))
{
self[key] = getValue() ;
}
return self[key];
}
var assemblyName = callingAssemblies.GetOrCreate
(
path,
() => Assembly.GetCallingAssembly().GetName().Name
);
好的,不同的方法:
public static bool TryAddValue<TKey,TValue>(this System.Collections.Generic.IDictionary<TKey,List<TValue>> dictionary, TKey key, TValue value)
{
// Null check (useful or not, depending on your null checking approach)
if (value == null)
return false;
List<TValue> tempValue = default(List<TValue>);
try
{
if (!dictionary.TryGetValue(key, out tempValue))
{
dictionary.Add(key, tempValue = new List<TValue>());
}
else
{
// Double null check (useful or not, depending on your null checking approach)
if (tempValue == null)
{
dictionary[key] = (tempValue = new List<TValue>());
}
}
tempValue.Add(value);
return true;
}
catch
{
return false;
}
}
通过这种方式,您必须“尝试将”您的值添加到通用列表(显然可以概括为通用集合),空检查并尝试获取字典中的现有键/值。 用法和示例:
var x = new Dictionary<string,List<string>>();
x.TryAddValue("test", null); // return false due to null value. Doesn't add the key
x.TryAddValue("test", "ok"); // it works adding the key/value
x.TryAddValue("test", "ok again"); // it works adding the value to the existing list
希望有帮助。
只是使用生成器/工厂来获取值的另一个版本,用于昂贵的价值创造:
public static TValue GetOrCreate<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, Func<TValue> factory)
{
if (dict.TryGetValue(key, out var val))
return val;
val = factory();
dict[key] = val;
return val;
}
ConcurrentDictionary.GetOrAdd
完全符合要求。
初始化
ConcurrentDictionary<string, List<string>> dict = new();
用法
var list = dict.GetOrAdd(key, _ => new List<string>());
注意我们如何使用工厂方法根据需要创建新列表。这可以防止在每次调用
GetOrAdd
时预先分配列表。