51

我经常发现自己创建了一个Dictionary具有重要值的类(例如List),然后在填充数据时总是编写相同的代码模式。

例如:

var dict = new Dictionary<string, List<string>>();
string key = "foo";
string aValueForKey = "bar";

也就是说,我想插入"bar"与 key 对应的列表中"foo",其中 key"foo"可能没有映射到任何东西。

这是我使用不断重复模式的地方:

List<string> keyValues;
if (!dict.TryGetValue(key, out keyValues))
  dict.Add(key, keyValues = new List<string>());
keyValues.Add(aValueForKey);

有没有更优雅的方式来做到这一点?

对此问题没有答案的相关问题:

4

8 回答 8

62

我们对此略有不同的看法,但效果是相似的:

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 也会在您的代码中找到为其定义的扩展方法。

于 2013-04-24T13:29:18.913 回答
5

和很多编程问题一样,当你发现自己做了很多事情时,把它重构为一个方法:

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);
}
于 2013-04-24T13:50:40.800 回答
4

对于更多的读者,这里有一些我认为合适的扩展。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];
        }
    }
于 2020-04-06T18:36:21.657 回答
2

这是构造函数需要参数的解决方案。

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))
于 2020-04-06T14:52:12.390 回答
1

如果您使用 .Net Core,您可以使用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"];
于 2020-01-14T13:51:54.053 回答
0

好的,不同的方法:

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

希望能帮助到你。

于 2013-04-24T14:30:54.820 回答
0

那这个呢?

var keyValues = dictionary[key] = dictionary.ContainsKey(key) ? dictionary[key] : new List<string>();
keyValues.Add(aValueForKey);
于 2014-06-26T12:19:21.583 回答
0

我缺少 的 GetOrAdd Dictionary,它确实存在于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。最终将两个 1 转换before为相同的 Guid。

在你的情况下:

ConcurrentDictionary<int, List<String>> dict;
keyValues = dict.GetOrAdd(key, new List<String>());
keyValues.Add(aValueForKey);
于 2020-06-09T12:06:16.420 回答