将字典初始化为自定义默认值

时间:2017-12-13 12:27:41

标签: c# dictionary

我正在尝试创建一个for循环,它在字典中调用类A的几个实例的函数,如果没有键的值,它会创建它然后调用它。 在我看来,似乎必须有一种方法来在首次访问密钥时创建一个值。

我目前正在使用此代码,但我认为这不是最好的做法:

(dictionary[i] = dictionary.ContainsKey(arr[i]) ? dictionary[i] : new A()).Push(10);

C#中是否有针对此类问题的清洁工具?

2 个答案:

答案 0 :(得分:1)

我说 clean 代码看起来像这样:

var key = arr[i];
var hasKey = dictionary.ContainsKey(key);

if (!hasKey)
    dictionary.Add(key, new A());

var itemToUse = dictionary[key];
itemToUse.Push(10);

虽然在我看来你正在寻找更短的。我想你真正要问的是一个简单的方法:

如果密钥存在,则返回给定密钥的值,否则将密钥添加到具有默认值的字典中。

我认为上面的代码更多地讲述了意图,但是如果你想要不同的东西,我可以考虑采用以下两种解决方案。

第一个是获取项目的扩展方法:

public static TValue Get<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue)
{
    var hasKey = dictionary.ContainsKey(key);

    if (!hasKey)
        dictionary.Add(key, defaultValue);

    return dictionary[key];
}

您可以将其用作:

dict.Get(arr[i], defaultValue: new A())
    .Push(10);

我能想到的第二个解决方案是Dictionary的新衍生物:

class DefaultDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
    private readonly Func<TKey, TValue> _defaultValueFactory;

    public DefaultDictionary(TValue defaultValue)
    {
        _defaultValueFactory = new Func<TKey, TValue>(x => defaultValue);
    }

    public DefaultDictionary(Func<TValue> defaultValueFactory)
    {
        _defaultValueFactory = new Func<TKey, TValue>(x => defaultValueFactory()) ?? throw new ArgumentNullException(nameof(defaultValueFactory));
    }

    public DefaultDictionary(Func<TKey, TValue> defaultValueFactory)
    {
        _defaultValueFactory = defaultValueFactory ?? throw new ArgumentNullException(nameof(defaultValueFactory));
    }

    public new TValue this[TKey key]
    {
        get
        {
            var hasKey = ContainsKey(key);

            if (!hasKey)
            {
                var defaultValue = _defaultValueFactory(key);
                Add(key, defaultValue);
            }

            return base[key];
        }
        set
        {
            base[key] = value;
        }
    }
}

使用方法如下:

var dictionary = new DefaultDictionary<string, A>(() => new A());
// ...
dictionary[arr[i]].Push(10);

我必须警告你一些事情,这个Dictionary的派生隐藏了索引操作符。由于使用IDictionary作为成员类型是一种常见做法(例如private IDictionary<string, A> dictionary作为成员),因此您无法在不进行转换的情况下使用重载版本。因此,每次要使用重载索引器时,要么将变量强制转换为DefaultDictionary,要么为此新字典设置接口,如:

interface IDefaultDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    new TValue this[TKey key] { get; set; }
}

让你的成员,变量将它用作定义类型:

private IDefaultDictionary<string, A> dictionary;

但这也意味着作为一个具体的课程,你现在必须使用DefaultDictionary,这是一种权衡。

答案 1 :(得分:1)

ConcurrentDictionary有一个GetOrAdd方法(以及其他有用的方法,如AddOrUpdateTryRemove等)。如果只是一个简单的字典有GetOrAdd你就可以使用它......

幸运的是,您可以在静态类中创建一个扩展方法,您可能应该将其命名为DictionaryExtensions

public static TValue GetOrAdd<TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary,
    TKey key,
    Func<TKey, TValue> valueFactory)
{
    if (dictionary == null)
        throw new ArgumentNullException(nameof(dictionary));
    if (key == null)
        throw new ArgumentNullException(nameof(key));
    if (valueFactory == null)
        throw new ArgumentNullException(nameof(valueFactory));

    if (dictionary.TryGetValue(key, out var existingValue))
        return existingValue;
    var value = valueFactory(key);
    dictionary.Add(key, value);
    return value;
}

如何使用它:

dictionary.GetOrAdd(i, () => new A()).Push(10);

此版本使用值工厂,以便仅在需要时执行new A()。另一个ConcurrentDictionary.GetOrAdd()重载使用作为参数提供的值,您可以将其视为替代值。

我发现像这样创建与ConcurrentDictionary上的方法非常相似的扩展方法非常有用。